Files
sky_phone/frontend/src/stores/phone-persistence.test.ts
T

171 lines
5.2 KiB
TypeScript

import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { usePhoneStore } from '@/stores/phone'
import { nuiCall, type NuiResponse } from '@/utils/nui'
vi.mock('@/utils/nui', () => ({
nuiCall: vi.fn(),
}))
const mockNuiCall = vi.mocked(nuiCall)
function deferredResponse<T>(): {
promise: Promise<NuiResponse<T>>
resolve: (response: NuiResponse<T>) => void
} {
let resolve!: (response: NuiResponse<T>) => void
const promise = new Promise<NuiResponse<T>>((next) => {
resolve = next
})
return { promise, resolve }
}
function openPhone(imei: string, token: string, revision: number): void {
usePhoneStore().open({
device: {
data: { settings: { payload: {}, revision } },
imei,
name: `Phone ${imei}`,
sim: null,
},
token,
})
}
describe('phone device persistence scope', () => {
beforeEach(() => {
vi.stubGlobal('window', {
matchMedia: vi.fn(() => ({ matches: false })),
})
setActivePinia(createPinia())
mockNuiCall.mockReset()
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('does not apply a late save response to a newer device session', async () => {
const stale = deferredResponse<{ revision: number }>()
mockNuiCall.mockReturnValueOnce(stale.promise)
const phone = usePhoneStore()
openPhone('111', 'session-a', 2)
phone.saveDeviceNamespace('settings', { value: 'old' })
await Promise.resolve()
expect(mockNuiCall).toHaveBeenCalledWith('device:save', {
imei: '111',
namespace: 'settings',
payload: { value: 'old' },
revision: 2,
sessionToken: 'session-a',
})
openPhone('222', 'session-b', 7)
stale.resolve({ data: { revision: 3 }, success: true })
await stale.promise
await Promise.resolve()
expect(phone.device?.imei).toBe('222')
expect(phone.deviceRevisions.settings).toBe(7)
})
it('drops queued writes from an obsolete device generation', async () => {
const first = deferredResponse<{ revision: number }>()
mockNuiCall.mockReturnValueOnce(first.promise)
const phone = usePhoneStore()
openPhone('111', 'session-a', 0)
phone.saveDeviceNamespace('settings', { order: 1 })
phone.saveDeviceNamespace('settings', { order: 2 })
await Promise.resolve()
openPhone('222', 'session-b', 0)
first.resolve({ data: { revision: 1 }, success: true })
await first.promise
await Promise.resolve()
await Promise.resolve()
expect(mockNuiCall).toHaveBeenCalledTimes(1)
})
it('flushes every queued write after a normal visibility close', async () => {
const first = deferredResponse<{ revision: number }>()
mockNuiCall
.mockReturnValueOnce(first.promise)
.mockResolvedValueOnce({ data: { revision: 2 }, success: true })
const phone = usePhoneStore()
openPhone('111', 'session-a', 0)
phone.saveDeviceNamespace('settings', { order: 1 })
phone.saveDeviceNamespace('settings', { order: 2 })
await Promise.resolve()
phone.close()
const flushed = phone.flushDevicePersistence()
first.resolve({ data: { revision: 1 }, success: true })
await flushed
expect(mockNuiCall).toHaveBeenCalledTimes(2)
expect(mockNuiCall).toHaveBeenLastCalledWith('device:save', {
imei: '111',
namespace: 'settings',
payload: { order: 2 },
revision: 1,
sessionToken: 'session-a',
})
expect(phone.deviceRevisions.settings).toBe(2)
})
it('keeps queued writes scoped across a same-session bootstrap update', async () => {
const first = deferredResponse<{ revision: number }>()
mockNuiCall
.mockReturnValueOnce(first.promise)
.mockResolvedValueOnce({ data: { revision: 2 }, success: true })
const phone = usePhoneStore()
openPhone('111', 'session-a', 0)
phone.saveDeviceNamespace('settings', { order: 1 })
phone.saveDeviceNamespace('settings', { order: 2 })
await Promise.resolve()
first.resolve({ data: { revision: 1 }, success: true })
await first.promise
await Promise.resolve()
openPhone('111', 'session-a', 1)
await phone.flushDevicePersistence()
expect(mockNuiCall).toHaveBeenCalledTimes(2)
expect(phone.deviceRevisions.settings).toBe(2)
})
it('waits for writes queued while a persistence flush is in progress', async () => {
const first = deferredResponse<{ revision: number }>()
const queuedDuringFlush = deferredResponse<{ revision: number }>()
mockNuiCall
.mockReturnValueOnce(first.promise)
.mockReturnValueOnce(queuedDuringFlush.promise)
const phone = usePhoneStore()
openPhone('111', 'session-a', 0)
phone.saveDeviceNamespace('settings', { order: 1 })
await Promise.resolve()
let flushCompleted = false
const flushed = phone.flushDevicePersistence().then(() => {
flushCompleted = true
})
phone.saveDeviceNamespace('widgets', { order: 2 })
await Promise.resolve()
first.resolve({ data: { revision: 1 }, success: true })
await first.promise
await Promise.resolve()
expect(flushCompleted).toBe(false)
queuedDuringFlush.resolve({ data: { revision: 1 }, success: true })
await flushed
expect(mockNuiCall).toHaveBeenCalledTimes(2)
expect(phone.deviceRevisions.widgets).toBe(1)
})
})