189 lines
10 KiB
JavaScript
189 lines
10 KiB
JavaScript
'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(' | '));
|
||
},
|
||
};
|