diff --git a/resources/js/app.tsx b/resources/js/app.tsx index dacdc2923..b3a7bc854 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -16,6 +16,7 @@ import { trackCurrentPage } from '@/lib/current-page'; import { createEcho } from '@/lib/echo'; import { trackFollowRequests } from '@/lib/follow-requests'; import { startMobileApp } from '@/lib/mobile-app'; +import { trackFeatureFlagsHistory } from '@/lib/operations-features-history'; import { pageTitle } from '@/lib/page-title'; import { trackPrefetchedPages } from '@/lib/prefetched-pages'; import { @@ -33,6 +34,7 @@ trackCurrentPage(); trackCommitPhotoPage(); trackVisualViewport(); trackAnalyticsEvents(); +trackFeatureFlagsHistory(); trackPrefetchedPages(); connectRealtime(createEcho()); // What a page shows was read once the visit bringing all of it started, and the first one once the browser asked for diff --git a/resources/js/lib/operations-features-history.ts b/resources/js/lib/operations-features-history.ts new file mode 100644 index 000000000..ff2140ba8 --- /dev/null +++ b/resources/js/lib/operations-features-history.ts @@ -0,0 +1,15 @@ +import { router } from '@inertiajs/react'; + +/** Feature switches can be changed by another admin while the page is cached. */ +export function trackFeatureFlagsHistory(): VoidFunction { + return router.on('navigate', ({ detail: { type, page } }) => { + if (type !== 'history' || page.component !== 'operations/features') { + return; + } + + router.reload({ + only: ['features'], + preserveScroll: true, + }); + }); +} diff --git a/tests/Unit/FeatureFlagsHistory.test.tsx b/tests/Unit/FeatureFlagsHistory.test.tsx new file mode 100644 index 000000000..b96fec66b --- /dev/null +++ b/tests/Unit/FeatureFlagsHistory.test.tsx @@ -0,0 +1,54 @@ +import assert from 'node:assert/strict'; +import { test, vi } from 'vite-plus/test'; + +type NavigationType = 'initial' | 'visit' | 'history'; +type Navigation = { + detail: { + type: NavigationType; + page: { component: string }; + }; +}; + +const reload = vi.fn(); +let navigate: ((event: Navigation) => void) | undefined; + +vi.doMock('@inertiajs/react', () => ({ + router: { + on: (event: string, listener: (event: Navigation) => void) => { + assert.equal(event, 'navigate'); + navigate = listener; + + return () => { + navigate = undefined; + }; + }, + reload, + }, +})); + +const { trackFeatureFlagsHistory } = await import( + '../../resources/js/lib/operations-features-history' +); + +test('refreshes operations feature flags only on browser history restoration', () => { + reload.mockClear(); + const stop = trackFeatureFlagsHistory(); + const go = (type: NavigationType, component: string) => + navigate?.({ detail: { type, page: { component } } }); + + go('initial', 'operations/features'); + go('visit', 'operations/features'); + go('history', 'log/show'); + assert.equal(reload.mock.calls.length, 0); + + go('history', 'operations/features'); + assert.equal(reload.mock.calls.length, 1); + assert.deepEqual(reload.mock.calls[0][0], { + only: ['features'], + preserveScroll: true, + }); + + stop(); + go('history', 'operations/features'); + assert.equal(reload.mock.calls.length, 1); +});