Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tests/unit/typst-gather.test.ts
12919 views
1
/*
2
* typst-gather.test.ts
3
*
4
* Unit tests for typst-gather config generation.
5
*
6
* Copyright (C) 2025 Posit Software, PBC
7
*/
8
9
import { unitTest } from "../test.ts";
10
import { assert, assertEquals } from "testing/asserts";
11
import {
12
AnalyzeResult,
13
generateConfigFromAnalysis,
14
} from "../../src/command/call/typst-gather/cmd.ts";
15
16
// Helper to check a line exists in generated config
17
function assertContains(config: string, expected: string, msg?: string) {
18
assert(config.includes(expected), msg || `Expected config to contain: ${expected}\nGot:\n${config}`);
19
}
20
21
function assertNotContains(config: string, unexpected: string, msg?: string) {
22
assert(!config.includes(unexpected), msg || `Expected config NOT to contain: ${unexpected}\nGot:\n${config}`);
23
}
24
25
// --- Unit tests for generateConfigFromAnalysis ---
26
27
unitTest(
28
"generateConfigFromAnalysis - empty result produces valid config",
29
async () => {
30
const result: AnalyzeResult = { imports: [], files: [] };
31
const config = generateConfigFromAnalysis(result);
32
assertContains(config, 'destination = "typst/packages"');
33
assertContains(config, '# discover = "template.typ"');
34
assertContains(config, '# cetz = "0.4.1"');
35
assertContains(config, "# [local]");
36
},
37
);
38
39
unitTest(
40
"generateConfigFromAnalysis - preview only imports",
41
async () => {
42
const result: AnalyzeResult = {
43
imports: [
44
{ namespace: "preview", name: "cetz", version: "0.4.1", source: "template.typ", direct: true },
45
{ namespace: "preview", name: "fletcher", version: "0.5.0", source: "template.typ", direct: true },
46
],
47
files: ["template.typ"],
48
};
49
const config = generateConfigFromAnalysis(result);
50
assertContains(config, '# cetz = "0.4.1"');
51
assertContains(config, '# fletcher = "0.5.0"');
52
assertContains(config, "# [local]");
53
// Should NOT have an uncommented [local] section
54
assertNotContains(config, "\n[local]");
55
},
56
);
57
58
unitTest(
59
"generateConfigFromAnalysis - local only imports",
60
async () => {
61
const result: AnalyzeResult = {
62
imports: [
63
{ namespace: "local", name: "my-pkg", version: "1.0.0", source: "template.typ", direct: true },
64
],
65
files: ["template.typ"],
66
};
67
const config = generateConfigFromAnalysis(result);
68
assertContains(config, "[local]");
69
assertContains(config, 'my-pkg = "/path/to/my-pkg"');
70
assertContains(config, "@local/my-pkg:1.0.0 (in template.typ)");
71
// Preview section should have placeholder example
72
assertContains(config, '# cetz = "0.4.1"');
73
},
74
);
75
76
unitTest(
77
"generateConfigFromAnalysis - mixed preview and local imports",
78
async () => {
79
const result: AnalyzeResult = {
80
imports: [
81
{ namespace: "preview", name: "cetz", version: "0.4.1", source: "template.typ", direct: true },
82
{ namespace: "local", name: "my-pkg", version: "1.0.0", source: "template.typ", direct: true },
83
],
84
files: ["template.typ"],
85
};
86
const config = generateConfigFromAnalysis(result);
87
assertContains(config, '# cetz = "0.4.1"');
88
assertContains(config, "[local]");
89
assertContains(config, 'my-pkg = "/path/to/my-pkg"');
90
},
91
);
92
93
unitTest(
94
"generateConfigFromAnalysis - transitive preview from local shows source",
95
async () => {
96
const result: AnalyzeResult = {
97
imports: [
98
{ namespace: "preview", name: "cetz", version: "0.4.1", source: "template.typ", direct: true },
99
{ namespace: "preview", name: "oxifmt", version: "0.2.1", source: "@local/my-pkg", direct: false },
100
{ namespace: "local", name: "my-pkg", version: "1.0.0", source: "template.typ", direct: true },
101
],
102
files: ["template.typ"],
103
};
104
const config = generateConfigFromAnalysis(result);
105
assertContains(config, '# cetz = "0.4.1"');
106
assertContains(config, '# oxifmt = "0.2.1" # via @local/my-pkg');
107
},
108
);
109
110
unitTest(
111
"generateConfigFromAnalysis - deduplication of preview imports",
112
async () => {
113
const result: AnalyzeResult = {
114
imports: [
115
{ namespace: "preview", name: "cetz", version: "0.4.1", source: "template.typ", direct: true },
116
{ namespace: "preview", name: "cetz", version: "0.4.1", source: "other.typ", direct: true },
117
],
118
files: ["template.typ", "other.typ"],
119
};
120
const config = generateConfigFromAnalysis(result);
121
// Should only appear once
122
const matches = config.match(/# cetz = "0\.4\.1"/g);
123
assertEquals(matches?.length, 1, "Expected cetz to appear exactly once");
124
},
125
);
126
127
unitTest(
128
"generateConfigFromAnalysis - with rootdir",
129
async () => {
130
const result: AnalyzeResult = { imports: [], files: ["template.typ"] };
131
const config = generateConfigFromAnalysis(result, "_extensions/my-ext");
132
assertContains(config, 'rootdir = "_extensions/my-ext"');
133
assertContains(config, 'destination = "typst/packages"');
134
},
135
);
136
137
unitTest(
138
"generateConfigFromAnalysis - no rootdir when not provided",
139
async () => {
140
const result: AnalyzeResult = { imports: [], files: ["template.typ"] };
141
const config = generateConfigFromAnalysis(result);
142
assertNotContains(config, "rootdir");
143
},
144
);
145
146
unitTest(
147
"generateConfigFromAnalysis - discover single file is string",
148
async () => {
149
const result: AnalyzeResult = { imports: [], files: ["template.typ"] };
150
const config = generateConfigFromAnalysis(result);
151
assertContains(config, 'discover = "template.typ"');
152
assertNotContains(config, "discover = [");
153
},
154
);
155
156
unitTest(
157
"generateConfigFromAnalysis - discover multiple files is array",
158
async () => {
159
const result: AnalyzeResult = {
160
imports: [],
161
files: ["template.typ", "typst-show.typ"],
162
};
163
const config = generateConfigFromAnalysis(result);
164
assertContains(config, 'discover = ["template.typ", "typst-show.typ"]');
165
},
166
);
167
168
unitTest(
169
"generateConfigFromAnalysis - windows backslashes converted to forward slashes",
170
async () => {
171
const result: AnalyzeResult = {
172
imports: [],
173
files: ["subdir\\template.typ"],
174
};
175
const config = generateConfigFromAnalysis(result, "ext\\my-ext");
176
assertContains(config, 'rootdir = "ext/my-ext"');
177
assertContains(config, 'discover = "subdir/template.typ"');
178
assertNotContains(config, "\\");
179
},
180
);
181
182
unitTest(
183
"generateConfigFromAnalysis - local imports only include direct",
184
async () => {
185
const result: AnalyzeResult = {
186
imports: [
187
{ namespace: "local", name: "my-pkg", version: "1.0.0", source: "template.typ", direct: true },
188
{ namespace: "local", name: "other-pkg", version: "2.0.0", source: "@local/my-pkg", direct: false },
189
],
190
files: ["template.typ"],
191
};
192
const config = generateConfigFromAnalysis(result);
193
assertContains(config, "[local]");
194
assertContains(config, 'my-pkg = "/path/to/my-pkg"');
195
// Transitive @local should NOT appear in the [local] section
196
assertNotContains(config, 'other-pkg = "/path/to/other-pkg"');
197
},
198
);
199
200
// --- Large import list ---
201
202
unitTest(
203
"generateConfigFromAnalysis - handles large import list without truncation",
204
async () => {
205
const imports = [];
206
for (let i = 0; i < 60; i++) {
207
imports.push({
208
namespace: "preview",
209
name: `package-${i}`,
210
version: `${i}.0.0`,
211
source: "template.typ",
212
direct: true,
213
});
214
}
215
const result: AnalyzeResult = { imports, files: ["template.typ"] };
216
const config = generateConfigFromAnalysis(result);
217
// All 60 packages should appear
218
for (let i = 0; i < 60; i++) {
219
assertContains(config, `# package-${i} = "${i}.0.0"`);
220
}
221
},
222
);
223
224
// --- Paths with spaces ---
225
226
unitTest(
227
"generateConfigFromAnalysis - paths with spaces are quoted correctly",
228
async () => {
229
const result: AnalyzeResult = {
230
imports: [],
231
files: ["my project/my template.typ"],
232
};
233
const config = generateConfigFromAnalysis(result, "my ext/sub dir");
234
assertContains(config, 'rootdir = "my ext/sub dir"');
235
assertContains(config, 'discover = "my project/my template.typ"');
236
},
237
);
238
239
// --- Paths with special TOML characters ---
240
241
unitTest(
242
"generateConfigFromAnalysis - paths with special chars in discover",
243
async () => {
244
const result: AnalyzeResult = {
245
imports: [],
246
files: ['dir "quoted"/template.typ'],
247
};
248
// The current implementation doesn't escape quotes inside TOML strings.
249
// This test documents the current behavior — paths with quotes in them
250
// would produce invalid TOML. This is acceptable since filesystem paths
251
// with literal quotes are extremely rare.
252
const config = generateConfigFromAnalysis(result);
253
assertContains(config, "discover");
254
assertContains(config, "template.typ");
255
},
256
);
257
258