insta-toc
Version:
Simultaneously generate, update, and maintain a table of contents for your notes in real time.
91 lines (67 loc) • 3.97 kB
text/typescript
import type { HeadingCache } from "obsidian";
import { describe, expect, test, vi } from "vitest";
import { ManageToc } from "../src/ManageToc";
import { getDefaultLocalSettings } from "../src/settings/Settings";
import { createEditor, createPluginMock, createValidatorMock } from "./mocks/pluginClassMocks";
function getEndPosition(text: string): { line: number; ch: number; } {
const lines = text.split("\n");
return { line: lines.length - 1, ch: lines[lines.length - 1]?.length ?? 0 };
}
describe("ManageToc global/local setting precedence", () => {
test("falls back to global tocTitle when local title.name is null", async () => {
const localSettings = getDefaultLocalSettings();
localSettings.title.name = null;
localSettings.title.level = null;
localSettings.title.center = null;
const fileHeadings = [ { heading: "Heading 1", level: 1 }, {
heading: "Heading 2",
level: 2
} ] as HeadingCache[];
const { validator } = createValidatorMock(localSettings, fileHeadings);
const pluginMock = createPluginMock({ tocTitle: "Global Table of Contents" });
await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);
const generatedTocBlock = pluginMock.getCapturedContent();
expect(generatedTocBlock).toContain("Global Table of Contents");
expect(generatedTocBlock).not.toContain("null Global Table of Contents");
expect(generatedTocBlock).toContain("\n - Heading 2");
});
test("prefers local title settings over global defaults when provided", async () => {
const localSettings = getDefaultLocalSettings();
localSettings.title.name = "Local TOC";
localSettings.title.level = 2;
localSettings.title.center = true;
const fileHeadings = [ { heading: "Heading 1", level: 1 } ] as HeadingCache[];
const { validator } = createValidatorMock(localSettings, fileHeadings);
const pluginMock = createPluginMock({ tocTitle: "Global Table of Contents" });
await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);
const generatedTocBlock = pluginMock.getCapturedContent();
expect(generatedTocBlock).toContain("## Local TOC");
expect(generatedTocBlock).not.toContain("Global Table of Contents");
});
test("updates only the minimal changed range for an existing toc block", async () => {
const localSettings = getDefaultLocalSettings();
const initialHeadings = [ { heading: "Heading 1", level: 1 }, {
heading: "Old Heading 2",
level: 1
} ] as HeadingCache[];
const updatedHeadings = [ { heading: "Heading 1", level: 1 }, {
heading: "Heading 2",
level: 1
} ] as HeadingCache[];
const { validator } = createValidatorMock(localSettings, initialHeadings);
const pluginMock = createPluginMock(undefined, createEditor([]));
await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);
const initialTocBlock = pluginMock.getCapturedContent();
validator.fileHeadings = updatedHeadings;
validator.tocInsertPos = { from: { line: 0, ch: 0 }, to: getEndPosition(initialTocBlock) };
const applySpy = vi.spyOn(pluginMock.plugin.editorService, "applyEditorChange");
await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);
expect(pluginMock.getCapturedContent()).toContain("- Heading 2");
expect(pluginMock.getCapturedContent()).not.toContain("- Old Heading 2");
expect(applySpy).toHaveBeenCalledTimes(1);
const [ from, to, insert ] = applySpy.mock.calls[0] ?? [];
expect(from).toEqual({ line: 16, ch: 2 });
expect(to).toEqual({ line: 16, ch: 6 });
expect(insert).toBe("");
});
});