diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html index 0f3e2636a..b318cbc51 100644 --- a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html +++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html @@ -25,7 +25,12 @@

@if (isDraft()) {

{{ file.file_name }}

} @else { - + {{ file.file_name }} } diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.spec.ts b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.spec.ts index bfc7847a0..b5c846f18 100644 --- a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.spec.ts +++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.spec.ts @@ -1,10 +1,15 @@ -import { TestBed } from '@angular/core/testing'; +import { MockProvider } from 'ng-mocks'; + +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FieldType } from '@osf/shared/enums/field-type.enum'; import { Question } from '@osf/shared/models/registration/page-schema.model'; +import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service'; import { MOCK_REVIEW } from '@testing/mocks/review.mock'; import { provideOSFCore } from '@testing/osf.testing.provider'; +import { provideRouterMock, RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock'; +import { ViewOnlyLinkHelperMock, ViewOnlyLinkHelperMockType } from '@testing/providers/view-only-link-helper.mock'; import { RegistrationBlocksDataComponent } from './registration-blocks-data.component'; @@ -13,39 +18,61 @@ const MOCK_QUESTIONS: Question[] = [ { id: '2', displayText: 'Q2', required: false, responseKey: 'question2', fieldType: FieldType.Checkbox }, ]; +interface SetupOverrides { + routerUrl?: string; + viewOnlyParam?: string | null; +} + describe('RegistrationBlocksDataComponent', () => { - beforeEach(() => { + let component: RegistrationBlocksDataComponent; + let fixture: ComponentFixture; + let routerMock: RouterMockType; + let viewOnlyHelper: ViewOnlyLinkHelperMockType; + + function setup(overrides: SetupOverrides = {}) { + routerMock = RouterMockBuilder.create() + .withUrl(overrides.routerUrl ?? '/') + .build(); + viewOnlyHelper = ViewOnlyLinkHelperMock.simple(); + viewOnlyHelper.getViewOnlyParamFromUrl.mockReturnValue( + overrides.viewOnlyParam !== undefined ? overrides.viewOnlyParam : null + ); + TestBed.configureTestingModule({ imports: [RegistrationBlocksDataComponent], - providers: [provideOSFCore()], + providers: [ + provideOSFCore(), + provideRouterMock(routerMock), + MockProvider(ViewOnlyLinkHelperService, viewOnlyHelper), + ], }); - }); - it('should create', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); + fixture = TestBed.createComponent(RegistrationBlocksDataComponent); + component = fixture.componentInstance; fixture.detectChanges(); + } + + beforeEach(() => setup()); - expect(fixture.componentInstance).toBeTruthy(); + it('should create', () => { + expect(component).toBeTruthy(); }); it('should compute updatedKeysMap from updatedFields', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); fixture.componentRef.setInput('updatedFields', ['question1', 'question3']); fixture.detectChanges(); - expect(fixture.componentInstance.updatedKeysMap()).toEqual({ question1: true, question3: true }); + expect(component.updatedKeysMap()).toEqual({ question1: true, question3: true }); }); it('should return empty updatedKeysMap when updatedFields is empty', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); fixture.componentRef.setInput('updatedFields', []); fixture.detectChanges(); - expect(fixture.componentInstance.updatedKeysMap()).toEqual({}); + expect(component.updatedKeysMap()).toEqual({}); }); it('should render questions with review data', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); fixture.componentRef.setInput('questions', MOCK_QUESTIONS); fixture.componentRef.setInput('reviewData', MOCK_REVIEW); fixture.detectChanges(); @@ -56,7 +83,6 @@ describe('RegistrationBlocksDataComponent', () => { }); it('should show required error when question is required and no data on non-overview page', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); fixture.componentRef.setInput('questions', MOCK_QUESTIONS); fixture.componentRef.setInput('reviewData', {}); fixture.componentRef.setInput('isOverviewPage', false); @@ -67,7 +93,6 @@ describe('RegistrationBlocksDataComponent', () => { }); it('should not show required error on overview page', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); fixture.componentRef.setInput('questions', MOCK_QUESTIONS); fixture.componentRef.setInput('reviewData', {}); fixture.componentRef.setInput('isOverviewPage', true); @@ -78,7 +103,6 @@ describe('RegistrationBlocksDataComponent', () => { }); it('should show updated tag when field is updated and not original revision', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); fixture.componentRef.setInput('questions', MOCK_QUESTIONS); fixture.componentRef.setInput('reviewData', MOCK_REVIEW); fixture.componentRef.setInput('updatedFields', ['question1']); @@ -90,7 +114,6 @@ describe('RegistrationBlocksDataComponent', () => { }); it('should not show updated tag on original revision', () => { - const fixture = TestBed.createComponent(RegistrationBlocksDataComponent); fixture.componentRef.setInput('questions', MOCK_QUESTIONS); fixture.componentRef.setInput('reviewData', MOCK_REVIEW); fixture.componentRef.setInput('updatedFields', ['question1']); @@ -100,4 +123,24 @@ describe('RegistrationBlocksDataComponent', () => { const tags = fixture.nativeElement.querySelectorAll('p-tag'); expect(tags.length).toBe(0); }); + + it('should return empty string when file url is missing', () => { + expect(component.getFileUrl()).toBe(''); + }); + + it('should return original file url when view_only param is absent', () => { + const htmlUrl = 'https://osf.io/abc12/'; + + expect(component.getFileUrl(htmlUrl)).toBe(htmlUrl); + }); + + it('should append view_only query param to file url', () => { + viewOnlyHelper.getViewOnlyParamFromUrl.mockReturnValue('token'); + + const htmlUrl = 'https://osf.io/abc12/'; + const result = component.getFileUrl(htmlUrl); + + expect(viewOnlyHelper.getViewOnlyParamFromUrl).toHaveBeenCalledWith('/'); + expect(result).toBe('https://osf.io/abc12/?view_only=token'); + }); }); diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts index d1c49eb2f..908471dbc 100644 --- a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts +++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts @@ -3,12 +3,14 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Message } from 'primeng/message'; import { Tag } from 'primeng/tag'; -import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'; +import { Router } from '@angular/router'; import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants/input-validation-messages.const'; import { FieldType } from '@osf/shared/enums/field-type.enum'; import { Question } from '@osf/shared/models/registration/page-schema.model'; import { FixSpecialCharPipe } from '@osf/shared/pipes/fix-special-char.pipe'; +import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service'; @Component({ selector: 'osf-registration-blocks-data', @@ -18,6 +20,9 @@ import { FixSpecialCharPipe } from '@osf/shared/pipes/fix-special-char.pipe'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class RegistrationBlocksDataComponent { + private readonly router = inject(Router); + private readonly viewOnlyService = inject(ViewOnlyLinkHelperService); + questions = input(); // eslint-disable-next-line @typescript-eslint/no-explicit-any reviewData = input>({}); @@ -34,4 +39,20 @@ export class RegistrationBlocksDataComponent { readonly FieldType = FieldType; readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; + + getFileUrl(htmlUrl?: string): string { + if (!htmlUrl) { + return ''; + } + + const viewOnly = this.viewOnlyService.getViewOnlyParamFromUrl(this.router.url); + + if (!viewOnly) { + return htmlUrl; + } + + const url = new URL(htmlUrl); + url.searchParams.set('view_only', viewOnly); + return url.toString(); + } }