feat(profiles): name the copy of a stopped catch-up, one copy per profile, startup error cleared (#395) #436
4 changed files with 48 additions and 3 deletions
|
|
@ -613,7 +613,7 @@ Sémantique simulée, chaque point prouvé par `src/test/hookHarness.test.ts` :
|
|||
- `useEffect` : capturé pendant le rendu, joué seulement par `commit()` s'il a une dépendance changée, nettoyages d'abord ; `replayEffects()` joue le double passage de StrictMode au montage, `unmount()` les nettoyages ;
|
||||
- tout autre hook (`useContext`, `useLayoutEffect`, `useSyncExternalStore`, `useId`, `use`…) **lève** : un test rougit sur son assertion, jamais par « Invalid hook call ». Mocker le module qui l'appelle (`useOnboarding`, `useLocation`, `useTranslation`…).
|
||||
|
||||
Non simulé : le nouveau rendu après un setter ou un dispatch, les enfants (un composant dans l'arbre rendu reste un élément), le DOM, le concurrent, la boucle de mises à jour pendant le rendu (le `render()` suivant la lit).
|
||||
Non simulé : le nouveau rendu après un setter ou un dispatch, les enfants (un composant dans l'arbre rendu reste un élément), le DOM, le concurrent, la boucle de mises à jour pendant le rendu (le `render()` suivant la lit), et un reducer qui change d'un rendu à l'autre : le `dispatch` d'un `useReducer` garde le reducer du premier rendu (il est créé une fois, avec l'emplacement), là où React applique celui du dernier rendu. Un reducer défini au niveau du module, seul cas utilisé, n'est pas touché.
|
||||
|
||||
Utilisateurs : `useOnboardingChecklist.wiring.test.ts`, `UsersSettingsPage.test.tsx`, `BudgetTable.wiring.test.ts`, `BudgetPage.wiring.test.ts`, `AppShell.slot.test.tsx`, `OnboardingStepCard.test.tsx`, `PriceFetchConsentToggle.test.tsx` (#389). Hors périmètre de #409, `useImportWizard.mount.test.ts` et `useImportWizard.executeImport.test.ts` gardent leurs faux non persistants.
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ describe("useOnboardingChecklist — wiring (#396)", () => {
|
|||
vi.useFakeTimers();
|
||||
harness.dispatch.mockReset();
|
||||
|
||||
// A route change re-runs the read, not the follower: one subscription only.
|
||||
// A route change re-runs the read (that it leaves the follower and its
|
||||
// pending re-read alone is pinned by the next test).
|
||||
ctx.pathname = "/reports";
|
||||
render();
|
||||
harness.commit();
|
||||
|
|
@ -146,6 +147,31 @@ describe("useOnboardingChecklist — wiring (#396)", () => {
|
|||
expect(actionsOfType("LOAD_START")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("a route change during the 150 ms wait does not drop the deferred re-read", async () => {
|
||||
render();
|
||||
harness.commit();
|
||||
await flush();
|
||||
vi.useFakeTimers();
|
||||
harness.dispatch.mockReset();
|
||||
|
||||
notifyDataChanged();
|
||||
vi.advanceTimersByTime(100);
|
||||
// The route changes while the re-read waits: the read effect runs again,
|
||||
// the follower (deps: [refresh]) must not be cleaned up and resubscribed,
|
||||
// which would drop its pending timer.
|
||||
ctx.pathname = "/reports";
|
||||
render();
|
||||
harness.commit();
|
||||
await flush();
|
||||
expect(actionsOfType("LOAD_START")).toHaveLength(1);
|
||||
|
||||
vi.advanceTimersByTime(50);
|
||||
await flush();
|
||||
// One read for the route, one for the data event.
|
||||
expect(actionsOfType("LOAD_START")).toHaveLength(2);
|
||||
expect(getOnboardingSnapshot).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("stores a card latch that changed during render, and only then", () => {
|
||||
mountWith({ ...ready, cardPage: "/import" });
|
||||
ctx.pathname = "/reports";
|
||||
|
|
|
|||
|
|
@ -140,6 +140,22 @@ describe("hookHarness — useMemo and useCallback", () => {
|
|||
expect(compute).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("recomputes when the dependency array changes length, even over an equal prefix", () => {
|
||||
// React warns on such an array; the harness still treats it as a change.
|
||||
// Shrinking keeps every remaining item equal, and growing by `undefined`
|
||||
// matches the missing item read as `undefined`: only the length differs.
|
||||
let deps: unknown[] = [1, 2];
|
||||
const compute = vi.fn(() => ({}));
|
||||
const Memo = () => R.useMemo(compute, deps);
|
||||
harness.render(Memo);
|
||||
deps = [1];
|
||||
harness.render(Memo);
|
||||
expect(compute).toHaveBeenCalledTimes(2);
|
||||
deps = [1, undefined];
|
||||
harness.render(Memo);
|
||||
expect(compute).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("useMemo without dependencies recomputes at every render", () => {
|
||||
const compute = vi.fn(() => ({}));
|
||||
const Memo = () => R.useMemo(compute, undefined as unknown as []);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,10 @@
|
|||
// children (a component in the returned tree is an element, never called),
|
||||
// the DOM, concurrent features, the bail-out of a setter called with the same
|
||||
// value, the render-phase update loop React runs for a dispatch during render
|
||||
// (the next `render()` reads it).
|
||||
// (the next `render()` reads it), and a reducer that changes between renders:
|
||||
// the `dispatch` of a `useReducer` keeps the reducer of the first render (it is
|
||||
// created once, with the slot), where React applies the reducer of the latest
|
||||
// render. A module-level reducer, the only kind in use, is not affected.
|
||||
//
|
||||
// React is imported here only as a type: a value import, from inside the
|
||||
// factory that builds the mock of "react", would load that very mock.
|
||||
|
|
|
|||
Loading…
Reference in a new issue