fix(autoobserve): never show a previous org's held total in the suggestions drawer

A total shrink holds the old total while the drawer moves back to the last page. An org switch now drops that hold, and an older clamp can no longer clear or overwrite the newer org's.
This commit is contained in:
Ashish Kolhe
2026-11-03 11:59:20 +05:30
parent ef4d17ad07
commit 41ec2b73db
2 changed files with 62 additions and 1 deletions
@@ -244,4 +244,60 @@ describe("AllSuggestionsDrawer (journey 3)", () => {
expect(wrapper.find(COUNT).text()).toBe("24");
expect(wrapper.findComponent(OTable).props("loading")).toBe(false);
});
it("never shows the previous org's held total when the org switches mid-clamp", async () => {
api.mockResolvedValueOnce(page(["a1"], 30));
const wrapper = mountDrawer();
await flushPromises();
api.mockResolvedValueOnce(page([], 24));
let release: (value: unknown) => void = () => undefined;
api.mockReturnValueOnce(new Promise((resolve) => (release = resolve)) as never);
wrapper.findComponent(OTable).vm.$emit("pagination-change", { page: 2, pageSize: 25 });
await flushPromises();
expect(wrapper.find(COUNT).text()).toBe("24");
api.mockResolvedValueOnce(page(["b1"], 1));
setOrg("org-b");
await flushPromises();
expect(rows(wrapper).map((r) => r.id)).toEqual(["b1"]);
expect(wrapper.find(COUNT).text()).toBe("1");
expect(wrapper.findComponent(OTable).props("loading")).toBe(false);
release(page(["a1"], 24));
await flushPromises();
expect(rows(wrapper).map((r) => r.id)).toEqual(["b1"]);
expect(wrapper.find(COUNT).text()).toBe("1");
expect(wrapper.findComponent(OTable).props("loading")).toBe(false);
});
it("keeps the new org's own clamp when the old org's clamp refetch settles late", async () => {
const held = () => {
let release: (value: unknown) => void = () => undefined;
const promise = new Promise((resolve) => (release = resolve));
return { promise: promise as never, release: (v: unknown) => release(v) };
};
const toPage2 = () =>
wrapper.findComponent(OTable).vm.$emit("pagination-change", { page: 2, pageSize: 25 });
api.mockResolvedValueOnce(page(["a1"], 30));
const wrapper = mountDrawer();
await flushPromises();
const orgA = held();
api.mockResolvedValueOnce(page([], 24)).mockReturnValueOnce(orgA.promise);
toPage2();
await flushPromises();
api.mockResolvedValueOnce(page(["b1"], 30));
setOrg("org-b");
await flushPromises();
const orgB = held();
api.mockResolvedValueOnce(page([], 20)).mockReturnValueOnce(orgB.promise);
toPage2();
await flushPromises();
expect(wrapper.find(COUNT).text()).toBe("20");
orgA.release(page(["a1"], 24));
await flushPromises();
expect(wrapper.find(COUNT).text()).toBe("20");
expect(wrapper.findComponent(OTable).props("loading")).toBe(true);
orgB.release(page(["b1"], 20));
await flushPromises();
expect(wrapper.find(COUNT).text()).toBe("20");
expect(wrapper.findComponent(OTable).props("loading")).toBe(false);
});
});
@@ -177,6 +177,8 @@ const rows = computed<SuggestionRow[]>(() =>
toSuggestionRows(t, zone.value, query.data.value?.suggestions ?? []),
);
const clampTotal = ref<number | null>(null);
// An org switch voids the hold, so an older org's clamp can never set or clear the newer org's total.
let clampRun = 0;
const total = computed(() => clampTotal.value ?? query.data.value?.total ?? 0);
const skeleton = useHeldSkeleton(() => props.open && state.value.status === "loading");
const paging = computed(() => query.isPlaceholderData.value && query.isFetching.value);
@@ -202,16 +204,19 @@ watch(
watch(org, () => {
page.value = 1;
clampRun += 1;
clampTotal.value = null;
});
// The page it lands on may be cached from before the total shrank, so it is fetched again under the new total.
watch(pastEnd, async (past) => {
if (!past) return;
const run = ++clampRun;
clampTotal.value = total.value;
page.value = Math.max(1, Math.ceil(clampTotal.value / DRAWER_PAGE));
await nextTick();
if (!query.isPlaceholderData.value) await query.refetch();
clampTotal.value = null;
if (run === clampRun) clampTotal.value = null;
});
const columns = computed<OTableColumnDef<SuggestionRow>[]>(() => [