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
@@ -106,9 +106,8 @@ shown on the node and editable in the inspector.
|
||||
its flow pins only while at least one is. Unconnected and with nothing typed, it writes the zero
|
||||
of its kind (0, false, empty), and the node shows that value beside the name. All of them are written together, their inputs read
|
||||
first, so one node swaps two variables. `count = count + 1` is one `Variables` node feeding its
|
||||
own output through an `Add` back into its input. With a single variable the node is titled by it
|
||||
(*speed*). The ports are picked from the behavior's variables rather than named, they take their
|
||||
type from the declaration, and renaming a variable carries them (and their edges) along. Files
|
||||
own output through an `Add` back into its input. The ports are picked from the behavior's
|
||||
variables rather than named, they take their type from the declaration, and renaming a variable carries them (and their edges) along. Files
|
||||
from before this node still load: their `Get/Set variable(s)` and `Variable` nodes become
|
||||
`Variables` nodes that read or write the same variables. `Properties` is the same node for the
|
||||
object it is handed (fixed input `object`). Its **Type** setting, above the list, decides what
|
||||
@@ -117,9 +116,9 @@ shown on the node and editable in the inspector.
|
||||
port along, in any graph), and *Any* offers the kinds (Number, Text, …) and leaves the name to be
|
||||
typed. A picked property keeps its field and default – an unconnected `color` is a colour field
|
||||
starting white, shown on the node as `#ffffff`, and a camera's `width` is its whole-pixel view
|
||||
width. Switching the type keeps the ports; one the new type lacks is marked. The node is titled
|
||||
by its type or behavior (*Sprite properties*, *Game properties*). Textures and scenes are no type
|
||||
here: they are not objects in a running game. Older files load their `Get/Set property(ies)`, `Set enabled` and `Set text` as
|
||||
width. Switching the type keeps the ports; one the new type lacks is marked. The node shows its
|
||||
type as a setting row (*Type: Sprite*, plus *Behavior: Game* under that type). Textures and
|
||||
scenes are no type here: they are not objects in a running game. Older files load their `Get/Set property(ies)`, `Set enabled` and `Set text` as
|
||||
`Properties` nodes, typed by the first object type holding every key, else the one behavior
|
||||
declaring them, else Any – which is also what a node only about `enabled` gets, since every
|
||||
object has it. A variable is a
|
||||
@@ -294,7 +293,11 @@ shown on the node and editable in the inspector.
|
||||
point on the title bar, and its hidden links share one mark per side. It narrows to its title
|
||||
(never wider than an open node, where a long title is cut). A collapsed node has no
|
||||
ports to grab, so a link dropped on it goes nowhere – expand it to wire it. Like hiding a link,
|
||||
this is drawing only: `collapsed` on the node is saved with the project.
|
||||
this is drawing only: `collapsed` on the node is saved with the project. A collapsed
|
||||
`Variables` or `Properties` node, titled just that while open, is titled by what it does, since
|
||||
its ports are out of sight:
|
||||
*Set 2 variables* when more than one is written, else *Get 2 variables* when more than one is
|
||||
only read, else *Set speed* / *Get speed*, and *No variables* when it has none.
|
||||
- **Header bar** (the same one the Texture and Container tabs have): the behavior picker,
|
||||
**Fit** (which zooms out far enough to hold every node, measuring each by its own drawn
|
||||
height – `editor/framing.ts`), and the size of the graph as "N nodes · M links".
|
||||
@@ -667,7 +670,7 @@ npm run check # typecheck + lint + format:check + tests, what CI would ru
|
||||
# code is lost (tail's is reported), so a red run reads as green
|
||||
npm run build # writes dist/ and regenerates index.html
|
||||
export GM_REMOTE=user@host # optional: run the browser elsewhere; see below
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 575 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 578 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 – a subset while working on one step
|
||||
./e2e.sh start [GM_STEPS] # the same run detached, see below
|
||||
./e2e.sh status # live progress, or the last run's result
|
||||
|
||||
@@ -51,6 +51,17 @@ export function declaredOn(
|
||||
return def.declaresPorts === side ? [...ports] : [];
|
||||
}
|
||||
|
||||
/** What a collapsed Variables or Properties node does, since its ports are out of sight. */
|
||||
export function collapsedTitle(ports: readonly DeclaredPort[], noun: string): string {
|
||||
const set = ports.filter((p) => p.write);
|
||||
const get = ports.filter((p) => !p.write);
|
||||
if (set.length > 1) return `Set ${set.length} ${noun}`;
|
||||
if (get.length > 1) return `Get ${get.length} ${noun}`;
|
||||
if (set[0]) return `Set ${set[0].key}`;
|
||||
if (get[0]) return `Get ${get[0].key}`;
|
||||
return `No ${noun}`;
|
||||
}
|
||||
|
||||
/** The single start (or end) node of a graph; a second one is ignored. */
|
||||
export function terminalNode(graph: Graph, type: string): GraphNode | undefined {
|
||||
return graph.nodes.find((n) => n.type === type);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { PROPERTIES } from '../../nested';
|
||||
import { collapsedTitle, PROPERTIES } from '../../nested';
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import {
|
||||
OBJECT_BEHAVIOR_SETTING,
|
||||
OBJECT_TYPE_SETTING,
|
||||
propertiesTitle,
|
||||
propertyPort,
|
||||
propertyTargetOf,
|
||||
} from '../../propertyPorts';
|
||||
import { asEntity, writeTyped } from '../../values';
|
||||
import { exec, obj } from '../shared';
|
||||
@@ -19,13 +19,13 @@ export const Properties: NodeDef = {
|
||||
inputs: [obj('object')],
|
||||
outputs: [],
|
||||
params: [OBJECT_TYPE_SETTING, OBJECT_BEHAVIOR_SETTING],
|
||||
// The title names both ("Sprite properties", "Game properties").
|
||||
hiddenParams: () => [OBJECT_TYPE_SETTING.key, OBJECT_BEHAVIOR_SETTING.key],
|
||||
hiddenParams: (params) => (propertyTargetOf(params) === 'behavior' ? [] : [OBJECT_BEHAVIOR_SETTING.key]),
|
||||
declaresPorts: 'both',
|
||||
declaredPort: (port, node) => propertyPort(port, node.params),
|
||||
derivePorts: ({ node, behaviors }) => {
|
||||
const label = propertiesTitle(node, behaviors);
|
||||
return (node.ports ?? []).some((p) => p.write) ? { label, inputs: [exec()], outputs: [exec()] } : { label };
|
||||
derivePorts: ({ node }) => {
|
||||
const ports = node.ports ?? [];
|
||||
const shape = ports.some((p) => p.write) ? { inputs: [exec()], outputs: [exec()] } : {};
|
||||
return node.collapsed ? { ...shape, label: collapsedTitle(ports, 'properties') } : shape;
|
||||
},
|
||||
read: (_ctx, _params, input) => asEntity(input('object'))?.props ?? {},
|
||||
run: (_ctx, inputs, _params, def) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { portDefOf, VARIABLES } from '../../nested';
|
||||
import { collapsedTitle, portDefOf, VARIABLES } from '../../nested';
|
||||
import { type NodeDef, withInlineDefault } from '../../NodeRegistry';
|
||||
import { writeTyped } from '../../values';
|
||||
import { exec } from '../shared';
|
||||
@@ -21,9 +21,8 @@ export const Variables: NodeDef = {
|
||||
declaredPort: (port) => withInlineDefault(portDefOf(port)),
|
||||
derivePorts: ({ node }) => {
|
||||
const ports = node.ports ?? [];
|
||||
const only = ports.length === 1 ? ports[0]?.key : undefined;
|
||||
const shape = ports.some((p) => p.write) ? { inputs: [exec()], outputs: [exec()] } : {};
|
||||
return only ? { ...shape, label: only } : shape;
|
||||
return node.collapsed ? { ...shape, label: collapsedTitle(ports, 'variables') } : shape;
|
||||
},
|
||||
read: (ctx) => ctx.variables,
|
||||
run: (ctx, inputs, _params, def) => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ASSET_TYPE_LABELS } from '../model/AssetTypes';
|
||||
import { type PropertyBag, textOf } from '../model/Properties';
|
||||
import { type PropertyDef, type PropertySchema, schemaFor } from '../properties/PropertySchema';
|
||||
import type { DeclaredPort, GraphNode } from './Graph';
|
||||
import type { BehaviorLookup } from './lookup';
|
||||
import type { DeclaredPort } from './Graph';
|
||||
import { portDefOf } from './nested';
|
||||
import { type PortDef, withInlineDefault } from './NodeRegistry';
|
||||
import { portKindOf } from './variablePorts';
|
||||
@@ -63,10 +62,3 @@ export function propertyPort(port: DeclaredPort, params: PropertyBag): PortDef {
|
||||
if (!field || portKindOf(field) !== port.kind) return plain;
|
||||
return { ...plain, field, default: field.default };
|
||||
}
|
||||
|
||||
export function propertiesTitle(node: GraphNode, behaviors: BehaviorLookup): string {
|
||||
const target = propertyTargetOf(node.params);
|
||||
if (target === 'any') return 'Properties';
|
||||
const behavior = target === 'behavior' ? behaviors(textOf(node.params[OBJECT_BEHAVIOR_PARAM] ?? '')) : null;
|
||||
return `${behavior?.name ?? targetLabel(target)} properties`;
|
||||
}
|
||||
@@ -384,6 +384,7 @@ function formatField(def: PropertyDef, value: Value, project: Project | null): s
|
||||
if (def.kind === 'asset') return assetRefLabel(project, asString(value));
|
||||
if (def.kind === 'texture') return textureRefLabel(project, asString(value));
|
||||
if (def.kind === 'color') return toHex(asNumber(value));
|
||||
if (def.kind === 'enum') return def.options.find((o) => o.value === value)?.label ?? formatValue(value);
|
||||
return formatValue(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { CommandError } from '../../src/core/commands/Command';
|
||||
import { CollapseNodesCommand } from '../../src/core/commands/graphCommands';
|
||||
import { AddPortCommand, RenamePortCommand } from '../../src/core/commands/portCommands';
|
||||
import { RenameVariableCommand } from '../../src/core/commands/variableCommands';
|
||||
import {
|
||||
@@ -141,18 +142,46 @@ describe('what a port carries, by the type of the node', () => {
|
||||
expect(propertyPort({ key: 'ready', kind: 'boolean' }, {}).default).toBe(false);
|
||||
});
|
||||
|
||||
it('names the node after its type, or after the behavior it points at', () => {
|
||||
it('shows its type as a setting row, and the behavior only under that type', () => {
|
||||
const fixture = makeProject();
|
||||
const game = makeBehavior(fixture, 'Game');
|
||||
const edit = graphEditor(fixture, makeBehavior(fixture, 'Player'));
|
||||
const title = (params: PropertyBag) =>
|
||||
resolveDef(edit.properties([], params), game.graph, behaviorLookup(fixture.project))!.label;
|
||||
expect(title({})).toBe('Properties');
|
||||
expect(title(SPRITE)).toBe('Sprite properties');
|
||||
expect(title({ [OBJECT_TYPE_PARAM]: 'behavior', [OBJECT_BEHAVIOR_PARAM]: game.id })).toBe(
|
||||
'Game properties',
|
||||
const edit = graphEditor(fixture, makeBehavior(fixture, 'B'));
|
||||
const hidden = (params: PropertyBag) => {
|
||||
const node = edit.properties([], params);
|
||||
return resolveDef(node, { nodes: [node], edges: [] }, NO_BEHAVIORS)!.hiddenParams?.(node.params);
|
||||
};
|
||||
expect(hidden(SPRITE)).toEqual([OBJECT_BEHAVIOR_PARAM]);
|
||||
expect(hidden({})).toEqual([OBJECT_BEHAVIOR_PARAM]);
|
||||
expect(hidden({ [OBJECT_TYPE_PARAM]: 'behavior' })).toEqual([]);
|
||||
});
|
||||
|
||||
it('is titled Properties while open and says what it does once collapsed', () => {
|
||||
const fixture = makeProject();
|
||||
const behavior = makeBehavior(fixture, 'B');
|
||||
const edit = graphEditor(fixture, behavior);
|
||||
const width = { key: 'width', kind: 'number' } as const;
|
||||
const nodes = [
|
||||
edit.properties([set('width'), set('height')], SPRITE),
|
||||
edit.properties([width, { key: 'height', kind: 'number' }], SPRITE),
|
||||
edit.properties([set('width')], SPRITE),
|
||||
edit.properties([width], SPRITE),
|
||||
edit.properties([], SPRITE),
|
||||
];
|
||||
const titles = () => nodes.map((node) => resolveDef(node, behavior.graph, NO_BEHAVIORS)!.label);
|
||||
expect(new Set(titles())).toEqual(new Set(['Properties']));
|
||||
fixture.stack.execute(
|
||||
new CollapseNodesCommand(
|
||||
behavior.id,
|
||||
nodes.map((node) => node.id),
|
||||
true,
|
||||
),
|
||||
);
|
||||
expect(title({ [OBJECT_TYPE_PARAM]: 'behavior' })).toBe('Behavior properties');
|
||||
expect(titles()).toEqual([
|
||||
'Set 2 properties',
|
||||
'Get 2 properties',
|
||||
'Set width',
|
||||
'Get width',
|
||||
'No properties',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { CollapseNodesCommand } from '../../src/core/commands/graphCommands';
|
||||
import { RemovePortCommand, SetPortWriteCommand } from '../../src/core/commands/portCommands';
|
||||
import { AddVariableCommand, RenameVariableCommand } from '../../src/core/commands/variableCommands';
|
||||
import { type GraphNode, GraphInterpreter, inlineValue, resolveDef } from '../../src/core/graph';
|
||||
@@ -238,22 +239,37 @@ describe('Variables node titles', () => {
|
||||
const titleOf = (behavior: BehaviorAsset, node: GraphNode) =>
|
||||
resolveDef(node, behavior.graph, () => null)!.label;
|
||||
|
||||
it('name a single variable, and stay plain for none or several', () => {
|
||||
const { behavior, edit } = setup();
|
||||
const one = edit.variables(['count'], { write: true });
|
||||
const none = edit.variables([]);
|
||||
const two = edit.variables(['count', 'copy']);
|
||||
expect([one, none, two].map((node) => titleOf(behavior, node))).toEqual([
|
||||
'count',
|
||||
'Variables',
|
||||
'Variables',
|
||||
]);
|
||||
});
|
||||
|
||||
it('follow a renamed variable', () => {
|
||||
it('follow a renamed variable once collapsed', () => {
|
||||
const { fixture, behavior, edit } = setup();
|
||||
const node = edit.variables(['count']);
|
||||
fixture.stack.execute(new CollapseNodesCommand(behavior.id, [node.id], true));
|
||||
fixture.stack.execute(new RenameVariableCommand(behavior.id, 'count', 'score'));
|
||||
expect(titleOf(behavior, node)).toBe('score');
|
||||
expect(titleOf(behavior, node)).toBe('Get score');
|
||||
});
|
||||
|
||||
it('say what the node does once it is collapsed, and only then', () => {
|
||||
const { fixture, behavior, edit } = setup(['a', 'b', 'c']);
|
||||
const nodes = [
|
||||
edit.variables(['a', 'b', 'c'], { write: ['a', 'b'] }),
|
||||
edit.variables(['a', 'b', 'c'], { write: ['a'] }),
|
||||
edit.variables(['a', 'b'], { write: ['a'] }),
|
||||
edit.variables(['a'], { write: true }),
|
||||
edit.variables(['a']),
|
||||
edit.variables([]),
|
||||
];
|
||||
const open = nodes.map((node) => titleOf(behavior, node));
|
||||
const ids = nodes.map((node) => node.id);
|
||||
fixture.stack.execute(new CollapseNodesCommand(behavior.id, ids, true));
|
||||
expect(nodes.map((node) => titleOf(behavior, node))).toEqual([
|
||||
'Set 2 variables',
|
||||
'Get 2 variables',
|
||||
'Set a',
|
||||
'Set a',
|
||||
'Get a',
|
||||
'No variables',
|
||||
]);
|
||||
fixture.stack.undo();
|
||||
expect(nodes.map((node) => titleOf(behavior, node))).toEqual(open);
|
||||
expect(new Set(open)).toEqual(new Set(['Variables']));
|
||||
});
|
||||
});
|
||||
@@ -6,3 +6,7 @@ ok, title bei variables node NUR wenn zusammengeklappt ändern:
|
||||
- ansonsten => "No variables"
|
||||
|
||||
dasselbe bei properties node
|
||||
|
||||
---
|
||||
Umgesetzt: aufgeklappt heissen die Nodes immer `Variables` / `Properties`. Der Typ des `Properties`-Nodes steht deshalb als Zeile unter dem Titel (`Type: Sprite`, bei Behavior zusaetzlich `Behavior: <Name>`).
|
||||
„Get {n}“ zaehlt nur Ports ohne Set.
|
||||
@@ -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