mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-20 22:41:22 +02:00
226 lines
7.0 KiB
JavaScript
226 lines
7.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readdirSync, readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
import {
|
|
autoTipCandidates,
|
|
isPlanTip,
|
|
pickAutoTip,
|
|
TIPS,
|
|
tipsFor,
|
|
} from "./tips.ts";
|
|
|
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
const LOCALES = path.join(HERE, "..", "i18n", "locales");
|
|
|
|
/** An install that uses nothing a tip talks about. */
|
|
const IDLE = {
|
|
profiles: [],
|
|
currentOs: "macos",
|
|
groupCount: 0,
|
|
extensionGroupCount: 0,
|
|
inTeam: false,
|
|
cookieBotEnrolled: false,
|
|
syncServerConfigured: false,
|
|
};
|
|
|
|
const ALL_PLAN = {
|
|
active: true,
|
|
cloudBackup: true,
|
|
cookieBot: true,
|
|
crossOsFingerprints: true,
|
|
browserAutomation: true,
|
|
teamCollaboration: true,
|
|
remoteControl: true,
|
|
};
|
|
|
|
function ids(tips) {
|
|
return tips.map((tip) => tip.id);
|
|
}
|
|
|
|
const NONE = {
|
|
active: false,
|
|
cloudBackup: false,
|
|
cookieBot: false,
|
|
crossOsFingerprints: false,
|
|
browserAutomation: false,
|
|
teamCollaboration: false,
|
|
remoteControl: false,
|
|
};
|
|
|
|
test("tip ids are unique and every tip has copy in every locale", () => {
|
|
const ids = TIPS.map((tip) => tip.id);
|
|
assert.equal(new Set(ids).size, ids.length);
|
|
const locales = readdirSync(LOCALES).filter((name) => name.endsWith(".json"));
|
|
assert.ok(locales.length >= 2, "expected several locale files");
|
|
for (const name of locales) {
|
|
const bundle = JSON.parse(readFileSync(path.join(LOCALES, name), "utf8"));
|
|
for (const id of ids) {
|
|
const item = bundle.tips?.items?.[id];
|
|
assert.ok(item, `${name} is missing tips.items.${id}`);
|
|
for (const field of ["label", "title", "body", "action"]) {
|
|
assert.equal(
|
|
typeof item[field],
|
|
"string",
|
|
`${name}: tips.items.${id}.${field} must be a string`,
|
|
);
|
|
assert.ok(
|
|
item[field].trim().length > 0,
|
|
`${name}: tips.items.${id}.${field} is empty`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test("a free install is offered every essential and no plan tip", () => {
|
|
const offered = tipsFor(NONE);
|
|
assert.ok(offered.length > 0);
|
|
assert.ok(offered.every((tip) => !isPlanTip(tip)));
|
|
assert.equal(offered.length, TIPS.filter((tip) => !isPlanTip(tip)).length);
|
|
});
|
|
|
|
test("a plan tip needs an active plan with that capability", () => {
|
|
const solo = { ...NONE, active: true, cloudBackup: true, cookieBot: true };
|
|
const offered = tipsFor(solo).map((tip) => tip.id);
|
|
assert.ok(offered.includes("cloudBackup"));
|
|
assert.ok(offered.includes("cookieBot"));
|
|
assert.ok(!offered.includes("team"), "solo has no team collaboration");
|
|
assert.ok(!offered.includes("remoteControl"));
|
|
|
|
const lapsed = { ...solo, active: false };
|
|
assert.ok(
|
|
tipsFor(lapsed).every((tip) => !isPlanTip(tip)),
|
|
"a lapsed plan is offered only the essentials",
|
|
);
|
|
});
|
|
|
|
test("the automatic flow picks one unseen tip at random", () => {
|
|
const offered = tipsFor(NONE);
|
|
assert.equal(
|
|
pickAutoTip(offered, [], IDLE, () => 0),
|
|
offered[0],
|
|
);
|
|
assert.equal(
|
|
pickAutoTip(offered, [], IDLE, () => 0.999_999),
|
|
offered.at(-1),
|
|
"the top of the random range lands on the last candidate",
|
|
);
|
|
assert.equal(
|
|
pickAutoTip(offered, [offered[0].id], IDLE, () => 0),
|
|
offered[1],
|
|
"a seen tip is never picked",
|
|
);
|
|
assert.equal(pickAutoTip(offered, ids(offered), IDLE), null);
|
|
|
|
const picked = new Set();
|
|
for (let roll = 0; roll < offered.length; roll += 1) {
|
|
picked.add(pickAutoTip(offered, [], IDLE, () => roll / offered.length)?.id);
|
|
}
|
|
assert.equal(
|
|
picked.size,
|
|
offered.length,
|
|
"every candidate can come up, not only the first in catalog order",
|
|
);
|
|
});
|
|
|
|
test("an install that uses nothing yet is offered every unseen tip", () => {
|
|
const offered = tipsFor(ALL_PLAN);
|
|
assert.deepEqual(ids(autoTipCandidates(offered, [], IDLE)), ids(offered));
|
|
});
|
|
|
|
test("a tip for a feature already in use is skipped", () => {
|
|
const offered = tipsFor(ALL_PLAN);
|
|
const skipped = (usage) =>
|
|
ids(offered).filter(
|
|
(id) => !ids(autoTipCandidates(offered, [], usage)).includes(id),
|
|
);
|
|
const withProfile = (profile) => ({ ...IDLE, profiles: [profile] });
|
|
|
|
assert.deepEqual(skipped(withProfile({ dns_blocklist: "pro" })), [
|
|
"dnsBlocklist",
|
|
]);
|
|
assert.deepEqual(
|
|
skipped(withProfile({ dns_blocklist: null })),
|
|
[],
|
|
"a profile with DNS blocking off does not count",
|
|
);
|
|
assert.deepEqual(skipped(withProfile({ password_protected: true })), [
|
|
"profilePassword",
|
|
]);
|
|
assert.deepEqual(skipped(withProfile({ clear_on_close: true })), [
|
|
"clearOnClose",
|
|
]);
|
|
assert.deepEqual(skipped(withProfile({ group_id: "g1" })), ["groups"]);
|
|
assert.deepEqual(skipped({ ...IDLE, groupCount: 2 }), ["groups"]);
|
|
assert.deepEqual(skipped(withProfile({ extension_group_id: "e1" })), [
|
|
"extensionGroups",
|
|
]);
|
|
assert.deepEqual(skipped({ ...IDLE, extensionGroupCount: 1 }), [
|
|
"extensionGroups",
|
|
]);
|
|
assert.deepEqual(skipped(withProfile({ sync_mode: "Encrypted" })), [
|
|
"selfHostedSync",
|
|
"cloudBackup",
|
|
]);
|
|
assert.deepEqual(skipped(withProfile({ sync_mode: "Disabled" })), []);
|
|
assert.deepEqual(skipped({ ...IDLE, syncServerConfigured: true }), [
|
|
"selfHostedSync",
|
|
]);
|
|
assert.deepEqual(
|
|
skipped(
|
|
withProfile({ host_os: "macos", wayfern_config: { os: "windows" } }),
|
|
),
|
|
["crossOs"],
|
|
);
|
|
assert.deepEqual(
|
|
skipped(withProfile({ wayfern_config: { os: "windows" } })),
|
|
["crossOs"],
|
|
"without a recorded host, this desktop's OS is the native one",
|
|
);
|
|
assert.deepEqual(
|
|
skipped(withProfile({ host_os: "macos", wayfern_config: { os: "macos" } })),
|
|
[],
|
|
"a native fingerprint is not a cross-OS one",
|
|
);
|
|
assert.deepEqual(skipped({ ...IDLE, proxyChecked: true }), ["proxyCheck"]);
|
|
assert.deepEqual(skipped({ ...IDLE, fingerprintGateChanged: true }), [
|
|
"fingerprintGate",
|
|
]);
|
|
assert.deepEqual(skipped({ ...IDLE, isDefaultBrowser: true }), [
|
|
"defaultBrowser",
|
|
]);
|
|
assert.deepEqual(skipped({ ...IDLE, trashUsed: true }), ["trash"]);
|
|
assert.deepEqual(skipped({ ...IDLE, apiEnabled: true }), [
|
|
"localApi",
|
|
"automation",
|
|
]);
|
|
assert.deepEqual(skipped({ ...IDLE, cookieBotEnrolled: true }), [
|
|
"cookieBot",
|
|
]);
|
|
assert.deepEqual(skipped({ ...IDLE, agentUsed: true }), ["agent"]);
|
|
assert.deepEqual(skipped({ ...IDLE, inTeam: true }), ["team"]);
|
|
assert.deepEqual(skipped({ ...IDLE, remoteControlEnabled: true }), [
|
|
"remoteControl",
|
|
]);
|
|
});
|
|
|
|
test("only the command palette and import tips have no usage signal", () => {
|
|
assert.deepEqual(ids(TIPS.filter((tip) => tip.inUse === undefined)), [
|
|
"commandPalette",
|
|
"importProfiles",
|
|
]);
|
|
});
|
|
|
|
test("the pick skips tips in use and gives up when nothing is left", () => {
|
|
const offered = tipsFor(NONE);
|
|
const unseen = ["dnsBlocklist", "trash"];
|
|
const seen = ids(offered).filter((id) => !unseen.includes(id));
|
|
const dnsOn = { ...IDLE, profiles: [{ dns_blocklist: "light" }] };
|
|
for (const roll of [0, 0.5, 0.99]) {
|
|
assert.equal(pickAutoTip(offered, seen, dnsOn, () => roll)?.id, "trash");
|
|
}
|
|
assert.equal(pickAutoTip(offered, seen, { ...dnsOn, trashUsed: true }), null);
|
|
});
|