Add syncing, tests
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
})
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user