返回 CodeWhale
usage-counting-copy.test.ts
根目录 / web / lib / usage-counting-copy.test.ts
1 import { readdirSync, readFileSync } from "node:fs";
2 import { join } from "node:path";
3 import { fileURLToPath } from "node:url";
4 import { describe, expect, it } from "vitest";
5 import { USAGE_COUNTING_COPY } from "./content/usage-counting";
6 import { WEBSITE_USAGE_DISCLOSURE } from "./legal-copy";
7 import { PRODUCT_COUNTER_FIELDS, type ProductCounter } from "./telemetry/product-usage";
8
9 // How the public copy names each counter the website can send.
10 const WEBSITE_COUNTER_WORDING: Partial<Record<ProductCounter, { en: string; zh: string }>> = {
11 page_view: { en: "page views", zh: "页面浏览" },
12 docs_view: { en: "documentation views", zh: "文档浏览" },
13 install_copy: { en: "install-command copies", zh: "安装命令复制" },
14 download: { en: "downloads", zh: "下载" },
15 login: { en: "sign-in", zh: "登录" },
16 signup: { en: "sign-up", zh: "注册" },
17 error_shown: { en: "error pages shown", zh: "错误页面" },
18 };
19
20 function sources(dir: string): string[] {
21 return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
22 const path = join(dir, entry.name);
23 if (entry.isDirectory()) return sources(path);
24 return /\.tsx?$/.test(entry.name) && !/\.test\.tsx?$/.test(entry.name) ? [path] : [];
25 });
26 }
27
28 /** Counters the website records: route views plus every call site and marker. */
29 function recordedCounters(): Set<string> {
30 const recorded = new Set<string>(["page_view", "docs_view"]);
31 for (const dir of ["../app", "../components"]) {
32 for (const file of sources(fileURLToPath(new URL(dir, import.meta.url)))) {
33 const text = readFileSync(file, "utf8");
34 for (const m of text.matchAll(/recordUsage\("(\w+)"\)|data-usage="(\w+)"/g)) {
35 recorded.add(m[1] ?? m[2]);
36 }
37 for (const m of text.matchAll(/data-usage=\{[^}]*\}/g)) {
38 for (const q of m[0].matchAll(/"(\w+)"/g)) recorded.add(q[1]);
39 }
40 }
41 }
42 return recorded;
43 }
44
45 describe("usage-counting copy", () => {
46 const recorded = recordedCounters();
47 const privacy = WEBSITE_USAGE_DISCLOSURE.body;
48
49 it("only names website counters the copy knows how to describe", () => {
50 for (const counter of recorded) {
51 expect(PRODUCT_COUNTER_FIELDS as readonly string[]).toContain(counter);
52 expect(WEBSITE_COUNTER_WORDING).toHaveProperty(counter);
53 }
54 });
55
56 it.each(Object.entries(WEBSITE_COUNTER_WORDING))(
57 "names %s exactly when the site records it",
58 (counter, wording) => {
59 const sent = recorded.has(counter);
60 expect(USAGE_COUNTING_COPY.summary.en.includes(wording.en)).toBe(sent);
61 expect(USAGE_COUNTING_COPY.summary.zh.includes(wording.zh)).toBe(sent);
62 expect(privacy.includes(wording.en)).toBe(sent);
63 },
64 );
65 });
66
66 lines TYPESCRIPT