Ctrl K
ComunidadGuías…

tests/settings-index.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// The settings index (client/components/settings/settingsIndex.ts) and the
// search over it.
//
// The index is GENERATED from the panel's source and gated by
// `npm run check-settings`, so what is worth testing here is not that it
// matches — the gate proves that — but that a reader searching for a thing
// they can see actually finds it, in both languages.

// Both dictionaries, installed: under Node there is no chunk to fetch
// (client/i18n.ts loads one language at a time in a page).
import "../client/i18n/both.ts";
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { SETTINGS_INDEX } from "../client/components/settings/settingsIndex.ts";
import { DESKTOP_ONLY_ROWS, searchSettings } from "../client/components/settings/searchSettings.ts";
import { setLang, t } from "../client/i18n.ts";
import { TABS } from "../client/components/settings/tabs.ts";

describe("the settings index", () => {
  it("covers every page the panel has but About, which holds facts and no rows", () => {
    const tabs = new Set(SETTINGS_INDEX.map((e) => e.tab));
    for (const id of TABS.map((s) => s.id).filter((id) => id !== "about")) {
      assert.ok(tabs.has(id), `no rows indexed for the ${id} page`);
    }
  });

  it("names no key twice in the whole index — a row lives in exactly one section", () => {
    const seen = new Set<string>();
    for (const e of SETTINGS_INDEX) {
      assert.ok(!seen.has(e.label), `${e.label} is indexed twice`);
      seen.add(e.label);
    }
  });

  it("names no key twice within a tab", () => {
    const seen = new Set<string>();
    for (const e of SETTINGS_INDEX) {
      const key = `${e.tab}/${e.label}`;
      assert.ok(!seen.has(key), `duplicate row ${key}`);
      seen.add(key);
    }
  });

  it("keeps every page short — ten rows in sight at most, and none a lone row", () => {
    // The 3.15 re-cut exists because Publishing ran to twenty-one rows while
    // Identity and Typography held five each. A section nobody scrolls to the
    // end of is a section whose rows may as well not exist; this pins the shape.
    //
    // Counted as ONE DEVICE sees it: parts (a control folded into its host,
    // `row` set) are not rows; the desktop app's own rows are drawn on the
    // desktop only; and a pocket's Backup & sync rows never share a screen
    // with an instance's — so a section carries the larger of each pair.
    const DESKTOP_APP = new Set(["rowAppName", "rowAppIcon", "rowAppLauncher", "rowAppLauncherWin", "rowUpdates"]);
    const tabs = new Set(SETTINGS_INDEX.map((e) => e.tab));
    for (const tab of tabs) {
      const rows = SETTINGS_INDEX.filter((e) => e.tab === tab && e.row === undefined);
      const view = (pocket: boolean, desktopApp: boolean) =>
        rows.filter((e) => (e.mode === undefined || e.mode === (pocket ? "pocket" : "instance")) && (desktopApp || !DESKTOP_APP.has(e.label))).length;
      const n = Math.max(view(false, true), view(true, true));
      const inSight = rows.filter((e) => e.adv !== true && (e.mode === undefined || e.mode === "instance") && !DESKTOP_APP.has(e.label)).length;
      assert.ok(inSight <= 10, `${tab} shows ${inSight} rows before its Advanced line`);
      assert.ok(n <= 18, `${tab} carries ${n} rows`);
      // About is facts first; its rows are the app's own (one in a browser).
      assert.ok(view(false, false) >= 2, `${tab} carries only ${view(false, false)} rows`);
    }
  });

  it("points every part at a row of its own section, and keeps parts out of Advanced unless their host is in it", () => {
    for (const e of SETTINGS_INDEX) {
      if (e.row === undefined) continue;
      const host = SETTINGS_INDEX.find((h) => h.label === e.row && h.row === undefined);
      assert.ok(host, `${e.label} names ${e.row}, which is not a row`);
      assert.equal(host.tab, e.tab, `${e.label} is in ${e.tab} but its host is in ${host.tab}`);
      assert.equal(e.adv === true, host.adv === true, `${e.label} and its host disagree about Advanced`);
    }
  });

  it("finds a row by the words behind its ⓘ, and lands a part on itself", () => {
    setLang("en");
    const labels = (q: string): string[] => searchSettings(q, true).map((h) => h.entry.label);
    // "Mastodon" is in the fediverse row's hint; "bookmarklet" only in the
    // clipper's ⓘ paragraph (moreClipper) — reference text a reader remembers.
    assert.ok(labels("Mastodon").includes("rowFediverse"));
    // Every row with an ⓘ paragraph is found by that paragraph's longest word.
    const longest = (text: string): string =>
      text.split(/[^\p{L}\p{N}]+/u).reduce((a, w) => (w.length > a.length ? w : a), "");
    const ref = SETTINGS_INDEX.find((e) => e.more?.length && !labels(longest(t(e.more[0]))).includes(e.label));
    assert.equal(ref, undefined, `a row's ⓘ text does not find it: ${ref?.label}`);
    const part = searchSettings("Fediverse name", true)[0];
    assert.equal(part.entry.label, "rowFediverseHandle");
    assert.equal(part.entry.row, "rowFediverse");
  });

  it("puts the desktop's update switch where a search for it lands — on the desktop", () => {
    const row = SETTINGS_INDEX.find((e) => e.label === "rowUpdates");
    assert.ok(row, "the Software updates row is not indexed");
    assert.equal(row.tab, "device");
    setLang("en");
    assert.ok(searchSettings("software updates", true).some((h) => h.entry.label === "rowUpdates"));
    assert.ok(searchSettings("installed", true).some((h) => h.entry.label === "rowUpdates"), "the hint's promise is not searchable");
    setLang("ar");
    assert.ok(searchSettings("تحديثات", true).some((h) => h.entry.label === "rowUpdates"));
    setLang("en");
  });

  it("keeps desktop-only rows out of a browser's results, and names only real rows", () => {
    setLang("en");
    assert.ok(!searchSettings("software updates", false).some((h) => h.entry.label === "rowUpdates"), "a browser search landed on a row it cannot draw");
    assert.ok(!searchSettings("this app's name", false).some((h) => h.entry.label === "rowAppName"));
    for (const label of DESKTOP_ONLY_ROWS) {
      assert.ok(SETTINGS_INDEX.some((e) => e.label === label), `${label} is not an index entry`);
    }
  });

  it("carries the environment variables an operator would search for", () => {
    const envs = new Set(SETTINGS_INDEX.map((e) => e.env).filter(Boolean));
    for (const name of ["SITE_NAME", "SITE_LANG", "PUBLIC_LAYOUT"]) {
      assert.ok(envs.has(name), `${name} is not reachable from the settings search`);
    }
  });
});

