This commit is contained in:
2026-10-16 16:34:15 +08:00
parent 7a3769670b
commit ff366a4daf
80 changed files with 9961 additions and 888 deletions

View File

@@ -0,0 +1,132 @@
// P2-23(audit):AI 面板 placeholder 区分「无文件」vs「未配置」。
//
// 设计:纯函数 pickPlaceholder 决策三态文案,
// - !hasFile → 「请先打开一个 Markdown 文件」
// - hasFile + !configured → 「AI 未配置 — 点「⚙ 设置」填 Base URL 与 API Key」
// - hasFile + configured → 「描述你想如何修改当前文档...」
//
// 把决策抽成纯函数方便单测(避免起 jsdom 跑 DOM 副作用)。panel 内部
// setEnabled / setConfigured 走它。
//
// 另测:setEnabled / setConfigured 在 jsdom 下同步 DOM 状态(input.placeholder +
// .ai-chat-configure-link.hidden),保证「内联设置链」只在该显示时显示。
// @vitest-environment jsdom
import { describe, it, expect, beforeEach } from 'vitest';
import { pickPlaceholder, AiChatPanel } from '../../src/ai/ai-chat-panel.js';
describe('pickPlaceholder —— P2-23 三态文案', () => {
it('无文件 → 提示先打开文件', () => {
expect(pickPlaceholder({ hasFile: false, configured: true }))
.toBe('请先打开一个 Markdown 文件');
});
it('有文件 + 未配置 → 提示去设置里配置', () => {
const text = pickPlaceholder({ hasFile: true, configured: false });
expect(text).toContain('AI 未配置');
expect(text).toContain('设置');
});
it('有文件 + 已配置 → 默认描述型 placeholder', () => {
expect(pickPlaceholder({ hasFile: true, configured: true }))
.toBe('描述你想如何修改当前文档...');
});
it('优先级:!hasFile 永远盖过 !configured(即便都没配)', () => {
expect(pickPlaceholder({ hasFile: false, configured: false }))
.toBe('请先打开一个 Markdown 文件');
});
});
describe('AiChatPanel.setEnabled / setConfigured —— DOM 同步', () => {
/** @returns {HTMLElement} 重建一份 index.html 里的 ai-chat-dock 骨架 */
function buildDock() {
const dock = document.createElement('div');
dock.className = 'ai-chat-dock';
dock.innerHTML = `
<div class="ai-chat-form-row">
<button class="ai-chat-toggle" type="button">×</button>
<div class="ai-chat-input-wrap">
<input type="text" class="ai-chat-input" />
<button class="ai-chat-configure-link" type="button" hidden>⚙ 设置</button>
</div>
<button class="ai-chat-submit" type="button" data-mode="submit" disabled>生成修改</button>
</div>
`;
return dock;
}
let dock;
beforeEach(() => {
dock = buildDock();
});
it('未初始化时默认 configured=true(避免中间态被误判)', () => {
const panel = new AiChatPanel({
element: dock,
onSubmit: () => {},
onCancel: () => {},
});
// 默认 _configured !== false === true
panel.setEnabled(false); // 无文件
expect(dock.querySelector('.ai-chat-input').placeholder)
.toBe('请先打开一个 Markdown 文件');
});
it('hasFile + !configured → placeholder 切到「未配置」+ 内联链显示', () => {
const panel = new AiChatPanel({
element: dock,
onSubmit: () => {},
onCancel: () => {},
});
panel.setEnabled(true); // 有文件
panel.setConfigured(false); // 未配置
const input = dock.querySelector('.ai-chat-input');
const link = dock.querySelector('.ai-chat-configure-link');
expect(input.placeholder).toContain('AI 未配置');
expect(link.hidden).toBe(false);
});
it('hasFile + configured → placeholder 切回「描述修改」+ 内联链隐藏', () => {
const panel = new AiChatPanel({
element: dock,
onSubmit: () => {},
onCancel: () => {},
});
panel.setEnabled(true);
panel.setConfigured(false);
panel.setConfigured(true); // 配置完成
const input = dock.querySelector('.ai-chat-input');
const link = dock.querySelector('.ai-chat-configure-link');
expect(input.placeholder).toBe('描述你想如何修改当前文档...');
expect(link.hidden).toBe(true);
});
it('!hasFile 永远隐藏内联链(不论配置与否)', () => {
const panel = new AiChatPanel({
element: dock,
onSubmit: () => {},
onCancel: () => {},
});
panel.setEnabled(false); // 无文件
panel.setConfigured(false); // 未配置
expect(dock.querySelector('.ai-chat-configure-link').hidden).toBe(true);
});
it('点击内联链调 onOpenSettings 回调(如果传了)', () => {
let called = 0;
const panel = new AiChatPanel({
element: dock,
onSubmit: () => {},
onCancel: () => {},
onOpenSettings: () => { called += 1; },
});
panel.setEnabled(true);
panel.setConfigured(false);
const link = dock.querySelector('.ai-chat-configure-link');
link.click();
expect(called).toBe(1);
});
});