fix(downloads): a site the downloader no longer understands says so, and offers a retry
yt-dlp asks for a bug report on an extraction failure it did not expect: the site answered in a way its extractor does not read (measured on a TikTok link: fails on every attempt under yt-dlp 2026.07.04, downloads under 2026.08.19). That failure read as "Something went wrong" with no retry; it is now its own category, site_changed, with a Retry in the failure window. A contract test holds every failure category to its copy in both languages.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// The failure window offers a Retry only where a retry can help: a transient failure, or a site the
|
||||
// downloader did not understand (an update on the server cures it). Never for an unclassified one.
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import type { DownloadJob } from "../lib/api";
|
||||
import { ErrorModal } from "./downloadModals";
|
||||
|
||||
vi.mock("react-i18next", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("react-i18next")>()),
|
||||
useTranslation: () => ({
|
||||
i18n: { language: "en" },
|
||||
t: (key: string | string[]) => (Array.isArray(key) ? key[0] : key),
|
||||
}),
|
||||
}));
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
function open(error_code: string) {
|
||||
const onRetry = vi.fn();
|
||||
const job = { id: 1, status: "error", error: "ERROR: x", error_code } as unknown as DownloadJob;
|
||||
render(<ErrorModal job={job} onRetry={onRetry} onClose={() => {}} />);
|
||||
return onRetry;
|
||||
}
|
||||
|
||||
describe("ErrorModal", () => {
|
||||
it("offers a retry when the site changed under the downloader", () => {
|
||||
const onRetry = open("site_changed");
|
||||
expect(screen.getByText("downloads.errors.fail.cat.site_changed.short")).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole("button", { name: "downloads.actions.retry" }));
|
||||
expect(onRetry).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("offers none for an unclassified failure", () => {
|
||||
open("unknown");
|
||||
expect(screen.queryByRole("button", { name: "downloads.actions.retry" })).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -106,7 +106,9 @@ export function MetaEditModal({ job, onClose }: { job: DownloadJob; onClose: ()
|
||||
// retry control regardless. `unknown` is deliberately NOT here: an unclassified failure is most often an
|
||||
// auth wall we couldn't parse (a login-walled Facebook post reads as a bare "no format"), so a
|
||||
// prominent Retry would mislead; force-retrying from the row stays available for genuine transients.
|
||||
const RETRYABLE_ERRORS = ["postprocess", "network", "convert_failed"];
|
||||
// `site_changed` is here: the site answered in a way the downloader did not understand, which a
|
||||
// downloader update on the server cures — the same link then goes through on a retry.
|
||||
const RETRYABLE_ERRORS = ["postprocess", "network", "convert_failed", "site_changed"];
|
||||
|
||||
// A readable "why did this fail" window for a failed job: a category headline + plain explanation,
|
||||
// the raw yt-dlp/ffmpeg text behind a disclosure, and a Retry when the failure may be transient.
|
||||
|
||||
Reference in New Issue
Block a user