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.
51 lines
2.0 KiB
TypeScript
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);
|
|
});
|
|
});
|