chore(schema): delete tautology test, add helpers, collapse styleRuleSlots

- Delete templates.test.ts: re-tests z.enum semantics with a hardcoded
  fixture that duplicates the template list in templates.ts.
- default.ts: add 2-line section(icon) helper; 12 repeated 7-field blocks
  collapse to one-liners. Output is byte-identical.
- data.ts: add 2-line itemSection<T> factory; 12 baseSectionSchema.extend()
  blocks collapse to one-liners. Inferred types unchanged.
- data.ts: replace 15-field hand-listed styleRuleSlotsSchema with
  z.partialRecord(styleSlotSchema, styleIntentSchema); parse behaviour and
  TypeScript type are equivalent (unknown keys still rejected).

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
This commit is contained in:
Amruth Pillai
2026-07-04 19:38:01 +02:00
parent 8de15822fb
commit 7e35e8b657
3 changed files with 46 additions and 223 deletions
+23 -63
View File
@@ -297,53 +297,28 @@ export const baseSectionSchema = z.object({
startOnNewPage: z.boolean().catch(false).describe("If true, the section always begins on a new page."),
});
const awardsSectionSchema = baseSectionSchema.extend({
items: z.array(awardItemSchema).describe("The items to display in the awards section."),
});
// ponytail: 12 identical baseSectionSchema.extend({ items }) blocks collapsed to a factory
const itemSection = <T extends z.ZodTypeAny>(itemSchema: T, description: string) =>
baseSectionSchema.extend({ items: z.array(itemSchema).describe(description) });
const certificationsSectionSchema = baseSectionSchema.extend({
items: z.array(certificationItemSchema).describe("The items to display in the certifications section."),
});
const educationSectionSchema = baseSectionSchema.extend({
items: z.array(educationItemSchema).describe("The items to display in the education section."),
});
const experienceSectionSchema = baseSectionSchema.extend({
items: z.array(experienceItemSchema).describe("The items to display in the experience section."),
});
const interestsSectionSchema = baseSectionSchema.extend({
items: z.array(interestItemSchema).describe("The items to display in the interests section."),
});
const languagesSectionSchema = baseSectionSchema.extend({
items: z.array(languageItemSchema).describe("The items to display in the languages section."),
});
const profilesSectionSchema = baseSectionSchema.extend({
items: z.array(profileItemSchema).describe("The items to display in the profiles section."),
});
const projectsSectionSchema = baseSectionSchema.extend({
items: z.array(projectItemSchema).describe("The items to display in the projects section."),
});
const publicationsSectionSchema = baseSectionSchema.extend({
items: z.array(publicationItemSchema).describe("The items to display in the publications section."),
});
const referencesSectionSchema = baseSectionSchema.extend({
items: z.array(referenceItemSchema).describe("The items to display in the references section."),
});
const skillsSectionSchema = baseSectionSchema.extend({
items: z.array(skillItemSchema).describe("The items to display in the skills section."),
});
const volunteerSectionSchema = baseSectionSchema.extend({
items: z.array(volunteerItemSchema).describe("The items to display in the volunteer section."),
});
const awardsSectionSchema = itemSection(awardItemSchema, "The items to display in the awards section.");
const certificationsSectionSchema = itemSection(
certificationItemSchema,
"The items to display in the certifications section.",
);
const educationSectionSchema = itemSection(educationItemSchema, "The items to display in the education section.");
const experienceSectionSchema = itemSection(experienceItemSchema, "The items to display in the experience section.");
const interestsSectionSchema = itemSection(interestItemSchema, "The items to display in the interests section.");
const languagesSectionSchema = itemSection(languageItemSchema, "The items to display in the languages section.");
const profilesSectionSchema = itemSection(profileItemSchema, "The items to display in the profiles section.");
const projectsSectionSchema = itemSection(projectItemSchema, "The items to display in the projects section.");
const publicationsSectionSchema = itemSection(
publicationItemSchema,
"The items to display in the publications section.",
);
const referencesSectionSchema = itemSection(referenceItemSchema, "The items to display in the references section.");
const skillsSectionSchema = itemSection(skillItemSchema, "The items to display in the skills section.");
const volunteerSectionSchema = itemSection(volunteerItemSchema, "The items to display in the volunteer section.");
const sectionsSchema = z.object({
profiles: profilesSectionSchema.describe("The section to display the profiles of the author."),
@@ -573,24 +548,9 @@ const styleIntentSchema = z
export type StyleIntent = z.infer<typeof styleIntentSchema>;
// ponytail: 15 hand-listed optional slots collapsed to partialRecord; unknown keys still rejected
const styleRuleSlotsSchema = z
.strictObject({
section: styleIntentSchema.optional(),
heading: styleIntentSchema.optional(),
item: styleIntentSchema.optional(),
text: styleIntentSchema.optional(),
secondaryText: styleIntentSchema.optional(),
link: styleIntentSchema.optional(),
icon: styleIntentSchema.optional(),
level: styleIntentSchema.optional(),
richParagraph: styleIntentSchema.optional(),
richList: styleIntentSchema.optional(),
richListItemRow: styleIntentSchema.optional(),
richListItemContent: styleIntentSchema.optional(),
richLink: styleIntentSchema.optional(),
richBold: styleIntentSchema.optional(),
richMark: styleIntentSchema.optional(),
})
.partialRecord(styleSlotSchema, styleIntentSchema)
.refine((slots) => Object.values(slots).some(Boolean), {
message: "At least one style slot must be configured.",
});
+23 -108
View File
@@ -1,5 +1,16 @@
import type { ResumeData } from "./data";
// ponytail: 12 identical 7-field section blocks collapsed to a helper
const section = (icon: string) => ({
title: "",
icon,
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [] as never[],
});
export const defaultResumeData: ResumeData = {
picture: {
hidden: false,
@@ -32,114 +43,18 @@ export const defaultResumeData: ResumeData = {
content: "",
},
sections: {
profiles: {
title: "",
icon: "messenger-logo",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
experience: {
title: "",
icon: "briefcase",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
education: {
title: "",
icon: "graduation-cap",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
projects: {
title: "",
icon: "code-simple",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
skills: {
title: "",
icon: "compass-tool",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
languages: {
title: "",
icon: "translate",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
interests: {
title: "",
icon: "football",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
awards: {
title: "",
icon: "trophy",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
certifications: {
title: "",
icon: "certificate",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
publications: {
title: "",
icon: "books",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
volunteer: {
title: "",
icon: "hand-heart",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
references: {
title: "",
icon: "phone",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [],
},
profiles: section("messenger-logo"),
experience: section("briefcase"),
education: section("graduation-cap"),
projects: section("code-simple"),
skills: section("compass-tool"),
languages: section("translate"),
interests: section("football"),
awards: section("trophy"),
certifications: section("certificate"),
publications: section("books"),
volunteer: section("hand-heart"),
references: section("phone"),
},
customSections: [],
metadata: {
-52
View File
@@ -1,52 +0,0 @@
import { describe, expect, it } from "vitest";
import { templateSchema } from "./templates";
describe("templateSchema", () => {
it("accepts known template names", () => {
const validTemplates = [
"azurill",
"bronzor",
"chikorita",
"ditgar",
"ditto",
"gengar",
"glalie",
"kakuna",
"lapras",
"leafish",
"meowth",
"onyx",
"pikachu",
"rhyhorn",
"scizor",
];
for (const t of validTemplates) {
expect(templateSchema.safeParse(t).success).toBe(true);
}
});
it("rejects unknown template names", () => {
expect(templateSchema.safeParse("unknown").success).toBe(false);
expect(templateSchema.safeParse("").success).toBe(false);
expect(templateSchema.safeParse("ONYX").success).toBe(false); // case-sensitive
});
it("rejects non-string values", () => {
expect(templateSchema.safeParse(null).success).toBe(false);
expect(templateSchema.safeParse(undefined).success).toBe(false);
expect(templateSchema.safeParse(42).success).toBe(false);
});
it("returns the exact value for a valid template", () => {
const result = templateSchema.safeParse("onyx");
expect(result.success).toBe(true);
if (result.success) {
expect(result.data).toBe("onyx");
}
});
it("includes 14 templates", () => {
const validTemplates = templateSchema.options;
expect(validTemplates).toHaveLength(15);
});
});