Test that every spelling of a clip is caught and every near miss is left alone, so an edit to the checker cannot quietly stop asking.
Add "Clip comment check tests" from ntent to this repo.
Fetch https://ntent.app/r/f/check-clip-comments-test as plain text. Write it verbatim to scripts/check-clip-comments.test.mjs.
It needs check-clip-comments.mjs. If this repo does not have it, stop and tell me.
Then check it: node --test passes. Stage an edit to check-clip-comments.mjs that stops overflow-hidden being caught and the commit is refused, naming this file.Reads https://ntent.app/r/f/check-clip-comments-test
Read the code
check-clip-comments.test.mjsscripts/check-clip-comments.test.mjs
286 lines
/**
* Tests for the clip checker, run against real files on disk.
*
* Two ways for this check to lie, and each case below is one side of a pair.
* Too narrow, and a clip spelled some way it does not know reads as explained
* or is not seen at all: an earlier version of this check was caught once by
* its own probe, passing a clip whose comment sat above the style key while the
* clip was that block's second property. Too wide, and it fires on
* text-overflow, on overflow-auto, or on a clip somebody did explain in the
* place people actually write it, and a gate that fires on explained code gets
* bypassed rather than satisfied.
*
* Every case writes a throwaway repo in a temp directory. Nothing here touches
* the repository you are running from.
*
* Run: node --test scripts/check-clip-comments.test.mjs
*/
import test, { after } from 'node:test'
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, dirname } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
// Every temp dir this file makes. They are removed when the file's tests end, pass or fail.
const made = []
const tmp = (prefix) => {
const dir = mkdtempSync(join(tmpdir(), prefix))
made.push(dir)
return dir
}
after(() => {
for (const dir of made) rmSync(dir, { recursive: true, force: true })
})
const CHECK = join(dirname(fileURLToPath(import.meta.url)), 'check-clip-comments.mjs')
// The installer's customise step edits scanDirs and ignorePaths (for example
// ['app'] and ['components/ui']). Every case below is written against src/ and
// src/components/ui, so each run is of a copy of the script with those two
// pinned. Running the installed copy as it stands failed the selftest in 23 of
// 48 repos in the field study. Everything else in CONFIG is left as installed.
const PINNED = { scanDirs: "['src']", ignorePaths: "['src/components/ui']" }
/** The script with the pinned CONFIG lines replaced, written next to nothing it imports from. */
function pinnedScript(dir, pins = PINNED) {
let text = readFileSync(CHECK, 'utf8')
for (const [key, literal] of Object.entries(pins)) {
const re = new RegExp(`^ ${key}: .*,$`, 'm')
assert.match(text, re, `CONFIG.${key} is on one line, so a test can replace it`)
text = text.replace(re, ` ${key}: ${literal},`)
}
// The copy lives in the temp repo, so the relative import has to be absolute.
text = text.replace("'./lib/waivers.mjs'", JSON.stringify(pathToFileURL(join(dirname(CHECK), 'lib', 'waivers.mjs')).href))
const script = join(dir, 'check-clip-comments.pinned.mjs')
writeFileSync(script, text)
return script
}
/** Write { 'src/a.tsx': source, ... } into a fresh repo and check it. */
function check(tree, args = ['--check'], pins = PINNED) {
const dir = tmp('clip-comments-')
for (const [path, source] of Object.entries(tree)) {
mkdirSync(dirname(join(dir, path)), { recursive: true })
writeFileSync(join(dir, path), source)
}
const script = pinnedScript(dir, pins)
try {
const out = execFileSync('node', [script, ...args], { cwd: dir, encoding: 'utf8' })
return { code: 0, out }
} catch (e) {
return { code: e.status, out: (e.stdout ?? '') + (e.stderr ?? '') }
}
}
/** The 1-based lines reported for one file. */
const reported = (out, file) => {
const block = (out.split(` ${file}\n`)[1] ?? '').split(/\n {2}\S/)[0]
return [...block.matchAll(/^\s+(\d+):\s+UNEXPLAINED-CLIP/gm)].map((m) => Number(m[1]))
}
// ------------------------------------------------------------- it fires ---
test('a bare clip is caught however it is spelled', () => {
const run = check({
'src/Card.tsx': `export const Card = () => <div className="md:overflow-hidden rounded-lg" />
export const Row = () => <div className="!overflow-x-hidden" />
export const Track = () => <div className="overflow-clip" />
export const Pane = () => <div style={{ overflow: 'hidden' }} />
export const Drawer = ({ open }) => <div style={{ overflowY: open ? 'auto' : 'hidden' }} />
export const styles = { 'overflow-x': 'clip' }
export const Sheet = ({ mode }) => <div style={{ overflow: mode || 'hidden' }} />
`,
'src/card.css': `.card {
border-radius: 8px;
overflow: hidden;
}
.rail {
overflow-x: clip;
}
.chip {
@apply overflow-hidden;
}
`,
})
assert.equal(run.code, 1, run.out)
assert.deepEqual(reported(run.out, 'src/Card.tsx'), [1, 2, 3, 4, 5, 6, 7])
assert.deepEqual(reported(run.out, 'src/card.css'), [3, 6, 9])
assert.match(run.out, /10 of 10 clip\(s\)/)
})
test('a comment that is only a tool directive says nothing about the clip', () => {
// Otherwise any directive is a way round the rule.
const run = check({
'src/Pane.tsx': `// eslint-disable-next-line no-restricted-syntax
export const A = () => <div className="overflow-hidden" />
/* project-check-ignore */
export const B = () => <div className="overflow-hidden" />
// @ts-expect-error
export const C = () => <div className="overflow-hidden" />
`,
})
assert.equal(run.code, 1, run.out)
assert.deepEqual(reported(run.out, 'src/Pane.tsx'), [2, 4, 6])
})
test('a comment above the component is not a comment about its clip', () => {
const run = check({
'src/Page.tsx': `export function Page() {
// Renders the invoice list.
return (
<div className="overflow-hidden">
<List />
</div>
)
}
`,
// In a style object the line ending in a comma is the sibling above, so the
// comment that explains the header's clip does not vouch for the body's.
'src/styles.ts': `export const styles = {
// Clips the header image to the card's top corners.
header: { overflow: 'hidden' },
body: {
padding: 8,
overflow: 'hidden',
},
}
`,
})
assert.equal(run.code, 1, run.out)
assert.deepEqual(reported(run.out, 'src/Page.tsx'), [4])
assert.deepEqual(reported(run.out, 'src/styles.ts'), [6])
})
// ------------------------------------------------------- it stays quiet ---
test('an explained clip passes in every place people write the comment', () => {
const run = check({
'src/Card.tsx': `export const A = () => <div className="overflow-hidden" /> // clips the photo to the radius
export const B = () => (
<section>
{/* Holds the progress fill inside the track's rounded ends. */}
<div className="overflow-hidden rounded-full" />
{/* Rounds the avatar image to a circle. */}
<div
data-slot="avatar"
className="size-8 overflow-hidden rounded-full"
/>
</section>
)
// Keeps the slide strip inside the viewport; the ring lives on the slide.
// eslint-disable-next-line no-restricted-syntax
export const C = () => <div className="overflow-x-hidden" />
/* project-check-ignore -- the vendor slider paints its thumb outside the track */
export const D = () => <div className="overflow-clip" />
// Clips the thumb to the track while it animates.
const slider = cva(
'relative overflow-hidden',
)
`,
'src/styles.ts': `export const styles = {
// Clips the underlays, which span the full width behind the foreground.
container: {
borderRadius: 8,
overflow: 'hidden',
},
mask: {
// Rounds the progress fill to the track's own corner.
overflow: 'hidden',
},
}
`,
'src/app.css': `/* The shell scrolls inside itself, so the document never does. */
html,
body {
height: 100%;
overflow: hidden;
}
.card { /* clips the cover image to the corner radius */
border-radius: 8px;
overflow: hidden;
}
`,
})
assert.equal(run.code, 0, run.out)
assert.match(run.out, /ok\. 10 clip\(s\) in 3 files/)
})
test('what only looks like a clip is not one', () => {
const run = check({
'src/Text.tsx': `export const A = () => <p className="truncate" />
export const B = () => <p className="line-clamp-2 overflow-auto overflow-x-auto" />
export const C = () => <p style={{ textOverflow: 'clip', overflowWrap: 'anywhere' }} />
export type Props = { overflow: 'hidden' | 'visible' }
// the old overflow-hidden here cut the focus ring
export const D = () => <p className="text-clip" />
`,
'src/text.css': `.a { text-overflow: clip; }
.b { overflow-clip-margin: 4px; overflow: auto; }
`,
})
assert.equal(run.code, 0, run.out)
assert.match(run.out, /ok\. 0 clip\(s\) in 2 files/)
})
// ------------------------------------------------ what it does not check ---
test('vendored primitives are skipped, and the run says so', () => {
const run = check({
'src/components/ui/avatar.tsx': 'export const Avatar = () => <span className="overflow-hidden rounded-full" />\n',
'src/components/Card.tsx': 'export const Card = () => <div className="rounded-lg" />\n',
})
assert.equal(run.code, 0, run.out)
assert.match(run.out, /ok\. 0 clip\(s\) in 1 file,/)
assert.match(run.out, /not checked, in CONFIG\.ignorePaths: src\/components\/ui/)
})
test('a staged vendored file alone is an empty run, not a pass', () => {
// The hook passes staged paths. A commit touching only src/components/ui
// checked nothing, and exit 2 is how it says so.
const tree = { 'src/components/ui/avatar.tsx': 'export const A = () => <span className="overflow-hidden" />\n' }
const run = check(tree, ['--check', 'src/components/ui/avatar.tsx'])
assert.equal(run.code, 2, run.out)
assert.match(run.out, /read 0 files/)
assert.match(run.out, /not checked, in CONFIG\.ignorePaths: src\/components\/ui/)
})
test('nothing to scan is exit 2 under --check, and never reads as a pass', () => {
const run = check({ 'README.md': '# nothing here\n' })
assert.equal(run.code, 2, run.out)
assert.match(run.out, /This is not a pass/)
assert.equal(check({ 'README.md': '# nothing here\n' }, []).code, 0, 'a report run still exits 0')
})
test('an empty run says what it looked for and where, and what to set', () => {
const run = check({ 'README.md': '# nothing here\n' })
assert.equal(run.code, 2, run.out)
assert.match(run.out, /read 0 files under src/)
assert.match(run.out, /\.ts, \.tsx, \.css/)
assert.match(run.out, /CONFIG\.scanDirs/)
})
test('a tree of .vue, .svelte and .astro files names them as the files it cannot read', () => {
const run = check({
'src/App.vue': '<template><div class="overflow-hidden" /></template>\n',
'src/Card.svelte': '<div class="overflow-hidden" />\n',
'src/pages/index.astro': '<div class="overflow-hidden" />\n',
})
assert.equal(run.code, 2, run.out)
assert.match(run.out, /found 3 \.astro, \.svelte, \.vue file\(s\)/)
assert.match(run.out, /does not read them/)
})
test('given paths, it checks only those', () => {
const tree = {
'src/a.tsx': 'export const A = () => <div className="overflow-hidden" />\n',
'src/b.tsx': 'export const B = () => <div className="overflow-hidden" />\n',
}
const run = check(tree, ['--check', 'src/b.tsx'])
assert.equal(run.code, 1, run.out)
assert.deepEqual(reported(run.out, 'src/b.tsx'), [1])
assert.doesNotMatch(run.out, /src\/a\.tsx/)
})
How to tell it works: node --test passes. Stage an edit to check-clip-comments.mjs that stops overflow-hidden being caught and the commit is refused, naming this file.