/** * 36-reminders: Reminders Vue 组件化 — 深层测试 */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { h, render, nextTick } from 'vue' import RemindersComponent from '../../src/apps/reminders/Reminders.vue' import { store } from '../../src/composables/useStore' import { setupDOM } from '../helpers' function mk() { const e = document.createElement('div'); e.className = 'window'; document.getElementById('window-layer')?.appendChild(e); const b = document.createElement('div'); b.className = 'win-body'; e.appendChild(b); return { el: e, body: b, timers: [] as any[], data: {} } } describe('36-reminders', () => { let w: any beforeEach(() => { localStorage.clear(); store.set('reminders', null); setupDOM(); w = mk() }) afterEach(() => { render(null, w.body); w.el.remove() }) function m() { render(h(RemindersComponent, { win: w }), w.body) } it('3个默认列表', () => { m(); expect(w.body.querySelectorAll('.fb-side-item').length).toBe(3) }) it('颜色标识', () => { m(); expect(w.body.querySelector('.rem-color')).toBeTruthy() }) it('列表名称', () => { m(); expect(w.body.querySelector('.rem-lname')?.textContent).toBeTruthy() }) it('未完成计数', () => { m(); expect(w.body.querySelector('.rem-count')?.textContent).toBeTruthy() }) it('默认选中首列表', () => { m(); expect(w.body.querySelector('.fb-side-item.sel')).toBeTruthy() }) it('切换列表sel转移', async () => { m(); const items = w.body.querySelectorAll('.fb-side-item'); click(items[1]); await nextTick(); expect(items[1].classList.contains('sel')).toBe(true) }) it('checkbox存在', () => { m(); expect(w.body.querySelector('.rem-check')).toBeTruthy() }) it('标题输入存在', () => { m(); expect(w.body.querySelector('.rem-title')).toBeTruthy() }) it('日期输入存在', () => { m(); expect(w.body.querySelector('.rem-due')).toBeTruthy() }) it('默认数据含3个列表', () => { m(); expect(w.body.querySelectorAll('.fb-side-item').length).toBe(3) }) it('勾选未完成变done', async () => { m(); const cb = w.body.querySelector('.rem-check:not(.done)') as HTMLElement; click(cb); await nextTick(); await nextTick(); expect(w.body.querySelectorAll('.rem-check.done').length).toBeGreaterThanOrEqual(1) }) it('勾选后store持久化', async () => { m(); const cb = w.body.querySelector('.rem-check:not(.done)') as HTMLElement; click(cb); await nextTick(); await nextTick(); expect(store.get('reminders', {}).items.some((i: any) => i.done)).toBe(true) }) it('Enter新建清空输入', async () => { m(); const inp = w.body.querySelector('.rem-new') as HTMLInputElement; inp.value = '新提醒'; inp.dispatchEvent(new Event('input', { bubbles: true })); inp.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await nextTick(); expect((w.body.querySelector('.rem-new') as HTMLInputElement).value).toBe('') }) it('空输入不新建', async () => { m(); const before = w.body.querySelectorAll('.rem-row').length; (w.body.querySelector('.rem-new') as HTMLInputElement).dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await nextTick(); expect(w.body.querySelectorAll('.rem-row').length).toBe(before) }) it('标题change更新', async () => { m(); const ti = w.body.querySelector('.rem-title') as HTMLInputElement; ti.value = '改了'; ti.dispatchEvent(new Event('change', { bubbles: true })); await nextTick(); expect(store.get('reminders', {}).items.some((i: any) => i.title === '改了')).toBe(true) }) it('日期change更新', async () => { m(); const di = w.body.querySelector('.rem-due') as HTMLInputElement; di.value = '2026-12-25'; di.dispatchEvent(new Event('change', { bubbles: true })); await nextTick(); expect(store.get('reminders', {}).items.some((i: any) => i.due === '2026-12-25')).toBe(true) }) it('空列表提示', () => { store.set('reminders', { lists: [{ id:'e1', name:'空', color:'#ff9f0a' }], items: [] }); m(); expect(w.body.querySelector('.empty-state')?.textContent).toContain('此列表为空') }) it('store加载预设', () => { store.set('reminders', { lists: [{ id:'x1', name:'自定义', color:'#ff453a' }], items: [] }); m(); expect(w.body.querySelector('.rem-lname')?.textContent).toBe('自定义') }) it('卸载', () => { m(); render(null, w.body); expect(w.body.children.length).toBe(0) }) }) function click(el: Element | null) { el?.dispatchEvent(new MouseEvent('click', { bubbles: true })) }