gs4f2wcln: Fit long text-only posts to the slide and add Read more and more/less for long post text on mobile
This commit is contained in:
@@ -7,6 +7,7 @@ import { pickCurrent, isDoubleTap, isTapTarget, featuredSlideIndex, railPercent
|
||||
import { FIXTURE_DISCOVER } from "@/dev/fixtures";
|
||||
import { CompactLayoutContext, isCompactWidth } from "@/components/layout-context";
|
||||
import { toCardPosts } from "@/lib/feed-model";
|
||||
import { pickQuoteFit, QUOTE_SIZES } from "@/components/SlideText";
|
||||
|
||||
const NOW = Date.parse("2026-10-19T12:00:00Z");
|
||||
const item = (id: string, authorId: string, mediaItems: unknown[] = []) => ({
|
||||
@@ -50,10 +51,10 @@ describe("mobile feed", () => {
|
||||
it("renders one slide per post, text as a quote and media under the fades", () => {
|
||||
const html = render({});
|
||||
expect(html.match(/data-testid="feed-slide"/g)).toHaveLength(3);
|
||||
expect(html).toContain('class="slide-quote">text t1</p>');
|
||||
expect(html).toMatch(/class="slide-quote" data-fit-size="31" style="font-size:31px">text t1<\/p>/);
|
||||
expect(html).toMatch(/slide slide-on-media[^"]*"[^>]*aria-label="Post by Author i1"/);
|
||||
expect(html).toContain("slide-scrim-top");
|
||||
expect(html).toContain('class="slide-text">text i1</p>');
|
||||
expect(html).toMatch(/class="slide-text"[^>]*>text i1<\/p>/);
|
||||
expect(html).toContain('aria-label="Play video"');
|
||||
// The quote slide shows its text once, as the quote.
|
||||
expect(html.match(/text t1/g)).toHaveLength(1);
|
||||
@@ -61,7 +62,7 @@ describe("mobile feed", () => {
|
||||
|
||||
it("marks the first slide as the one on screen", () => {
|
||||
const html = render({});
|
||||
expect(html).toMatch(/class="slide slide-on-text is-cur"/);
|
||||
expect(html).toMatch(/class="slide slide-on-text is-cur slide-own"/);
|
||||
expect(html).toContain('data-current="0"');
|
||||
});
|
||||
|
||||
@@ -101,7 +102,7 @@ describe("mobile stylesheet", () => {
|
||||
});
|
||||
it("uses a 31px serif quote with a crimson mark, and eases media from 104%", () => {
|
||||
expect(css).toMatch(/\.slide-quote \{[^}]*font-size: 31px/);
|
||||
expect(css).toMatch(/\.slide-quote::before \{[^}]*color: var\(--accent\)/);
|
||||
expect(css).toMatch(/\.slide-quote-mark \{[^}]*color: var\(--accent\)/);
|
||||
expect(css).toMatch(/transform: scale\(1\.04\)/);
|
||||
expect(css).toMatch(/\.is-cur \.slide-art \{ transform: scale\(1\); \}/);
|
||||
});
|
||||
@@ -206,3 +207,53 @@ describe("videoFit", () => {
|
||||
expect(videoFit(0, 0)).toBe("cover");
|
||||
});
|
||||
});
|
||||
|
||||
describe("long post text", () => {
|
||||
it("picks the largest quote size that fits, measured, not counted", () => {
|
||||
const fitsBelow = (max: number) => (size: number) => size <= max;
|
||||
expect(pickQuoteFit(fitsBelow(40), () => 0)).toEqual({ size: 31, lines: 0 });
|
||||
expect(pickQuoteFit(fitsBelow(26), () => 0)).toEqual({ size: 26, lines: 0 });
|
||||
expect(pickQuoteFit(fitsBelow(22), () => 0)).toEqual({ size: 22, lines: 0 });
|
||||
expect(pickQuoteFit(fitsBelow(19), () => 0)).toEqual({ size: 19, lines: 0 });
|
||||
expect([...QUOTE_SIZES]).toEqual([31, 26, 22, 19]);
|
||||
});
|
||||
|
||||
it("clamps at 19px to the whole lines that fit when nothing fits", () => {
|
||||
const tried: number[] = [];
|
||||
const fit = pickQuoteFit((s) => (tried.push(s), false), () => 17.8);
|
||||
expect(tried).toEqual([31, 26, 22, 19]);
|
||||
expect(fit).toEqual({ size: 19, lines: 17 });
|
||||
expect(pickQuoteFit(() => false, () => 0.2)).toEqual({ size: 19, lines: 1 });
|
||||
});
|
||||
|
||||
it("shows no button on short text until a measurement says it is long", () => {
|
||||
const html = render({});
|
||||
expect(html).not.toContain("Read more");
|
||||
expect(html).not.toMatch(/>more<\/button>/);
|
||||
expect(html).not.toContain("slide-reader");
|
||||
});
|
||||
|
||||
it("keeps taps on open text from counting toward a double-tap like", () => {
|
||||
const el = { closest: (sel: string) => (sel.includes("[data-expanded-text]") ? {} : null) } as unknown as Element;
|
||||
expect(isTapTarget(el)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("long post text stylesheet", () => {
|
||||
const css = readFileSync("src/styles/mobile.css", "utf8");
|
||||
it("fits the quote between the top bar and the author block", () => {
|
||||
expect(css).toMatch(/\.slide-quote-w \{[^}]*top: calc\(72px[^}]*bottom: 181px[^}]*overflow: hidden/);
|
||||
});
|
||||
it("makes the buttons at least 44px tall", () => {
|
||||
expect(css).toMatch(/\.slide-more \{[^}]*min-height: 44px/);
|
||||
});
|
||||
it("holds the reel still while text is open, and the open text scrolls", () => {
|
||||
expect(css).toMatch(/\.reel\[data-locked="true"\] \{ overflow-y: hidden; \}/);
|
||||
expect(css).toMatch(/\.slide-reader-scroll \{[^}]*overflow-y: auto/);
|
||||
expect(css).toMatch(/\.slide-text\.is-open \{[^}]*overflow-y: auto/);
|
||||
expect(css).toMatch(/\.slide-text-open \.slide-scrim \{[^}]*var\(--media-scrim\)/);
|
||||
});
|
||||
it("drops the expand animation under reduced motion", () => {
|
||||
expect(css).toMatch(/prefers-reduced-motion: reduce\) \{[^}]*\.slide-reader,\s*\.slide-text\.is-open,/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user