diff --git a/.yarn/cache/aveta-npm-1.5.2-2bc9533fb8-48381cbc42.zip b/.yarn/cache/aveta-npm-1.5.2-2bc9533fb8-48381cbc42.zip deleted file mode 100644 index c69a12e5b..000000000 Binary files a/.yarn/cache/aveta-npm-1.5.2-2bc9533fb8-48381cbc42.zip and /dev/null differ diff --git a/.yarn/cache/cliui-npm-8.0.1-3b029092cf-eaa5561aeb.zip b/.yarn/cache/cliui-npm-8.0.1-3b029092cf-eaa5561aeb.zip deleted file mode 100644 index 8701b6450..000000000 Binary files a/.yarn/cache/cliui-npm-8.0.1-3b029092cf-eaa5561aeb.zip and /dev/null differ diff --git a/.yarn/cache/get-caller-file-npm-2.0.5-80e8a86305-b9769a836d.zip b/.yarn/cache/get-caller-file-npm-2.0.5-80e8a86305-b9769a836d.zip deleted file mode 100644 index 0aa2c9cd0..000000000 Binary files a/.yarn/cache/get-caller-file-npm-2.0.5-80e8a86305-b9769a836d.zip and /dev/null differ diff --git a/.yarn/cache/require-directory-npm-2.1.1-8608aee50b-a72468e258.zip b/.yarn/cache/require-directory-npm-2.1.1-8608aee50b-a72468e258.zip deleted file mode 100644 index bc4ba9cb9..000000000 Binary files a/.yarn/cache/require-directory-npm-2.1.1-8608aee50b-a72468e258.zip and /dev/null differ diff --git a/.yarn/cache/y18n-npm-5.0.8-5f3a0a7e62-5f1b5f95e3.zip b/.yarn/cache/y18n-npm-5.0.8-5f3a0a7e62-5f1b5f95e3.zip deleted file mode 100644 index 8237762e5..000000000 Binary files a/.yarn/cache/y18n-npm-5.0.8-5f3a0a7e62-5f1b5f95e3.zip and /dev/null differ diff --git a/.yarn/cache/yargs-npm-17.7.2-80b62638e1-abb3e37678.zip b/.yarn/cache/yargs-npm-17.7.2-80b62638e1-abb3e37678.zip deleted file mode 100644 index 44b97347f..000000000 Binary files a/.yarn/cache/yargs-npm-17.7.2-80b62638e1-abb3e37678.zip and /dev/null differ diff --git a/.yarn/cache/yargs-parser-npm-21.1.1-8fdc003314-9dc2c217ea.zip b/.yarn/cache/yargs-parser-npm-21.1.1-8fdc003314-9dc2c217ea.zip deleted file mode 100644 index 32e7f6b59..000000000 Binary files a/.yarn/cache/yargs-parser-npm-21.1.1-8fdc003314-9dc2c217ea.zip and /dev/null differ diff --git a/plugins/google-search-console/package.json b/plugins/google-search-console/package.json index 1f047b2f0..0a532a93c 100644 --- a/plugins/google-search-console/package.json +++ b/plugins/google-search-console/package.json @@ -10,11 +10,11 @@ "check-eslint": "run g:check-eslint", "pack": "run g:pack", "preview": "run g:preview", - "check-typescript": "run g:check-typescript" + "check-typescript": "run g:check-typescript", + "check-vitest": "run g:check-vitest" }, "dependencies": { "@ataverascrespo/react18-ts-textfit": "^1.0.0", - "aveta": "^1.5.2", "cheerio": "^1.1.2", "framer-plugin": "3.11.0-alpha.12", "react": "^18.3.1", diff --git a/plugins/google-search-console/src/formatStat.test.ts b/plugins/google-search-console/src/formatStat.test.ts new file mode 100644 index 000000000..2de80b961 --- /dev/null +++ b/plugins/google-search-console/src/formatStat.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest" +import { formatStat } from "./formatStat" + +describe("formatStat", () => { + it("keeps values below one thousand unchanged", () => { + expect(formatStat(0)).toBe("0") + expect(formatStat(12)).toBe("12") + expect(formatStat(999)).toBe("999") + }) + + it("abbreviates large values with one decimal place", () => { + expect(formatStat(1000)).toBe("1K") + expect(formatStat(1234)).toBe("1.2K") + expect(formatStat(10_500)).toBe("10.5K") + expect(formatStat(1_200_000)).toBe("1.2M") + }) + + it("carries rounded values into the next unit", () => { + expect(formatStat(999_950)).toBe("1M") + }) + + it("preserves negative values", () => { + expect(formatStat(-1234)).toBe("-1.2K") + }) + + it("returns a dash for non-finite values", () => { + expect(formatStat(Number.POSITIVE_INFINITY)).toBe("—") + expect(formatStat(Number.NaN)).toBe("—") + }) +}) diff --git a/plugins/google-search-console/src/formatStat.ts b/plugins/google-search-console/src/formatStat.ts new file mode 100644 index 000000000..b12f42a7c --- /dev/null +++ b/plugins/google-search-console/src/formatStat.ts @@ -0,0 +1,31 @@ +const statUnits = ["", "K", "M", "B", "T", "P", "E"] as const + +export function formatStat(value: number) { + if (!Number.isFinite(value)) { + return "—" + } + + const sign = value < 0 ? "-" : "" + let scaledValue = Math.abs(value) + let unitIndex = 0 + + while (scaledValue >= 1000 && unitIndex < statUnits.length - 1) { + scaledValue /= 1000 + unitIndex += 1 + } + + if (scaledValue >= 1000) { + return value.toString() + } + + let roundedValue = Number.isInteger(scaledValue) ? scaledValue : Number(scaledValue.toFixed(1)) + + while (roundedValue >= 1000 && unitIndex < statUnits.length - 1) { + roundedValue /= 1000 + unitIndex += 1 + } + + const unit = statUnits[unitIndex] ?? "" + + return `${sign}${roundedValue.toString()}${unit}` +} diff --git a/plugins/google-search-console/src/screens/Performance.tsx b/plugins/google-search-console/src/screens/Performance.tsx index 9fc6b4ecd..8bcd0ada9 100644 --- a/plugins/google-search-console/src/screens/Performance.tsx +++ b/plugins/google-search-console/src/screens/Performance.tsx @@ -1,9 +1,9 @@ -import aveta from "aveta" import { type CSSProperties, useMemo, useState } from "react" import { Area, AreaChart, ResponsiveContainer, Tooltip, type TooltipContentProps, XAxis } from "recharts" import type { NameType, Payload, ValueType } from "recharts/types/component/DefaultTooltipContent" import FitText from "../components/FitText" import Loading from "../components/Loading" +import { formatStat } from "../formatStat" import type { GoogleQueryResult } from "../types" import { mapQueries } from "../utils" @@ -151,7 +151,7 @@ export default function Performance({ performance }: PerformanceProps) { }} >