fix(plex,channels): deep-review fixes — durable watch-sync + no shared-link clobber
Deep-review of the quickfix batch surfaced two data/preference-loss bugs plus smaller items; all fixed with tests. - watch_sync _pull_apply: don't let the Plex-authoritative pull overwrite a DIRTY local row (a change whose immediate push failed) — that stranded the value forever since _repush_dirty only retries dirty rows. Leave it dirty for re-push. - PlexProvider / ChannelsProvider: a partial shared link opened in a fresh tab persisted mount-time DEFAULTS over the account's stored scope/sort/filters (base read before the account was pinned). Persist/keep only URL-present dims; re-hydrate the omitted ones from storage. - playback resume write-back: guard against out-of-order delivery (a superseded older push now drops) and evict the throttle map so it can't grow unbounded. - PlexPlayer: announce mid-playback buffering to assistive tech (role=status + sr-only label); new plex.player.buffering (HU+EN). - columnSort: hoist the shared key:dir sort codec used by both URL-state modules. - Tests: dirty-guard + flag-settle + per-key throttle scoping + superseded-push (backend); hold-repeat/suppress-click/unmount + partial-link no-clobber + refetch-doesn't-clobber-undo (frontend). Backend pytest 475, FE vitest 519.
This commit is contained in:
@@ -80,11 +80,12 @@ function mount() {
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
qc.setQueryData(qk.tags(), [{ id: 1, name: "Docu" }]);
|
||||
vi.spyOn(api, "tags").mockResolvedValue([{ id: 1, name: "Docu" }] as never);
|
||||
return render(
|
||||
const utils = render(
|
||||
<QueryClientProvider client={qc}>
|
||||
<PersonalViewsModal />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
return { ...utils, qc };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -92,6 +93,8 @@ beforeEach(() => {
|
||||
h.saveCurrent.mockClear();
|
||||
h.rename.mockClear();
|
||||
h.activeViewId = null;
|
||||
// Fresh baseline each test — one test mutates CTX.views to simulate a refetch.
|
||||
CTX.views = [{ id: 5, name: "Docs", filters: VIEW_FILTERS, position: 0, is_default: false }];
|
||||
});
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
@@ -119,6 +122,39 @@ describe("PersonalViewsModal — S7 editable detail pane", () => {
|
||||
expect(h.updateFilters.mock.calls[1]![1]).toMatchObject({ tags: [1] });
|
||||
});
|
||||
|
||||
it("a same-view refetch (new-identity filters) does not wipe the undo history", async () => {
|
||||
// Guards the [selectedId]-only re-seed effect (its exhaustive-deps disable): after a chip prune the
|
||||
// real provider refetches and hands back the SAME view id with a fresh-identity filters object. The
|
||||
// draft must NOT re-seed on that (it would destroy the undo history and the Undo control). A deps
|
||||
// regression that added selected.filters would make this fail.
|
||||
const { rerender, qc } = mount();
|
||||
fireEvent.click(await screen.findByLabelText("feed.chips.remove"));
|
||||
await waitFor(() => expect(h.updateFilters).toHaveBeenCalledTimes(1));
|
||||
expect(await screen.findByLabelText("common.undo")).toBeTruthy();
|
||||
|
||||
// Simulate the post-write refetch: same id 5, new object identity, carrying the pruned value.
|
||||
CTX.views = [
|
||||
{
|
||||
id: 5,
|
||||
name: "Docs",
|
||||
filters: { ...VIEW_FILTERS, tags: [] },
|
||||
position: 0,
|
||||
is_default: false,
|
||||
},
|
||||
];
|
||||
rerender(
|
||||
<QueryClientProvider client={qc}>
|
||||
<PersonalViewsModal />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
// Undo history survived the refetch — the control is still there and still re-persists on click.
|
||||
const undo = await screen.findByLabelText("common.undo");
|
||||
fireEvent.click(undo);
|
||||
await waitFor(() => expect(h.updateFilters).toHaveBeenCalledTimes(2));
|
||||
expect(h.updateFilters.mock.calls[1]![1]).toMatchObject({ tags: [1] });
|
||||
});
|
||||
|
||||
it("Save-current stores the live filters into the view when it isn't already the active one", async () => {
|
||||
mount();
|
||||
fireEvent.click(await screen.findByText("views.saveCurrent"));
|
||||
|
||||
Reference in New Issue
Block a user