Skip to content

Instantly share code, notes, and snippets.

@5cover
Created March 20, 2026 16:10
Show Gist options
  • Select an option

  • Save 5cover/fd4e136a16b33c82a18a55b0e11ce122 to your computer and use it in GitHub Desktop.

Select an option

Save 5cover/fd4e136a16b33c82a18a55b0e11ce122 to your computer and use it in GitHub Desktop.
format testing.ts
import { expect, describe, it } from 'bun:test';
import jsonVariantsHandler, { JSON_VARIANTS, variantToFormat } from '../../src/handlers/jsonVariants.js';
import CommonFormats from '../../src/CommonFormats.ts';
import { FileFormat } from '../../src/FormatHandler.js';
describe('jsonVariants', () => {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const jsonFormat = CommonFormats.JSON.supported('json', true, true, true);
const basenames = {
vals: ['', '.', '..', 'file', 'file.dots'] as const,
at(i: number) {
return this.vals[i % this.vals.length];
},
};
type Variant = keyof typeof JSON_VARIANTS;
type TestContext = { handler: jsonVariantsHandler; variantFormat: FileFormat; i: number };
type Transform = (ctx: TestContext, cases: readonly [string, PositiveCase][]) => Promise<void>;
// have a case for each identifiable distinct statement on what is supported
type PositiveCase = {
content: string;
value: unknown;
// the canonical representation of content. defaults to content if unspecified.
// e.g. specify with the minified representation of content contains formatting
canonical?: string;
};
type NegativeCase = { content: string; error: ErrorConstructor };
interface TestCases {
positive: Record<string, PositiveCase>;
negative: Record<string, NegativeCase>;
}
const testCases = {
json5: {
positive: {
'basic object 1': {
content: `{
// comment
unquoted: 'value',
trailing: [1, 2,],
}`,
value: {
unquoted: 'value',
trailing: [1, 2],
},
canonical: "{unquoted:'value',trailing:[1,2]}",
},
'basic object 2': {
content: '{"enabled":true,"nested":{"value":3}}',
value: {
enabled: true,
nested: { value: 3 },
},
canonical: '{enabled:true,nested:{value:3}}',
},
},
negative: {},
},
} as const satisfies Record<Variant, TestCases>;
const transforms = {
parses: [parse, stringify],
roundtrips: [stringify, parse, stringify],
minifies: [parse, stringify, parse],
idempotence: [parse, parse, parse, stringify, stringify, stringify],
} as const satisfies Record<string, Transform[]>;
Object.entries(testCases).forEach(([variantKey, testCase], i) => {
const variant = JSON_VARIANTS[variantKey as Variant];
const variantFormat = variantToFormat([variantKey, variant]);
Object.entries(transforms).forEach(([transformKey, transforms], j) =>
describe(`${variantKey}: ${transformKey}`, () => {
testPositive(
{
handler: new jsonVariantsHandler(),
i: i + j,
variantFormat,
},
Object.entries(testCase.positive),
transforms
);
})
);
});
async function testPositive(
ctx: TestContext,
cases: readonly [string, PositiveCase][],
transforms: readonly Transform[]
) {
for (const t of transforms) {
describe(t.name, () => t(ctx, cases));
}
}
// convert Variant -> JSON
async function parse(ctx: TestContext, cases: readonly [string, PositiveCase][]) {
(
await ctx.handler.doConvert(
cases.map(([, { content }], i) => ({
name: `${basenames.at(ctx.i + i)}.json`,
bytes: encoder.encode(content),
})),
ctx.variantFormat,
jsonFormat
)
).forEach((output, i) =>
it(cases[i][0], () => {
expect(output.name).toBe(`${basenames.at(ctx.i + i)}.json`);
const value = JSON.parse(decoder.decode(output.bytes)) as unknown;
expect(value).toEqual(cases[i][1].value);
})
);
}
// convert JSON -> Variant
async function stringify(ctx: TestContext, cases: readonly [string, PositiveCase][]) {
return (
await ctx.handler.doConvert(
cases.map(([, { value }], i) => ({
name: `${basenames.at(ctx.i + i)}.json`,
bytes: encoder.encode(JSON.stringify(value)),
})),
jsonFormat,
ctx.variantFormat
)
).forEach((output, i) =>
it(cases[i][0], () => {
expect(output.name).toBe(`${basenames.at(ctx.i + i)}.${ctx.variantFormat.extension}`);
const content = decoder.decode(output.bytes);
expect(content).toBe(cases[i][1].canonical ?? cases[i][1].content);
})
);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment