镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-20 13:47:47 +08:00
新增 OneDrive 个人版适配器、MAIN 桥接、名称校验与冲突检测、目录身份与分页遍历。同步更新三语简介、商店素材、仓库描述及 manifest 域名注册。修复面板键盘冒泡至宿主页面的隔离与工具栏 Escape 作用域。修正 OneDrive 同步结果语义为手动刷新提示, 避免误报同步失败。补全对应单测与浏览器验收脚本。
77 行
3.3 KiB
TypeScript
77 行
3.3 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { FileSelectorPanel } from '../../src/content/components/file-selector-panel';
|
|
import { Toolbar } from '../../src/content/components/toolbar';
|
|
|
|
afterEach(() => {
|
|
document.body.replaceChildren();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
async function mountPanel() {
|
|
const panel = new FileSelectorPanel();
|
|
document.body.append(panel);
|
|
await panel.updateComplete;
|
|
const nested = document.createElement('div');
|
|
const root = nested.attachShadow({ mode: 'open' });
|
|
const input = document.createElement('input');
|
|
root.append(input);
|
|
panel.shadowRoot!.append(nested);
|
|
return { panel, root, input };
|
|
}
|
|
|
|
describe('FileSelectorPanel keyboard boundary', () => {
|
|
it.each(['keydown', 'keypress', 'keyup'])('contains %s without cancelling native input or child handlers', async (type) => {
|
|
const { input } = await mountPanel();
|
|
const onPage = vi.fn();
|
|
const onInput = vi.fn();
|
|
document.addEventListener(type, onPage);
|
|
input.addEventListener(type, onInput);
|
|
try {
|
|
for (const args of [{ key: 'a' }, { key: 'v', ctrlKey: true }, { key: 'Tab' }, { key: 'Process', isComposing: true }]) {
|
|
const event = new KeyboardEvent(type, { ...args, bubbles: true, composed: true, cancelable: true });
|
|
expect(input.dispatchEvent(event)).toBe(true);
|
|
expect(event.defaultPrevented).toBe(false);
|
|
}
|
|
expect(onInput).toHaveBeenCalledTimes(4);
|
|
expect(onPage).not.toHaveBeenCalled();
|
|
document.body.dispatchEvent(new KeyboardEvent(type, { key: 'a', bubbles: true }));
|
|
expect(onPage).toHaveBeenCalledTimes(1);
|
|
} finally {
|
|
document.removeEventListener(type, onPage);
|
|
}
|
|
});
|
|
|
|
it('keeps the toolbar Escape action inside the panel and restores its button focus', async () => {
|
|
const { root } = await mountPanel();
|
|
const toolbar = new Toolbar();
|
|
root.append(toolbar);
|
|
await toolbar.updateComplete;
|
|
const button = toolbar.shadowRoot!.querySelector<HTMLButtonElement>('#type-filter-button')!;
|
|
button.click();
|
|
await toolbar.updateComplete;
|
|
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
|
|
expect(button.getAttribute('aria-expanded')).toBe('true');
|
|
const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true, cancelable: true });
|
|
button.dispatchEvent(event);
|
|
await toolbar.updateComplete;
|
|
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
|
|
expect(button.getAttribute('aria-expanded')).toBe('false');
|
|
expect(event.defaultPrevented).toBe(true);
|
|
expect(toolbar.shadowRoot!.activeElement).toBe(button);
|
|
});
|
|
|
|
it('removes the boundary listener on disconnect and reinstalls it on reconnect', async () => {
|
|
const { panel, input } = await mountPanel();
|
|
panel.remove();
|
|
const detached = new KeyboardEvent('keydown', { bubbles: true, composed: true });
|
|
const stop = vi.spyOn(detached, 'stopPropagation');
|
|
input.dispatchEvent(detached);
|
|
expect(stop).not.toHaveBeenCalled();
|
|
document.body.append(panel);
|
|
const reattached = new KeyboardEvent('keydown', { bubbles: true, composed: true });
|
|
const stopAgain = vi.spyOn(reattached, 'stopPropagation');
|
|
input.dispatchEvent(reattached);
|
|
expect(stopAgain).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|