Two findings from #269. A reference from a container into a placement nested in it was dead from the start, and deleting an asset left references to the objects it took along showing "(missing)". - A reference is stored the way its holder shows the object. In a container tab that is q~x for content of a nested placement q. - referenceCandidates translates it when the container is placed: in placement r, q~x becomes r~q/x. pathOfId and idOfPath in instance.ts do the mapping, objectChoices offers the same spelling the other way. The rule holds at any depth. - Storing q/x as proposed in #285 was dropped. The container tab would then have had to translate its own ids. - No migration. All projects and snapshots hold three references, none of them with ~ or /. - Create container lets a reference to content of a member placement follow it. It was left dead before. - Paste lets such a reference point into the copy. It kept pointing at the original before. Both go through the new renamedReference. - Delete asset records what every holder shows before the cascade and clears every reference to an object that is gone afterwards, with dropReferencesToGone from #269. The row shows None, undo restores the references (#286). Tests: - Gate: 198 files, 2882 tests (2873 + 9), exit 0 - Unit object-references: 26 passed (17 + 9) - e2e 1-17,57: 566 checks passed, 57 has +57i and +57j - Six unit counter-probes fail between 1 and 3 tests each - The browser counter-probe fails 57i and 57j as described in the issues - e2e total 1609 is computed, not measured in a full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
214218d1b2
commit
5e21d473a4
9 files changed
+295
-17
No files matched your search
@@ -10,6 +10,9 @@
|
||||
* Variables tab it goes to the scene view and comes back with Take.
|
||||
*
|
||||
* Removing an object in the Container tab empties a reference a placement of it holds (#269).
|
||||
* Inside a container the Aim can pick content of a placement nested there, and the container's
|
||||
* placement in the scene reaches it (#285). Deleting that nested container's asset empties the
|
||||
* reference again (#286).
|
||||
*
|
||||
* Everything it adds carries an `or57` id and goes again at the end.
|
||||
*/
|
||||
@@ -340,6 +343,102 @@ export async function objectReferences(t) {
|
||||
JSON.stringify({ rowBefore, tabThen, templateLeft, emptied, rowAfter, restored }),
|
||||
);
|
||||
|
||||
// 57i. Outer 57 holds an Aim and a placement of Inner 57 (#285). In the Container tab the Aim
|
||||
// picks Inner's Nest 57, stored as Outer's own view names it. Outer's placement in the scene
|
||||
// shows that name on its Aim, and a run reads the copy of Nest 57 there.
|
||||
await t.showTab('scene');
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
const scene = __gm.state.activeScene;
|
||||
${MAKE_OBJECT}
|
||||
const sprite = p.assets.find((a) => a.type === 'sprite');
|
||||
p.assets.push({
|
||||
id: 'or57-inner',
|
||||
type: 'container',
|
||||
name: 'Inner 57',
|
||||
props: {},
|
||||
objects: [make('or57-i-nest', sprite.id, '', 0, 'Nest 57')],
|
||||
});
|
||||
p.assets.push({
|
||||
id: 'or57-outer',
|
||||
type: 'container',
|
||||
name: 'Outer 57',
|
||||
props: {},
|
||||
objects: [
|
||||
make('or57-o-aim', 'or57-aim', '', 0, 'Aim 57'),
|
||||
make('or57-o-inner', 'or57-inner', '', 50, 'Inner 57'),
|
||||
],
|
||||
});
|
||||
scene.objects.push(make('or57-outer-placed', 'or57-outer', '', ${LANES.nestedReference}, 'Outer 57'));
|
||||
`);
|
||||
const outerTarget = () =>
|
||||
json(
|
||||
"__gm.state.project.assets.find((a) => a.id === 'or57-outer').objects.find((o) => o.id === 'or57-o-aim')?.overrides.target ?? null",
|
||||
);
|
||||
const nestName = JSON.stringify({ $object: 'or57-o-inner~or57-i-nest' });
|
||||
await select({ kind: 'object', holderId: 'or57-outer', objectId: 'or57-o-aim' });
|
||||
await sleep(200);
|
||||
const outerTab = await json('__gm.tab()');
|
||||
await press_(await json("__gm.inspectorField('target')"), 'the target field in the Container tab');
|
||||
const inOuter = (await json('__gm.overlayTexts()')) ?? [];
|
||||
// Nest 57 sits one level below Inner 57, so the menu indents it.
|
||||
const nestEntry = inOuter.find((text) => text.trim() === 'Nest 57');
|
||||
await press_(await json(`__gm.locate(${JSON.stringify(nestEntry)}, true)`), 'Nest 57 in the menu');
|
||||
const stored = await outerTarget();
|
||||
await t.showTab('scene');
|
||||
const nestedAim = { kind: 'object', holderId: sceneId, objectId: 'or57-outer-placed~or57-o-aim' };
|
||||
await select(nestedAim);
|
||||
await sleep(200);
|
||||
const nestedRow = { nest: await inInspector('Nest 57'), missing: await inInspector('(missing)') };
|
||||
await shot('05-nested-reference');
|
||||
await evaluate('__gm.state.select(null)');
|
||||
const runFrom = t.consoleLog.length;
|
||||
await t.play();
|
||||
await sleep(600);
|
||||
await stop();
|
||||
const nestedLogged = t.consoleLog
|
||||
.slice(runFrom)
|
||||
.filter((line) => line.startsWith('[behavior Aim 57]'))
|
||||
.map((line) => line.split(']').pop().trim());
|
||||
check(
|
||||
'a pick in the Container tab reaches into a nested placement, and the placement in the scene shows and reads it',
|
||||
outerTab === 'container' &&
|
||||
nestEntry !== undefined &&
|
||||
JSON.stringify(stored) === nestName &&
|
||||
nestedRow.nest &&
|
||||
!nestedRow.missing &&
|
||||
nestedLogged.includes('Nest 57'),
|
||||
JSON.stringify({ outerTab, inOuter, stored, nestedRow, nestedLogged }),
|
||||
);
|
||||
|
||||
// 57j. Deleting Inner 57 in the asset list (#286) takes its placement out of Outer 57 and empties
|
||||
// the Aim's reference to Nest 57 with it: the row says None, not (missing). One undo brings
|
||||
// both back.
|
||||
const innerRow = await json("__gm.assetRow('or57-inner')");
|
||||
if (!innerRow) throw new Error('Inner 57 is not in the asset list');
|
||||
await click(innerRow.x, innerRow.y, 'right');
|
||||
await sleep(150);
|
||||
await press_(await json("__gm.locate('Delete…', true)"), 'Delete… in the row menu');
|
||||
await press_(await json("__gm.locate('Delete', true)"), 'Delete in the dialog');
|
||||
const innerLeft = await json("__gm.state.project.assets.some((a) => a.id === 'or57-inner')");
|
||||
const cleared = await outerTarget();
|
||||
await select(nestedAim);
|
||||
await sleep(200);
|
||||
const clearedRow = await targetRow();
|
||||
await shot('06-asset-deleted');
|
||||
await undo();
|
||||
await sleep(150);
|
||||
const reinstated = await outerTarget();
|
||||
check(
|
||||
'deleting an asset empties a reference to an object it took along, undo brings it back',
|
||||
!innerLeft &&
|
||||
JSON.stringify(cleared) === JSON.stringify({ $object: '' }) &&
|
||||
clearedRow.none &&
|
||||
!clearedRow.missing &&
|
||||
JSON.stringify(reinstated) === nestName,
|
||||
JSON.stringify({ innerLeft, cleared, clearedRow, reinstated }),
|
||||
);
|
||||
|
||||
// Clean up. Back to the scene view first: removing Aim 57 closes 57g's Variables tab, and the
|
||||
// editor then shows whatever document earlier steps left open, not the scene view.
|
||||
await t.showTab('scene');
|
||||
@@ -348,6 +447,6 @@ export async function objectReferences(t) {
|
||||
const p = __gm.state.project;
|
||||
const scene = __gm.state.activeScene;
|
||||
scene.objects = scene.objects.filter((o) => !o.id.startsWith('or57-'));
|
||||
p.assets = p.assets.filter((a) => a.id !== 'or57-aim' && a.id !== 'or57-box');
|
||||
p.assets = p.assets.filter((a) => !a.id.startsWith('or57-'));
|
||||
`);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export const LANES = {
|
||||
pixelArt: -5400,
|
||||
eyedropper: -5800,
|
||||
containerReference: -6200,
|
||||
nestedReference: -6600,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user