mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-24 07:12:18 +10:00
Squashed commit of the following:
commit b2b0470a1d9267d042ec0ac66523c6635bf5b199
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 13:13:38 2026 +0200
chore: update .gitignore to include .vite-hooks and modify pnpm-lock.yaml for dependencies
commit d28fadb5cd8706c874e616102878b4a394ec84c1
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 13:08:04 2026 +0200
fix: remove timestamp conflict guard
commit c6998d9dbab19d09d3c8054feef1d2e4117555eb
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 12:11:51 2026 +0200
chore(release): v5.1.5
commit f33d168711804880e1f12e88d24290aae16cc258
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 11:58:35 2026 +0200
revert: compose.yml
commit d961e6535811a10c335525fb33a08d03e737278d
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 11:58:08 2026 +0200
refactor(agent): replace 'revert' terminology with 'restore' for clarity, resolves #3086
commit 17f351171be218e33f01c469d95e4164d4c8dc57
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 11:10:41 2026 +0200
refactor(pdf): simplify sidebar section filtering and update summary feature logic
commit d55179b9d76879e3204de185e8b53fadd0a107ed
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:53:37 2026 +0200
chore: update pnpm-lock.yaml and turbo.json
commit 7cade6980e1a04352536bd44ef773f338c4ef599
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:38:30 2026 +0200
fix(polyfill): add tested polyfill for Map Upsert methods
commit 26d175bb9c53d93225d1e907678445252c13d660
Merge: 1cf33dc6c 5b1297fa2
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:23:29 2026 +0200
Merge remote-tracking branch 'origin/main' into feat/explore-hono-orpc-migration
# Conflicts:
# packages/api/src/services/agent-url.ts
# packages/runtime-externals/package.json
commit 1cf33dc6c9d81735730ad656e16dab6501c6d6a1
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Tue May 19 09:22:12 2026 +0200
chore: preserve branch changes before main sync
commit b380a4b00fdbcdd81ff4f8ef72b330fd027ccda5
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Mon May 18 07:50:28 2026 +0200
chore: lot of fixes for monorepo migration
commit 8fcf0ec64e1c29572ebaff494338368bfcf75760
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 13:57:17 2026 +0200
chore: update knip version and refine web app routing with new SEO endpoints
commit 234e68086ff15610a93877354c98e2c020364533
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 12:10:06 2026 +0200
refactor(auth): update OAuth routes to include API prefix and remove unused schema endpoint
commit 91c84b9a8496b0ce21d71cae9f8b2a027638c9ac
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:54:29 2026 +0200
chore: update dependencies and enhance PWA metadata in web app
commit 150117d4a5a9dd6cd92c64891aad8cae90f6a7af
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:12:35 2026 +0200
docs: revise manifest-only pwa testing scope
commit 6b939a55661aec9dd8122b184e4b60a5c7325fb5
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:11:33 2026 +0200
docs: add manifest-only pwa design
commit 1422e1fc96c400948b273210a1067251087d15d4
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:05:04 2026 +0200
chore(dev): simplify server proxy config
commit bc2ff5a9f6fda41e6c40333c8f163aa23a6c5e48
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:04:50 2026 +0200
docs: add unsafe oauth redirect plan
commit 445359ebe9b96c1515bf1c4c3f73ba8a8448ec12
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 11:04:34 2026 +0200
feat(auth): add unsafe oauth redirect flag
commit 73fffdd24598e56b2793f7657919bc794835892e
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 10:55:02 2026 +0200
docs: design unsafe oauth redirect flag
commit c0066aa19c15fc8a4c8e5179ed49889c117519f4
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 10:22:04 2026 +0200
chore: update translation source paths
commit 9033da082418d252aafd6c2eed72f71f014be3d9
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 10:09:25 2026 +0200
refactor(arch): react spa + hono migration
commit 6f27936c11bda895977dc63ee550c3346d4ce24b
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Fri May 15 01:10:47 2026 +0200
docs: add docker nightly tagging design
commit ecc1fd9a88a0ee1dca2f1977dfc17f74527fe1da
Author: Amruth Pillai <im.amruth@gmail.com>
Date: Thu May 14 20:05:44 2026 +0200
feat: migrate to hono spa server
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
|
||||
import { applyResumePatches, createResumePatches, jsonPatchOperationSchema, ResumePatchError } from "./patch";
|
||||
|
||||
describe("jsonPatchOperationSchema", () => {
|
||||
it("validates add op", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "add", path: "/foo", value: 1 });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("requires value for add", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "add", path: "/foo" });
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("validates remove op without value", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "remove", path: "/foo" });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("validates replace op", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "replace", path: "/foo", value: 2 });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("validates move op with from", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "move", path: "/a", from: "/b" });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("requires from for move", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "move", path: "/a" });
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("validates copy op with from", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "copy", path: "/a", from: "/b" });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("validates test op with value", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "test", path: "/a", value: 1 });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects unknown op", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "swap", path: "/a", value: 1 });
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("permits any value type for add (unknown)", () => {
|
||||
const result = jsonPatchOperationSchema.safeParse({ op: "add", path: "/x", value: { nested: { array: [1] } } });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createResumePatches", () => {
|
||||
it("returns empty array when documents are equal", () => {
|
||||
const patches = createResumePatches(defaultResumeData, defaultResumeData);
|
||||
expect(patches).toEqual([]);
|
||||
});
|
||||
|
||||
it("emits a replace op when a scalar field changes", () => {
|
||||
const next = { ...defaultResumeData, basics: { ...defaultResumeData.basics, name: "Alice" } };
|
||||
const patches = createResumePatches(defaultResumeData, next);
|
||||
|
||||
expect(patches.some((p) => p.op === "replace" && p.path === "/basics/name")).toBe(true);
|
||||
});
|
||||
|
||||
it("captures multiple changes in distinct operations", () => {
|
||||
const next = {
|
||||
...defaultResumeData,
|
||||
basics: { ...defaultResumeData.basics, name: "Alice", email: "alice@example.com" },
|
||||
};
|
||||
const patches = createResumePatches(defaultResumeData, next);
|
||||
|
||||
expect(patches.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyResumePatches", () => {
|
||||
it("applies a single replace op", () => {
|
||||
const result = applyResumePatches(defaultResumeData, [{ op: "replace", path: "/basics/name", value: "Alice" }]);
|
||||
|
||||
expect(result.basics.name).toBe("Alice");
|
||||
// Original is preserved (not mutated)
|
||||
expect(defaultResumeData.basics.name).toBe("");
|
||||
});
|
||||
|
||||
it("does not mutate the input data", () => {
|
||||
const before = JSON.stringify(defaultResumeData);
|
||||
applyResumePatches(defaultResumeData, [{ op: "replace", path: "/basics/name", value: "Bob" }]);
|
||||
expect(JSON.stringify(defaultResumeData)).toBe(before);
|
||||
});
|
||||
|
||||
it("applies multiple ops in sequence", () => {
|
||||
const result = applyResumePatches(defaultResumeData, [
|
||||
{ op: "replace", path: "/basics/name", value: "Alice" },
|
||||
{ op: "replace", path: "/basics/email", value: "alice@example.com" },
|
||||
]);
|
||||
|
||||
expect(result.basics.name).toBe("Alice");
|
||||
expect(result.basics.email).toBe("alice@example.com");
|
||||
});
|
||||
|
||||
it("supports replacing the root document", () => {
|
||||
const snapshot = { ...defaultResumeData, basics: { ...defaultResumeData.basics, name: "Snapshot Name" } };
|
||||
|
||||
const result = applyResumePatches(defaultResumeData, [{ op: "replace", path: "", value: snapshot }]);
|
||||
|
||||
expect(result.basics.name).toBe("Snapshot Name");
|
||||
});
|
||||
|
||||
it("throws ResumePatchError for unresolvable path", () => {
|
||||
expect(() =>
|
||||
applyResumePatches(defaultResumeData, [{ op: "replace", path: "/does/not/exist", value: "x" }]),
|
||||
).toThrow(ResumePatchError);
|
||||
});
|
||||
|
||||
it("throws ResumePatchError for failed test op", () => {
|
||||
try {
|
||||
applyResumePatches(defaultResumeData, [{ op: "test", path: "/basics/name", value: "wrong-value" }]);
|
||||
expect.unreachable();
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ResumePatchError);
|
||||
const patchError = error as ResumePatchError;
|
||||
expect(patchError.code).toBe("TEST_OPERATION_FAILED");
|
||||
expect(patchError.index).toBe(0);
|
||||
expect(patchError.message).toContain("Test operation failed");
|
||||
}
|
||||
});
|
||||
|
||||
it("includes the failing operation in the error", () => {
|
||||
try {
|
||||
applyResumePatches(defaultResumeData, [{ op: "test", path: "/basics/name", value: "wrong" }]);
|
||||
expect.unreachable();
|
||||
} catch (error) {
|
||||
expect((error as ResumePatchError).operation).toEqual({
|
||||
op: "test",
|
||||
path: "/basics/name",
|
||||
value: "wrong",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("rolls back if patched document fails schema validation", () => {
|
||||
// Setting picture.size to a value outside schema bounds (min 32, max 512)
|
||||
expect(() =>
|
||||
applyResumePatches(defaultResumeData, [{ op: "replace", path: "/picture/size", value: 9999 }]),
|
||||
).toThrow(/Patch produced invalid resume data/);
|
||||
});
|
||||
|
||||
it("supports adding to arrays", () => {
|
||||
const result = applyResumePatches(defaultResumeData, [
|
||||
{
|
||||
op: "add",
|
||||
path: "/sections/skills/items/-",
|
||||
value: {
|
||||
id: "abcdef0123456789",
|
||||
hidden: false,
|
||||
icon: "",
|
||||
iconColor: "",
|
||||
name: "TypeScript",
|
||||
proficiency: "Advanced",
|
||||
level: 4,
|
||||
keywords: [],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.sections.skills.items).toHaveLength(1);
|
||||
expect(result.sections.skills.items[0]?.name).toBe("TypeScript");
|
||||
});
|
||||
|
||||
it("supports remove operation on existing path", () => {
|
||||
// First add an item, then remove it
|
||||
const withItem = applyResumePatches(defaultResumeData, [
|
||||
{
|
||||
op: "add",
|
||||
path: "/sections/skills/items/-",
|
||||
value: {
|
||||
id: "abcdef0123456789",
|
||||
hidden: false,
|
||||
icon: "",
|
||||
iconColor: "",
|
||||
name: "Go",
|
||||
proficiency: "Advanced",
|
||||
level: 4,
|
||||
keywords: [],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const removed = applyResumePatches(withItem, [{ op: "remove", path: "/sections/skills/items/0" }]);
|
||||
expect(removed.sections.skills.items).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ResumePatchError", () => {
|
||||
it("captures all expected properties", () => {
|
||||
const error = new ResumePatchError("CODE", "msg", 2, { op: "test", path: "/x", value: 1 });
|
||||
expect(error.name).toBe("ResumePatchError");
|
||||
expect(error.code).toBe("CODE");
|
||||
expect(error.message).toBe("msg");
|
||||
expect(error.index).toBe(2);
|
||||
expect(error.operation).toEqual({ op: "test", path: "/x", value: 1 });
|
||||
});
|
||||
|
||||
it("is instanceof Error", () => {
|
||||
const error = new ResumePatchError("X", "y", 0, { op: "remove", path: "/x" });
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user