fix(identity): sync default sender identity per account (#507)
The default sender identity (`preferredPrimaryId`) lived only in the browser-local `identity-storage` store and was never written to the synced settings, so the choice was lost on clearing site data / switching browsers and never appeared in exported settings. Persist it 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. This supersedes the earlier username-keyed fix that had landed on main: the username-keyed map, `loadIdentities()` fallback write, and the `applyPreferredIdentityOrdering` store action (plus its settings-store hook) are removed so a single account-keyed mechanism remains. - settings-store: `preferredIdentityIds` (accountId -> identityId) in state, defaults, export, import (non-record guard), rehydrate coercion, v6 migration. - auth-store: `applyPreferredIdentity(accountId?)` reorders the active account's identities once synced settings load, and performs the one-time migration of the pre-#507 browser-local default into the synced map (keyed by accountId). Invoked from every `loadFromServer().finally()` (login / OAuth / SSO / switch / restore). `loadIdentities()` now only applies the local fallback ordering. - identity-manager-modal: the star action writes the choice by `activeAccountId`. - identity-store: `preferredPrimaryId` kept as a local (sync-off) fallback. - tests: per-account independence, export/import round-trip, import guard, and applyPreferredIdentity reorder / active-account gating / local-default migration.
This commit is contained in:
committed by
Linus Rath
parent
20d02214df
commit
01e5cd69cf
@@ -0,0 +1,58 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useSettingsStore } 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' });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user