Skip to content

Commit 1bf3217

Browse files
committed
Improve test coverage
1 parent dc58c06 commit 1bf3217

File tree

16 files changed

+394
-92
lines changed

16 files changed

+394
-92
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ title: Changelog
77
### Bug Fixes
88

99
- Classes and functions exported with `export { type X }` are no longer missing comments, #2970.
10+
- Setting `locale` to an unknown value will now cause TypeDoc to operate in English instead of a debug locale.
11+
- Array options will now report an error if set to a non-array/non-string value.
1012

1113
## v0.28.6 (2025-06-27)
1214

src/lib/converter/utils/repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export class GitRepository implements Repository {
126126
logger: Logger,
127127
): GitRepository | undefined {
128128
gitRevision ||= git("-C", path, "rev-parse", "HEAD").stdout.trim();
129-
if (!gitRevision) return; // Will only happen in a repo with no commits.
129+
if (gitRevision == "HEAD") return; // Will only happen in a repo with no commits.
130130

131131
let urlTemplate: string | undefined;
132132
if (sourceLinkTemplate) {

src/lib/internationalization/internationalization.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export function loadTranslations(lang: string): Record<string, string> {
6666
try {
6767
return req(`./locales/${lang}.cjs`);
6868
} catch {
69-
return {};
69+
return loadTranslations("en");
7070
}
7171
}
7272

src/lib/internationalization/locales/en.cts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ export = {
411411
"The useHostedBaseUrlForAbsoluteLinks option requires that hostedBaseUrl be set",
412412
favicon_must_have_one_of_the_following_extensions_0: "Favicon must have one of the following extensions: {0}",
413413
option_0_must_be_an_object: "The '{0}' option must be a non-array object",
414+
option_0_must_be_an_array_of_string: "The '{0}' option must be set to an array of strings",
414415
option_0_must_be_a_function: "The '{0}' option must be a function",
415416
option_0_must_be_object_with_urls: `{0} must be an object with string labels as keys and URL values`,
416417
visibility_filters_only_include_0: `visibilityFilters can only include the following non-@ keys: {0}`,

src/lib/models/Comment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class CommentTag {
116116
similarTo(other: CommentTag) {
117117
return (
118118
this.tag === other.tag &&
119-
this.name === other.tag &&
119+
this.name === other.name &&
120120
Comment.combineDisplayParts(this.content) ===
121121
Comment.combineDisplayParts(other.content)
122122
);

src/lib/utils/options/declaration.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,16 @@ export type DeclarationOptionToOptionType<T extends DeclarationOption> = T exten
685685
T extends FlagsDeclarationOption<infer U> ? U :
686686
ParameterTypeToOptionTypeMap[Exclude<T["type"], undefined>];
687687

688+
function toStringArray(value: unknown, option: DeclarationOption) {
689+
if (Array.isArray(value) && value.every(v => typeof v === "string")) {
690+
return value;
691+
} else if (typeof value === "string") {
692+
return [value];
693+
}
694+
695+
throw new Error(i18n.option_0_must_be_an_array_of_string(option.name));
696+
}
697+
688698
const converters: {
689699
[K in ParameterType]: (
690700
value: unknown,
@@ -737,33 +747,18 @@ const converters: {
737747
return !!value;
738748
},
739749
[ParameterType.Array](value, option) {
740-
let strArrValue: string[] = [];
741-
if (Array.isArray(value)) {
742-
strArrValue = value.map(String);
743-
} else if (typeof value === "string") {
744-
strArrValue = [value];
745-
}
750+
const strArrValue = toStringArray(value, option);
746751
option.validate?.(strArrValue);
747752
return strArrValue;
748753
},
749754
[ParameterType.PathArray](value, option, configPath) {
750-
let strArrValue: string[] = [];
751-
if (Array.isArray(value)) {
752-
strArrValue = value.map(String);
753-
} else if (typeof value === "string") {
754-
strArrValue = [value];
755-
}
755+
const strArrValue = toStringArray(value, option);
756756
const normalized = strArrValue.map((path) => normalizePath(resolve(configPath, path)));
757757
option.validate?.(normalized);
758758
return normalized;
759759
},
760760
[ParameterType.ModuleArray](value, option, configPath) {
761-
let strArrValue: string[] = [];
762-
if (Array.isArray(value)) {
763-
strArrValue = value.map(String);
764-
} else if (typeof value === "string") {
765-
strArrValue = [value];
766-
}
761+
const strArrValue = toStringArray(value, option);
767762
const resolved = resolveModulePaths(strArrValue, configPath);
768763
option.validate?.(resolved);
769764
return resolved;
@@ -782,7 +777,8 @@ const converters: {
782777

783778
return createGlobString(configPath, s);
784779
};
785-
const globs = Array.isArray(value) ? value.map(toGlobString) : [toGlobString(value)];
780+
const strArrValue = toStringArray(value, option);
781+
const globs = strArrValue.map(toGlobString);
786782
option.validate?.(globs);
787783
return globs;
788784
},

src/lib/validation/links.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ export function validateLinks(
4545
for (const id in project.reflections) {
4646
checkReflection(project.reflections[id], logger);
4747
}
48-
49-
if (!(project.id in project.reflections)) {
50-
checkReflection(project, logger);
51-
}
5248
}
5349

5450
function checkReflection(reflection: Reflection, logger: Logger) {

src/test/Repository.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,51 @@ describe("RepositoryManager - git enabled", () => {
314314
equal(repo.getURL(ign, 1), undefined);
315315
});
316316
});
317+
318+
describe("RepositoryManager - edge cases", () => {
319+
let fix: Project;
320+
const logger = new TestLogger();
321+
const manager = new RepositoryManager(
322+
"",
323+
"",
324+
"remote",
325+
"",
326+
false, // disable git
327+
logger,
328+
);
329+
330+
beforeEach(() => {
331+
fix = tempdirProject();
332+
});
333+
334+
afterEach(() => {
335+
fix.rm();
336+
logger.expectNoOtherMessages();
337+
});
338+
339+
it("Handles repositories without any commit", () => {
340+
fix.write();
341+
git(fix.cwd, "init", "-b", "test");
342+
equal(manager.getRepository(fix.cwd + "/test.txt"), undefined);
343+
});
344+
345+
it("Handles a remote which does not exist", () => {
346+
fix.addFile("test.txt");
347+
fix.write();
348+
git(fix.cwd, "init", "-b", "test");
349+
git(fix.cwd, "add", ".");
350+
git(fix.cwd, "commit", "-m", "test", "--no-gpg-sign");
351+
equal(manager.getRepository(fix.cwd + "/test.txt"), undefined);
352+
logger.expectMessage('warn: The provided git remote "remote" was not valid. Source links will be broken');
353+
});
354+
355+
it("Handles a remote which does not match a known domain", () => {
356+
fix.addFile("test.txt");
357+
fix.write();
358+
git(fix.cwd, "init", "-b", "test");
359+
git(fix.cwd, "add", ".");
360+
git(fix.cwd, "commit", "-m", "test", "--no-gpg-sign");
361+
git(fix.cwd, "remote", "add", "remote", "https://example.com/fake.git");
362+
equal(manager.getRepository(fix.cwd + "/test.txt"), undefined);
363+
});
364+
});

src/test/converter/comment/comment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Module doc comment with document.
2+
* Module doc comment with document and link to [self](./comment.ts) and {@link https://example.com}
33
*
44
* @document document.md
55
* @packageDocumentation

src/test/converter/comment/specs.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,22 @@
1616
"summary": [
1717
{
1818
"kind": "text",
19-
"text": "Module doc comment with document."
19+
"text": "Module doc comment with document and link to [self]("
20+
},
21+
{
22+
"kind": "relative-link",
23+
"text": "./comment.ts",
24+
"target": 1
25+
},
26+
{
27+
"kind": "text",
28+
"text": ") and "
29+
},
30+
{
31+
"kind": "inline-tag",
32+
"tag": "@link",
33+
"text": "https://example.com",
34+
"target": "https://example.com"
2035
}
2136
]
2237
},
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* @module
3+
* @mergeModuleWith notUsed
4+
*/
5+
export const test = 1;

src/test/internationalization.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ allValidTranslationKeys.push(
1818
allValidTranslationKeys.push(...inlineTags.map((s) => "tag_" + s.substring(1)));
1919

2020
describe("Internationalization", () => {
21-
const inter = new Internationalization();
21+
let inter: Internationalization;
22+
beforeEach(() => {
23+
inter = new Internationalization();
24+
});
2225
afterEach(() => inter.setLocale("en"));
2326

2427
it("Supports getting the list of supported languages", () => {
@@ -43,6 +46,31 @@ describe("Internationalization", () => {
4346
inter.setLocale("zh");
4447
equal(i18n.loaded_plugin_0("X"), "已加载插件 X");
4548
});
49+
50+
it("Handles locales which do not exist", () => {
51+
equal(i18n.loaded_plugin_0("X"), "Loaded plugin X");
52+
inter.setLocale("fakeLocale");
53+
equal(i18n.loaded_plugin_0("X"), "Loaded plugin X");
54+
});
55+
56+
it("Supports adding translations for loaded locale", () => {
57+
inter.addTranslations("en", { testTranslation: "Test translation" });
58+
// @ts-expect-error testTranslation isn't a defined translation
59+
equal(i18n.testTranslation(), "Test translation");
60+
});
61+
62+
it("Supports adding translations for not-loaded locale", () => {
63+
inter.addTranslations("fake", { testTranslation: "Fake translation" });
64+
inter.setLocale("fake");
65+
// @ts-expect-error testTranslation isn't a defined translation
66+
equal(i18n.testTranslation(), "Fake translation");
67+
});
68+
69+
it("Considers translations which have only been added by plugins to be real", () => {
70+
inter.addTranslations("fake", { testTranslation: "Fake translation" });
71+
const supported = inter.getSupportedLanguages();
72+
ok(supported.includes("fake"));
73+
});
4674
});
4775

4876
describe("Locales", () => {

src/test/models/comment.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
import { deepStrictEqual as equal } from "assert";
22
import { Comment, type CommentDisplayPart, CommentTag } from "../../index.js";
33

4+
describe("Comment.similarTo", () => {
5+
it("Checks for similar summaries", () => {
6+
const a = new Comment([{ kind: "text", text: "a" }]);
7+
const b = new Comment([{ kind: "text", text: "a" }]);
8+
const c = new Comment([{ kind: "text", text: "c" }]);
9+
10+
equal(a.similarTo(b), true);
11+
equal(a.similarTo(c), false);
12+
});
13+
14+
it("Ignores modifier tags", () => {
15+
const a = new Comment([{ kind: "text", text: "a" }]);
16+
a.modifierTags.add("@test");
17+
const b = new Comment([{ kind: "text", text: "a" }]);
18+
19+
equal(a.similarTo(b), true);
20+
});
21+
22+
it("Checks block tags", () => {
23+
const a = new Comment([], [new CommentTag("@test", [{ kind: "text", text: "a" }])]);
24+
const b = new Comment([], [new CommentTag("@test", [{ kind: "text", text: "a" }])]);
25+
const c = new Comment([], [new CommentTag("@test", [{ kind: "text", text: "c" }])]);
26+
const d = new Comment([], [new CommentTag("@test2", [{ kind: "text", text: "c" }])]);
27+
const e = new Comment();
28+
29+
equal(a.similarTo(b), true);
30+
equal(a.similarTo(c), false);
31+
equal(a.similarTo(d), false);
32+
equal(a.similarTo(e), false);
33+
});
34+
});
35+
436
describe("Comment.combineDisplayParts", () => {
537
it("Handles text and code", () => {
638
const parts: CommentDisplayPart[] = [

src/test/utils/options/declaration.test.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,10 @@ describe("Options - conversions", () => {
200200
convert(["12,3"], optionWithType(ParameterType.Array), ""),
201201
["12,3"],
202202
);
203-
equal(convert(true, optionWithType(ParameterType.Array), ""), []);
203+
throws(
204+
() => convert(true, optionWithType(ParameterType.Array), ""),
205+
new Error("The 'test' option must be set to an array of strings"),
206+
);
204207

205208
equal(
206209
convert("/,a", optionWithType(ParameterType.PathArray), ""),
@@ -214,9 +217,9 @@ describe("Options - conversions", () => {
214217
),
215218
[normalizePath(resolve("/foo"))],
216219
);
217-
equal(
218-
convert(true, optionWithType(ParameterType.PathArray), ""),
219-
[],
220+
throws(
221+
() => convert(true, optionWithType(ParameterType.PathArray), ""),
222+
new Error("The 'test' option must be set to an array of strings"),
220223
);
221224

222225
equal(
@@ -231,9 +234,14 @@ describe("Options - conversions", () => {
231234
),
232235
["a,b"],
233236
);
234-
equal(
235-
convert(true, optionWithType(ParameterType.ModuleArray), ""),
236-
[],
237+
throws(
238+
() => convert(true, optionWithType(ParameterType.ModuleArray), ""),
239+
new Error("The 'test' option must be set to an array of strings"),
240+
);
241+
242+
throws(
243+
() => convert(true, optionWithType(ParameterType.GlobArray), ""),
244+
new Error("The 'test' option must be set to an array of strings"),
237245
);
238246
});
239247

0 commit comments

Comments
 (0)