Skip to content
Merged
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
90 changes: 90 additions & 0 deletions tests/browser/pad-names.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Pad labels in the canvas tooltip and the detail pane's pad table.
// netdaq-small has a KiCad pad name on every pad, so a pad shows as the pin
// the netlist names (U24:11 is "pin 11"). ciaa-acc has no names, so its pads
// keep the old "pad N", N being the place in the footprint's pad list.

const { test, expect } = require('@playwright/test');
const fs = require('fs');
const path = require('path');

const BOARDS = {
named: {
page: 'file://' + path.join(__dirname, 'board.html'),
fixture: path.join(__dirname, '..', 'fixtures', 'netdaq-small.json'),
},
unnamed: {
page: 'file://' + path.join(__dirname, 'ciaa-acc.html'),
fixture: path.join(__dirname, '..', 'fixtures', 'ciaa-acc.json'),
},
};

function footprint(board, ref) {
const data = JSON.parse(fs.readFileSync(BOARDS[board].fixture, 'utf8'));
return data.pcbdata.footprints.find((fp) => fp.ref === ref);
}

async function load(page, board, ref) {
await page.goto('about:blank');
await page.goto(BOARDS[board].page + `#comp=${ref}&side=F&zoom=selected`);
await page.waitForFunction(() => window.__pcbaTest && window.__pcbaTest.ready(), null, { timeout: 60000 });
await page.waitForFunction(() => window.__pcbaTest.idle(), null, { timeout: 60000 });
}

// Move the pointer onto a front board point and return the tooltip text.
async function hover(page, [x, y]) {
const v = await page.evaluate(() => window.__pcbaTest.viewBox('F'));
const box = await page.locator('#frontcanvas').boundingBox();
const fx = 0.5 + (x - v.cx) / v.w;
const fy = 0.5 + (y - v.cy) / v.h;
await page.mouse.move(box.x + box.width * fx, box.y + box.height * fy);
const tooltip = page.locator('#canvas-tooltip');
await expect(tooltip).toBeVisible();
return tooltip.innerText();
}

async function padTableNumbers(page) {
return page.locator('#detail-pane .pad-table tr td:first-child').allInnerTexts();
}

test('named board: hovering a pad shows its pin name, not its place in the list', async ({ page }) => {
const fp = footprint('named', 'U24');
// ibom sorts pads by name as text, so the third pad is pin 11.
expect(fp.pads[2].name).toBe('11');
await load(page, 'named', 'U24');

const text = await hover(page, fp.pads[2].pos);
expect(text).toContain('U24');
expect(text).toContain('pin 11');
expect(text).not.toContain('pad 3');

const pin1 = fp.pads.find((p) => p.pin1);
expect(await hover(page, pin1.pos)).toContain('pin ' + pin1.name);

expect(await padTableNumbers(page)).toEqual(fp.pads.map((p) => p.name));
});

test('unnamed board: hovering a pad still shows pad N', async ({ page }) => {
test.setTimeout(120000);
const fp = footprint('unnamed', 'U46');
expect(fp.pads.every((p) => p.name === undefined)).toBe(true);
expect(fp.pads[3].pin1).toBeFalsy();
await load(page, 'unnamed', 'U46');

const text = await hover(page, fp.pads[3].pos);
expect(text).toContain('U46');
expect(text).toContain('pad 4');
expect(text).not.toContain('pin 4');

expect(await padTableNumbers(page)).toEqual(fp.pads.map((p, i) => String(i + 1)));
});

test('the tooltip shows a pad name as text, not markup', async ({ page }) => {
await load(page, 'named', 'U24');
const fp = footprint('named', 'U24');
await page.evaluate(() => {
const fp = pcbdata.footprints.find((f) => f.ref === 'U24');
fp.pads[2].name = '<b>X</b>';
});
const text = await hover(page, fp.pads[2].pos);
expect(text).toContain('pin <b>X</b>');
});
3 changes: 2 additions & 1 deletion web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,8 @@ function renderDetailPane(fpIdx, showPads) {
var tr = document.createElement("tr");

var tdNum = document.createElement("td");
tdNum.textContent = i + 1;
// The pad name when the export has one, else the place in the list.
tdNum.textContent = padName(pad) || (i + 1);
tr.appendChild(tdNum);

var tdNet = document.createElement("td");
Expand Down
21 changes: 18 additions & 3 deletions web/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -1062,15 +1062,30 @@ function canvasHitScan(side, x, y) {
return null;
}

// What the viewer calls pad j of a footprint. A pad name from the CAD export
// ("11", "A1", "EP") is the pin as the netlist names it (REFDES:PIN), so it
// reads "pin 11". Exports without names (stock ibom, Allegro) only have the
// pad's place in the footprint's list, which is not the pin number, so that
// reads "pad N".
// The pad's name, or "" when it has none. Same test as the MCP tools, so
// both agree on which pads have a pin name.
function padName(pad) {
return typeof pad.name === "string" ? pad.name : "";
}

function padLabel(pad, j) {
if (padName(pad)) return "pin " + padName(pad);
return pad.pin1 ? "pin 1" : ("pad " + (j + 1));
}

function padHitScan(layer, x, y) {
if (!layerShows(layer, "pads")) return null;
for (var i = 0; i < pcbdata.footprints.length; i++) {
var fp = pcbdata.footprints[i];
for (var j = 0; j < fp.pads.length; j++) {
var pad = fp.pads[j];
if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
var label = pad.pin1 ? "pin 1" : ("pad " + (j + 1));
return { fpIdx: i, padLabel: label, net: pad.net };
return { fpIdx: i, padLabel: padLabel(pad, j), net: pad.net };
}
}
}
Expand Down Expand Up @@ -1266,7 +1281,7 @@ function handleMouseMove(e, layerdict) {
var comp = pcbdata.components[padHit.fpIdx];
var line1 = fp.ref + (comp && comp.val ? " \u2014 " + comp.val : "");
var line2 = padHit.padLabel + (padHit.net ? " \u2014 " + padHit.net : "");
tooltip.innerHTML = "<span>" + line1 + "</span><br><span style='color:var(--text-muted)'>" + line2 + "</span>";
tooltip.innerHTML = "<span>" + escapeHtml(line1) + "</span><br><span style='color:var(--text-muted)'>" + escapeHtml(line2) + "</span>";
tooltip.style.display = "block";
tooltip.style.left = tipX + "px";
tooltip.style.top = tipY + "px";
Expand Down
Loading