spreadsheet ui
This commit is contained in:
parent
7e34d0c704
commit
e369ad9462
9 changed files with 355 additions and 60 deletions
|
|
@ -75,7 +75,7 @@ try {
|
|||
// its `aria-label`, which is also what a screen reader is told it is.
|
||||
// Two bars carry commands: the location bar says where an edit lands and holds
|
||||
// what creates things there, the transport strip holds what acts on a cel.
|
||||
const bars = ['.loc', '.pane.time .pane-head'];
|
||||
const bars = ['.loc', '.pane.time .pane-head', '.section'];
|
||||
const within = (suffix) => bars.map((b) => `${b} ${suffix}`).join(', ');
|
||||
const named = label =>
|
||||
`(b => b.textContent.trim() === ${JSON.stringify(label)}` +
|
||||
|
|
@ -113,7 +113,10 @@ try {
|
|||
await sleep(180);
|
||||
await shut();
|
||||
};
|
||||
const shot = async () => (await evaluate('laneSnapshot()'));
|
||||
// UUIDs expose a mutable hash cache through clj->js; compare their identity,
|
||||
// not that implementation detail, when asserting exact undo restoration.
|
||||
const shot = async () => JSON.parse(JSON.stringify(await evaluate('laneSnapshot()'),
|
||||
(_key, value) => value?.uuid ?? value));
|
||||
const instances = s => Object.values(s.clip.symbols.main.nodes).filter(n => n.kind === 'instance')
|
||||
.sort((a, b) => a.time.at - b.time.at);
|
||||
await click('lane');
|
||||
|
|
@ -285,7 +288,7 @@ try {
|
|||
// only appears once a sheet has more than one lane.
|
||||
await click('timeline');
|
||||
assert.equal(await evaluate('document.querySelectorAll(".tl-cel").length'), 5);
|
||||
await click('+ lane');
|
||||
await click('lane');
|
||||
await click('cel sheet');
|
||||
assert.equal(await evaluate('document.querySelectorAll(".cs-head:not(.cs-frame)").length'), 2);
|
||||
await evaluate(`([...document.querySelectorAll('.cs-cell')].slice(0, 2)
|
||||
|
|
@ -301,6 +304,76 @@ try {
|
|||
'clicking a gap selects that column before overwrite');
|
||||
assert.equal(s.history.done.length, before.history.done.length + 12,
|
||||
'correction, lane creation, and overwrite are separate undo steps');
|
||||
// Real pointer capture and keyboard events exercise the spreadsheet surface.
|
||||
const cellAt = async (c, f) => evaluate(`(() => {
|
||||
const el = document.querySelector('[data-cs-col="${c}"][data-cs-frame="${f}"]');
|
||||
el.scrollIntoView({block: 'center'});
|
||||
const r = el.getBoundingClientRect();
|
||||
return {x: r.x + r.width / 2, y: r.y + r.height / 2};
|
||||
})()`);
|
||||
const mouse = (type, p) => send('Input.dispatchMouseEvent', {
|
||||
type, ...p, button: 'left', buttons: type === 'mouseReleased' ? 0 : 1, clickCount: 1,
|
||||
});
|
||||
const key = async (key, extra = {}) => {
|
||||
await send('Input.dispatchKeyEvent', {type: 'keyDown', key, ...extra});
|
||||
await send('Input.dispatchKeyEvent', {type: 'keyUp', key, ...extra});
|
||||
await sleep(120);
|
||||
};
|
||||
const start = await cellAt(0, 0);
|
||||
await mouse('mousePressed', start);
|
||||
await mouse('mouseMoved', await cellAt(1, 2));
|
||||
await mouse('mouseReleased', await cellAt(1, 2));
|
||||
await sleep(150);
|
||||
assert.equal(await evaluate('document.querySelectorAll(".cs-cell.selected").length'), 6);
|
||||
await key('c', {modifiers: 2});
|
||||
const dest = await cellAt(0, 10);
|
||||
await mouse('mousePressed', dest);
|
||||
await mouse('mouseReleased', dest);
|
||||
await sleep(120);
|
||||
const beforePaste = await shot();
|
||||
await key('v', {modifiers: 2});
|
||||
const pasted = await shot();
|
||||
assert.equal(pasted.history.done.length, beforePaste.history.done.length + 1,
|
||||
'multi-lane paste is one undo step');
|
||||
assert.deepEqual(Object.keys(pasted.clip.symbols), Object.keys(beforePaste.clip.symbols),
|
||||
'copy reuses the drawings');
|
||||
await key('z', {modifiers: 2});
|
||||
assert.deepEqual((await shot()).clip, beforePaste.clip, 'undo restores the entire rectangle');
|
||||
await key('ArrowDown', {modifiers: 8});
|
||||
assert.equal(await evaluate('document.querySelectorAll(".cs-cell.selected").length'), 2);
|
||||
const first = await cellAt(0, 0);
|
||||
await mouse('mousePressed', first);
|
||||
await mouse('mouseReleased', first);
|
||||
await sleep(150);
|
||||
const handle = await evaluate(`(() => {
|
||||
const r = document.querySelector('.cs-fill-handle').getBoundingClientRect();
|
||||
return {x: r.x + r.width / 2, y: r.y + r.height / 2};
|
||||
})()`);
|
||||
const beforeHold = await shot();
|
||||
await mouse('mousePressed', handle);
|
||||
await mouse('mouseMoved', await cellAt(0, 4));
|
||||
await mouse('mouseReleased', await cellAt(0, 4));
|
||||
await sleep(150);
|
||||
const afterHold = await shot();
|
||||
assert.equal(afterHold.history.done.length, beforeHold.history.done.length + 1,
|
||||
'a hold drag commits once');
|
||||
assert.equal(instances(afterHold).filter(n => {
|
||||
const old = instances(beforeHold).find(o => o.id === n.id);
|
||||
return old && n.span[1] !== old.span[1] && n.time.at + n.span[1] === 5;
|
||||
}).length, 1, 'the dragged hold ends at the previewed boundary');
|
||||
await key('z', {modifiers: 2});
|
||||
assert.deepEqual((await shot()).clip, beforeHold.clip);
|
||||
await key('Delete');
|
||||
const cleared = await shot();
|
||||
assert.equal(cleared.history.done.length, beforeHold.history.done.length + 1,
|
||||
'Delete clears selected frames in one step, without the global node-delete handler');
|
||||
assert.equal(cleared.clip.symbols.main.frames, beforeHold.clip.symbols.main.frames);
|
||||
await key('z', {modifiers: 2});
|
||||
assert.deepEqual((await shot()).clip, beforeHold.clip);
|
||||
await key('x', {modifiers: 2});
|
||||
assert.equal((await shot()).history.done.length, beforeHold.history.done.length + 1);
|
||||
await key('z', {modifiers: 2});
|
||||
assert.deepEqual((await shot()).clip, beforeHold.clip, 'cut is independently undoable');
|
||||
assert.equal(errors.length, 0, JSON.stringify(errors));
|
||||
console.log('PASS: lane commands and corrections agree from timeline and cel sheet; no server writes');
|
||||
} finally {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue