返回 CodeWhale
contributor-merch.test.ts
根目录 / web / lib / contributor-merch.test.ts
1 import { describe, expect, it } from "vitest";
2 import { contributorCost, validateContributorIdentity } from "./contributor-merch";
3 import { MERCH_COPY, MERCH_LANGUAGES } from "./content/contributor-merch";
4 import { locales } from "./i18n/config";
5
6 describe("contributor at-cost estimate", () => {
7 it("covers supplier production, separate postage and processing without a profit or reserve", () => {
8 const quote = contributorCost();
9 expect(quote.total).toBe(45.53);
10 expect(quote.merchandise).toBe(38.09);
11 expect(quote.shipping).toBe(7.44);
12 expect(quote.production + quote.processing + quote.shipping).toBeCloseTo(quote.total, 10);
13 const afterProcessorUsd = quote.total / 6.7 * .961 - .30;
14 expect(afterProcessorUsd).toBeGreaterThanOrEqual(5.12 + 1.11);
15 expect(afterProcessorUsd - (5.12 + 1.11)).toBeLessThan(.01 / 6.7);
16 });
17 it("grosses up different supplier and payment costs rather than adding a hidden margin", () => {
18 const quote = contributorCost({ production: 4, shipping: 2, rate: .05, flat: .2, fx: 7 });
19 const expected = Math.ceil((4 + 2 + .2) / .95 * 7 * 100) / 100;
20 expect(quote.total).toBe(expected);
21 expect(quote.shipping).toBe(14);
22 });
23 it.each([{ fx: 0 }, { rate: 1 }, { shipping: -1 }, { production: Number.NaN }])("rejects invalid cost inputs %o", (inputs) => {
24 expect(() => contributorCost(inputs)).toThrow(RangeError);
25 });
26 });
27
28 describe("contributor identity context", () => {
29 it.each(["octocat", "@octocat", "1234567", "a-name"])("accepts identity alone: %s", (github) => {
30 expect(validateContributorIdentity({ github, contribution: "" })).toBeNull();
31 });
32 it("accepts a contribution URL without requiring a GitHub identity or a merged-PR threshold", () => {
33 expect(validateContributorIdentity({ github: "", contribution: "https://github.com/codewhale-hq/CodeWhale/issues/1" })).toBeNull();
34 expect(validateContributorIdentity({ github: "", contribution: "https://example.org/documentation-improvement" })).toBeNull();
35 });
36 it("requires one identity reference", () => {
37 expect(validateContributorIdentity({ github: " ", contribution: " " })).toBe("missing");
38 });
39 it.each(["a--b", "-name", "name-", "a b"])("rejects malformed identity: %s", (github) => {
40 expect(validateContributorIdentity({ github, contribution: "" })).toBe("badIdentity");
41 });
42 it.each(["javascript:alert(1)", "http://example.org", "https://user:password@example.org", "not-a-url"])("rejects unsafe or malformed URL: %s", (contribution) => {
43 expect(validateContributorIdentity({ github: "", contribution })).toBe("badUrl");
44 });
45 });
46
47 describe("contributor languages and print text", () => {
48 it("covers all currently routed website languages and Traditional Chinese", () => {
49 const languages = MERCH_LANGUAGES.map((v) => v.code);
50 expect(languages).toHaveLength(19);
51 expect(new Set(languages).size).toBe(languages.length);
52 expect(languages).toEqual(expect.arrayContaining([...locales, "zh-TW"]));
53 for (const language of languages) {
54 const copy = MERCH_COPY[language];
55 expect(Object.keys(copy).sort()).toEqual(Object.keys(MERCH_COPY.en).sort());
56 expect(Object.values(copy).every((value) => typeof value === "string" ? value.length > 0 : value.length === 3)).toBe(true);
57 expect(copy.phrases[0]).toContain("Codewhale");
58 expect(copy.phrases[2]).toContain("Codewhale");
59 if (language !== "en") expect(copy.title).not.toBe(MERCH_COPY.en.title);
60 }
61 });
62 });
63
63 lines TYPESCRIPT