check-clip-comments.test.mjs

Clip comment check tests

Tier 3engineering

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.

CHECK-CLIP-COMMENTS.TEST.MJS CLIP CHECK CLIP SPELLINGS NEAR MISSES TESTS PASS
Add this to your project
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.

Included in