fix(ui,i18n): E30 review round 2 — honest bio failure, a full show summary, one HU name per thing

- Person page: a bio ask that fails says so with a retry instead of reading as 'no biography'.
- Show page: the summary is a ClampedText (5 lines + More), like the person and collection pages;
  one More/Less pair (plex.textMore/textLess) for all three.
- Channels: a re-slotted row is placed in the language the server orders by (the stored
  preference, else English), not the UI's guess.
- A skipped run's body is only the reason (its title already says skipped).
- HU: Behúzási kvótatartalék; Eseménynapló ürítve / az eseménynaplót; indok; közvetlenül
  játszható / közvetlen közvetítés in the Plex hints.
- Tests: ClampedText re-measures on a resize, the show summary clamp, serverOrderLang.
This commit is contained in:
2026-10-01 08:31:04 +02:00
parent 6ac9ffd45e
commit 9454717f47
19 changed files with 140 additions and 45 deletions
@@ -3,7 +3,7 @@
// A clamped text offers More only when it is actually cut (E30 S6 on the person page, shared with the
// collection page in the E30 review, where a one-line summary carried a More that did nothing).
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
import { ClampedText } from "./ClampedText";
beforeAll(() => {
@@ -44,4 +44,33 @@ describe("ClampedText", () => {
box.mockRestore();
}
});
it("measures again when its box changes: the window or the text size moved the cut", () => {
const seen: ResizeObserverCallback[] = []; // the callbacks of observers watching something
const real = globalThis.ResizeObserver;
globalThis.ResizeObserver = class {
constructor(private cb: ResizeObserverCallback) {}
observe() {
seen.push(this.cb);
}
unobserve() {}
disconnect() {}
} as unknown as typeof ResizeObserver;
let height = 60;
const tall = vi
.spyOn(HTMLElement.prototype, "scrollHeight", "get")
.mockImplementation(() => height);
const box = vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockReturnValue(60);
try {
render(view("A text that fits, until the reader raises the text size."));
expect(screen.queryByText("More")).toBeNull();
height = 400; // the text size went up: the same text now runs past three lines
act(() => seen[seen.length - 1]!([], {} as ResizeObserver));
expect(screen.getByText("More")).toBeTruthy();
} finally {
tall.mockRestore();
box.mockRestore();
globalThis.ResizeObserver = real;
}
});
});
+1 -1
View File
@@ -2,7 +2,7 @@ import { useLayoutEffect, useRef, useState } from "react";
import { Button } from "./Button";
// Static class names, so Tailwind sees them.
const CLAMP = { 3: "line-clamp-3", 8: "line-clamp-8" } as const;
const CLAMP = { 3: "line-clamp-3", 5: "line-clamp-5", 8: "line-clamp-8" } as const;
/** A long text (a biography, a summary) clamped to a few lines, with a More / Less toggle offered only
* when the text is actually cut — measured, so a short text carries no dead button. Re-measured on a