80ed11e3b6
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>
133 lines
4.0 KiB
TypeScript
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();
|
|
});
|
|
});
|