Title a collapsed Variables or Properties node by what it does
Collapsed, the node reads "Set 2 variables", "Get width" or "No properties"; expanded it is always "Variables" / "Properties". The Properties node shows its object type (and behavior) as a line under the title instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
58f19a3451
commit
0376ae175d
11 files changed
+182
-69
No files matched your search
@@ -186,7 +186,53 @@ export async function variableNodes(t) {
|
||||
await sleep(300);
|
||||
check('and undo points it back', (await portsOf('vp-get'))[0].key === 'vx');
|
||||
|
||||
// 23f. A Pack keeps declaring its own fields, but a variable that already carries the shape can
|
||||
// 23f. Collapsed, the ports are out of sight, so the title says what the node does – and only then.
|
||||
const titleOf = (id) => json(`__gm.graph.nodeLabel(${JSON.stringify(id)})`);
|
||||
const toggle = async (id) => {
|
||||
const chevron = await json(`__gm.graph.chevronAt(${JSON.stringify(id)})`);
|
||||
await click(chevron.x, chevron.y);
|
||||
await sleep(250);
|
||||
};
|
||||
/** `locate` matches exactly, so a title cut short by a too-narrow node is not found. */
|
||||
const drawnWhole = async (text) => {
|
||||
const canvas = await json('__gm.graph.canvasRect()');
|
||||
const hits = await json(`__gm.locateAll(${JSON.stringify(text)})`);
|
||||
return hits.some((p) => p.x > canvas.x && p.x < canvas.x + canvas.w);
|
||||
};
|
||||
await toggle('vp-set');
|
||||
await toggle('vp-get');
|
||||
const shut = { set: await titleOf('vp-set'), get: await titleOf('vp-get') };
|
||||
check(
|
||||
'collapsed, a node writing two variables says so, and one only reading two says that',
|
||||
shut.set === 'Set 2 variables' &&
|
||||
shut.get === 'Get 2 variables' &&
|
||||
(await drawnWhole('Set 2 variables')) &&
|
||||
(await drawnWhole('Get 2 variables')),
|
||||
JSON.stringify(shut),
|
||||
);
|
||||
await shot('02b-collapsed-titles');
|
||||
const narrow = (await json(`__gm.graph.nodeRect('vp-get')`)).w;
|
||||
await clickLastSet();
|
||||
const switched = await titleOf('vp-get');
|
||||
check(
|
||||
'switching one to Set while collapsed retitles it and resizes it to the new title',
|
||||
switched === 'Set vy' &&
|
||||
(await drawnWhole('Set vy')) &&
|
||||
(await json(`__gm.graph.nodeRect('vp-get')`)).w < narrow,
|
||||
JSON.stringify({ switched, narrow }),
|
||||
);
|
||||
await undo();
|
||||
await sleep(300);
|
||||
await toggle('vp-set');
|
||||
await toggle('vp-get');
|
||||
const open = [await titleOf('vp-set'), await titleOf('vp-get')];
|
||||
check(
|
||||
'expanded again, both are plain Variables nodes',
|
||||
open.join() === 'Variables,Variables',
|
||||
JSON.stringify(open),
|
||||
);
|
||||
|
||||
// 23g. A Pack keeps declaring its own fields, but a variable that already carries the shape can
|
||||
// hand it over. A color field arrives as a number port – the graph has no colors.
|
||||
await mutate(`
|
||||
const a = __gm.state.project.assets.find((x) => x.id === 'vp-logic');
|
||||
@@ -232,7 +278,7 @@ export async function variableNodes(t) {
|
||||
await sleep(300);
|
||||
check('one undo takes all of them back', (await portsOf('vp-pack')).length === 0);
|
||||
|
||||
// 23g. A list of structs declares the shape of its items, which is the bundle a Pack builds for it.
|
||||
// 23h. A list of structs declares the shape of its items, which is the bundle a Pack builds for it.
|
||||
await click(from.x, from.y);
|
||||
await sleep(200);
|
||||
await clickLast('shapes (item)');
|
||||
@@ -246,7 +292,7 @@ export async function variableNodes(t) {
|
||||
await undo();
|
||||
await sleep(300);
|
||||
|
||||
// 23h. A behavior's own inputs are not fields of a bundle, so the helper stays out of that section.
|
||||
// 23i. A behavior's own inputs are not fields of a bundle, so the helper stays out of that section.
|
||||
await mutate(`
|
||||
__gm.state.project.assets.find((x) => x.id === 'vp-logic').graph.nodes.push(
|
||||
${JSON.stringify(node('vp-start', 'BehaviorStart', 560, 600))},
|
||||
@@ -262,7 +308,7 @@ export async function variableNodes(t) {
|
||||
JSON.stringify(helper),
|
||||
);
|
||||
|
||||
// 23i. From the palette the node comes empty; picking a variable titles it, and switched to Set
|
||||
// 23j. From the palette the node comes empty; picking a variable keeps its title, and switched to Set
|
||||
// it runs `vz = vz + 1` once a frame – its own output feeding its input via Add.
|
||||
const nodeIds = async () => (await asset('vp-logic')).graph.nodes.map((n) => n.id);
|
||||
const idsBefore = await nodeIds();
|
||||
@@ -278,7 +324,6 @@ export async function variableNodes(t) {
|
||||
const added = (await asset('vp-logic')).graph.nodes.find((n) => !idsBefore.includes(n.id));
|
||||
check('the palette offers the Variables node', added?.type === 'Variables', JSON.stringify(added));
|
||||
const variableId = added?.id ?? '';
|
||||
const titleOf = (id) => json(`__gm.graph.nodeLabel(${JSON.stringify(id)})`);
|
||||
const untitled = await titleOf(variableId);
|
||||
const bare = !(await portAt(variableId, 'exec', false));
|
||||
await clickLast('+ Variable');
|
||||
@@ -286,8 +331,8 @@ export async function variableNodes(t) {
|
||||
await sleep(300);
|
||||
await clickLastSet();
|
||||
check(
|
||||
'it starts without pins, and picking its variable makes that the title',
|
||||
untitled === 'Variables' && bare && (await titleOf(variableId)) === 'vz',
|
||||
'it starts without pins, and picking its variable leaves the title Variables',
|
||||
untitled === 'Variables' && bare && (await titleOf(variableId)) === 'Variables',
|
||||
JSON.stringify({ untitled, bare, titled: await titleOf(variableId) }),
|
||||
);
|
||||
check('switched to Set it grows its flow pins', Boolean(await portAt(variableId, 'exec', false)));
|
||||
|
||||
@@ -36,6 +36,7 @@ export async function propertyNodes(t) {
|
||||
(p) => `${p.key}:${p.kind}${p.write ? ':set' : ''}`,
|
||||
);
|
||||
const title = (nodeId) => json(`__gm.graph.nodeLabel(${JSON.stringify(nodeId)})`);
|
||||
const settings = async (nodeId) => (await json(`__gm.graph.nodeSettings(${JSON.stringify(nodeId)})`)) ?? [];
|
||||
const select = async (nodeId) => {
|
||||
await evaluate(
|
||||
`__gm.state.select({ kind: 'node', assetId: 'pp-logic', nodeId: ${JSON.stringify(nodeId)}, nodeIds: [${JSON.stringify(nodeId)}] })`,
|
||||
@@ -111,14 +112,22 @@ export async function propertyNodes(t) {
|
||||
JSON.stringify({ width: box?.width, color: box?.color, seen }),
|
||||
);
|
||||
|
||||
// 24c. Named after its type; the type is chosen above the list it decides.
|
||||
// 24c. Titled Properties; the type stands on the node as a setting row, chosen above the list it decides.
|
||||
await select('pp-set');
|
||||
const typeRow = await json(`__gm.inspectorField('Type', true)`);
|
||||
const header = await locate('PROPERTIES', true);
|
||||
check(
|
||||
'the node is named after its type, chosen above its properties',
|
||||
(await title('pp-set')) === 'Sprite properties' && typeRow && typeRow.y < header.y,
|
||||
JSON.stringify({ title: await title('pp-set'), type: typeRow?.y, header: header.y }),
|
||||
'the node is titled Properties and shows its type, chosen above its properties',
|
||||
(await title('pp-set')) === 'Properties' &&
|
||||
(await settings('pp-set')).join() === 'Type: Sprite' &&
|
||||
typeRow &&
|
||||
typeRow.y < header.y,
|
||||
JSON.stringify({
|
||||
title: await title('pp-set'),
|
||||
rows: await settings('pp-set'),
|
||||
type: typeRow?.y,
|
||||
header: header.y,
|
||||
}),
|
||||
);
|
||||
check(
|
||||
'the node shows a typed-in color as a color',
|
||||
@@ -165,9 +174,11 @@ export async function propertyNodes(t) {
|
||||
await choose('Type', 'Camera');
|
||||
const camera = await offered();
|
||||
check(
|
||||
'switching to Camera renames the node and offers the fields of a camera',
|
||||
(await title('pp-set')) === 'Camera properties' && camera.includes('zoom') && !camera.includes('texture'),
|
||||
JSON.stringify({ title: await title('pp-set'), offered: camera }),
|
||||
'switching to Camera shows it on the node and offers the fields of a camera',
|
||||
(await settings('pp-set')).join() === 'Type: Camera' &&
|
||||
camera.includes('zoom') &&
|
||||
!camera.includes('texture'),
|
||||
JSON.stringify({ rows: await settings('pp-set'), offered: camera }),
|
||||
);
|
||||
check(
|
||||
'a port the new type does not have stays, marked',
|
||||
@@ -177,7 +188,7 @@ export async function propertyNodes(t) {
|
||||
await shot('03-camera');
|
||||
await undo();
|
||||
await sleep(300);
|
||||
check('undo switches back', (await title('pp-set')) === 'Sprite properties', await title('pp-set'));
|
||||
check('undo switches back', (await settings('pp-set')).join() === 'Type: Sprite', await settings('pp-set'));
|
||||
|
||||
// 24g. Any names its ports by hand, and the kind is picked when adding one. A name that happens
|
||||
// to be a property's stays of the kind picked.
|
||||
@@ -218,9 +229,11 @@ export async function propertyNodes(t) {
|
||||
await choose('Behavior', 'Reshape (behavior)');
|
||||
const vars = await offered();
|
||||
check(
|
||||
'picking the behavior offers its variables and names the node after it',
|
||||
vars.includes('seen') && vars.includes('enabled') && (await title('pp-beh')) === 'Reshape properties',
|
||||
JSON.stringify({ title: await title('pp-beh'), offered: vars }),
|
||||
'picking the behavior offers its variables and shows it on the node',
|
||||
vars.includes('seen') &&
|
||||
vars.includes('enabled') &&
|
||||
(await settings('pp-beh')).join() === 'Type: Behavior,Behavior: Reshape',
|
||||
JSON.stringify({ rows: await settings('pp-beh'), offered: vars }),
|
||||
);
|
||||
await addProperty('seen');
|
||||
check('a variable is picked as a port', (await portsOf('pp-beh')).join() === 'seen:number');
|
||||
|
||||
Reference in new issue
Block a user