/** * 背景选择器的**界面可达性**判据。 * * 为什么需要这一层:`stores/background.test.ts` 只钉了 store 的状态机, * 而线上真实缺陷恰好落在"状态 → 控件"这一跳上 —— * 点「图片」时 kind 被折叠回 `none`,于是上传控件(只在 `kind === 'image'` * 下渲染)**从不出现**:store 的单测全绿,用户却完全无法自定义背景。 * * 所以这里测的是"用户走得到走不到":点「图片」→ 选文件按钮必须出现 → * 选完图背景必须真的亮起来。 */ import { describe, expect, it, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import BackgroundPicker from '../../src/components/BackgroundPicker'; import { LEGACY_STORAGE_KEY, storageKey, useBackgroundStore } from '../../src/stores/backgroundStore'; import * as store from '../../src/stores/backgroundStore'; describe('BackgroundPicker', () => { beforeEach(() => { localStorage.clear(); document.documentElement.removeAttribute('data-bg'); document.documentElement.className = ''; document.documentElement.removeAttribute('style'); useBackgroundStore.setState({ ...store.DEFAULT_BACKGROUND }); vi.restoreAllMocks(); }); it('★ 点「图片」后必须出现选文件控件(否则自定义背景根本走不到)', async () => { render(); // 初始是「无」:没有文件输入 expect(document.querySelector('input[type=file]')).toBeNull(); await userEvent.click(screen.getByRole('button', { name: '图片' })); expect(useBackgroundStore.getState().kind).toBe('image'); expect(document.querySelector('input[type=file]')).not.toBeNull(); expect(screen.getByRole('button', { name: /选择图片|更换图片/ })).toBeInTheDocument(); }); it('★ 选完图片后背景真的亮起来,并落盘', async () => { vi.spyOn(store, 'prepareImage').mockResolvedValue({ ok: true, dataUrl: 'data:image/jpeg;base64,ZZZZ' }); render(); await userEvent.click(screen.getByRole('button', { name: '图片' })); const input = document.querySelector('input[type=file]') as HTMLInputElement; const file = new File(['x'], 'bg.jpg', { type: 'image/jpeg' }); await userEvent.upload(input, file); await waitFor(() => expect(document.documentElement.dataset.bg).toBe('on')); expect(document.documentElement.style.getPropertyValue('--bg-image')).toContain('ZZZZ'); expect(JSON.parse(localStorage.getItem(storageKey(''))!).kind).toBe('image'); }); it('图片准备失败时说出原因,并且不假装成功', async () => { vi.spyOn(store, 'prepareImage').mockResolvedValue({ ok: false, reason: '图片过大(超过 20MB),请先裁剪' }); render(); await userEvent.click(screen.getByRole('button', { name: '图片' })); const input = document.querySelector('input[type=file]') as HTMLInputElement; await userEvent.upload(input, new File(['x'], 'bg.jpg', { type: 'image/jpeg' })); expect(await screen.findByRole('alert')).toHaveTextContent('图片过大'); // 没换成就不该亮背景 expect(document.documentElement.dataset.bg).toBe('off'); }); it('「无」把背景关掉(对照:图片档不是永久卡住)', async () => { useBackgroundStore.getState().setImage('data:image/png;base64,YYYY'); render(); await userEvent.click(screen.getByRole('button', { name: '无' })); expect(useBackgroundStore.getState().kind).toBe('none'); expect(document.documentElement.dataset.bg).toBe('off'); }); });