feat(downloads): show container/format chip in library rows (E7 S1)
The completed download rows already surfaced size + duration; add the concrete container (MP4/M4A/WebM…) as a compact chip so the library reads as a media list with a visible format — the audio-vs-video distinction the running-only phase pill can't show at rest. Pure display of the already-serialized asset.container; locked by a small DOM test (present when ready, absent before the worker fills it).
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// Library-row metadata (E7·S1): the completed row surfaces the concrete container/format alongside the
|
||||
// already-shown size. The format chip reads the raw `asset.container` (uppercased in the UI), so it must
|
||||
// render when present and be absent when the asset has no container yet (queued/running before the worker
|
||||
// fills it). i18n is key-passthrough here.
|
||||
import { describe, it, expect, afterEach, vi } from "vitest";
|
||||
import { render, screen, cleanup } from "@testing-library/react";
|
||||
|
||||
vi.mock("react-i18next", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("react-i18next")>();
|
||||
return { ...actual, useTranslation: () => ({ t: (key: string) => key }) };
|
||||
});
|
||||
|
||||
import { DownloadRow } from "./downloadRow";
|
||||
import type { DownloadJob } from "../lib/api";
|
||||
|
||||
const baseJob: DownloadJob = {
|
||||
id: 1,
|
||||
status: "done",
|
||||
progress: 100,
|
||||
speed_bps: null,
|
||||
eta_s: null,
|
||||
phase: null,
|
||||
error: null,
|
||||
error_code: null,
|
||||
queue_pos: 0,
|
||||
created_at: null,
|
||||
source_kind: "youtube",
|
||||
source_ref: "abc",
|
||||
source_url: null,
|
||||
poster_url: null,
|
||||
profile_id: null,
|
||||
spec: {
|
||||
mode: "av",
|
||||
max_height: null,
|
||||
container: null,
|
||||
audio_format: null,
|
||||
vcodec: null,
|
||||
embed_subs: false,
|
||||
embed_chapters: false,
|
||||
embed_thumbnail: false,
|
||||
sponsorblock: false,
|
||||
},
|
||||
display_name: "Clip",
|
||||
display_uploader: null,
|
||||
display_uploader_url: null,
|
||||
extra_links: [],
|
||||
can_download: true,
|
||||
expired: false,
|
||||
asset: null,
|
||||
};
|
||||
|
||||
const withAsset = (container: string | null): DownloadJob => ({
|
||||
...baseJob,
|
||||
asset: {
|
||||
id: 10,
|
||||
status: "ready",
|
||||
title: "Clip",
|
||||
uploader: null,
|
||||
uploader_url: null,
|
||||
upload_date: null,
|
||||
duration_s: 90,
|
||||
size_bytes: 148_800_000,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
container,
|
||||
thumbnail_url: null,
|
||||
expires_at: null,
|
||||
},
|
||||
});
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("DownloadRow format chip", () => {
|
||||
it("shows the uppercased container next to the size when ready", () => {
|
||||
render(<DownloadRow job={withAsset("mp4")} />);
|
||||
// Raw container is stored lowercase; the row uppercases it via CSS, so the text node stays "mp4".
|
||||
expect(screen.getByText(/·\s*mp4/i)).toBeTruthy();
|
||||
expect(screen.getByText(/142 MB/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("omits the format chip when the asset has no container yet", () => {
|
||||
const { container } = render(<DownloadRow job={withAsset(null)} />);
|
||||
expect(container.textContent).not.toMatch(/·\s*(mp4|m4a|webm)/i);
|
||||
});
|
||||
});
|
||||
@@ -215,6 +215,11 @@ export function DownloadRow({
|
||||
<ChannelLine job={job} />
|
||||
<div className="flex items-center gap-2 mt-1 text-xs">
|
||||
<StatusPill job={job} />
|
||||
{job.asset?.container ? (
|
||||
// Concrete container (mp4 / m4a / webm …) — reads as the file format, and its audio-vs-video
|
||||
// extension is the video/audio distinction the running-only phase pill can't show at rest.
|
||||
<span className="text-muted uppercase">· {job.asset.container}</span>
|
||||
) : null}
|
||||
{job.asset?.size_bytes ? (
|
||||
<span className="text-muted">· {formatBytes(job.asset.size_bytes)}</span>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user