refactor: enforce frontend view model boundaries
This commit is contained in:
@@ -25,25 +25,32 @@ describe("sync connection and session boundaries", () => {
|
||||
refresh: () => Promise.resolve(true),
|
||||
});
|
||||
|
||||
const { promise: blocked, resolve: release } = Promise.withResolvers<void>();
|
||||
const { promise: blocked, resolve: release }
|
||||
= Promise.withResolvers<void>();
|
||||
|
||||
const accepted = {
|
||||
id: "work",
|
||||
now: "2026-09-25T12:00:00Z",
|
||||
scope: { screen: "accounts" },
|
||||
};
|
||||
|
||||
let started = 0;
|
||||
let isChanged = false;
|
||||
server.use(
|
||||
http.post("*/api/v1/tbank-sync", async () => {
|
||||
started += 1;
|
||||
await blocked;
|
||||
|
||||
return HttpResponse.json(accepted, { status: 202 });
|
||||
}),
|
||||
http.get("*/api/v1/tbank-sync/:id", async () => {
|
||||
const now = isChanged ? "2026-09-25T12:01:00Z" : accepted.now;
|
||||
started += 1;
|
||||
await blocked;
|
||||
return HttpResponse.json((/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
|
||||
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
|
||||
return HttpResponse.json(
|
||||
{
|
||||
...accepted,
|
||||
errorCode: null,
|
||||
@@ -51,21 +58,26 @@ describe("sync connection and session boundaries", () => {
|
||||
progress: { completed: 1, discovering: false, total: 1 },
|
||||
resources: [],
|
||||
state: "succeeded",
|
||||
}
|
||||
/* eslint-enable unicorn/no-null */));
|
||||
},
|
||||
/* eslint-enable unicorn/no-null */
|
||||
);
|
||||
}),
|
||||
);
|
||||
const { queryClient, wrapper } = createQueryWrapper();
|
||||
const published: string[] = [];
|
||||
|
||||
const unsubscribe = queryClient.getQueryCache().subscribe((event) => {
|
||||
if ((event.query.queryKey as readonly unknown[])[1]
|
||||
!== "/api/v1/tbank-sync/{id}") {
|
||||
if (
|
||||
(event.query.queryKey as readonly unknown[])[1]
|
||||
!== "/api/v1/tbank-sync/{id}"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = event.query.state.data as undefined | { now?: string };
|
||||
if (data?.now) published.push(data.now);
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => ({
|
||||
mutation: useTbankSync(),
|
||||
@@ -73,39 +85,49 @@ describe("sync connection and session boundaries", () => {
|
||||
}),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
const screen: InvestmentScreen = { screen: "accounts" };
|
||||
let mutation!: Promise<unknown>;
|
||||
act(() => {
|
||||
mutation = result.current.mutation.mutateAsync({ body: screen });
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/valid-expect -- Attach rejection handling before releasing the request; the assertion is awaited below.
|
||||
const discarded = expect(mutation).rejects.toMatchObject({
|
||||
name: "AbortError",
|
||||
});
|
||||
|
||||
await waitFor(() => expect(started).toBe(2));
|
||||
await act(async () => {
|
||||
isChanged = true;
|
||||
|
||||
if (boundary === "connection")
|
||||
commitTbankConnectionBoundary(queryClient, (/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
commitTbankConnectionBoundary(
|
||||
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
|
||||
queryClient,
|
||||
{
|
||||
connected: false,
|
||||
connectedAt: null,
|
||||
}
|
||||
/* eslint-enable unicorn/no-null */));
|
||||
},
|
||||
/* eslint-enable unicorn/no-null */
|
||||
);
|
||||
else {
|
||||
generation += 1;
|
||||
queryClient.clear();
|
||||
}
|
||||
|
||||
release();
|
||||
await blocked;
|
||||
});
|
||||
await discarded;
|
||||
expect(result.current.mutation.data).toBeUndefined();
|
||||
|
||||
const key = apiQueryClient.queryOptions(
|
||||
"get",
|
||||
"/api/v1/tbank-sync/{id}",
|
||||
{ params: { path: { id: "work" } } },
|
||||
).queryKey;
|
||||
|
||||
// A new request may start after a successful connection boundary; an old result cannot win it.
|
||||
if (boundary === "session")
|
||||
// eslint-disable-next-line vitest/no-conditional-expect -- The fixed table case selects the expected outcome; every case asserts its own branch.
|
||||
|
||||
Reference in New Issue
Block a user