Files
feuerwerk/tests/editor/scene-visibility.test.ts
T
adminandClaude Opus 5.5 0c41cb97b1
game-maker / gate (push) Successful in 3m43s
game-maker / suite (push) Failing after 11m7s
Clip the children of an object to a clip area (#260)
An inventory grid could not scroll, because nothing could cut a child
off at an edge. A game also had no way to hear the mouse wheel. Both
are needed for a scrolling list, a dialog text or a window with more
content than room.

- A new object type Clip area has width and height and turns, scales
  and mirrors with its parent. The size gizmo drags width and height.
- Everything below a clip area, on every level, is drawn only inside
  its rectangle, in the Scene view as in the game. Nested areas cut to
  their intersection. The rule works on a convex polygon, so a turned
  area cuts right as well.
- On click, Is hovered and On mouse wheel do not hit a cut-off part.
  The Scene view does not pick it either.
- In the Scene view the area is a dashed frame that is grabbed only at
  its edge, so a click inside lands on the content. The eye under
  Shape hides the frame. In the game the area is invisible.
- While an object below an area or one of its parents is selected, the
  Scene view shows the cut-off part at 30 % opacity and it can be
  grabbed there. The rule lives in core/properties/clip.ts.
- The Properties node knows the type Clip area.
- The new event node On mouse wheel fires by the rule of On click. Its
  delta counts in notches, positive is down, and a frame's turns
  arrive as one value. It also gives x and y.

Limits:

- Only sprites and texts show the faded rest. Tilemaps and particles
  are cut and can be grabbed when open, but are not shown faded.
- Masks by texture shape are out, as noted in the issue.

Tests:

- ./check.sh: 2773 tests, CHECK EXIT 0.
- Full e2e run (local): ALL PASSED, 1549 checks, measured.
- tests/core/clip-areas.test.ts: 12 tests for the cut rule, scene and
  game, save and load, hits, the wheel with and without owner, the
  notch conversion and the open rule.
- e2e 58, 5 checks with real mouse and wheel: the scene cuts (74 of 74
  red inside, 0 outside), a click on the cut part picks nothing, the
  selected rest is faded and grabbable, the game cuts, one notch
  logs 1.
- e2e 04 and 46 count the object types and the eye rows. They now
  include Clip area.
- Counter-checks: 6 in unit tests and 3 in the browser, all fail as
  expected. The originals were restored and checked with cmp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 07:08:00 +02:00

110 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* What the scene view shows of each placeable type – symbol, name, a collider's outline. It lives
* in the project like the game scale: saved, but never an undo step.
*/
import { describe, expect, it } from 'vitest';
import { createEmptyProject } from '../../src/core/model/Project';
import {
defaultSceneVisibility,
sceneVisibilityOf,
shows,
VISIBILITY_TYPES,
visibilityParts,
} from '../../src/core/model/sceneVisibility';
import { deserializeProject, serializeProject } from '../../src/core/store/serialize';
import { EditorState } from '../../src/editor/state';
function loaded() {
const state = new EditorState();
state.loadProject(createEmptyProject('p', 'P'), 1);
return state;
}
describe('scene visibility', () => {
it('lists every placeable type, Behavior to Tilemap', () => {
expect(VISIBILITY_TYPES).toEqual([
'behavior',
'camera',
'clip',
'collider',
'container',
'particles',
'rigidbody',
'sprite',
'text',
'tilemap',
]);
});
it('gives a symbol switch only to types with a symbol, and an outline switch to what collides', () => {
expect(visibilityParts('behavior')).toEqual(['symbol', 'label']);
expect(visibilityParts('collider')).toEqual(['symbol', 'label', 'shape']);
expect(visibilityParts('sprite')).toEqual(['label']);
expect(visibilityParts('tilemap')).toEqual(['label', 'shape']);
});
it('shows what it always showed in a new project and in one written before there was any', () => {
for (const visibility of [
createEmptyProject('p', 'P').sceneVisibility,
deserializeProject({ id: 'p', name: 'P', assets: [] }).sceneVisibility,
]) {
expect(shows(visibility, 'behavior', 'symbol')).toBe(true);
expect(shows(visibility, 'behavior', 'label')).toBe(true);
expect(shows(visibility, 'collider', 'shape')).toBe(true);
expect(shows(visibility, 'tilemap', 'shape')).toBe(true);
expect(shows(visibility, 'sprite', 'label')).toBe(false);
expect(shows(visibility, 'sprite', 'shape')).toBe(true);
}
});
it('survives a save and a load', () => {
const project = createEmptyProject('p', 'P');
project.sceneVisibility.collider.shape = false;
project.sceneVisibility.tilemap.shape = false;
project.sceneVisibility.rigidbody.label = false;
project.sceneVisibility.sprite.label = true;
const back = deserializeProject(JSON.parse(JSON.stringify(serializeProject(project)))).sceneVisibility;
expect(back.collider.shape).toBe(false);
expect(back.tilemap.shape).toBe(false);
expect(back.rigidbody.label).toBe(false);
expect(back.sprite.label).toBe(true);
expect(back.behavior).toEqual(defaultSceneVisibility().behavior);
});
it('reads untrusted JSON part by part, the rest as the defaults', () => {
const read = sceneVisibilityOf({
sceneVisibility: { camera: { symbol: false, label: 'no' }, texture: { label: true }, text: 3 },
});
expect(read.camera).toEqual({ symbol: false, label: true, shape: true });
expect(read.text).toEqual(defaultSceneVisibility().text);
expect('texture' in read).toBe(false);
expect(sceneVisibilityOf(null)).toEqual(defaultSceneVisibility());
});
it('never hides what a type does not have to hide', () => {
const visibility = defaultSceneVisibility();
visibility.sprite.symbol = false;
visibility.sprite.shape = false;
expect(shows(visibility, 'sprite', 'shape')).toBe(true);
visibility.behavior.shape = false;
expect(shows(visibility, 'behavior', 'shape')).toBe(true);
});
it('saves a switch without an undo step', () => {
const state = loaded();
state.setSceneVisibility('collider', 'shape', false);
expect(state.project.sceneVisibility.collider.shape).toBe(false);
expect(state.saveStatus).toBe('dirty');
expect(state.commands.canUndo).toBe(false);
});
it('keeps a switch flipped while playing after Stop', () => {
const state = loaded();
state.enterPlay();
state.setSceneVisibility('behavior', 'label', false);
expect(state.project.sceneVisibility.behavior.label).toBe(false);
state.exitPlay();
expect(state.project.sceneVisibility.behavior.label).toBe(false);
});
});