Files
siftlode/frontend/src/lib/useUndoable.dom.test.ts
T
peter 035ee937a0 fix(playlists): the list shows the order the server holds
A read with the same rows in another order never reached the screen: a
revert to YouTube, a sync or another device left the old order showing, and
the next write put it back on the server. With no write out on any list the
screen now takes it, and the undo history built on the old order goes. Own
reorders still keep theirs. This replaces the one flag all lists shared after
a rollback, and shows a reorder accepted after its list was picked again.

Also what the fourth review found: a list's reorders go one after the other,
a toast Undo asks for its place once the writes out have landed, an accepted
reorder keeps fresher rows from the cache, and a Play all disabled by the last
row going no longer strands the focus.
2026-11-03 03:01:17 +01:00

51 lines
2.0 KiB
TypeScript

// @vitest-environment jsdom
//
// The undo container chains calls made in one tick: a `reset` not yet committed is what the next
// call builds on. A `rebase` read the mirror ref, which only follows a commit, so it rebased the
// value from BEFORE the reset and wrote that back — the playlist page, remounted under StrictMode
// over a cached list, re-ran its seeding effect (reset, then rebase) and painted an empty list.
import { describe, expect, it } from "vitest";
import { act, renderHook } from "@testing-library/react";
import { useUndoable } from "./useUndoable";
describe("useUndoable", () => {
it("rebases the value a reset in the same tick installed, not the one before it", () => {
const { result } = renderHook(() => useUndoable<number[]>([]));
act(() => {
result.current.reset([1, 2]);
result.current.rebase((xs) => xs.map((x) => x * 10));
});
expect(result.current.value).toEqual([10, 20]);
});
it("peeks at a value set in this tick, before it renders", () => {
const { result } = renderHook(() => useUndoable<number[]>([1]));
const { peek, set } = result.current;
act(() => {
set([1, 2]);
expect(peek()).toEqual([1, 2]);
});
expect(peek()).toEqual([1, 2]);
});
it("keeps the undo history through a rebase", () => {
const { result } = renderHook(() => useUndoable<number[]>([1]));
act(() => result.current.set([1, 2]));
act(() => result.current.rebase((xs) => xs.map((x) => x + 1)));
expect(result.current.value).toEqual([2, 3]);
act(() => result.current.undo());
expect(result.current.value).toEqual([2]);
});
it("undoes to a rebased snapshot when the undo comes in the same tick as the rebase", () => {
const { result } = renderHook(() => useUndoable<number[]>([1, 2]));
act(() => result.current.set([2, 1]));
act(() => {
result.current.rebase((xs) => xs.filter((x) => x !== 2)); // a row removed
result.current.undo();
});
expect(result.current.value).toEqual([1]);
expect(result.current.canRedo).toBe(true);
});
});