Add headless regression tests for Prop Editor functionality

- Implement tests for PropFile, including preset conversion, file dictionary conversion, round-trip serialization, version rejection, default values for missing keys, flattening, sanitization, and save/load functionality.
- Create tests for PropEditor, covering window title formatting, mode button text, safe saving, and recent file list management.
- Add tests for PropTheme, validating fallback behavior, invalid input handling, and default values for primitives and UI tokens.
- Introduce tests for ThumbnailCache, ensuring proper key formatting, PNG handling, and behavior with missing files.
This commit is contained in:
2026-09-19 21:26:03 -04:00
parent db4ff8d939
commit b66ded5f60
34 changed files with 7021 additions and 2 deletions
+51
View File
@@ -0,0 +1,51 @@
# Phase 9 Prop Editor — Round 6 (Recent-on-save, Reshaped Flag, Selection Contrast)
Status: **implemented** (2026-09-19; direct round — no open design choices).
Source: three user feedback points against the shipped Round 5 Prop Editor.
## Feedback summary
1. Saving a file should add it to File → Recent when it isn't there yet.
2. A circle's inspector Radius field becomes meaningless once the circle's vertices are
reshaped; every primitive needs a `reshaped` flag so primitive-specific inspector fields
hide/disable after reshaping.
3. A white shape in front of a white shape makes the white selection outline/box invisible;
the selection color should contrast with the shape's fill.
## Design decisions
- **Recent-on-save:** `_save_to()` calls `_push_recent(path)` on success (dedupe +
front-insert + trim, same helper as load — a save is recent activity).
- **`reshaped` flag:** new boolean on every shape dict (internal + export + `.prop` schema,
default `false`; additive and backward compatible — old files load as `false`). Set to
`true` by: `insert_vertex`, `remove_vertex`, vertex-drag motion, `create_point_on_edge`
(via insert), and **non-uniform scaling of a circle** (both the gizmo drag and
`scale_selected`); cleared by `set_shape_radius` (which regenerates points from the radius).
Translate/rotate/mirror/uniform-scale do **not** set it — they preserve primitive-ness.
The inspector hides the Radius row and shows a muted "✏️ This shape was reshaped" note when
the selected shape's flag is set (the flag exists on every primitive so future
primitive-specific fields can gate on it the same way).
- **Selection contrast:** new `PropCanvas.contrast_color_for(fill_hex) -> Color` — dark
(`#1F1F1F`) for light fills (luminance > 0.5), white otherwise. Applied to the selection
polyline and all gizmo drawing (bbox, rotation dot, scale crosses), which were previously
hard white and vanished on white shapes. Vertex handles stay fill-colored (stickman-editor
parity, Round 2 decision); the Create-Point flash stays yellow.
## Changes
| Target | Change |
| --- | --- |
| `scripts/prop_editor.gd` | `_save_to` pushes the path to recents on success; `_reshaped_note` label; `_refresh_inspector_from_selection` gates `_radius_row.visible` on `not reshaped` and shows the note when reshaped. |
| `scripts/prop_canvas.gd` | `"reshaped"` in `_make_shape_dict`/`_coerce_shape`/`_export_shape`; mutation sites set/clear it; `contrast_color_for` static; selection polyline + gizmos use the contrast color. |
| `scripts/prop_file.gd` | `"reshaped"` serialized/parsed (default `false`). |
| `tests/test_prop_editor_canvas.gd` | New `_test_reshaped_flag` (insert/remove/radius-reset/scale/mirror/translate/rotate matrix) and `_test_selection_contrast`. |
| `tests/test_prop_editor_file.gd` | Round-trip `reshaped` preservation + default-false assertions. |
## Verification
- `--check-only` clean; all five suites green (file 68, theme 53, canvas 100, thumb 10,
round3 21).
- Manual F6: save a new prop → appears in File → 🕑 Recent; reshape a circle (drag a vertex,
create a point, or stretch non-uniformly) → Radius row disappears + muted note; select a
white shape → dark outline/box/dot/crosses.