ui/phase-22: skip the no-op stance click in the races table
Clicking the already-active WAR/PEACE button still appended a \`setDiplomaticStance\` whose \`relation\` matched the row's current value. The engine would accept the duplicate harmlessly, but the order tab inflates with rows that say nothing and every auto-sync re-ships the redundant payload. Compare against the overlayed stance (so a queued-but-not-applied change suppresses a re-click that matches the *intended* state, not just the server snapshot) and short-circuit when they agree. Mirrors the vote picker, which already had the same guard. vitest.config.ts: \`mergeConfig\` refuses callback-form base configs, so resolve \`vite.config.ts\`'s callback with the test context first and merge the plain object. Surfaced after the \`loadEnv\` migration switched the root config to the callback form. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -148,6 +148,15 @@ data fetching is performed here — the layout is responsible.
|
|||||||
|
|
||||||
async function setStance(acceptor: string, relation: Relation): Promise<void> {
|
async function setStance(acceptor: string, relation: Relation): Promise<void> {
|
||||||
if (draft === undefined) return;
|
if (draft === undefined) return;
|
||||||
|
// No-op when the row already reflects the requested stance — the
|
||||||
|
// engine would accept the duplicate, but queueing a wire entry
|
||||||
|
// that re-states the current state inflates the order tab and
|
||||||
|
// the auto-sync envelope for nothing. The current stance reads
|
||||||
|
// off the overlay (`races[i].relation`), so a queued-but-not-
|
||||||
|
// applied stance change correctly suppresses a duplicate click
|
||||||
|
// that matches the *queued* intent, not just the server snapshot.
|
||||||
|
const current = races.find((r) => r.name === acceptor)?.relation;
|
||||||
|
if (current === relation) return;
|
||||||
await draft.add({
|
await draft.add({
|
||||||
kind: "setDiplomaticStance",
|
kind: "setDiplomaticStance",
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
|
|||||||
@@ -226,6 +226,18 @@ describe("races table", () => {
|
|||||||
expect(names).toEqual(["Beta", "Gamma", "Alpha"]);
|
expect(names).toEqual(["Beta", "Gamma", "Alpha"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("clicking the already-active stance is a no-op (no command queued)", async () => {
|
||||||
|
const ui = mountTable(
|
||||||
|
makeReport([race({ name: "Andori", relation: "WAR" })]),
|
||||||
|
);
|
||||||
|
await fireEvent.click(ui.getByTestId("races-stance-war"));
|
||||||
|
// Give the async handler one microtask to settle, then assert
|
||||||
|
// the draft remained empty — the click matched the current
|
||||||
|
// stance, so nothing should land in the order queue.
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(draft.commands).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
test("clicking PEACE on a WAR row appends setDiplomaticStance and flips the overlay", async () => {
|
test("clicking PEACE on a WAR row appends setDiplomaticStance and flips the overlay", async () => {
|
||||||
const ui = mountTable(
|
const ui = mountTable(
|
||||||
makeReport([race({ name: "Andori", relation: "WAR" })]),
|
makeReport([race({ name: "Andori", relation: "WAR" })]),
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { defineConfig, mergeConfig } from "vitest/config";
|
import { defineConfig, mergeConfig } from "vitest/config";
|
||||||
import viteConfig from "./vite.config";
|
import viteConfig from "./vite.config";
|
||||||
|
|
||||||
export default mergeConfig(
|
// `vite.config.ts` exports a `defineConfig(({ mode }) => …)` callback
|
||||||
viteConfig,
|
// so the dev server can load `.env*` files through Vite's `loadEnv`.
|
||||||
defineConfig({
|
// `mergeConfig` does not accept callback-form configs, so resolve the
|
||||||
|
// callback here with the same context Vitest would supply, then hand
|
||||||
|
// the plain object to the merge.
|
||||||
|
export default defineConfig(async (ctx) => {
|
||||||
|
const resolved =
|
||||||
|
typeof viteConfig === "function" ? await viteConfig(ctx) : viteConfig;
|
||||||
|
return mergeConfig(resolved, {
|
||||||
resolve: {
|
resolve: {
|
||||||
// Force the browser entry of Svelte so `mount` is available in jsdom.
|
// Force the browser entry of Svelte so `mount` is available in jsdom.
|
||||||
conditions: ["browser"],
|
conditions: ["browser"],
|
||||||
@@ -14,5 +20,5 @@ export default mergeConfig(
|
|||||||
globals: true,
|
globals: true,
|
||||||
setupFiles: ["./tests/setup.ts"],
|
setupFiles: ["./tests/setup.ts"],
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
);
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user