import { enableAutoUnmount, mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import { afterEach, describe, expect, it } from 'vitest' import { createMemoryTaskRepository } from '@/data/taskRepository' import { useTaskStore } from '@/stores/tasks' import { useToastStore } from '@/stores/toast' import BatchAddDrawer from './BatchAddDrawer.vue' enableAutoUnmount(afterEach) function setup(open = true) { const pinia = createPinia() setActivePinia(pinia) const store = useTaskStore() store.init({ repository: createMemoryTaskRepository(), crossTab: false }) // 抽屉会被 Teleport 到 body,测试中改为就地渲染以便断言 const wrapper = mount(BatchAddDrawer, { props: { open }, global: { plugins: [pinia], stubs: { teleport: true } }, attachTo: document.body, }) return { store, wrapper } } function buttonByText(wrapper: ReturnType['wrapper'], text: string) { return wrapper.findAll('button').find((node) => node.text().includes(text)) } describe('BatchAddDrawer 录入', () => { it('打开后显示多行清单输入框', () => { const { wrapper } = setup() expect(wrapper.find('textarea[aria-label="批量任务清单"]').exists()).toBe(true) }) it('按行实时统计将添加的任务数', async () => { const { wrapper } = setup() await wrapper.find('textarea').setValue('第一条\n\n- 第二条\n第三条') expect(buttonByText(wrapper, '个任务')?.text()).toContain('添加 3 个任务') }) it('清单为空时提交按钮不可用', () => { const { wrapper } = setup() const button = buttonByText(wrapper, '添加 0 个任务') expect(button).toBeDefined() expect(button?.attributes('disabled')).toBeDefined() }) it('只有空行时同样不可提交', async () => { const { wrapper } = setup() await wrapper.find('textarea').setValue(' \n\n') expect(buttonByText(wrapper, '添加 0 个任务')?.attributes('disabled')).toBeDefined() }) }) describe('BatchAddDrawer 提交', () => { it('按清单顺序创建任务,第一条在最上面', async () => { const { store, wrapper } = setup() await wrapper.find('textarea').setValue('第一条\n第二条\n第三条') await buttonByText(wrapper, '添加 3 个任务')?.trigger('click') expect(store.byColumn.todo.map((task) => task.title)).toEqual(['第一条', '第二条', '第三条']) }) it('目标列、优先级、截止日期与标签整批共用', async () => { const { store, wrapper } = setup() await wrapper.find('textarea').setValue('第一条\n第二条') await buttonByText(wrapper, '进行中')?.trigger('click') await buttonByText(wrapper, '高')?.trigger('click') await wrapper.find('input[type="date"]').setValue('2026-10-20') const tagInput = wrapper.find('input[aria-label="添加标签"]') await tagInput.setValue('前端') await tagInput.trigger('keydown.enter') await buttonByText(wrapper, '添加 2 个任务')?.trigger('click') const created = store.byColumn.doing expect(created.map((task) => task.title)).toEqual(['第一条', '第二条']) expect(created[0]?.priority).toBe(3) expect(created[0]?.dueDate).toBe('2026-10-20') expect(created[0]?.tags).toEqual(['前端']) expect(created[1]?.tags).toEqual(['前端']) }) it('提交后请求关闭并提示数量', async () => { const { wrapper } = setup() await wrapper.find('textarea').setValue('A\nB') await buttonByText(wrapper, '添加 2 个任务')?.trigger('click') expect(wrapper.emitted('close')).toHaveLength(1) expect(useToastStore().toasts.at(-1)?.message).toBe('已添加 2 个任务') }) it('取消不创建任何任务', async () => { const { store, wrapper } = setup() await wrapper.find('textarea').setValue('A\nB') await buttonByText(wrapper, '取消')?.trigger('click') expect(store.tasks).toHaveLength(0) expect(wrapper.emitted('close')).toHaveLength(1) }) it('再次打开时清空上一次的残留', async () => { const { wrapper } = setup() await wrapper.find('textarea').setValue('上一次的清单') await wrapper.setProps({ open: false }) await wrapper.setProps({ open: true }) expect((wrapper.find('textarea').element as HTMLTextAreaElement).value).toBe('') }) it('误提交时可以整批撤销', async () => { const { store, wrapper } = setup() await wrapper.find('textarea').setValue('A\nB') await buttonByText(wrapper, '添加 2 个任务')?.trigger('click') const action = useToastStore().toasts.at(-1)?.action expect(action?.label).toBe('撤销') action?.run() expect(store.activeTasks).toHaveLength(0) }) })