/** * 40-bear — 熊掌记 深层测试 * * 使用 vitest 原汁原味的 vi.mock() 模式 mock 依赖模块(FS/WM)。 * 使用 @vue/test-utils mount() 渲染组件。 */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { mount } from '@vue/test-utils' import { nextTick } from 'vue' import BearComponent from '../../src/apps/bear/Bear.vue' import { setupDOM, createMockWin } from '../helpers' // ---- vi.mock() 模拟依赖(使用 vi.hoisted 解决 hoisting 问题)---- const { mockWrite, mockSetTitle } = vi.hoisted(() => ({ mockWrite: vi.fn(), mockSetTitle: vi.fn(), })) vi.mock('../../src/composables/useFS', () => ({ fs: { HOME: '/Users/guest', exists: vi.fn(() => false), read: vi.fn(() => ''), write: mockWrite, baseName: (p: string) => p.split('/').pop() || '', }, })) vi.mock('../../src/composables/useWM', () => ({ wm: { setTitle: mockSetTitle, }, })) describe('40-bear — 熊掌记', () => { let win: ReturnType beforeEach(() => { vi.clearAllMocks() setupDOM() win = createMockWin({ id: 'bear', appId: 'bear' }) }) afterEach(() => { win.el.remove() }) function mountComponent() { return mount(BearComponent, { props: { win }, attachTo: win.body, }) } function findTextarea() { return win.body.querySelector('textarea') as HTMLTextAreaElement } // ==================== DOM 结构 ==================== describe('DOM 结构', () => { it('textarea 存在', () => { mountComponent() expect(findTextarea()).toBeTruthy() }) it('占位符包含 Markdown', () => { mountComponent() expect(findTextarea().placeholder).toContain('Markdown') }) it('标签栏存在', () => { mountComponent() expect(win.body.querySelector('.bear-tagbar')).toBeTruthy() }) }) // ==================== 标签 ==================== describe('标签', () => { it('包含 #灵感 和 #工作', () => { mountComponent() const tags = [...win.body.querySelectorAll('.bear-tag')].map(t => t.textContent) expect(tags).toContain('#灵感') expect(tags).toContain('#工作') }) it('标签个数为 2', () => { mountComponent() expect(win.body.querySelectorAll('.bear-tag')).toHaveLength(2) }) }) // ==================== 编辑 ==================== describe('编辑', () => { it('输入触发 onInput', async () => { mountComponent() const ta = findTextarea() ta.value = 'hello' ta.dispatchEvent(new Event('input', { bubbles: true })) await nextTick() expect(mockSetTitle).toHaveBeenCalled() }) it('中文输入正常', async () => { mountComponent() const ta = findTextarea() ta.value = '你好世界' ta.dispatchEvent(new Event('input', { bubbles: true })) await nextTick() expect(ta.value).toBe('你好世界') }) it('空输入不崩溃', async () => { mountComponent() const ta = findTextarea() ta.value = '' ta.dispatchEvent(new Event('input', { bubbles: true })) await nextTick() expect(ta.value).toBe('') }) }) // ==================== 保存 ==================== describe('保存', () => { it('appState.save 存在', () => { mountComponent() expect(typeof win.appState.save).toBe('function') }) it('save 调用 write', () => { mountComponent() const ta = findTextarea() ta.value = 'test content' win.appState.save() expect(mockWrite).toHaveBeenCalledWith( '/Users/guest/Documents/熊掌记.md', 'test content', { mime: 'text/markdown' } ) }) it('空内容 save 不抛错', () => { mountComponent() findTextarea().value = '' expect(() => win.appState.save()).not.toThrow() }) }) // ==================== 快捷键 ==================== describe('快捷键', () => { it('⌘S 触发保存', async () => { mountComponent() const ta = findTextarea() ta.value = 'cmd-s-test' ta.dispatchEvent(new Event('input', { bubbles: true })) win.el.dispatchEvent(new KeyboardEvent('keydown', { key: 's', metaKey: true, bubbles: true })) expect(mockWrite).toHaveBeenCalled() }) it('普通 S 不触发保存', async () => { mountComponent() const before = mockWrite.mock.calls.length win.el.dispatchEvent(new KeyboardEvent('keydown', { key: 's', metaKey: false, bubbles: true })) expect(mockWrite).toHaveBeenCalledTimes(before) }) }) // ==================== 边界 ==================== describe('边界', () => { it('极长文本不崩溃', () => { mountComponent() const ta = findTextarea() ta.value = 'a'.repeat(10000) ta.dispatchEvent(new Event('input', { bubbles: true })) expect(ta.value.length).toBe(10000) }) it('特殊字符不崩溃', () => { mountComponent() const ta = findTextarea() ta.value = '' ta.dispatchEvent(new Event('input', { bubbles: true })) expect(ta.value).toContain('script') }) it('重复挂载不崩溃', () => { mountComponent() mountComponent() expect(findTextarea()).toBeTruthy() }) }) // ==================== 卸载 ==================== describe('卸载', () => { it('卸载后 DOM 清理干净', () => { const wrapper = mountComponent() wrapper.unmount() expect(win.body.children).toHaveLength(0) }) }) })