Files
galaxy-game/ui/frontend/tests/selection-store.test.ts
T
Ilia Denisov 80ed11e3b6
Tests · UI / test (push) Waiting to run
Tests · UI / test (pull_request) Successful in 2m45s
feat(ui): F8-10 — tables planets / ship-groups / fleets, ship-classes delete guard (#53)
Lights up three previously-stubbed table active views and tightens the
existing one:

  - table-planets: 4 kind checkboxes (own / foreign / uninhabited /
    unknown) + race dropdown that filters the foreign slice; row click
    selects + centres the planet on the map.
  - table-ship-groups: local + foreign groups in one grid, owner
    checkboxes, planet dropdown (destination OR origin), class
    dropdown; on-planet click focuses the destination planet, in-space
    click focuses the ship group itself (camera follows interpolated
    position).
  - table-fleets: own fleets only with the shared planet dropdown;
    on-planet click focuses the planet, in-space click centres the
    camera on the interpolated fleet position without altering the
    selection (no fleet variant in Selected).
  - table-ship-classes: per-row Delete is disabled with a count tooltip
    while at least one local ship group references the class. The
    engine refuses the removal anyway; the UI pre-empts the surface.

Wires the click → map flow through a transient `SelectionStore.focus`
/ `focusPoint` channel that `map.svelte` consumes once on mount —
in-memory only, so an F5 does not re-centre.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:35:38 +02:00

133 lines
4.0 KiB
TypeScript

// SelectionStore unit tests. The store is in-memory only and carries
// no async lifecycle, so the cases focus on the rune transitions and
// the post-`dispose` no-op contract.
import { describe, expect, test } from "vitest";
import { SelectionStore } from "../src/lib/selection.svelte";
describe("SelectionStore", () => {
test("initial state has no selection", () => {
const store = new SelectionStore();
expect(store.selected).toBeNull();
});
test("selectPlanet records the planet id", () => {
const store = new SelectionStore();
store.selectPlanet(42);
expect(store.selected).toEqual({ kind: "planet", id: 42 });
});
test("selectPlanet replaces the previous selection", () => {
const store = new SelectionStore();
store.selectPlanet(1);
store.selectPlanet(2);
expect(store.selected).toEqual({ kind: "planet", id: 2 });
});
test("clear resets the selection to null", () => {
const store = new SelectionStore();
store.selectPlanet(7);
store.clear();
expect(store.selected).toBeNull();
});
test("dispose blocks subsequent mutations", () => {
const store = new SelectionStore();
store.selectPlanet(3);
store.dispose();
expect(store.selected).toBeNull();
store.selectPlanet(4);
expect(store.selected).toBeNull();
store.clear();
expect(store.selected).toBeNull();
});
test("focus sets the selection and queues the pending focus", () => {
const store = new SelectionStore();
store.focus({ kind: "planet", id: 11 });
expect(store.selected).toEqual({ kind: "planet", id: 11 });
expect(store.consumePendingFocus()).toEqual({ kind: "planet", id: 11 });
});
test("focus also works for a ship group target", () => {
const store = new SelectionStore();
store.focus({ kind: "shipGroup", ref: { variant: "local", id: "abc" } });
expect(store.selected).toEqual({
kind: "shipGroup",
ref: { variant: "local", id: "abc" },
});
expect(store.consumePendingFocus()).toEqual({
kind: "shipGroup",
ref: { variant: "local", id: "abc" },
});
});
test("consumePendingFocus clears the queued request", () => {
const store = new SelectionStore();
store.focus({ kind: "planet", id: 1 });
expect(store.consumePendingFocus()).not.toBeNull();
expect(store.consumePendingFocus()).toBeNull();
});
test("consumePendingFocus returns null when no focus was queued", () => {
const store = new SelectionStore();
store.selectPlanet(5);
expect(store.consumePendingFocus()).toBeNull();
});
test("clear leaves any queued pending focus untouched", () => {
const store = new SelectionStore();
store.focus({ kind: "planet", id: 9 });
store.clear();
expect(store.selected).toBeNull();
expect(store.consumePendingFocus()).toEqual({ kind: "planet", id: 9 });
});
test("dispose drops a queued pending focus", () => {
const store = new SelectionStore();
store.focus({ kind: "planet", id: 2 });
store.dispose();
expect(store.consumePendingFocus()).toBeNull();
});
test("focus is a no-op after dispose", () => {
const store = new SelectionStore();
store.dispose();
store.focus({ kind: "planet", id: 7 });
expect(store.selected).toBeNull();
expect(store.consumePendingFocus()).toBeNull();
});
test("focusPoint queues a coord without touching selection", () => {
const store = new SelectionStore();
store.selectPlanet(1);
store.focusPoint(12, 34);
expect(store.selected).toEqual({ kind: "planet", id: 1 });
expect(store.consumePendingCenter()).toEqual({ x: 12, y: 34 });
});
test("consumePendingCenter clears the queued point", () => {
const store = new SelectionStore();
store.focusPoint(5, 7);
expect(store.consumePendingCenter()).toEqual({ x: 5, y: 7 });
expect(store.consumePendingCenter()).toBeNull();
});
test("dispose drops a queued pending centre", () => {
const store = new SelectionStore();
store.focusPoint(1, 2);
store.dispose();
expect(store.consumePendingCenter()).toBeNull();
});
test("focusPoint is a no-op after dispose", () => {
const store = new SelectionStore();
store.dispose();
store.focusPoint(1, 2);
expect(store.consumePendingCenter()).toBeNull();
});
});