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
修复活动回合空快照引用及停用后晚到请求覆盖问题 对齐共享卡片角标与窗口发布次数回归断言 同步原生HTTP权限检查与图集显式切片测试契约 整理菜单组件导入顺序并记录本地验证范围
208 lines
7.0 KiB
TypeScript
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();
|
|
});
|
|
});
|