macos-web/tests/cases/04-finder.test.js
2026-07-17 16:52:13 +08:00

189 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
/* ④⑤⑥ Finder 历史、双击/选择渲染、桌面重绘限制、复制移动与废纸篓 */
module.exports = {
name: '04-finder',
title: 'Finder 历史前进后退、双击、多选、重命名、复制移动、废纸篓还原、桌面重绘限制',
async run({ t, track, newPage, unlock, http }) {
const env = track(await newPage(http));
const { page } = env;
await unlock(page);
const H = '/Users/guest';
// ============ ④ 历史Desktop → Documents → Downloads ============
await page.evaluate(() => window.Apps.open('finder', { path: '/Users/guest/Desktop' }));
await page.waitForTimeout(300);
const fstate = () => page.evaluate(() => {
const w = window.WM.windowsForApp('finder').find(x => x.appState.path.startsWith('/Users/guest'));
const s = w.appState;
return {
path: s.path, hi: s.hi, hist: [...s.history],
backDis: w.body.querySelector('.fb-btn').disabled,
fwdDis: w.body.querySelectorAll('.fb-btn')[1].disabled,
};
});
let s0 = await fstate();
t.eq(s0.hist.length, 1, 'args.path 打开时历史只有 1 条(不得重复写入)');
t.eq(s0.backDis, true, '初始后退禁用');
t.eq(s0.fwdDis, true, '初始前进禁用');
await page.evaluate(h => { const w = window.WM.windowsForApp('finder')[0]; w.appState.navigate(h + '/Documents'); w.appState.navigate(h + '/Downloads'); }, H);
let s1 = await fstate();
t.eq(JSON.stringify(s1.hist), JSON.stringify([H + '/Desktop', H + '/Documents', H + '/Downloads']), '导航后历史为完整路径序列');
t.eq(s1.hi, 2, 'index 指向当前路径');
// 连续后退
await page.evaluate(() => window.WM.windowsForApp('finder')[0].appState.back());
s1 = await fstate();
t.eq(s1.path, H + '/Documents', '后退一步到 Documents');
t.eq(s1.backDis, false, '后退按钮仍可用');
t.eq(s1.fwdDis, false, '前进按钮可用');
await page.evaluate(() => window.WM.windowsForApp('finder')[0].appState.back());
s1 = await fstate();
t.eq(s1.path, H + '/Desktop', '后退两步到 Desktop');
t.eq(s1.backDis, true, '到顶后后退禁用');
// 连续前进
await page.evaluate(() => window.WM.windowsForApp('finder')[0].appState.fwd());
s1 = await fstate();
t.eq(s1.path, H + '/Documents', '前进一步到 Documents');
await page.evaluate(() => window.WM.windowsForApp('finder')[0].appState.fwd());
s1 = await fstate();
t.eq(s1.path, H + '/Downloads', '前进两步到 Downloads');
t.eq(s1.fwdDis, true, '到底后前进禁用');
t.eq(s1.hist.length, 3, '后退/前进不写历史');
// 后退后新导航删除 forward 分支
await page.evaluate(() => window.WM.windowsForApp('finder')[0].appState.back());
await page.evaluate(h => window.WM.windowsForApp('finder')[0].appState.navigate(h + '/Pictures'), H);
s1 = await fstate();
t.eq(JSON.stringify(s1.hist), JSON.stringify([H + '/Desktop', H + '/Documents', H + '/Pictures']), '新导航删除 forward 分支');
t.eq(s1.fwdDis, true, 'forward 分支删除后前进禁用');
// ============ ⑤ 单击不重建 DOM ============
await page.evaluate(h => window.WM.windowsForApp('finder')[0].appState.navigate(h + '/Desktop'), H);
await page.waitForTimeout(200);
const stable = await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
const before = w.body.querySelector('.fb-item');
const beforeName = before.querySelector('.fi-name').textContent;
before.click(); // 单击
const after = w.body.querySelector('.fb-item');
return { same: before === after, sel: before.classList.contains('sel'), name: beforeName };
});
t.ok(stable.same, '单击后列表节点保持同一引用(未重建 DOM');
t.ok(stable.sel, '单击后选中 class 生效');
// ============ ⑤ Cmd/Shift 多选 ============
const multi = await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
const items = [...w.body.querySelectorAll('.fb-item')];
const ev = (node, opts) => node.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, ...opts }));
ev(items[0], {});
ev(items[1], { metaKey: true });
const afterCmd = w.appState.selection.size;
ev(items[0], {}); // 重设锚点
ev(items[items.length - 1], { shiftKey: true });
return { afterCmd, afterShift: w.appState.selection.size, total: items.length };
});
t.eq(multi.afterCmd, 2, 'Cmd 多选 2 项');
t.eq(multi.afterShift, multi.total, 'Shift 范围选择覆盖全部');
// ============ ⑤ 真实双击 ============
const dbl = await page.evaluate(h => {
const w = window.WM.windowsForApp('finder')[0];
const folder = [...w.body.querySelectorAll('.fb-item')].find(n => n.querySelector('.fi-name').textContent === 'Sample Folder');
folder.dispatchEvent(new MouseEvent('click', { bubbles: true }));
folder.dispatchEvent(new MouseEvent('click', { bubbles: true }));
folder.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }));
return w.appState.path;
});
t.eq(dbl, H + '/Desktop/Sample Folder', '真实双击文件夹必须进入文件夹');
// 双击文本打开文本编辑
await page.evaluate(h => window.WM.windowsForApp('finder')[0].appState.navigate(h + '/Desktop'), H);
await page.waitForTimeout(150);
await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
const f = [...w.body.querySelectorAll('.fb-item')].find(n => n.querySelector('.fi-name').textContent === 'welcome.txt');
f.dispatchEvent(new MouseEvent('click', { bubbles: true }));
f.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }));
});
await page.waitForTimeout(300);
t.eq(await page.evaluate(() => window.WM.windowsForApp('textedit').length), 1, '双击文本打开文本编辑');
await page.evaluate(() => { window.WM.windowsForApp('textedit')[0] && window.WM.close(window.WM.windowsForApp('textedit')[0]); });
await page.waitForTimeout(250);
// ============ ⑤ 重命名Enter 提交)============
const ren = await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
const f = [...w.body.querySelectorAll('.fb-item')].find(n => n.querySelector('.fi-name').textContent === 'welcome.txt');
w.appState.selection = new Set([f.dataset.path]);
w.appState.startRename(f.dataset.path);
const input = w.body.querySelector('.fi-rename');
input.value = 'welcome-renamed.txt';
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
return true;
});
await page.waitForTimeout(300);
t.eq(await page.evaluate(h => window.FS.exists(h + '/Desktop/welcome-renamed.txt'), H), true, '重命名生效');
t.eq(await page.evaluate(h => window.FS.exists(h + '/Desktop/welcome.txt'), H), false, '旧名已消失');
t.eq(await page.$$eval('.modal-mask', n => n.length), 0, '重命名无残留模态框');
// ============ 复制 / 移动 ============
await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
const f = [...w.body.querySelectorAll('.fb-item')].find(n => n.querySelector('.fi-name').textContent === 'welcome-renamed.txt');
w.appState.selection = new Set([f.dataset.path]);
w.appState.copySel('copy');
w.appState.navigate('/Users/guest/Documents');
w.appState.paste();
});
await page.waitForTimeout(250);
t.eq(await page.evaluate(h => window.FS.exists(h + '/Documents/welcome-renamed.txt'), H), true, '⌘C/⌘V 复制到 Documents');
await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
const f = [...w.body.querySelectorAll('.fb-item')].find(n => n.querySelector('.fi-name').textContent === 'welcome-renamed.txt');
w.appState.selection = new Set([f.dataset.path]);
w.appState.copySel('cut');
w.appState.navigate('/Users/guest/Downloads');
w.appState.paste();
});
await page.waitForTimeout(250);
t.eq(await page.evaluate(h => window.FS.exists(h + '/Downloads/welcome-renamed.txt'), H), true, '剪切粘贴移动到 Downloads');
t.eq(await page.evaluate(h => window.FS.exists(h + '/Documents/welcome-renamed.txt'), H), false, '移动后源已消失');
// ============ 废纸篓还原 ============
await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
const f = [...w.body.querySelectorAll('.fb-item')].find(n => n.querySelector('.fi-name').textContent === 'welcome-renamed.txt');
w.appState.selection = new Set([f.dataset.path]);
w.appState.trashSelection();
});
await page.waitForTimeout(250);
t.eq(await page.evaluate(h => window.FS.exists(h + '/Downloads/welcome-renamed.txt'), H), false, '已移到废纸篓');
const trashed = await page.evaluate(() => {
const w = window.WM.windowsForApp('finder')[0];
w.appState.navigate(window.FS.TRASH);
const items = window.FS.list(window.FS.TRASH);
if (!items.length) return null;
window.FS.restore(items.find(i => i.name.includes('welcome-renamed')).name);
return true;
});
t.ok(trashed, '废纸篓中执行放回原处');
await page.waitForTimeout(250);
t.eq(await page.evaluate(h => window.FS.exists(h + '/Downloads/welcome-renamed.txt'), H), true, '还原回到原位置 Downloads');
// ============ ⑥ 桌面重绘限制 ============
// 记录桌面图标 DOM 引用Documents 写入不得重建Desktop 写入必须重建
const deskRef = await page.evaluate(() => {
const el1 = document.querySelector('#desktop-icons .desk-icon');
window.__deskEl = el1;
return !!el1;
});
t.ok(deskRef, '桌面有图标');
await page.evaluate(h => window.FS.write(h + '/Documents/internal-op.txt', 'x'), H);
await page.waitForTimeout(200);
t.eq(await page.evaluate(() => document.querySelector('#desktop-icons .desk-icon') === window.__deskEl), true, '文稿内部变化不重建桌面图标');
await page.evaluate(h => window.FS.write(h + '/Desktop/desk-op.txt', 'x'), H);
await page.waitForTimeout(200);
t.eq(await page.evaluate(() => document.querySelector('#desktop-icons .desk-icon') === window.__deskEl), false, 'Desktop 直接子项变化重建桌面图标');
await page.evaluate(h => window.FS.trash(h + '/Desktop/desk-op.txt'), H);
t.eq(env.errors.length, 0, '全程无未捕获异常: ' + env.errors.join(' | '));
},
};