Add multi-selection clipboard commands
This commit is contained in:
parent
987e289f89
commit
edca82cd5d
10 changed files with 839 additions and 36 deletions
|
|
@ -96,6 +96,14 @@ try {
|
|||
};
|
||||
const shot = async () => JSON.parse(JSON.stringify(await evaluate('laneSnapshot()'),
|
||||
(_key, value) => value?.uuid ?? value));
|
||||
const shortcut = async (key, code, modifiers = 2) => {
|
||||
const windowsVirtualKeyCode = key.toUpperCase().charCodeAt(0);
|
||||
await send('Input.dispatchKeyEvent', {type: 'rawKeyDown', key, code, modifiers,
|
||||
windowsVirtualKeyCode});
|
||||
await send('Input.dispatchKeyEvent', {type: 'keyUp', key, code, modifiers: 0,
|
||||
windowsVirtualKeyCode});
|
||||
await sleep(180);
|
||||
};
|
||||
const mainInstances = s => Object.values(s.clip.symbols.main.nodes)
|
||||
.filter(n => n.kind === 'instance');
|
||||
const laneSymbols = s => Object.values(s.clip.symbols).filter(sym => sym.display === 'lane');
|
||||
|
|
@ -109,7 +117,7 @@ try {
|
|||
assert.equal(placed.length, 1, 'new symbol places one instance');
|
||||
assert.equal(s.clip.symbols[placed[0].source.symbol].display, undefined,
|
||||
'new symbol remains ordinary');
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-track").length'), 1,
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-track:not(.palette)").length'), 1,
|
||||
'an ordinary symbol is an ordinary timeline row');
|
||||
|
||||
await clickNew('lane');
|
||||
|
|
@ -151,16 +159,17 @@ try {
|
|||
await send('Input.dispatchMouseEvent', {type: 'mouseMoved', x: drop.sx, y: drop.sy});
|
||||
await send('Input.dispatchMouseEvent', {type: 'mousePressed', x: drop.sx, y: drop.sy,
|
||||
button: 'left', buttons: 1, clickCount: 1});
|
||||
await send('Input.dispatchMouseEvent', {type: 'mouseMoved', x: drop.sx + 12, y: drop.sy,
|
||||
await send('Input.dispatchMouseEvent', {type: 'mouseMoved', x: drop.sx + 6, y: drop.sy,
|
||||
button: 'left', buttons: 1});
|
||||
await sleep(100);
|
||||
await sleep(60);
|
||||
await send('Input.dispatchMouseEvent', {type: 'mouseMoved', x: drop.sx + 18, y: drop.sy,
|
||||
button: 'left', buttons: 1});
|
||||
await sleep(140);
|
||||
await send('Input.dispatchMouseEvent', {type: 'mouseMoved', x: drop.tx, y: drop.ty,
|
||||
button: 'left', buttons: 1});
|
||||
await sleep(120);
|
||||
await sleep(300);
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-label.ghost").length'), 0,
|
||||
'pool drop does not preview an invented lane');
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-cel.ghost").length'), 1,
|
||||
'pool drop previews inside the existing lane');
|
||||
await send('Input.dispatchMouseEvent', {type: 'mouseReleased', x: drop.tx, y: drop.ty,
|
||||
button: 'left', buttons: 0, clickCount: 1});
|
||||
await sleep(300);
|
||||
|
|
@ -196,8 +205,51 @@ try {
|
|||
s = await shot();
|
||||
assert.deepEqual(laneSymbols(s).map(x => Object.keys(x.nodes).length).sort(), [0, 1],
|
||||
'a clip body moves from one explicit lane to the other');
|
||||
|
||||
// Start a clean local document for clipboard and timeline multi-selection.
|
||||
await evaluate(`re_frame.core.dispatch_sync(cljs.core.vector(
|
||||
cljs.core.keyword('arthur.events.project/new'))) `);
|
||||
await sleep(250);
|
||||
await clickNew('inside');
|
||||
const clipboardBaseline = (await shot()).clip;
|
||||
await shortcut('d', 'KeyD');
|
||||
assert.equal(mainInstances(await shot()).length, 2, 'duplicate copies the selected instance');
|
||||
await shortcut('z', 'KeyZ');
|
||||
assert.equal(mainInstances(await shot()).length, 1, 'one undo removes the whole duplicate');
|
||||
await shortcut('z', 'KeyZ', 10);
|
||||
assert.equal(mainInstances(await shot()).length, 2, 'one redo restores the duplicate');
|
||||
assert(await evaluate(`(() => {
|
||||
const rows = [...document.querySelectorAll('.tl-label')].filter(x => x.draggable);
|
||||
if (rows.length < 2) return false;
|
||||
rows[0].click();
|
||||
rows[1].dispatchEvent(new MouseEvent('click', {bubbles: true, shiftKey: true}));
|
||||
return true;
|
||||
})()`), 'two timeline rows are available for multi-selection');
|
||||
await sleep(200);
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-label.on").length'), 2,
|
||||
'timeline Shift-click mirrors the two-member selection');
|
||||
assert(await evaluate(`(() => {
|
||||
const row = [...document.querySelectorAll('.tl-label')].find(x => x.draggable);
|
||||
if (!row) return false; row.click(); return true;
|
||||
})()`), 'a timeline row can select the redone instance');
|
||||
await sleep(120);
|
||||
await shortcut('c', 'KeyC');
|
||||
await shortcut('x', 'KeyX');
|
||||
assert.equal(mainInstances(await shot()).length, 1, 'cut removes the selection');
|
||||
await shortcut('z', 'KeyZ');
|
||||
assert.equal(mainInstances(await shot()).length, 2, 'one undo restores the cut');
|
||||
await evaluate(`re_frame.core.dispatch_sync(cljs.core.vector(
|
||||
cljs.core.keyword('arthur.events.ui/aim'), null))`);
|
||||
await sleep(80);
|
||||
await shortcut('v', 'KeyV');
|
||||
assert.equal(mainInstances(await shot()).length, 3, 'paste uses the copied snapshot');
|
||||
await shortcut('z', 'KeyZ');
|
||||
await shortcut('z', 'KeyZ');
|
||||
assert.equal(mainInstances(await shot()).length, 1, 'clipboard edits undo independently');
|
||||
assert.deepEqual((await shot()).clip, clipboardBaseline,
|
||||
'clipboard exercises undo back to the byte-for-byte document');
|
||||
assert.equal(errors.length, 0, JSON.stringify(errors));
|
||||
console.log('PASS: symbols are ordinary; explicit lanes rename, accept drops, and exchange clips');
|
||||
console.log('PASS: lanes, mirrored selection, and atomic clipboard commands');
|
||||
} finally {
|
||||
if (ws?.readyState === WebSocket.OPEN) ws.close();
|
||||
chrome.kill('SIGTERM');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue