fix(feed): round-2 deep-review remediations (incl. a real coerce-on-seed bug)
The re-review of the fix round surfaced 4 findings; fixing the coverage one
exposed that the C1 migration was ineffective:
- FeedFiltersProvider.loadAccountFilters: run coerceTagModes on the RAW stored
blob, not the object merged over DEFAULT_FILTERS. The merge injects
DEFAULT_TAG_MODES (all "or"), which coerceTagModes prefers over the legacy
scalar `tagMode` — so the migration never fired. Now a persisted legacy
tagMode:'and' actually migrates into tagModes.topic (proven by a new test).
- test_feed_views: the tag_mode_other=and case was vacuous (only chC carried
either "other" tag, so AND == OR). Give chA an "other" tag too so OR-other
{chA,chC} differs from AND-other {chC} — the conjunctive path now narrows.
- DateFilterSelect: reset the custom reveal in useLayoutEffect (not useEffect)
so the correction commits before paint — no one-frame "Custom range" flicker
on the trigger after an external clear.
- SavedViewsProvider: extract the sticky-view tie-break to a pure, tested
lib/savedViews.ts (preferSticky).
New tests: savedViews (preferSticky), FeedFiltersProvider.dom (legacy tagMode
migration on the localStorage/default-view seed).
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { preferSticky } from "./savedViews";
|
||||
|
||||
const v = (id: number) => ({ id, name: `v${id}` });
|
||||
|
||||
describe("preferSticky", () => {
|
||||
it("returns null when nothing matches", () => {
|
||||
expect(preferSticky([], 3)).toBeNull();
|
||||
expect(preferSticky([], null)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns the only match regardless of stickyId", () => {
|
||||
expect(preferSticky([v(5)], null)?.id).toBe(5);
|
||||
expect(preferSticky([v(5)], 99)?.id).toBe(5);
|
||||
});
|
||||
|
||||
it("prefers the sticky view among several identical-filter matches", () => {
|
||||
// A (id 1) is earlier in the list, B (id 2) was explicitly applied (sticky).
|
||||
expect(preferSticky([v(1), v(2)], 2)?.id).toBe(2);
|
||||
});
|
||||
|
||||
it("falls back to the first match when the sticky id isn't among the matches", () => {
|
||||
expect(preferSticky([v(1), v(2)], 99)?.id).toBe(1);
|
||||
expect(preferSticky([v(1), v(2)], null)?.id).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
// Pure selection helpers for saved ("personal") views, split out of SavedViewsProvider so the
|
||||
// tie-break logic is unit-testable without mounting the provider (its React Query / context deps).
|
||||
|
||||
/** Among the views whose filters match the live filters, pick the one to treat as active: prefer the
|
||||
* explicitly-applied ("sticky") view when several capture identical filters, so apply(B) isn't
|
||||
* silently re-attributed to an earlier duplicate A; otherwise the first match. Null if none match. */
|
||||
export function preferSticky<T extends { id: number }>(
|
||||
matches: T[],
|
||||
stickyId: number | null,
|
||||
): T | null {
|
||||
return matches.find((v) => v.id === stickyId) ?? matches[0] ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user