// SPDX-FileCopyrightText: 2026 Kerstin Humm // // SPDX-License-Identifier: AGPL-3.0-or-later import { describe, expect, test } from "vitest"; import { AnnotatedJson, Annotation, isAnnotatedArray, isAnnotatedObject, isAnnotation, isJsonObject, rules, } from "./index.js"; function expectSatisfies( value: unknown, predicate: (v: unknown) => v is T, message?: string, ): asserts value is T { const result = predicate(value); expect( result, message ?? `Value '${ JSON.stringify(value) }' did not satisfy predicate '${predicate.name}'`, ).toBe(true); } // Recursively compare an AnnotatedJson with its test stub function compareAnnotated( annotated: AnnotatedJson, testStub: AnnotatedJson, ) { // Normalise Annotation to Annotation[] if (isAnnotation(annotated)) { annotated = [annotated]; } // Normalise string to string[] if (typeof testStub === "string") { testStub = [testStub]; } if (isAnnotatedObject(testStub)) { // For the type checker… expectSatisfies( testStub.annotations, (a) => Array.isArray(a) && a.every((s) => typeof s === "string"), ); expectSatisfies( annotated, isAnnotatedObject, ); for (const name in testStub.object) { expect(annotated.object).toHaveProperty(name); compareAnnotated(annotated.annotations, testStub.annotations); compareAnnotated( annotated.object[name]!, testStub.object[name]!, ); } } else if (isAnnotatedArray(testStub)) { expectSatisfies( annotated, isAnnotatedArray, ); expect( annotated.array.length, `${JSON.stringify(annotated.array)} is not the same length as ${ JSON.stringify(testStub.array) }`, ).toBe(testStub.array.length); for (let i = 0, len = testStub.array.length; i < len; i++) { compareAnnotated( annotated.array[i]!, testStub.array[i]!, ); } } else if ( Array.isArray(testStub) && testStub.every((s) => typeof s === "string") && Array.isArray(annotated) && annotated.every((a) => isAnnotation(a)) ) { expect( annotated.length, `${JSON.stringify(annotated)} is not the same length as ${ JSON.stringify(testStub) }`, ).toBe(testStub.length); for (let i = 0, len = testStub.length; i < len; i++) { // The stub is either interpreted as the id or, if non-existent as the kind expect(annotated[i]!.id ?? annotated[i]!.kind).toBe(testStub[i]); } } else { expect.unreachable(); } } rules.forEach(({ name, validate, tests }) => { if (tests.length > 0) { describe(name, () => { for (const { value, result } of tests) { test(`${JSON.stringify(value)} => ${JSON.stringify(result)}`, () => { expect(value).not.toSatisfy(isAnnotation); expect(value).toSatisfy(isJsonObject); expectSatisfies( value, isJsonObject, ); compareAnnotated(validate(value), result); }); } }); } });