Add the Google Calendar settings screen and wire Push to Calendar (Feature 16) #129
@@ -269,4 +269,71 @@ describe('week mutations', () => {
|
||||
hook.rerender({ week: LATER })
|
||||
expect(weekMutations(queryClient, NEXT)).toEqual([])
|
||||
})
|
||||
|
||||
it('leaves behind what settles in a week no longer shown, unless that week still has one pending or it is the failure that speaks for it', async () => {
|
||||
const friday = deferred()
|
||||
const monday = deferred()
|
||||
const nextPush = deferred()
|
||||
const laterPush = deferred()
|
||||
stubServer({
|
||||
[`GET ${PLAN}`]: () => jsonResponse(200, planBody()),
|
||||
[`PUT ${FRIDAY}`]: () => friday.reply,
|
||||
[`PUT ${MONDAY}`]: () => monday.reply,
|
||||
[`GET /api/v1/plans/${NEXT}`]: () => errorResponse(404, 'not_found'),
|
||||
[`POST /api/v1/plans/${NEXT}/push-calendar`]: () => nextPush.reply,
|
||||
[`GET /api/v1/plans/${LATER}`]: () => errorResponse(404, 'not_found'),
|
||||
[`POST /api/v1/plans/${LATER}/push-calendar`]: () => laterPush.reply,
|
||||
})
|
||||
const api = createApiClient(fakeLocation('/plan'), createNetworkStatus(window), createVerifiedSession(localStorage))
|
||||
await api.loadMe()
|
||||
const queryClient = createQueryClient()
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<ApiProvider client={api}>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
</ApiProvider>
|
||||
)
|
||||
const hook = renderHook(
|
||||
({ week }) => ({ plan: usePlan(week), swap: useSwapDay(week), push: usePushToCalendar(week) }),
|
||||
{ wrapper, initialProps: { week: WEEK } },
|
||||
)
|
||||
await waitFor(() => expect(hook.result.current.plan.isSuccess).toBe(true))
|
||||
|
||||
// Two swaps of the week and a push of the next are left pending, and a
|
||||
// third week is shown.
|
||||
act(() => hook.result.current.swap.mutate(ORZO))
|
||||
act(() => hook.result.current.swap.mutate(FISH))
|
||||
await waitFor(() => expect(weekMutations(queryClient, WEEK)).toEqual(['swap pending', 'swap pending']))
|
||||
hook.rerender({ week: NEXT })
|
||||
await waitFor(() => expect(hook.result.current.plan.isSuccess).toBe(true))
|
||||
act(() => hook.result.current.push.mutate())
|
||||
hook.rerender({ week: LATER })
|
||||
await waitFor(() => expect(hook.result.current.plan.isSuccess).toBe(true))
|
||||
expect(weekMutations(queryClient, NEXT)).toEqual(['push pending'])
|
||||
|
||||
// The later swap settles first: the earlier, still pending, must see it,
|
||||
// so both stay.
|
||||
await act(async () => monday.send(jsonResponse(200, planBody({ 0: { id: 'r1', name: 'Fish tacos' } }))))
|
||||
await waitFor(() => expect(weekMutations(queryClient, WEEK)).toEqual(['swap pending', 'swap success']))
|
||||
|
||||
// The push succeeds while the third week is shown: it is left behind,
|
||||
// and the week's pending swap is not.
|
||||
await act(async () => nextPush.send(jsonResponse(200, { ...planBody(), week_start: NEXT })))
|
||||
await waitFor(() => expect(weekMutations(queryClient, NEXT)).toEqual([]))
|
||||
expect(weekMutations(queryClient, WEEK)).toEqual(['swap pending', 'swap success'])
|
||||
|
||||
// Once the earlier swap fails, the week has none pending: both are left
|
||||
// behind, the failure not being the week's latest.
|
||||
await act(async () => friday.send(errorResponse(409, 'dinner_not_plannable')))
|
||||
await waitFor(() => expect(weekMutations(queryClient, WEEK)).toEqual([]))
|
||||
|
||||
// A push of the third week fails once another is shown: it stays, to be
|
||||
// shown on coming back, until yet another week is.
|
||||
act(() => hook.result.current.push.mutate())
|
||||
hook.rerender({ week: NEXT })
|
||||
await waitFor(() => expect(hook.result.current.plan.isSuccess).toBe(true))
|
||||
await act(async () => laterPush.send(errorResponse(500, 'internal')))
|
||||
await waitFor(() => expect(weekMutations(queryClient, LATER)).toEqual(['push error']))
|
||||
hook.rerender({ week: WEEK })
|
||||
expect(weekMutations(queryClient, LATER)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
+25
-11
@@ -185,20 +185,34 @@ type WeekMutation = 'swap' | 'push'
|
||||
// useWeekMutationsKept bounds the mutations of kind the cache keeps. When a
|
||||
// week is shown, the settled ones of every other week are removed, unless
|
||||
// that week still has one pending: a pending one, and those an earlier
|
||||
// pending one must see, stay across leaving its week and coming back. The
|
||||
// cache holds at most the week shown's and those of weeks left pending.
|
||||
// pending one must see, stay across leaving its week and coming back. When
|
||||
// one settles, the same is done, except that a week's latest, when it
|
||||
// failed, stays until another week is shown, so that coming straight back to
|
||||
// it shows the failure. The cache holds at most the week shown's, those of
|
||||
// weeks left pending, and one failure of each week whose last pending one
|
||||
// settled since the week shown was.
|
||||
function useWeekMutationsKept(weekStart: string, kind: WeekMutation) {
|
||||
const queryClient = useQueryClient()
|
||||
useEffect(() => {
|
||||
const mutations = queryClient.getMutationCache()
|
||||
const left = mutations.findAll({
|
||||
predicate: ({ options: { mutationKey: key } }) =>
|
||||
key !== undefined && key.length === 3 && key[0] === 'plans' && key[1] !== weekStart && key[2] === kind,
|
||||
})
|
||||
const pending = new Set(left.filter((m) => m.state.status === 'pending').map((m) => m.options.mutationKey![1]))
|
||||
for (const mutation of left) {
|
||||
if (!pending.has(mutation.options.mutationKey![1])) mutations.remove(mutation)
|
||||
const isKind = (key: readonly unknown[] | undefined) => key !== undefined && key.length === 3 && key[0] === 'plans' && key[2] === kind
|
||||
function sweep(settled: boolean) {
|
||||
const left = mutations.findAll({
|
||||
predicate: ({ options: { mutationKey: key } }) => isKind(key) && key![1] !== weekStart,
|
||||
})
|
||||
const pending = new Set(left.filter((m) => m.state.status === 'pending').map((m) => m.options.mutationKey![1]))
|
||||
const latest = new Map(left.map((m) => [m.options.mutationKey![1], m]))
|
||||
for (const mutation of left) {
|
||||
const week = mutation.options.mutationKey![1]
|
||||
if (pending.has(week)) continue
|
||||
if (settled && latest.get(week) === mutation && mutation.state.status === 'error') continue
|
||||
mutations.remove(mutation)
|
||||
}
|
||||
}
|
||||
sweep(false)
|
||||
return mutations.subscribe((event) => {
|
||||
if (event.type === 'updated' && isKind(event.mutation.options.mutationKey) && event.mutation.state.status !== 'pending') sweep(true)
|
||||
})
|
||||
}, [queryClient, weekStart, kind])
|
||||
}
|
||||
|
||||
@@ -252,7 +266,7 @@ export function useSwapDay(weekStart: string): SwapDay {
|
||||
const isLatest = () => mutations.findAll({ mutationKey }).every((other) => other.mutationId <= mutation.mutationId)
|
||||
const mutation = mutations.build<Plan, Error, Swap, { previous: PlanDay; swapped: PlanDay }>(queryClient, {
|
||||
mutationKey,
|
||||
// Kept until dismissed or its week is left settled
|
||||
// Kept until dismissed or settled in a week not shown
|
||||
// (useWeekMutationsKept): a later swap must stay in the cache while an
|
||||
// earlier one is pending.
|
||||
gcTime: Infinity,
|
||||
@@ -350,7 +364,7 @@ export function usePushToCalendar(weekStart: string): PushToCalendar {
|
||||
function mutate() {
|
||||
const mutation = mutations.build<Plan, Error, void, unknown>(queryClient, {
|
||||
mutationKey,
|
||||
// Kept until reset or its week is left settled (useWeekMutationsKept):
|
||||
// Kept until reset or settled in a week not shown (useWeekMutationsKept):
|
||||
// a push pending in a week left must still be found when it is shown
|
||||
// again.
|
||||
gcTime: Infinity,
|
||||
|
||||
Reference in New Issue
Block a user