fix(downloads): GIF review round 2 — cap as sent, shown length, retry, Join reason
The panels judge a GIF's length in whole milliseconds between the ends as sent, the server's own rule, and a refused length reads rounded up. A shared picture says it failed only once its retry failed too. Join's reason shows on a key's focus; the editor's failure toasts name GIFs. The edit route's cap is test-held, and DESIGN.md records the GIF UI.
This commit is contained in:
@@ -4,8 +4,10 @@ import {
|
||||
gifEstimateText,
|
||||
GIF_MAX_BYTES,
|
||||
GIF_PREVIEW_MAX_BYTES,
|
||||
gifShownLength,
|
||||
gifVerdict,
|
||||
} from "./gif";
|
||||
import { formatTimecode } from "./timecode";
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
|
||||
@@ -50,22 +52,42 @@ describe("gifEstimateText", () => {
|
||||
|
||||
describe("gifVerdict", () => {
|
||||
it("refuses past the length cap, whatever the size", () => {
|
||||
expect(gifVerdict(30.2, { hi: 1 })).toBe("too_long");
|
||||
expect(gifVerdict(30, { hi: 1 })).toBe("ok");
|
||||
expect(gifVerdict(0, 30.2, { hi: 1 })).toBe("too_long");
|
||||
expect(gifVerdict(0, 30, { hi: 1 })).toBe("ok");
|
||||
});
|
||||
|
||||
it("judges the length the server judges: whole milliseconds between the ends as sent", () => {
|
||||
// A float difference a hair over 30 s is exactly 30 s once the ends are sent.
|
||||
expect(60.529 - 30.529).toBeGreaterThan(30);
|
||||
expect(gifVerdict(30.529, 60.529, { hi: 1 })).toBe("ok");
|
||||
expect(gifVerdict(0, 30.001, { hi: 1 })).toBe("too_long");
|
||||
// Each end is sent rounded on its own: 10.0004–40.0008 goes as 10–40.001, 30001 ms, refused…
|
||||
expect(gifVerdict(10.0004, 40.0008, { hi: 1 })).toBe("too_long");
|
||||
// …and 10.0006–40.0012 as 10.001–40.001, exactly 30 s, made.
|
||||
expect(gifVerdict(10.0006, 40.0012, { hi: 1 })).toBe("ok");
|
||||
});
|
||||
|
||||
it("warns past the server's cap, then past the link preview's", () => {
|
||||
expect(gifVerdict(10, { hi: GIF_MAX_BYTES + 0.6 * MB })).toBe("may_fail");
|
||||
expect(gifVerdict(10, { hi: GIF_MAX_BYTES })).toBe("no_preview");
|
||||
expect(gifVerdict(10, { hi: GIF_PREVIEW_MAX_BYTES + 0.6 * MB })).toBe("no_preview");
|
||||
expect(gifVerdict(10, { hi: GIF_PREVIEW_MAX_BYTES })).toBe("ok");
|
||||
expect(gifVerdict(0, 10, { hi: GIF_MAX_BYTES + 0.6 * MB })).toBe("may_fail");
|
||||
expect(gifVerdict(0, 10, { hi: GIF_MAX_BYTES })).toBe("no_preview");
|
||||
expect(gifVerdict(0, 10, { hi: GIF_PREVIEW_MAX_BYTES + 0.6 * MB })).toBe("no_preview");
|
||||
expect(gifVerdict(0, 10, { hi: GIF_PREVIEW_MAX_BYTES })).toBe("ok");
|
||||
});
|
||||
|
||||
it("judges the size the reader is shown: a high end shown as 30 MB is not past 30 MB", () => {
|
||||
// 30 s at 480 lands a hair over 30 MiB, and reads "about 12–30 MB".
|
||||
const hi = GIF_MAX_BYTES + 0.4 * MB;
|
||||
expect(gifEstimateText({ lo: 12 * MB, hi }, "en").hi).toBe("30");
|
||||
expect(gifVerdict(30, { hi })).toBe("no_preview");
|
||||
expect(gifVerdict(10, { hi: GIF_PREVIEW_MAX_BYTES + 0.4 * MB })).toBe("ok");
|
||||
expect(gifVerdict(0, 30, { hi })).toBe("no_preview");
|
||||
expect(gifVerdict(0, 10, { hi: GIF_PREVIEW_MAX_BYTES + 0.4 * MB })).toBe("ok");
|
||||
});
|
||||
});
|
||||
|
||||
describe("gifShownLength", () => {
|
||||
it("never shows a refused range as one the cap allows", () => {
|
||||
expect(formatTimecode(gifShownLength(0, 30.03))).toBe("0:30.1");
|
||||
expect(formatTimecode(gifShownLength(0, 30.001))).toBe("0:30.1");
|
||||
expect(gifShownLength(30.529, 60.529)).toBe(30);
|
||||
expect(gifShownLength(0, 12.34)).toBe(12.34);
|
||||
});
|
||||
});
|
||||
|
||||
+18
-2
@@ -53,8 +53,24 @@ export function gifEstimateText(est: { lo: number; hi: number }, lang: string) {
|
||||
* pass the cap (refused when it does), it may be too big to move in a link preview, or fine. */
|
||||
export type GifVerdict = "too_long" | "may_fail" | "no_preview" | "ok";
|
||||
|
||||
export function gifVerdict(seconds: number, est: { hi: number }): GifVerdict {
|
||||
if (seconds > GIF_MAX_S + 0.0005) return "too_long";
|
||||
/** A time as the request sends it: to the millisecond. */
|
||||
export const atMs = (s: number) => Math.round(s * 1000) / 1000;
|
||||
|
||||
/** A range's length in whole milliseconds, between its ends as the request sends them: the length
|
||||
* the server judges (gif.too_long), so a range the panel allows is never refused for its length,
|
||||
* and none it refuses would have been made. */
|
||||
export const spanMs = (start: number, end: number) =>
|
||||
Math.round(end * 1000) - Math.round(start * 1000);
|
||||
|
||||
/** A range's length to show beside a verdict: over the cap it is rounded up to the tenth, so a
|
||||
* refused range never reads as one the cap allows ("0:30.0" for 30.03 s). */
|
||||
export function gifShownLength(start: number, end: number): number {
|
||||
const ms = spanMs(start, end);
|
||||
return ms > GIF_MAX_S * 1000 ? Math.ceil(ms / 100) / 10 : ms / 1000;
|
||||
}
|
||||
|
||||
export function gifVerdict(start: number, end: number, est: { hi: number }): GifVerdict {
|
||||
if (spanMs(start, end) > GIF_MAX_S * 1000) return "too_long";
|
||||
// Judged on the high end as shown (whole megabytes): "about 12–30 MB" beside "it may pass 30 MB"
|
||||
// would contradict itself, and the estimate is no finer than that anyway.
|
||||
const hi = Math.round(est.hi / MB) * MB;
|
||||
|
||||
Reference in New Issue
Block a user