// @vitest-environment happy-dom import { afterEach, describe, expect, it, vi } from 'vitest' import React from 'react' import { act, cleanup, render, screen, waitFor } from '@testing-library/react' import { ApiProvider, useApi } from '../api-context' import { useOptionsResolver, invalidateOptionsCache } from '../use-options-resolver' afterEach(() => { cleanup() invalidateOptionsCache() }) // A picker opened right after creating a record must list it: the 30 s // options cache is dropped by every write, whether it goes through useApi() // or straight through the host's axios instance. function Picker() { const { options } = useOptionsResolver({ modelKey: '', ref: 'Brand', fieldKey: 'id' }) return {options.map((o) => o.label).join(',')} } function hostClient(brands: string[]) { const handlers: Array<(res: any) => any> = [] const client = { get: vi.fn(async () => ({ data: { success: true, data: brands.map((b) => ({ id: b, label: b })) } })), // The batch path is unavailable on this fake host: /api/q 404s. post: vi.fn(async (url: string) => { if (url === '/q') return { data: { success: false } } return { data: { success: true } } }), put: vi.fn(async () => ({ data: { success: true } })), delete: vi.fn(async () => ({ data: { success: true } })), interceptors: { response: { use: (ok: (res: any) => any) => { handlers.push(ok) return handlers.length - 1 }, eject: vi.fn(), }, }, } const hostWrite = async (method: string, url = '/data/brands') => { for (const h of handlers) h({ config: { method, url } }) } return { client, hostWrite } } describe('options cache invalidation', () => { it('a host write through its own client drops the cached options', async () => { const brands = ['A'] const { client, hostWrite } = hostClient(brands) const view = render( , ) await waitFor(() => expect(screen.getByTestId('opts').textContent).toBe('A')) brands.push('B') await act(async () => { await hostWrite('post') }) view.unmount() render( , ) await waitFor(() => expect(screen.getByTestId('opts').textContent).toBe('A,B')) }) it('neither a GET, the /q read batch nor a toast in the bell drops the cache', async () => { const brands = ['A'] const { client, hostWrite } = hostClient(brands) const view = render( , ) await waitFor(() => expect(screen.getByTestId('opts').textContent).toBe('A')) const calls = client.get.mock.calls.length brands.push('B') await act(async () => { await hostWrite('get') await hostWrite('post', '/q') await hostWrite('post', '/notifications/me') }) view.unmount() render( , ) await waitFor(() => expect(screen.getByTestId('opts').textContent).toBe('A')) expect(client.get.mock.calls.length).toBe(calls) }) it('a write through useApi() drops the cached options', async () => { const brands = ['A'] const { client } = hostClient(brands) let write: (() => Promise) | null = null function Writer() { const api = useApi() write = () => api.post('/data/brands', { name: 'B' }) return null } const view = render( , ) await waitFor(() => expect(screen.getByTestId('opts').textContent).toBe('A')) brands.push('B') await act(async () => { await write!() }) view.unmount() render( , ) await waitFor(() => expect(screen.getByTestId('opts').textContent).toBe('A,B')) }) })