feat: add form and page platform

This commit is contained in:
donghyeon-ka
2026-07-26 15:22:52 +09:00
parent fdcf0de5bf
commit b327d7370b
54 changed files with 2036 additions and 122 deletions
@@ -82,10 +82,14 @@ describe("reference feature boundary contracts", () => {
it("owns route, operation and query contributions in one removable contract", () => {
expect(Object.keys(REFERENCE_FEATURE_CONTRACT.routes)).toEqual([
"REFERENCE_RESOURCE_LIST",
"REFERENCE_RESOURCE_DETAIL",
"REFERENCE_RESOURCE_FORM",
"REFERENCE_RESOURCE_STATUS",
]);
expect(Object.keys(REFERENCE_FEATURE_CONTRACT.apiOperations)).toEqual([
"LIST_REFERENCE_RESOURCES",
"CREATE_REFERENCE_RESOURCE",
"GET_REFERENCE_RESOURCE",
]);
expect(Object.keys(REFERENCE_FEATURE_CONTRACT.queryRegistry)).toEqual([
"REFERENCE_RESOURCE",
@@ -58,6 +58,14 @@ function inputWith(
createdAtLabel: null,
},
}),
getResource: async (resourceId) => ({
ok: true,
value: {
resourceId,
title: "Detail",
createdAtLabel: null,
},
}),
...overrides,
};
}
@@ -129,7 +137,7 @@ describe("reference feature page states", () => {
);
});
it("deduplicates optimistic create and rolls back a conflict", async () => {
it("deduplicates create, preserves input and surfaces a conflict", async () => {
const user = userEvent.setup();
let finish:
| ((result: ReferenceResult<ReferenceResourceView>) => void)
@@ -141,31 +149,22 @@ describe("reference feature page states", () => {
}),
);
renderReference(
inputWith({
listResources: async () => ({
ok: true,
value: [
{
resourceId: "existing",
title: "Existing",
createdAtLabel: null,
},
],
}),
createResource,
}),
inputWith({ createResource }),
"/examples/reference-resources/new",
);
await screen.findByText("Existing");
await user.type(screen.getByLabelText("새 항목 이름"), "Conflicting");
const submit = screen.getByRole("button", { name: "추가" });
await screen.findByRole("heading", {
name: "Reference resource 만들기",
});
await user.type(
screen.getByRole("textbox", { name: /새 항목 이름/ }),
"Conflicting",
);
await user.type(screen.getByLabelText("설명"), "Keep this input");
const submit = screen.getByRole("button", { name: "저장" });
await user.dblClick(submit);
await waitFor(() => expect(createResource).toHaveBeenCalledOnce());
expect(await screen.findByText("Conflicting")).toHaveAttribute(
"data-optimistic",
"true",
);
expect(screen.getByText("mutation-pending")).toBeVisible();
expect(screen.getByRole("button", { name: "저장 중…" })).toBeDisabled();
finish?.({
ok: false,
@@ -175,14 +174,11 @@ describe("reference feature page states", () => {
0,
),
});
expect(await screen.findByText(/다른 변경과 충돌했습니다/)).toBeVisible();
expect(
await screen.findByRole("button", { name: "충돌 해결" }),
).toBeVisible();
expect(screen.queryByText("Conflicting")).not.toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "충돌 해결" }));
await waitFor(() =>
expect(screen.queryByText("mutation-conflict")).not.toBeInTheDocument(),
);
screen.getByRole("textbox", { name: /새 항목 이름/ }),
).toHaveValue("Conflicting");
expect(screen.getByLabelText("설명")).toHaveValue("Keep this input");
});
it("keeps stale data visible during refresh failure and recovers on retry", async () => {
@@ -219,8 +215,7 @@ describe("reference feature page states", () => {
});
renderReference(inputWith({ listResources }));
await screen.findByText("Existing");
await user.type(screen.getByLabelText("새 항목 이름"), "Created");
await user.click(screen.getByRole("button", { name: "추가" }));
await user.click(screen.getByRole("button", { name: "새로고침" }));
expect(await screen.findByText("stale-degraded")).toBeVisible();
expect(screen.getByText("Existing")).toBeVisible();
@@ -46,6 +46,9 @@ const releaseManifest = {
"route-examples-states": "assets/states.js",
"route-examples-auth": "assets/auth.js",
"route-reference-resources": "assets/reference.js",
"route-reference-resource-detail": "assets/reference-detail.js",
"route-reference-resource-form": "assets/reference-form.js",
"route-reference-resource-status": "assets/reference-status.js",
"route-not-found": "assets/not-found.js",
},
};
@@ -120,8 +123,14 @@ describe("reference feature production vertical path", () => {
"?limit=5&tags=open&tags=new",
);
await user.type(screen.getByLabelText("새 항목 이름"), " Created ");
await user.click(screen.getByRole("button", { name: "추가" }));
await user.click(screen.getByRole("button", { name: "새 항목 만들기" }));
await user.type(
await screen.findByRole("textbox", { name: /새 항목 이름/ }),
" Created ",
);
await user.click(screen.getByRole("button", { name: "저장" }));
expect(await screen.findByText("저장했습니다.")).toBeVisible();
await user.click(screen.getByRole("button", { name: "목록으로 돌아가기" }));
expect(await screen.findByText("Created")).toBeVisible();
expect(createRequests).toHaveBeenCalledWith({ name: "Created" });
expect(listRequests.mock.calls.length).toBeGreaterThanOrEqual(2);