Skip to content

Commit 1827757

Browse files
committed
Refactor tests and parsing
1 parent 3dd5603 commit 1827757

File tree

4 files changed

+179
-151
lines changed

4 files changed

+179
-151
lines changed

src/customizations.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe } from 'mocha'
2+
import { Customizations, Settings, Extension, parseCustomizations } from './customizations'
3+
import assert from 'node:assert'
4+
5+
interface TestCase<T> {
6+
desc: string,
7+
configFile: string,
8+
expect: T | string,
9+
}
10+
11+
function runTests<T, S = keyof Customizations>(key: S, testCases: TestCase<T>[]) {
12+
for (const testCase of testCases) {
13+
it(testCase.desc, () => {
14+
const actual = parseCustomizations(testCase.configFile);
15+
if (actual instanceof Error) {
16+
assert.deepEqual(actual.message, testCase.expect);
17+
} else {
18+
const full: any = {
19+
settings: {},
20+
extensions: [],
21+
};
22+
full[key] = testCase.expect;
23+
assert.deepEqual(actual, full);
24+
}
25+
})
26+
}
27+
}
28+
29+
describe("'customizations' format", () => {
30+
31+
describe("*.settings", () => {
32+
const cases: TestCase<Settings>[] = [
33+
{
34+
desc: "empty",
35+
configFile: "{}",
36+
expect: {},
37+
},
38+
{
39+
desc: "invalid jsonc format",
40+
configFile: "}",
41+
expect: "devcontainer.json must be a valid jsonc file",
42+
},
43+
{
44+
desc: "base types are supported",
45+
configFile: `
46+
{
47+
"customizations": {
48+
"EDITOR": {
49+
"settings": {
50+
"number": 1.1,
51+
"boolean": true,
52+
"string": "devcontainer",
53+
"object": {
54+
"number": 1,
55+
}
56+
}
57+
}
58+
}
59+
}
60+
`,
61+
expect: {
62+
number: 1.1,
63+
boolean: true,
64+
string: "devcontainer",
65+
object: {
66+
number: 1
67+
}
68+
}
69+
}
70+
];
71+
runTests("settings", cases.map(tc => ({
72+
...tc,
73+
desc: `(vscode) ${tc.desc}`,
74+
configFile: tc.configFile.replaceAll("EDITOR", "vscode"),
75+
})));
76+
// runTests(cases.map(tc => ({
77+
// ...tc,
78+
// desc: `(codium) ${tc.desc}`,
79+
// configFile: tc.configFile.replaceAll("EDITOR", "vscodium"),
80+
// })));
81+
});
82+
83+
describe("vscode.extensions", () => {
84+
runTests<Extension[]>("extensions", [
85+
{
86+
desc: "empty",
87+
configFile: "{}",
88+
expect: []
89+
},
90+
{
91+
desc: "valid list",
92+
configFile: `
93+
{
94+
"customizations": {
95+
"vscode": {
96+
"extensions": ["golang.Go"]
97+
}
98+
}
99+
}
100+
`,
101+
expect: [{
102+
id: "golang.Go"
103+
}]
104+
}
105+
])
106+
})
107+
})
108+

src/customizations.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { z } from "zod";
2+
import * as jsonc from "jsonc-parser";
3+
4+
// Type definitions according to devcontainer specification
5+
6+
const SpecSettings = z.object({}).catchall(z.any()).default({});
7+
type SpecSettings = z.infer<typeof SpecSettings>;
8+
9+
const VsCodeExtensions = z.array(z.string()).default([]);
10+
type VsCodeExtensions = z.infer<typeof VsCodeExtensions>;
11+
12+
// const CodiumRegistry = z.object({
13+
// url: z.string().url(),
14+
// headers: z.object({}).catchall(z.string()),
15+
// });
16+
// type CodiumRegistry = z.infer<typeof CodiumRegistry>;
17+
18+
const SpecCustomizations = z.object({
19+
vscode: z.optional(
20+
z.object({
21+
settings: SpecSettings,
22+
extensions: VsCodeExtensions,
23+
}),
24+
),
25+
});
26+
type SpecCustomizations = z.infer<typeof SpecCustomizations>;
27+
28+
const DevContainerConfig = z.object({
29+
customizations: SpecCustomizations.default({}),
30+
});
31+
type DevContainerConfig = z.infer<typeof DevContainerConfig>;
32+
33+
// Internal type definitions
34+
35+
export type Settings = SpecSettings;
36+
37+
export type Extension = {
38+
id: string;
39+
registry?: string;
40+
version?: string;
41+
};
42+
43+
export type Customizations = {
44+
settings: Settings;
45+
// registries: Record<string, Registry>;
46+
extensions: Extension[];
47+
};
48+
49+
const emptyCustomuzations = { settings: {}, extensions: [] };
50+
51+
export function parseCustomizations(configContent: string): Customizations | Error {
52+
let errors: jsonc.ParseError[] = [];
53+
const parsed = jsonc.parse(configContent, errors, { allowTrailingComma: true });
54+
if (errors.length > 0) {
55+
return new Error("devcontainer.json must be a valid jsonc file");
56+
}
57+
const validated = DevContainerConfig.safeParse(parsed);
58+
if (validated.error) {
59+
return validated.error;
60+
}
61+
62+
const vscode = validated.data.customizations.vscode;
63+
if (vscode) {
64+
return {
65+
settings: vscode.settings,
66+
extensions: vscode.extensions.map(id => ({ id: id })),
67+
};
68+
}
69+
70+
return emptyCustomuzations;
71+
}

src/spec.test.ts

Lines changed: 0 additions & 130 deletions
This file was deleted.

src/spec_v2.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)