Font picker fix round: one search field, honest empty-enumeration fallback, tooltip only on the closed trigger

- FontMenuList: the separate custom-family input is gone; the search
  field absorbed its job — a no-match query renders a 'Use <query>'
  action row (customFamilyFor, pure, pinned), Enter picks it
- openFontMenu: an empty-but-successful queryLocalFonts() now falls back
  to the curated list (Electron 44 grants local-fonts and still
  enumerates zero fonts — reproduced with a hidden-Window probe); the
  footer split states WHY: 'could not be read' vs 'needs the desktop app'
- The Hint tooltip wraps the trigger button only and unmounts while the
  menu is open — it no longer pops over the dropdown
This commit is contained in:
amosblomqvist
2026-09-25 20:12:57 +03:00
parent ad61301324
commit ac1c1a2818
3 changed files with 148 additions and 104 deletions
+27 -3
View File
@@ -11,7 +11,7 @@ import assert from "node:assert/strict";
import fs from "node:fs";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, it } from "node:test";
import { FontMenuList, fontMenuResults } from "../components/settings/model-menu";
import { FontMenuList, customFamilyFor, fontMenuResults } from "../components/settings/model-menu";
import {
CURATED_FALLBACK_FONTS,
DEFAULT_READING_FONT,
@@ -77,8 +77,12 @@ describe("FontMenuList (thin SSR)", () => {
assert.ok(html.includes('data-testid="font-option-Georgia"'), "a family row");
assert.ok(html.includes('data-testid="font-option-Menlo"'), "another family row");
assert.ok(html.includes('data-testid="font-search"'), "the search field");
assert.ok(html.includes('data-testid="font-custom-input"'), "the custom-family escape");
assert.ok(html.includes('data-testid="font-custom-set"'), "the Set button");
// ONE field (owner ask 2026-09-26: "why two?") — the custom-family
// input is gone; the search field absorbed its job via the
// "Use '<query>'" action row.
assert.ok(!html.includes('data-testid="font-custom-input"'), "no separate custom input");
assert.ok(!html.includes('data-testid="font-custom-set"'), "no Set button");
assert.ok(!html.includes("Type a family name"), "no second field placeholder");
assert.ok(html.includes("Search fonts…"), "the search placeholder");
});
@@ -149,3 +153,23 @@ describe("the emphasis-ink factory ruling (globals.css)", () => {
assert.ok(!html.includes("#6fa9d6"));
});
});
describe("customFamilyFor — the single-field custom escape (2026-09-26)", () => {
const families = ["Arial", "CMU Serif", "Georgia", "Menlo"];
it("a query matching nothing becomes the custom pick", () => {
assert.equal(customFamilyFor("Baskerville", families), "Baskerville");
assert.equal(customFamilyFor(" zzz ", families), "zzz");
});
it("a matching family is the better pick — no action row", () => {
assert.equal(customFamilyFor("georgia", families), null);
// the default row matches too — "source"/"sans"/"default" queries pick it
assert.equal(customFamilyFor("source", families), null);
});
it("an empty query is never a custom pick", () => {
assert.equal(customFamilyFor("", families), null);
assert.equal(customFamilyFor(" ", families), null);
});
});