import { writeFile } from 'node:fs/promises' import { expect, test } from '@playwright/test' import { addTask, openBackupPanel } from './helpers' test.beforeEach(async ({ page }) => { await page.goto('/') }) test('导出 → 清空 → 导入,数据完全还原', async ({ page }, testInfo) => { await addTask(page, '备份任务甲') await addTask(page, '备份任务乙') await expect(page.locator('article')).toHaveCount(2) // 导出 await openBackupPanel(page) const [download] = await Promise.all([ page.waitForEvent('download'), page.getByRole('button', { name: '导出为 JSON' }).click(), ]) expect(download.suggestedFilename()).toMatch(/^kanban-\d{4}-\d{2}-\d{2}\.json$/) const exported = testInfo.outputPath('export.json') await download.saveAs(exported) // 模拟数据丢失 await page.evaluate(() => localStorage.removeItem('kanban.tasks')) await page.reload() await expect(page.locator('article')).toHaveCount(0) // 导入 await openBackupPanel(page) await page.setInputFiles('input[type="file"]', exported) await expect(page.getByText('覆盖当前全部 0 个任务')).toBeVisible() await page.getByRole('button', { name: '确认覆盖导入' }).click() await expect(page.locator('article')).toHaveCount(2) await expect(page.locator('article').filter({ hasText: '备份任务甲' })).toBeVisible() await expect(page.locator('article').filter({ hasText: '备份任务乙' })).toBeVisible() await page.reload() await expect(page.locator('article')).toHaveCount(2) }) test('导入非法文件时给出错误且不破坏现有数据', async ({ page }, testInfo) => { await addTask(page, '不能丢的任务') const brokenFile = testInfo.outputPath('broken.json') await writeFile(brokenFile, '{ this is not json', 'utf8') await openBackupPanel(page) await page.setInputFiles('input[type="file"]', brokenFile) await expect(page.getByText('不是合法的 JSON')).toBeVisible() await expect(page.locator('article')).toHaveCount(1) await expect(page.locator('article').filter({ hasText: '不能丢的任务' })).toBeVisible() }) test('导入结构不合法时同样拒绝并保留现有数据', async ({ page }, testInfo) => { await addTask(page, '保留我') const badShape = testInfo.outputPath('bad-shape.json') await writeFile(badShape, JSON.stringify({ tasks: [{ id: 'broken' }] }), 'utf8') await openBackupPanel(page) await page.setInputFiles('input[type="file"]', badShape) await expect(page.getByText('第 1 条任务格式不合法')).toBeVisible() await expect(page.getByRole('button', { name: '确认覆盖导入' })).toHaveCount(0) await expect(page.locator('article')).toHaveCount(1) }) test('自动备份列表可恢复数据', async ({ page }) => { await addTask(page, '会被快照记录的任务') // 重新打开看板会写入一份启动快照 await page.reload() await expect(page.locator('article')).toHaveCount(1) // 清空数据后,用启动快照恢复 await page.evaluate(() => localStorage.removeItem('kanban.tasks')) await page.reload() await expect(page.locator('article')).toHaveCount(0) await openBackupPanel(page) await page.getByRole('button', { name: '恢复' }).first().click() await expect(page.getByText(/已从备份恢复 \d+ 个任务/)).toBeVisible() await expect(page.locator('article').filter({ hasText: '会被快照记录的任务' })).toBeVisible() })