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

209 lines
5.5 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.

/**
* 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<typeof createMockWin>
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 = '<script>alert(1)</script>'
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)
})
})
})