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
80 changes: 39 additions & 41 deletions ui/scripts/__tests__/check-action-forward-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ import {
SURFACES,
RUNTIME_CONSUMERS,
} from '../check-action-forward-parity.mjs';
import {
globMatches,
jobBlock,
repoPath,
rootCi,
triggerBlock,
uiFilterGlobs,
uiRun,
UI_JOBS,
withoutComments,
} from './helpers/root-ci';

/**
* objectui#4050 — the behaviour test for `scripts/check-action-forward-parity.mjs`.
Expand Down Expand Up @@ -723,60 +734,47 @@ describe('the gate is wired to run', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as {
scripts: Record<string, string>;
};
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
// under `ui/.github/` runs nowhere (objectstack#25712).
const ci = rootCi();
const job = jobBlock(ci, UI_JOBS.typeCheck);

it('package.json exposes it as a named script', () => {
expect(pkg.scripts['check:action-forward-parity']).toBe('node scripts/check-action-forward-parity.mjs');
});

it('ci.yml runs it after the install it needs (it resolves @objectstack/spec)', () => {
const install = ci.indexOf('pnpm install --frozen-lockfile');
const step = ci.indexOf('run: pnpm check:action-forward-parity');
expect(step, 'ci.yml does not run `pnpm check:action-forward-parity`').toBeGreaterThan(-1);
const install = job.indexOf('pnpm install --frozen-lockfile');
const step = job.indexOf(`run: ${uiRun('check:action-forward-parity')}`);
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:action-forward-parity`').toBeGreaterThan(-1);
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
});

it('is not hidden from the renderers by ci.yml path filters', () => {
// ci.yml `paths-ignore`s markdown, content/, docs/, apps/site/ and
// .changeset/ on its `push` trigger — since objectui#3523 step 2 the only
// trigger that still carries the filter (ci.yml:6; lint.yml:32 is the twin),
// and the only thing the assertion below can see, since it slices the `on:`
// block. None can match `packages/components/src/renderers/**`, so a push
// that edits a forward whitelist always starts this workflow.
//
// For a PULL REQUEST the conclusion holds for a second and stronger reason.
// This lead-in used to state it in a bare present tense that read as if the
// filter still applied there; objectui#4384 qualified it, together with its
// near-verbatim twin in `check-i18n-en-drift.test.ts`, closing the #3857 /
// #4369 / #4381 family (PRs #4371 / #4380 / #4382). No `paths-ignore`
// survives on `pull_request` at all, so such a PR starts this workflow a
// fortiori — measured there: PR #3856, one markdown file, 16 checks. What
// decides a pull request now is the in-job `Decide whether this change needs
// a full run` step, whose exclusion list is that `push` filter unchanged,
// held identical to it by `scripts/__tests__/merge-queue-reporting.test.ts`
// — so the patterns pinned below are also what keeps the expensive steps
// running on a renderer PR.
const ignored = (ci.slice(0, ci.indexOf('jobs:')).match(/^\s+- '.*'$/gm) ?? []).map((line) =>
line.trim().replace(/^- '/, '').replace(/'$/, ''),
// The root ci.yml carries no `paths-ignore` on any trigger: it starts on
// every pull_request, every queue build, every push to main and the hourly
// schedule. What decides whether `type-check-ui` then runs is the `filter`
// job's `ui:` paths filter, which the job's `if:` reads, so every renderer
// a forward whitelist lives in must match one of its globs or a pull
// request that edits one skips this gate. (Until objectstack#25712 this
// pinned objectui's own `push` `paths-ignore` list, in a file nothing runs.)
expect(withoutComments(triggerBlock(ci)), 'a trigger grew a path filter').not.toMatch(/^\s+paths(-ignore)?:/m);
expect(job, '`type-check-ui` no longer reads the `ui` filter output').toContain(
"needs.filter.outputs.ui != 'false'",
);
expect(ignored.length, 'the paths-ignore parse found nothing — this pin would be vacuous').toBeGreaterThan(3);

/** `**` crosses separators, `*` does not — enough for the five patterns above. */
const matches = (glob: string, file: string): boolean =>
new RegExp(
`^${glob
.split('**')
.map((part) => part.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*'))
.join('.*')}$`,
).test(file);

const globs = uiFilterGlobs(ci);
for (const surface of SURFACES) {
for (const glob of ignored) {
expect(matches(glob, surface.file), `${glob} hides ${surface.file}`).toBe(false);
}
// The matcher itself must be able to say yes, or the loop above proves
// nothing — the vacuity trap this whole file is about.
expect(matches('**/*.md', 'content/docs/guide/ci-cd-pipeline.md')).toBe(true);
const file = repoPath(surface.file);
expect(
globs.some((glob) => globMatches(glob, file)),
`no \`ui:\` filter glob covers ${file}`,
).toBe(true);
}
// The matcher itself must be able to say no, or the loop above proves
// nothing — the vacuity trap this whole file is about.
expect(globs.some((glob) => globMatches(glob, 'packages/spec/src/index.ts'))).toBe(false);
});
});
12 changes: 8 additions & 4 deletions ui/scripts/__tests__/check-doc-example-shared-reader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
inline,
rungsOf,
} from '../check-doc-example-shared-reader.mjs';
import { jobBlock, rootCi, uiRun, UI_JOBS } from './helpers/root-ci';

