From 3eed206d0e58f819049318ceb767beb053816c30 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sun, 26 Jul 2026 06:08:30 +0900 Subject: [PATCH] fix: validate fuse connections against its pin labels (#754) --- lib/components/fuse.ts | 17 +++++------- tests/fuse-connections.test.ts | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 tests/fuse-connections.test.ts diff --git a/lib/components/fuse.ts b/lib/components/fuse.ts index 1a4798b8..99ee34b3 100644 --- a/lib/components/fuse.ts +++ b/lib/components/fuse.ts @@ -1,4 +1,5 @@ import { z } from "zod" +import { createConnectionsProp } from "lib/common/connectionsProp" import { type CommonComponentProps, commonComponentProps, @@ -8,6 +9,7 @@ import { type SchematicOrientation, } from "lib/common/schematicOrientation" import type { Connections } from "lib/utility-types/connections-and-selectors" +import { expectTypesMatch } from "lib/typecheck" /** * Pin labels for fuse component @@ -38,7 +40,7 @@ export interface FuseProps /** * Connections to other components */ - connections?: Connections + connections?: Connections } /** @@ -49,16 +51,9 @@ export const fuseProps = commonComponentProps.extend({ voltageRating: z.union([z.number(), z.string()]).optional(), schShowRatings: z.boolean().optional(), schOrientation: schematicOrientation.optional(), - connections: z - .record( - z.string(), - z.union([ - z.string(), - z.array(z.string()).readonly(), - z.array(z.string()), - ]), - ) - .optional(), + connections: createConnectionsProp(fusePinLabels).optional(), }) export type InferredFuseProps = z.input + +expectTypesMatch(true) diff --git a/tests/fuse-connections.test.ts b/tests/fuse-connections.test.ts new file mode 100644 index 00000000..168459a6 --- /dev/null +++ b/tests/fuse-connections.test.ts @@ -0,0 +1,48 @@ +import { expect, test } from "bun:test" +import { capacitorProps } from "lib/components/capacitor" +import { fuseProps } from "lib/components/fuse" + +const parseFuse = (connections: unknown) => + fuseProps.safeParse({ name: "F1", currentRating: "1A", connections }) + +test("fuse accepts its real pin labels in connections", () => { + expect(parseFuse({ pin1: ".R1 > .pin1" }).success).toBe(true) + expect(parseFuse({ pin2: ".R1 > .pin1" }).success).toBe(true) + expect(parseFuse({ pin1: ".R1 > .pin1", pin2: ".R2 > .pin1" }).success).toBe( + true, + ) +}) + +test("fuse rejects a pin label it does not have", () => { + // Previously accepted: the schema used a bare z.record(z.string(), ...) so + // any key at all passed, including a typo'd pin that the fuse has no port for. + expect(parseFuse({ pin99: ".R1 > .pin1" }).success).toBe(false) + expect(parseFuse({ "not a pin at all": ".R1 > .pin1" }).success).toBe(false) + expect(parseFuse({ "": ".R1 > .pin1" }).success).toBe(false) +}) + +test("fuse validates connections the same way the other two-pin passives do", () => { + // Same typo, same shape of component — these must agree. + const typo = { pin99: ".R1 > .pin1" } + + expect(parseFuse(typo).success).toBe( + capacitorProps.safeParse({ + name: "C1", + capacitance: "1uF", + connections: typo, + }).success, + ) +}) + +test("fuse still accepts array and readonly-array connection targets", () => { + expect(parseFuse({ pin1: [".R1 > .pin1", ".R2 > .pin1"] }).success).toBe(true) + expect( + parseFuse({ pin1: [".R1 > .pin1"] as readonly string[] }).success, + ).toBe(true) +}) + +test("connections stays optional on fuse", () => { + expect(fuseProps.safeParse({ name: "F1", currentRating: "1A" }).success).toBe( + true, + ) +})