151 lines
7.8 KiB
TypeScript
151 lines
7.8 KiB
TypeScript
import { GlobalRegistrator } from "@happy-dom/global-registrator";
|
|
if (!GlobalRegistrator.isRegistered) GlobalRegistrator.register();
|
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
import { expect, test } from "bun:test";
|
|
import { act } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { createSeedDataset } from "../../domain/seed.ts";
|
|
import { monthView } from "../../domain/views.ts";
|
|
import { store } from "../../store/index.ts";
|
|
import { MonthPieShowcase } from "./MonthPieShowcase.tsx";
|
|
|
|
test("pie colors survive filtering, clearing, purchase focus, and allocation sorting", () => {
|
|
localStorage.removeItem("budget2:show-month-breakdown");
|
|
const data = createSeedDataset("2026-09");
|
|
const posts = data.posts.slice(0, 3);
|
|
data.purchases = posts.map((post, index) => ({
|
|
id: `purchase-${index}`, date: "2026-09", description: `Purchase ${index}`,
|
|
total: { amount: 300 - index * 100, currency: "DKK" }, splitMode: "percent" as const,
|
|
splits: [{ postId: post.id, value: 100, absorbsRemainder: true }], schedule: null,
|
|
}));
|
|
data.posts.forEach(post => { post.rules = []; });
|
|
posts.forEach((post, index) => {
|
|
post.rules = [{ from: "2026-09", rule: { kind: "fixed", amount: { amount: (index + 1) * 100, currency: "DKK" } } }];
|
|
});
|
|
const original = { get: store.get, subscribe: store.subscribe };
|
|
store.get = () => data;
|
|
store.subscribe = () => () => {};
|
|
const container = document.createElement("div");
|
|
const root = createRoot(container);
|
|
const fills = () => new Map([...container.querySelectorAll("svg title")].map(title => [title.textContent!.split(":")[0], title.parentElement!.getAttribute("fill")]));
|
|
const checkbox = (index: number) => container.querySelector(`input[aria-label="Include ${posts[index]!.name}"]`) as HTMLInputElement;
|
|
const button = (label: string) => [...container.querySelectorAll("button")].find(item => item.textContent === label)!;
|
|
try {
|
|
act(() => root.render(<MonthPieShowcase view={monthView(data, "2026-09")} baseCurrency="DKK" />));
|
|
const initial = fills();
|
|
expect(new Set(initial.values()).size).toBe(3);
|
|
act(() => checkbox(0).click());
|
|
expect(fills().get(posts[1]!.name)).toBe(initial.get(posts[1]!.name));
|
|
expect(fills().get(posts[2]!.name)).toBe(initial.get(posts[2]!.name));
|
|
for (const index of [1, 2]) {
|
|
const dot = checkbox(index).parentElement!.querySelector('span[aria-hidden="true"]') as HTMLElement;
|
|
expect(dot.style.backgroundColor).toBe(initial.get(posts[index]!.name)!);
|
|
}
|
|
act(() => checkbox(1).click());
|
|
const purchaseColor = fills().get("Purchase 2");
|
|
expect(purchaseColor).toBeTruthy();
|
|
const purchaseDot = container.querySelector('ol span[aria-hidden="true"]') as HTMLElement;
|
|
expect(purchaseDot.style.backgroundColor).toBe(purchaseColor!);
|
|
act(() => button("Clear").click());
|
|
act(() => checkbox(2).click());
|
|
expect(fills().get("Purchase 2")).toBe(purchaseColor);
|
|
act(() => button("Select all").click());
|
|
expect(fills()).toEqual(initial);
|
|
act(() => button("Allocated").click());
|
|
expect(fills()).toEqual(initial);
|
|
} finally {
|
|
act(() => root.unmount());
|
|
Object.assign(store, original);
|
|
localStorage.removeItem("budget2:show-month-breakdown");
|
|
}
|
|
});
|
|
|
|
test("hiding the breakdown stays remembered after remount and leaves its control reachable", () => {
|
|
localStorage.removeItem("budget2:show-month-breakdown");
|
|
const data = createSeedDataset("2026-09");
|
|
const original = { get: store.get, subscribe: store.subscribe };
|
|
store.get = () => data;
|
|
store.subscribe = () => () => {};
|
|
const container = document.createElement("div");
|
|
let root = createRoot(container);
|
|
const render = () => root.render(<MonthPieShowcase view={monthView(data, "2026-09")} baseCurrency="DKK" />);
|
|
try {
|
|
act(render);
|
|
const toggle = container.querySelector('button[aria-controls]') as HTMLButtonElement;
|
|
expect(toggle).not.toBeNull();
|
|
expect(toggle.getAttribute("aria-expanded")).toBe("true");
|
|
act(() => toggle.click());
|
|
expect(container.querySelector('svg[role="img"]')).toBeNull();
|
|
act(() => root.unmount());
|
|
root = createRoot(container);
|
|
act(render);
|
|
const remembered = container.querySelector('button[aria-controls]') as HTMLButtonElement;
|
|
expect(remembered.getAttribute("aria-expanded")).toBe("false");
|
|
act(() => remembered.click());
|
|
expect(container.querySelector('svg[role="img"]')).not.toBeNull();
|
|
} finally {
|
|
act(() => root.unmount());
|
|
Object.assign(store, original);
|
|
localStorage.removeItem("budget2:show-month-breakdown");
|
|
}
|
|
});
|
|
|
|
test("pie showcase switches between spending and allocations", () => {
|
|
const data = createSeedDataset("2026-09");
|
|
data.posts[0]!.rules = [{ from: "2026-09", rule: { kind: "fixed", amount: { amount: 250, currency: "DKK" } } }];
|
|
data.months[0]!.income = { amount: 1000, currency: "DKK" };
|
|
data.purchases.push({ id: "p", date: "2026-09", description: "Games", total: { amount: 50, currency: "DKK" }, splitMode: "percent", splits: [{ postId: data.posts[0]!.id, value: 100, absorbsRemainder: true }], schedule: null });
|
|
const original = { get: store.get, subscribe: store.subscribe };
|
|
store.get = () => data;
|
|
store.subscribe = () => () => {};
|
|
const container = document.createElement("div");
|
|
const root = createRoot(container);
|
|
try {
|
|
act(() => root.render(<MonthPieShowcase view={monthView(data, "2026-09")} baseCurrency="DKK" />));
|
|
expect(container.textContent).toContain("50.00 DKK");
|
|
const allocation = [...container.querySelectorAll("button")].find((button) => button.textContent === "Allocated")!;
|
|
act(() => allocation.click());
|
|
expect(allocation.getAttribute("aria-pressed")).toBe("true");
|
|
expect(container.textContent).toContain("250.00 DKK");
|
|
expect(container.querySelector("svg[role='img']")).not.toBeNull();
|
|
} finally {
|
|
act(() => root.unmount());
|
|
Object.assign(store, original);
|
|
}
|
|
});
|
|
|
|
test("post checkboxes filter spending, reveal purchase detail, and stay reachable with none selected", () => {
|
|
localStorage.removeItem("budget2:show-month-breakdown");
|
|
const data = createSeedDataset("2026-09");
|
|
data.purchases = [
|
|
{ id: "small", date: "2026-09", description: "Small game", total: { amount: 50, currency: "DKK" }, splitMode: "percent", splits: [{ postId: data.posts[0]!.id, value: 100, absorbsRemainder: true }], schedule: null },
|
|
{ id: "large", date: "2026-09", description: "Large shop", total: { amount: 900, currency: "DKK" }, splitMode: "percent", splits: [{ postId: data.posts[1]!.id, value: 100, absorbsRemainder: true }], schedule: null },
|
|
];
|
|
const original = { get: store.get, subscribe: store.subscribe };
|
|
store.get = () => data;
|
|
store.subscribe = () => () => {};
|
|
const container = document.createElement("div");
|
|
const root = createRoot(container);
|
|
try {
|
|
act(() => root.render(<MonthPieShowcase view={monthView(data, "2026-09")} baseCurrency="DKK" />));
|
|
const food = container.querySelector('input[aria-label="Include Food"]') as HTMLInputElement;
|
|
act(() => food.click());
|
|
expect(container.querySelector('svg title')?.textContent).toContain("Small game");
|
|
expect(container.textContent).toContain("Purchases in Video Games");
|
|
const games = container.querySelector('input[aria-label="Include Video Games"]') as HTMLInputElement;
|
|
act(() => games.click());
|
|
expect(container.textContent).toContain("No posts selected");
|
|
expect(container.querySelectorAll('input[type="checkbox"]')).toHaveLength(2);
|
|
const all = [...container.querySelectorAll("button")].find(button => button.textContent === "Select all")!;
|
|
act(() => all.click());
|
|
expect(food.checked).toBe(true);
|
|
expect(games.checked).toBe(true);
|
|
expect(container.textContent).toContain("950.00 DKK");
|
|
} finally {
|
|
act(() => root.unmount());
|
|
Object.assign(store, original);
|
|
localStorage.removeItem("budget2:show-month-breakdown");
|
|
}
|
|
});
|