Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/web/src/app/__tests__/collections-new.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ describe("NewCollectionPage", () => {
const description = screen.getByLabelText("description");

expect(name).toHaveAttribute("aria-describedby", "name-counter");
expect(description).toHaveAttribute("aria-describedby", "description-counter");
expect(description).toHaveAttribute(
"aria-describedby",
"description-counter",
);
expect(screen.getByText("0/100")).toHaveAttribute("id", "name-counter");
expect(screen.getByText("0/500")).toHaveAttribute(
"id",
Expand Down
23 changes: 18 additions & 5 deletions apps/web/src/app/__tests__/home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ describe("Home page", () => {

// Add additional assertions to increase coverage on page.tsx guest view
expect(screen.getByText(/Research output hosting/)).toBeInTheDocument();
expect(screen.getByText(/OpenShelf は、論文・スライド・補足資料などをまとめて管理し/)).toBeInTheDocument();
expect(
screen.getByText(
/OpenShelf は、論文・スライド・補足資料などをまとめて管理し/,
),
).toBeInTheDocument();
expect(screen.getByText(/1\. 成果物をまとめる/)).toBeInTheDocument();
expect(screen.getByText(/2\. 公開範囲を選ぶ/)).toBeInTheDocument();
expect(screen.getByText(/3\. 永続URLで共有/)).toBeInTheDocument();
Expand Down Expand Up @@ -131,18 +135,27 @@ describe("Home page", () => {
expect(screen.getAllByText("公開")).toHaveLength(1);
});


it("handles apiFetch returning not ok", async () => {
authState = { user: { id: "user-1", name: "Alice" }, loading: false, login };
vi.mocked(apiFetch).mockResolvedValue(new Response("error", { status: 500 }));
authState = {
user: { id: "user-1", name: "Alice" },
loading: false,
login,
};
vi.mocked(apiFetch).mockResolvedValue(
new Response("error", { status: 500 }),
);
render(<Home />);
await waitFor(() => {
expect(screen.getByText("まだ成果物がありません")).toBeInTheDocument();
});
});

it("handles apiFetch error gracefully", async () => {
authState = { user: { id: "user-1", name: "Alice" }, loading: false, login };
authState = {
user: { id: "user-1", name: "Alice" },
loading: false,
login,
};
vi.mocked(apiFetch).mockRejectedValue(new Error("Network error"));
render(<Home />);
await waitFor(() => {
Expand Down
40 changes: 30 additions & 10 deletions apps/web/src/app/__tests__/paper-edit-page.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import PaperEditPage from "../papers/[id]/edit/page";
import { apiFetch } from "@/lib/api";
Expand Down Expand Up @@ -89,13 +95,17 @@ describe("PaperEditPage", () => {
target: { value: "Updated title" },
});

expect(screen.getByText(`${"Updated title".length}/300`)).toBeInTheDocument();
expect(
screen.getByText(`${"Updated title".length}/300`),
).toBeInTheDocument();

fireEvent.change(screen.getByLabelText(/概要/i), {
target: { value: "Updated abstract" },
});

expect(screen.getByText(`${"Updated abstract".length}/5000`)).toBeInTheDocument();
expect(
screen.getByText(`${"Updated abstract".length}/5000`),
).toBeInTheDocument();

fireEvent.click(screen.getByLabelText(/公開ページに閲覧数を表示する/i));
fireEvent.change(screen.getByLabelText(/タグ/i), {
Expand Down Expand Up @@ -141,7 +151,8 @@ describe("PaperEditPage", () => {
const patchCall = vi
.mocked(apiFetch)
.mock.calls.find(
([url, init]) => url === "/api/papers/paper-1" && init?.method === "PATCH",
([url, init]) =>
url === "/api/papers/paper-1" && init?.method === "PATCH",
);
const patchBody = JSON.parse((patchCall?.[1]?.body as string) ?? "{}");
expect(patchBody).toMatchObject({
Expand Down Expand Up @@ -321,14 +332,19 @@ describe("PaperEditPage", () => {

render(<PaperEditPage />);
expect(await screen.findByDisplayValue("Initial")).toBeInTheDocument();
fireEvent.change(screen.getByLabelText(/タイトル/i), { target: { value: " " } });
fireEvent.change(screen.getByLabelText(/タイトル/i), {
target: { value: " " },
});
fireEvent.click(screen.getByRole("button", { name: "保存する" }));

expect(await screen.findByText("タイトルを入力してください。")).toBeInTheDocument();
expect(
await screen.findByText("タイトルを入力してください。"),
).toBeInTheDocument();
const patchCalls = vi
.mocked(apiFetch)
.mock.calls.filter(
([url, req]) => url === "/api/papers/paper-1" && req?.method === "PATCH",
([url, req]) =>
url === "/api/papers/paper-1" && req?.method === "PATCH",
);
expect(patchCalls).toHaveLength(0);
});
Expand Down Expand Up @@ -363,14 +379,17 @@ describe("PaperEditPage", () => {

render(<PaperEditPage />);
expect(await screen.findByDisplayValue("Initial")).toBeInTheDocument();
fireEvent.change(screen.getByLabelText(/発表年/i), { target: { value: "" } });
fireEvent.change(screen.getByLabelText(/発表年/i), {
target: { value: "" },
});
fireEvent.click(screen.getByRole("button", { name: "保存する" }));

const patchCall = await waitFor(() =>
vi
.mocked(apiFetch)
.mock.calls.find(
([url, req]) => url === "/api/papers/paper-1" && req?.method === "PATCH",
([url, req]) =>
url === "/api/papers/paper-1" && req?.method === "PATCH",
),
);
const patchBody = JSON.parse((patchCall?.[1]?.body as string) ?? "{}");
Expand Down Expand Up @@ -510,7 +529,8 @@ describe("PaperEditPage", () => {
vi
.mocked(apiFetch)
.mock.calls.find(
([url, req]) => url === "/api/papers/paper-1" && req?.method === "PATCH",
([url, req]) =>
url === "/api/papers/paper-1" && req?.method === "PATCH",
),
);
const patchBody = JSON.parse((patchCall?.[1]?.body as string) ?? "{}");
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/app/__tests__/settings-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ describe("SettingsPage", () => {
it("shows github username in preview when display name is blank", () => {
render(<SettingsPage />);

fireEvent.change(screen.getByLabelText("表示名"), { target: { value: " " } });
fireEvent.change(screen.getByLabelText("表示名"), {
target: { value: " " },
});

expect(screen.getByText("alice")).toBeInTheDocument();
});
Expand Down
32 changes: 24 additions & 8 deletions apps/web/src/app/invites/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ export default function InvitesPage() {
const router = useRouter();
const [invites, setInvites] = useState<ReceivedInvite[]>([]);
const [fetching, setFetching] = useState(true);
const [processingById, setProcessingById] = useState<Map<string, InviteAction>>(
() => new Map(),
);
const [processingById, setProcessingById] = useState<
Map<string, InviteAction>
>(() => new Map());

useEffect(() => {
if (!loading && !user) router.push("/");
Expand Down Expand Up @@ -82,7 +82,9 @@ export default function InvitesPage() {
: i,
),
);
toast.success(action === "accept" ? "招待を承認しました" : "招待を拒否しました");
toast.success(
action === "accept" ? "招待を承認しました" : "招待を拒否しました",
);
} else {
toast.error("処理に失敗しました");
}
Expand Down Expand Up @@ -201,7 +203,13 @@ export default function InvitesPage() {
{processingAction === "accept" && (
<Spinner className="h-4 w-4" />
)}
<span className={processingAction === "accept" ? "sr-only" : undefined}>
<span
className={
processingAction === "accept"
? "sr-only"
: undefined
}
>
承認
</span>
</button>
Expand All @@ -215,16 +223,24 @@ export default function InvitesPage() {
{processingAction === "decline" && (
<Spinner className="h-4 w-4" />
)}
<span className={processingAction === "decline" ? "sr-only" : undefined}>
<span
className={
processingAction === "decline"
? "sr-only"
: undefined
}
>
拒否
</span>
</button>
</div>
) : (
<div className="shrink-0 text-xs text-gray-400">対応済み</div>
<div className="shrink-0 text-xs text-gray-400">
対応済み
</div>
)}
</div>
</li>
</li>
);
})}
</ul>
Expand Down
42 changes: 31 additions & 11 deletions apps/web/src/app/orgs/[slug]/__tests__/org-page-client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ vi.mock("next/link", () => ({
}));

vi.mock("next/image", () => ({
// eslint-disable-next-line @next/next/no-img-element
default: ({ alt = "image", ...props }: any) => <img alt={alt} {...props} />,
}));

Expand Down Expand Up @@ -225,7 +226,8 @@ describe("OrgPageClient", () => {
);
}
if (
url === "/api/orgs/lab/papers?paginate=1&autoYear=1&venue=ASE&category=report&page=2"
url ===
"/api/orgs/lab/papers?paginate=1&autoYear=1&venue=ASE&category=report&page=2"
) {
return new Response(
JSON.stringify({
Expand Down Expand Up @@ -273,14 +275,16 @@ describe("OrgPageClient", () => {
expect(screen.getByText("2 / 3")).toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "次へ" }));
const nextCall = replaceMock.mock.calls[replaceMock.mock.calls.length - 1][0];
const nextCall =
replaceMock.mock.calls[replaceMock.mock.calls.length - 1][0];
expect(nextCall).toContain("/orgs/lab?");
expect(nextCall).toContain("page=3");
expect(nextCall).toContain("venue=ASE");
expect(nextCall).toContain("category=report");

fireEvent.click(screen.getByRole("button", { name: "前へ" }));
const prevCall = replaceMock.mock.calls[replaceMock.mock.calls.length - 1][0];
const prevCall =
replaceMock.mock.calls[replaceMock.mock.calls.length - 1][0];
expect(prevCall).toContain("/orgs/lab?");
expect(prevCall).toContain("page=1");
expect(prevCall).toContain("venue=ASE");
Expand Down Expand Up @@ -340,14 +344,18 @@ describe("OrgPageClient", () => {
);
}
if (url === "/api/orgs/lab/collections") {
return new Response(JSON.stringify({ collections: [] }), { status: 200 });
return new Response(JSON.stringify({ collections: [] }), {
status: 200,
});
}
throw new Error(`Unexpected request: ${String(url)}`);
});

render(<OrgPageClient slug="lab" />);
expect(await screen.findByText("Research Lab")).toBeInTheDocument();
expect(screen.queryByRole("link", { name: "⚙ 設定" })).not.toBeInTheDocument();
expect(
screen.queryByRole("link", { name: "⚙ 設定" }),
).not.toBeInTheDocument();
expect(
screen.queryByRole("link", { name: "+ 成果物を追加" }),
).not.toBeInTheDocument();
Expand Down Expand Up @@ -381,14 +389,18 @@ describe("OrgPageClient", () => {
return new Response(JSON.stringify({ members: [] }), { status: 200 });
}
if (url === "/api/orgs/lab/collections") {
return new Response(JSON.stringify({ collections: [] }), { status: 200 });
return new Response(JSON.stringify({ collections: [] }), {
status: 200,
});
}
throw new Error(`Unexpected request: ${String(url)}`);
});

render(<OrgPageClient slug="lab" />);
expect(await screen.findByText("Research Lab")).toBeInTheDocument();
expect(await screen.findByText("まだ成果物がありません")).toBeInTheDocument();
expect(
await screen.findByText("まだ成果物がありません"),
).toBeInTheDocument();
});

it("applies explicit all-year filter when selecting empty year option", async () => {
Expand Down Expand Up @@ -431,7 +443,9 @@ describe("OrgPageClient", () => {
return new Response(JSON.stringify({ members: [] }), { status: 200 });
}
if (url === "/api/orgs/lab/collections") {
return new Response(JSON.stringify({ collections: [] }), { status: 200 });
return new Response(JSON.stringify({ collections: [] }), {
status: 200,
});
}
throw new Error(`Unexpected request: ${String(url)}`);
});
Expand Down Expand Up @@ -484,7 +498,9 @@ describe("OrgPageClient", () => {
return new Response(JSON.stringify({ members: [] }), { status: 200 });
}
if (url === "/api/orgs/lab/collections") {
return new Response(JSON.stringify({ collections: [] }), { status: 200 });
return new Response(JSON.stringify({ collections: [] }), {
status: 200,
});
}
throw new Error(`Unexpected request: ${String(url)}`);
});
Expand Down Expand Up @@ -534,13 +550,17 @@ describe("OrgPageClient", () => {
return new Response(JSON.stringify({ members: [] }), { status: 200 });
}
if (url === "/api/orgs/lab/collections") {
return new Response(JSON.stringify({ collections: [] }), { status: 200 });
return new Response(JSON.stringify({ collections: [] }), {
status: 200,
});
}
throw new Error(`Unexpected request: ${String(url)}`);
});

render(<OrgPageClient slug="lab" />);
expect(await screen.findByText("Research Lab")).toBeInTheDocument();
expect(await screen.findByText("まだ成果物がありません")).toBeInTheDocument();
expect(
await screen.findByText("まだ成果物がありません"),
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ vi.mock("next/link", () => ({

vi.mock("next/image", () => ({
default: ({ src, alt, ...props }: any) => (
// eslint-disable-next-line @next/next/no-img-element
<img src={typeof src === "string" ? src : ""} alt={alt} {...props} />
),
}));
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/app/papers/[id]/__tests__/badge-snippet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ vi.mock("@/components/toast", () => ({
},
}));

vi.mock("next/image", () => ({
// eslint-disable-next-line @next/next/no-img-element
default: ({ unoptimized: _unoptimized, ...props }: any) => <img {...props} />,
}));

describe("BadgeSnippet", () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down
2 changes: 0 additions & 2 deletions apps/web/src/app/papers/[id]/__tests__/page-metadata.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ describe("papers/[id]/page metadata", () => {
expect(metadata.openGraph?.title).toBe("成果物詳細 | OpenShelf");
});


it("generates generic metadata when id is invalid", async () => {
// Generate metadata without await as the component is sync for generating metadata via props
const metadata = await generateMetadata({ params: { id: "../bad" } });
expect(metadata.title).toBe("成果物詳細 | OpenShelf");
});

});
Loading
Loading