diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31923b5..15620d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,4 @@ jobs: cache: pnpm - run: pnpm install - run: pnpm run lint + - run: pnpm run test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afd6005..4acf8d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ For major changes requiring RFC: ## Contributing Test Fixtures -Test fixtures validate TOON implementations across languages. Add your test to `tests/fixtures/encode/` or `tests/fixtures/decode/`, validate against `tests/fixtures.schema.json`, and submit a PR. +Test fixtures validate TOON implementations across languages. Add your test to `tests/fixtures/encode/` or `tests/fixtures/decode/`, run `pnpm test` to validate it against `tests/fixtures.schema.json`, and submit a PR. See [tests/README.md](./tests/README.md) for fixture structure and guidelines. diff --git a/package.json b/package.json index 7641466..9223570 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,12 @@ ], "scripts": { "lint": "eslint .", + "test": "node scripts/validate-fixtures.ts", "release": "bumpp" }, "devDependencies": { "@antfu/eslint-config": "^9.5.1", + "ajv": "^8.20.0", "bumpp": "^12.3.0", "eslint": "^10.11.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55be21f..e53a3f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@antfu/eslint-config': specifier: ^9.5.1 version: 9.5.1(@typescript-eslint/typescript-estree@8.71.0(typescript@6.0.3))(@typescript-eslint/utils@8.71.0(eslint@10.11.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.43)(eslint@10.11.0(jiti@2.7.0))(typescript@6.0.3) + ajv: + specifier: ^8.20.0 + version: 8.20.0 bumpp: specifier: ^12.3.0 version: 12.3.0 @@ -390,6 +393,9 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansis@4.4.0: resolution: {integrity: sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==} engines: {node: '>=14'} @@ -782,6 +788,9 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -922,6 +931,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1240,6 +1252,10 @@ packages: resolution: {integrity: sha512-ycwFAS14Jw4mppvmK4GR/J6u3WpWpjkEApehuHtLc/8VpPNpDMbQ4WjqwplXifGeyKOzHSFLmSPqzksDQE2Sfg==} hasBin: true + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + reserved-identifiers@1.2.0: resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} engines: {node: '>=18'} @@ -1828,6 +1844,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansis@4.4.0: {} are-docs-informative@0.1.1: {} @@ -2252,6 +2275,8 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 + fast-uri@3.1.8: {} + fast-wrap-ansi@0.2.2: dependencies: fast-string-width: 3.0.2 @@ -2363,6 +2388,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} jsonc-eslint-parser@3.3.0: @@ -2881,6 +2908,8 @@ snapshots: dependencies: jsesc: 3.1.0 + require-from-string@2.0.2: {} + reserved-identifiers@1.2.0: {} resolve-pkg-maps@1.0.0: {} diff --git a/scripts/validate-fixtures.ts b/scripts/validate-fixtures.ts new file mode 100644 index 0000000..045f11d --- /dev/null +++ b/scripts/validate-fixtures.ts @@ -0,0 +1,29 @@ +import { readdir, readFile } from 'node:fs/promises' +import { join } from 'node:path' +import process from 'node:process' +import Ajv from 'ajv' + +const testsDir = join(import.meta.dirname, '../tests') +const schema = JSON.parse(await readFile(join(testsDir, 'fixtures.schema.json'), 'utf8')) +const validate = new Ajv({ allErrors: true }).compile(schema) + +const fixtureErrors: string[] = [] +for (const category of ['encode', 'decode']) { + const categoryDir = join(testsDir, 'fixtures', category) + const fileNames = (await readdir(categoryDir)).filter(fileName => fileName.endsWith('.json')) + for (const fileName of fileNames) { + const fixture = JSON.parse(await readFile(join(categoryDir, fileName), 'utf8')) + const fixturePath = `${category}/${fileName}` + if (!validate(fixture)) + fixtureErrors.push(...validate.errors!.map(error => `${fixturePath}: ${error.instancePath || '/'} ${error.message}`)) + if (fixture.category !== category) + fixtureErrors.push(`${fixturePath}: /category is "${fixture.category}", expected "${category}"`) + } +} + +if (fixtureErrors.length > 0) { + console.error(fixtureErrors.join('\n')) + process.exit(1) +} + +console.log('All fixtures match tests/fixtures.schema.json') diff --git a/tests/README.md b/tests/README.md index b06f6e0..6993848 100644 --- a/tests/README.md +++ b/tests/README.md @@ -148,17 +148,8 @@ Load each fixture file, run every entry in its `tests` array through your encode ## Validating Fixtures -All fixture files should validate against [`fixtures.schema.json`](./fixtures.schema.json). Run the commands below from the repository root: - -```bash -# Using ajv-cli -npx ajv-cli validate -s tests/fixtures.schema.json -d "tests/fixtures/**/*.json" - -# Using check-jsonschema (Python) -pip install check-jsonschema -check-jsonschema --schemafile tests/fixtures.schema.json tests/fixtures/**/*.json -``` +`pnpm test` validates every fixture against [`fixtures.schema.json`](./fixtures.schema.json) and checks that its `category` matches its directory. ## Contributing Test Cases -Add your test to the matching fixture file, reference the spec section it exercises, verify the expected output against SPEC.md, and validate the file against the schema before submitting a PR. See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines. +Add your test to the matching fixture file, reference the spec section it exercises, verify the expected output against SPEC.md, and run `pnpm test` before submitting a PR. See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.