Skip to content

Commit

Permalink
Add unit tests for DefaultRouter
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit0 committed Dec 15, 2024
1 parent 12d3901 commit a2bbe04
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 132 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ title: Changelog
- API: Introduced a `Router` which is used for URL creation. `Reflection.url`,
`Reflection.anchor`, and `Reflection.hasOwnDocument` have been removed.

TODO:

- Add option for choosing router

## Unreleased

## v0.27.5 (2024-12-14)
Expand Down
103 changes: 59 additions & 44 deletions src/lib/output/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Application } from "../application.js";
import {
type DeclarationReflection,
ReflectionKind,
TraverseProperty,
type ProjectReflection,
type Reflection,
} from "../models/index.js";
Expand Down Expand Up @@ -93,6 +92,9 @@ export interface Router {
getSlugger(reflection: Reflection): Slugger;
}

/**
* TypeDoc's default router implementation.
*/
export class DefaultRouter implements Router {
// Note: This will always contain lowercased names to avoid issues with
// case-insensitive file systems.
Expand All @@ -109,6 +111,8 @@ export class DefaultRouter implements Router {

constructor(readonly application: Application) {}

extension = "html";

directories = new Map<ReflectionKind, [dir: string, kind: PageKind]>([
[ReflectionKind.Class, ["classes", PageKind.Reflection]],
[ReflectionKind.Interface, ["interfaces", PageKind.Reflection]],
Expand All @@ -131,18 +135,18 @@ export class DefaultRouter implements Router {

if (project.readme?.length) {
pages.push({
url: "index.html",
kind: PageKind.Reflection,
url: `index.${this.extension}`,
kind: PageKind.Index,
model: project,
});
} else {
pages.push({
url: "index.html",
kind: PageKind.Index,
url: `modules.${this.extension}`,
kind: PageKind.Reflection,
model: project,
});
} else {
pages.push({
url: "modules.html",
url: `index.${this.extension}`,
kind: PageKind.Reflection,
model: project,
});
Expand All @@ -152,7 +156,7 @@ export class DefaultRouter implements Router {

if (this.includeHierarchySummary && getHierarchyRoots(project)) {
pages.push({
url: "hierarchy.html",
url: `hierarchy.${this.extension}`,
kind: PageKind.Hierarchy,
model: project,
});
Expand Down Expand Up @@ -183,12 +187,27 @@ export class DefaultRouter implements Router {

relativeUrl(from: Reflection, to: Reflection): string {
let slashes = 0;
const full = this.getFullUrl(from);
for (let i = 0; i < full.length; ++i) {
if (full[i] === "/") ++slashes;
const fromUrl = this.getFullUrl(from);
const toUrl = this.getFullUrl(to);
let equal = true;
let start = 0;

for (let i = 0; i < fromUrl.length; ++i) {
equal = equal && fromUrl[i] === toUrl[i];
if (fromUrl[i] === "/") {
if (equal) {
start = i + 1;
} else {
++slashes;
}
}
}

return "../".repeat(slashes) + this.getFullUrl(to);
if (equal) {
return `#${this.getAnchor(to)}`;
}

return "../".repeat(slashes) + toUrl.substring(start);
}

baseRelativeUrl(from: Reflection, target: string): string {
Expand Down Expand Up @@ -279,39 +298,35 @@ export class DefaultRouter implements Router {
return;
}

let refl: Reflection | undefined = reflection;
const parts = [refl.name];
while (refl.parent && refl.parent !== pageReflection) {
refl = refl.parent;
// Avoid duplicate names for signatures and useless __type in anchors
if (
!refl.kindOf(
ReflectionKind.TypeLiteral |
ReflectionKind.FunctionOrMethod,
)
) {
parts.unshift(refl.name);
if (!reflection.kindOf(ReflectionKind.TypeLiteral)) {
let refl: Reflection | undefined = reflection;
const parts = [refl.name];
while (refl.parent && refl.parent !== pageReflection) {
refl = refl.parent;
// Avoid duplicate names for signatures and useless __type in anchors
if (
!refl.kindOf(
ReflectionKind.TypeLiteral |
ReflectionKind.FunctionOrMethod,
)
) {
parts.unshift(refl.name);
}
}

const anchor = this.getSlugger(pageReflection).slug(
parts.join("."),
);

this.fullUrls.set(
reflection,
this.fullUrls.get(pageReflection)! + "#" + anchor,
);
this.anchors.set(reflection, anchor);
}

const anchor = this.getSlugger(pageReflection).slug(parts.join("."));

this.fullUrls.set(
reflection,
this.fullUrls.get(pageReflection)! + "#" + anchor,
);
this.anchors.set(reflection, anchor);

reflection.traverse((child, prop) => {
switch (prop) {
case TraverseProperty.Children:
case TraverseProperty.GetSignature:
case TraverseProperty.SetSignature:
case TraverseProperty.IndexSignature:
case TraverseProperty.Signatures:
case TraverseProperty.TypeParameter:
this.buildAnchors(child, pageReflection);
}
reflection.traverse((child) => {
this.buildAnchors(child, pageReflection);
return true;
});
}
Expand All @@ -332,10 +347,10 @@ export class DefaultRouter implements Router {
}

this.usedFileNames.add(`${lowerBaseName}-${index}`);
return `${baseName}-${index}.html`;
return `${baseName}-${index}.${this.extension}`;
}

this.usedFileNames.add(lowerBaseName);
return `${baseName}.html`;
return `${baseName}.${this.extension}`;
}
}
35 changes: 2 additions & 33 deletions src/test/behavior.c2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@ import {
import { filterMap } from "../lib/utils/index.js";
import { CommentStyle } from "../lib/utils/options/declaration.js";
import { TestLogger } from "./TestLogger.js";
import {
getConverter2App,
getConverter2Base,
getConverter2Program,
} from "./programs.js";
import { join } from "path";
import { existsSync } from "fs";
import { clearCommentCache } from "../lib/converter/comments/index.js";
import { getConverter2App, getConverter2Project } from "./programs.js";
import { getComment, query, querySig } from "./utils.js";

type NameTree = { [name: string]: NameTree | undefined };
Expand Down Expand Up @@ -65,34 +58,10 @@ function getLinkTexts(refl: Reflection) {
});
}

const base = getConverter2Base();
const app = getConverter2App();
const program = getConverter2Program();

function convert(...entries: [string, ...string[]]) {
const entryPoints = entries.map((entry) => {
const entryPoint = [
join(base, `behavior/${entry}.ts`),
join(base, `behavior/${entry}.d.ts`),
join(base, `behavior/${entry}.tsx`),
join(base, `behavior/${entry}.js`),
join(base, "behavior", entry, "index.ts"),
join(base, "behavior", entry, "index.js"),
].find(existsSync);

ok(entryPoint, `No entry point found for ${entry}`);
const sourceFile = program.getSourceFile(entryPoint);
ok(sourceFile, `No source file found for ${entryPoint}`);

return { displayName: entry, program, sourceFile, entryPoint };
});

app.options.setValue(
"entryPoints",
entryPoints.map((e) => e.entryPoint),
);
clearCommentCache();
return app.converter.convert(entryPoints);
return getConverter2Project(entries, "behavior");
}

describe("Behavior Tests", () => {
Expand Down
29 changes: 29 additions & 0 deletions src/test/converter2/behavior/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface Foo {
codeGeneration?: {
strings: boolean;
wasm: boolean;
};

iterator(options?: {
destroyOnReturn?: boolean;
}): AsyncIterableIterator<any>;
}

// `a` gets an anchor because it is directly within a type alias
export type Obj = { a: string };

export const abc = { abcProp: { nested: true } };

// `b` does NOT get an anchor as it isn't a direct descendent
export type ObjArray = { b: string }[];

export function Func(param: string): { noUrl: boolean } {
return { noUrl: !!param };
}

// Duplicate name with different case
export function func() {}

export namespace Nested {
export const refl = 1;
}
10 changes: 0 additions & 10 deletions src/test/converter2/behavior/routerBugs.ts

This file was deleted.

49 changes: 5 additions & 44 deletions src/test/issues.c2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import {
notDeepStrictEqual as notEqual,
ok,
} from "assert";
import { existsSync } from "fs";
import { join } from "path";
import { clearCommentCache } from "../lib/converter/comments/index.js";
import {
Comment,
CommentTag,
Expand All @@ -22,11 +19,7 @@ import {
UnionType,
} from "../lib/models/index.js";
import type { InlineTagDisplayPart } from "../lib/models/comments/comment.js";
import {
getConverter2App,
getConverter2Base,
getConverter2Program,
} from "./programs.js";
import { getConverter2App, getConverter2Project } from "./programs.js";
import { TestLogger } from "./TestLogger.js";
import {
equalKind,
Expand All @@ -38,42 +31,7 @@ import {
} from "./utils.js";
import { DefaultRouter, DefaultTheme, PageEvent } from "../index.js";

const base = getConverter2Base();
const app = getConverter2App();
const program = getConverter2Program();

function doConvert(entries: string[]) {
const entryPoints = entries
.map((entry) =>
[
join(base, `issues/${entry}.ts`),
join(base, `issues/${entry}.d.ts`),
join(base, `issues/${entry}.tsx`),
join(base, `issues/${entry}.js`),
join(base, "issues", entry, "index.ts"),
join(base, "issues", entry, "index.js"),
join(base, "issues", entry),
].find(existsSync),
)
.filter((x) => x !== undefined);

const files = entryPoints.map((e) => program.getSourceFile(e));
for (const [index, file] of files.entries()) {
ok(file, `No source file found for ${entryPoints[index]}`);
}

app.options.setValue("entryPoints", entryPoints);
clearCommentCache();
return app.converter.convert(
files.map((file, index) => {
return {
displayName: entries[index].replace(/\.[tj]sx?$/, ""),
program,
sourceFile: file!,
};
}),
);
}

describe("Issue Tests", () => {
let logger: TestLogger;
Expand All @@ -86,7 +44,10 @@ describe("Issue Tests", () => {
const issueNumber = this.currentTest?.title.match(/#(\d+)/)?.[1];
ok(issueNumber, "Test name must contain an issue number.");
convert = (...entries) =>
doConvert(entries.length ? entries : [`gh${issueNumber}`]);
getConverter2Project(
entries.length ? entries : [`gh${issueNumber}`],
"issues",
);
});

afterEach(() => {
Expand Down
Loading

0 comments on commit a2bbe04

Please sign in to comment.