This commit is contained in:
Amruth Pillai
2026-04-02 00:14:54 +02:00
parent d9a24448e8
commit 4fd43657dc
175 changed files with 11886 additions and 1840 deletions
@@ -0,0 +1,95 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vite-plus/test";
afterEach(cleanup);
import { LinkedTitle } from "./linked-title";
describe("LinkedTitle", () => {
describe("renders as link when showLinkInTitle and website.url are provided", () => {
it("renders an anchor tag", () => {
render(
<LinkedTitle title="Acme Corp" website={{ url: "https://acme.com", label: "Acme" }} showLinkInTitle={true} />,
);
const link = screen.getByRole("link", { name: "Acme Corp" });
expect(link).toBeDefined();
expect(link.getAttribute("href")).toBe("https://acme.com");
});
it("sets target=_blank and rel=noopener on the link", () => {
render(
<LinkedTitle title="Company" website={{ url: "https://example.com", label: "" }} showLinkInTitle={true} />,
);
const link = screen.getByRole("link");
expect(link.getAttribute("target")).toBe("_blank");
expect(link.getAttribute("rel")).toBe("noopener");
});
it("wraps the title in a strong tag inside the link", () => {
render(
<LinkedTitle title="Bold Title" website={{ url: "https://example.com", label: "" }} showLinkInTitle={true} />,
);
const strong = screen.getByText("Bold Title");
expect(strong.tagName).toBe("STRONG");
});
});
describe("renders as plain strong tag otherwise", () => {
it("renders strong when showLinkInTitle is false", () => {
render(
<LinkedTitle title="Plain Title" website={{ url: "https://example.com", label: "" }} showLinkInTitle={false} />,
);
const strong = screen.getByText("Plain Title");
expect(strong.tagName).toBe("STRONG");
expect(screen.queryByRole("link")).toBeNull();
});
it("renders strong when showLinkInTitle is undefined", () => {
render(<LinkedTitle title="No Link" />);
expect(screen.getByText("No Link").tagName).toBe("STRONG");
expect(screen.queryByRole("link")).toBeNull();
});
it("renders strong when website is undefined", () => {
render(<LinkedTitle title="No Website" showLinkInTitle={true} />);
expect(screen.getByText("No Website").tagName).toBe("STRONG");
expect(screen.queryByRole("link")).toBeNull();
});
it("renders strong when website.url is empty", () => {
render(<LinkedTitle title="Empty URL" website={{ url: "", label: "" }} showLinkInTitle={true} />);
expect(screen.getByText("Empty URL").tagName).toBe("STRONG");
expect(screen.queryByRole("link")).toBeNull();
});
});
describe("className prop", () => {
it("applies className to the link element", () => {
render(
<LinkedTitle
title="Styled"
website={{ url: "https://x.com", label: "" }}
showLinkInTitle={true}
className="custom-class"
/>,
);
const link = screen.getByRole("link");
expect(link.className).toContain("custom-class");
});
it("applies className to the strong element", () => {
render(<LinkedTitle title="Styled Strong" className="my-class" />);
const strong = screen.getByText("Styled Strong");
expect(strong.className).toContain("my-class");
});
});
});
+272
View File
@@ -0,0 +1,272 @@
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
// Mock dependencies before importing the store
vi.mock("@lingui/core/macro", () => ({
t: (strings: TemplateStringsArray) => strings[0],
}));
vi.mock("sonner", () => ({
toast: {
error: vi.fn(() => "toast-id"),
dismiss: vi.fn(),
},
}));
vi.mock("@/integrations/orpc/client", () => ({
orpc: {
resume: {
update: {
call: vi.fn(() => Promise.resolve()),
},
},
},
}));
import { defaultResumeData } from "@/schema/resume/data";
import { useResumeStore } from "./resume";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeResume(overrides?: Record<string, unknown>) {
return {
id: "test-resume-id",
name: "Test Resume",
slug: "test-resume",
tags: ["test"],
isLocked: false,
data: structuredClone(defaultResumeData),
...overrides,
};
}
afterEach(() => {
// Reset store between tests
useResumeStore.getState().initialize(null);
});
// ---------------------------------------------------------------------------
// initialize
// ---------------------------------------------------------------------------
describe("useResumeStore — initialize", () => {
it("sets resume and isReady to true", () => {
const resume = makeResume();
useResumeStore.getState().initialize(resume);
const state = useResumeStore.getState();
expect(state.isReady).toBe(true);
expect(state.resume.id).toBe("test-resume-id");
expect(state.resume.name).toBe("Test Resume");
});
it("sets isReady to false when initialized with null", () => {
useResumeStore.getState().initialize(null);
const state = useResumeStore.getState();
expect(state.isReady).toBe(false);
});
it("replaces previous resume data", () => {
useResumeStore.getState().initialize(makeResume({ name: "First" }));
expect(useResumeStore.getState().resume.name).toBe("First");
useResumeStore.getState().initialize(makeResume({ name: "Second" }));
expect(useResumeStore.getState().resume.name).toBe("Second");
});
});
// ---------------------------------------------------------------------------
// updateResumeData
// ---------------------------------------------------------------------------
describe("useResumeStore — updateResumeData", () => {
it("updates resume data via immer draft", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.name = "Jane Doe";
});
expect(useResumeStore.getState().resume.data.basics.name).toBe("Jane Doe");
});
it("can update nested fields", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.website.url = "https://example.com";
draft.basics.email = "jane@example.com";
});
expect(useResumeStore.getState().resume.data.basics.website.url).toBe("https://example.com");
expect(useResumeStore.getState().resume.data.basics.email).toBe("jane@example.com");
});
it("can add items to sections", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.sections.skills.items.push({
id: "s1",
hidden: false,
icon: "",
name: "TypeScript",
proficiency: "Advanced",
level: 4,
keywords: [],
});
});
expect(useResumeStore.getState().resume.data.sections.skills.items).toHaveLength(1);
expect(useResumeStore.getState().resume.data.sections.skills.items[0].name).toBe("TypeScript");
});
it("does not update when resume is not initialized", () => {
// Store starts with null resume
useResumeStore.getState().initialize(null);
// Should not throw
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.name = "Should not apply";
});
});
it("blocks updates when resume is locked", async () => {
const { toast } = await import("sonner");
useResumeStore.getState().initialize(makeResume({ isLocked: true }));
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.name = "Should not apply";
});
// Name should remain unchanged
expect(useResumeStore.getState().resume.data.basics.name).toBe("");
// Should show error toast
expect(toast.error).toHaveBeenCalled();
});
it("does not block updates on unlocked resume", () => {
useResumeStore.getState().initialize(makeResume({ isLocked: false }));
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.name = "Updated";
});
expect(useResumeStore.getState().resume.data.basics.name).toBe("Updated");
});
});
// ---------------------------------------------------------------------------
// Multiple updates
// ---------------------------------------------------------------------------
describe("useResumeStore — multiple updates", () => {
it("applies sequential updates correctly", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.name = "First Update";
});
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.headline = "Developer";
});
const data = useResumeStore.getState().resume.data;
expect(data.basics.name).toBe("First Update");
expect(data.basics.headline).toBe("Developer");
});
it("preserves unmodified sections across updates", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.basics.name = "Jane";
});
// Metadata and other sections should be untouched
expect(useResumeStore.getState().resume.data.metadata.template).toBe("onyx");
expect(useResumeStore.getState().resume.data.sections.skills.items).toHaveLength(0);
});
});
// ---------------------------------------------------------------------------
// Temporal store (undo/redo)
// ---------------------------------------------------------------------------
describe("useResumeStore — temporal", () => {
it("has a temporal store accessible", () => {
expect(useResumeStore.temporal).toBeDefined();
expect(useResumeStore.temporal.getState).toBeDefined();
});
it("temporal store has undo/redo actions", () => {
const temporal = useResumeStore.temporal.getState();
expect(typeof temporal.undo).toBe("function");
expect(typeof temporal.redo).toBe("function");
expect(typeof temporal.clear).toBe("function");
});
});
// ---------------------------------------------------------------------------
// Edge cases
// ---------------------------------------------------------------------------
describe("useResumeStore — edge cases", () => {
it("can update metadata fields", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.metadata.template = "pikachu";
});
expect(useResumeStore.getState().resume.data.metadata.template).toBe("pikachu");
});
it("can update picture settings", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.picture.url = "https://example.com/photo.jpg";
draft.picture.size = 120;
});
expect(useResumeStore.getState().resume.data.picture.url).toBe("https://example.com/photo.jpg");
expect(useResumeStore.getState().resume.data.picture.size).toBe(120);
});
it("can remove all items from a section", () => {
const resume = makeResume();
resume.data.sections.skills.items = [
{ id: "s1", hidden: false, icon: "", name: "JS", proficiency: "", level: 0, keywords: [] },
];
useResumeStore.getState().initialize(resume);
useResumeStore.getState().updateResumeData((draft) => {
draft.sections.skills.items = [];
});
expect(useResumeStore.getState().resume.data.sections.skills.items).toHaveLength(0);
});
it("can update custom sections", () => {
useResumeStore.getState().initialize(makeResume());
useResumeStore.getState().updateResumeData((draft) => {
draft.customSections.push({
id: "custom-1",
type: "experience",
title: "Freelance",
columns: 1,
hidden: false,
items: [],
});
});
expect(useResumeStore.getState().resume.data.customSections).toHaveLength(1);
expect(useResumeStore.getState().resume.data.customSections[0].title).toBe("Freelance");
});
});
+27 -2
View File
@@ -32,12 +32,37 @@ type ResumeStore = ResumeStoreState & ResumeStoreActions;
const controller = new AbortController();
const signal = controller.signal;
const _syncResume = (resume: Resume) => {
void orpc.resume.update.call({ id: resume.id, data: resume.data }, { signal });
let syncErrorToastId: string | number | undefined;
const _syncResume = async (resume: Resume) => {
try {
await orpc.resume.update.call({ id: resume.id, data: resume.data }, { signal });
// Dismiss error toast on successful sync
if (syncErrorToastId !== undefined) {
toast.dismiss(syncErrorToastId);
syncErrorToastId = undefined;
}
} catch (error: unknown) {
// Ignore aborted requests (e.g. page navigation)
if (error instanceof DOMException && error.name === "AbortError") return;
syncErrorToastId = toast.error(
t`Your latest changes could not be saved. Please make sure you are connected to the internet and try again.`,
{ id: syncErrorToastId, duration: Infinity },
);
}
};
const syncResume = debounce(_syncResume, 500, { signal });
// Flush pending sync before the page unloads to prevent data loss
if (typeof window !== "undefined") {
window.addEventListener("beforeunload", () => {
syncResume.flush();
});
}
let errorToastId: string | number | undefined;
type PartializedState = { resume: Resume | null };