mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
81de426f96
Add getCommandDiagnostics and getShortcutDiagnostics mocks to fix
'Cannot read properties of undefined' errors in InteractiveMode tests.
regression from 32a305cb
566 lines
19 KiB
TypeScript
566 lines
19 KiB
TypeScript
import { homedir } from "node:os";
|
|
import * as path from "node:path";
|
|
import { Container } from "@mariozechner/pi-tui";
|
|
import { beforeAll, describe, expect, test, vi } from "vitest";
|
|
import type { SourceInfo } from "../src/core/source-info.js";
|
|
import { InteractiveMode } from "../src/modes/interactive/interactive-mode.js";
|
|
import { initTheme } from "../src/modes/interactive/theme/theme.js";
|
|
|
|
function renderLastLine(container: Container, width = 120): string {
|
|
const last = container.children[container.children.length - 1];
|
|
if (!last) return "";
|
|
return last.render(width).join("\n");
|
|
}
|
|
|
|
function renderAll(container: Container, width = 120): string {
|
|
return container.children.flatMap((child) => child.render(width)).join("\n");
|
|
}
|
|
|
|
function normalizeRenderedOutput(container: Container, width = 220): string {
|
|
return renderAll(container, width)
|
|
.replace(/\u001b\[[0-9;]*m/g, "")
|
|
.replace(/\\/g, "/")
|
|
.split("\n")
|
|
.map((line) => line.replace(/\s+$/g, ""))
|
|
.join("\n")
|
|
.trim();
|
|
}
|
|
|
|
type ExtensionFixture = {
|
|
path: string;
|
|
sourceInfo?: SourceInfo;
|
|
};
|
|
|
|
describe("InteractiveMode.showStatus", () => {
|
|
beforeAll(() => {
|
|
// showStatus uses the global theme instance
|
|
initTheme("dark");
|
|
});
|
|
|
|
test("coalesces immediately-sequential status messages", () => {
|
|
const fakeThis: any = {
|
|
chatContainer: new Container(),
|
|
ui: { requestRender: vi.fn() },
|
|
lastStatusSpacer: undefined,
|
|
lastStatusText: undefined,
|
|
};
|
|
|
|
(InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_ONE");
|
|
expect(fakeThis.chatContainer.children).toHaveLength(2);
|
|
expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_ONE");
|
|
|
|
(InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_TWO");
|
|
// second status updates the previous line instead of appending
|
|
expect(fakeThis.chatContainer.children).toHaveLength(2);
|
|
expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_TWO");
|
|
expect(renderLastLine(fakeThis.chatContainer)).not.toContain("STATUS_ONE");
|
|
});
|
|
|
|
test("appends a new status line if something else was added in between", () => {
|
|
const fakeThis: any = {
|
|
chatContainer: new Container(),
|
|
ui: { requestRender: vi.fn() },
|
|
lastStatusSpacer: undefined,
|
|
lastStatusText: undefined,
|
|
};
|
|
|
|
(InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_ONE");
|
|
expect(fakeThis.chatContainer.children).toHaveLength(2);
|
|
|
|
// Something else gets added to the chat in between status updates
|
|
fakeThis.chatContainer.addChild({ render: () => ["OTHER"], invalidate: () => {} });
|
|
expect(fakeThis.chatContainer.children).toHaveLength(3);
|
|
|
|
(InteractiveMode as any).prototype.showStatus.call(fakeThis, "STATUS_TWO");
|
|
// adds spacer + text
|
|
expect(fakeThis.chatContainer.children).toHaveLength(5);
|
|
expect(renderLastLine(fakeThis.chatContainer)).toContain("STATUS_TWO");
|
|
});
|
|
});
|
|
|
|
describe("InteractiveMode.setToolsExpanded", () => {
|
|
test("applies expansion state to the active header and chat entries", () => {
|
|
const header = { setExpanded: vi.fn() };
|
|
const chatChild = { setExpanded: vi.fn() };
|
|
const fakeThis: any = {
|
|
toolOutputExpanded: false,
|
|
customHeader: undefined,
|
|
builtInHeader: header,
|
|
chatContainer: { children: [chatChild] },
|
|
ui: { requestRender: vi.fn() },
|
|
};
|
|
|
|
(InteractiveMode as any).prototype.setToolsExpanded.call(fakeThis, true);
|
|
|
|
expect(fakeThis.toolOutputExpanded).toBe(true);
|
|
expect(header.setExpanded).toHaveBeenCalledWith(true);
|
|
expect(chatChild.setExpanded).toHaveBeenCalledWith(true);
|
|
expect(fakeThis.ui.requestRender).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("InteractiveMode.createExtensionUIContext setTheme", () => {
|
|
test("persists theme changes to settings manager", () => {
|
|
initTheme("dark");
|
|
|
|
let currentTheme = "dark";
|
|
const settingsManager = {
|
|
getTheme: vi.fn(() => currentTheme),
|
|
setTheme: vi.fn((theme: string) => {
|
|
currentTheme = theme;
|
|
}),
|
|
};
|
|
const fakeThis: any = {
|
|
session: { settingsManager },
|
|
settingsManager,
|
|
ui: { requestRender: vi.fn() },
|
|
};
|
|
|
|
const uiContext = (InteractiveMode as any).prototype.createExtensionUIContext.call(fakeThis);
|
|
const result = uiContext.setTheme("light");
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(settingsManager.setTheme).toHaveBeenCalledWith("light");
|
|
expect(currentTheme).toBe("light");
|
|
expect(fakeThis.ui.requestRender).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("does not persist invalid theme names", () => {
|
|
initTheme("dark");
|
|
|
|
const settingsManager = {
|
|
getTheme: vi.fn(() => "dark"),
|
|
setTheme: vi.fn(),
|
|
};
|
|
const fakeThis: any = {
|
|
session: { settingsManager },
|
|
settingsManager,
|
|
ui: { requestRender: vi.fn() },
|
|
};
|
|
|
|
const uiContext = (InteractiveMode as any).prototype.createExtensionUIContext.call(fakeThis);
|
|
const result = uiContext.setTheme("__missing_theme__");
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(settingsManager.setTheme).not.toHaveBeenCalled();
|
|
expect(fakeThis.ui.requestRender).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("InteractiveMode.showLoadedResources", () => {
|
|
beforeAll(() => {
|
|
initTheme("dark");
|
|
});
|
|
|
|
function createShowLoadedResourcesThis(options: {
|
|
quietStartup: boolean;
|
|
verbose?: boolean;
|
|
toolOutputExpanded?: boolean;
|
|
cwd?: string;
|
|
contextFiles?: Array<{ path: string; content?: string }>;
|
|
extensions?: ExtensionFixture[];
|
|
skills?: Array<{ filePath: string; name: string }>;
|
|
skillDiagnostics?: Array<{ type: "warning" | "error" | "collision"; message: string }>;
|
|
useRealScopeGroups?: boolean;
|
|
}) {
|
|
const fakeThis: any = {
|
|
options: { verbose: options.verbose ?? false },
|
|
toolOutputExpanded: options.toolOutputExpanded ?? false,
|
|
chatContainer: new Container(),
|
|
settingsManager: {
|
|
getQuietStartup: () => options.quietStartup,
|
|
},
|
|
sessionManager: {
|
|
getCwd: () => options.cwd ?? "/tmp/project",
|
|
},
|
|
session: {
|
|
promptTemplates: [],
|
|
extensionRunner: {
|
|
getCommandDiagnostics: () => [],
|
|
getShortcutDiagnostics: () => [],
|
|
},
|
|
resourceLoader: {
|
|
getPathMetadata: () => new Map(),
|
|
getAgentsFiles: () => ({ agentsFiles: options.contextFiles ?? [] }),
|
|
getSkills: () => ({
|
|
skills: options.skills ?? [],
|
|
diagnostics: options.skillDiagnostics ?? [],
|
|
}),
|
|
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
getExtensions: () => ({ extensions: options.extensions ?? [], errors: [], runtime: {} }),
|
|
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
},
|
|
},
|
|
formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p),
|
|
formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p),
|
|
getStartupExpansionState: () => (InteractiveMode as any).prototype.getStartupExpansionState.call(fakeThis),
|
|
buildScopeGroups: () => [],
|
|
formatScopeGroups: () => "resource-list",
|
|
isPackageSource: (sourceInfo?: SourceInfo) =>
|
|
(InteractiveMode as any).prototype.isPackageSource.call(fakeThis, sourceInfo),
|
|
getShortPath: (p: string, sourceInfo?: SourceInfo) =>
|
|
(InteractiveMode as any).prototype.getShortPath.call(fakeThis, p, sourceInfo),
|
|
getCompactPathLabel: (p: string, sourceInfo?: SourceInfo) =>
|
|
(InteractiveMode as any).prototype.getCompactPathLabel.call(fakeThis, p, sourceInfo),
|
|
getCompactPackageSourceLabel: (sourceInfo?: SourceInfo) =>
|
|
(InteractiveMode as any).prototype.getCompactPackageSourceLabel.call(fakeThis, sourceInfo),
|
|
getCompactExtensionLabel: (p: string, sourceInfo?: SourceInfo) =>
|
|
(InteractiveMode as any).prototype.getCompactExtensionLabel.call(fakeThis, p, sourceInfo),
|
|
getCompactDisplayPathSegments: (p: string) =>
|
|
(InteractiveMode as any).prototype.getCompactDisplayPathSegments.call(fakeThis, p),
|
|
getCompactNonPackageExtensionLabel: (
|
|
p: string,
|
|
index: number,
|
|
allPaths: Array<{ path: string; segments: string[] }>,
|
|
) => (InteractiveMode as any).prototype.getCompactNonPackageExtensionLabel.call(fakeThis, p, index, allPaths),
|
|
getCompactExtensionLabels: (extensions: ExtensionFixture[]) =>
|
|
(InteractiveMode as any).prototype.getCompactExtensionLabels.call(fakeThis, extensions),
|
|
formatDiagnostics: () => "diagnostics",
|
|
getBuiltInCommandConflictDiagnostics: () => [],
|
|
};
|
|
|
|
if (options.useRealScopeGroups) {
|
|
fakeThis.getScopeGroup = (sourceInfo?: SourceInfo) =>
|
|
(InteractiveMode as any).prototype.getScopeGroup.call(fakeThis, sourceInfo);
|
|
fakeThis.buildScopeGroups = (items: Array<{ path: string; sourceInfo?: SourceInfo }>) =>
|
|
(InteractiveMode as any).prototype.buildScopeGroups.call(fakeThis, items);
|
|
fakeThis.formatScopeGroups = (groups: unknown, formatOptions: unknown) =>
|
|
(InteractiveMode as any).prototype.formatScopeGroups.call(fakeThis, groups, formatOptions);
|
|
}
|
|
|
|
return fakeThis;
|
|
}
|
|
|
|
function createSourceInfo(
|
|
filePath: string,
|
|
options: {
|
|
source: string;
|
|
scope: "user" | "project" | "temporary";
|
|
origin: "package" | "top-level";
|
|
baseDir?: string;
|
|
},
|
|
): SourceInfo {
|
|
return {
|
|
path: filePath,
|
|
source: options.source,
|
|
scope: options.scope,
|
|
origin: options.origin,
|
|
baseDir: options.baseDir,
|
|
};
|
|
}
|
|
|
|
function createExtensionFixtures(): ExtensionFixture[] {
|
|
return [
|
|
{
|
|
path: "/tmp/project/.pi/extensions/answer.ts",
|
|
sourceInfo: createSourceInfo("/tmp/project/.pi/extensions/answer.ts", {
|
|
source: "local",
|
|
scope: "project",
|
|
origin: "top-level",
|
|
baseDir: "/tmp/project/.pi/extensions",
|
|
}),
|
|
},
|
|
{
|
|
path: "/tmp/project/.pi/extensions/local-index/index.ts",
|
|
sourceInfo: createSourceInfo("/tmp/project/.pi/extensions/local-index/index.ts", {
|
|
source: "local",
|
|
scope: "project",
|
|
origin: "top-level",
|
|
baseDir: "/tmp/project/.pi/extensions",
|
|
}),
|
|
},
|
|
{
|
|
path: "/tmp/agent/extensions/user-index/index.ts",
|
|
sourceInfo: createSourceInfo("/tmp/agent/extensions/user-index/index.ts", {
|
|
source: "local",
|
|
scope: "user",
|
|
origin: "top-level",
|
|
baseDir: "/tmp/agent/extensions",
|
|
}),
|
|
},
|
|
{
|
|
path: "/tmp/project/.pi/npm/node_modules/pi-markdown-preview/extensions/index.ts",
|
|
sourceInfo: createSourceInfo("/tmp/project/.pi/npm/node_modules/pi-markdown-preview/extensions/index.ts", {
|
|
source: "npm:pi-markdown-preview",
|
|
scope: "project",
|
|
origin: "package",
|
|
baseDir: "/tmp/project/.pi/npm/node_modules/pi-markdown-preview",
|
|
}),
|
|
},
|
|
{
|
|
path: "/tmp/project/.pi/npm/node_modules/@scope/pi-scoped/extensions/index.ts",
|
|
sourceInfo: createSourceInfo("/tmp/project/.pi/npm/node_modules/@scope/pi-scoped/extensions/index.ts", {
|
|
source: "npm:@scope/pi-scoped",
|
|
scope: "project",
|
|
origin: "package",
|
|
baseDir: "/tmp/project/.pi/npm/node_modules/@scope/pi-scoped",
|
|
}),
|
|
},
|
|
{
|
|
path: "/tmp/project/.pi/git/github.com/HazAT/pi-interactive-subagents/extensions/index.ts",
|
|
sourceInfo: createSourceInfo(
|
|
"/tmp/project/.pi/git/github.com/HazAT/pi-interactive-subagents/extensions/index.ts",
|
|
{
|
|
source: "git:github.com/HazAT/pi-interactive-subagents",
|
|
scope: "project",
|
|
origin: "package",
|
|
baseDir: "/tmp/project/.pi/git/github.com/HazAT/pi-interactive-subagents",
|
|
},
|
|
),
|
|
},
|
|
{
|
|
path: "/tmp/project/.pi/git/github.com/HazAT/pi-interactive-subagents/extensions/subagents/index.ts",
|
|
sourceInfo: createSourceInfo(
|
|
"/tmp/project/.pi/git/github.com/HazAT/pi-interactive-subagents/extensions/subagents/index.ts",
|
|
{
|
|
source: "git:github.com/HazAT/pi-interactive-subagents",
|
|
scope: "project",
|
|
origin: "package",
|
|
baseDir: "/tmp/project/.pi/git/github.com/HazAT/pi-interactive-subagents",
|
|
},
|
|
),
|
|
},
|
|
{
|
|
path: "/tmp/temp/cli-extension.ts",
|
|
sourceInfo: createSourceInfo("/tmp/temp/cli-extension.ts", {
|
|
source: "cli",
|
|
scope: "temporary",
|
|
origin: "top-level",
|
|
baseDir: "/tmp/temp",
|
|
}),
|
|
},
|
|
];
|
|
}
|
|
|
|
test("shows a compact resource listing by default", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
const output = renderAll(fakeThis.chatContainer);
|
|
expect(output).toContain("[Skills]");
|
|
expect(output).toContain("commit");
|
|
expect(output).not.toContain("resource-list");
|
|
});
|
|
|
|
test("shows full resource listing when expanded", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
toolOutputExpanded: true,
|
|
skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
const output = renderAll(fakeThis.chatContainer);
|
|
expect(output).toContain("[Skills]");
|
|
expect(output).toContain("resource-list");
|
|
expect(output).not.toContain("commit");
|
|
});
|
|
|
|
test("shows full resource listing on verbose startup even when tool output is collapsed", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: true,
|
|
verbose: true,
|
|
toolOutputExpanded: false,
|
|
skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
const output = renderAll(fakeThis.chatContainer);
|
|
expect(output).toContain("[Skills]");
|
|
expect(output).toContain("resource-list");
|
|
expect(output).not.toContain("commit");
|
|
});
|
|
|
|
test("abbreviates extensions in compact listing", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
extensions: [{ path: "/tmp/extensions/answer.ts" }, { path: "/tmp/extensions/btw.ts" }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
const output = renderAll(fakeThis.chatContainer);
|
|
expect(output).toContain("[Extensions]");
|
|
expect(output).toContain("answer.ts, btw.ts");
|
|
expect(output).not.toContain("extensions/answer.ts");
|
|
});
|
|
|
|
test("captures mixed extension layouts in compact output", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
extensions: createExtensionFixtures(),
|
|
useRealScopeGroups: true,
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
|
"[Extensions]
|
|
@scope/pi-scoped, answer.ts, cli-extension.ts, HazAT/pi-interactive-subagents, HazAT/pi-interactive-subagents:subagents, local-index/index.ts, pi-markdown-preview, user-index/index.ts"`);
|
|
});
|
|
|
|
test("adds more parent folders until local extension labels are unique", () => {
|
|
const extensions: ExtensionFixture[] = [
|
|
{
|
|
path: "/tmp/alpha/one/index.ts",
|
|
sourceInfo: createSourceInfo("/tmp/alpha/one/index.ts", {
|
|
source: "cli",
|
|
scope: "temporary",
|
|
origin: "top-level",
|
|
baseDir: "/tmp/alpha",
|
|
}),
|
|
},
|
|
{
|
|
path: "/tmp/beta/one/index.ts",
|
|
sourceInfo: createSourceInfo("/tmp/beta/one/index.ts", {
|
|
source: "cli",
|
|
scope: "temporary",
|
|
origin: "top-level",
|
|
baseDir: "/tmp/beta",
|
|
}),
|
|
},
|
|
{
|
|
path: "/tmp/gamma/one/index.ts",
|
|
sourceInfo: createSourceInfo("/tmp/gamma/one/index.ts", {
|
|
source: "cli",
|
|
scope: "temporary",
|
|
origin: "top-level",
|
|
baseDir: "/tmp/gamma",
|
|
}),
|
|
},
|
|
];
|
|
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
extensions,
|
|
useRealScopeGroups: true,
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
|
"[Extensions]
|
|
alpha/one/index.ts, beta/one/index.ts, gamma/one/index.ts"`);
|
|
});
|
|
|
|
test("captures mixed extension layouts in expanded output", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
toolOutputExpanded: true,
|
|
extensions: createExtensionFixtures(),
|
|
useRealScopeGroups: true,
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
|
|
"[Extensions]
|
|
project
|
|
/tmp/project/.pi/extensions/answer.ts
|
|
/tmp/project/.pi/extensions/local-index/index.ts
|
|
git:github.com/HazAT/pi-interactive-subagents
|
|
extensions/index.ts
|
|
extensions/subagents/index.ts
|
|
npm:@scope/pi-scoped
|
|
extensions/index.ts
|
|
npm:pi-markdown-preview
|
|
extensions/index.ts
|
|
user
|
|
/tmp/agent/extensions/user-index/index.ts
|
|
path
|
|
/tmp/temp/cli-extension.ts"`);
|
|
});
|
|
|
|
test("shows context paths relative to cwd while preserving full external paths", () => {
|
|
const home = homedir();
|
|
const cwd = path.join(home, "Development", "pi-mono");
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
cwd,
|
|
contextFiles: [{ path: path.join(home, ".pi", "agent", "AGENTS.md") }, { path: path.join(cwd, "AGENTS.md") }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
const output = renderAll(fakeThis.chatContainer).replace(/\\/g, "/");
|
|
expect(output).toContain("[Context]");
|
|
expect(output).toContain("~/.pi/agent/AGENTS.md, AGENTS.md");
|
|
expect(output).not.toContain(`${cwd.replace(/\\/g, "/")}/AGENTS.md`);
|
|
});
|
|
|
|
test("shows full context paths when expanded", () => {
|
|
const home = homedir();
|
|
const cwd = path.join(home, "Development", "pi-mono");
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: false,
|
|
toolOutputExpanded: true,
|
|
cwd,
|
|
contextFiles: [{ path: path.join(home, ".pi", "agent", "AGENTS.md") }, { path: path.join(cwd, "AGENTS.md") }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
});
|
|
|
|
const output = renderAll(fakeThis.chatContainer).replace(/\\/g, "/");
|
|
expect(output).toContain("[Context]");
|
|
expect(output).toContain("~/.pi/agent/AGENTS.md");
|
|
expect(output).toContain("~/Development/pi-mono/AGENTS.md");
|
|
expect(output).not.toContain("~/.pi/agent/AGENTS.md, AGENTS.md");
|
|
});
|
|
|
|
test("does not show verbose listing on quiet startup during reload", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: true,
|
|
skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
extensions: [{ path: "/tmp/ext/index.ts" }],
|
|
force: false,
|
|
showDiagnosticsWhenQuiet: true,
|
|
});
|
|
|
|
expect(fakeThis.chatContainer.children).toHaveLength(0);
|
|
});
|
|
|
|
test("still shows diagnostics on quiet startup when requested", () => {
|
|
const fakeThis = createShowLoadedResourcesThis({
|
|
quietStartup: true,
|
|
skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }],
|
|
skillDiagnostics: [{ type: "warning", message: "duplicate skill name" }],
|
|
});
|
|
|
|
(InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, {
|
|
force: false,
|
|
showDiagnosticsWhenQuiet: true,
|
|
});
|
|
|
|
const output = renderAll(fakeThis.chatContainer);
|
|
expect(output).toContain("[Skill conflicts]");
|
|
expect(output).not.toContain("[Skills]");
|
|
});
|
|
});
|