conversationsSidebarSlice.migration.test.ts8.1 KBView on GitHub import { migrateV1Persisted, readPersisted } from '@/modules/conversationsPage/slice/conversationsSidebarSlice';
describe('conversationsSidebarSlice — v1 → v2 migration', () => {
describe('migrateV1Persisted', () => {
test('groupBy: starredAndResponse → sentinel', () => {
const out = migrateV1Persisted({
groupBy: 'starredAndResponse',
sortBy: 'lastActivity',
filters: {},
});
expect(out.schema).toBe(2);
expect(out.groupBy).toEqual({ kind: 'sentinel', value: 'starredAndResponse' });
});
test('groupBy: awaitingResponse / none → sentinel', () => {
expect(migrateV1Persisted({ groupBy: 'awaitingResponse' }).groupBy).toEqual({
kind: 'sentinel',
value: 'awaitingResponse',
});
expect(migrateV1Persisted({ groupBy: 'none' }).groupBy).toEqual({
kind: 'sentinel',
value: 'none',
});
});
test('groupBy: status / priority → column', () => {
expect(migrateV1Persisted({ groupBy: 'status' }).groupBy).toEqual({
kind: 'column',
columnId: 'status',
});
expect(migrateV1Persisted({ groupBy: 'priority' }).groupBy).toEqual({
kind: 'column',
columnId: 'priority',
});
});
test('groupBy: owner → primaryUser column, aop → aopId column', () => {
expect(migrateV1Persisted({ groupBy: 'owner' }).groupBy).toEqual({
kind: 'column',
columnId: 'primaryUser',
});
expect(migrateV1Persisted({ groupBy: 'aop' }).groupBy).toEqual({
kind: 'column',
columnId: 'aopId',
});
});
test('groupBy: legacy "starred" collapses to starredAndResponse sentinel', () => {
expect(migrateV1Persisted({ groupBy: 'starred' }).groupBy).toEqual({
kind: 'sentinel',
value: 'starredAndResponse',
});
});
test('sortBy: lastActivity → lastContactedAt desc, priority 0', () => {
const out = migrateV1Persisted({ groupBy: 'none', sortBy: 'lastActivity', filters: {} });
expect(out.columns.lastContactedAt?.sort).toEqual({
active: true,
direction: 'desc',
priority: 0,
});
});
test('sortBy: dueDateAsc → nextStepDate asc', () => {
const out = migrateV1Persisted({ groupBy: 'none', sortBy: 'dueDateAsc', filters: {} });
expect(out.columns.nextStepDate?.sort).toEqual({
active: true,
direction: 'asc',
priority: 0,
});
});
test('sortBy: name / dealValueDesc map to name asc and dealValue desc', () => {
expect(
migrateV1Persisted({ groupBy: 'none', sortBy: 'name' }).columns.name?.sort?.direction,
).toBe('asc');
expect(
migrateV1Persisted({ groupBy: 'none', sortBy: 'dealValueDesc' }).columns.dealValue?.sort
?.direction,
).toBe('desc');
});
test('filters.status → columns.status.filter.selected', () => {
const out = migrateV1Persisted({
groupBy: 'none',
filters: { status: ['active', 'qualification'] },
});
expect(out.columns.status?.filter?.selected).toEqual(['active', 'qualification']);
});
test('filters.priority + filters.aopIds + hasOpenTasks survive', () => {
const out = migrateV1Persisted({
groupBy: 'none',
filters: {
priority: ['high'],
aopIds: ['aop-1', 'aop-2'],
hasOpenTasks: true,
},
});
expect(out.columns.priority?.filter?.selected).toEqual(['high']);
expect(out.columns.aopId?.filter?.selected).toEqual(['aop-1', 'aop-2']);
expect(out.columns.currentTasks?.filter?.binaryChoice).toBe('true');
});
test('filters.onlyStarred + filters.hasSlackChannel intentionally dropped (no server-side equivalent)', () => {
const out = migrateV1Persisted({
groupBy: 'none',
filters: { onlyStarred: true, hasSlackChannel: true },
});
// Neither produces a columns entry.
expect(Object.keys(out.columns)).toHaveLength(0);
});
test('collapsedGroupKeys is preserved', () => {
const out = migrateV1Persisted({
groupBy: 'none',
collapsedGroupKeys: { foo: true, bar: false },
});
expect(out.collapsedGroupKeys).toEqual({ foo: true, bar: false });
});
test('unknown groupBy falls back to default sentinel', () => {
expect(migrateV1Persisted({ groupBy: 'unknown-thing' as unknown }).groupBy).toEqual({
kind: 'sentinel',
value: 'starredAndResponse',
});
});
});
describe('readPersisted', () => {
const STORAGE_KEY=[redacted];
beforeEach(() => {
window.localStorage.clear();
});
test('returns defaults when nothing is stored', () => {
const out = readPersisted();
expect(out.schema).toBe(2);
expect(out.groupBy).toEqual({ kind: 'sentinel', value: 'starredAndResponse' });
expect(out.columns).toEqual({});
expect(out.collapsedGroupKeys).toEqual({});
});
test('round-trips a v2 blob', () => {
const blob = {
schema: 2,
groupBy: { kind: 'column', columnId: 'status' },
columns: { status: { filter: { selected: ['active'] } } },
collapsedGroupKeys: { x: true },
};
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(blob));
const out = readPersisted();
expect(out.groupBy).toEqual({ kind: 'column', columnId: 'status' });
expect(out.columns.status?.filter?.selected).toEqual(['active']);
expect(out.collapsedGroupKeys).toEqual({ x: true });
});
test('migrates a v1 blob (no schema field) end-to-end', () => {
const v1 = {
groupBy: 'status',
sortBy: 'name',
filters: { status: ['active'], hasOpenTasks: true },
collapsedGroupKeys: { foo: true },
};
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(v1));
const out = readPersisted();
expect(out.schema).toBe(2);
expect(out.groupBy).toEqual({ kind: 'column', columnId: 'status' });
expect(out.columns.status?.filter?.selected).toEqual(['active']);
expect(out.columns.name?.sort).toEqual({ active: true, direction: 'asc', priority: 0 });
expect(out.columns.currentTasks?.filter?.binaryChoice).toBe('true');
expect(out.collapsedGroupKeys).toEqual({ foo: true });
});
test('returns defaults on JSON parse error', () => {
window.localStorage.setItem(STORAGE_KEY, '{not-json');
const out = readPersisted();
expect(out.groupBy).toEqual({ kind: 'sentinel', value: 'starredAndResponse' });
});
test('rewrites legacy sortDimension "strength" → "signal" on read', () => {
const blob = {
schema: 2,
groupBy: { kind: 'sentinel', value: 'none' },
columns: {
wm_score: {
sort: { active: true, direction: 'desc', priority: 0, sortDimension: 'strength' },
},
},
collapsedGroupKeys: {},
};
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(blob));
const out = readPersisted();
expect(out.columns.wm_score?.sort?.sortDimension).toBe('signal');
});
test('drops an unrecognized sortDimension so the value default applies', () => {
const blob = {
schema: 2,
groupBy: { kind: 'sentinel', value: 'none' },
columns: {
wm_score: {
sort: { active: true, direction: 'asc', priority: 0, sortDimension: 'bogus' },
},
},
collapsedGroupKeys: {},
};
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(blob));
const out = readPersisted();
expect(out.columns.wm_score?.sort?.sortDimension).toBeUndefined();
});
test('leaves valid sortDimension untouched', () => {
const blob = {
schema: 2,
groupBy: { kind: 'sentinel', value: 'none' },
columns: {
wm_score: {
sort: { active: true, direction: 'desc', priority: 0, sortDimension: 'signal' },
},
},
collapsedGroupKeys: {},
};
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(blob));
const out = readPersisted();
expect(out.columns.wm_score?.sort?.sortDimension).toBe('signal');
});
});
});