/** * 35-stickies — 便笺 深层测试 * * 使用 vitest 原汁原味的 vi.mock() 模式 mock store/WM。 * 使用 @vue/test-utils mount() 渲染组件。 * 统一在 afterEach 中恢复 fake timers。 */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { mount } from '@vue/test-utils' import { nextTick } from 'vue' import StickiesComponent from '../../src/apps/stickies/Stickies.vue' // ---- mock 数据 ---- const mockNotes = [ { id: 's1', text: '双击便笺即可编辑。\n通过菜单可以更换颜色。', color: 'yellow', x: null, y: null }, ] const { mockStoreSet, mockStoreGet } = vi.hoisted(() => ({ mockStoreSet: vi.fn(), mockStoreGet: vi.fn((_key: string, fallback: any) => structuredClone(fallback)), })) vi.mock('../../src/composables/useStore', () => ({ store: { PREFIX: 'macos-web:', get: mockStoreGet, set: mockStoreSet, remove: vi.fn(), clearAll: vi.fn(), }, })) vi.mock('../../src/composables/useWM', () => ({ wm: { close: vi.fn(), setTitle: vi.fn() }, })) describe('35-stickies — 便笺', () => { let el: HTMLElement, body: HTMLElement beforeEach(() => { vi.clearAllMocks() localStorage.clear() el = document.createElement('div') el.className = 'window' document.body.appendChild(el) body = document.createElement('div') body.className = 'win-body' el.appendChild(body) // 默认返回预设便笺 mockStoreGet.mockReturnValue(structuredClone(mockNotes)) }) afterEach(() => { el.remove() // 确保恢复真实 timers(已在 setup.ts 全局 afterEach 中处理,此处兜底) vi.useRealTimers() }) function mountComponent() { return mount(StickiesComponent, { props: { win: { id: 'st1', appId: 'stickies', el, body, timers: [], data: { noteId: 's1' } }, }, attachTo: body, }) } function editableDiv() { return body.querySelector('[contenteditable]') as HTMLElement } // ==================== 渲染 ==================== describe('渲染', () => { it('contenteditable div 存在', () => { mountComponent() expect(editableDiv()).toBeTruthy() }) it('有 no-chrome 类', () => { mountComponent() expect(body.querySelector('.no-chrome')).toBeTruthy() }) it('显示预设文本', () => { mountComponent() expect(editableDiv().textContent).toContain('双击便笺') }) it('setColor 函数存在且不抛错', () => { const wrapper = mountComponent() const appState = (wrapper.vm as any).$props.win.appState expect(typeof appState.setColor).toBe('function') expect(() => appState.setColor('yellow')).not.toThrow() }) }) // ==================== 编辑 ==================== describe('编辑', () => { it('输入更新 DOM 文本', async () => { mountComponent() const div = editableDiv() div.textContent = '新内容' div.dispatchEvent(new Event('input', { bubbles: true })) await nextTick() expect(div.textContent).toContain('新内容') }) it('输入后触发持久化 debounce', async () => { vi.useFakeTimers() mountComponent() const div = editableDiv() div.textContent = '持久化文本' div.dispatchEvent(new Event('input', { bubbles: true })) vi.advanceTimersByTime(500) expect(mockStoreSet).toHaveBeenCalled() const call = mockStoreSet.mock.calls.find((c: any[]) => c[0] === 'stickies') expect(call).toBeTruthy() expect(call[1][0].text).toBe('持久化文本') vi.useRealTimers() }) }) // ==================== 颜色 ==================== describe('颜色', () => { function getAppState() { const wrapper = mountComponent() return (wrapper.vm as any).$props.win.appState } it('setColor 蓝色不抛错', () => { expect(() => getAppState().setColor('blue')).not.toThrow() }) it('换色后 store 更新', () => { const appState = getAppState() appState.setColor('blue') expect(mockStoreSet).toHaveBeenCalled() const call = mockStoreSet.mock.calls.find((c: any[]) => c[0] === 'stickies') expect(call[1][0].color).toBe('blue') }) it('全部 5 色可切换', () => { const appState = getAppState() for (const c of ['yellow', 'blue', 'green', 'pink', 'purple']) { expect(() => appState.setColor(c)).not.toThrow() } }) }) // ==================== 删除 ==================== describe('删除', () => { it('removeSelf 函数存在', () => { mountComponent() const wrapper = mountComponent() const appState = (wrapper.vm as any).$props.win.appState expect(typeof appState.removeSelf).toBe('function') }) it('removeSelf 从 store 移除', () => { const wrapper = mountComponent() const appState = (wrapper.vm as any).$props.win.appState appState.removeSelf() expect(mockStoreSet).toHaveBeenCalledWith('stickies', []) }) }) // ==================== appState ==================== describe('appState', () => { it('暴露 note 对象', () => { mountComponent() const wrapper = mountComponent() const appState = (wrapper.vm as any).$props.win.appState expect(appState.note).toBeTruthy() expect(appState.note.id).toBe('s1') }) it('暴露 setColor', () => { mountComponent() const wrapper = mountComponent() const appState = (wrapper.vm as any).$props.win.appState expect(typeof appState.setColor).toBe('function') }) }) // ==================== 卸载 ==================== describe('卸载', () => { it('卸载后 DOM 为空', () => { const wrapper = mountComponent() wrapper.unmount() expect(body.children).toHaveLength(0) }) }) })