Adds TECH_LOG_STUDIO_SOURCE (MOCK | HTTP, default MOCK) to the V2 runtime
config schema so a build can switch createTechLogFeatureInstalledInput
between the mock and HTTP Studio gateways without a rebuild. V1 documents
predate the key and always normalize to MOCK. The HTTP gateway is
constructed with only { operations } per Task 4's actual signature -
no CSRF provider is wired here; that lands with attachCredentials at a
later composition-root task.
Updates every existing Studio test call site to the new required
createTechLogFeatureInstalledInput(context) signature via a shared
tests/helpers/studio-install-context.ts MOCK fixture, so the whole
existing Studio suite keeps exercising the mock adapter unchanged.
130 lines
5.9 KiB
TypeScript
130 lines
5.9 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { render, screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { Outlet, RouterProvider, createMemoryRouter } from "react-router-dom";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { createTechLogFeatureInstalledInput } from "../../../src/features/tech-log/adapters/create-tech-log-feature-input.ts";
|
|
import { MOCK_STUDIO_INSTALL_CONTEXT } from "../../helpers/studio-install-context.ts";
|
|
import type { StudioGateway } from "../../../src/features/tech-log/application/ports/studio-gateway.ts";
|
|
import { StudioHomePage } from "../../../src/features/tech-log/presentation/studio/pages/studio-home-page.tsx";
|
|
import { StudioNotFoundPage } from "../../../src/features/tech-log/presentation/studio/pages/studio-not-found-page.tsx";
|
|
import { StudioShell } from "../../../src/features/tech-log/presentation/studio/studio-shell.tsx";
|
|
import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx";
|
|
import { createTestApplication } from "../../helpers/create-test-application.ts";
|
|
|
|
afterEach(() => vi.restoreAllMocks());
|
|
|
|
function renderStudio(
|
|
initialEntry: string,
|
|
createStudioGateway: () => StudioGateway,
|
|
) {
|
|
const router = createMemoryRouter(
|
|
[
|
|
{
|
|
path: "/studio",
|
|
element: (
|
|
<StudioShell>
|
|
<Outlet />
|
|
</StudioShell>
|
|
),
|
|
children: [
|
|
{ index: true, element: <StudioHomePage /> },
|
|
{ path: "documents", element: <p>작업본 라우트</p> },
|
|
{ path: "documents/new", element: <p>새 문서 라우트</p> },
|
|
{ path: "publications", element: <p>게시 기록 라우트</p> },
|
|
{ path: "*", element: <StudioNotFoundPage /> },
|
|
],
|
|
},
|
|
],
|
|
{ initialEntries: [initialEntry] },
|
|
);
|
|
const installed = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input;
|
|
const application = createTestApplication({
|
|
featureInputs: {
|
|
"tech-log": { ...installed, createStudioGateway },
|
|
},
|
|
});
|
|
const view = render(
|
|
<ApplicationProvider application={application}>
|
|
<RouterProvider router={router} />
|
|
</ApplicationProvider>,
|
|
);
|
|
return { ...view, router };
|
|
}
|
|
|
|
describe("TechLog Studio shell", () => {
|
|
it("creates one application-provided gateway for child navigation and exposes the source header", async () => {
|
|
const user = userEvent.setup();
|
|
const gateway = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway();
|
|
const createGateway = vi.fn(() => gateway);
|
|
|
|
const { container, router } = renderStudio("/studio", createGateway);
|
|
|
|
expect(await screen.findByRole("heading", { level: 1, name: "작업 흐름" })).toBeVisible();
|
|
expect(createGateway).toHaveBeenCalledTimes(1);
|
|
expect(
|
|
Array.from(
|
|
container.querySelectorAll<HTMLAnchorElement>(".studio-desktop-navigation a"),
|
|
(link) => [link.textContent, link.getAttribute("href")],
|
|
),
|
|
).toEqual([
|
|
["작업본", "/studio/documents"],
|
|
["게시 기록", "/studio/publications"],
|
|
["새 문서", "/studio/documents/new"],
|
|
["공개 사이트 보기", "/"],
|
|
]);
|
|
expect(screen.getByRole("link", { name: "공개 사이트 보기" })).toHaveAttribute("href", "/");
|
|
expect(screen.queryByText(/로그인|sign in/i)).not.toBeInTheDocument();
|
|
|
|
await user.click(within(screen.getByRole("navigation", { name: "Studio 주 탐색" })).getByRole("link", { name: "작업본" }));
|
|
|
|
expect(router.state.location.pathname).toBe("/studio/documents");
|
|
expect(screen.getByText("작업본 라우트")).toBeVisible();
|
|
expect(createGateway).toHaveBeenCalledTimes(1);
|
|
expect(screen.getAllByRole("link", { name: "작업본" })[0]).toHaveAttribute("aria-current", "page");
|
|
});
|
|
|
|
it("recreates the provider generation and cancels obsolete work after a persisted pageshow", async () => {
|
|
const user = userEvent.setup();
|
|
let firstSignal: AbortSignal | undefined;
|
|
const first = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway();
|
|
const second = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway();
|
|
vi.spyOn(first, "getDashboard").mockImplementation(({ signal } = {}) => {
|
|
firstSignal = signal;
|
|
return new Promise(() => {});
|
|
});
|
|
const secondDashboard = vi.spyOn(second, "getDashboard");
|
|
const createGateway = vi.fn()
|
|
.mockReturnValueOnce(first)
|
|
.mockReturnValueOnce(second);
|
|
|
|
renderStudio("/studio", createGateway);
|
|
await waitFor(() => expect(firstSignal).toBeDefined());
|
|
await user.click(screen.getByRole("button", { name: "Studio 메뉴 열기" }));
|
|
expect(screen.getByRole("button", { name: "Studio 메뉴 닫기" })).toBeVisible();
|
|
|
|
window.dispatchEvent(new PageTransitionEvent("pageshow", { persisted: true }));
|
|
|
|
await waitFor(() => expect(createGateway).toHaveBeenCalledTimes(2));
|
|
expect(firstSignal?.aborted).toBe(true);
|
|
expect(screen.getByRole("button", { name: "Studio 메뉴 열기" })).toHaveAttribute("aria-expanded", "false");
|
|
expect(await screen.findByRole("heading", { level: 1, name: "작업 흐름" })).toBeVisible();
|
|
expect(secondDashboard).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("keeps unknown Studio routes inside the Studio shell without authentication UI", () => {
|
|
const createGateway = vi.fn(
|
|
() => createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway(),
|
|
);
|
|
|
|
renderStudio("/studio/does-not-exist", createGateway);
|
|
|
|
expect(screen.getByRole("banner")).toHaveClass("studio-header");
|
|
expect(screen.getByRole("heading", { level: 1, name: "Studio 화면을 찾을 수 없습니다" })).toBeVisible();
|
|
expect(screen.getByRole("link", { name: "작업본으로 돌아가기" })).toHaveAttribute("href", "/studio/documents");
|
|
expect(screen.queryByText(/로그인|sign in/i)).not.toBeInTheDocument();
|
|
});
|
|
});
|