/**
* 背景选择器的**界面可达性**判据。
*
* 为什么需要这一层:`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');
});
});