mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-14 23:07:01 +10:00
Filter invalid style intents to preserve valid custom styles (#3241)
* Filter invalid style intents to preserve valid custom styles * fix: preserve valid custom style rules --------- Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
committed by
GitHub
parent
9085a199cf
commit
689e7e24d4
@@ -318,11 +318,75 @@ describe("summarySchema", () => {
|
||||
});
|
||||
|
||||
describe("styleRulesSchema", () => {
|
||||
const educationHeadingRule = {
|
||||
id: "education-heading",
|
||||
label: "Education heading",
|
||||
enabled: true,
|
||||
target: { scope: "sectionType", sectionType: "education" },
|
||||
slots: { heading: { fontSize: 20 } },
|
||||
};
|
||||
|
||||
it("defaults to an empty rule list on default resume metadata", () => {
|
||||
expect(defaultResumeData.metadata.styleRules).toEqual([]);
|
||||
expect(styleRulesSchema.safeParse(defaultResumeData.metadata.styleRules).success).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves valid rules when another rule has an invalid style intent", () => {
|
||||
expect(
|
||||
styleRulesSchema.parse([
|
||||
educationHeadingRule,
|
||||
{
|
||||
id: "experience-heading",
|
||||
label: "Experience heading",
|
||||
enabled: true,
|
||||
target: { scope: "sectionType", sectionType: "experience" },
|
||||
slots: { heading: { fontSize: -1 } },
|
||||
},
|
||||
]),
|
||||
).toEqual([educationHeadingRule]);
|
||||
});
|
||||
|
||||
it("drops invalid style fields without dropping the whole rule", () => {
|
||||
expect(
|
||||
styleRulesSchema.parse([
|
||||
{
|
||||
id: "partial-heading",
|
||||
label: "Partial heading",
|
||||
enabled: true,
|
||||
target: { scope: "global" },
|
||||
slots: { heading: { color: "rgba(0, 0, 0, 1)", fontSize: -1 } },
|
||||
},
|
||||
]),
|
||||
).toEqual([
|
||||
{
|
||||
id: "partial-heading",
|
||||
label: "Partial heading",
|
||||
enabled: true,
|
||||
target: { scope: "global" },
|
||||
slots: { heading: { color: "rgba(0, 0, 0, 1)" } },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to an empty rule list for non-array persisted values", () => {
|
||||
expect(styleRulesSchema.parse({})).toEqual([]);
|
||||
});
|
||||
|
||||
it("drops rules with unknown slot keys", () => {
|
||||
expect(
|
||||
styleRulesSchema.parse([
|
||||
{
|
||||
id: "unknown-slot",
|
||||
label: "Unknown slot",
|
||||
enabled: true,
|
||||
target: { scope: "global" },
|
||||
slots: { richListItemMarker: { color: "rgba(0, 0, 0, 1)" } },
|
||||
},
|
||||
educationHeadingRule,
|
||||
]),
|
||||
).toEqual([educationHeadingRule]);
|
||||
});
|
||||
|
||||
it("accepts global, section-type, and section-id targets", () => {
|
||||
const rules = [
|
||||
{
|
||||
|
||||
@@ -569,7 +569,47 @@ export const styleRuleSchema = z.strictObject({
|
||||
slots: styleRuleSlotsSchema.describe("The semantic style slots configured by this rule."),
|
||||
});
|
||||
|
||||
export const styleRulesSchema = z.array(styleRuleSchema).catch([]);
|
||||
const filterStyleIntent = (intent: unknown): StyleIntent | undefined => {
|
||||
const styleIntentShape = styleIntentSchema.shape;
|
||||
if (typeof intent !== "object" || intent === null) return undefined;
|
||||
const filteredIntent = Object.entries(intent).filter(([key, value]) => {
|
||||
const fieldSchema = styleIntentSchema.shape[key as keyof typeof styleIntentShape];
|
||||
if (!fieldSchema) return false;
|
||||
return fieldSchema.safeParse(value).success;
|
||||
});
|
||||
return filteredIntent.length > 0 ? (Object.fromEntries(filteredIntent) as StyleIntent) : undefined;
|
||||
};
|
||||
|
||||
export const styleRulesSchema = z
|
||||
.array(z.unknown())
|
||||
.transform((arr) =>
|
||||
arr
|
||||
.map((item) => {
|
||||
const base = z
|
||||
.strictObject({
|
||||
id: z.string().min(1),
|
||||
label: z.string().catch(""),
|
||||
enabled: z.boolean().catch(true),
|
||||
target: styleRuleTargetSchema,
|
||||
slots: z.partialRecord(styleSlotSchema, z.unknown()),
|
||||
})
|
||||
.safeParse(item);
|
||||
|
||||
if (!base.success) return undefined;
|
||||
|
||||
const cleanedSlots = Object.fromEntries(
|
||||
Object.entries(base.data.slots)
|
||||
.map(([slot, intent]) => [slot, filterStyleIntent(intent)])
|
||||
.filter((entry): entry is [string, StyleIntent] => entry[1] !== undefined),
|
||||
);
|
||||
|
||||
if (Object.keys(cleanedSlots).length === 0) return undefined;
|
||||
|
||||
return { ...base.data, slots: cleanedSlots };
|
||||
})
|
||||
.filter((rule): rule is StyleRule => rule !== undefined),
|
||||
)
|
||||
.catch([]);
|
||||
|
||||
export type StyleRule = z.infer<typeof styleRuleSchema>;
|
||||
export type StyleRuleTarget = z.infer<typeof styleRuleTargetSchema>;
|
||||
|
||||
Reference in New Issue
Block a user