Skip to content

Commit ccf2f3a

Browse files
committed
Migrate to official annotation tests
1 parent 243585b commit ccf2f3a

13 files changed

+107
-1670
lines changed

annotations/index.spec.ts

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import fs from "node:fs";
2-
import path from "node:path";
3-
import { fileURLToPath } from "node:url";
4-
import { describe, it, expect, beforeEach, beforeAll, afterAll } from "vitest";
2+
import { describe, test, expect, beforeEach, beforeAll, afterAll } from "vitest";
3+
import { isCompatible } from "./test-utils.js";
54
import { toAbsoluteIri } from "@hyperjump/uri";
65
import { annotate } from "./index.js";
76
import { registerSchema, unregisterSchema } from "../lib/index.js";
87
import "../stable/index.js";
98
import "../draft-2020-12/index.js";
9+
import "../draft-2019-09/index.js";
1010
import "../draft-07/index.js";
1111
import "../draft-06/index.js";
1212
import "../draft-04/index.js";
@@ -19,12 +19,18 @@ import type { Json } from "@hyperjump/json-pointer";
1919

2020

2121
type Suite = {
22-
title: string;
22+
description: string;
23+
suite: TestCase[];
24+
};
25+
26+
type TestCase = {
27+
description: string;
28+
compatibility: string;
2329
schema: SchemaObject;
24-
subjects: Subject[];
30+
tests: Test[];
2531
};
2632

27-
type Subject = {
33+
type Test = {
2834
instance: Json;
2935
assertions: Assertion[];
3036
};
@@ -35,57 +41,68 @@ type Assertion = {
3541
expected: unknown[];
3642
};
3743

38-
const __filename = fileURLToPath(import.meta.url);
39-
const __dirname = path.dirname(__filename);
40-
41-
const dialectId = "https://json-schema.org/validation";
4244
const host = "https://annotations.json-schema.hyperjump.io";
4345

44-
const testSuiteFilePath = `${__dirname}/tests`;
46+
const testSuiteFilePath = "./node_modules/json-schema-test-suite/annotations/tests";
4547

46-
describe("Annotations", () => {
47-
fs.readdirSync(testSuiteFilePath, { withFileTypes: true })
48-
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
49-
.forEach((entry) => {
50-
const file = `${testSuiteFilePath}/${entry.name}`;
51-
const suites = JSON.parse(fs.readFileSync(file, "utf8")) as Suite[];
48+
const testRunner = (version: number, dialect: string) => {
49+
describe(dialect, () => {
50+
fs.readdirSync(testSuiteFilePath, { withFileTypes: true })
51+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
52+
.forEach((entry) => {
53+
const file = `${testSuiteFilePath}/${entry.name}`;
54+
const suite = JSON.parse(fs.readFileSync(file, "utf8")) as Suite;
5255

53-
suites.forEach((suite) => {
54-
describe(suite.title + "\n" + JSON.stringify(suite.schema, null, " "), () => {
55-
let annotator: Annotator;
56-
let id: string;
56+
for (const testCase of suite.suite) {
57+
if (!isCompatible(testCase.compatibility, version)) {
58+
continue;
59+
}
5760

58-
beforeAll(async () => {
59-
id = `${host}/${encodeURIComponent(suite.title)}`;
60-
registerSchema(suite.schema, id, dialectId);
61+
describe(testCase.description + "\n" + JSON.stringify(testCase.schema, null, " "), () => {
62+
let annotator: Annotator;
63+
let id: string;
6164

62-
annotator = await annotate(id);
63-
});
65+
beforeAll(async () => {
66+
id = `${host}/${encodeURIComponent(testCase.description)}`;
67+
registerSchema(testCase.schema, id, dialect);
6468

65-
afterAll(() => {
66-
unregisterSchema(id);
67-
});
69+
annotator = await annotate(id);
70+
});
6871

69-
suite.subjects.forEach((subject) => {
70-
describe("Instance: " + JSON.stringify(subject.instance), () => {
71-
let instance: JsonNode;
72+
afterAll(() => {
73+
unregisterSchema(id);
74+
});
7275

73-
beforeEach(() => {
74-
// TODO: What's wrong with the type?
75-
instance = annotator(subject.instance); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
76-
});
76+
for (const subject of testCase.tests) {
77+
describe("Instance: " + JSON.stringify(subject.instance), () => {
78+
let instance: JsonNode;
7779

78-
subject.assertions.forEach((assertion) => {
79-
it(`${assertion.keyword} annotations at '${assertion.location}' should be ${JSON.stringify(assertion.expected)}`, () => {
80-
const dialect: string | undefined = suite.schema.$schema ? toAbsoluteIri(suite.schema.$schema as string) : undefined;
81-
const subject = Instance.get(assertion.location, instance);
82-
const annotations = subject ? Instance.annotation(subject, assertion.keyword, dialect) : [];
83-
expect(annotations).to.eql(assertion.expected);
80+
beforeEach(() => {
81+
// TODO: What's wrong with the type?
82+
instance = annotator(subject.instance); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
8483
});
84+
85+
for (const assertion of subject.assertions) {
86+
test(`${assertion.keyword} annotations at '${assertion.location}' should be ${JSON.stringify(assertion.expected)}`, () => {
87+
const dialect: string | undefined = testCase.schema.$schema ? toAbsoluteIri(testCase.schema.$schema as string) : undefined;
88+
const subject = Instance.get(`#${assertion.location}`, instance);
89+
const annotations = subject ? Instance.annotation(subject, assertion.keyword, dialect) : [];
90+
expect(annotations).to.eql(Object.values(assertion.expected));
91+
});
92+
}
8593
});
86-
});
94+
}
8795
});
88-
});
96+
}
8997
});
90-
});
98+
});
99+
};
100+
101+
describe("annotations", () => {
102+
testRunner(9999, "https://json-schema.org/validation");
103+
testRunner(2020, "https://json-schema.org/draft/2020-12/schema");
104+
testRunner(2019, "https://json-schema.org/draft/2019-09/schema");
105+
testRunner(7, "http://json-schema.org/draft-07/schema");
106+
testRunner(6, "http://json-schema.org/draft-06/schema");
107+
testRunner(4, "http://json-schema.org/draft-04/schema");
91108
});

annotations/test-utils.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const isCompatible: (compatibility: string | undefined, versionUnderTest: number) => boolean;

annotations/test-utils.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export const isCompatible = (compatibility, versionUnderTest) => {
2+
if (compatibility === undefined) {
3+
return true;
4+
}
5+
6+
const constraints = compatibility.split(",");
7+
for (const constraint of constraints) {
8+
const matches = /(?<operator><=|>=|=)?(?<version>\d+)/.exec(constraint);
9+
if (!matches) {
10+
throw Error(`Invalid compatibility string: ${compatibility}`);
11+
}
12+
13+
const operator = matches[1] ?? ">=";
14+
const version = parseInt(matches[2], 10);
15+
16+
switch (operator) {
17+
case ">=":
18+
if (versionUnderTest < version) {
19+
return false;
20+
}
21+
break;
22+
case "<=":
23+
if (versionUnderTest > version) {
24+
return false;
25+
}
26+
break;
27+
case "=":
28+
if (versionUnderTest !== version) {
29+
return false;
30+
}
31+
break;
32+
default:
33+
throw Error(`Unsupported contraint operator: ${operator}`);
34+
}
35+
}
36+
37+
return true;
38+
};

0 commit comments

Comments
 (0)