fix(downloads): media-library UI review fixes (E7)
Deep-review remediation on the frontend, all findings adversarially verified: - LibraryPlayer picks audio vs video from the canonical media_kind (single source), not a hardcoded container whitelist, so an uploaded audio file with an uncommon/absent container renders in <audio> instead of a broken video box (legacy null media_kind keeps the heuristic). - UploadDialog aborts the in-flight upload when the dialog is closed by X/ESC/backdrop/Back (not only the footer Cancel); drag highlight uses an enter/leave depth counter to stop the flicker; the progress bar gets a stable role=progressbar aria-label with the value on aria-valuenow. - DownloadCenter's Shared tab renders honest loading/error states instead of a blank panel, and the library/shared row-action gating moves to a single tested helper (lib/downloadActions). Tests: downloadActions gating matrix, the uploadReq XHR path (abort/401/422-localize/onerror), and the media_kind-driven audio/video branch. i18n: downloads.upload.uploadProgress (HU + EN).
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// E7·S4b — the in-app library player streams the completed item's LOCAL file into a native media
|
||||
// element (no transcode; S4a guarantees a browser-native container). A video asset (has pixel dims)
|
||||
// renders <video src=/file>; an audio-only asset (m4a/mp3, no dims) renders <audio src=/file>. i18n is
|
||||
// key-passthrough here.
|
||||
// element. video/audio is chosen from the stored media_kind (the single source), NOT a container
|
||||
// list — so an audio upload with an uncommon/absent container still renders <audio src=/file>. i18n
|
||||
// is key-passthrough here.
|
||||
import { describe, it, expect, afterEach, vi } from "vitest";
|
||||
import { render, cleanup } from "@testing-library/react";
|
||||
|
||||
@@ -89,11 +89,37 @@ describe("LibraryPlayer", () => {
|
||||
expect(document.body.querySelector("audio")).toBeNull();
|
||||
});
|
||||
|
||||
it("plays an audio-only asset (no dims, m4a) in an <audio> element", () => {
|
||||
render(<LibraryPlayer job={withAsset({ container: "m4a" })} onClose={() => {}} />);
|
||||
it("plays an audio-only asset (media_kind='audio') in an <audio> element", () => {
|
||||
render(
|
||||
<LibraryPlayer
|
||||
job={withAsset({ media_kind: "audio", container: "m4a" })}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
const audio = document.body.querySelector("audio");
|
||||
expect(audio).not.toBeNull();
|
||||
expect(audio!.getAttribute("src")).toBe("/api/downloads/7/file");
|
||||
expect(document.body.querySelector("video")).toBeNull();
|
||||
});
|
||||
|
||||
it("uses media_kind — an audio upload with an uncommon container still renders <audio>", () => {
|
||||
// The exact bug the fix targets: the old container whitelist (m4a/mp3/…) would drop an .m4b/.weba
|
||||
// audio upload into a <video> box. media_kind='audio' is authoritative regardless of container.
|
||||
render(
|
||||
<LibraryPlayer
|
||||
job={withAsset({ media_kind: "audio", container: "m4b" })}
|
||||
onClose={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(document.body.querySelector("audio")).not.toBeNull();
|
||||
expect(document.body.querySelector("video")).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to the container heuristic for a legacy null media_kind", () => {
|
||||
// A pre-discriminator asset (media_kind null) still resolves audio vs video from dims+container.
|
||||
render(
|
||||
<LibraryPlayer job={withAsset({ media_kind: null, container: "mp3" })} onClose={() => {}} />,
|
||||
);
|
||||
expect(document.body.querySelector("audio")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user