diff --git a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts index 2e3797ea6..dd307274f 100644 --- a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts +++ b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.spec.ts @@ -2,9 +2,11 @@ import { MockComponents, MockProvider } from 'ng-mocks'; import { DialogService } from 'primeng/dynamicdialog'; +import { signal, WritableSignal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OperationNames } from '@shared/enums/operation-names.enum'; +import { OperationInvocation } from '@shared/models/addons/operation-invocation.model'; import { AddonsSelectors } from '@shared/stores/addons'; import { GoogleFilePickerComponent } from '../../google-file-picker/google-file-picker.component'; @@ -115,4 +117,144 @@ describe('StorageItemSelectorComponent', () => { expect(breadcrumbs[0].id).toBe(itemId); expect(breadcrumbs[0].label).toBe(itemName); }); + + describe('showLoadMoreButton', () => { + let mockOperationInvocation: WritableSignal; + + beforeEach(async () => { + mockDialogService = DialogServiceMockBuilder.create().withOpenMock().build(); + mockOperationInvocation = signal(null); + + await TestBed.resetTestingModule() + .configureTestingModule({ + imports: [ + StorageItemSelectorComponent, + OSFTestingModule, + ...MockComponents(GoogleFilePickerComponent, SelectComponent), + ], + providers: [ + provideMockStore({ + signals: [ + { + selector: AddonsSelectors.getSelectedStorageItem, + value: null, + }, + { + selector: AddonsSelectors.getOperationInvocationSubmitting, + value: false, + }, + { + selector: AddonsSelectors.getCreatedOrUpdatedConfiguredAddonSubmitting, + value: false, + }, + { + selector: AddonsSelectors.getOperationInvocation, + value: mockOperationInvocation, + }, + ], + }), + MockProvider(DialogService, mockDialogService), + ], + }) + .compileComponents(); + + fixture = TestBed.createComponent(StorageItemSelectorComponent); + component = fixture.componentInstance; + fixture.componentRef.setInput('isGoogleFilePicker', false); + fixture.componentRef.setInput('accountName', 'test-account'); + fixture.componentRef.setInput('accountId', 'test-id'); + fixture.componentRef.setInput('operationInvocationResult', []); + }); + + it('should return false when operationInvocation is null', () => { + mockOperationInvocation.set(null); + fixture.detectChanges(); + + expect(component.showLoadMoreButton()).toBe(false); + }); + + it('should return false when nextSampleCursor is not present', () => { + mockOperationInvocation.set({ + id: 'test-id', + type: 'operation-invocation', + invocationStatus: 'success', + operationName: 'list_root_items', + operationKwargs: {}, + operationResult: [], + itemCount: 10, + thisSampleCursor: 'cursor-1', + }); + fixture.detectChanges(); + + expect(component.showLoadMoreButton()).toBe(false); + }); + + it('should return true when nextSampleCursor differs from thisSampleCursor', () => { + mockOperationInvocation.set({ + id: 'test-id', + type: 'operation-invocation', + invocationStatus: 'success', + operationName: 'list_root_items', + operationKwargs: {}, + operationResult: [], + itemCount: 20, + thisSampleCursor: 'cursor-1', + nextSampleCursor: 'cursor-2', + }); + fixture.detectChanges(); + + expect(component.showLoadMoreButton()).toBe(true); + }); + + it('should return true for opaque/base64 cursors like GitLab uses', () => { + // GitLab uses base64-encoded cursors where lexicographic comparison doesn't work + mockOperationInvocation.set({ + id: 'test-id', + type: 'operation-invocation', + invocationStatus: 'success', + operationName: 'list_root_items', + operationKwargs: {}, + operationResult: [], + itemCount: 20, + thisSampleCursor: 'eyJpZCI6MTIzfQ==', + nextSampleCursor: 'eyJpZCI6MTQ1fQ==', + }); + fixture.detectChanges(); + + expect(component.showLoadMoreButton()).toBe(true); + }); + + it('should return false when nextSampleCursor equals thisSampleCursor', () => { + mockOperationInvocation.set({ + id: 'test-id', + type: 'operation-invocation', + invocationStatus: 'success', + operationName: 'list_root_items', + operationKwargs: {}, + operationResult: [], + itemCount: 10, + thisSampleCursor: 'cursor-1', + nextSampleCursor: 'cursor-1', + }); + fixture.detectChanges(); + + expect(component.showLoadMoreButton()).toBe(false); + }); + + it('should return true when nextSampleCursor exists but thisSampleCursor is undefined', () => { + mockOperationInvocation.set({ + id: 'test-id', + type: 'operation-invocation', + invocationStatus: 'success', + operationName: 'list_root_items', + operationKwargs: {}, + operationResult: [], + itemCount: 20, + nextSampleCursor: 'cursor-2', + }); + fixture.detectChanges(); + + expect(component.showLoadMoreButton()).toBe(true); + }); + }); }); diff --git a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts index 597e887e2..27b66d12d 100644 --- a/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts +++ b/src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts @@ -193,10 +193,10 @@ export class StorageItemSelectorComponent implements OnInit { readonly showLoadMoreButton = computed(() => { const invocation = this.operationInvocation(); - if (!invocation?.nextSampleCursor || !invocation?.thisSampleCursor) { + if (!invocation?.nextSampleCursor) { return false; } - return invocation.nextSampleCursor > invocation.thisSampleCursor; + return invocation.nextSampleCursor !== invocation.thisSampleCursor; }); handleCreateOperationInvocation(