From 7f4971fa4936257294550ae1db23c92394858a8b Mon Sep 17 00:00:00 2001 From: itzptk Date: Fri, 5 Jun 2026 01:11:47 +0200 Subject: [PATCH] fix: append history load more results --- src/app/core/stores/history.store.spec.ts | 94 +++++++++++++++++++ src/app/core/stores/history.store.ts | 2 +- .../history-view/history-view.component.html | 10 +- .../history-view.component.spec.ts | 11 ++- 4 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 src/app/core/stores/history.store.spec.ts diff --git a/src/app/core/stores/history.store.spec.ts b/src/app/core/stores/history.store.spec.ts new file mode 100644 index 0000000..4a26617 --- /dev/null +++ b/src/app/core/stores/history.store.spec.ts @@ -0,0 +1,94 @@ +import { TestBed } from '@angular/core/testing'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import type { HistoryListRequest, HistoryListResponse, HistoryStats, JobHistoryRecord } from '../../../shared/contracts'; +import { BrewFacadeService } from '../services/brew-facade.service'; +import { HistoryStore } from './history.store'; + +const stats: HistoryStats = { + totalJobs: 2, + successRate: 1, + medianDurationMs: 100, + last7Days: { total: 2, succeeded: 2, failed: 0 }, + failureRateByAction: [] +}; + +describe('HistoryStore', () => { + afterEach(() => { + TestBed.resetTestingModule(); + }); + + it('appends later history pages to the currently loaded rows', async () => { + const firstPageRecord = historyRecord('job-1'); + const secondPageRecord = historyRecord('job-2'); + const { store, facade } = setupHistoryStore({ + 1: [firstPageRecord], + 2: [secondPageRecord] + }, 2); + + await store.load(1); + await store.load(2); + + expect(store.items().map((item) => item.jobId)).toEqual(['job-1', 'job-2']); + expect(store.total()).toBe(2); + expect(store.page()).toBe(2); + expect(facade.listHistory).toHaveBeenNthCalledWith(1, { page: 1, pageSize: 50 }); + expect(facade.listHistory).toHaveBeenNthCalledWith(2, { page: 2, pageSize: 50 }); + }); + + it('replaces loaded history rows when reloading the first page', async () => { + const originalFirstPageRecord = historyRecord('job-1'); + const secondPageRecord = historyRecord('job-2'); + const refreshedFirstPageRecord = historyRecord('job-3'); + const pages: Record = { + 1: [originalFirstPageRecord], + 2: [secondPageRecord] + }; + const { store } = setupHistoryStore(pages, 2); + + await store.load(1); + await store.load(2); + pages[1] = [refreshedFirstPageRecord]; + await store.load(1); + + expect(store.items().map((item) => item.jobId)).toEqual(['job-3']); + expect(store.page()).toBe(1); + }); +}); + +function setupHistoryStore(pages: Record, total: number) { + const facade = { + listHistory: vi.fn(async (request: HistoryListRequest): Promise => ({ + items: pages[request.page ?? 1] ?? [], + total, + page: request.page ?? 1, + pageSize: request.pageSize ?? 50 + })), + getHistoryStats: vi.fn(async () => stats) + }; + + TestBed.configureTestingModule({ + providers: [{ provide: BrewFacadeService, useValue: facade }] + }); + + const store = TestBed.inject(HistoryStore); + return { store, facade }; +} + +function historyRecord(jobId: string): JobHistoryRecord { + return { + jobId, + action: 'upgradeAll', + packageName: null, + kind: 'system', + status: 'succeeded', + command: 'brew upgrade', + exitCode: 0, + durationMs: 100, + error: null, + source: 'manual', + startedAt: '2026-01-01T00:00:00.000Z', + completedAt: '2026-01-01T00:01:00.000Z', + outputTail: null + }; +} diff --git a/src/app/core/stores/history.store.ts b/src/app/core/stores/history.store.ts index 42c2207..b574025 100644 --- a/src/app/core/stores/history.store.ts +++ b/src/app/core/stores/history.store.ts @@ -45,7 +45,7 @@ export const HistoryStore = signalStore( ]); patchState(store, { - items: list.items, + items: page === 1 ? list.items : [...store.items(), ...list.items], total: list.total, stats, loading: false diff --git a/src/app/features/history/history-view/history-view.component.html b/src/app/features/history/history-view/history-view.component.html index 9a39f1e..20fbfed 100644 --- a/src/app/features/history/history-view/history-view.component.html +++ b/src/app/features/history/history-view/history-view.component.html @@ -83,9 +83,13 @@

History

} -
- Load more -
+ @if (historyStore.items().length < historyStore.total()) { +
+ + Load more + +
+ } } @if (selectedSnapshot(); as snapshot) { diff --git a/src/app/features/history/history-view/history-view.component.spec.ts b/src/app/features/history/history-view/history-view.component.spec.ts index e22815a..33fd831 100644 --- a/src/app/features/history/history-view/history-view.component.spec.ts +++ b/src/app/features/history/history-view/history-view.component.spec.ts @@ -68,10 +68,10 @@ const stats: HistoryStats = { }; describe('HistoryViewComponent', () => { - async function render(items: JobHistoryRecord[] = [record]) { + async function render(items: JobHistoryRecord[] = [record], total = items.length) { const store = { items: signal(items), - total: signal(items.length), + total: signal(total), page: signal(1), pageSize: signal(50), stats: signal(stats), @@ -115,6 +115,13 @@ describe('HistoryViewComponent', () => { expect(html.textContent).toContain('Showing first 3 of 4 packages. 1 omitted from persisted history.'); expect(html.textContent).not.toContain('upgrade complete'); }); + + it('hides the load more button after all history rows are loaded', async () => { + const { fixture } = await render([record], 1); + const html = fixture.nativeElement as HTMLElement; + + expect(findButtonByText(html, 'Load more')).toBeNull(); + }); }); function findButtonByText(root: HTMLElement, text: string): HTMLElement | null {