feat(PBI-76): one-shot localStorage→user-settings migration helper
Reads all legacy keys (sprint_pb_*, pbi_*, story_sort, debug-mode, and dynamic *_filter_kind/*_filter_status for jobs columns) and returns a typed UserSettings patch plus the keys to clear. Idempotent via scrum4me:settings_migrated=v1 marker. Skips invalid values silently so existing corrupt entries do not block migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a0e5867857
commit
e2ecb788e5
2 changed files with 332 additions and 0 deletions
106
__tests__/lib/user-settings-migration.test.ts
Normal file
106
__tests__/lib/user-settings-migration.test.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
buildMigrationPatch,
|
||||
clearLegacyLocalStorage,
|
||||
} from '@/lib/user-settings-migration'
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
describe('buildMigrationPatch', () => {
|
||||
it('returns no data when nothing is stored', () => {
|
||||
const result = buildMigrationPatch()
|
||||
expect(result.hasData).toBe(false)
|
||||
expect(result.patch).toEqual({})
|
||||
expect(result.legacyKeys).toEqual([])
|
||||
})
|
||||
|
||||
it('skips after marker is set', () => {
|
||||
localStorage.setItem('scrum4me:sprint_pb_filter_status', 'all')
|
||||
localStorage.setItem('scrum4me:settings_migrated', 'v1')
|
||||
const result = buildMigrationPatch()
|
||||
expect(result.hasData).toBe(false)
|
||||
})
|
||||
|
||||
it('extracts sprint backlog prefs into nested patch', () => {
|
||||
localStorage.setItem('scrum4me:sprint_pb_filter_status', 'all')
|
||||
localStorage.setItem('scrum4me:sprint_pb_sort', 'priority')
|
||||
localStorage.setItem('scrum4me:sprint_pb_sort_dir', 'desc')
|
||||
localStorage.setItem('scrum4me:sprint_pb_collapsed', JSON.stringify(['pbi-1', 'pbi-2']))
|
||||
localStorage.setItem('scrum4me:sprint_pb_filter_popover_open', 'true')
|
||||
|
||||
const result = buildMigrationPatch()
|
||||
|
||||
expect(result.hasData).toBe(true)
|
||||
expect(result.patch.views?.sprintBacklog).toEqual({
|
||||
filterStatus: 'all',
|
||||
sort: 'priority',
|
||||
sortDir: 'desc',
|
||||
collapsedPbis: ['pbi-1', 'pbi-2'],
|
||||
filterPopoverOpen: true,
|
||||
})
|
||||
expect(result.legacyKeys).toContain('scrum4me:sprint_pb_filter_status')
|
||||
expect(result.legacyKeys).toContain('scrum4me:sprint_pb_collapsed')
|
||||
})
|
||||
|
||||
it('extracts pbi-list prefs', () => {
|
||||
localStorage.setItem('scrum4me:pbi_sort', 'date')
|
||||
localStorage.setItem('scrum4me:pbi_filter_priority', '2')
|
||||
|
||||
const result = buildMigrationPatch()
|
||||
expect(result.patch.views?.pbiList).toEqual({ sort: 'date', filterPriority: 2 })
|
||||
})
|
||||
|
||||
it('extracts story_sort', () => {
|
||||
localStorage.setItem('scrum4me:story_sort', 'code')
|
||||
const result = buildMigrationPatch()
|
||||
expect(result.patch.views?.storyPanel).toEqual({ sort: 'code' })
|
||||
})
|
||||
|
||||
it('extracts debug-mode', () => {
|
||||
localStorage.setItem('scrum4me:debug-mode', 'true')
|
||||
const result = buildMigrationPatch()
|
||||
expect(result.patch.devTools).toEqual({ debugMode: true })
|
||||
})
|
||||
|
||||
it('extracts jobs-column dynamic prefixes from CSV values', () => {
|
||||
localStorage.setItem('queue_filter_kind', 'TASK_IMPLEMENTATION,SPRINT_IMPLEMENTATION')
|
||||
localStorage.setItem('queue_filter_status', 'queued,running')
|
||||
|
||||
const result = buildMigrationPatch()
|
||||
expect(result.patch.views?.jobsColumns?.['queue']).toEqual({
|
||||
kinds: ['TASK_IMPLEMENTATION', 'SPRINT_IMPLEMENTATION'],
|
||||
statuses: ['queued', 'running'],
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores invalid enum values', () => {
|
||||
localStorage.setItem('scrum4me:sprint_pb_filter_status', 'BOGUS')
|
||||
const result = buildMigrationPatch()
|
||||
expect(result.hasData).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('clearLegacyLocalStorage', () => {
|
||||
it('removes given keys and sets the marker', () => {
|
||||
localStorage.setItem('scrum4me:sprint_pb_sort', 'code')
|
||||
localStorage.setItem('scrum4me:pbi_sort', 'priority')
|
||||
|
||||
clearLegacyLocalStorage(['scrum4me:sprint_pb_sort', 'scrum4me:pbi_sort'])
|
||||
|
||||
expect(localStorage.getItem('scrum4me:sprint_pb_sort')).toBeNull()
|
||||
expect(localStorage.getItem('scrum4me:pbi_sort')).toBeNull()
|
||||
expect(localStorage.getItem('scrum4me:settings_migrated')).toBe('v1')
|
||||
})
|
||||
|
||||
it('sets marker even with empty keys list (no-op migration)', () => {
|
||||
clearLegacyLocalStorage([])
|
||||
expect(localStorage.getItem('scrum4me:settings_migrated')).toBe('v1')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue