Finish the struct and list leftovers: inline lists, list fields, item kinds
An unconnected list input is a button into the List tab instead of a number field; a struct field can hold a list, so ListSlot carries an optional field name and the three owner cases only say what sits at a key; and a list variable picks its item kind in the inspector (marker doubles as the chooser), so a list of structs no longer has to be born from a project file. The declaration keeps the storable shapes finite: a struct field may be a list, but that list holds primitives only, so list->struct->primitive and struct->list->primitive are the two deepest forms. check 674, e2e 483, jump-run 24, Tetris 19. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fe3c63c48c
commit
10d030376a
18 files changed
+984
-184
No files matched your search
@@ -95,6 +95,34 @@ export async function listTab(t) {
|
||||
await sleep(200);
|
||||
check('and the count follows the value', (await find('2 items')) !== null);
|
||||
|
||||
// 6b. What a list holds is a choice in the inspector, not only in a project file: the kind mark
|
||||
// doubles as the picker, and changing it runs every value through the new declaration.
|
||||
const numsOf = () => json(`__gm.state.project.assets.find((a) => a.id === 'listy')`);
|
||||
await clickText('\u2261#');
|
||||
await sleep(150);
|
||||
await clickText('Texts');
|
||||
await sleep(250);
|
||||
let listy = await numsOf();
|
||||
check(
|
||||
'the kind mark picks what the list holds',
|
||||
listy.variables[0].items === 'text',
|
||||
listy.variables[0].items,
|
||||
);
|
||||
check('and the mark itself says what it holds now', (await find('≡Aa')) !== null);
|
||||
check(
|
||||
'and every value follows the declaration',
|
||||
JSON.stringify(listy.props.nums) === '["1","2"]',
|
||||
JSON.stringify(listy.props.nums),
|
||||
);
|
||||
await undo();
|
||||
await sleep(250);
|
||||
listy = await numsOf();
|
||||
check(
|
||||
'undo puts the kind and the values back',
|
||||
listy.variables[0].items === 'number' && JSON.stringify(listy.props.nums) === '[1,2]',
|
||||
JSON.stringify(listy.props.nums),
|
||||
);
|
||||
|
||||
// 7. A list whose target is gone empties the tab rather than showing a stale one.
|
||||
await mutate("__gm.state.openList({ kind: 'asset', assetId: 'listy', key: 'nums' })");
|
||||
await sleep(200);
|
||||
@@ -176,10 +204,73 @@ export async function listTab(t) {
|
||||
JSON.stringify(back),
|
||||
);
|
||||
|
||||
// 8b. The item shape is declared in the inspector as well, with the control a struct has – and
|
||||
// primitives only, because a list inside a list item is the one shape the model does not store.
|
||||
const cellsOf = () => json(`__gm.state.project.assets.find((a) => a.id === 'celly').props.cells`);
|
||||
check('a list of structs carries the add-a-field control', (await find('+ Field')) !== null);
|
||||
await clickText('+ Field');
|
||||
await sleep(150);
|
||||
await clickText('Boolean');
|
||||
await sleep(250);
|
||||
check(
|
||||
'+ Field grows every item of the list at once',
|
||||
JSON.stringify(await cellsOf()) ===
|
||||
JSON.stringify([
|
||||
{ x: 30, y: 11, field1: false },
|
||||
{ x: 44, y: 55, field1: false },
|
||||
]),
|
||||
JSON.stringify(await cellsOf()),
|
||||
);
|
||||
info = await json('__gm.list.info()');
|
||||
check(
|
||||
'and the tab shows the new field on every row',
|
||||
info.items[0] === 'x: 30, y: 11, field1: false',
|
||||
info.items[0],
|
||||
);
|
||||
await shot('06-item-field');
|
||||
await undo();
|
||||
await sleep(250);
|
||||
check(
|
||||
'undo takes it out of every item again',
|
||||
JSON.stringify(await cellsOf()) ===
|
||||
JSON.stringify([
|
||||
{ x: 30, y: 11 },
|
||||
{ x: 44, y: 55 },
|
||||
]),
|
||||
JSON.stringify(await cellsOf()),
|
||||
);
|
||||
|
||||
// 9. A node's unconnected `list` input carries its value in the very same params bag a setting
|
||||
// does, so it is a count button too – before this it offered a number field.
|
||||
await mutate(`
|
||||
const a = __gm.state.project.assets.find((x) => x.id === 'celly');
|
||||
a.graph.nodes.push({ id: 'counter', type: 'Count', x: 40, y: 40, params: {} });
|
||||
__gm.showTab('graph');
|
||||
__gm.state.select({ kind: 'node', assetId: 'celly', nodeId: 'counter', nodeIds: ['counter'] });
|
||||
__gm.state.preview();
|
||||
`);
|
||||
await sleep(300);
|
||||
const portButton = await find('0 items');
|
||||
check('an unconnected list input reads as an item count', portButton !== null);
|
||||
await click(portButton.x, portButton.y);
|
||||
await sleep(250);
|
||||
check('clicking it opens the List tab on the port', (await json('__gm.tab()')) === 'list');
|
||||
info = await json('__gm.list.info()');
|
||||
check('titled by the node and the port it belongs to', info.title === 'Count · list', info.title);
|
||||
await clickText('+ Item');
|
||||
await sleep(250);
|
||||
const params = await json(`__gm.state.project.assets.find((a) => a.id === 'celly').graph.nodes[0].params`);
|
||||
check(
|
||||
'and the item lands in the node params, where the node reads it',
|
||||
JSON.stringify(params) === '{"list":[0]}',
|
||||
JSON.stringify(params),
|
||||
);
|
||||
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
p.assets = p.assets.filter((a) => a.id !== 'celly');
|
||||
__gm.state.select(null);
|
||||
__gm.showTab('scene');
|
||||
__gm.state.preview();
|
||||
`);
|
||||
await sleep(200);
|
||||
|
||||
Reference in new issue
Block a user