/**
* objectui#7652 — a doc comment must not prescribe a call-site spelling that a
Expand Down Expand Up @@ -482,10 +483,13 @@ describe('check-doc-example-shared-reader — this repository', () => {
const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
expect(manifest.scripts['check:doc-example-readers']).toBe('node scripts/check-doc-example-shared-reader.mjs');

const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
expect(ci, 'the gate must run in CI, next to the other source-parsing gates').toContain(
'run: pnpm check:doc-example-readers',
);
// objectstack's root ci.yml runs objectui's gates: its `type-check-ui` job is
// objectui's `Type Check`, where the source-parsing gates run (objectstack#25697).
// The ci.yml under `ui/.github/` runs nowhere (objectstack#25712).
expect(
jobBlock(rootCi(), UI_JOBS.typeCheck),
'the gate must run in CI, next to the other source-parsing gates',
).toContain(`run: ${uiRun('check:doc-example-readers')}`);

// The page that inventories the gates is pinned by command (objectui#3653), so
// this would fail there too — asserted here as well because a reader looking
Expand Down
10 changes: 6 additions & 4 deletions ui/scripts/__tests__/check-doc-expression-carriage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,10 +848,12 @@ describe('check-doc-expression-carriage: the real tree, and the posture', () =>
});

describe('check-doc-expression-carriage: the wiring', () => {
it('is actually run by ci.yml — a gate nobody runs cannot be told from one that passes', () => {
const ci = fs.readFileSync(path.join(ROOT, '.github/workflows/ci.yml'), 'utf8');
expect(ci).toContain(`node ${GATE}`);
});
// `is actually run by ci.yml` retired with objectstack#25712. The census ran,
// report-only, in objectui ci.yml's `docs` job (`Build Docs`), and that file
// runs nowhere under the monorepo. The job is row 7 `Build Docs
// (@object-ui/site)` of the objectstack#25697 workflow roster, "kept,
// follow-up port": no root job runs the census until that port lands, and
// the port brings this pin back against its job (`helpers/root-ci.ts`).

it('is named on the page that documents what CI runs', () => {
const page = fs.readFileSync(path.join(ROOT, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');
Expand Down
12 changes: 8 additions & 4 deletions ui/scripts/__tests__/check-handler-key-read-sites.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
registrationsIn,
parseSource,
} from '../check-handler-key-read-sites.mjs';
import { jobBlock, rootCi, uiRun, UI_JOBS } from './helpers/root-ci';

/**
* objectui#7753 — every `on*` key a registered renderer READS off the authored
Expand Down Expand Up @@ -1148,10 +1149,13 @@ describe('check-handler-key-read-sites — this repository', () => {
const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
expect(manifest.scripts['check:handler-key-reads']).toBe('node scripts/check-handler-key-read-sites.mjs');

const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
expect(ci, 'the gate must run in CI, next to the other source-parsing gates').toContain(
'run: pnpm check:handler-key-reads',
);
// objectstack's root ci.yml runs objectui's gates: its `type-check-ui` job is
// objectui's `Type Check`, where the source-parsing gates run (objectstack#25697).
// The ci.yml under `ui/.github/` runs nowhere (objectstack#25712).
expect(
jobBlock(rootCi(), UI_JOBS.typeCheck),
'the gate must run in CI, next to the other source-parsing gates',
).toContain(`run: ${uiRun('check:handler-key-reads')}`);

// The page that inventories the gates is pinned by command (objectui#3653), so
// this would fail there too — asserted here as well because a reader looking
Expand Down
13 changes: 9 additions & 4 deletions ui/scripts/__tests__/check-i18n-call-site-keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
RESERVED_OPTION_NAMES,
unresolvableSpellings,
} from '../check-i18n-call-site-keys.mjs';
import { jobBlock, rootCi, uiRun, UI_JOBS } from './helpers/root-ci';

/**
* objectui#3530 — the behaviour test for `scripts/check-i18n-call-site-keys.mjs`.
Expand Down Expand Up @@ -2129,10 +2130,14 @@ describe('the gate is wired to run', () => {
});

it('ci.yml runs it after the install it needs (it imports typescript)', () => {
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
const install = ci.indexOf('pnpm install --frozen-lockfile');
const step = ci.indexOf('pnpm check:i18n-keys');
expect(step, 'ci.yml does not run `pnpm check:i18n-keys`').toBeGreaterThan(-1);
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
// under `ui/.github/` runs nowhere (objectstack#25712).
const job = jobBlock(rootCi(), UI_JOBS.typeCheck);
const install = job.indexOf('pnpm install --frozen-lockfile');
const step = job.indexOf(`run: ${uiRun('check:i18n-keys')}`);
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-keys`').toBeGreaterThan(-1);
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
});
});
33 changes: 23 additions & 10 deletions ui/scripts/__tests__/check-i18n-designer-table-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
readDesignerPairs,
ZH_ONLY_FAMILIES,
} from '../check-i18n-designer-table-parity.mjs';
import { globMatches, jobBlock, repoPath, rootCi, triggerBlock, uiFilterGlobs, uiRun, UI_JOBS, withoutComments } from './helpers/root-ci';
import { DESIGNER_TABLE_CONSTS } from '../check-i18n-dead-keys.mjs';

/**
Expand Down Expand Up @@ -322,7 +323,11 @@ describe('the CLI, end to end', () => {

describe('the gate is wired to run', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
// under `ui/.github/` runs nowhere (objectstack#25712).
const ci = rootCi();
const job = jobBlock(ci, UI_JOBS.typeCheck);

it('package.json exposes it as a named script', () => {
expect(pkg.scripts['check:i18n-designer-parity']).toBe('node scripts/check-i18n-designer-table-parity.mjs');
Expand All @@ -333,18 +338,26 @@ describe('the gate is wired to run', () => {
// check-i18n-dead-keys.mjs: that one is invoked in no workflow at all, by
// design ("This is a REPORT, not a gate"), so a leg there could never be
// observed red in CI — the disease objectui#8834 exists to cure.
const install = ci.indexOf('pnpm install --frozen-lockfile');
const step = ci.indexOf('run: pnpm check:i18n-designer-parity');
expect(step, 'ci.yml does not run `pnpm check:i18n-designer-parity`').toBeGreaterThan(-1);
const install = job.indexOf('pnpm install --frozen-lockfile');
const step = job.indexOf(`run: ${uiRun('check:i18n-designer-parity')}`);
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-designer-parity`').toBeGreaterThan(-1);
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
});

it('is not hidden from the designer table by ci.yml path filters', () => {
// Same reasoning as check-i18n-en-drift.test.ts's twin assertion: an ignore
// entry that covered `packages/` would make this gate unreachable for
// exactly the PRs it judges.
const ignored = ci.slice(0, ci.indexOf('jobs:')).match(/^\s+- '.*'$/gm) ?? [];
for (const pattern of ignored) expect(pattern).not.toMatch(/packages|scripts/);
expect(ignored.length).toBeGreaterThan(0);
// Same reasoning as check-i18n-en-drift.test.ts's twin assertion: the root
// ci.yml's triggers carry no path filter, and the `filter` job's `ui:` globs
// decide whether `type-check-ui` runs, so a glob set that missed the
// designer table or this gate would make it unreachable for exactly the
// PRs it judges.
expect(withoutComments(triggerBlock(ci)), 'a trigger grew a path filter').not.toMatch(/^\s+paths(-ignore)?:/m);
expect(job, '`type-check-ui` no longer reads the `ui` filter output').toContain(
"needs.filter.outputs.ui != 'false'",
);
const globs = uiFilterGlobs(ci);
for (const file of [DESIGNER_TABLE, 'scripts/check-i18n-designer-table-parity.mjs'].map(repoPath)) {
expect(globs.some((glob) => globMatches(glob, file)), `no \`ui:\` filter glob covers ${file}`).toBe(true);
}
});
});
64 changes: 32 additions & 32 deletions ui/scripts/__tests__/check-i18n-en-drift.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
readDesignerTables,
} from '../check-i18n-en-drift.mjs';
import { DESIGNER_TABLE } from '../check-i18n-designer-table-parity.mjs';
import { globMatches, jobBlock, repoPath, rootCi, triggerBlock, uiFilterGlobs, uiRun, UI_JOBS, withoutComments } from './helpers/root-ci';

/**
* objectui#3650 — the behaviour test for `scripts/check-i18n-en-drift.mjs`.
Expand Down Expand Up @@ -964,55 +965,54 @@ describe('resolving the commit to compare against', () => {

describe('the gate is wired to run', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
const ci = fs.readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8');
// The workflow GitHub runs: objectstack's root ci.yml, whose `type-check-ui`
// job is objectui's `Type Check` (objectstack#25697). objectui's own ci.yml
// under `ui/.github/` runs nowhere (objectstack#25712).
const ci = rootCi();
const job = jobBlock(ci, UI_JOBS.typeCheck);

it('package.json exposes it as a named script', () => {
expect(pkg.scripts['check:i18n-drift']).toBe('node scripts/check-i18n-en-drift.mjs');
});

it('ci.yml runs it after the install it needs (it imports typescript)', () => {
const install = ci.indexOf('pnpm install --frozen-lockfile');
const step = ci.indexOf('run: pnpm check:i18n-drift');
expect(step, 'ci.yml does not run `pnpm check:i18n-drift`').toBeGreaterThan(-1);
const install = job.indexOf('pnpm install --frozen-lockfile');
const step = job.indexOf(`run: ${uiRun('check:i18n-drift')}`);
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-drift`').toBeGreaterThan(-1);
expect(install, '`type-check-ui` no longer installs dependencies').toBeGreaterThan(-1);
expect(step, 'the check runs before dependencies are installed').toBeGreaterThan(install);
});

it('gives that job a full-history checkout, or the merge base does not exist', () => {
// actions/checkout defaults to depth 1. Without this the gate cannot resolve
// a base — and by design that is a red build, not a silent pass, so losing
// this line breaks CI loudly. The pin is here so the reason survives.
const job = ci.slice(ci.indexOf(' type-check:'), ci.indexOf('run: pnpm check:i18n-drift'));
expect(job).toContain('fetch-depth: 0');
const step = job.indexOf(`run: ${uiRun('check:i18n-drift')}`);
expect(step, '`type-check-ui` does not run `pnpm --dir ui run check:i18n-drift`').toBeGreaterThan(-1);
expect(job.slice(0, step)).toContain('fetch-depth: 0');
});

it('is not hidden from the locale packs by ci.yml path filters', () => {
// ci.yml `paths-ignore`s markdown, content/, docs/, apps/site/ and
// .changeset/ on its `push` trigger — since objectui#3523 step 2 the only
// trigger that still carries the filter (ci.yml:6; lint.yml:32 is the twin),
// and the only thing the assertion below can see, since it slices the `on:`
// block. None of them can match `packages/i18n/src/locales/*.ts` or the
// ledger, so a push that edits an en string always starts this workflow.
// The root ci.yml carries no `paths-ignore` on any trigger: it starts on
// every pull_request, every queue build, every push to main and the hourly
// schedule. What decides whether `type-check-ui` then runs is the `filter`
// job's `ui:` paths filter, which the job's `if:` reads, so the ten locale
// packs, the ledger and the designer table must each match one of its
// globs. (Until objectstack#25712 this pinned objectui's own `push`
// `paths-ignore` list and its in-job `Decide whether this change needs a
// full run` step, in a ci.yml nothing runs; its near-verbatim twin is in
// `check-action-forward-parity.test.ts`.)
//
// For a PULL REQUEST the conclusion holds for a second and stronger reason.
// This lead-in used to state it in a bare present tense that read as if the
// filter still applied there; objectui#4384 qualified it, together with its
// near-verbatim twin in `check-action-forward-parity.test.ts`, closing the
// #3857 / #4369 / #4381 family (PRs #4371 / #4380 / #4382). No
// `paths-ignore` survives on `pull_request` at all, so such a PR starts this
// workflow a fortiori — measured there: PR #3856, one markdown file, 16
// checks. What decides a pull request now is the in-job `Decide whether this
// change needs a full run` step, whose exclusion list is that `push` filter
// unchanged, held identical to it by
// `scripts/__tests__/merge-queue-reporting.test.ts` — so the patterns pinned
// below are also what keeps the expensive steps running on a locale-pack PR.
//
// A new entry that DID cover them would make the gate unreachable for
// exactly the PRs it judges, on both lanes at once — the objectui#3547 /
// control-bytes.yml lesson, one workflow over.
const ignored = ci.slice(0, ci.indexOf('jobs:')).match(/^\s+- '.*'$/gm) ?? [];
for (const pattern of ignored) {
expect(pattern).not.toMatch(/packages|scripts|locales/);
// A glob set that missed them would make the gate unreachable for exactly
// the PRs it judges — the objectui#3547 / control-bytes.yml lesson, one
// workflow over.
expect(withoutComments(triggerBlock(ci)), 'a trigger grew a path filter').not.toMatch(/^\s+paths(-ignore)?:/m);
expect(job, '`type-check-ui` no longer reads the `ui` filter output').toContain(
"needs.filter.outputs.ui != 'false'",
);
const globs = uiFilterGlobs(ci);
for (const file of [...LOCALES.map(packPath), LEDGER_PATH, DESIGNER_TABLE].map(repoPath)) {
expect(globs.some((glob) => globMatches(glob, file)), `no \`ui:\` filter glob covers ${file}`).toBe(true);
}
expect(ignored.length).toBeGreaterThan(0);
});
});
Loading
Loading