Files
Genarrative/packages/shared/src/components/OverflowActions.test.tsx
T
suzmii c329c438d9
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
修复资源画布PR的CI回归(#410)
修复活动回合空快照引用及停用后晚到请求覆盖问题
对齐共享卡片角标与窗口发布次数回归断言
同步原生HTTP权限检查与图集显式切片测试契约
整理菜单组件导入顺序并记录本地验证范围
2026-09-17 20:17:26 +08:00

208 lines
7.0 KiB
TypeScript

/** @vitest-environment jsdom */
import {
cleanup,
fireEvent,
render,
screen,
within,
} from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { CanvasCardCornerActions } from './CanvasCardCornerActions';
import { OverflowActions } from './OverflowActions';
afterEach(cleanup);
describe('操作收纳与卡片角标', () => {
it('具名入口可收纳全部动作,并优先向上展开', () => {
render(
<OverflowActions maxVisible={0} label="素材处理">
<button>快速编辑</button>
<button>改造</button>
</OverflowActions>,
);
const trigger = screen.getByRole('button', { name: '素材处理' });
expect(screen.getAllByRole('button')).toHaveLength(1);
vi.spyOn(trigger, 'getBoundingClientRect').mockReturnValue({
left: 200,
right: 300,
top: 350,
bottom: 380,
width: 100,
height: 30,
x: 200,
y: 350,
toJSON: () => ({}),
});
const height = vi
.spyOn(HTMLElement.prototype, 'scrollHeight', 'get')
.mockReturnValue(100);
fireEvent.mouseEnter(trigger);
const group = screen.getByRole('group', { name: '素材处理操作' });
expect(parseFloat(group.style.top)).toBeLessThan(350);
expect(
within(group)
.getAllByRole('button')
.map((b) => b.textContent),
).toEqual(['快速编辑', '改造']);
fireEvent.keyDown(document.body, { key: 'Escape' });
expect(screen.queryByRole('group')).toBeNull();
height.mockRestore();
vi.restoreAllMocks();
});
it('前五项可见,跳过分隔符和空 fragment,悬停展示剩余项并执行原回调', () => {
const click = vi.fn();
render(
<OverflowActions maxVisible={5}>
<>
<button></button>
<span aria-hidden="true" />
<button></button>
<></>
<button></button>
<button></button>
<button></button>
<span aria-hidden="true" />
<button onClick={click}></button>
<button disabled></button>
</>
</OverflowActions>,
);
expect(screen.getAllByRole('button')).toHaveLength(6);
expect(screen.queryByText('六')).toBeNull();
fireEvent.mouseEnter(screen.getByRole('button', { name: '更多' }));
const group = screen.getByRole('group', { name: '更多操作' });
expect(
within(group)
.getAllByRole('button')
.map((b) => b.textContent),
).toEqual(['六', '七']);
expect((screen.getByText('七') as HTMLButtonElement).disabled).toBe(true);
fireEvent.click(screen.getByText('六'));
expect(click).toHaveBeenCalledTimes(1);
expect(screen.queryByRole('group')).toBeNull();
});
it('悬停后点击仍展开、移入浮层不消失、外部关闭,Escape 恢复焦点', () => {
vi.useFakeTimers();
render(
<OverflowActions maxVisible={1}>
<button></button>
<button></button>
</OverflowActions>,
);
const more = screen.getByRole('button', { name: '更多' });
fireEvent.mouseEnter(more);
fireEvent.click(more);
fireEvent.mouseLeave(more);
fireEvent.mouseEnter(screen.getByRole('group'));
vi.advanceTimersByTime(200);
expect(screen.getByText('二')).toBeTruthy();
fireEvent.keyDown(screen.getByText('二'), { key: 'Escape' });
expect(screen.queryByRole('group')).toBeNull();
expect(document.activeElement).toBe(more);
fireEvent.click(more);
fireEvent.pointerDown(document.body);
expect(screen.queryByRole('group')).toBeNull();
vi.useRealTimers();
});
it('不溢出不显示更多,默认保持 Web 原样', () => {
const view = render(
<OverflowActions maxVisible={2}>
<button></button>
<button></button>
</OverflowActions>,
);
expect(screen.queryByText('更多')).toBeNull();
view.rerender(
<OverflowActions>
{Array.from({ length: 10 }, (_, i) => (
<button key={i}>{i}</button>
))}
</OverflowActions>,
);
expect(screen.getAllByRole('button')).toHaveLength(10);
});
it('动作减少至不溢出后关闭浮层,恢复动作不会自动重开', () => {
const view = render(
<OverflowActions maxVisible={1}>
<button></button>
<button></button>
</OverflowActions>,
);
fireEvent.mouseEnter(screen.getByRole('button', { name: '更多' }));
expect(screen.getByRole('group')).toBeTruthy();
view.rerender(
<OverflowActions maxVisible={1}>
<button></button>
</OverflowActions>,
);
expect(screen.queryByRole('group')).toBeNull();
view.rerender(
<OverflowActions maxVisible={1}>
<button></button>
<button></button>
</OverflowActions>,
);
expect(screen.queryByRole('group')).toBeNull();
});
it('浮层在窗口右下边界向上展开,方向键跳过禁用项', () => {
render(
<OverflowActions maxVisible={1}>
<button></button>
<button></button>
<button disabled></button>
<button></button>
</OverflowActions>,
);
const more = screen.getByRole('button', { name: '更多' });
vi.spyOn(more, 'getBoundingClientRect').mockReturnValue({
left: window.innerWidth - 35,
right: window.innerWidth,
top: window.innerHeight - 40,
bottom: window.innerHeight - 10,
width: 35,
height: 30,
x: 0,
y: 0,
toJSON: () => ({}),
});
// jsdom 没有真实布局,提供浮层测量以验证向上定位。
const height = vi
.spyOn(HTMLElement.prototype, 'scrollHeight', 'get')
.mockReturnValue(200);
fireEvent.click(more);
const panel = screen.getByRole('group');
expect(parseFloat(panel.style.top)).toBeLessThan(window.innerHeight - 40);
screen.getByText('二').focus();
fireEvent.keyDown(screen.getByText('二'), { key: 'ArrowDown' });
expect(document.activeElement).toBe(screen.getByText('四'));
fireEvent.keyDown(screen.getByText('四'), { key: 'Home' });
expect(document.activeElement).toBe(screen.getByText('二'));
height.mockRestore();
vi.restoreAllMocks();
});
it('卡片角标阻断指针和键盘冒泡,类型及信息分别执行', () => {
const parent = vi.fn(),
kind = vi.fn(),
info = vi.fn();
render(
<div onClick={parent} onPointerDown={parent} onKeyDown={parent}>
<CanvasCardCornerActions
kindLabel="角色"
kindAriaLabel="素材类型"
infoLabel="资源信息"
onKindClick={kind}
onInfoClick={info}
/>
</div>,
);
const label = screen.getByRole('button', { name: '素材类型' });
fireEvent.pointerDown(label);
fireEvent.click(label);
fireEvent.keyDown(label, { key: 'Enter' });
fireEvent.click(screen.getByRole('button', { name: '资源信息' }));
expect(kind).toHaveBeenCalledTimes(2);
expect(info).toHaveBeenCalledOnce();
expect(parent).not.toHaveBeenCalled();
});
});