Make e2e 40, 46, 51 and 52 pass in the full run again
The suite job failed on every push since 6015869. Each module passed on
its own, but the full run showed two kinds of fault.
- e2e 40 still expected the object menu and the empty-cell menu from
before #240 and #241. It now expects Copy, Paste, Group... and Remove
from group, and Paste alone on an empty cell.
- e2e 46g read the outline of the first collider in the scene. In the
full run that is a leftover of module 43, off screen, so it read as
hidden before any switch. The check now places a collider of its own
next to its tilemap and removes both at the end.
- e2e 51 copied the seed's red and blue crate. In the full run module 09
has packed both into a container. The module now places two crates
with colliders of its own and removes them at the end.
- e2e 52 compared whole row lists against the seed scene. In the full
run that scene holds about 50 more rows. The module now works in a
scene of its own, which it removes at the end. It had never run in CI
because 51 died before it.
The check in e2e 41 that failed once (task 347, a dragged body 15 px
off in its first sample) is left alone. It failed in 1 of the last 60
logs, while a local e2e run loaded the same host.
Tests:
- full e2e run in the game-maker-ci image with GPU, as the runner does:
ALL PASSED, 1452 checks, E2E EXIT 0 (the README sum is now measured)
- GM_STEPS=1,46,51,52 on the host: ALL PASSED, 51 checks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3abb6894a7
commit
38f009e035
5 files changed
+119
-58
No files matched your search
@@ -829,11 +829,15 @@ export async function tilemap(t) {
|
||||
const OBJECT_MENU = [
|
||||
'Select asset',
|
||||
'Open asset',
|
||||
'Copy',
|
||||
'Paste',
|
||||
'Add collider…',
|
||||
'Add rigidbody…',
|
||||
'Create container',
|
||||
'Unpack container',
|
||||
'Unparent (move to top level)',
|
||||
'Group…',
|
||||
'Remove from group',
|
||||
'Remove from scene',
|
||||
];
|
||||
const onHidden = await cellPoint(1, 1);
|
||||
@@ -869,15 +873,16 @@ export async function tilemap(t) {
|
||||
JSON.stringify({ topRows, topPicked }),
|
||||
);
|
||||
|
||||
// Empty space opens no menu and leaves the selection alone.
|
||||
// Empty space opens only the menu to paste into the scene (#240) and leaves the selection alone.
|
||||
const emptyPoint = await cellPoint(COLUMNS - 1, ROWS - 1);
|
||||
await click(emptyPoint.x, emptyPoint.y, 'right');
|
||||
await settle();
|
||||
const emptyMenu = await json('__gm.menuRows()');
|
||||
const emptyPicked = await selected();
|
||||
check(
|
||||
'a right-click on an empty cell opens no menu and keeps the selection',
|
||||
emptyMenu === null && JSON.stringify(emptyPicked) === JSON.stringify([MAP_OBJECT]),
|
||||
'a right-click on an empty cell opens only Paste and keeps the selection',
|
||||
JSON.stringify(emptyMenu) === JSON.stringify(['Paste']) &&
|
||||
JSON.stringify(emptyPicked) === JSON.stringify([MAP_OBJECT]),
|
||||
JSON.stringify({ emptyMenu, emptyPicked }),
|
||||
);
|
||||
await press('Escape');
|
||||
|
||||
@@ -181,11 +181,13 @@ export async function topbar(t) {
|
||||
})()`);
|
||||
const ids = {
|
||||
sprite: await firstOf('sprite'),
|
||||
collider: await firstOf('collider'),
|
||||
collider: 'vis-col-obj',
|
||||
behavior: await firstOf('behavior'),
|
||||
tilemap: 'vis-map-obj',
|
||||
};
|
||||
// A map of its own with one solid row, laid over the sprite so it is on screen: the seed has none.
|
||||
// A collider of its own there too: in the full run the first one in the scene is a leftover of an
|
||||
// earlier module, off screen, and its outline reads as hidden before any switch is touched.
|
||||
await t.mutate(`
|
||||
const scene = __gm.state.activeScene;
|
||||
const assets = __gm.state.project.assets;
|
||||
@@ -197,6 +199,9 @@ export async function topbar(t) {
|
||||
Object.assign(o, { id: 'vis-map-obj', assetId: 'vis-map', name: 'Vis map', position: { ...at },
|
||||
parentId: '', overrides: {}, instanceEdits: {} });
|
||||
scene.objects.push(o);
|
||||
assets.push({ id: 'vis-col', type: 'collider', name: 'Vis collider',
|
||||
props: { shape: 'box', width: 40, height: 40, radius: 20, isTrigger: false } });
|
||||
scene.objects.push(Object.assign(structuredClone(o), { id: 'vis-col-obj', assetId: 'vis-col', name: 'Vis collider' }));
|
||||
__gm.state.select(null)`);
|
||||
const undoBefore = await json('__gm.state.commands.canUndo');
|
||||
await clickToolbar('visibility');
|
||||
@@ -295,8 +300,8 @@ export async function topbar(t) {
|
||||
await press('Escape');
|
||||
await t.mutate(`
|
||||
const scene = __gm.state.activeScene;
|
||||
scene.objects.splice(scene.objects.findIndex((o) => o.id === 'vis-map-obj'), 1);
|
||||
scene.objects = scene.objects.filter((o) => o.id !== 'vis-map-obj' && o.id !== 'vis-col-obj');
|
||||
const assets = __gm.state.project.assets;
|
||||
for (const id of ['vis-map', 'vis-wall']) assets.splice(assets.findIndex((a) => a.id === id), 1)`);
|
||||
for (const id of ['vis-map', 'vis-wall', 'vis-col']) assets.splice(assets.findIndex((a) => a.id === id), 1)`);
|
||||
await t.saved();
|
||||
}
|
||||
@@ -18,10 +18,28 @@ export async function clipboard(t) {
|
||||
await press('Escape');
|
||||
await showTab('scene');
|
||||
await evaluate('__gm.state.clipboard = null');
|
||||
// Two crates of its own, each with a collider, where the seed has its red and blue crate. In the
|
||||
// full run those two are long packed into a container by module 09, so they are no fixture here.
|
||||
const BLUE = 'clip-blue';
|
||||
const RED = 'clip-red';
|
||||
await t.mutate(`
|
||||
const scene = __gm.state.activeScene;
|
||||
const put = (id, assetId, parentId, x, y, name) => {
|
||||
const o = structuredClone(scene.objects[0]);
|
||||
Object.assign(o, { id, assetId, parentId, name, position: { x, y, z: 0 }, overrides: {}, instanceEdits: {} });
|
||||
delete o.groupId;
|
||||
if ('enabled' in o) o.enabled = true;
|
||||
scene.objects.push(o);
|
||||
};
|
||||
put(${JSON.stringify(BLUE)}, 'crate', '', 120, -80, 'Blue crate');
|
||||
put(${JSON.stringify(BLUE)} + '-col', 'collider', ${JSON.stringify(BLUE)}, 0, 0, 'Collider');
|
||||
put(${JSON.stringify(RED)}, 'crate', '', -150, 0, 'Red crate');
|
||||
put(${JSON.stringify(RED)} + '-col', 'collider', ${JSON.stringify(RED)}, 0, 0, 'Collider');
|
||||
__gm.state.select(null)`);
|
||||
|
||||
// 51a. Ctrl+C on the blue crate, Ctrl+V with the pointer over empty space: the crate and its
|
||||
// collider come back as new objects, the crate standing right under the pointer.
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o2', objectIds: ['o2'] });
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: BLUE, objectIds: [BLUE] });
|
||||
await press('c', { ctrl: true });
|
||||
const spot = await json('__gm.emptySpot()');
|
||||
const before = await stored();
|
||||
@@ -51,16 +69,16 @@ export async function clipboard(t) {
|
||||
|
||||
// 51b. With the pointer over the hierarchy the copy has nowhere to be aimed: it lands one grid
|
||||
// step down and right of the original, beside it rather than inside it.
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o2', objectIds: ['o2'] });
|
||||
await moveTo(await json('__gm.hierarchyRow("o2")'));
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: BLUE, objectIds: [BLUE] });
|
||||
await moveTo(await json(`__gm.hierarchyRow(${JSON.stringify(BLUE)})`));
|
||||
await press('v', { ctrl: true });
|
||||
const besideId = (await selected()).objectId;
|
||||
const beside = (await stored()).find((o) => o.id === besideId);
|
||||
const original = (await stored()).find((o) => o.id === 'o2');
|
||||
const original = (await stored()).find((o) => o.id === BLUE);
|
||||
check(
|
||||
'without the pointer on the view the copy lands a grid step beside the original',
|
||||
beside !== undefined &&
|
||||
beside.id !== 'o2' &&
|
||||
beside.id !== BLUE &&
|
||||
beside.parentId === original.parentId &&
|
||||
beside.position.x === original.position.x + 100 &&
|
||||
beside.position.y === original.position.y + 100,
|
||||
@@ -70,7 +88,7 @@ export async function clipboard(t) {
|
||||
|
||||
// 51c. The object menu: right-click the red crate, "Paste" puts the copy inside what was hit –
|
||||
// the crate or the collider drawn over it, whichever the pointer picks.
|
||||
const red = await json('__gm.objectScreen("o1")');
|
||||
const red = await json(`__gm.objectScreen(${JSON.stringify(RED)})`);
|
||||
await click(red.x, red.y, 'right');
|
||||
const target = (await selected()).objectId;
|
||||
const rows = (await json('__gm.menuRows()')) ?? [];
|
||||
@@ -81,7 +99,7 @@ export async function clipboard(t) {
|
||||
'the object menu offers Copy and Paste, and Paste puts the copy into the object clicked',
|
||||
rows.includes('Copy') &&
|
||||
rows.includes('Paste') &&
|
||||
['o1', 'oc1'].includes(target) &&
|
||||
[RED, `${RED}-col`].includes(target) &&
|
||||
inside?.assetId === 'crate' &&
|
||||
inside.parentId === target,
|
||||
JSON.stringify({ target, rows, inside }),
|
||||
@@ -128,4 +146,8 @@ export async function clipboard(t) {
|
||||
check('the graph is back as it was', (await nodes()).length === count);
|
||||
await evaluate('__gm.state.clipboard = null');
|
||||
await showTab('scene');
|
||||
await t.mutate(`
|
||||
const scene = __gm.state.activeScene;
|
||||
scene.objects = scene.objects.filter((o) => !o.id.startsWith('clip-'));`);
|
||||
await t.saved();
|
||||
}
|
||||
@@ -2,13 +2,37 @@ import { same } from '../lib/harness.mjs';
|
||||
|
||||
/**
|
||||
* Groups in the hierarchy (#241), made, opened, filled, renamed and dissolved with the real menus,
|
||||
* clicks, keys and a real drag. The fixture's top level is o1, o2, o3, o5; the order rules and
|
||||
* drops are unit-tested, this checks that the panel and the dialog do what those rules say.
|
||||
* Everything is taken back by undo, so the fixture leaves as it came.
|
||||
* clicks, keys and a real drag. The order rules and drops are unit-tested, this checks that the
|
||||
* panel and the dialog do what those rules say.
|
||||
* It works in a scene of its own, whose top level is g1, g2 (with the collider gc2), g3 and g5: the
|
||||
* checks compare whole lists of rows, and in the full run the fixture's scene holds whatever the
|
||||
* modules before left there. The scene is removed at the end.
|
||||
*/
|
||||
export async function hierarchyGroups(t) {
|
||||
const { check, json, evaluate, click, clickText, drag, press, typeText, select, showTab, undo, shot } = t;
|
||||
|
||||
const SCENE = 'groups-52';
|
||||
const home = await json('__gm.state.project.activeSceneId');
|
||||
await t.mutate(`
|
||||
const p = __gm.state.project;
|
||||
const base = __gm.state.activeScene.objects[0];
|
||||
const make = (id, assetId, parentId, x, name) => {
|
||||
const o = structuredClone(base);
|
||||
Object.assign(o, { id, assetId, parentId, name, position: { x, y: 0, z: 0 }, overrides: {}, instanceEdits: {} });
|
||||
delete o.groupId;
|
||||
if ('enabled' in o) o.enabled = true;
|
||||
return o;
|
||||
};
|
||||
p.assets.push({ id: ${JSON.stringify(SCENE)}, type: 'scene', name: 'Groups 52', props: { backgroundColor: 0x22252c },
|
||||
objects: [
|
||||
make('g1', 'crate', '', -200, 'Crate 1'),
|
||||
make('g2', 'crate', '', 0, 'Crate 2'),
|
||||
make('gc2', 'collider', 'g2', 0, 'Collider'),
|
||||
make('g3', 'crate', '', 200, 'Crate 3'),
|
||||
make('g5', 'mover', '', 300, 'Mover'),
|
||||
] });
|
||||
p.activeSceneId = ${JSON.stringify(SCENE)}`);
|
||||
|
||||
const rowIds = () => json('__gm.hierarchyOrder()');
|
||||
const scene = () => json('__gm.state.activeScene');
|
||||
const selected = async () => (await json('__gm.state.selection'))?.objectIds ?? [];
|
||||
@@ -23,8 +47,8 @@ export async function hierarchyGroups(t) {
|
||||
await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()');
|
||||
|
||||
// 52a. A child cannot go into a group: the entry is there, greyed out.
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'oc2', objectIds: ['oc2'] });
|
||||
const child = await rowAt('oc2');
|
||||
await select({ kind: 'object', holderId: SCENE, objectId: 'gc2', objectIds: ['gc2'] });
|
||||
const child = await rowAt('gc2');
|
||||
await click(child.x, child.y, 'right');
|
||||
const childEntry = await menuRow('Group…');
|
||||
check('Group… is greyed out for a child', childEntry.enabled === false, JSON.stringify(childEntry));
|
||||
@@ -32,8 +56,8 @@ export async function hierarchyGroups(t) {
|
||||
await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()');
|
||||
|
||||
// 52b. Two top-level objects, the row menu's "Group 2 objects…", a name typed into the dialog.
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o1', objectIds: ['o1', 'o3'] });
|
||||
const first = await rowAt('o1');
|
||||
await select({ kind: 'object', holderId: SCENE, objectId: 'g1', objectIds: ['g1', 'g3'] });
|
||||
const first = await rowAt('g1');
|
||||
await click(first.x, first.y, 'right');
|
||||
const entry = await menuRow('Group 2 objects…');
|
||||
await clickText('Group 2 objects…');
|
||||
@@ -53,13 +77,13 @@ export async function hierarchyGroups(t) {
|
||||
made.objects
|
||||
.filter((o) => o.groupId === group.id)
|
||||
.map((o) => o.id)
|
||||
.join() === 'o1,o3',
|
||||
.join() === 'g1,g3',
|
||||
JSON.stringify({ entry, dialogOpened, groups: made.groups }),
|
||||
);
|
||||
// o3 is listed with o1 now; with both members selected the group stays folded.
|
||||
// g3 is listed with g1 now; with both members selected the group stays folded.
|
||||
check(
|
||||
'the group row stands where o1 stood, folded',
|
||||
same(await rowIds(), [row, 'o2', 'o5']),
|
||||
'the group row stands where g1 stood, folded',
|
||||
same(await rowIds(), [row, 'g2', 'g5']),
|
||||
JSON.stringify(await rowIds()),
|
||||
);
|
||||
|
||||
@@ -69,26 +93,26 @@ export async function hierarchyGroups(t) {
|
||||
await click(groupRow.x, groupRow.y);
|
||||
check(
|
||||
'clicking the group row selects all its members and leaves it folded',
|
||||
same(await selected(), ['o1', 'o3']) && same(await rowIds(), [row, 'o2', 'o5']),
|
||||
same(await selected(), ['g1', 'g3']) && same(await rowIds(), [row, 'g2', 'g5']),
|
||||
JSON.stringify({ selected: await selected(), rows: await rowIds() }),
|
||||
);
|
||||
const chevron = await json(`__gm.hierarchyChevron(${JSON.stringify(row)})`);
|
||||
await click(chevron.x, chevron.y);
|
||||
check(
|
||||
'its chevron lists the members below it',
|
||||
same(await rowIds(), [row, 'o1', 'o3', 'o2', 'o5']),
|
||||
same(await rowIds(), [row, 'g1', 'g3', 'g2', 'g5']),
|
||||
JSON.stringify(await rowIds()),
|
||||
);
|
||||
await shot('52-group-open');
|
||||
|
||||
// 52d. Dragging o5 onto the group row puts it in behind the last member; undo takes it out.
|
||||
const from = await rowAt('o5');
|
||||
// 52d. Dragging g5 onto the group row puts it in behind the last member; undo takes it out.
|
||||
const from = await rowAt('g5');
|
||||
const onto = await rowAt(row);
|
||||
await drag(from.x, from.y, onto.x, onto.y);
|
||||
check(
|
||||
'dragging an object onto the group row adds it to the group',
|
||||
same(await rowIds(), [row, 'o1', 'o3', 'o5', 'o2']) &&
|
||||
(await scene()).objects.find((o) => o.id === 'o5')?.groupId === group?.id,
|
||||
same(await rowIds(), [row, 'g1', 'g3', 'g5', 'g2']) &&
|
||||
(await scene()).objects.find((o) => o.id === 'g5')?.groupId === group?.id,
|
||||
JSON.stringify(await rowIds()),
|
||||
);
|
||||
await undo();
|
||||
@@ -114,7 +138,7 @@ export async function hierarchyGroups(t) {
|
||||
'Ungroup dissolves the group and keeps its members',
|
||||
ungrouped.groups === undefined &&
|
||||
!ungrouped.objects.some((o) => o.groupId) &&
|
||||
same(await rowIds(), ['o1', 'o2', 'o3', 'o5']),
|
||||
same(await rowIds(), ['g1', 'g2', 'g3', 'g5']),
|
||||
JSON.stringify({ groups: ungrouped.groups, rows: await rowIds() }),
|
||||
);
|
||||
|
||||
@@ -132,48 +156,48 @@ export async function hierarchyGroups(t) {
|
||||
|
||||
// 52g. A plain click on a row that is already part of a multi-selection keeps only that row –
|
||||
// the other member as well as the primary one.
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o1', objectIds: ['o1', 'o2', 'o3'] });
|
||||
const other = await rowAt('o2');
|
||||
await select({ kind: 'object', holderId: SCENE, objectId: 'g1', objectIds: ['g1', 'g2', 'g3'] });
|
||||
const other = await rowAt('g2');
|
||||
await click(other.x, other.y);
|
||||
const narrowedToOther = await selected();
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o1', objectIds: ['o1', 'o2', 'o3'] });
|
||||
const primary = await rowAt('o1');
|
||||
await select({ kind: 'object', holderId: SCENE, objectId: 'g1', objectIds: ['g1', 'g2', 'g3'] });
|
||||
const primary = await rowAt('g1');
|
||||
await click(primary.x, primary.y);
|
||||
const narrowedToPrimary = await selected();
|
||||
check(
|
||||
'clicking a selected row narrows the selection to that row',
|
||||
same(narrowedToOther, ['o2']) && same(narrowedToPrimary, ['o1']),
|
||||
same(narrowedToOther, ['g2']) && same(narrowedToPrimary, ['g1']),
|
||||
JSON.stringify({ narrowedToOther, narrowedToPrimary }),
|
||||
);
|
||||
await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()');
|
||||
|
||||
// 52h. Shift-click takes every row from the last plain click to the clicked one; a second
|
||||
// Shift-click redraws the range from the same anchor, and it runs upwards just as well.
|
||||
// It starts on o2: 52g's last click hit o1, and a second one there would count as a double click.
|
||||
// It starts on g2: 52g's last click hit g1, and a second one there would count as a double click.
|
||||
const clickRow = async (id, modifiers) => {
|
||||
const at = await rowAt(id);
|
||||
await click(at.x, at.y, 'left', modifiers);
|
||||
};
|
||||
await clickRow('o2');
|
||||
await clickRow('o5', { shift: true });
|
||||
await clickRow('g2');
|
||||
await clickRow('g5', { shift: true });
|
||||
const down = await selected();
|
||||
const primaryAfter = (await json('__gm.state.selection'))?.objectId;
|
||||
await clickRow('o1', { shift: true });
|
||||
await clickRow('g1', { shift: true });
|
||||
const up = await selected();
|
||||
check(
|
||||
'Shift-click selects the rows in between',
|
||||
same([...down].sort(), ['o2', 'o3', 'o5']) &&
|
||||
primaryAfter === 'o5' &&
|
||||
same([...up].sort(), ['o1', 'o2']) &&
|
||||
same(await rowIds(), ['o1', 'o2', 'o3', 'o5']),
|
||||
same([...down].sort(), ['g2', 'g3', 'g5']) &&
|
||||
primaryAfter === 'g5' &&
|
||||
same([...up].sort(), ['g1', 'g2']) &&
|
||||
same(await rowIds(), ['g1', 'g2', 'g3', 'g5']),
|
||||
JSON.stringify({ down, primaryAfter, up, rows: await rowIds() }),
|
||||
);
|
||||
await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()');
|
||||
|
||||
// 52i. A group "Pile" of o5 alone; its folder stands in the icon column of the objects, the
|
||||
// count left of it. Then o1 and o2 join it through the dialog's group dropdown.
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o5', objectIds: ['o5'] });
|
||||
const lone = await rowAt('o5');
|
||||
// 52i. A group "Pile" of g5 alone; its folder stands in the icon column of the objects, the
|
||||
// count left of it. Then g1 and g2 join it through the dialog's group dropdown.
|
||||
await select({ kind: 'object', holderId: SCENE, objectId: 'g5', objectIds: ['g5'] });
|
||||
const lone = await rowAt('g5');
|
||||
await click(lone.x, lone.y, 'right');
|
||||
await clickText('Group…');
|
||||
await typeText('Pile');
|
||||
@@ -182,7 +206,7 @@ export async function hierarchyGroups(t) {
|
||||
const pileRow = `group:${pile?.id}`;
|
||||
const folder = await json(`__gm.hierarchyIcon(${JSON.stringify(pileRow)})`);
|
||||
const count = await json(`__gm.hierarchyHint(${JSON.stringify(pileRow)})`);
|
||||
const objectIcon = await json('__gm.hierarchyIcon("o1")');
|
||||
const objectIcon = await json('__gm.hierarchyIcon("g1")');
|
||||
check(
|
||||
"a group's folder stands in the objects' icon column, its count left of it",
|
||||
folder !== null &&
|
||||
@@ -192,8 +216,8 @@ export async function hierarchyGroups(t) {
|
||||
count.x < folder.x - 8,
|
||||
JSON.stringify({ folder, objectIcon, count }),
|
||||
);
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o1', objectIds: ['o1', 'o2'] });
|
||||
const joiner = await rowAt('o1');
|
||||
await select({ kind: 'object', holderId: SCENE, objectId: 'g1', objectIds: ['g1', 'g2'] });
|
||||
const joiner = await rowAt('g1');
|
||||
await click(joiner.x, joiner.y, 'right');
|
||||
await clickText('Group 2 objects…');
|
||||
await clickText('New group');
|
||||
@@ -210,15 +234,15 @@ export async function hierarchyGroups(t) {
|
||||
joined.groups?.length === 1 &&
|
||||
same(
|
||||
joined.objects.filter((o) => o.groupId === pile?.id).map((o) => o.id),
|
||||
['o1', 'o2', 'o5'],
|
||||
['g1', 'g2', 'g5'],
|
||||
),
|
||||
JSON.stringify({ choices, groups: joined.groups, objects: joined.objects.map((o) => [o.id, o.groupId]) }),
|
||||
);
|
||||
await undo();
|
||||
|
||||
// 52j. Dragging one row of a multi-selection takes the others along – here onto the group row.
|
||||
await select({ kind: 'object', holderId: 'scene1', objectId: 'o1', objectIds: ['o1', 'o2'] });
|
||||
const grab = await rowAt('o2');
|
||||
await select({ kind: 'object', holderId: SCENE, objectId: 'g1', objectIds: ['g1', 'g2'] });
|
||||
const grab = await rowAt('g2');
|
||||
const into = await rowAt(pileRow);
|
||||
await drag(grab.x, grab.y, into.x, into.y);
|
||||
const dragged = await scene();
|
||||
@@ -227,18 +251,23 @@ export async function hierarchyGroups(t) {
|
||||
const undone = await scene();
|
||||
check(
|
||||
'dragging a selected row moves the whole selection, in one undo step',
|
||||
same(inPile, ['o5', 'o1', 'o2']) &&
|
||||
same(inPile, ['g5', 'g1', 'g2']) &&
|
||||
same(
|
||||
undone.objects.filter((o) => o.groupId === pile?.id).map((o) => o.id),
|
||||
['o5'],
|
||||
['g5'],
|
||||
),
|
||||
JSON.stringify({ inPile, undone: undone.objects.map((o) => [o.id, o.groupId]) }),
|
||||
);
|
||||
await undo();
|
||||
check(
|
||||
'undo takes the group of o5 back too',
|
||||
'undo takes the group of g5 back too',
|
||||
(await scene()).groups === undefined,
|
||||
JSON.stringify((await scene()).groups),
|
||||
);
|
||||
await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()');
|
||||
await t.mutate(`
|
||||
const p = __gm.state.project;
|
||||
p.activeSceneId = ${JSON.stringify(home)};
|
||||
p.assets = p.assets.filter((a) => a.id !== ${JSON.stringify(SCENE)})`);
|
||||
await t.saved();
|
||||
}
|
||||
Reference in new issue
Block a user