macos-web/tests/cases/35-stickies.test.ts
2026-07-23 15:16:57 +08:00

200 lines
5.7 KiB
TypeScript
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.

/**
* 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)
})
})
})