Files
SRCmail/stores/__tests__/settings-store-preferred-identity.test.ts
T
Stefan Hildebrandt 034f7a4b9b fix(identity): sync default sender identity per account (#507)
The default sender identity (`preferredPrimaryId`) lived only in the
browser-local `identity-storage` Zustand store and was never written to
the server-side synced settings. As a result the choice was lost when
clearing site data or switching browsers, and never appeared in the
exported settings JSON.

Persist the default identity in the synced settings store, keyed per
account (`preferredIdentityIds: Record<accountId, identityId>`), mirroring
the existing per-account `allMailFolderIds`. Per-account keying is required
because JMAP identity ids are account-scoped and would otherwise collide
across accounts / the unified mailbox.

- settings-store: add `preferredIdentityIds` to state, defaults, export
  (so it shows in exported JSON), import (with a non-record guard),
  rehydrate coercion, and a v6->v7 migration.
- auth-store: add `applyPreferredIdentity()`, invoked in every
  `loadFromServer().finally()` (login / OAuth / SSO / switch / restore) so
  the synced default reorders the active account's identities once server
  settings load (the composer defaults From to identities[0]).
- identity-manager-modal: the star action also writes the choice to the
  synced per-account map, triggering server sync + export inclusion.
- identity-store: keep `preferredPrimaryId` in local persist as a sync-off
  fallback; synced settings are the durable cross-device source of truth.
- tests: per-account independence, export/import round-trip, non-record
  import guard, and v6->v7 migration.
2026-07-11 21:14:56 +02:00

82 lines
3.3 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { useSettingsStore, migrateSettings } from '../settings-store';
describe('settings-store per-account preferredIdentityIds (issue #507)', () => {
beforeEach(() => {
useSettingsStore.setState({ preferredIdentityIds: {} });
});
it('defaults to an empty record (no account has a synced default)', () => {
expect(useSettingsStore.getState().preferredIdentityIds).toEqual({});
});
it('keeps each account default independent', () => {
useSettingsStore.setState({
preferredIdentityIds: { 'acct-1': 'b', 'acct-2': 'c' },
});
const map = useSettingsStore.getState().preferredIdentityIds;
expect(map['acct-1']).toBe('b');
expect(map['acct-2']).toBe('c');
expect(map['acct-3']).toBeUndefined();
});
it('round-trips through export -> import so the choice survives clearing site data', () => {
useSettingsStore.setState({ preferredIdentityIds: { 'acct-1': 'b' } });
const json = useSettingsStore.getState().exportSettings();
// Appears in exported JSON (issue #507 acceptance criterion).
expect(JSON.parse(json).preferredIdentityIds).toEqual({ 'acct-1': 'b' });
// Simulate a fresh browser: clear, then import the exported settings.
useSettingsStore.setState({ preferredIdentityIds: {} });
expect(useSettingsStore.getState().importSettings(json)).toBe(true);
expect(useSettingsStore.getState().preferredIdentityIds).toEqual({ 'acct-1': 'b' });
});
describe('importSettings non-record guard', () => {
it('ignores a legacy array shape', () => {
useSettingsStore.setState({ preferredIdentityIds: { 'acct-1': 'b' } });
const ok = useSettingsStore.getState().importSettings(
JSON.stringify({ preferredIdentityIds: ['b'] }),
);
expect(ok).toBe(true);
expect(useSettingsStore.getState().preferredIdentityIds).toEqual({ 'acct-1': 'b' });
});
it('ignores a null value', () => {
useSettingsStore.setState({ preferredIdentityIds: { 'acct-1': 'b' } });
useSettingsStore.getState().importSettings(JSON.stringify({ preferredIdentityIds: null }));
expect(useSettingsStore.getState().preferredIdentityIds).toEqual({ 'acct-1': 'b' });
});
it('accepts a proper per-account record', () => {
useSettingsStore.getState().importSettings(
JSON.stringify({ preferredIdentityIds: { 'acct-9': 'a' } }),
);
expect(useSettingsStore.getState().preferredIdentityIds).toEqual({ 'acct-9': 'a' });
});
});
describe('migrateSettings v6 -> v7', () => {
it('adds an empty preferredIdentityIds map for pre-v7 users', () => {
const out = migrateSettings({ allMailFolderIds: {} }, 6) as unknown as Record<string, unknown>;
expect(out.preferredIdentityIds).toEqual({});
});
it('coerces a non-record preferredIdentityIds to an empty map', () => {
const out = migrateSettings(
{ allMailFolderIds: {}, preferredIdentityIds: ['b'] },
7,
) as unknown as Record<string, unknown>;
expect(out.preferredIdentityIds).toEqual({});
});
it('preserves a valid per-account map across migration', () => {
const out = migrateSettings(
{ allMailFolderIds: {}, preferredIdentityIds: { 'acct-1': 'b' } },
7,
) as unknown as Record<string, unknown>;
expect(out.preferredIdentityIds).toEqual({ 'acct-1': 'b' });
});
});
});