Files
pnpm/.github/scripts/ts-test-scope.test.mjs
Zoltan Kochan 07839df5c3 docs: move product documentation beside its implementation (#16385)
* docs: move website documentation into the pnpm repository

* docs: keep v11 and v12 documentation beside their implementations

* docs(pnpr): keep registry documentation beside its implementation

* ci: classify product documentation as documentation changes

* test: cover product documentation in Rust CI exclusions

* ci: isolate documentation publishing and support manual sync

* test: make dependency status fixture timestamps deterministic
2026-09-29 15:56:43 +02:00

96 lines
5.9 KiB
JavaScript

import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { test } from 'node:test'
import { determineTestScope } from './ts-test-scope.mjs'
test('PR scope stays pinned when main advances; global changes and missing bases run everything', () => {
const dir = mkdtempSync(path.join(tmpdir(), 'ci-scope-'))
const origin = path.join(dir, 'origin')
const checkout = path.join(dir, 'checkout')
const git = (cwd, ...args) => execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
const commit = (cwd, file, content) => {
mkdirSync(path.dirname(path.join(cwd, file)), { recursive: true })
writeFileSync(path.join(cwd, file), content)
git(cwd, 'add', '.')
git(cwd, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-m', 'test')
return git(cwd, 'rev-parse', 'HEAD')
}
try {
mkdirSync(origin)
git(origin, 'init', '-b', 'main')
commit(origin, 'pnpm11/example/src/index.ts', 'base')
const base = commit(origin, 'global-test-config.json', 'original config')
git(dir, 'clone', origin, checkout)
git(checkout, 'switch', '-c', 'pr')
commit(checkout, 'pnpr/docs/introduction.md', 'registry docs')
assert.equal(determineTestScope({ event: 'pull_request', base, cwd: checkout }).full_tests, 'false', 'registry docs alone do not require all TS tests')
git(checkout, 'reset', '--hard', 'HEAD^')
commit(checkout, 'pnpm11/example/src/index.ts', 'pr')
commit(origin, 'pnpm11/unrelated/src/index.ts', 'new main')
const scope = () => determineTestScope({ event: 'pull_request', base, cwd: checkout })
assert.equal(scope().full_tests, 'false')
assert.equal(git(checkout, 'rev-parse', 'origin/main'), base)
assert.equal(git(checkout, 'diff', '--name-only', 'origin/main', 'HEAD'), 'pnpm11/example/src/index.ts')
commit(checkout, '.changeset/fix-example.md', 'Release note')
assert.equal(scope().full_tests, 'false', 'TS code plus release notes remain affected')
commit(checkout, 'pnpm/crates/example/src/lib.rs', 'rust')
assert.equal(scope().full_tests, 'false', 'TS code plus pacquet sources remain affected')
for (const file of ['package.json', 'pnpm-lock.yaml', '.pnpmfile.cjs', 'pnpm-workspace.yaml', '__patches__/dependency.patch', '.github/scripts/new-helper.mjs', '.changeset/config.json', 'pnpr/crates/example/src/lib.rs', 'pnpr/.fixtures/packages/example/package.json', 'Cargo.lock']) {
commit(checkout, file, 'global input')
assert.equal(scope().full_tests, 'true', file)
git(checkout, 'reset', '--hard', 'HEAD^')
}
git(checkout, 'mv', 'global-test-config.json', 'pnpm11/example/moved-config.json')
git(checkout, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-m', 'rename global config')
assert.equal(scope().full_tests, 'true', 'moving a global input into a package still affects all tests')
git(checkout, 'reset', '--hard', 'HEAD^')
git(checkout, 'rm', 'global-test-config.json')
git(checkout, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-m', 'delete global config')
assert.equal(scope().full_tests, 'true', 'deleted global inputs still affect all tests')
assert.equal(determineTestScope({ event: 'pull_request', base: 'f'.repeat(40), cwd: checkout }).full_tests, 'true')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
test('merge queues, main, manual and unknown events cannot narrow tests', () => {
for (const event of ['merge_group', 'push', 'workflow_dispatch', 'unknown', undefined]) {
assert.equal(determineTestScope({ event, base: 'a'.repeat(40) }).full_tests, 'true')
}
for (const base of [undefined, '', 'main', '--upload-pack=evil', 'a'.repeat(39)]) {
assert.equal(determineTestScope({ event: 'pull_request', base }).full_tests, 'true')
}
})
test('Rust gate configuration keeps exclusions limited to approved documentation', () => {
const workflow = readFileSync(new URL('../workflows/pacquet-ci.yml', import.meta.url), 'utf8')
const filters = workflow.slice(workflow.indexOf(' predicate-quantifier:'), workflow.indexOf('\n test:'))
assert.match(filters, /predicate-quantifier: some-with-excludes/)
const patterns = filters.split(' rust:\n')[1].split('\n').map(line => line.match(/- '([^']+)'/)?.[1]).filter(Boolean)
const docs = filters.split(' docs:\n')[1].split(' rust:\n')[0].split('\n').map(line => line.match(/- '([^']+)'/)?.[1]).filter(Boolean)
assert.deepEqual(patterns.filter(pattern => pattern.startsWith('!')), [
'!pnpm/docs/**',
'!pnpr/docs/**',
'!pnpm/*.md',
'!pnpm/plans/*.md',
'!pnpm/scripts/*.md',
'!pnpm/crates/*/*.md',
'!pnpm/tasks/*/*.md',
'!pnpr/*.md',
'!pnpr/crates/*/*.md',
'!pnpr/docker/*.md',
'!pnpr/client/*.md',
])
// Markdown under pnpr/.fixtures ships inside registry fixtures.
const recursiveDocs = new Set(['!pnpm/docs/**', '!pnpr/docs/**'])
assert.ok(!patterns.some(pattern => pattern.startsWith('!') && pattern.includes('**') && !recursiveDocs.has(pattern)), 'recursive exclusions only cover product documentation')
for (const pattern of ['pnpm/**', 'pnpr/**', '.config/nextest.toml', 'fixtures/**', 'pnpm11/installing/deps-installer/test/fixtures/patch-pkg/**', 'pnpm11/deps/compliance/commands/test/sbom/fixtures/**']) {
assert.ok(patterns.includes(pattern), `required test input: ${pattern}`)
}
assert.deepEqual(docs, patterns.filter(pattern => pattern.startsWith('!')).map(pattern => pattern.slice(1)), 'docs covers every exclusion, so the spell check still runs')
assert.match(workflow.slice(workflow.indexOf('\n typos:'), workflow.indexOf('\n deny:')), /needs.changes.outputs.docs == 'true'/)
})