Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
121 lines
5.0 KiB
TypeScript
121 lines
5.0 KiB
TypeScript
import { createElement, Fragment } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { describe, expect, it } from "vitest";
|
|
import { StatusBadge } from "~/components/console/status-badge.tsx";
|
|
import { Button } from "~/components/ui/button.tsx";
|
|
import { ClampText, type MediaFacts, MediaHero } from "./media-detail.tsx";
|
|
|
|
// 详情页头图(方案 A):背景图渐隐到页面底色、无背景图退化为普通头部;状态徽标并进事实行;
|
|
// 操作按钮并进头图区且只有一个实心主操作;简介折叠只留一个「展开」入口。
|
|
|
|
const facts = (over: Partial<MediaFacts> = {}): MediaFacts => ({
|
|
title: "人生切割术",
|
|
originalTitle: "Severance",
|
|
year: 2022,
|
|
kind: "series",
|
|
kindLabel: "剧集",
|
|
genres: ["剧情"],
|
|
voteAverage: 8.4,
|
|
voteCount: 1200,
|
|
airStatus: "Returning Series",
|
|
runtime: 55,
|
|
seasonCount: 2,
|
|
episodeCount: 19,
|
|
releaseDate: "2022-02-17",
|
|
lastAirDate: "2025-03-20",
|
|
platform: "Apple TV",
|
|
originCountry: ["US"],
|
|
overview: "故事发生在一家名为卢蒙的超级大公司内。",
|
|
tagline: null,
|
|
posterPath: "/sev-poster.jpg",
|
|
backdropPath: "/severance-bg.jpg",
|
|
externalLinks: [{ label: "TMDB", href: "https://www.themoviedb.org/tv/95396" }],
|
|
...over,
|
|
});
|
|
|
|
const render = (props: Parameters<typeof MediaHero>[0]) => renderToStaticMarkup(createElement(MediaHero, props));
|
|
|
|
/** 取 data-slot 标记的元素起始处之后的一段 HTML(足够断言其中的内容) */
|
|
const slotHtml = (html: string, slot: string) => {
|
|
const i = html.indexOf(`data-slot="${slot}"`);
|
|
return i < 0 ? null : html.slice(i, html.indexOf("</div>", i) + 6);
|
|
};
|
|
|
|
describe("MediaHero 头图", () => {
|
|
it("有背景图:剧照层经图片代理、带渐隐材质,不再是深色圆角卡片;文字用常规前景色", () => {
|
|
const html = render({ facts: facts() });
|
|
expect(html).toContain('data-backdrop="image"');
|
|
expect(html).toContain("media-hero-backdrop");
|
|
expect(html).toContain('src="/api/v1/img/tmdb/w1280/severance-bg.jpg"');
|
|
expect(html).not.toMatch(/bg-scrim|text-on-media|rounded-2xl/);
|
|
expect(html).toContain("text-foreground");
|
|
});
|
|
|
|
it("没有背景图:退化为普通头部,不渲染剧照层和空的深色块", () => {
|
|
const html = render({ facts: facts({ backdropPath: null }) });
|
|
expect(html).toContain('data-backdrop="none"');
|
|
expect(html).not.toContain("media-hero-backdrop");
|
|
expect(html).not.toContain("w1280");
|
|
expect(html).not.toMatch(/bg-scrim/);
|
|
expect(html).toContain("人生切割术");
|
|
});
|
|
|
|
it("状态徽标(正常 / Emby 等)与播出状态在同一行", () => {
|
|
const html = render({
|
|
facts: facts(),
|
|
badges: StatusBadge({ tone: "success", children: "正常" }),
|
|
});
|
|
const row = slotHtml(html, "media-hero-badges");
|
|
expect(row).toContain("连载中");
|
|
expect(row).toContain("正常");
|
|
});
|
|
|
|
it("操作按钮并进头图区、在外链之后;只有订阅是实心主操作,其余是描边次操作,没有棱镜描边", () => {
|
|
const html = render({
|
|
facts: facts(),
|
|
actions: createElement(
|
|
Fragment,
|
|
null,
|
|
createElement(Button, { variant: "solid", size: "sm" }, "订阅"),
|
|
createElement(Button, { variant: "outline", size: "sm" }, "搜索资源"),
|
|
createElement(Button, { variant: "outline", size: "sm" }, "查看订阅"),
|
|
),
|
|
});
|
|
const actions = slotHtml(html, "media-hero-actions");
|
|
expect(actions).not.toBeNull();
|
|
expect(html.indexOf('data-slot="media-hero-actions"')).toBeGreaterThan(html.indexOf("TMDB"));
|
|
expect(actions!.match(/pill-solid/g)).toHaveLength(1);
|
|
expect(actions!.match(/pill-secondary/g)).toHaveLength(2);
|
|
expect(actions).not.toContain("pill-primary");
|
|
expect(actions!.indexOf("pill-solid")).toBeLessThan(actions!.indexOf("订阅"));
|
|
});
|
|
|
|
it("没有操作时不渲染操作行;标语有值才显示", () => {
|
|
const plain = render({ facts: facts() });
|
|
expect(plain).not.toContain("media-hero-actions");
|
|
expect(plain).not.toContain("media-hero-tagline");
|
|
const tagged = render({ facts: facts({ tagline: "工作与生活,一刀两断" }) });
|
|
expect(slotHtml(tagged, "media-hero-tagline")).toContain("工作与生活,一刀两断");
|
|
});
|
|
});
|
|
|
|
describe("ClampText 折叠", () => {
|
|
const long = "很长的简介。".repeat(60);
|
|
|
|
it("超出行数:折叠成行尾渐隐 + 一个「展开」入口,不再另加省略号", () => {
|
|
const html = renderToStaticMarkup(createElement(ClampText, { text: long, lines: 4 }));
|
|
expect(html).toContain("clamp-fade");
|
|
expect(html).toContain("--clamp-lines:4");
|
|
expect(html).not.toContain("line-clamp");
|
|
expect(html).not.toMatch(/…|\.\.\./);
|
|
expect(html.match(/展开/g)).toHaveLength(1);
|
|
expect(html).toContain('aria-expanded="false"');
|
|
});
|
|
|
|
it("放得下时不折叠、不给「展开」", () => {
|
|
const html = renderToStaticMarkup(createElement(ClampText, { text: "短简介", lines: 4 }));
|
|
expect(html).not.toContain("clamp-fade");
|
|
expect(html).not.toContain("展开");
|
|
});
|
|
});
|