From f746afa3af1116e18eb9ef58aaab242efefcc4e9 Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Tue, 10 Mar 2026 15:53:12 +0200 Subject: [PATCH] fix(ENG-10424): Fix view files in files section using VOL --- .../files/pages/files/files.component.spec.ts | 140 +++++++++++++++++- .../files/pages/files/files.component.ts | 6 +- 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/app/features/files/pages/files/files.component.spec.ts b/src/app/features/files/pages/files/files.component.spec.ts index 656984423..36741cc1a 100644 --- a/src/app/features/files/pages/files/files.component.spec.ts +++ b/src/app/features/files/pages/files/files.component.spec.ts @@ -4,7 +4,7 @@ import { DialogService } from 'primeng/dynamicdialog'; import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { SENTRY_TOKEN } from '@core/provider/sentry.provider'; import { FileUploadDialogComponent } from '@osf/shared/components/file-upload-dialog/file-upload-dialog.component'; @@ -18,8 +18,10 @@ import { CustomConfirmationService } from '@osf/shared/services/custom-confirmat import { FilesService } from '@osf/shared/services/files.service'; import { CurrentResourceSelectors } from '@osf/shared/stores/current-resource'; import { GoogleFilePickerComponent } from '@shared/components/google-file-picker/google-file-picker.component'; +import { FileLabelModel } from '@shared/models/files/file-label.model'; import { FilesSelectionActionsComponent } from '../../components'; +import { FileProvider } from '../../constants'; import { FilesSelectors } from '../../store'; import { FilesComponent } from './files.component'; @@ -29,6 +31,8 @@ import { getNodeFilesMappedData } from '@testing/data/files/node.data'; import { testNode } from '@testing/mocks/base-node.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; import { MockComponentWithSignal } from '@testing/providers/component-provider.mock'; +import { ActivatedRouteMock } from '@testing/providers/route-provider.mock'; +import { provideRouterMock, RouterMockType } from '@testing/providers/router-provider.mock'; import { provideMockStore } from '@testing/providers/store-provider.mock'; describe('Component: Files', () => { @@ -188,4 +192,138 @@ describe('Component: Files', () => { expect(() => component.updateFilesList()).not.toThrow(); }); }); + + describe('handleRootFolderChange', () => { + it('should preserve view_only query param when switching storage providers', () => { + const router = TestBed.inject(Router); + const navigateSpy = jest.spyOn(router, 'navigate').mockResolvedValue(true); + + const selectedFolder: FileLabelModel = { + label: 'Dropbox', + folder: { provider: FileProvider.Dropbox } as any, + }; + + component.handleRootFolderChange(selectedFolder); + + expect(navigateSpy).toHaveBeenCalledWith([`/${component.resourceId()}/files`, FileProvider.Dropbox], { + queryParamsHandling: 'preserve', + }); + }); + }); + + describe('invalid provider fallback effect', () => { + let innerComponent: FilesComponent; + let innerFixture: ComponentFixture; + let routerMock: RouterMockType; + + beforeEach(async () => { + jest.clearAllMocks(); + routerMock = { + ...TestBed.inject(Router), + navigate: jest.fn().mockResolvedValue(true), + url: '/abc123/files/unknownprovider?view_only=testtoken', + } as RouterMockType; + + await TestBed.configureTestingModule({ + imports: [ + FilesComponent, + OSFTestingModule, + ...MockComponents( + FileUploadDialogComponent, + FormSelectComponent, + GoogleFilePickerComponent, + LoadingSpinnerComponent, + SearchInputComponent, + SubHeaderComponent, + ViewOnlyLinkMessageComponent, + GoogleFilePickerComponent, + FilesSelectionActionsComponent + ), + ], + providers: [ + FilesService, + MockProvider(CustomConfirmationService), + DialogService, + { + provide: SENTRY_TOKEN, + useValue: { + captureException: jest.fn(), + captureMessage: jest.fn(), + setUser: jest.fn(), + }, + }, + { + provide: ActivatedRoute, + useValue: ActivatedRouteMock.withParams({ fileProvider: 'unknownprovider' }).build(), + }, + provideRouterMock(routerMock), + provideMockStore({ + signals: [ + { + selector: CurrentResourceSelectors.getResourceDetails, + value: testNode, + }, + { + selector: FilesSelectors.getRootFolders, + value: getNodeFilesMappedData(), + }, + { + selector: FilesSelectors.getCurrentFolder, + value: getNodeFilesMappedData(0), + }, + { + selector: FilesSelectors.getConfiguredStorageAddons, + value: getConfiguredAddonsMappedData(), + }, + { + selector: FilesSelectors.getProvider, + value: 'osfstorage', + }, + { + selector: FilesSelectors.getStorageSupportedFeatures, + value: { + osfstorage: ['AddUpdateFiles', 'DownloadAsZip', 'DeleteFiles', 'CopyInto'], + }, + }, + ], + }), + ], + }) + .overrideComponent(FilesComponent, { + remove: { + imports: [FilesTreeComponent], + }, + add: { + imports: [ + MockComponentWithSignal('osf-files-tree', [ + 'files', + 'currentFolder', + 'isLoading', + 'viewOnly', + 'resourceId', + 'provider', + 'storage', + 'totalCount', + 'allowedMenuActions', + 'supportUpload', + 'selectedFiles', + 'scrollHeight', + ]), + ], + }, + }) + .compileComponents(); + + innerFixture = TestBed.createComponent(FilesComponent); + innerComponent = innerFixture.componentInstance; + innerFixture.detectChanges(); + }); + + it('should preserve view_only query param when redirecting to osfstorage for invalid provider', () => { + expect(routerMock.navigate).toHaveBeenCalledWith( + [`/${innerComponent.resourceId()}/files`, FileProvider.OsfStorage], + { queryParamsHandling: 'preserve' } + ); + }); + }); }); diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts index f06b4a010..75b852eae 100644 --- a/src/app/features/files/pages/files/files.component.ts +++ b/src/app/features/files/pages/files/files.component.ts @@ -292,7 +292,9 @@ export class FilesComponent { const rootFoldersOption = rootFoldersOptions.find((option) => option.folder.provider === providerName); if (!rootFoldersOption) { - this.router.navigate([`/${this.resourceId()}/files`, FileProvider.OsfStorage]); + this.router.navigate([`/${this.resourceId()}/files`, FileProvider.OsfStorage], { + queryParamsHandling: 'preserve', + }); } else { this.currentRootFolder.set({ label: rootFoldersOption.label, @@ -688,6 +690,6 @@ export class FilesComponent { handleRootFolderChange(selectedFolder: FileLabelModel) { const provider = selectedFolder.folder?.provider; const resourceId = this.resourceId(); - this.router.navigate([`/${resourceId}/files`, provider]); + this.router.navigate([`/${resourceId}/files`, provider], { queryParamsHandling: 'preserve' }); } }