refactor: 프론트 템플릿 리펙토링

This commit is contained in:
donghyeon-ka
2026-09-18 15:16:58 +09:00
parent c10a709f2c
commit 5cc41467ae
80 changed files with 7227 additions and 4672 deletions
@@ -0,0 +1,129 @@
// @vitest-environment jsdom
import { act, renderHook, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import {
type ApplicationResult,
useApplicationQuery,
} from "../../src/presentation/adapters/query/application-query.ts";
import { createFailure } from "../../src/contracts/errors.ts";
import { queryClient, wrapper } from "./application-query-fixture.tsx";
describe("application query inbound bridge", () => {
it("latches a background failure over stale data and clears it on retry success", async () => {
const client = queryClient();
const responses: ApplicationResult<string[]>[] = [
{ ok: true, value: ["first"] },
{
ok: false,
error: createFailure("SERVER_FAILURE", "LIST", 0),
},
{ ok: true, value: ["recovered"] },
];
const execute = vi.fn(async () => responses.shift() ?? responses[0]);
const hook = renderHook(
() =>
useApplicationQuery({
queryKey: ["resource", "list"],
execute,
}),
{ wrapper: wrapper(client) },
);
await waitFor(() => expect(hook.result.current.data).toEqual(["first"]));
await act(() => hook.result.current.retry());
await waitFor(() =>
expect(hook.result.current.state.indicator).toBe("stale-degraded"),
);
expect(hook.result.current.state.base).toBe("success");
expect(hook.result.current.data).toEqual(["first"]);
await act(() => hook.result.current.retry());
await waitFor(() =>
expect(hook.result.current.data).toEqual(["recovered"]),
);
expect(hook.result.current.state.indicator).toBeNull();
expect(execute).toHaveBeenCalledTimes(3);
});
it("projects an initial application failure into terminal state", async () => {
const client = queryClient();
const failure = createFailure("FORBIDDEN", "LIST", 0);
const hook = renderHook(
() =>
useApplicationQuery({
queryKey: ["forbidden"],
execute: async () => ({ ok: false, error: failure }),
}),
{ wrapper: wrapper(client) },
);
await waitFor(() =>
expect(hook.result.current.state.base).toBe("terminal-error"),
);
expect(hook.result.current.state.failure).toBe(failure);
});
it("normalizes an unexpected execute rejection into a safe terminal failure", async () => {
const client = queryClient();
const hook = renderHook(
() =>
useApplicationQuery({
queryKey: ["unexpected-rejection"],
execute: async () => {
throw new Error("private upstream detail");
},
}),
{ wrapper: wrapper(client) },
);
await waitFor(() =>
expect(hook.result.current.state.base).toBe("terminal-error"),
);
expect(hook.result.current.state.failure).toMatchObject({
kind: "UNKNOWN_FAILURE",
operationId: "APPLICATION_QUERY",
userMessageKey: "error.unknown_failure",
action: "contact-support",
});
expect(JSON.stringify(hook.result.current.state.failure)).not.toContain(
"private upstream detail",
);
});
it("passes cancellation to the application and does not retain an unmounted error", async () => {
const client = queryClient();
let aborted = false;
const execute = vi.fn(
({ signal }: { signal: AbortSignal }) =>
new Promise<ApplicationResult<unknown>>((resolve) => {
signal.addEventListener(
"abort",
() => {
aborted = true;
resolve({
ok: false,
error: createFailure("REQUEST_ABORTED", "LIST", 0),
});
},
{ once: true },
);
}),
);
const hook = renderHook(
() =>
useApplicationQuery({
queryKey: ["cancelled"],
execute,
}),
{ wrapper: wrapper(client) },
);
await waitFor(() => expect(execute).toHaveBeenCalledOnce());
hook.unmount();
await waitFor(() => expect(aborted).toBe(true));
expect(client.getQueryState(["cancelled"])?.status).not.toBe("error");
});
});