Skip to content

Component tests#5

Draft
opcode81 wants to merge 29 commits into
developfrom
component-tests
Draft

Component tests#5
opcode81 wants to merge 29 commits into
developfrom
component-tests

Conversation

@opcode81

Copy link
Copy Markdown

WIP; See the new memory

opcode81 added 29 commits June 5, 2026 12:00
Add test case covering virtually all cases in the diagram of
sync cases in the developer documentation.

Changes in test principles:
* transformations -> operations
* operations can include tests
* more well-defined roles of relevant shapes/items
  and more well-defined operations (particularly make-nested)
The propagation watcher (watch-component-changes) we start to exercise
automatic component sync also schedules thumbnail renders on every
component-changed event. Thumbnail rendering reaches dom/get-css-variable
-> window.getComputedStyle, which has no window in the headless test
runner. The render is async/queued, so it fired during a later test in
the full suite and crashed the run (each test passed in isolation).

Stub the public seam dwth/update-thumbnail to a no-op event via a :each
before/after fixture (the same set!-and-restore pattern as the wasm-mock
helpers, whose :after runs only after each async test's done). Scope is
limited to this namespace; other suites see the real function. Full sync
suite now stable: 8 tests, 120 assertions, green across repeated runs.
The sync-scenario operations tracked a single component lineage via flat
situation roles (:main-child-rect, :remote-child-rect, ...). Swap needs
several lineages to coexist (its target is a different component), and
swapping replaces a level's head in place. Replace the flat roles with
named component OBJECTS under :vars :components, each grouping that
lineage's fields (main-component-id, remote/main head+rect, nesting-count,
nesting-data, copies). Every level's nesting-data now also stores the
swap-stable :nested-head-parent (a swap keeps the parent, so assertions
re-resolve parent -> current head -> rect).

create-component/make-nested/instantiate-copy/reset-copy-instance/
propagate-to-copy now take a lineage name. target-shape-id accepts a
(situation -> id) function target so change-property can target a computed
object field at apply-time; add tm/shape-by-id and lineage accessors.

Case K migrated to the object API (88 assertions, unchanged). Full frontend
suite green (8 tests, 120 assertions). Common scenario scaffolding test
removed per the frontend-only testing policy; common suite green (7/30).
…vels)

swap-component(name, level, target, :keep-touched?) drives the production
generate-component-swap on a lineage's nesting level (its nested-head),
replacing it with the target lineage's component. On the frontend it dispatches
the real dwl/component-swap event, so the swap commits through the normal path
and the watcher AUTOMATICALLY propagates it to copies — including the deeper
nesting levels (each of which contains a copy derived from the swapped one).

level-rect/level-color re-resolve a level's rect via the swap-stable
nested-head-parent (parent -> current head -> rect), robust to the swap having
replaced the head in place (Penpot keeps the head id, rewrites it to the new
component, stamps a :swap-slot touched group).

Case L proves the "single swap in copy" property: a swap at level i surfaces at
level i AND the deeper levels via automatic propagation, while shallower levels
are untouched. Frontend suite green (9 tests, 123 assertions).
Build a 3-level nesting, then OPTIONALLY swap the nested component at each level
for a differently-coloured target, and assert the colour at every level. Sweeps
all 2^3 subsets of {swap level 0,1,2}.

Established empirically (on the frontend) the propagation rule: a swap at level i
auto-propagates to level i and every OUTER (higher-index) level, until a swap at
a higher level overrides it. So the colour at level i is the applied swap at the
highest index j <= i, else base. The assertion derives this with a backward
search over the applied swaps, matching the spec's precedence model.

Frontend suite green (9 tests, 144 assertions), stable across repeated runs.
Record case L (swap sweep) and the swap-component operation; refresh the
sync-scenario section to the component-OBJECT model (components dict,
nesting-data with the swap-stable nested-head-parent) it had drifted from;
note the swap propagation rule, the function-target form of target-shape-id,
shape-by-id, and the dwl/component-swap frontend realisation. Add variant
switch (keep-touched swap) as the next step and re-record the parked penpot#9304
regression-check reminder.
Extract a shared nesting helper (nest-in-new-outer-component) from make-nested,
parametrising the inner-instantiation step and the seek-rect origin, so the same
contain-outward mechanism serves both plain nesting and variant nesting.

Add three variant scenario ops:
- make-variant-container: builds a variant set synchronously via the add-variant
  test-helper idiom (container frame + member roots as children carrying the
  shared variant-id, then update-component stamps variant-id + variant-properties
  on each component) with explicit, chosen selector values.
- make-nested-component-with-variant: nests a chosen variant member via the
  shared helper.
- switch-variant: a frontend op driving the real variants-switch workspace event
  (discovers the sibling in the container, routes through component-swap
  keep-touched? true), with a structure-blind target (role | label | fn) and a
  nested-head-of target fn.

Case M (Design A): introduce one variant instance at the innermost level, wrap it
with plain nesting, and assert that switching the single variant head propagates
outward to every enclosing level — the variant-switch flavour of case L's swap.

Plain make-nested now seeks the current :main-rect (not the fixed origin) so the
nesting resolves whether prior nestings were plain or variant. All existing cases
(common 7/30, frontend B-F/I/K 88/L 24) remain green; case M is 6/0.
…l switch sweep

nested-head was set to the inner copy introduced at each level, but per spec it
should be the DEEPEST instance: the descendant of the inner copy head that
corresponds to the origin instance (remote-head). The bug was latent for case L
(single-layer nesting, where the two coincide) but surfaced for variant nesting,
where the variant sits below an outer wrapper at each level.

- Compute nested-head via self-or-descendant-corresponding-to (new helper that,
  unlike descendant-corresponding-to, also matches the head itself — needed at
  level 0 where the inner copy head IS the origin image).
- nest-in-new-outer-component now takes seek-head-id alongside seek-rect-id and
  seeks the FIXED deepest origin (:remote-head/:remote-rect) for both.
- make-nested-component-with-variant re-points :remote-head/:remote-rect to the
  variant member: nesting a variant makes that member the new deepest origin, so
  subsequent plain make-nested descends to the variant's image (its nested-head)
  at every level.

With nested-head now pointing at the variant at every level, switch-variant can
target any level, so case M becomes the full per-level sweep (case L's exact
precedence asserter) rather than a single innermost switch.

All cases remain green: common 7/30; frontend B-F/I, K 88, L 24, M now 24.
…iant op

Pure rename (record MakeNested -> MakeNestedComponent, constructor, the
instance? check in the frontend interpreter, all call sites, and comments).
Reads as a sibling of make-nested-component-with-variant; the bare name no
longer implies 'component' by omission. No behaviour change: common 7/30,
frontend K 88 / L 24 / M 24 (and others) remain green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant