feat(web): money formatting + hide-amounts, ported from iOS
This commit is contained in:
parent
0275aeaaaf
commit
ad215b9416
4 changed files with 67 additions and 0 deletions
14
src/ds/money.test.ts
Normal file
14
src/ds/money.test.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { describe, it, expect, beforeEach } from "vitest";
|
||||||
|
import { tugrik, tugrikShort, grouped, tugrikRaw, isHidden, setHidden, MASKED } from "./money";
|
||||||
|
|
||||||
|
beforeEach(() => { try { localStorage.clear(); } catch {} });
|
||||||
|
|
||||||
|
describe("money", () => {
|
||||||
|
it("groups tugrik", () => expect(tugrik("452200")).toBe("452,200₮"));
|
||||||
|
it("groups digits only", () => expect(grouped("500000")).toBe("500,000"));
|
||||||
|
it("abbreviates millions", () => expect(tugrikShort("1200000")).toBe("1,2сая₮"));
|
||||||
|
it("keeps sub-million as full", () => expect(tugrikShort("900000")).toBe("900,000₮"));
|
||||||
|
it("masks when hidden", () => { setHidden(true); expect(tugrik("100")).toBe(MASKED); });
|
||||||
|
it("raw never masks", () => { setHidden(true); expect(tugrikRaw("100")).toBe("100₮"); });
|
||||||
|
it("isHidden reflects storage", () => { setHidden(true); expect(isHidden()).toBe(true); });
|
||||||
|
});
|
||||||
23
src/ds/money.ts
Normal file
23
src/ds/money.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
export const MASKED = "••••••";
|
||||||
|
const KEY = "mercury.hideAmounts";
|
||||||
|
|
||||||
|
export function isHidden(): boolean {
|
||||||
|
try { return localStorage.getItem(KEY) === "true"; } catch { return false; }
|
||||||
|
}
|
||||||
|
export function setHidden(b: boolean): void {
|
||||||
|
try { localStorage.setItem(KEY, String(b)); } catch {}
|
||||||
|
}
|
||||||
|
function num(v: string | number): number {
|
||||||
|
return typeof v === "number" ? v : parseFloat(v || "0") || 0;
|
||||||
|
}
|
||||||
|
export function grouped(v: string | number): string {
|
||||||
|
return new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(num(v));
|
||||||
|
}
|
||||||
|
export function tugrikRaw(v: string | number): string { return `${grouped(v)}₮`; }
|
||||||
|
export function tugrikShortRaw(v: string | number): string {
|
||||||
|
const m = num(v) / 1_000_000;
|
||||||
|
if (m >= 1) return `${m.toFixed(1).replace(".", ",")}сая₮`;
|
||||||
|
return tugrikRaw(v);
|
||||||
|
}
|
||||||
|
export function tugrik(v: string | number): string { return isHidden() ? MASKED : tugrikRaw(v); }
|
||||||
|
export function tugrikShort(v: string | number): string { return isHidden() ? MASKED : tugrikShortRaw(v); }
|
||||||
6
vitest.config.ts
Normal file
6
vitest.config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: { environment: "jsdom", setupFiles: ["./vitest.setup.ts"], globals: true },
|
||||||
|
});
|
||||||
24
vitest.setup.ts
Normal file
24
vitest.setup.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
|
||||||
|
// Polyfill localStorage for jsdom if not available
|
||||||
|
if (typeof localStorage === "undefined") {
|
||||||
|
const store: Record<string, string> = {};
|
||||||
|
(global as any).localStorage = {
|
||||||
|
getItem: (key: string) => store[key] || null,
|
||||||
|
setItem: (key: string, value: string) => {
|
||||||
|
store[key] = String(value);
|
||||||
|
},
|
||||||
|
removeItem: (key: string) => {
|
||||||
|
delete store[key];
|
||||||
|
},
|
||||||
|
clear: () => {
|
||||||
|
for (const key in store) {
|
||||||
|
delete store[key];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: (index: number) => Object.keys(store)[index] || null,
|
||||||
|
get length() {
|
||||||
|
return Object.keys(store).length;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue