mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-26 01:34:51 +08:00
Add a Settings tab to the usage page that replaces the Pricing tab: an amount display section on top (currency + hand-entered exchange rate) and the existing pricing configuration below. - Display currencies: USD, CNY, EUR, JPY, TWD, TRY; USD stays the default. Rates are entered manually; untouched preset rates are not written to settings.json so later preset updates still reach those users. - Costs stay stored in USD; conversion only happens at display time. Converted amounts carry a "≈" prefix and hovers add the USD value. - Chart data is converted before plotting so axis ticks stay round. - Yen drops two decimals, and usage amounts now group thousands. - The sidebar "Today" amount drops decimals from 100 up so it no longer squeezes the nav label. - Settings are device-local (`usageDisplay` in ~/.cc-switch/settings.json).
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
import { UsageDisplaySection } from "@/components/usage/UsageDisplaySection";
|
|
|
|
const pickCurrency = async (
|
|
user: ReturnType<typeof userEvent.setup>,
|
|
code: string,
|
|
) => {
|
|
await user.click(
|
|
screen.getByRole("combobox", { name: "usage.display.currency" }),
|
|
);
|
|
await user.click(
|
|
await screen.findByRole("option", { name: new RegExp(`^${code}`) }),
|
|
);
|
|
};
|
|
|
|
describe("UsageDisplaySection", () => {
|
|
// jsdom 没有 scrollIntoView,下拉打开时会调
|
|
beforeAll(() => {
|
|
Element.prototype.scrollIntoView = vi.fn();
|
|
});
|
|
|
|
it("offers every display currency and hides the rate while showing USD", async () => {
|
|
const user = userEvent.setup();
|
|
const onChange = vi.fn();
|
|
render(<UsageDisplaySection value={undefined} onChange={onChange} />);
|
|
|
|
expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
|
|
await user.click(
|
|
screen.getByRole("combobox", { name: "usage.display.currency" }),
|
|
);
|
|
const options = await screen.findAllByRole("option");
|
|
expect(options.map((o) => o.textContent?.slice(0, 3))).toEqual([
|
|
"USD",
|
|
"CNY",
|
|
"EUR",
|
|
"JPY",
|
|
"TWD",
|
|
"TRY",
|
|
]);
|
|
await user.keyboard("{Escape}");
|
|
|
|
await pickCurrency(user, "JPY");
|
|
expect(onChange).toHaveBeenCalledWith({ currency: "JPY" });
|
|
// 预置汇率:用户没填过时从这里起步
|
|
expect(screen.getByRole("textbox")).toHaveValue("158");
|
|
});
|
|
|
|
it("rejects a bad rate and keeps the rates of other currencies", async () => {
|
|
const user = userEvent.setup();
|
|
const onChange = vi.fn();
|
|
render(
|
|
<UsageDisplaySection
|
|
value={{ currency: "CNY", exchangeRates: { CNY: 7, EUR: 0.9 } }}
|
|
onChange={onChange}
|
|
/>,
|
|
);
|
|
const input = screen.getByRole("textbox");
|
|
expect(input).toHaveValue("7");
|
|
|
|
await user.clear(input);
|
|
await user.type(input, "0{Enter}");
|
|
expect(screen.getByText("usage.display.rateInvalid")).toBeInTheDocument();
|
|
expect(input).toHaveAttribute("aria-invalid", "true");
|
|
expect(onChange).not.toHaveBeenCalled();
|
|
|
|
await user.clear(input);
|
|
await user.type(input, "7.25{Enter}");
|
|
expect(onChange).toHaveBeenCalledWith({
|
|
currency: "CNY",
|
|
exchangeRates: { CNY: 7.25, EUR: 0.9 },
|
|
});
|
|
});
|
|
|
|
it("switches the rate box to the rate saved for the picked currency", async () => {
|
|
const user = userEvent.setup();
|
|
const onChange = vi.fn();
|
|
render(
|
|
<UsageDisplaySection
|
|
value={{ currency: "CNY", exchangeRates: { CNY: 7, EUR: 0.9 } }}
|
|
onChange={onChange}
|
|
/>,
|
|
);
|
|
await pickCurrency(user, "EUR");
|
|
expect(screen.getByRole("textbox")).toHaveValue("0.9");
|
|
});
|
|
|
|
it("does not write the preset rate when it was left unchanged", async () => {
|
|
const user = userEvent.setup();
|
|
const onChange = vi.fn();
|
|
render(
|
|
<UsageDisplaySection value={{ currency: "TWD" }} onChange={onChange} />,
|
|
);
|
|
await user.type(screen.getByRole("textbox"), "{Enter}");
|
|
expect(onChange).not.toHaveBeenCalled();
|
|
});
|
|
});
|