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>
This commit is contained in:
@@ -44,4 +44,89 @@ describe("SelectionStore", () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user