Playback reporting (position sync / resume-on-another-device): - player_configure_jellyfin now builds a PlaybackReporter sharing the player controller's Arc, so Start/Progress/Stopped actually reach Jellyfin on every auth path (login/restore/reauth); previously they never did. - The PlaybackReporterWrapper now shares the same Arc the controller and MPV progress loop report through, instead of a dead parallel Option. - Android position callbacks now emit throttled progress reports (30s/item), mirroring the MPV backend. Duration flash on pause: - resolveDuration() prefers the live store duration for the already-loaded track over the runTimeTicks estimate, so pausing no longer clobbers the slider's max to 0 when runTimeTicks is missing. Video leaking into audio mini player: - isVideoItem() also checks the backend PlayerMediaItem mediaType discriminator, so a video started via player_play_item (no Jellyfin `type`, mediaType "video") no longer surfaces in the audio mini player. Middle-truncation of long media names: - New truncateMiddle util applied to track/episode/card/mini-player titles so distinguishing tails (episode numbers, suffixes) stay visible. Adds regression tests for the duration and mini-player fixes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { truncateMiddle } from "./truncateMiddle";
|
|
|
|
describe("truncateMiddle", () => {
|
|
it("returns short strings unchanged", () => {
|
|
expect(truncateMiddle("short", 32)).toBe("short");
|
|
expect(truncateMiddle("exactly-len", 11)).toBe("exactly-len");
|
|
});
|
|
|
|
it("abbreviates in the middle keeping head and tail", () => {
|
|
const result = truncateMiddle("long_media_name_like_this", 16);
|
|
expect(result).toContain("…");
|
|
expect(result.length).toBe(16);
|
|
expect(result.startsWith("long")).toBe(true);
|
|
expect(result.endsWith("this")).toBe(true);
|
|
});
|
|
|
|
it("never exceeds maxLength", () => {
|
|
for (const len of [1, 2, 3, 5, 10, 25]) {
|
|
expect(truncateMiddle("a".repeat(100), len).length).toBeLessThanOrEqual(len);
|
|
}
|
|
});
|
|
|
|
it("handles null and undefined", () => {
|
|
expect(truncateMiddle(null)).toBe("");
|
|
expect(truncateMiddle(undefined)).toBe("");
|
|
});
|
|
|
|
it("falls back to head-truncation when there is no room for content", () => {
|
|
expect(truncateMiddle("abcdef", 1)).toBe("a");
|
|
expect(truncateMiddle("abcdef", 0)).toBe("");
|
|
});
|
|
|
|
it("supports a custom ellipsis", () => {
|
|
const result = truncateMiddle("long_media_name_like_this", 16, "...");
|
|
expect(result).toContain("...");
|
|
expect(result.length).toBe(16);
|
|
});
|
|
});
|