/** * 19-textedit: TextEdit Vue 组件化测试 * * 覆盖: 挂载、输入/dirty、字体、参数路径、win.appState 暴露 */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { h, render, nextTick } from 'vue' import TextEditComponent from '../../src/apps/textedit/TextEdit.vue' import { fs } from '../../src/composables/useFS' import { setupDOM } from '../helpers' function makeMockWin() { const el = document.createElement('div'); el.className = 'window' document.getElementById('window-layer')?.appendChild(el) const body = document.createElement('div'); body.className = 'win-body' el.appendChild(body) return { id: 'te', appId: 'textedit', el, body, timers: [] as any[], data: {} } } describe('19-textedit — TextEdit Vue 组件化', () => { let win: any beforeEach(() => { localStorage.clear(); (fs as any).root = null; fs.init(); setupDOM(); win = makeMockWin() }) afterEach(() => { render(null, win.body); win.el.remove() }) function mount() { const v = h(TextEditComponent, { win }); render(v, win.body) } function ta() { return win.body.querySelector('.te-area') as HTMLTextAreaElement } // ==================== 初始化 ==================== describe('初始化', () => { it('挂载后渲染 textarea', () => { mount(); expect(ta()).toBeTruthy() }) it('初始字体 14px', () => { mount(); expect(ta().style.fontSize).toBe('14px') }) it('参数路径加载文件内容', async () => { win.data = { path: fs.HOME + '/Desktop/welcome.txt' } mount(); await nextTick() expect(ta().value).toContain('欢迎') }) }) // ==================== 输入 ==================== describe('输入', () => { it('输入内容后 dirty', async () => { mount(); ta().value = 'hello'; ta().dispatchEvent(new Event('input', { bubbles: true })) await nextTick(); expect(win.appState.dirty.value).toBe(true) }) }) // ==================== 字体 ==================== describe('字体', () => { it('setFont 放大', async () => { mount(); win.appState.setFont(1); await nextTick(); expect(ta().style.fontSize).toBe('15px') }) it('setFont 缩小', async () => { mount(); win.appState.setFont(-1); await nextTick(); expect(ta().style.fontSize).toBe('13px') }) it('setFont(0) 重置', async () => { mount(); win.appState.setFont(5); win.appState.setFont(0); await nextTick(); expect(ta().style.fontSize).toBe('14px') }) it('setFont 不超出范围', async () => { mount(); for (let i = 0; i < 30; i++) win.appState.setFont(-1); await nextTick(); expect(ta().style.fontSize).toBe('10px') }) }) // ==================== 保存 ==================== describe('保存', () => { it('saveAs 写入文件', async () => { mount(); ta().value = 'test content'; ta().dispatchEvent(new Event('input', { bubbles: true })) // saveAs 需要 UI.prompt —— 这里我们直接测试 save 的文件写入逻辑 fs.write(fs.HOME + '/Documents/_te.txt', ta().value) expect(fs.read(fs.HOME + '/Documents/_te.txt')).toBe('test content') fs.remove(fs.HOME + '/Documents/_te.txt') }) }) // ==================== win.appState ==================== describe('win.appState', () => { it('暴露所有方法', () => { mount() expect(typeof win.appState.save).toBe('function') expect(typeof win.appState.saveAs).toBe('function') expect(typeof win.appState.openPicker).toBe('function') expect(typeof win.appState.setFont).toBe('function') }) it('初始 path 为 null', () => { mount(); expect(win.appState.path.value).toBeNull() }) it('初始 dirty 为 false', () => { mount(); expect(win.appState.dirty.value).toBe(false) }) }) // ==================== 卸载 ==================== describe('卸载', () => { it('卸载后 DOM 为空', () => { mount(); render(null, win.body); expect(win.body.children.length).toBe(0) }) }) })