Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1384,22 +1384,56 @@ export abstract class PrimitiveComponent<

getString(): string {
const { lowercaseComponentName: cname, _parsedProps: props, parent } = this
const id = this._getCircuitRelativeRenderId()
if (props?.pinNumber !== undefined && parent?.props?.name && props?.name) {
return `<${cname}#${this._renderId}(pin:${props.pinNumber} .${parent?.props.name}>.${props.name}) />`
return `<${cname}#${id}(pin:${props.pinNumber} .${parent?.props.name}>.${props.name}) />`
}
if (parent?.props?.name && props?.name) {
return `<${cname}#${this._renderId}(.${parent?.props.name}>.${props?.name}) />`
return `<${cname}#${id}(.${parent?.props.name}>.${props?.name}) />`
}
if (props?.from && props?.to) {
return `<${cname}#${this._renderId}(from:${props.from} to:${props?.to}) />`
return `<${cname}#${id}(from:${props.from} to:${props?.to}) />`
}
if (props?.name) {
return `<${cname}#${this._renderId} name=".${props?.name}" />`
return `<${cname}#${id} name=".${props?.name}" />`
}
if (props?.portHints) {
return `<${cname}#${this._renderId}(${props.portHints.map((ph: string) => `.${ph}`).join(", ")}) />`
return `<${cname}#${id}(${props.portHints.map((ph: string) => `.${ph}`).join(", ")}) />`
}
return `<${cname}#${this._renderId} />`
return `<${cname}#${id} />`
}

/**
* A render id that is stable for a given circuit.
*
* `_renderId` comes from a module-level counter that is never reset, so it
* keeps climbing across every circuit built in the same process. That leaks
* into user-facing warnings and errors via `getString()`, making the same
* board produce different messages on each render. This offsets it against
* the lowest id seen in the circuit, so the number is relative to the
* circuit rather than to the process.
*/
_getCircuitRelativeRenderId(): string {
const root = this.root as unknown as {
_renderIdOrigin?: number
children?: any[]
} | null
if (!root) return this._renderId

if (root._renderIdOrigin === undefined) {
let lowest = Number.POSITIVE_INFINITY
const visit = (node: any) => {
const value = Number(node?._renderId)
if (Number.isFinite(value)) lowest = Math.min(lowest, value)
for (const child of node?.children ?? []) visit(child)
}
for (const child of root.children ?? []) visit(child)
root._renderIdOrigin = Number.isFinite(lowest) ? lowest : 0
}

const relative = Number(this._renderId) - root._renderIdOrigin
if (!Number.isFinite(relative) || relative < 0) return this._renderId
return `${relative}`
}

getDisplayName(): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect, test } from "bun:test"
import { getTestFixture } from "tests/fixtures/get-test-fixture"

const buildAndGetWarning = async () => {
const { circuit } = getTestFixture()

circuit.add(
<board width="20mm" height="20mm" routingDisabled>
<resistor name="R1" resistance="1k" footprint="0402" pcbX={0} />
<net name="VCC" />
<trace from=".R1 > .pin1" to="net.VCC" />
</board>,
)

await circuit.renderUntilSettled()

const warning = circuit
.getCircuitJson()
.find((e: any) => e.type === "source_unnamed_trace_warning") as any

return warning?.message as string | undefined
}

test("warning messages are identical for identical circuits", async () => {
// `getString()` embeds `_renderId`, which comes from a module-level counter
// that is never reset. Rendering the same board three times in one process
// used to yield "trace#14", "trace#36", "trace#58" — so any consumer that
// snapshots or diffs warnings sees spurious changes.
const messages = [
await buildAndGetWarning(),
await buildAndGetWarning(),
await buildAndGetWarning(),
]

for (const message of messages) {
expect(message).toBeDefined()
expect(message).toContain("is missing a name")
}

expect(new Set(messages).size).toBe(1)
})

test("render ids stay distinct within a circuit", async () => {
const { circuit } = getTestFixture()

circuit.add(
<board width="30mm" height="30mm" routingDisabled>
<resistor name="R1" resistance="1k" footprint="0402" pcbX={-5} />
<resistor name="R2" resistance="2k" footprint="0402" pcbX={5} />
</board>,
)

await circuit.renderUntilSettled()

const strings = (circuit.selectAll("resistor") as any[]).map((r) =>
r.getString(),
)

// Making the id circuit-relative must not collapse distinct components onto
// the same identifier.
expect(strings.length).toBe(2)
expect(new Set(strings).size).toBe(2)
})
Loading