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 {View} from 'react-native'
|
||||||
|
|
||||||
import {Nux} from '#/state/queries/nuxs'
|
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Announcement} from '#/components/dialogs/BlockingAnnouncements/announcements/PolicyUpdate202508'
|
import {Announcement} from '#/components/dialogs/BlockingAnnouncements/announcements/PolicyUpdate202508'
|
||||||
import {useAnnouncementState} from '#/components/dialogs/BlockingAnnouncements/useAnnouncementState'
|
import {useAnnouncementState} from '#/components/dialogs/BlockingAnnouncements/useAnnouncementState'
|
||||||
@@ -13,9 +12,7 @@ export const Portal = portalGroup.Portal
|
|||||||
export const Outlet = portalGroup.Outlet
|
export const Outlet = portalGroup.Outlet
|
||||||
|
|
||||||
export function BlockingAnnouncements() {
|
export function BlockingAnnouncements() {
|
||||||
const state = useAnnouncementState({
|
const state = useAnnouncementState()
|
||||||
id: Nux.BlockingAnnouncementPolicyUpdate202508,
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* See `window.clearNux` example in `/state/queries/nuxs` for a way to clear
|
* See `window.clearNux` example in `/state/queries/nuxs` for a way to clear
|
||||||
|
|||||||
@@ -1,34 +1,119 @@
|
|||||||
import {useMemo} from 'react'
|
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 = {
|
export type AnnouncementState = {
|
||||||
completed: boolean
|
completed: boolean
|
||||||
complete: () => void
|
complete: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAnnouncementState({id}: {id: Nux}) {
|
export function useAnnouncementState() {
|
||||||
const nux = useNux(id)
|
const nux = useNux(ACTIVE_ANNOUNCEMENT)
|
||||||
const {mutate: save, variables} = useSaveNux()
|
const {mutate: save, variables} = useSaveNux()
|
||||||
|
const deviceStorage = useStorage(device, [ACTIVE_ANNOUNCEMENT])
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
/**
|
const nuxIsReady = nux.status === 'ready'
|
||||||
* Until data has loaded, assumed completed
|
const nuxIsCompleted = nux.nux?.completed === true
|
||||||
*/
|
const nuxIsOptimisticallyCompleted = !!variables?.completed
|
||||||
let completed = nux.status === 'ready' ? nux.nux?.completed === true : true
|
const [completedForDevice, setCompletedForDevice] = deviceStorage
|
||||||
|
|
||||||
if (variables?.completed) {
|
const completed = computeCompletedState({
|
||||||
completed = true
|
nuxIsReady,
|
||||||
}
|
nuxIsCompleted,
|
||||||
|
nuxIsOptimisticallyCompleted,
|
||||||
|
completedForDevice,
|
||||||
|
})
|
||||||
|
|
||||||
|
syncCompletedState({
|
||||||
|
nuxIsReady,
|
||||||
|
nuxIsCompleted,
|
||||||
|
nuxIsOptimisticallyCompleted,
|
||||||
|
completedForDevice,
|
||||||
|
save,
|
||||||
|
setCompletedForDevice,
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
completed,
|
completed,
|
||||||
complete() {
|
complete() {
|
||||||
save({
|
save({
|
||||||
id,
|
id: ACTIVE_ANNOUNCEMENT,
|
||||||
completed: true,
|
completed: true,
|
||||||
data: undefined,
|
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
|
* Device data that's specific to the device and does not vary based account
|
||||||
*/
|
*/
|
||||||
@@ -13,6 +15,11 @@ export type Device = {
|
|||||||
devMode: boolean
|
devMode: boolean
|
||||||
demoMode: boolean
|
demoMode: boolean
|
||||||
activitySubscriptionsNudged?: boolean
|
activitySubscriptionsNudged?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocking announcements. New IDs are required for each new announcement.
|
||||||
|
*/
|
||||||
|
[Nux.BlockingAnnouncementPolicyUpdate202508]?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Account = {
|
export type Account = {
|
||||||
|
|||||||
Reference in New Issue
Block a user