mirror of
https://github.com/vicky469/aside.git
synced 2026-07-22 17:42:04 +00:00
242 lines
8.1 KiB
TypeScript
242 lines
8.1 KiB
TypeScript
import * as assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import type { TFile } from "obsidian";
|
|
import {
|
|
PluginRegistrationController,
|
|
resolveCommentProtocolTarget,
|
|
type EditorMenuItemLike,
|
|
type EditorMenuLike,
|
|
} from "../src/control/pluginRegistrationController";
|
|
|
|
function createFile(path: string): TFile {
|
|
return {
|
|
path,
|
|
basename: path.split("/").pop()?.replace(/\.[^.]+$/, "") ?? path,
|
|
extension: path.split(".").pop() ?? "",
|
|
} as TFile;
|
|
}
|
|
|
|
function createMenuHarness() {
|
|
const items: Array<{
|
|
title: string | null;
|
|
icon: string | null;
|
|
onClick: (() => void | Promise<void>) | null;
|
|
}> = [];
|
|
|
|
const menu: EditorMenuLike = {
|
|
addItem(builder) {
|
|
const item: EditorMenuItemLike & {
|
|
title: string | null;
|
|
icon: string | null;
|
|
onClickHandler: (() => void | Promise<void>) | null;
|
|
} = {
|
|
title: null,
|
|
icon: null,
|
|
onClickHandler: null,
|
|
setTitle(title: string) {
|
|
this.title = title;
|
|
return this;
|
|
},
|
|
setIcon(icon: string) {
|
|
this.icon = icon;
|
|
return this;
|
|
},
|
|
onClick(callback: () => void | Promise<void>) {
|
|
this.onClickHandler = callback;
|
|
return this;
|
|
},
|
|
};
|
|
|
|
builder(item);
|
|
items.push({
|
|
title: item.title,
|
|
icon: item.icon,
|
|
onClick: item.onClickHandler,
|
|
});
|
|
},
|
|
};
|
|
|
|
return { menu, items };
|
|
}
|
|
|
|
function createHarness() {
|
|
const registerViewCalls: Array<{ viewType: string; creator: (leaf: unknown) => unknown }> = [];
|
|
const protocolHandlers = new Map<string, (params: Record<string, unknown>) => void>();
|
|
const removedCommandIds: string[] = [];
|
|
const commands: Array<{
|
|
id: string;
|
|
name: string;
|
|
icon: string;
|
|
callback?: () => Promise<void> | void;
|
|
editorCallback?: (editor: { somethingSelected(): boolean }, view: { file: TFile | null }) => Promise<void> | void;
|
|
}> = [];
|
|
let editorMenuHandler: ((menu: EditorMenuLike, editor: { somethingSelected(): boolean }, view: { file: TFile | null }) => void) | null = null;
|
|
const ribbonActions: Array<{ icon: string; title: string; callback: () => void }> = [];
|
|
const createdSidebarLeaves: unknown[] = [];
|
|
const draftCalls: Array<{ selected: boolean; filePath: string | null }> = [];
|
|
const highlightedCommentTargets: Array<{ filePath: string; commentId: string }> = [];
|
|
let openIndexNoteCount = 0;
|
|
let installVaultAgentsFileCount = 0;
|
|
let uninstallVaultAgentsFileCount = 0;
|
|
|
|
const controller = new PluginRegistrationController({
|
|
manifestId: "side-note2",
|
|
iconId: "side-note2-icon",
|
|
registerView: (viewType, creator) => {
|
|
registerViewCalls.push({ viewType, creator });
|
|
},
|
|
registerObsidianProtocolHandler: (action, handler) => {
|
|
protocolHandlers.set(action, handler);
|
|
},
|
|
removeCommand: (commandId) => {
|
|
removedCommandIds.push(commandId);
|
|
},
|
|
addCommand: (command) => {
|
|
commands.push(command);
|
|
},
|
|
registerEditorMenu: (handler) => {
|
|
editorMenuHandler = handler;
|
|
},
|
|
addRibbonIcon: (icon, title, callback) => {
|
|
ribbonActions.push({ icon, title, callback });
|
|
},
|
|
createSidebarView: (leaf) => {
|
|
createdSidebarLeaves.push(leaf);
|
|
return { leaf };
|
|
},
|
|
startDraftFromEditorSelection: async (editor, file) => {
|
|
draftCalls.push({
|
|
selected: editor.somethingSelected(),
|
|
filePath: file?.path ?? null,
|
|
});
|
|
},
|
|
highlightCommentById: async (filePath, commentId) => {
|
|
highlightedCommentTargets.push({ filePath, commentId });
|
|
},
|
|
openIndexNote: async () => {
|
|
openIndexNoteCount += 1;
|
|
},
|
|
installVaultAgentsFile: async () => {
|
|
installVaultAgentsFileCount += 1;
|
|
},
|
|
uninstallVaultAgentsFile: async () => {
|
|
uninstallVaultAgentsFileCount += 1;
|
|
},
|
|
});
|
|
|
|
return {
|
|
controller,
|
|
registerViewCalls,
|
|
protocolHandlers,
|
|
removedCommandIds,
|
|
commands,
|
|
getEditorMenuHandler: () => editorMenuHandler,
|
|
ribbonActions,
|
|
createdSidebarLeaves,
|
|
draftCalls,
|
|
highlightedCommentTargets,
|
|
getOpenIndexNoteCount: () => openIndexNoteCount,
|
|
getInstallVaultAgentsFileCount: () => installVaultAgentsFileCount,
|
|
getUninstallVaultAgentsFileCount: () => uninstallVaultAgentsFileCount,
|
|
};
|
|
}
|
|
|
|
test("plugin registration controller registers the view, protocol handler, command, and ribbon action", async () => {
|
|
const harness = createHarness();
|
|
const editorFile = createFile("docs/file.md");
|
|
|
|
harness.controller.register();
|
|
|
|
assert.deepEqual(harness.registerViewCalls.map((call) => call.viewType), ["sidenote2-view"]);
|
|
assert.equal(harness.registerViewCalls[0].creator({ id: "leaf-1" }) instanceof Object, true);
|
|
assert.deepEqual(harness.createdSidebarLeaves, [{ id: "leaf-1" }]);
|
|
assert.deepEqual(harness.removedCommandIds, ["side-note2:activate-view"]);
|
|
assert.deepEqual(harness.commands.map((command) => command.id), [
|
|
"add-comment-to-selection",
|
|
"install-vault-agents-file",
|
|
"uninstall-vault-agents-file",
|
|
]);
|
|
assert.deepEqual(harness.ribbonActions.map((action) => action.title), ["Open SideNote2 index"]);
|
|
|
|
await harness.commands[0].editorCallback?.(
|
|
{ somethingSelected: () => true },
|
|
{ file: editorFile },
|
|
);
|
|
assert.deepEqual(harness.draftCalls, [{
|
|
selected: true,
|
|
filePath: editorFile.path,
|
|
}]);
|
|
|
|
harness.protocolHandlers.get("side-note2-comment")?.({});
|
|
harness.protocolHandlers.get("side-note2-comment")?.({
|
|
file: "docs/file.md",
|
|
commentId: "comment-1",
|
|
});
|
|
await Promise.resolve();
|
|
assert.deepEqual(harness.highlightedCommentTargets, [{
|
|
filePath: "docs/file.md",
|
|
commentId: "comment-1",
|
|
}]);
|
|
|
|
harness.ribbonActions[0].callback();
|
|
await Promise.resolve();
|
|
assert.equal(harness.getOpenIndexNoteCount(), 1);
|
|
|
|
await harness.commands[1].callback?.();
|
|
assert.equal(harness.getInstallVaultAgentsFileCount(), 1);
|
|
|
|
await harness.commands[2].callback?.();
|
|
assert.equal(harness.getUninstallVaultAgentsFileCount(), 1);
|
|
});
|
|
|
|
test("plugin registration controller only adds the editor menu item for active selections", async () => {
|
|
const harness = createHarness();
|
|
const editorMenuHandler = (() => {
|
|
harness.controller.register();
|
|
return harness.getEditorMenuHandler();
|
|
})();
|
|
const file = createFile("docs/file.md");
|
|
|
|
assert.ok(editorMenuHandler);
|
|
|
|
const emptyMenuHarness = createMenuHarness();
|
|
editorMenuHandler?.(
|
|
emptyMenuHarness.menu,
|
|
{ somethingSelected: () => false },
|
|
{ file },
|
|
);
|
|
assert.deepEqual(emptyMenuHarness.items, []);
|
|
|
|
const selectedMenuHarness = createMenuHarness();
|
|
editorMenuHandler?.(
|
|
selectedMenuHarness.menu,
|
|
{ somethingSelected: () => true },
|
|
{ file },
|
|
);
|
|
|
|
assert.deepEqual(selectedMenuHarness.items.map((item) => ({
|
|
title: item.title,
|
|
icon: item.icon,
|
|
})), [{
|
|
title: "Add comment to selection",
|
|
icon: "side-note2-icon",
|
|
}]);
|
|
|
|
await selectedMenuHarness.items[0].onClick?.();
|
|
assert.deepEqual(harness.draftCalls, [{
|
|
selected: true,
|
|
filePath: file.path,
|
|
}]);
|
|
});
|
|
|
|
test("comment protocol target resolution requires both file and comment id", () => {
|
|
assert.equal(resolveCommentProtocolTarget({}), null);
|
|
assert.equal(resolveCommentProtocolTarget({ file: "docs/file.md" }), null);
|
|
assert.deepEqual(resolveCommentProtocolTarget({
|
|
file: "docs/file.md",
|
|
commentId: "comment-1",
|
|
}), {
|
|
filePath: "docs/file.md",
|
|
commentId: "comment-1",
|
|
});
|
|
});
|