/*
Regression tests for the uniformity tool.
It had none, and that is why `_sinFilas_` survived the August translation to
English for weeks: the class the filter looked for stopped existing, and nothing
said so until a baseline was taken and `/dhcp/leases/` (an empty table on a
fresh harness) handed over its "No Lease Found" cell as the density sample
and produced a FOURTH table signature that does not exist.
A tool that measures drift and is not itself measured is a tool that will drift.
*/
import { describe, expect, it, beforeEach } from 'vitest'
import { screenSignatures, merge } from './uniformity.js'
/** A table like the console's: a header, and whatever rows are given. */
function table(rows) {
const main = document.createElement('main')
main.innerHTML = `
Zone
${rows}
`
document.body.replaceChildren(main)
return main
}
const WITH_ROWS = '
home.test
'
/* The empty row. The class carries its hash suffix exactly as CSS modules emit
it, because that is what the filter has to survive. */
const NO_ROWS = '
No Lease Found
'
describe('the empty row is not a density sample', () => {
beforeEach(() => document.body.replaceChildren())
/*
THE ONE THIS FILE EXISTS FOR, and its contract changed on 2026-09-07: an empty
table used to sign with a placeholder where the cell padding goes, and that is
not "nothing": it is a signature of its own, and `/dhcp/leases/` on a fresh
harness handed it over as a third table look for months. A table with no data cell now contributes NO signature at all.
The other half of the guard stays the same: if the `_noRows_` filter ever stops
recognising the class, the padding of the empty row leaks in and invents one.
*/
it('an empty table contributes no signature, not even one with a placeholder cell', () => {
table(NO_ROWS)
expect(screenSignatures().table).toBeUndefined()
})
it('a table with rows does contribute its own', () => {
table(WITH_ROWS)
expect(screenSignatures().table[0]).toContain('td 9px 10px')
})
/* The mix, which is the case of `/dhcp/leases/` when it DOES have leases: the
good cell sits behind the empty one and must not lose to it. */
it('with both, the data cell wins and not the empty one', () => {
table(NO_ROWS + WITH_ROWS)
const [signature] = screenSignatures().table
expect(signature).toContain('td 9px 10px')
expect(signature).not.toContain('24px')
})
/*
And the test that really closes the hole: the class is read from the CODE, not
from this constant. If someone renames it again (as happened in the
translation), this fails here and not in a baseline taken six weeks later.
*/
it('the class the filter skips is the one the code uses today', async () => {
/* From the project root and not from `import.meta.url`: under jsdom that URL
is not `file:` and `fileURLToPath` throws. */
const { readFileSync } = await import('node:fs')
const { join } = await import('node:path')
const root = process.cwd()
const tableSource = readFileSync(join(root, 'src/ui/Table.tsx'), 'utf8')
const tool = readFileSync(join(root, 'dev/uniformity.js'), 'utf8')
const cls = tableSource.match(/className=\{styles\.(\w*[Nn]o[Rr]ows\w*)\}/)?.[1]
expect(cls, 'Table.tsx no longer paints a recognisable empty-row class').toBeTruthy()
/*
Against the FILTER LINE, not against the whole file. The first version of this
test looked at all of `uniformity.js` and passed even with the filter broken,
because the right name appeared in a comment. A test that a comment can satisfy
proves nothing, and the only reason it was noticed is that it was tested in
the negative.
*/
const filter = tool.match(/const td = .*\n?.*!\/(\S+?)\/\.test/)?.[1]
?? tool.split('\n').find((l) => l.includes('.find((c) =>'))
expect(
filter,
`the empty-row filter does not skip the class Table.tsx uses today ("${cls}")`,
).toContain(`_${cls}_`)
})
})
describe('merge groups by family without losing which route each signature comes from', () => {
it('joins the same signature from two routes and names them', () => {
const report = merge([
{ route: '/zones/', signatures: { table: ['A'] } },
{ route: '/cache/', signatures: { table: ['A'] } },
{ route: '/dhcp/leases/', signatures: { table: ['B'] } },
])
const t = report.find((f) => f.family === 'table')
expect(t.count).toBe(2)
expect(t.signatures).toContain('A → /zones/, /cache/')
expect(t.signatures).toContain('B → /dhcp/leases/')
})
})
/*
Portals, and alerts split by type.
Two holes at once. `Dialog` mounts through a portal, outside ``, so for a
long time this tool **did not see a single piece of dialog content**, while the
whole modal system was being decided. And the alert family measured
`borderRadius | inset`, so an alert could go from filled to outlined **without any
family moving**: exactly the decision that had to be taken was invisible to the
guard meant to protect it.
*/
function alert({ type, background, icon = false, inDialog = false }) {
const main = document.createElement('main')
const box = `
${icon ? '' : ''}${type}! text
`
if (inDialog) {
main.innerHTML = ''
const d = document.createElement('div')
d.setAttribute('role', 'dialog')
d.innerHTML = box
document.body.replaceChildren(main, d)
} else {
main.innerHTML = box
document.body.replaceChildren(main)
}
}
describe('the guard sees portals and splits alerts by type', () => {
beforeEach(() => document.body.replaceChildren())
/*
THE INVARIANT. Widening the tool must not move what was already measured: the
baseline was taken with no dialog open, and it has to stay valid.
*/
it('with no dialog open it only looks at , as before', () => {
const main = document.createElement('main')
main.innerHTML = '
'
document.body.replaceChildren(main, outside)
const f = screenSignatures()
expect(Object.keys(f)).toContain('notice-info')
expect(Object.keys(f)).not.toContain('notice-warning')
})
it('an alert inside a dialog IS measured now', () => {
alert({ type: 'warning', background: '#456', inDialog: true })
expect(screenSignatures()['notice-warning']).toHaveLength(1)
})
it('info and warning land in different families, not in one', () => {
const main = document.createElement('main')
main.innerHTML =
'
a
' +
'
b
'
document.body.replaceChildren(main)
const f = screenSignatures()
expect(f['notice-info']).toHaveLength(1)
expect(f['notice-warning']).toHaveLength(1)
expect(f.notice).toBeUndefined()
})
/* The measure that was missing: with the old signature an alert could go from
filled to outlined without anything moving. */
it('tells filled from outlined', () => {
alert({ type: 'info', background: '#123' })
expect(screenSignatures()['notice-info'][0]).toContain('filled')
alert({ type: 'info', background: 'transparent' })
expect(screenSignatures()['notice-info'][0]).toContain('outlined')
})
/* The width cap: the third thing the signature did not see. Without this,
`--notice-max` could be applied (or stop being applied) without anything
moving. */
it('the signature includes the painted width', () => {
const main = document.createElement('main')
main.innerHTML = '
a
'
document.body.replaceChildren(main)
/* jsdom does no layout, so the width comes out as 0: what this test pins is
that the signature CARRIES the width, not how much it measures. The number
is checked in the sweep, against the real page and at 1440. */
expect(screenSignatures()['notice-info'][0]).toMatch(/width \d+px/)
main.innerHTML = '
a
'
document.body.replaceChildren(main)
expect(screenSignatures()['notice-info'][0]).toMatch(/width \d+px/)
})
it('and detects the icon', () => {
alert({ type: 'warning', background: '#456', icon: true })
expect(screenSignatures()['notice-warning'][0]).toContain('with-icon')
alert({ type: 'warning', background: '#456', icon: false })
expect(screenSignatures()['notice-warning'][0]).toContain('no-icon')
})
})
/*
The two control-width families. They measure what is RENDERED (not what is
declared) because that is the lesson that already cost two corrections in a single
day: the alerts' cap lived on the container, and the fill was decided by the order
of the bundle. Looking at the declaration would have come out green both times.
*/
describe('control widths', () => {
beforeEach(() => document.body.replaceChildren())
const withFields = (html) => {
const main = document.createElement('main')
main.innerHTML = html
document.body.replaceChildren(main)
}
/* The numeric field of a FORM and that of a CELL are two objects: the first has
the width its label's grid gives it and the second fills its column. Together
they gave 104 px and 151 px in Settings and looked like drift. */
it('separates the form numeric field from the cell one, and does not measure text fields', () => {
withFields('')
const f = screenSignatures()
expect(f['field-num-width-form']).toHaveLength(1)
expect(f['field-num-width-form'][0]).toMatch(/^\d+px$/)
expect(f['field-num-width-cell']).toBeUndefined()
})
it('the numeric field inside a cell lands in the other family', () => {
withFields('
')
const f = screenSignatures()
expect(f['field-num-width-cell']).toHaveLength(1)
expect(f['field-num-width-form']).toBeUndefined()
})
/* A textarea's height is `rows` times the line plus the frame, so measuring the
height turned somebody asking for five rows instead of three into drift. What
is measured is the formula, and `rows` drops out. */
it('measures the textarea formula and not its height', () => {
withFields('')
const f = screenSignatures()['field-area-height']
expect(f).toHaveLength(1)
expect(f[0]).toMatch(/^line .* \+ frame \d+px$/)
})
})
/*
The row grid, panel and modal APART.
`Form` tells the two apart on purpose (210 px in the dense form, 180 in the tighter
space of the dialog) and it says so in `ui/Form.module.css`. An earlier audit
called it drift from a grep, without opening the file, and it had to be corrected.
Measuring them together would have built that confusion into the instrument.
*/
describe('the row grid is measured by context', () => {
beforeEach(() => document.body.replaceChildren())
it('separates the panel row from the modal one', () => {
const main = document.createElement('main')
main.innerHTML =
'
' +
'
' +
'
' +
'
'
document.body.replaceChildren(main)
const f = screenSignatures()
expect(f['grid-panel']).toHaveLength(1)
expect(f['grid-modal']).toHaveLength(1)
})
/* `_mrow_` contains the substring `row`: if the question were asked the other way
round, every modal row would count as a panel row and the two families would
mix. */
it('does not mistake a modal row for a panel row', () => {
const main = document.createElement('main')
main.innerHTML =
'
' +
'
'
document.body.replaceChildren(main)
const f = screenSignatures()
expect(f['grid-modal']).toHaveLength(1)
expect(f['grid-panel']).toBeUndefined()
})
/*
And the `.row` of ANOTHER module is not a form row.
Measured in the console: Cache, Allowed, Blocked and View Logs have their own
`.row`, which is not a grid, and the family came out with a `none` signature
that is no form's grid. What tells the two apart is the label, which every
`Form` row paints as a direct child.
*/
it("ignores another module's `.row`, which is not a form row", () => {
const main = document.createElement('main')
main.innerHTML =
'