Files
feuerwerk/tests/editor/asset-groups.test.ts
T
adminandClaude Opus 5.5 5e09ba9a50 Group the asset panel by category, then by type
The asset panel listed one heading per type, 30 of them side by side.
Now the types sit under five categories, and each type heading reads
like its entry in the + menu.

- ASSET_GROUPS replaces ASSET_GROUP_LABELS and gives every type a name
  and a category: Basics, Visuals, Logics, Physics or Audio.
- The categories keep that fixed order. The groups in a category and
  the assets in a group stay sorted by name.
- A category heading folds like a group heading. A project opens with
  every category open and every group folded, as before.
- Selecting an asset unfolds its category and its group. Folding the
  category of the selected asset lets go of the selection.
- The headings are singular now (Scene, Sprite, Sound FX). The old
  plural names only kept them apart from the + menu entries.
- locate looks in the popup layer first. A click by text meant for a
  menu entry hit the heading of the same name, since the panel is drawn
  first. Below an open menu a click only closes the menu anyway.
- New test hook __gm.assetCategories().

Tests:
- Unit asset-groups: 6 green, counter-check (empty categories kept)
  1 red
- e2e 04: ALL PASSED, 63 checks (1 new). Counter-check with locate
  taking the first hit again: 2 red, the click on Behavior created
  no asset
- Gate: CHECK EXIT 0, 261 files, 4040 tests
- The category names got their final plural form after these runs.
  The unit test ran again on them (6 green), e2e 04 did not.

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

72 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { Asset } from '../../src/core/model/Asset';
import { ASSET_TYPES, type AssetType } from '../../src/core/model/AssetTypes';
import { ASSET_CATEGORIES, ASSET_GROUPS, groupAssets } from '../../src/editor/panels/assetGroups';
let serial = 0;
const asset = (type: AssetType, name: string): Asset =>
({ id: `a${++serial}`, type, name, props: {} }) as unknown as Asset;
const groupsOf = (assets: readonly Asset[]) => groupAssets(assets).flatMap((c) => c.groups);
const shape = (assets: Asset[]) => groupsOf(assets).map((g) => [g.type, g.assets.map((a) => a.name)]);
describe('groupAssets (#176)', () => {
it('puts every asset into the group of its type, and no empty group', () => {
const assets = [asset('texture', 'Sheet'), asset('sprite', 'Hero'), asset('texture', 'Tiles')];
expect(shape(assets)).toEqual([
['sprite', ['Hero']],
['texture', ['Sheet', 'Tiles']],
]);
});
it('puts the groups under their category, categories in their fixed order and none empty', () => {
const assets = [
asset('song', 'Tune'),
asset('behavior', 'Mover'),
asset('scene', 'Level'),
asset('sprite', 'Hero'),
];
expect(groupAssets(assets).map((c) => [c.category, c.groups.map((g) => g.type)])).toEqual([
['Basics', ['scene']],
['Visuals', ['sprite']],
['Logics', ['behavior']],
['Audio', ['song']],
]);
});
it('sorts the groups in a category by their heading, not by type or declaration order', () => {
const types = [...ASSET_TYPES].reverse();
const categories = groupAssets(types.map((t) => asset(t, 'x')));
expect(categories.map((c) => c.category)).toEqual([...ASSET_CATEGORIES]);
for (const { groups } of categories) {
const headings = groups.map((g) => ASSET_GROUPS[g.type].name);
expect(headings).toEqual([...headings].sort((a, b) => a.localeCompare(b)));
}
expect(categories.flatMap((c) => c.groups)).toHaveLength(ASSET_TYPES.length);
// By heading: "State machine" sorts under S, the type key `statemachine` would not tell.
const logic = categories
.find((c) => c.category === 'Logics')!
.groups.map((g) => ASSET_GROUPS[g.type].name);
expect(logic.indexOf('Behavior')).toBeLessThan(logic.indexOf('State machine'));
expect(logic.indexOf('State machine')).toBeLessThan(logic.indexOf('Struct'));
});
it('sorts the assets in a group by name, ignoring case and reading numbers as numbers', () => {
const assets = ['Level 10', 'level 2', 'Boss', 'Level 1', 'arena'].map((n) => asset('scene', n));
expect(shape(assets)).toEqual([['scene', ['arena', 'Boss', 'Level 1', 'level 2', 'Level 10']]]);
});
it('keeps the project order among equal names', () => {
const first = asset('sprite', 'Coin');
const second = asset('sprite', 'coin');
expect(groupsOf([first, second])[0]!.assets).toEqual([first, second]);
expect(groupsOf([second, first])[0]!.assets).toEqual([second, first]);
});
it('leaves the project list alone', () => {
const assets = [asset('sprite', 'B'), asset('sprite', 'A')];
groupAssets(assets);
expect(assets.map((a) => a.name)).toEqual(['B', 'A']);
});
});