Add syncing, tests

This commit is contained in:
Eric Bailey
2025-08-05 11:51:25 -05:00
parent e70fccc14f
commit c877626db6
6 changed files with 328 additions and 16 deletions
@@ -0,0 +1,195 @@
import {describe, test} from '@jest/globals'
import {
computeCompletedState,
syncCompletedState,
} from '#/components/dialogs/BlockingAnnouncements/useAnnouncementState'
jest.mock('../../../../state/queries/nuxs')
describe('computeCompletedState', () => {
test(`initial state`, () => {
const completed = computeCompletedState({
nuxIsReady: false,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: false,
completedForDevice: undefined,
})
expect(completed).toBe(true)
})
test(`nux loaded state`, () => {
const completed = computeCompletedState({
nuxIsReady: true,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: false,
completedForDevice: undefined,
})
expect(completed).toBe(false)
})
test(`nux saving state`, () => {
const completed = computeCompletedState({
nuxIsReady: true,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: true,
completedForDevice: undefined,
})
expect(completed).toBe(true)
})
test(`nux is completed`, () => {
const completed = computeCompletedState({
nuxIsReady: true,
nuxIsCompleted: true,
nuxIsOptimisticallyCompleted: false,
completedForDevice: undefined,
})
expect(completed).toBe(true)
})
test(`initial state, but already completed for device`, () => {
const completed = computeCompletedState({
nuxIsReady: false,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: false,
completedForDevice: true,
})
expect(completed).toBe(true)
})
})
describe('syncCompletedState', () => {
describe('!nuxIsReady', () => {
test(`!completedForDevice, no-op`, () => {
const save = jest.fn()
const setCompletedForDevice = jest.fn()
syncCompletedState({
nuxIsReady: false,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: false,
completedForDevice: false,
save,
setCompletedForDevice,
})
expect(save).not.toHaveBeenCalled()
expect(setCompletedForDevice).not.toHaveBeenCalled()
})
test(`completedForDevice, no-op`, () => {
const save = jest.fn()
const setCompletedForDevice = jest.fn()
syncCompletedState({
nuxIsReady: false,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: false,
completedForDevice: true,
save,
setCompletedForDevice,
})
expect(save).not.toHaveBeenCalled()
expect(setCompletedForDevice).not.toHaveBeenCalled()
})
})
describe('nuxIsReady', () => {
describe(`!nuxIsCompleted`, () => {
describe(`!nuxIsOptimisticallyCompleted`, () => {
test(`!completedForDevice, no-op`, () => {
const save = jest.fn()
const setCompletedForDevice = jest.fn()
syncCompletedState({
nuxIsReady: true,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: false,
completedForDevice: false,
save,
setCompletedForDevice,
})
expect(save).not.toHaveBeenCalled()
expect(setCompletedForDevice).not.toHaveBeenCalled()
})
test(`completedForDevice, syncs to server`, () => {
const save = jest.fn()
const setCompletedForDevice = jest.fn()
syncCompletedState({
nuxIsReady: true,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: false,
completedForDevice: true,
save,
setCompletedForDevice,
})
expect(save).toHaveBeenCalled()
expect(setCompletedForDevice).not.toHaveBeenCalled()
})
})
/**
* Catches the case where we already called `save` to sync device state
* to server, thus `nuxIsOptimisticallyCompleted` is true.
*/
describe(`nuxIsOptimisticallyCompleted`, () => {
test(`completedForDevice, no-op`, () => {
const save = jest.fn()
const setCompletedForDevice = jest.fn()
syncCompletedState({
nuxIsReady: true,
nuxIsCompleted: false,
nuxIsOptimisticallyCompleted: true,
completedForDevice: true,
save,
setCompletedForDevice,
})
expect(save).not.toHaveBeenCalled()
expect(setCompletedForDevice).not.toHaveBeenCalled()
})
})
})
describe(`nuxIsCompleted`, () => {
test(`!completedForDevice, syncs to device`, () => {
const save = jest.fn()
const setCompletedForDevice = jest.fn()
syncCompletedState({
nuxIsReady: true,
nuxIsCompleted: true,
nuxIsOptimisticallyCompleted: false,
completedForDevice: false,
save,
setCompletedForDevice,
})
expect(save).not.toHaveBeenCalled()
expect(setCompletedForDevice).toHaveBeenCalled()
})
test(`completedForDevice, no-op`, () => {
const save = jest.fn()
const setCompletedForDevice = jest.fn()
syncCompletedState({
nuxIsReady: true,
nuxIsCompleted: true,
nuxIsOptimisticallyCompleted: false,
completedForDevice: true,
save,
setCompletedForDevice,
})
expect(save).not.toHaveBeenCalled()
expect(setCompletedForDevice).not.toHaveBeenCalled()
})
})
})
})
@@ -0,0 +1,3 @@
import {Nux} from '#/state/queries/nuxs'
export const ACTIVE_ANNOUNCEMENT = Nux.BlockingAnnouncementPolicyUpdate202508
@@ -1,6 +1,5 @@
import {View} from 'react-native'
import {Nux} from '#/state/queries/nuxs'
import {atoms as a} from '#/alf'
import {Announcement} from '#/components/dialogs/BlockingAnnouncements/announcements/PolicyUpdate202508'
import {useAnnouncementState} from '#/components/dialogs/BlockingAnnouncements/useAnnouncementState'
@@ -13,9 +12,7 @@ export const Portal = portalGroup.Portal
export const Outlet = portalGroup.Outlet
export function BlockingAnnouncements() {
const state = useAnnouncementState({
id: Nux.BlockingAnnouncementPolicyUpdate202508,
})
const state = useAnnouncementState()
/*
* See `window.clearNux` example in `/state/queries/nuxs` for a way to clear
@@ -1,34 +1,119 @@
import {useMemo} from 'react'
import {type Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
import {useNux, useSaveNux} from '#/state/queries/nuxs'
import {ACTIVE_ANNOUNCEMENT} from '#/components/dialogs/BlockingAnnouncements/config'
import {device, useStorage} from '#/storage'
export type AnnouncementState = {
completed: boolean
complete: () => void
}
export function useAnnouncementState({id}: {id: Nux}) {
const nux = useNux(id)
export function useAnnouncementState() {
const nux = useNux(ACTIVE_ANNOUNCEMENT)
const {mutate: save, variables} = useSaveNux()
const deviceStorage = useStorage(device, [ACTIVE_ANNOUNCEMENT])
return useMemo(() => {
/**
* Until data has loaded, assumed completed
*/
let completed = nux.status === 'ready' ? nux.nux?.completed === true : true
const nuxIsReady = nux.status === 'ready'
const nuxIsCompleted = nux.nux?.completed === true
const nuxIsOptimisticallyCompleted = !!variables?.completed
const [completedForDevice, setCompletedForDevice] = deviceStorage
if (variables?.completed) {
completed = true
}
const completed = computeCompletedState({
nuxIsReady,
nuxIsCompleted,
nuxIsOptimisticallyCompleted,
completedForDevice,
})
syncCompletedState({
nuxIsReady,
nuxIsCompleted,
nuxIsOptimisticallyCompleted,
completedForDevice,
save,
setCompletedForDevice,
})
return {
completed,
complete() {
save({
id,
id: ACTIVE_ANNOUNCEMENT,
completed: true,
data: undefined,
})
},
}
}, [id, nux, save, variables])
}, [nux, save, variables, deviceStorage])
}
export function computeCompletedState({
nuxIsReady,
nuxIsCompleted,
nuxIsOptimisticallyCompleted,
completedForDevice,
}: {
nuxIsReady: boolean
nuxIsCompleted: boolean
nuxIsOptimisticallyCompleted: boolean
completedForDevice: boolean | undefined
}): boolean {
/**
* Assume completed to prevent flash
*/
let completed = true
/**
* Prefer server state, if available
*/
if (nuxIsReady) {
completed = nuxIsCompleted
}
/**
* Override with optimistic state or device state
*/
if (nuxIsOptimisticallyCompleted || !!completedForDevice) {
completed = true
}
return completed
}
export function syncCompletedState({
nuxIsReady,
nuxIsCompleted,
nuxIsOptimisticallyCompleted,
completedForDevice,
save,
setCompletedForDevice,
}: {
nuxIsReady: boolean
nuxIsCompleted: boolean
nuxIsOptimisticallyCompleted: boolean
completedForDevice: boolean | undefined
save: ReturnType<typeof useSaveNux>['mutate']
setCompletedForDevice: (value: boolean) => void
}) {
/*
* Sync device state to server state for this account
*/
if (
nuxIsReady &&
!nuxIsCompleted &&
!nuxIsOptimisticallyCompleted &&
!!completedForDevice
) {
save({
id: ACTIVE_ANNOUNCEMENT,
completed: true,
data: undefined,
})
} else if (nuxIsReady && nuxIsCompleted && !completedForDevice) {
/*
* Sync server state to device state
*/
setCompletedForDevice(true)
}
}
+25
View File
@@ -0,0 +1,25 @@
import {jest} from '@jest/globals'
export {Nux} from '#/state/queries/nuxs/definitions'
export const useNuxs = jest.fn(() => {
return {
nuxs: undefined,
status: 'loading' as const,
}
})
export const useNux = jest.fn((id: string) => {
return {
nux: undefined,
status: 'loading' as const,
}
})
export const useSaveNux = jest.fn(() => {
return {}
})
export const useResetNuxs = jest.fn(() => {
return {}
})
+7
View File
@@ -1,3 +1,5 @@
import {type Nux} from '#/state/queries/nuxs'
/**
* Device data that's specific to the device and does not vary based account
*/
@@ -13,6 +15,11 @@ export type Device = {
devMode: boolean
demoMode: boolean
activitySubscriptionsNudged?: boolean
/**
* Blocking announcements. New IDs are required for each new announcement.
*/
[Nux.BlockingAnnouncementPolicyUpdate202508]?: boolean
}
export type Account = {