Files
feuerwerk/tools/e2e/44-documents.mjs
T

166 lines
7.5 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.
import { clickDocumentTab } from './lib/scaffold.mjs';
/**
* Tabs as open documents (#171): a double-click in the asset panel opens a tab per asset, several of
* one kind at once; the tab keeps its document whatever is selected, and its zoom when another tab
* was on screen; the close cross and a middle click close it, a rename renames it and a deleted
* asset takes its tab along.
*/
export async function documents(t) {
const { check, json, evaluate, mutate, click, doubleClick, wheel, shot, showTab } = t;
await showTab('scene');
for (const doc of (await json('__gm.documents()')).docs)
await evaluate(`__gm.closeDocument(${JSON.stringify(doc)})`);
// Short names of their own: an earlier step leaves them too long for a tab to show whole.
const [a, b] = await json(`(() => {
const two = __gm.state.project.assets.filter((x) => x.type === 'texture' && x.width > 0).slice(0, 2);
const out = two.map((x, i) => ({ id: x.id, name: 'Sheet ' + 'AB'[i], was: x.name }));
two.forEach((x, i) => (x.name = out[i].name));
__gm.state.preview();
return out;
})()`);
const behavior = await json(
`(() => { const x = __gm.state.project.assets.find((x) => x.type === 'behavior' && x.graph.nodes.length > 0); return { id: x.id, node: x.graph.nodes[0].id }; })()`,
);
const docs = () => json('__gm.documents()');
const current = () => json('__gm.texture.current()');
const place = async (doc) => (await json('__gm.tabRow()')).shown.find((p) => p.tab === doc);
// 44a. Two textures, two tabs; each names its kind in front of the name.
for (const texture of [a, b]) {
const row = await json(`__gm.assetRow(${JSON.stringify(texture.id)})`);
await doubleClick(row.x, row.y);
}
const opened = await docs();
check(
'double-clicking two textures opens a tab for each, the second one showing',
JSON.stringify(opened.docs) === JSON.stringify([`asset:${a.id}`, `asset:${b.id}`]) &&
opened.active === `asset:${b.id}` &&
(await current()) === b.id,
JSON.stringify({ opened, current: await current() }),
);
const placeA = await place(`asset:${a.id}`);
const inTab = (p) => p.x > placeA.x && p.x < placeA.right && p.y > placeA.y && p.y < placeA.y + placeA.h;
const nameHits = await json(`__gm.locateAll(${JSON.stringify(a.name)})`);
const kindWords = (await json(`__gm.locateAll('Texture')`)).filter(inTab);
// The icon's 16 px in front of the name, read against the tab's own background at its left edge.
const iconInk = await json(`(() => {
const x0 = ${placeA.x} + 6, y = ${placeA.y + placeA.h / 2};
const background = __gm.pixelAt(${placeA.x} + 1, y);
let n = 0;
for (let dy = -7; dy <= 7; dy++)
for (let dx = 0; dx < 16; dx++) if (__gm.pixelAt(x0 + dx, y + dy) !== background) n++;
return n;
})()`);
check(
'a tab shows the icon of its kind, then the asset’s name – the kind no longer as a word',
placeA?.kind === 'Texture' && nameHits.some(inTab) && kindWords.length === 0 && iconInk >= 20,
JSON.stringify({ placeA, nameHits, kindWords, iconInk }),
);
// 44b. Selecting the other texture is a look at the inspector, not a change of document.
await evaluate(`__gm.state.select({ kind: 'asset', assetId: ${JSON.stringify(a.id)} })`);
check('selecting the other texture leaves the tab on its own', (await current()) === b.id, await current());
// 44c. Zoom and scroll stay with the document.
const view = () =>
json(
`(() => { const o = __gm.texture.pointAt(0, 0); const p = __gm.texture.pointAt(10, 0); return { x: o.x, y: o.y, scale: (p.x - o.x) / 10 }; })()`,
);
const fitted = await view();
const rect = await json('__gm.texture.rect()');
await wheel(rect.x + rect.w / 2, rect.y + rect.h / 2, -300);
const zoomed = await view();
check('the wheel zooms the texture', zoomed.scale > fitted.scale * 1.2, JSON.stringify({ fitted, zoomed }));
await clickDocumentTab(t, `asset:${a.id}`);
check('its neighbour’s tab shows the other texture', (await current()) === a.id, await current());
await clickDocumentTab(t, `asset:${b.id}`);
const back = await view();
check(
'coming back, the texture is zoomed and scrolled as it was left',
(await current()) === b.id &&
Math.abs(back.scale - zoomed.scale) < 1e-6 &&
Math.abs(back.x - zoomed.x) < 0.5 &&
Math.abs(back.y - zoomed.y) < 0.5,
JSON.stringify({ zoomed, back }),
);
// 44d. A rename renames the tab.
await mutate(
`__gm.state.project.assets.find((x) => x.id === ${JSON.stringify(b.id)}).name = 'Renamed sheet'; __gm.state.preview();`,
);
const renamedAt = await place(`asset:${b.id}`);
const renamedHits = await json(`__gm.locateAll('Renamed sheet')`);
check(
'renaming the asset renames its tab',
renamedHits.some(
(p) =>
p.x > renamedAt.x && p.x < renamedAt.right && p.y > renamedAt.y && p.y < renamedAt.y + renamedAt.h,
),
JSON.stringify({ renamedAt, renamedHits }),
);
await mutate(
`__gm.state.project.assets.find((x) => x.id === ${JSON.stringify(b.id)}).name = ${JSON.stringify(b.name)}; __gm.state.preview();`,
);
// 44e. A node lives only in its graph: selecting one brings that graph up, in a tab of its own.
await evaluate(
`__gm.state.select({ kind: 'node', assetId: ${JSON.stringify(behavior.id)}, nodeId: ${JSON.stringify(behavior.node)} })`,
);
const withGraph = await docs();
check(
'selecting a node opens its behavior next to the texture showing',
withGraph.active === `asset:${behavior.id}` &&
JSON.stringify(withGraph.docs) ===
JSON.stringify([`asset:${a.id}`, `asset:${b.id}`, `asset:${behavior.id}`]) &&
(await json('__gm.tab()')) === 'graph',
JSON.stringify(withGraph),
);
await shot('44-documents');
// 44f. The close cross closes the tab on screen; its left neighbour comes up.
const graphTab = await place(`asset:${behavior.id}`);
await click(graphTab.close.x, graphTab.close.y);
const afterClose = await docs();
check(
'the close cross closes the tab, and the one left of it shows',
!afterClose.docs.includes(`asset:${behavior.id}`) &&
afterClose.active === `asset:${b.id}` &&
(await current()) === b.id,
JSON.stringify(afterClose),
);
// 44g. A middle click closes a tab that is not showing, and leaves the one that is.
const tabA = await place(`asset:${a.id}`);
await click((tabA.x + tabA.close.x) / 2, tabA.y + tabA.h / 2, 'middle');
const afterMiddle = await docs();
check(
'a middle click closes a tab in the background',
JSON.stringify(afterMiddle.docs) === JSON.stringify([`asset:${b.id}`]) &&
afterMiddle.active === `asset:${b.id}`,
JSON.stringify(afterMiddle),
);
// 44h. A deleted asset takes its tab along, and the last one gone leaves the Scene tab showing.
await mutate(`
const p = __gm.state.project;
window.__deletedSheet = p.assets.find((x) => x.id === ${JSON.stringify(b.id)});
p.assets = p.assets.filter((x) => x.id !== ${JSON.stringify(b.id)});
__gm.state.preview();
`);
const afterDelete = await docs();
check(
'deleting the asset closes its tab and goes back to the scene',
afterDelete.docs.length === 0 && (await json('__gm.tab()')) === 'scene',
JSON.stringify({ afterDelete, tab: await json('__gm.tab()') }),
);
await mutate(
`__gm.state.project.assets.push(window.__deletedSheet); delete window.__deletedSheet; __gm.state.preview();`,
);
await mutate(`
for (const { id, was } of ${JSON.stringify([a, b])}) __gm.state.project.assets.find((x) => x.id === id).name = was;
__gm.state.preview();
`);
}