pixalynx/edge-connector
Configures USB-A, Micro-USB, and USB-C PCB-edge connectors with exposed copper contacts and automatically shaped, positioned board tongues.
- Version
- 1.0.0
- License
- GPL-3.0-only
- Stars
- 0
EdgeConnectors.test.mjs
import { expect, test } from "bun:test"
import { readFileSync } from "node:fs"
import { jsx } from "react/jsx-runtime"
import { Circuit } from "tscircuit"
import { UsbCEdgeConnector, MicroUsbEdgeConnector } from "./index.tsx"
import UsbCExample from "./examples/usb-c.circuit.tsx"
import MicroExample from "./examples/micro-usb.circuit.tsx"
async function render(element) {
const circuit = new Circuit()
circuit.add(element)
await circuit.renderUntilSettled()
return circuit.getCircuitJson()
}
function sourcePads(filename) {
const source = readFileSync(new URL(`./vendor/pcb-edge-usb/${filename}`, import.meta.url), "utf8")
const regex = /\(pad "([^"]+)" smd rect\s+\(at ([-\d.]+) ([-\d.]+) ([-\d.]+)\)\s+\(size ([-\d.]+) ([-\d.]+)\)\s+\(layers "([FB]).Cu"/g
return [...source.matchAll(regex)].map(([, pin, x, y, rotation, width, height, side]) => ({
pin, x: Number(x), y: Number(y), rotation: Number(rotation),
width: Number(width), height: Number(height), layer: side === "F" ? "top" : "bottom",
}))
}
const variants = [
{ name: "USB-C", component: UsbCEdgeConnector, example: UsbCExample, count: 24, shift: 2.4,
file: "USB_C_PCB_receptacle_24pin_1mm-cutout.kicad_mod" },
{ name: "Micro-B", component: MicroUsbEdgeConnector, example: MicroExample, count: 5, shift: 0,
file: "USB_Micro-B_PCB_receptacle_5pin_2mm_cutout.kicad_mod" },
]
for (const variant of variants) {
for (const rotation of [0, 90]) {
test(`${variant.name}: every pad matches upstream geometry and face at ${rotation} degrees`, async () => {
const json = await render(jsx("board", { width: 30, height: 30,
children: jsx(variant.component, { name: "J1", pcbX: 3, pcbY: 2, pcbRotation: rotation }),
}))
const pads = json.filter(e => e.type === "pcb_smtpad")
const expected = sourcePads(variant.file)
expect(expected).toHaveLength(variant.count)
expect(pads).toHaveLength(variant.count)
expect(json.filter(e => e.type === "pcb_solder_paste")).toHaveLength(0)
const sourceIds = new Set()
for (const source of expected) {
const pad = pads.find(p => p.port_hints.includes(source.pin))
const x = -source.y
const y = -source.x - variant.shift
expect(pad.x).toBeCloseTo(3 + (rotation === 0 ? x : -y), 5)
expect(pad.y).toBeCloseTo(2 + (rotation === 0 ? y : x), 5)
expect(pad.width).toBeCloseTo(rotation === 0 ? source.width : source.height)
expect(pad.height).toBeCloseTo(rotation === 0 ? source.height : source.width)
expect(pad.layer).toBe(source.layer)
expect(pad.is_covered_with_solder_mask).toBe(false)
const port = json.find(e => e.type === "pcb_port" && e.pcb_port_id === pad.pcb_port_id)
expect(port.layers).toEqual([source.layer])
const sourcePort = json.find(e => e.type === "source_port" && e.source_port_id === port.source_port_id)
expect(sourcePort.port_hints).toContain(source.pin)
sourceIds.add(sourcePort.source_port_id)
}
expect(sourceIds.size).toBe(variant.count)
expect(json.filter(e => /error$/.test(e.type))).toEqual([])
})
}
test(`${variant.name}: example retains cutouts and keeps contact corners inside the PCB`, async () => {
const json = await render(jsx(variant.example, {}))
const board = json.find(e => e.type === "pcb_board")
expect(board.thickness).toBe(0.6)
expect(board.outline.length).toBeGreaterThan(80)
const inside = (x, y) => {
let result = false
for (let i = 0, j = board.outline.length - 1; i < board.outline.length; j = i++) {
const a = board.outline[i], b = board.outline[j]
if ((a.y > y) !== (b.y > y) && x < (b.x-a.x)*(y-a.y)/(b.y-a.y)+a.x) result = !result
}
return result
}
for (const p of json.filter(e => e.type === "pcb_smtpad")) {
for (const sx of [-1, 1]) for (const sy of [-1, 1]) {
expect(inside(p.x + sx*p.width/2, p.y + sy*p.height/2)).toBe(true)
}
}
expect(json.filter(e => /error$/.test(e.type))).toEqual([])
})
}
test("USB-C connections keep CC1, CC2 and the two USB2 data contacts distinct", async () => {
const json = await render(jsx("board", { width: 30, height: 30,
children: jsx(UsbCEdgeConnector, { name: "J1", connections: {
CC1: "net.CC_ONE", CC2: "net.CC_TWO", A6: "net.DP_TOP", B6: "net.DP_BOTTOM",
} }),
}))
for (const [pin, net] of [["A5", "CC_ONE"], ["B5", "CC_TWO"], ["A6", "DP_TOP"], ["B6", "DP_BOTTOM"]]) {
const port = json.find(e => e.type === "source_port" && e.name === pin)
const target = json.find(e => e.type === "source_net" && e.name === net)
const trace = json.find(e => e.type === "source_trace" && e.connected_source_port_ids.includes(port.source_port_id))
expect(trace.connected_source_net_ids).toEqual([target.source_net_id])
}
expect(json.filter(e => e.type === "source_trace")).toHaveLength(4)
})