// The probes below search AS THE DESKTOP (`true`): they pick their row out
// of the whole index, and the first rows in it are the desktop-only ones a
// browser's search deliberately leaves out.
describe("searching the settings", () => {
  it("finds a row by its own label", () => {
    setLang("en");
    const label = t(SETTINGS_INDEX[0].label);
    const hits = searchSettings(label, true);
    assert.ok(hits.some((h) => h.entry.label === SETTINGS_INDEX[0].label), `"${label}" found nothing`);
  });

  it("finds a row by its ENVIRONMENT VARIABLE — the operator's half", () => {
    setLang("en");
    const hits = searchSettings("SITE_LANG");
    assert.ok(hits.length > 0, "SITE_LANG found nothing");
    assert.equal(hits[0].entry.env, "SITE_LANG");
  });

  it("is case-insensitive, so a variable typed in lower case still lands", () => {
    setLang("en");
    assert.ok(searchSettings("site_lang").length > 0);
  });

  it("returns nothing for an empty query rather than everything", () => {
    assert.deepEqual(searchSettings(""), []);
    assert.deepEqual(searchSettings("   "), []);
  });

  it("ranks a LABEL match above a help-text match", () => {
    setLang("en");
    // A word common enough to appear in both places; the row that is NAMED for
    // it must come first, because that is the one the reader meant.
    const hits = searchSettings("theme");
    assert.ok(hits.length > 1, "expected several theme rows");
    assert.ok(/theme/i.test(t(hits[0].entry.label)), `first hit was "${t(hits[0].entry.label)}"`);
  });

  it("searches ARABIC labels on an Arabic instance, from the same index", () => {
    setLang("ar");
    const entry = SETTINGS_INDEX.find((e) => /[؀-ۿ]/.test(t(e.label)));
    assert.ok(entry, "no Arabic label found — the dictionary is not translated");
    const hits = searchSettings(t(entry.label), true);
    assert.ok(hits.some((h) => h.entry.label === entry.label), "an Arabic label found nothing");
    setLang("en");
  });

  it("matches at a WORD START, so 'graph' no longer answers with para-graph", () => {
    setLang("en");
    const labels = (q: string) => searchSettings(q, true).map((h) => t(h.entry.label));
    assert.ok(!labels("graph").includes(t("rowTextDirection")), "'graph' matched inside 'paragraph'");
    assert.ok(!labels("date").includes(t("rowWhatsNew")), "'date' matched inside 'update'");
    // …while a word that begins a label or a hint still lands.
    assert.ok(labels("date").includes(t("rowDateCalendar")));
  });

  it("finds the rows a reader searches for by the words they use", () => {
    setLang("en");
    const labels = (q: string): string[] => searchSettings(q, true).map((h) => h.entry.label);
    for (const q of ["font", "fonts"]) {
      for (const row of ["rowFontProse", "rowFontMono", "rowFontArabic"]) {
        assert.ok(labels(q).includes(row), `"${q}" did not find ${row}`);
      }
    }
    assert.ok(labels("typeface").includes("rowFontUi"), "'typeface' found nothing");
    assert.ok(labels("width").includes("rowEditorWidth"), "'width' found nothing");
    assert.ok(labels("dark mode").includes("rowYourTheme"), "'dark mode' found nothing");
    assert.ok(labels("history").includes("rowNoteVersions"), "'history' found nothing");
    assert.ok(labels("rtl").includes("rowTextDirection"), "'rtl' found nothing");
    assert.ok(labels("designer").includes("rowOpenDesigner"), "'designer' found nothing");
    assert.ok(labels("SITE_LANG").includes("rowLanguage"), "an env name lost its word-start match on the underscore");
  });

  it("folds the alef family — and the fold provably DOES something", () => {
    setLang("ar");
    // The first version of this test compared the two spellings and passed
    // while both returned [] — two empty lists agreeing that the fold was
    // broken. So first find a label that really contains a hamza-alef, then
    // assert the BARE spelling finds it: non-empty, or the test says nothing.
    const hamza = SETTINGS_INDEX.find((e) => /[\u0622\u0623\u0625]/.test(t(e.label)));
    assert.ok(hamza, "no hamza-alef label in the index — pick another probe");
    const bare = t(hamza.label).replace(/[\u0622\u0623\u0625]/g, "\u0627");
    const hits = searchSettings(bare, true);
    assert.ok(
      hits.some((h) => h.entry.label === hamza.label),
      `bare-alef "${bare}" did not find the hamza-spelled label "${t(hamza.label)}"`,
    );
    setLang("en");
  });
});