@1aboveio/skills 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +13 -0
- package/.codex-plugin/resources.json +1827 -0
- package/README.md +22 -0
- package/package.json +9 -2
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/airflow/release-check.mjs +3 -1
- package/skills/app-dev/android-foundation/SKILL.md +1 -1
- package/skills/app-dev/android-foundation/release-check.mjs +3 -1
- package/skills/app-dev/app-debug/SKILL.md +1 -1
- package/skills/app-dev/app-debug/release-check.mjs +3 -1
- package/skills/app-dev/better-auth/SKILL.md +1 -1
- package/skills/app-dev/better-auth/release-check.mjs +3 -1
- package/skills/app-dev/data-catalog/SKILL.md +1 -1
- package/skills/app-dev/data-catalog/release-check.mjs +3 -1
- package/skills/app-dev/data-quality/SKILL.md +1 -1
- package/skills/app-dev/data-quality/release-check.mjs +3 -1
- package/skills/app-dev/ios-foundation/SKILL.md +1 -1
- package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
- package/skills/app-dev/monorepo/SKILL.md +1 -1
- package/skills/app-dev/monorepo/release-check.mjs +3 -1
- package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
- package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
- package/skills/app-dev/prisma-setup/SKILL.md +1 -1
- package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
- package/skills/app-dev/pyspark/SKILL.md +1 -1
- package/skills/app-dev/pyspark/release-check.mjs +3 -1
- package/skills/app-dev/python-backend/SKILL.md +1 -1
- package/skills/app-dev/python-backend/release-check.mjs +3 -1
- package/skills/app-dev/shadcn/SKILL.md +1 -1
- package/skills/app-dev/shadcn/release-check.mjs +3 -1
- package/skills/app-dev/zod-v4/SKILL.md +1 -1
- package/skills/app-dev/zod-v4/release-check.mjs +3 -1
- package/skills/cicd/cloud-build/SKILL.md +1 -1
- package/skills/cicd/cloud-build/release-check.mjs +3 -1
- package/skills/cicd/cloud-debug/SKILL.md +1 -1
- package/skills/cicd/cloud-debug/release-check.mjs +3 -1
- package/skills/cicd/cloud-deploy/SKILL.md +1 -1
- package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
- package/skills/cicd/docker/SKILL.md +1 -1
- package/skills/cicd/docker/release-check.mjs +3 -1
- package/skills/cicd/github-actions/SKILL.md +1 -1
- package/skills/cicd/github-actions/release-check.mjs +3 -1
- package/skills/cicd/github-flow/SKILL.md +1 -1
- package/skills/cicd/github-flow/release-check.mjs +3 -1
- package/skills/cicd/google-cloud/SKILL.md +1 -1
- package/skills/cicd/google-cloud/release-check.mjs +3 -1
- package/skills/cicd/mergify/SKILL.md +1 -1
- package/skills/cicd/mergify/release-check.mjs +3 -1
- package/skills/cicd/podman/SKILL.md +1 -1
- package/skills/cicd/podman/release-check.mjs +3 -1
- package/skills/content/article-review/SKILL.md +1 -1
- package/skills/content/article-review/release-check.mjs +3 -1
- package/skills/content/article-to-image/SKILL.md +1 -1
- package/skills/content/article-to-image/release-check.mjs +3 -1
- package/skills/content/flowchart-svg/SKILL.md +1 -1
- package/skills/content/flowchart-svg/release-check.mjs +3 -1
- package/skills/content/image-gen/SKILL.md +1 -1
- package/skills/content/image-gen/release-check.mjs +3 -1
- package/skills/content/mermaid/SKILL.md +1 -1
- package/skills/content/mermaid/release-check.mjs +3 -1
- package/skills/content/wechat-article-writer/SKILL.md +1 -1
- package/skills/content/wechat-article-writer/release-check.mjs +3 -1
- package/skills/content/wechat-publisher/SKILL.md +1 -1
- package/skills/content/wechat-publisher/release-check.mjs +3 -1
- package/skills/content/wechat-search/SKILL.md +1 -1
- package/skills/content/wechat-search/release-check.mjs +3 -1
- package/skills/content/xai-search/SKILL.md +88 -13
- package/skills/content/xai-search/release-check.mjs +3 -1
- package/skills/content/xai-search/scripts/xai_search.sh +125 -149
- package/skills/core/distill-writing/SKILL.md +1 -1
- package/skills/core/distill-writing/release-check.mjs +3 -1
- package/skills/core/doc-to-md/SKILL.md +1 -1
- package/skills/core/doc-to-md/release-check.mjs +3 -1
- package/skills/core/editorial-review/SKILL.md +1 -1
- package/skills/core/editorial-review/release-check.mjs +3 -1
- package/skills/core/house-style/SKILL.md +1 -1
- package/skills/core/house-style/release-check.mjs +3 -1
- package/skills/core/plain-language/SKILL.md +1 -1
- package/skills/core/plain-language/release-check.mjs +3 -1
- package/skills/core/polish-cn/SKILL.md +1 -1
- package/skills/core/polish-cn/release-check.mjs +3 -1
- package/skills/core/reg-parser/SKILL.md +1 -1
- package/skills/core/reg-parser/release-check.mjs +3 -1
- package/skills/core/terminology/SKILL.md +1 -1
- package/skills/core/terminology/release-check.mjs +3 -1
- package/skills/core/write-doc/SKILL.md +1 -1
- package/skills/core/write-doc/release-check.mjs +3 -1
- package/skills/engineering/better-review/SKILL.md +1 -1
- package/skills/engineering/better-review/release-check.mjs +3 -1
- package/skills/engineering/contributing/SKILL.md +1 -1
- package/skills/engineering/contributing/release-check.mjs +3 -1
- package/skills/engineering/e2e-test/SKILL.md +1 -1
- package/skills/engineering/e2e-test/release-check.mjs +3 -1
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
- package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
- package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
- package/skills/engineering/ensure-coverage/SKILL.md +1 -1
- package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
- package/skills/engineering/golden-path/SKILL.md +1 -1
- package/skills/engineering/golden-path/release-check.mjs +3 -1
- package/skills/engineering/harness-runtime/SKILL.md +1 -1
- package/skills/engineering/harness-runtime/release-check.mjs +3 -1
- package/skills/engineering/resolve-issues/SKILL.md +1 -1
- package/skills/engineering/resolve-issues/references/preflight.md +7 -0
- package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
- package/skills/engineering/resolve-issues/references/task-plan.md +5 -1
- package/skills/engineering/resolve-issues/release-check.mjs +3 -1
- package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
- package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
- package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
- package/skills/engineering/resolve-release/SKILL.md +1 -1
- package/skills/engineering/resolve-release/release-check.mjs +3 -1
- package/skills/engineering/rush-issues/SKILL.md +1 -1
- package/skills/engineering/rush-issues/release-check.mjs +3 -1
- package/skills/engineering/rush-release/SKILL.md +1 -1
- package/skills/engineering/rush-release/release-check.mjs +3 -1
- package/skills/engineering/test-quality/SKILL.md +1 -1
- package/skills/engineering/test-quality/release-check.mjs +3 -1
- package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
- package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
- package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
- package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
- package/skills/risk-compliance/contract-review/SKILL.md +1 -1
- package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
- package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
- package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
- package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
- package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
- package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
- package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
- package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
- package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
- package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -436
- package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
- package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
- package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
- package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
- package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
- package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
- package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
- package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
- package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
- package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
- package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
- package/skills/content/image-gen/scripts/options.test.mjs +0 -245
- package/skills/content/xai-search/tests/smoke_test.sh +0 -36
- package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
- package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
- package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
- package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
- package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
- package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
- package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
- package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
- package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
- package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
- package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
- package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
- package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
- package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
- package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
- package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
- package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
- package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
- package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
- package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
- package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
- package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
- package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
- package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
- package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
- package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
- package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
- package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
- package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
- package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
- package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
- package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
- package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
- package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
- package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
- package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
- package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
- package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
- package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
- package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
- package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
- package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
- package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
- package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
- package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
- package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
- package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
- package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
- package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
- package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
- package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
- package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
- package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
- package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
- package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
- package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
|
@@ -1,552 +0,0 @@
|
|
|
1
|
-
// Tests for the design-parity fidelity gate.
|
|
2
|
-
//
|
|
3
|
-
// The cases are derived from the incident, not invented: every divergence
|
|
4
|
-
// fmm-express #521 shipped (page measure, card plane, the Amount input's
|
|
5
|
-
// responsive type step, Description's scale) appears below as a test that the
|
|
6
|
-
// gate blocks it. A gate whose tests are hand-picked from what it happens to
|
|
7
|
-
// catch is the thing this skill calls a vacuous assertion.
|
|
8
|
-
import { test } from 'node:test'
|
|
9
|
-
import assert from 'node:assert/strict'
|
|
10
|
-
|
|
11
|
-
import fs from 'node:fs'
|
|
12
|
-
import os from 'node:os'
|
|
13
|
-
import path from 'node:path'
|
|
14
|
-
import { execFileSync, spawnSync } from 'node:child_process'
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
PARITY_CATALOG, ROLES, FLOOR_VIEWPORTS,
|
|
18
|
-
expectedRows, rowKey, isRealReason,
|
|
19
|
-
validateManifest, compareObserved, valuesAgree, normalize,
|
|
20
|
-
probeScript, scaffoldManifest, detectExpectationRewrites,
|
|
21
|
-
} from './design-parity.mjs'
|
|
22
|
-
|
|
23
|
-
const HERE = path.dirname(new URL(import.meta.url).pathname)
|
|
24
|
-
const SCRIPT = path.join(HERE, 'design-parity.mjs')
|
|
25
|
-
|
|
26
|
-
// The #521 element map, as it would be declared for the merchant charge form.
|
|
27
|
-
const ELEMENTS = {
|
|
28
|
-
container: '[data-testid="charge-form-plane"]',
|
|
29
|
-
primary: '[data-testid="charge-amount-input"]',
|
|
30
|
-
secondary: '[data-testid="description-input"]',
|
|
31
|
-
coupled: '[data-testid="charge-tip-section"]',
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// A complete, decided manifest — built from the catalog so the fixture cannot
|
|
35
|
-
// drift out of sync with doctrine the way a hand-written one would.
|
|
36
|
-
function completeManifest(overrides = {}) {
|
|
37
|
-
const rows = expectedRows(ELEMENTS).map((r) => ({
|
|
38
|
-
...r,
|
|
39
|
-
...(r.relation ? {} : { expected: '0px' }),
|
|
40
|
-
from: 'Dashboard.tipprototype.tsx:781',
|
|
41
|
-
}))
|
|
42
|
-
return {
|
|
43
|
-
source: { ref: 'proto/tip-charge-ia', sha: 'deadbee', path: 'frontend/src/pages/Dashboard.tipprototype.tsx', kind: 'react' },
|
|
44
|
-
surface: 'merchant-dashboard-charge-form',
|
|
45
|
-
url: '/dashboard',
|
|
46
|
-
viewports: FLOOR_VIEWPORTS,
|
|
47
|
-
elements: ELEMENTS,
|
|
48
|
-
rows,
|
|
49
|
-
...overrides,
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const problems = (v) => v.map((x) => `${x.row}: ${x.problem}`).join('\n')
|
|
54
|
-
|
|
55
|
-
// ── the catalog ────────────────────────────────────────────────────────────
|
|
56
|
-
|
|
57
|
-
test('every catalog entry carries the reason it exists (the thing that stops it being deleted)', () => {
|
|
58
|
-
assert.ok(PARITY_CATALOG.length > 0)
|
|
59
|
-
for (const e of PARITY_CATALOG) {
|
|
60
|
-
assert.ok(e.id && e.group, `${e.id}: id + group`)
|
|
61
|
-
assert.ok(e.why && e.why.length > 20, `${e.id}: a real why, not a label`)
|
|
62
|
-
assert.ok(e.relation || (ROLES.includes(e.role) && Array.isArray(e.css) && e.css.length), `${e.id}: a known role + css, or a relation`)
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('the catalog pins each thing #521 lost: measure, plane, per-viewport primary scale, secondary scale, coupled rhythm', () => {
|
|
67
|
-
const ids = PARITY_CATALOG.map((e) => e.id)
|
|
68
|
-
for (const id of ['container.measure', 'container.plane', 'primary.scale', 'secondary.scale', 'rhythm.coupled', 'hierarchy.order']) {
|
|
69
|
-
assert.ok(ids.includes(id), `catalog carries ${id}`)
|
|
70
|
-
}
|
|
71
|
-
// The plane is three properties on purpose: pinning only the border lets a card
|
|
72
|
-
// return through background + shadow.
|
|
73
|
-
const plane = PARITY_CATALOG.find((e) => e.id === 'container.plane')
|
|
74
|
-
assert.deepEqual(plane.css.sort(), ['background-color', 'border-top-width', 'box-shadow'])
|
|
75
|
-
// The responsive step-up is the silent loss, so scale rows are per-viewport.
|
|
76
|
-
assert.ok(PARITY_CATALOG.find((e) => e.id === 'primary.scale').perViewport)
|
|
77
|
-
assert.ok(PARITY_CATALOG.find((e) => e.id === 'secondary.scale').perViewport)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// ── expansion ──────────────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
test('expectedRows expands per-viewport rows across the floor and skips undeclared roles', () => {
|
|
83
|
-
const rows = expectedRows({ primary: '#a' })
|
|
84
|
-
const scale = rows.filter((r) => r.id === 'primary.scale')
|
|
85
|
-
assert.deepEqual(scale.map((r) => r.viewport).sort(), [...FLOOR_VIEWPORTS].sort())
|
|
86
|
-
assert.equal(rows.filter((r) => r.role === 'container').length, 0, 'container undeclared → no container rows owed')
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test('a relation is owed only when both operands are declared (no row nothing can measure)', () => {
|
|
90
|
-
assert.equal(expectedRows({ primary: '#a' }).filter((r) => r.relation).length, 0)
|
|
91
|
-
assert.equal(expectedRows({ primary: '#a', secondary: '#b' }).filter((r) => r.relation).length, 1)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// ── manifest validation ────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
test('a complete, decided manifest validates', () => {
|
|
97
|
-
const v = validateManifest(completeManifest())
|
|
98
|
-
assert.equal(v.length, 0, problems(v))
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
test('THE #521 CASE: a manifest that omits the container-plane rows is blocked, not passed', () => {
|
|
102
|
-
// The defect in miniature — the card plane was never on anyone's list, so nobody
|
|
103
|
-
// ever decided against it, and four rounds of user correction stood in for the gate.
|
|
104
|
-
const m = completeManifest()
|
|
105
|
-
m.rows = m.rows.filter((r) => r.id !== 'container.plane')
|
|
106
|
-
const v = validateManifest(m)
|
|
107
|
-
assert.ok(v.length >= 3, 'all three plane properties are owed')
|
|
108
|
-
assert.match(problems(v), /missing row container\.plane/)
|
|
109
|
-
assert.match(problems(v), /omission is not a decision/)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test('a dropped responsive viewport is blocked (the flattened Amount type scale)', () => {
|
|
113
|
-
const m = completeManifest()
|
|
114
|
-
m.rows = m.rows.filter((r) => !(r.id === 'primary.scale' && r.viewport === 1280))
|
|
115
|
-
assert.match(problems(validateManifest(m)), /missing row primary\.scale\|primary\|font-size\|1280/)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
test('an undecided row (expected: TODO) is blocked', () => {
|
|
119
|
-
const m = completeManifest()
|
|
120
|
-
m.rows[0].expected = 'TODO'
|
|
121
|
-
assert.match(problems(validateManifest(m)), /expected is TODO/)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
test('a row may be rejected — but only with a reason a reader can disagree with', () => {
|
|
125
|
-
const weak = completeManifest()
|
|
126
|
-
weak.rows[0] = { ...weak.rows[0], expected: undefined, reject: 'n/a' }
|
|
127
|
-
assert.match(problems(validateManifest(weak)), /no real reason/)
|
|
128
|
-
|
|
129
|
-
const real = completeManifest()
|
|
130
|
-
real.rows[0] = { ...real.rows[0], expected: undefined, from: undefined, reject: 'source is a Figma frame with no measurable page container; measure pinned on the primary instead' }
|
|
131
|
-
assert.equal(validateManifest(real).length, 0, problems(validateManifest(real)))
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
test('isRealReason refuses the ritual forms and accepts an argument', () => {
|
|
135
|
-
for (const s of ['', ' ', 'n/a', 'N/A', 'none', '--', 'TODO', 'tbd', 'no']) assert.equal(isRealReason(s), false, `refuses ${JSON.stringify(s)}`)
|
|
136
|
-
assert.equal(isRealReason('prototype has no fixed measure; the app shell owns width here'), true)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
test('every expected value names the source line it was read off', () => {
|
|
140
|
-
const m = completeManifest()
|
|
141
|
-
delete m.rows[0].from
|
|
142
|
-
assert.match(problems(validateManifest(m)), /no `from:` citation/)
|
|
143
|
-
|
|
144
|
-
const bad = completeManifest()
|
|
145
|
-
bad.rows[0].from = 'somewhere in the prototype'
|
|
146
|
-
assert.match(problems(validateManifest(bad)), /is not a `<path>:<line>` citation/)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
test('a manifest with no pinned source is blocked — an unpinned baseline is not fidelity', () => {
|
|
150
|
-
const noSha = completeManifest({ source: { path: 'p.tsx' } })
|
|
151
|
-
assert.match(problems(validateManifest(noSha)), /pin the design source at a commit/)
|
|
152
|
-
const noPath = completeManifest({ source: { sha: 'abc' } })
|
|
153
|
-
assert.match(problems(validateManifest(noPath)), /must name the design source/)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
test('citations are shape-checked but warned, not blocked, when git cannot answer', () => {
|
|
157
|
-
const v = validateManifest(completeManifest()) // no resolveSource passed
|
|
158
|
-
assert.equal(v.length, 0)
|
|
159
|
-
assert.ok(v.warnings.some((w) => /citation not resolved/.test(w.problem)))
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
test('a citation that does not resolve in the source blob is blocked', () => {
|
|
163
|
-
const v = validateManifest(completeManifest(), {
|
|
164
|
-
resolveSource: () => ({ ok: false, why: 'no `Dashboard.tipprototype.tsx` in deadbee' }),
|
|
165
|
-
})
|
|
166
|
-
assert.ok(v.length > 0)
|
|
167
|
-
assert.match(problems(v), /does not resolve/)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
test('an unknown role warns rather than blocking (its rows are simply not derived)', () => {
|
|
171
|
-
const m = completeManifest({ elements: { ...ELEMENTS, footer: '#f' } })
|
|
172
|
-
const v = validateManifest(m)
|
|
173
|
-
assert.equal(v.length, 0, problems(v))
|
|
174
|
-
assert.ok(v.warnings.some((w) => /unknown role/.test(w.problem)))
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
// ── comparison against the rendered page ───────────────────────────────────
|
|
178
|
-
|
|
179
|
-
function observed(pairs, viewport = 375) {
|
|
180
|
-
return { runs: [{ viewport, measurements: pairs.map(([role, css, value]) => ({ role, css, value })) }] }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function oneRow(row) {
|
|
184
|
-
return { source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375], rows: [row] }
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
test('THE #521 CASE: a page laid out to the wrong measure diverges', () => {
|
|
188
|
-
const m = oneRow({ id: 'container.measure', role: 'container', css: 'max-width', expected: '672px', from: 'p.tsx:781' })
|
|
189
|
-
const bad = compareObserved(m, observed([['container', 'max-width', '448px']])) // max-w-md against a max-w-2xl source
|
|
190
|
-
assert.equal(bad.length, 1)
|
|
191
|
-
assert.match(bad[0].problem, /expected 672px, page has 448px/)
|
|
192
|
-
assert.match(bad[0].problem, /source: p\.tsx:781/, 'the failure names the source line to go read')
|
|
193
|
-
|
|
194
|
-
assert.equal(compareObserved(m, observed([['container', 'max-width', '672px']])).length, 0)
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
test('THE #521 CASE: the card plane coming back through any one of its three properties diverges', () => {
|
|
198
|
-
const rows = [
|
|
199
|
-
{ id: 'container.plane', role: 'container', css: 'background-color', expected: 'rgba(0, 0, 0, 0)', from: 'p.tsx:781' },
|
|
200
|
-
{ id: 'container.plane', role: 'container', css: 'box-shadow', expected: 'none', from: 'p.tsx:781' },
|
|
201
|
-
{ id: 'container.plane', role: 'container', css: 'border-top-width', expected: '0px', from: 'p.tsx:781' },
|
|
202
|
-
]
|
|
203
|
-
const m = { source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375], rows }
|
|
204
|
-
const flat = observed([['container', 'background-color', 'rgba(0, 0, 0, 0)'], ['container', 'box-shadow', 'none'], ['container', 'border-top-width', '0px']])
|
|
205
|
-
assert.equal(compareObserved(m, flat).length, 0)
|
|
206
|
-
|
|
207
|
-
const shadowOnly = observed([['container', 'background-color', 'rgba(0, 0, 0, 0)'], ['container', 'box-shadow', 'rgba(0,0,0,0.05) 0px 1px 2px 0px'], ['container', 'border-top-width', '0px']])
|
|
208
|
-
assert.equal(compareObserved(m, shadowOnly).length, 1, 'a shadow alone still reads as a plane')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
test('THE #521 CASE: a lost responsive step-up diverges at the viewport that lost it', () => {
|
|
212
|
-
const m = {
|
|
213
|
-
source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375, 1280],
|
|
214
|
-
rows: [
|
|
215
|
-
{ id: 'primary.scale', role: 'primary', css: 'font-size', viewport: 375, expected: '24px', from: 'p.tsx:825' },
|
|
216
|
-
{ id: 'primary.scale', role: 'primary', css: 'font-size', viewport: 1280, expected: '30px', from: 'p.tsx:825' },
|
|
217
|
-
],
|
|
218
|
-
}
|
|
219
|
-
const flattened = { runs: [
|
|
220
|
-
{ viewport: 375, measurements: [{ role: 'primary', css: 'font-size', value: '24px' }] },
|
|
221
|
-
{ viewport: 1280, measurements: [{ role: 'primary', css: 'font-size', value: '24px' }] }, // never stepped up
|
|
222
|
-
] }
|
|
223
|
-
const bad = compareObserved(m, flattened)
|
|
224
|
-
assert.equal(bad.length, 1)
|
|
225
|
-
assert.match(bad[0].row, /\|1280$/, 'only the viewport that lost it fails')
|
|
226
|
-
assert.match(bad[0].problem, /expected 30px, page has 24px/)
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
test('THE #521 CASE: Description at Amount weight fails the relational row', () => {
|
|
230
|
-
const m = oneRow({ id: 'hierarchy.order', relation: 'primary.font-size > secondary.font-size', from: 'p.tsx:825' })
|
|
231
|
-
const equal = observed([['primary', 'font-size', '24px'], ['secondary', 'font-size', '24px']])
|
|
232
|
-
const bad = compareObserved(m, equal)
|
|
233
|
-
assert.equal(bad.length, 1)
|
|
234
|
-
assert.match(bad[0].problem, /primary\.font-size=24px is not > secondary\.font-size=24px/)
|
|
235
|
-
|
|
236
|
-
assert.equal(compareObserved(m, observed([['primary', 'font-size', '24px'], ['secondary', 'font-size', '14px']])).length, 0)
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
test('a missing element fails loudly — it must never read as agreement', () => {
|
|
240
|
-
const m = oneRow({ id: 'primary.box', role: 'primary', css: 'padding-top', expected: '12px', from: 'p.tsx:825' })
|
|
241
|
-
const gone = compareObserved(m, observed([['primary', 'padding-top', null]]))
|
|
242
|
-
assert.equal(gone.length, 1)
|
|
243
|
-
assert.match(gone[0].problem, /element not found/)
|
|
244
|
-
assert.match(gone[0].problem, /charge-amount-input/, 'names the selector that missed')
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
test('a row the probe never measured fails — silence is not a pass', () => {
|
|
248
|
-
const m = oneRow({ id: 'primary.box', role: 'primary', css: 'padding-top', expected: '12px', from: 'p.tsx:825' })
|
|
249
|
-
const bad = compareObserved(m, { runs: [] })
|
|
250
|
-
assert.equal(bad.length, 1)
|
|
251
|
-
assert.match(bad[0].problem, /not measured/)
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
test('a rejected row is not compared (the reject is the decision)', () => {
|
|
255
|
-
const m = oneRow({ id: 'container.measure', role: 'container', css: 'max-width', reject: 'app shell owns the measure on this route; nothing to pin here' })
|
|
256
|
-
assert.equal(compareObserved(m, { runs: [] }).length, 0)
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
test('a viewport-independent row that measures differently per viewport is caught, not silently passed on the first hit', () => {
|
|
260
|
-
const m = {
|
|
261
|
-
source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375, 1280],
|
|
262
|
-
rows: [{ id: 'container.padding', role: 'container', css: 'padding-left', expected: '0px', from: 'p.tsx:781' }],
|
|
263
|
-
}
|
|
264
|
-
const responsive = { runs: [
|
|
265
|
-
{ viewport: 375, measurements: [{ role: 'container', css: 'padding-left', value: '0px' }] },
|
|
266
|
-
{ viewport: 1280, measurements: [{ role: 'container', css: 'padding-left', value: '24px' }] },
|
|
267
|
-
] }
|
|
268
|
-
const bad = compareObserved(m, responsive)
|
|
269
|
-
assert.equal(bad.length, 1)
|
|
270
|
-
assert.match(bad[0].problem, /split it per viewport/)
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
test('sub-pixel layout noise is not a design decision; a real step is', () => {
|
|
274
|
-
assert.equal(valuesAgree('24px', '24.4px'), true)
|
|
275
|
-
assert.equal(valuesAgree('24px', '24.6px'), false)
|
|
276
|
-
assert.equal(valuesAgree('24', '24px'), true, 'a bare number in expected means px')
|
|
277
|
-
assert.equal(valuesAgree('none', 'none'), true)
|
|
278
|
-
assert.equal(valuesAgree('rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)'), false, 'colour strings are compared as written — normalize in the probe, not here')
|
|
279
|
-
assert.deepEqual(normalize(' 24PX '), { num: 24 })
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
// ── expectation rewrites (the re-baseline-to-green analogue) ───────────────
|
|
283
|
-
|
|
284
|
-
const twoVersions = (before, after, srcAfter) => [
|
|
285
|
-
{ source: { sha: 'aaa', path: 'p.tsx' }, elements: ELEMENTS, rows: [{ id: 'container.measure', role: 'container', css: 'max-width', ...before }] },
|
|
286
|
-
{ source: { sha: srcAfter || 'aaa', path: 'p.tsx' }, elements: ELEMENTS, rows: [{ id: 'container.measure', role: 'container', css: 'max-width', ...after }] },
|
|
287
|
-
]
|
|
288
|
-
|
|
289
|
-
test('rewriting an expectation to match the page, under an unmoved source, BLOCKS', () => {
|
|
290
|
-
// The cheapest way to green this gate: stop fixing the page, edit the target.
|
|
291
|
-
// One character in YAML, no PNG in the diff for a reviewer to notice.
|
|
292
|
-
const [prev, next] = twoVersions(
|
|
293
|
-
{ expected: '672px', from: 'p.tsx:781' },
|
|
294
|
-
{ expected: '448px', from: 'p.tsx:781' },
|
|
295
|
-
)
|
|
296
|
-
const out = detectExpectationRewrites(prev, next)
|
|
297
|
-
assert.equal(out.length, 1)
|
|
298
|
-
assert.equal(out[0].level, 'block')
|
|
299
|
-
assert.match(out[0].problem, /672px → 448px/)
|
|
300
|
-
assert.match(out[0].problem, /moves the target to the page/)
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
test('the same rewrite clears when the mis-read is declared', () => {
|
|
304
|
-
const [prev, next] = twoVersions(
|
|
305
|
-
{ expected: '672px', from: 'p.tsx:781' },
|
|
306
|
-
{ expected: '448px', from: 'p.tsx:781', rebase: 'misread the prototype: 672 was the sm: breakpoint, the base measure is 448' },
|
|
307
|
-
)
|
|
308
|
-
assert.equal(detectExpectationRewrites(prev, next).length, 0)
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
test('a `rebase:` that says nothing does not clear it', () => {
|
|
312
|
-
const [prev, next] = twoVersions(
|
|
313
|
-
{ expected: '672px', from: 'p.tsx:781' },
|
|
314
|
-
{ expected: '448px', from: 'p.tsx:781', rebase: 'n/a' },
|
|
315
|
-
)
|
|
316
|
-
assert.equal(detectExpectationRewrites(prev, next)[0].level, 'block')
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
test('when the design source itself moved, a changed expectation warns rather than blocks', () => {
|
|
320
|
-
// The source is no longer the same document; expectations are SUPPOSED to change.
|
|
321
|
-
// Blocking here would make re-pinning a prototype cost a fight with the gate.
|
|
322
|
-
const [prev, next] = twoVersions(
|
|
323
|
-
{ expected: '672px', from: 'p.tsx:781' },
|
|
324
|
-
{ expected: '448px', from: 'p.tsx:781' },
|
|
325
|
-
'bbb',
|
|
326
|
-
)
|
|
327
|
-
const out = detectExpectationRewrites(prev, next)
|
|
328
|
-
assert.equal(out[0].level, 'warn')
|
|
329
|
-
assert.match(out[0].problem, /design source moved/)
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
test('a changed expectation with a new citation warns — that is a re-read, not a capitulation', () => {
|
|
333
|
-
const [prev, next] = twoVersions(
|
|
334
|
-
{ expected: '672px', from: 'p.tsx:781' },
|
|
335
|
-
{ expected: '448px', from: 'p.tsx:602' },
|
|
336
|
-
)
|
|
337
|
-
assert.equal(detectExpectationRewrites(prev, next)[0].level, 'warn')
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
test('unchanged rows, new rows and removed rows are not rewrites', () => {
|
|
341
|
-
const [prev] = twoVersions({ expected: '672px', from: 'p.tsx:781' }, {})
|
|
342
|
-
assert.equal(detectExpectationRewrites(prev, prev).length, 0, 'identical manifests')
|
|
343
|
-
const added = { ...prev, rows: [...prev.rows, { id: 'primary.box', role: 'primary', css: 'padding-top', expected: '12px', from: 'p.tsx:825' }] }
|
|
344
|
-
assert.equal(detectExpectationRewrites(prev, added).length, 0, 'a new row rewrote nothing')
|
|
345
|
-
assert.equal(detectExpectationRewrites(prev, { ...prev, rows: [] }).length, 0, 'a removed row is caught by the completeness check, not here')
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
test('CLI: --diff blocks a manifest whose expectation was rewritten to match the page', () => {
|
|
349
|
-
// Real git history, because the check reads the previous version with `git show`.
|
|
350
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-git-'))
|
|
351
|
-
const git = (...a) => execFileSync('git', a, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
352
|
-
git('init', '-q')
|
|
353
|
-
git('config', 'user.email', 't@t'); git('config', 'user.name', 't')
|
|
354
|
-
// A COMPLETE manifest, so the only thing left to fail on is the rewrite itself.
|
|
355
|
-
const m = {
|
|
356
|
-
source: { sha: 'aaa', path: 'p.tsx' }, elements: { container: '#c' }, viewports: [375],
|
|
357
|
-
rows: expectedRows({ container: '#c' }, [375]).map((r) => ({ ...r, expected: r.css === 'max-width' ? '672px' : '0px', from: 'p.tsx:781' })),
|
|
358
|
-
}
|
|
359
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
|
|
360
|
-
git('add', '-A'); git('commit', '-qm', 'pin the measure')
|
|
361
|
-
|
|
362
|
-
m.rows.find((r) => r.css === 'max-width').expected = '448px' // the page won the argument
|
|
363
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
|
|
364
|
-
const r = run(['validate', 'm.json', '--diff', 'HEAD', '--root', dir], dir)
|
|
365
|
-
assert.equal(r.status, 1)
|
|
366
|
-
assert.match(r.stdout, /expectation\(s\) rewritten to match the page/)
|
|
367
|
-
assert.match(r.stdout, /672px → 448px/)
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
test('CLI: --diff is silent when the manifest is new at that base', () => {
|
|
371
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-git-'))
|
|
372
|
-
const git = (...a) => execFileSync('git', a, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
373
|
-
git('init', '-q'); git('config', 'user.email', 't@t'); git('config', 'user.name', 't')
|
|
374
|
-
fs.writeFileSync(path.join(dir, 'seed.txt'), 'x'); git('add', '-A'); git('commit', '-qm', 'seed')
|
|
375
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify({ source: { sha: 'a', path: 'p.tsx' }, elements: { container: '#c' }, viewports: [375], rows: [] }))
|
|
376
|
-
const r = run(['validate', 'm.json', '--diff', 'HEAD', '--root', dir], dir)
|
|
377
|
-
assert.ok(!/rewritten/.test(r.stdout), 'a manifest with no previous version reports nothing about rewrites')
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
// ── probe ──────────────────────────────────────────────────────────────────
|
|
381
|
-
|
|
382
|
-
// Run the emitted snippet against a stub page. Asserting on its SOURCE would pass
|
|
383
|
-
// on a snippet that throws; the only honest question about a probe is what it
|
|
384
|
-
// returns when something evaluates it.
|
|
385
|
-
function runProbe(js, { missing = [] } = {}) {
|
|
386
|
-
const queried = []
|
|
387
|
-
const document = {
|
|
388
|
-
querySelector(sel) { queried.push(sel); return missing.includes(sel) ? null : { sel } },
|
|
389
|
-
}
|
|
390
|
-
const getComputedStyle = (el) => new Proxy({}, { get: (_, css) => `<${el.sel}|${String(css)}>` })
|
|
391
|
-
const result = new Function('document', 'getComputedStyle', `return ${js}`)(document, getComputedStyle)
|
|
392
|
-
return { result, queried }
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
test('the probe measures every catalog property plus the operands the relations need', () => {
|
|
396
|
-
const { result, queried } = runProbe(probeScript({ surface: 's', elements: ELEMENTS, viewports: [375] }, 375))
|
|
397
|
-
assert.equal(result.surface, 's')
|
|
398
|
-
assert.equal(result.runs[0].viewport, 375)
|
|
399
|
-
|
|
400
|
-
const measured = new Set(result.runs[0].measurements.map((m) => `${m.role}|${m.css}`))
|
|
401
|
-
for (const owed of expectedRows(ELEMENTS)) {
|
|
402
|
-
if (owed.relation) continue
|
|
403
|
-
assert.ok(measured.has(`${owed.role}|${owed.css}`), `probe reads ${owed.role}.${owed.css}`)
|
|
404
|
-
}
|
|
405
|
-
// The relational row's operands must be measured even though no plain row asks
|
|
406
|
-
// for secondary.font-size at an unqualified viewport.
|
|
407
|
-
assert.ok(measured.has('primary|font-size') && measured.has('secondary|font-size'))
|
|
408
|
-
for (const sel of Object.values(ELEMENTS)) assert.ok(queried.includes(sel), `probe queries ${sel}`)
|
|
409
|
-
assert.ok(result.runs[0].measurements.every((m) => typeof m.value === 'string'))
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
test('the probe reports a missing element as null rather than throwing', () => {
|
|
413
|
-
// compareObserved turns null into "element not found"; a probe that threw here
|
|
414
|
-
// would produce no results file at all, and an absent file is easy to read as
|
|
415
|
-
// "nothing to report".
|
|
416
|
-
const { result } = runProbe(probeScript({ elements: ELEMENTS, viewports: [375] }, 375), { missing: [ELEMENTS.container] })
|
|
417
|
-
const container = result.runs[0].measurements.filter((m) => m.role === 'container')
|
|
418
|
-
assert.ok(container.length > 0)
|
|
419
|
-
assert.ok(container.every((m) => m.value === null))
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
test('the probe emits one entry per role+property, not one per manifest row', () => {
|
|
423
|
-
const js = probeScript({ elements: ELEMENTS, viewports: [375, 1280] }, 375)
|
|
424
|
-
const fontSizeEntries = js.match(/"css": "font-size"/g) || []
|
|
425
|
-
assert.equal(fontSizeEntries.length, 2, 'primary + secondary, not primary×2 viewports + secondary×2')
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
// ── scaffold ───────────────────────────────────────────────────────────────
|
|
429
|
-
|
|
430
|
-
test('scaffold emits the whole catalog as TODO rows, carrying each reason into the file', () => {
|
|
431
|
-
const md = scaffoldManifest({ source: { ref: 'proto/x', sha: 'abc123', path: 'proto.tsx' }, surface: 's', url: '/u', elements: ELEMENTS })
|
|
432
|
-
assert.match(md, /source:\n {2}ref: proto\/x\n {2}sha: abc123/)
|
|
433
|
-
for (const e of PARITY_CATALOG) {
|
|
434
|
-
assert.ok(md.includes(`id: ${e.id}`), `scaffold carries ${e.id}`)
|
|
435
|
-
assert.ok(md.includes(e.why), `scaffold carries the reason for ${e.id}`)
|
|
436
|
-
}
|
|
437
|
-
assert.match(md, /expected: TODO/)
|
|
438
|
-
assert.match(md, /read off the DESIGN SOURCE/, 'the file states the fidelity-not-drift rule where it will be read')
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
test('scaffold leaves no TODO value on a relational row (the relation IS the expectation)', () => {
|
|
442
|
-
const md = scaffoldManifest({ source: { path: 'p.tsx' }, elements: ELEMENTS })
|
|
443
|
-
const block = md.slice(md.indexOf('id: hierarchy.order'))
|
|
444
|
-
const nextRow = block.indexOf('\n - id:')
|
|
445
|
-
assert.ok(!(nextRow === -1 ? block : block.slice(0, nextRow)).includes('expected: TODO'))
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
test('scaffold comments out undeclared roles instead of emitting rows nothing can measure', () => {
|
|
449
|
-
const md = scaffoldManifest({ source: { path: 'p.tsx' }, elements: { primary: '#a' } })
|
|
450
|
-
assert.match(md, /# container: '<selector>'/)
|
|
451
|
-
assert.ok(!md.includes('id: container.measure'))
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
test('scaffold output is a manifest validate accepts once filled in', () => {
|
|
455
|
-
// The two ends have to agree, or the skeleton teaches a shape the gate rejects.
|
|
456
|
-
const owed = expectedRows(ELEMENTS)
|
|
457
|
-
const md = scaffoldManifest({ source: { path: 'p.tsx' }, elements: ELEMENTS })
|
|
458
|
-
for (const r of owed) assert.ok(md.includes(`id: ${r.id}`), `${rowKey(r)} is in the skeleton`)
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
// ── CLI ────────────────────────────────────────────────────────────────────
|
|
462
|
-
|
|
463
|
-
const run = (args, cwd) => spawnSync(process.execPath, [SCRIPT, ...args], { cwd, encoding: 'utf8' })
|
|
464
|
-
|
|
465
|
-
test('CLI: validate exits 1 and names the missing rows on an incomplete manifest', () => {
|
|
466
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
|
|
467
|
-
const m = completeManifest()
|
|
468
|
-
m.rows = m.rows.filter((r) => r.id !== 'container.plane')
|
|
469
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
|
|
470
|
-
const r = run(['validate', 'm.json', '--root', dir], dir)
|
|
471
|
-
assert.equal(r.status, 1)
|
|
472
|
-
assert.match(r.stdout, /missing row container\.plane/)
|
|
473
|
-
})
|
|
474
|
-
|
|
475
|
-
test('CLI: a run without --observed announces on stderr that it did not check the page', () => {
|
|
476
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
|
|
477
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(completeManifest()))
|
|
478
|
-
const r = run(['validate', 'm.json', '--root', dir], dir)
|
|
479
|
-
// The manifest cites a source that does not exist in `dir`, so this run blocks —
|
|
480
|
-
// what is asserted here is the NOTE, which must appear on the misleading path too.
|
|
481
|
-
assert.match(r.stderr, /reduced form/)
|
|
482
|
-
assert.match(r.stderr, /does NOT check the page/)
|
|
483
|
-
assert.match(r.stdout, /does not resolve/, 'and an unresolvable source citation blocks')
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
test('CLI: a manifest citing a real blob at a real commit resolves and passes', () => {
|
|
487
|
-
const repo = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: HERE, encoding: 'utf8' }).trim()
|
|
488
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
|
|
489
|
-
const tracked = 'skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs'
|
|
490
|
-
const m = completeManifest({ source: { ref: 'HEAD', sha: 'HEAD', path: tracked, kind: 'react' } })
|
|
491
|
-
m.rows = m.rows.map((r) => ({ ...r, from: r.from ? `${tracked}:10` : r.from }))
|
|
492
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
|
|
493
|
-
const r = run(['validate', path.join(dir, 'm.json'), '--root', repo], repo)
|
|
494
|
-
assert.equal(r.status, 0, `${r.stdout}\n${r.stderr}`)
|
|
495
|
-
assert.match(r.stdout, /page unchecked/, 'the pass says what it did not check')
|
|
496
|
-
})
|
|
497
|
-
|
|
498
|
-
test('CLI: --observed turns the reduced form into the real verdict, and divergence exits 1', () => {
|
|
499
|
-
const repo = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: HERE, encoding: 'utf8' }).trim()
|
|
500
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
|
|
501
|
-
const tracked = 'skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs'
|
|
502
|
-
const m = completeManifest({ source: { ref: 'HEAD', sha: 'HEAD', path: tracked, kind: 'react' } })
|
|
503
|
-
m.rows = m.rows.map((r) => ({ ...r, from: r.from ? `${tracked}:10` : r.from }))
|
|
504
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
|
|
505
|
-
|
|
506
|
-
// Every row expects '0px'; measure one of them wrong.
|
|
507
|
-
const runs = FLOOR_VIEWPORTS.map((viewport) => ({
|
|
508
|
-
viewport,
|
|
509
|
-
measurements: [...new Set(expectedRows(ELEMENTS).filter((r) => !r.relation).map((r) => `${r.role}|${r.css}`))]
|
|
510
|
-
.map((k) => { const [role, css] = k.split('|'); return { role, css, value: role === 'container' && css === 'max-width' ? '448px' : '0px' } }),
|
|
511
|
-
}))
|
|
512
|
-
fs.writeFileSync(path.join(dir, 'o.json'), JSON.stringify({ runs }))
|
|
513
|
-
|
|
514
|
-
const r = run(['validate', path.join(dir, 'm.json'), '--observed', path.join(dir, 'o.json'), '--root', repo], repo)
|
|
515
|
-
assert.equal(r.status, 1)
|
|
516
|
-
assert.match(r.stdout, /divergence\(s\) from the design source/)
|
|
517
|
-
assert.match(r.stdout, /expected 0px, page has 448px/)
|
|
518
|
-
assert.ok(!/reduced form/.test(r.stderr), 'with --observed there is no reduced-form caveat')
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
test('CLI: scaffold prints a manifest skeleton; probe prints evaluatable JS', () => {
|
|
522
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
|
|
523
|
-
const s = run(['scaffold', '--source', 'proto/x:proto.tsx', '--surface', 'charge-form', '--url', '/dashboard', '--role', `primary=${ELEMENTS.primary}`], dir)
|
|
524
|
-
assert.equal(s.status, 0, s.stderr)
|
|
525
|
-
assert.match(s.stdout, /surface: charge-form/)
|
|
526
|
-
assert.match(s.stdout, /id: primary\.scale/)
|
|
527
|
-
|
|
528
|
-
fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify({ surface: 's', elements: ELEMENTS, viewports: [375] }))
|
|
529
|
-
const p = run(['probe', 'm.json', '--viewport', '375'], dir)
|
|
530
|
-
assert.equal(p.status, 0, p.stderr)
|
|
531
|
-
assert.match(p.stdout, /getComputedStyle/)
|
|
532
|
-
assert.doesNotThrow(() => new Function(`return typeof (${p.stdout.trim()})`.replace('getComputedStyle', 'globalThis.__x')))
|
|
533
|
-
})
|
|
534
|
-
|
|
535
|
-
test('CLI: an unresolvable ref leaves `sha:` unfilled rather than copying the moving ref in', () => {
|
|
536
|
-
// A branch that lives in another repo (the prototype case) must not come back as
|
|
537
|
-
// a manifest that LOOKS pinned. The placeholder is what makes `validate` and the
|
|
538
|
-
// next reader both notice.
|
|
539
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
|
|
540
|
-
const r = run(['scaffold', '--source', 'proto/not-here:proto.tsx', '--surface', 's', '--url', '/u', '--role', 'primary=#a'], dir)
|
|
541
|
-
assert.equal(r.status, 0)
|
|
542
|
-
assert.match(r.stdout, /sha: <commit-sha>/)
|
|
543
|
-
assert.ok(!r.stdout.includes('sha: proto/not-here'))
|
|
544
|
-
assert.match(r.stderr, /could not resolve/)
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
test('CLI: usage errors exit 2, never 0', () => {
|
|
548
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
|
|
549
|
-
assert.equal(run([], dir).status, 2)
|
|
550
|
-
assert.equal(run(['validate', 'nope.json'], dir).status, 2)
|
|
551
|
-
assert.equal(run(['scaffold'], dir).status, 2)
|
|
552
|
-
})
|