209 lines
5.5 KiB
TypeScript
209 lines
5.5 KiB
TypeScript
/**
|
||
* 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)
|
||
})
|
||
})
|
||
})
|