mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
test(client): reset stores from a registry that a test holds complete
resetAllStores read a hand list of seven stores, and a new store simply never joined it. tests/helpers/storeRegistry.ts now names every Zustand store in src/ as either reset or left alone with the reason, and a test fails on a store that is in neither list or on an entry whose store is gone. The set of stores that are reset is unchanged.
This commit is contained in:
@@ -1,29 +1,13 @@
|
||||
import type { StoreApi } from 'zustand';
|
||||
import { useAuthStore } from '../../src/store/authStore';
|
||||
import { useTripStore } from '../../src/store/tripStore';
|
||||
import { useSettingsStore } from '../../src/store/settingsStore';
|
||||
import { useVacayStore } from '../../src/store/vacayStore';
|
||||
import { useAddonStore } from '../../src/store/addonStore';
|
||||
import { useInAppNotificationStore } from '../../src/store/inAppNotificationStore';
|
||||
import { usePermissionsStore } from '../../src/store/permissionsStore';
|
||||
// Journey store is reset individually in journey tests to avoid circular import issues
|
||||
import { RESET_STORES } from './storeRegistry';
|
||||
|
||||
// Capture initial states at import time (before any test modifies them)
|
||||
const initialAuthState = useAuthStore.getState();
|
||||
const initialTripState = useTripStore.getState();
|
||||
const initialSettingsState = useSettingsStore.getState();
|
||||
const initialVacayState = useVacayStore.getState();
|
||||
const initialAddonState = useAddonStore.getState();
|
||||
const initialNotifState = useInAppNotificationStore.getState();
|
||||
const initialPermsState = usePermissionsStore.getState();
|
||||
// The state of each store in the registry when this module was first imported,
|
||||
// before any test modified it.
|
||||
const initialStates = Object.entries(RESET_STORES).map(([name, store]) => ({ name, store, state: store.getState() }));
|
||||
|
||||
/** Puts every store in RESET_STORES (tests/helpers/storeRegistry.ts) back to its initial state. */
|
||||
export function resetAllStores(): void {
|
||||
useAuthStore.setState(initialAuthState, true);
|
||||
useTripStore.setState(initialTripState, true);
|
||||
useSettingsStore.setState(initialSettingsState, true);
|
||||
useVacayStore.setState(initialVacayState, true);
|
||||
useAddonStore.setState(initialAddonState, true);
|
||||
useInAppNotificationStore.setState(initialNotifState, true);
|
||||
usePermissionsStore.setState(initialPermsState, true);
|
||||
for (const { store, state } of initialStates) store.setState(state, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,9 +23,6 @@ export function resetAllStores(): void {
|
||||
*/
|
||||
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
|
||||
|
||||
export function seedStore<T extends object>(
|
||||
store: Pick<StoreApi<T>, 'setState'>,
|
||||
state: DeepPartial<T>,
|
||||
): void {
|
||||
export function seedStore<T extends object>(store: Pick<StoreApi<T>, 'setState'>, state: DeepPartial<T>): void {
|
||||
store.setState(state as Partial<T>);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { StoreApi } from 'zustand';
|
||||
import { useAddonStore } from '../../src/store/addonStore';
|
||||
import { useAuthStore } from '../../src/store/authStore';
|
||||
import { useInAppNotificationStore } from '../../src/store/inAppNotificationStore';
|
||||
import { usePermissionsStore } from '../../src/store/permissionsStore';
|
||||
import { useSettingsStore } from '../../src/store/settingsStore';
|
||||
import { useTripStore } from '../../src/store/tripStore';
|
||||
import { useVacayStore } from '../../src/store/vacayStore';
|
||||
|
||||
/**
|
||||
* Every Zustand store in src/, and what resetAllStores does with it.
|
||||
*
|
||||
* RESET_STORES are put back to the state they had when this module was first
|
||||
* imported, before any test touched them. UNRESET_STORES are left alone, each
|
||||
* with the reason; their tests reset them on their own. The registry test
|
||||
* (tests/unit/helpers/storeRegistry.test.ts) finds every `export const
|
||||
* use…Store = create…` under src/ and fails on one that is in neither list,
|
||||
* so a new store has to be placed here instead of being forgotten by the
|
||||
* reset and leaking state from one test into the next.
|
||||
*
|
||||
* The stores are imported by name rather than through a glob: an eager glob
|
||||
* would load every store module into each of the hundreds of test files that
|
||||
* reset, including the ones that mock a store module or import it in a
|
||||
* particular order (journeyStore), and a store moved from UNRESET to RESET
|
||||
* changes what those files start from. Each such move is its own change.
|
||||
*/
|
||||
type ResettableStore = Pick<StoreApi<object>, 'getState' | 'setState'>;
|
||||
|
||||
export const RESET_STORES: Record<string, ResettableStore> = {
|
||||
useAuthStore,
|
||||
useTripStore,
|
||||
useSettingsStore,
|
||||
useVacayStore,
|
||||
useAddonStore,
|
||||
useInAppNotificationStore,
|
||||
usePermissionsStore,
|
||||
};
|
||||
|
||||
/** Not reset so far; moving one into RESET_STORES changes what every file that resets starts from. */
|
||||
const NOT_YET = 'never reset by resetAllStores; its own tests set the state they need';
|
||||
|
||||
export const UNRESET_STORES: Record<string, string> = {
|
||||
useJourneyStore: 'reset in the journey tests themselves; importing it here pulls in a circular import',
|
||||
useBackgroundTasksStore: NOT_YET,
|
||||
useCollectionStore: NOT_YET,
|
||||
useDocSyncOfferStore: NOT_YET,
|
||||
useHelpStore: NOT_YET,
|
||||
usePluginStore: NOT_YET,
|
||||
useRoadtripPreferencesStore: NOT_YET,
|
||||
useSaveToCollectionStore: NOT_YET,
|
||||
useServerVersionStore: NOT_YET,
|
||||
useStudioStore: NOT_YET,
|
||||
useSystemNoticeStore: NOT_YET,
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { join, relative } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { useAuthStore } from '../../../src/store/authStore';
|
||||
import { resetAllStores } from '../../helpers/store';
|
||||
import { RESET_STORES, UNRESET_STORES } from '../../helpers/storeRegistry';
|
||||
|
||||
const SRC = join(process.cwd(), 'src');
|
||||
const STORE = /export const (use[A-Z]\w*Store)\s*=\s*create\b/g;
|
||||
|
||||
function sourceFiles(dir: string, out: string[] = []): string[] {
|
||||
for (const name of readdirSync(dir)) {
|
||||
const path = join(dir, name);
|
||||
if (statSync(path).isDirectory()) sourceFiles(path, out);
|
||||
else if (/\.tsx?$/.test(name) && !/\.(test|spec)\.tsx?$/.test(name)) out.push(path);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Every Zustand store src/ exports, as store name → file. */
|
||||
function storesInSrc(): Map<string, string> {
|
||||
const found = new Map<string, string>();
|
||||
for (const path of sourceFiles(SRC)) {
|
||||
for (const match of readFileSync(path, 'utf8').matchAll(STORE)) found.set(match[1], relative(SRC, path));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
describe('store registry', () => {
|
||||
it('FE-STORE-REGISTRY-001: places every store src/ exports, so a new one cannot slip past resetAllStores', () => {
|
||||
const inSrc = storesInSrc();
|
||||
expect(inSrc.size).toBeGreaterThan(0);
|
||||
const listed = new Set([...Object.keys(RESET_STORES), ...Object.keys(UNRESET_STORES)]);
|
||||
const missing = [...inSrc].filter(([name]) => !listed.has(name)).map(([name, file]) => `${name} (${file})`);
|
||||
expect(missing, 'add each to RESET_STORES or UNRESET_STORES in tests/helpers/storeRegistry.ts').toEqual([]);
|
||||
const gone = [...listed].filter((name) => !inSrc.has(name));
|
||||
expect(gone, 'remove each from tests/helpers/storeRegistry.ts').toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-STORE-REGISTRY-002: lists no store as both reset and left alone', () => {
|
||||
expect(Object.keys(RESET_STORES).filter((name) => name in UNRESET_STORES)).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-STORE-REGISTRY-003: resetAllStores puts a registered store back to its first state', () => {
|
||||
const before = useAuthStore.getState();
|
||||
useAuthStore.setState({ isAuthenticated: !before.isAuthenticated });
|
||||
resetAllStores();
|
||||
expect(useAuthStore.getState()).toBe(before);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user