Files
PlantAI-rag-new/tests/frontend/unit/b1/appshell.test.js
T
jerryandClaude Fable 5 0875bb036c P0/M1: 四部分重构——frontend→ui、backend→sdk 改名 + 引用修复 + 骨架
- git mv 保留历史:ui/(Vue3+Vite)、sdk/(BFF 中间层)
- 路径语义修正:db_path/figures_cache 改相对 sdk/(BFF cwd),
  历史库 sdk/backend/data 迁移至 sdk/data/(会话/图片缓存无损)
- 引用修复 ~15 处:config.py、deploy/*.sh、vitest/playwright 配置、
  单测导入路径、docs、README、gitignore、.env.example
- 骨架:agent/{qa,methodology,legacy-rag,vendor}、knowledge/manifest.yaml、
  tooling/{compile,kb,crypto,assemble,audit,tools,scripts} + README
- 门禁验证:sdk pytest 125 通过、ui vitest 158 通过、
  生产 BFF 脚本重启四上游全绿 + 静态托管 + 会话库无损

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 12:56:47 +08:00

145 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// AppShell 响应式单测(B1):四档断点的 class 切换(FR-RESP-001..004)
// jsdom + matchMedia mock。验证:
// desktop ≥1280:无 drawers-mode,折叠 class 生效
// laptop 1024–1279:无 drawers-mode(CSS 强制折叠由样式层处理)
// tablet 768–1023:drawers-mode
// mobile <768:drawers-mode + mobile
// 折叠持久化:localStorage pa-side-collapsed / pa-bench-collapsed
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia } from 'pinia';
import { nextTick } from 'vue';
import AppShell from '../../../../ui/src/components/layout/AppShell.vue';
// mock fetch(health store 若被使用则不真发请求)
vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }))));
/** 用 matchMedia mock 设置视口宽度 */
function setViewport(w) {
window.matchMedia = (q) => ({
matches: false,
media: q,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
});
Object.defineProperty(window, 'innerWidth', { configurable: true, writable: true, value: w });
}
async function mountShell(w = 1440) {
setViewport(w);
const pinia = createPinia();
const wrapper = mount(AppShell, { global: { plugins: [pinia] } });
await nextTick();
return wrapper;
}
describe('AppShell 响应式四档断点', () => {
beforeEach(() => {
localStorage.clear();
});
it('desktop ≥1280px:三栏,无 drawers-mode;折叠 class 与 localStorage 联动', async () => {
const w = await mountShell(1440);
const shell = w.find('.app-shell');
expect(shell.exists()).toBe(true);
expect(shell.classes()).not.toContain('drawers-mode');
expect(shell.classes()).not.toContain('mobile');
// 默认展开
expect(shell.classes()).not.toContain('side-collapsed');
expect(shell.classes()).not.toContain('bench-collapsed');
// 折叠把手点击 → side-collapsed + localStorage
await w.find('.side-handle-btn').trigger('click');
expect(w.find('.app-shell').classes()).toContain('side-collapsed');
expect(localStorage.getItem('pa-side-collapsed')).toBe('1');
await w.find('.side-handle-btn').trigger('click');
expect(localStorage.getItem('pa-side-collapsed')).toBe('0');
// bench 折叠(折叠入口按钮)
await w.find('.bench-collapse-btn').trigger('click');
expect(w.find('.app-shell').classes()).toContain('bench-collapsed');
expect(localStorage.getItem('pa-bench-collapsed')).toBe('1');
w.unmount();
});
it('折叠状态刷新后保持(localStorage 持久化)', async () => {
localStorage.setItem('pa-side-collapsed', '1');
localStorage.setItem('pa-bench-collapsed', '1');
const w = await mountShell(1440);
const cls = w.find('.app-shell').classes();
expect(cls).toContain('side-collapsed');
expect(cls).toContain('bench-collapsed');
w.unmount();
});
it('laptop 1024–1279px:三栏保留(CSS 锁折叠),无 drawers-mode', async () => {
const w = await mountShell(1180);
const cls = w.find('.app-shell').classes();
expect(cls).not.toContain('drawers-mode');
expect(cls).not.toContain('mobile');
// 折叠把手在笔记本档由 CSS 隐藏(类逻辑上仍可点击,但桌面外不提供)
expect(w.find('.side-handle-btn').exists()).toBe(true);
w.unmount();
});
it('tablet 768–1023px:drawers-mode(左右栏改抽屉),无 mobile', async () => {
const w = await mountShell(900);
const cls = w.find('.app-shell').classes();
expect(cls).toContain('drawers-mode');
expect(cls).not.toContain('mobile');
expect(cls).toContain('has-tools');
// 网格中的左右栏不再渲染(抽屉形态)
expect(w.find('.aside-host').exists()).toBe(false);
expect(w.find('.bench-wrap').exists()).toBe(false);
// 抽屉组件存在(初始关闭)
expect(w.findAllComponents({ name: 'Drawer' })).toBeTruthy();
w.unmount();
});
it('mobile <768px:drawers-mode + mobile', async () => {
const w = await mountShell(600);
const cls = w.find('.app-shell').classes();
expect(cls).toContain('drawers-mode');
expect(cls).toContain('mobile');
expect(w.find('.aside-host').exists()).toBe(false);
expect(w.find('.bench-wrap').exists()).toBe(false);
w.unmount();
});
it('汉堡按钮打开会话抽屉(<1024px)', async () => {
const w = await mountShell(900);
// 顶栏汉堡按钮(menuAvailable 为 true 时渲染)
const burger = w.find('.top-bar .hamburger-btn');
expect(burger.exists()).toBe(true);
await burger.trigger('click');
await nextTick();
// Drawer 已打开(Teleport 到 body,用 document 查询)
expect(document.querySelectorAll('.drawer.left.open').length).toBe(1);
// Esc 关闭
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
await nextTick();
expect(document.querySelectorAll('.drawer.left.open').length).toBe(0);
w.unmount();
});
it('工作台按钮打开右抽屉(<1024px)', async () => {
const w = await mountShell(900);
const btn = w.find('.top-bar .bench-btn');
expect(btn.exists()).toBe(true);
await btn.trigger('click');
await nextTick();
expect(document.querySelectorAll('.drawer.right.open').length).toBe(1);
// scrim 点击关闭
document.querySelector('.drawer-scrim')?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await nextTick();
expect(document.querySelectorAll('.drawer.right.open').length).toBe(0);
w.unmount();
});
});