diff --git a/apps/web/src/TaskDashboard.spec.ts b/apps/web/src/TaskDashboard.spec.ts index e72316b..0790fe5 100644 --- a/apps/web/src/TaskDashboard.spec.ts +++ b/apps/web/src/TaskDashboard.spec.ts @@ -59,6 +59,25 @@ describe("task dashboard with paginated API",()=>{ expect(wrapper.text()).toContain("Tarefa 27"); expect(listRequests[1]).toContain("page=2"); }); + it("ignores stale load-more results when filters change during pagination",async()=>{ + items=Array.from({length:28},(_,i)=>makeTask("task-"+i,"Tarefa "+i)); + const wrapper=await render(); + let resolvePage!: (value:Response)=>void; + const pending=new Promise(resolve=>{resolvePage=resolve;}); + const original=fetchMock.getMockImplementation() as (url:string,options?:RequestInit)=>Promise; + fetchMock.mockImplementation((url:string,options?:RequestInit)=>{ + if(url.includes("page=2")) return pending; + return original(url,options); + }); + await wrapper.find(".task-pagination button").trigger("click"); + await wrapper.find("#status-filter").setValue("COMPLETED"); + await flushPromises(); + expect(wrapper.findAll(".task-card")).toHaveLength(0); + resolvePage(response({data:items.slice(25),pagination:{page:2,limit:25,total:28,totalPages:2}})); + await flushPromises(); + expect(wrapper.findAll(".task-card")).toHaveLength(0); + expect(wrapper.find(".task-pagination").exists()).toBe(false); + }); it("applies both filters server-side and preserves global counts",async()=>{ items=[makeTask("task-1","Alta em andamento",{status:"IN_PROGRESS",priority:"HIGH"}),makeTask("task-2","Normal em andamento",{status:"IN_PROGRESS"}),makeTask("task-3","Alta concluĂ­da",{status:"COMPLETED",priority:"HIGH"})]; const wrapper=await render(); diff --git a/apps/web/src/TaskDashboard.vue b/apps/web/src/TaskDashboard.vue index 2915547..bda0d78 100644 --- a/apps/web/src/TaskDashboard.vue +++ b/apps/web/src/TaskDashboard.vue @@ -37,6 +37,7 @@ const pageSize = 25; const filteredTotal = ref(0); const loadingMore = ref(false); let loadGeneration = 0; +let loadMoreGeneration = 0; const loading = ref(true); const saving = ref(false); const errorMessage = ref(""); @@ -122,12 +123,15 @@ async function request(url: string, options?: RequestInit): Promise { async function loadTasks() { const generation = ++loadGeneration; + ++loadMoreGeneration; + loadingMore.value = false; loading.value = true; errorMessage.value = ""; retryLoadAvailable.value = false; page.value = 1; tasks.value = []; summary.value = null; + filteredTotal.value = 0; try { const query = new URLSearchParams({ page: "1", limit: String(pageSize) }); if (statusFilter.value !== "ALL") query.set("status", statusFilter.value); @@ -158,6 +162,7 @@ async function loadTasks() { async function loadMoreTasks() { if (loading.value || loadingMore.value || tasks.value.length >= filteredTotal.value) return; const generation = loadGeneration; + const moreGeneration = ++loadMoreGeneration; loadingMore.value = true; errorMessage.value = ""; try { @@ -168,15 +173,15 @@ async function loadMoreTasks() { if (searchTerm.value) query.set("search", searchTerm.value); query.set("sort", sortOrder.value); const result = await request(taskUrl() + "?" + query.toString()); - if (generation !== loadGeneration) return; + if (generation !== loadGeneration || moreGeneration !== loadMoreGeneration) return; const seen = new Set(tasks.value.map(task => task.id)); tasks.value = [...tasks.value, ...result.data.filter(task => !seen.has(task.id))]; page.value = nextPage; filteredTotal.value = result.pagination.total; } catch (error) { - if (generation === loadGeneration) errorMessage.value = error instanceof Error ? error.message : "Falha ao carregar mais tarefas."; + if (generation === loadGeneration && moreGeneration === loadMoreGeneration) errorMessage.value = error instanceof Error ? error.message : "Falha ao carregar mais tarefas."; } finally { - loadingMore.value = false; + if (moreGeneration === loadMoreGeneration) loadingMore.value = false; } }