Skip to content

Commit 2ae4c46

Browse files
authored
[ENG-10364] Part 1: Add unit tests for preprints (#895)
- Ticket: [ENG-10364] - Feature flag: n/a ## Summary of Changes 1. Update unit tests for preprints pages.
1 parent 38a7e46 commit 2ae4c46

58 files changed

Lines changed: 1687 additions & 1618 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/ngxs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Typical NGXS-related files are organized as follows:
4646
src/app/shared/stores/
4747
└── addons/
4848
├── addons.actions.ts # All action definitions
49-
├── addons.models.ts # Interfaces & data models
49+
├── addons.model.ts # Interfaces & data model
5050
├── addons.state.ts # State implementation
5151
├── addons.selectors.ts # Reusable selectors
5252
```

jest.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module.exports = {
33
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
44
globalSetup: '<rootDir>/jest.global-setup.ts',
55
collectCoverage: false,
6+
clearMocks: true,
7+
restoreMocks: true,
68
coverageReporters: ['json-summary', 'lcov', 'clover'],
79
moduleNameMapper: {
810
'^@osf/(.*)$': '<rootDir>/src/app/$1',
@@ -36,12 +38,10 @@ module.exports = {
3638
'!src/app/app.config.ts',
3739
'!src/app/app.routes.ts',
3840
'!src/app/**/*.routes.{ts,js}',
39-
'!src/app/**/**/*.routes.{ts,js}',
4041
'!src/app/**/*.route.{ts,js}',
4142
'!src/app/**/mappers/**',
4243
'!src/app/shared/mappers/**',
43-
'!src/app/**/*.models.{ts.js}',
44-
'!src/app/**/*.model.{ts.js}',
44+
'!src/app/**/*.model.{ts,js}',
4545
'!src/app/**/models/*.{ts,js}',
4646
'!src/app/shared/models/**',
4747
'!src/app/**/*.enum.{ts,js}',

src/app/features/preprints/mappers/preprint-providers.mapper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ export class PreprintProvidersMapper {
5757
): PreprintProviderShortInfo[] {
5858
return response.map((item) => ({
5959
id: item.id,
60-
descriptionHtml: item.attributes.description,
61-
name: item.attributes.name,
60+
name: replaceBadEncodedChars(item.attributes.name),
61+
descriptionHtml: replaceBadEncodedChars(item.attributes.description),
6262
whiteWideImageUrl: item.attributes.assets?.wide_white,
6363
squareColorNoTransparentImageUrl: item.attributes.assets?.square_color_no_transparent,
6464
}));

src/app/features/preprints/mappers/preprints.mapper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class PreprintsMapper {
4747
datePublished: response.attributes.date_published,
4848
dateLastTransitioned: response.attributes.date_last_transitioned,
4949
title: replaceBadEncodedChars(response.attributes.title),
50-
description: response.attributes.description,
50+
description: replaceBadEncodedChars(response.attributes.description),
5151
reviewsState: response.attributes.reviews_state,
5252
preprintDoiCreated: response.attributes.preprint_doi_created,
5353
currentUserPermissions: response.attributes.current_user_permissions,

src/app/features/preprints/pages/create-new-version/create-new-version.component.html

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
} @else {
77
<img
88
class="preprint-provider-hero-logo"
9-
alt="Provider Logo"
109
height="40"
1110
[src]="preprintProvider()!.brand.heroLogoImageUrl"
11+
[alt]="'preprints.selectService.providerLogoImageAlt' | translate"
1212
/>
1313
<h1 class="preprint-provider-name">
1414
{{ 'preprints.createNewVersionTitle' | translate }}
@@ -30,17 +30,17 @@ <h1 class="preprint-provider-name">
3030
}
3131
</section>
3232

33-
<section class="flex-1 bg-white px-3 py-4 md:py-4 md:px-4">
34-
@switch (currentStep().value) {
35-
@case (PreprintSteps.File) {
36-
<osf-file-step
37-
[provider]="preprintProvider()"
38-
(nextClicked)="moveToNextStep()"
39-
(backClicked)="moveToPreviousStep()"
40-
/>
41-
}
42-
@case (PreprintSteps.Review) {
43-
<osf-review-step [provider]="preprintProvider()" />
33+
@let provider = preprintProvider();
34+
35+
@if (provider) {
36+
<section class="flex-1 bg-white px-3 py-4 md:py-4 md:px-4">
37+
@switch (currentStep().value) {
38+
@case (PreprintSteps.File) {
39+
<osf-file-step [provider]="provider" (nextClicked)="moveToNextStep()" (backClicked)="navigateBack()" />
40+
}
41+
@case (PreprintSteps.Review) {
42+
<osf-review-step [provider]="preprintProvider()" />
43+
}
4444
}
45-
}
46-
</section>
45+
</section>
46+
}
Lines changed: 107 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Store } from '@ngxs/store';
2+
13
import { MockComponents, MockProvider } from 'ng-mocks';
24

35
import { of } from 'rxjs';
@@ -7,7 +9,6 @@ import { ActivatedRoute, Router } from '@angular/router';
79

810
import { StepperComponent } from '@osf/shared/components/stepper/stepper.component';
911
import { IS_WEB } from '@osf/shared/helpers/breakpoints.tokens';
10-
import { StepOption } from '@osf/shared/models/step-option.model';
1112
import { BrandService } from '@osf/shared/services/brand.service';
1213
import { BrowserTabService } from '@osf/shared/services/browser-tab.service';
1314
import { HeaderStyleService } from '@osf/shared/services/header-style.service';
@@ -16,181 +17,180 @@ import { FileStepComponent, ReviewStepComponent } from '../../components';
1617
import { createNewVersionStepsConst } from '../../constants';
1718
import { PreprintSteps } from '../../enums';
1819
import { PreprintProviderDetails } from '../../models';
19-
import { PreprintProvidersSelectors } from '../../store/preprint-providers';
20-
import { PreprintStepperSelectors } from '../../store/preprint-stepper';
20+
import { GetPreprintProviderById, PreprintProvidersSelectors } from '../../store/preprint-providers';
21+
import { FetchPreprintById, PreprintStepperSelectors, ResetPreprintStepperState } from '../../store/preprint-stepper';
2122

2223
import { CreateNewVersionComponent } from './create-new-version.component';
2324

24-
import { PREPRINT_MOCK } from '@testing/mocks/preprint.mock';
2525
import { PREPRINT_PROVIDER_DETAILS_MOCK } from '@testing/mocks/preprint-provider-details';
26-
import { TranslationServiceMock } from '@testing/mocks/translation.service.mock';
27-
import { OSFTestingModule } from '@testing/osf.testing.module';
26+
import { provideOSFCore } from '@testing/osf.testing.provider';
27+
import { BrandServiceMock, BrandServiceMockType } from '@testing/providers/brand-service.mock';
28+
import { BrowserTabServiceMock, BrowserTabServiceMockType } from '@testing/providers/browser-tab-service.mock';
29+
import { HeaderStyleServiceMock, HeaderStyleServiceMockType } from '@testing/providers/header-style-service.mock';
2830
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
29-
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
30-
import { provideMockStore } from '@testing/providers/store-provider.mock';
31+
import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
32+
import { mergeSignalOverrides, provideMockStore, SignalOverride } from '@testing/providers/store-provider.mock';
3133

3234
describe('CreateNewVersionComponent', () => {
3335
let component: CreateNewVersionComponent;
3436
let fixture: ComponentFixture<CreateNewVersionComponent>;
35-
let routerMock: ReturnType<RouterMockBuilder['build']>;
36-
let routeMock: ReturnType<ActivatedRouteMockBuilder['build']>;
37+
let store: Store;
38+
let routerMock: RouterMockType;
39+
let brandServiceMock: BrandServiceMockType;
40+
let headerStyleMock: HeaderStyleServiceMockType;
41+
let browserTabMock: BrowserTabServiceMockType;
3742

3843
const mockProvider: PreprintProviderDetails = PREPRINT_PROVIDER_DETAILS_MOCK;
39-
const mockPreprint = PREPRINT_MOCK;
4044
const mockProviderId = 'osf';
4145
const mockPreprintId = 'test_preprint_123';
4246

43-
beforeEach(async () => {
47+
const defaultSignals: SignalOverride[] = [
48+
{ selector: PreprintProvidersSelectors.getPreprintProviderDetails(mockProviderId), value: mockProvider },
49+
{ selector: PreprintProvidersSelectors.isPreprintProviderDetailsLoading, value: false },
50+
{ selector: PreprintStepperSelectors.hasBeenSubmitted, value: false },
51+
];
52+
53+
function setup(overrides?: { selectorOverrides?: SignalOverride[] }) {
54+
const signals = mergeSignalOverrides(defaultSignals, overrides?.selectorOverrides);
55+
4456
routerMock = RouterMockBuilder.create().withNavigate(jest.fn().mockResolvedValue(true)).build();
45-
routeMock = ActivatedRouteMockBuilder.create()
57+
const routeMock = ActivatedRouteMockBuilder.create()
4658
.withParams({ providerId: mockProviderId, preprintId: mockPreprintId })
4759
.withQueryParams({})
4860
.build();
4961

50-
await TestBed.configureTestingModule({
51-
imports: [
52-
CreateNewVersionComponent,
53-
OSFTestingModule,
54-
...MockComponents(StepperComponent, FileStepComponent, ReviewStepComponent),
55-
],
62+
brandServiceMock = BrandServiceMock.simple();
63+
headerStyleMock = HeaderStyleServiceMock.simple();
64+
browserTabMock = BrowserTabServiceMock.simple();
65+
66+
TestBed.configureTestingModule({
67+
imports: [CreateNewVersionComponent, ...MockComponents(StepperComponent, FileStepComponent, ReviewStepComponent)],
5668
providers: [
57-
TranslationServiceMock,
58-
MockProvider(BrandService),
59-
MockProvider(BrowserTabService),
60-
MockProvider(HeaderStyleService),
61-
MockProvider(Router, routerMock),
69+
provideOSFCore(),
6270
MockProvider(ActivatedRoute, routeMock),
71+
MockProvider(Router, routerMock),
72+
MockProvider(BrandService, brandServiceMock),
73+
MockProvider(HeaderStyleService, headerStyleMock),
74+
MockProvider(BrowserTabService, browserTabMock),
6375
MockProvider(IS_WEB, of(true)),
64-
provideMockStore({
65-
signals: [
66-
{
67-
selector: PreprintStepperSelectors.getPreprint,
68-
value: mockPreprint,
69-
},
70-
{
71-
selector: PreprintProvidersSelectors.getPreprintProviderDetails(mockProviderId),
72-
value: mockProvider,
73-
},
74-
{
75-
selector: PreprintProvidersSelectors.isPreprintProviderDetailsLoading,
76-
value: false,
77-
},
78-
{
79-
selector: PreprintStepperSelectors.hasBeenSubmitted,
80-
value: false,
81-
},
82-
],
83-
}),
76+
provideMockStore({ signals }),
8477
],
85-
}).compileComponents();
78+
});
8679

80+
store = TestBed.inject(Store);
8781
fixture = TestBed.createComponent(CreateNewVersionComponent);
8882
component = fixture.componentInstance;
8983
fixture.detectChanges();
90-
});
91-
92-
afterEach(() => {
93-
if (fixture) {
94-
fixture.destroy();
95-
}
96-
jest.restoreAllMocks();
97-
});
84+
}
9885

9986
it('should initialize with correct default values', () => {
87+
setup();
88+
10089
expect(component.PreprintSteps).toBe(PreprintSteps);
10190
expect(component.newVersionSteps).toBe(createNewVersionStepsConst);
10291
expect(component.currentStep()).toEqual(createNewVersionStepsConst[0]);
10392
expect(component.classes).toBe('flex-1 flex flex-column w-full');
10493
});
10594

106-
it('should return preprint from store', () => {
107-
const preprint = component.preprint();
108-
expect(preprint).toBe(mockPreprint);
109-
});
95+
it('should dispatch initial actions on creation', () => {
96+
setup();
11097

111-
it('should return preprint provider from store', () => {
112-
const provider = component.preprintProvider();
113-
expect(provider).toBe(mockProvider);
98+
expect(store.dispatch).toHaveBeenCalledWith(new GetPreprintProviderById(mockProviderId));
99+
expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintById(mockPreprintId));
114100
});
115101

116-
it('should return loading state from store', () => {
117-
const loading = component.isPreprintProviderLoading();
118-
expect(loading).toBe(false);
119-
});
102+
it('should apply branding when provider is available', () => {
103+
setup();
120104

121-
it('should return submission state from store', () => {
122-
const submitted = component.hasBeenSubmitted();
123-
expect(submitted).toBe(false);
105+
expect(brandServiceMock.applyBranding).toHaveBeenCalledWith(mockProvider.brand);
106+
expect(headerStyleMock.applyHeaderStyles).toHaveBeenCalledWith(
107+
mockProvider.brand.primaryColor,
108+
mockProvider.brand.secondaryColor,
109+
mockProvider.brand.heroBackgroundImageUrl
110+
);
111+
expect(browserTabMock.updateTabStyles).toHaveBeenCalledWith(mockProvider.faviconUrl, mockProvider.name);
124112
});
125113

126-
it('should return web environment state', () => {
127-
const isWeb = component.isWeb();
128-
expect(typeof isWeb).toBe('boolean');
129-
});
114+
it('should reset services on destroy', () => {
115+
setup();
130116

131-
it('should initialize with first step as current step', () => {
132-
expect(component.currentStep()).toEqual(createNewVersionStepsConst[0]);
117+
component.ngOnDestroy();
118+
119+
expect(headerStyleMock.resetToDefaults).toHaveBeenCalled();
120+
expect(brandServiceMock.resetBranding).toHaveBeenCalled();
121+
expect(browserTabMock.resetToDefaults).toHaveBeenCalled();
122+
expect(store.dispatch).toHaveBeenCalledWith(new ResetPreprintStepperState());
133123
});
134124

135-
it('should handle step change when moving to previous step', () => {
136-
const previousStep = createNewVersionStepsConst[0];
125+
it('should prevent beforeunload when not submitted', () => {
126+
setup();
127+
const event = { preventDefault: jest.fn() } as unknown as BeforeUnloadEvent;
137128

138-
component.stepChange(previousStep);
129+
component.onBeforeUnload(event);
139130

140-
expect(component.currentStep()).toEqual(previousStep);
131+
expect(event.preventDefault).toHaveBeenCalled();
141132
});
142133

143-
it('should not change step when moving to next step', () => {
144-
const currentStep = component.currentStep();
145-
const nextStep = createNewVersionStepsConst[1];
134+
it('should not prevent beforeunload when submitted', () => {
135+
setup({ selectorOverrides: [{ selector: PreprintStepperSelectors.hasBeenSubmitted, value: true }] });
136+
const event = { preventDefault: jest.fn() } as unknown as BeforeUnloadEvent;
146137

147-
component.stepChange(nextStep);
138+
component.onBeforeUnload(event);
148139

149-
expect(component.currentStep()).toEqual(currentStep);
140+
expect(event.preventDefault).not.toHaveBeenCalled();
150141
});
151142

152-
it('should move to next step', () => {
153-
const currentIndex = component.currentStep()?.index ?? 0;
154-
const nextStep = createNewVersionStepsConst[currentIndex + 1];
155-
156-
component.moveToNextStep();
143+
it('should prevent deactivation when not submitted', () => {
144+
setup();
157145

158-
expect(component.currentStep()).toEqual(nextStep);
146+
expect(component.canDeactivate()).toBe(false);
159147
});
160148

161-
it('should navigate to previous step (preprint page)', () => {
162-
component.moveToPreviousStep();
149+
it('should allow deactivation when submitted', () => {
150+
setup({ selectorOverrides: [{ selector: PreprintStepperSelectors.hasBeenSubmitted, value: true }] });
163151

164-
expect(routerMock.navigate).toHaveBeenCalledWith([mockPreprintId.split('_')[0]]);
152+
expect(component.canDeactivate()).toBe(true);
165153
});
166154

167-
it('should return canDeactivate state', () => {
168-
const canDeactivate = component.canDeactivate();
169-
expect(canDeactivate).toBe(false);
155+
it('should ignore stepping forward via stepper', () => {
156+
setup();
157+
158+
component.stepChange(createNewVersionStepsConst[1]);
159+
160+
expect(component.currentStep()).toEqual(createNewVersionStepsConst[0]);
170161
});
171162

172-
it('should handle beforeunload event', () => {
173-
const event = {
174-
preventDefault: jest.fn(),
175-
} as unknown as BeforeUnloadEvent;
163+
it('should allow stepping back via stepper', () => {
164+
setup();
165+
component.moveToNextStep();
176166

177-
const result = component.onBeforeUnload(event);
167+
component.stepChange(createNewVersionStepsConst[0]);
178168

179-
expect(event.preventDefault).toHaveBeenCalled();
180-
expect(result).toBe(false);
169+
expect(component.currentStep()).toEqual(createNewVersionStepsConst[0]);
181170
});
182171

183-
it('should handle step navigation correctly', () => {
172+
it('should move to next step', () => {
173+
setup();
174+
184175
component.moveToNextStep();
176+
185177
expect(component.currentStep()).toEqual(createNewVersionStepsConst[1]);
178+
});
186179

187-
component.stepChange(createNewVersionStepsConst[0]);
188-
expect(component.currentStep()).toEqual(createNewVersionStepsConst[0]);
180+
it('should not move past the last step', () => {
181+
setup();
182+
component.currentStep.set(createNewVersionStepsConst[createNewVersionStepsConst.length - 1]);
183+
184+
component.moveToNextStep();
185+
186+
expect(component.currentStep()).toEqual(createNewVersionStepsConst[createNewVersionStepsConst.length - 1]);
189187
});
190188

191-
it('should handle edge case when moving to next step with undefined current step', () => {
192-
component.currentStep.set({} as StepOption);
189+
it('should navigate back to preprint page', () => {
190+
setup();
193191

194-
expect(() => component.moveToNextStep()).not.toThrow();
192+
component.navigateBack();
193+
194+
expect(routerMock.navigate).toHaveBeenCalledWith([mockPreprintId.split('_')[0]]);
195195
});
196196
});

0 commit comments

Comments
 (0)