Skip to content

Commit b6938c7

Browse files
committed
chore(datacite-tracker): added tests to registry component
1 parent 224879b commit b6938c7

8 files changed

Lines changed: 212 additions & 20 deletions

File tree

jest.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ module.exports = {
7070
testPathIgnorePatterns: [
7171
'<rootDir>/src/app/app.config.ts',
7272
'<rootDir>/src/app/app.routes.ts',
73-
'<rootDir>/src/app/features/registry/',
7473
'<rootDir>/src/app/features/project/addons/components/configure-configure-addon/',
7574
'<rootDir>/src/app/features/project/addons/components/connect-configured-addon/',
7675
'<rootDir>/src/app/features/project/addons/components/disconnect-addon-modal/',

src/app/features/files/pages/file-detail/file-detail.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
></p-button>
2626

2727
@if (file()?.links?.download) {
28-
<p-button severity="secondary" (click)="downloadFile(file()?.links?.download!)">
28+
<p-button severity="secondary" (click)="downloadFile(file())">
2929
<i class="fas fa-download p-1"></i>
3030
</p-button>
3131
}

src/app/features/files/pages/file-detail/file-detail.component.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import { Tab, TabList, Tabs } from 'primeng/tabs';
99
import { switchMap } from 'rxjs';
1010

1111
import { ChangeDetectionStrategy, Component, DestroyRef, HostBinding, inject } from '@angular/core';
12-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
12+
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
1313
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
1414
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
1515

16+
import { ProjectOverviewSelectors } from '@osf/features/project/overview/store';
1617
import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components';
1718
import { OsfFile } from '@shared/models';
1819
import { CustomConfirmationService, ToastService } from '@shared/services';
20+
import { DataciteService } from '@shared/services/datacite/datacite.service';
1921

2022
import {
2123
FileKeywordsComponent,
@@ -66,6 +68,7 @@ export class FileDetailComponent {
6668
readonly sanitizer = inject(DomSanitizer);
6769
readonly toastService = inject(ToastService);
6870
readonly customConfirmationService = inject(CustomConfirmationService);
71+
readonly dataciteService = inject(DataciteService);
6972

7073
private readonly actions = createDispatchMap({
7174
getFile: GetFile,
@@ -77,6 +80,7 @@ export class FileDetailComponent {
7780
});
7881

7982
file = select(FilesSelectors.getOpenedFile);
83+
project = select(ProjectOverviewSelectors.getProject);
8084
isFileLoading = select(FilesSelectors.isOpenedFileLoading);
8185
safeLink: SafeResourceUrl | null = null;
8286
resourceId = '';
@@ -149,8 +153,13 @@ export class FileDetailComponent {
149153
});
150154
}
151155

152-
downloadFile(link: string): void {
153-
window.open(link)?.focus();
156+
downloadFile(file: OsfFile | null): void {
157+
const project = this.project();
158+
console.log(project);
159+
if (project && this.project()?.id === file?.target.id) {
160+
this.dataciteService.watchIdentifiable(toObservable(this.project));
161+
}
162+
window.open(file?.links?.download)?.focus();
154163
}
155164

156165
copyToClipboard(embedHtml: string): void {

src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { MockComponents, MockPipe, MockProvider } from 'ng-mocks';
55

66
import { of } from 'rxjs';
77

8+
import { DatePipe } from '@angular/common';
89
import { ComponentFixture, TestBed } from '@angular/core/testing';
910
import { ActivatedRoute, Router } from '@angular/router';
1011

@@ -15,18 +16,25 @@ import { ShareAndDownloadComponent } from '@osf/features/preprints/components/pr
1516
import { PreprintSelectors } from '@osf/features/preprints/store/preprint';
1617
import { PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers';
1718
import { MOCK_PROVIDER, MOCK_STORE, TranslateServiceMock } from '@shared/mocks';
19+
import { MetaTagsService } from '@shared/services';
20+
import { DataciteService } from '@shared/services/datacite/datacite.service';
1821

1922
import { PreprintDetailsComponent } from './preprint-details.component';
2023

21-
describe.skip('PreprintDetailsComponent', () => {
24+
describe('PreprintDetailsComponent', () => {
2225
let component: PreprintDetailsComponent;
2326
let fixture: ComponentFixture<PreprintDetailsComponent>;
2427

28+
let dataciteService: jest.Mocked<DataciteService>;
2529
const mockRoute: Partial<ActivatedRoute> = {
2630
params: of({ providerId: 'osf', preprintId: 'p1' }),
31+
queryParams: of({ providerId: 'osf', preprintId: 'p1' }),
2732
};
2833

2934
beforeEach(async () => {
35+
dataciteService = {
36+
logView: jest.fn().mockReturnValue(of(void 0)),
37+
} as unknown as jest.Mocked<DataciteService>;
3038
(MOCK_STORE.selectSignal as jest.Mock).mockImplementation((selector) => {
3139
switch (selector) {
3240
case PreprintProvidersSelectors.getPreprintProviderDetails('osf'):
@@ -54,9 +62,12 @@ describe.skip('PreprintDetailsComponent', () => {
5462
),
5563
],
5664
providers: [
65+
{ provide: DataciteService, useValue: dataciteService },
5766
MockProvider(Store, MOCK_STORE),
5867
MockProvider(Router),
5968
MockProvider(ActivatedRoute, mockRoute),
69+
MockProvider(MetaTagsService),
70+
MockProvider(DatePipe),
6071
TranslateServiceMock,
6172
],
6273
}).compileComponents();
Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,117 @@
1-
import { MockComponent } from 'ng-mocks';
1+
import { Store } from '@ngxs/store';
2+
3+
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
4+
import { MockComponent, MockModule, MockPipe } from 'ng-mocks';
5+
6+
import { ButtonModule } from 'primeng/button';
7+
import { DialogService } from 'primeng/dynamicdialog';
8+
import { Message } from 'primeng/message';
9+
import { TagModule } from 'primeng/tag';
10+
11+
import { EMPTY, of } from 'rxjs';
212

313
import { ComponentFixture, TestBed } from '@angular/core/testing';
14+
import { FormsModule } from '@angular/forms';
15+
import { ActivatedRoute, Router } from '@angular/router';
16+
import { RouterTestingModule } from '@angular/router/testing';
417

5-
import { SubHeaderComponent } from '@osf/shared/components';
18+
import { ProjectOverviewSelectors } from '@osf/features/project/overview/store';
19+
import { LoadingSpinnerComponent, ResourceMetadataComponent, SubHeaderComponent } from '@shared/components';
20+
import { IS_XSMALL } from '@shared/helpers';
21+
import { ToastService } from '@shared/services';
22+
import { DataciteService } from '@shared/services/datacite/datacite.service';
623

24+
import {
25+
LinkedResourcesComponent,
26+
OverviewComponentsComponent,
27+
OverviewToolbarComponent,
28+
OverviewWikiComponent,
29+
RecentActivityComponent,
30+
} from './components';
731
import { ProjectOverviewComponent } from './project-overview.component';
832

33+
// Mocking the child components used in the template
34+
const MockedSubHeaderComponent = MockComponent(SubHeaderComponent);
35+
const MockedLoadingSpinnerComponent = MockComponent(LoadingSpinnerComponent);
36+
const MockedOverviewWikiComponent = MockComponent(OverviewWikiComponent);
37+
const MockedOverviewComponentsComponent = MockComponent(OverviewComponentsComponent);
38+
const MockedLinkedResourcesComponent = MockComponent(LinkedResourcesComponent);
39+
const MockedRecentActivityComponent = MockComponent(RecentActivityComponent);
40+
const MockedOverviewToolbarComponent = MockComponent(OverviewToolbarComponent);
41+
const MockedResourceMetadataComponent = MockComponent(ResourceMetadataComponent);
42+
const MockedMessageComponent = MockComponent(Message);
43+
944
describe('ProjectOverviewComponent', () => {
1045
let component: ProjectOverviewComponent;
1146
let fixture: ComponentFixture<ProjectOverviewComponent>;
47+
let store: Store;
48+
let dataciteService: jest.Mocked<DataciteService>;
1249

1350
beforeEach(async () => {
51+
// Corrected mock for DataciteService to include all used methods
52+
dataciteService = {
53+
watchIdentifiable: jest.fn().mockReturnValue(of(void 0)),
54+
} as unknown as jest.Mocked<DataciteService>;
55+
1456
await TestBed.configureTestingModule({
15-
imports: [ProjectOverviewComponent, MockComponent(SubHeaderComponent)],
57+
// Import the component-under-test and mocks for all its template dependencies
58+
imports: [
59+
ProjectOverviewComponent,
60+
MockedSubHeaderComponent,
61+
MockedLoadingSpinnerComponent,
62+
MockedOverviewWikiComponent,
63+
MockedOverviewComponentsComponent,
64+
MockedLinkedResourcesComponent,
65+
MockedRecentActivityComponent,
66+
MockedOverviewToolbarComponent,
67+
MockedResourceMetadataComponent,
68+
MockedMessageComponent,
69+
MockPipe(TranslatePipe, (key: string) => key),
70+
MockModule(ButtonModule),
71+
MockModule(TagModule),
72+
MockModule(FormsModule),
73+
RouterTestingModule,
74+
],
75+
providers: [
76+
{
77+
provide: Store,
78+
useValue: {
79+
select: jest.fn(() => EMPTY),
80+
dispatch: jest.fn(() => of({})),
81+
},
82+
},
83+
{ provide: DataciteService, useValue: dataciteService },
84+
{
85+
provide: ActivatedRoute,
86+
useValue: { snapshot: { params: { id: 'test-id' }, queryParams: {} }, parent: null },
87+
},
88+
{ provide: Router, useValue: { navigate: jest.fn(), url: '' } },
89+
{ provide: ToastService, useValue: { showSuccess: jest.fn() } },
90+
{ provide: DialogService, useValue: { open: jest.fn().mockReturnValue({ onClose: of(null) }) } },
91+
{ provide: TranslateService, useValue: { instant: (key: string) => key } },
92+
{ provide: IS_XSMALL, useValue: of(false) },
93+
],
1694
}).compileComponents();
1795

1896
fixture = TestBed.createComponent(ProjectOverviewComponent);
1997
component = fixture.componentInstance;
20-
fixture.detectChanges();
98+
store = TestBed.inject(Store);
2199
});
22100

23101
it('should create', () => {
102+
// Setup the store selector for the project
103+
jest.spyOn(store, 'select').mockImplementation((selector: any) => {
104+
if (selector === ProjectOverviewSelectors.getProject) {
105+
return of({ id: 'test-id', identifiers: [{ category: 'doi', value: '123' }] });
106+
}
107+
return EMPTY;
108+
});
109+
110+
fixture.detectChanges();
111+
112+
// Verify that the component is created successfully
24113
expect(component).toBeTruthy();
114+
// Verify that the method on the mocked service was called
115+
expect(dataciteService.watchIdentifiable).toHaveBeenCalled();
25116
});
26117
});
Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,94 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
1+
import { Store } from '@ngxs/store';
2+
3+
import { of } from 'rxjs';
4+
5+
import { DatePipe } from '@angular/common';
6+
import { signal } from '@angular/core';
7+
import { TestBed } from '@angular/core/testing';
8+
9+
import { ProjectIdentifiers } from '@osf/features/project/overview/models';
10+
import { RegistryOverviewSelectors } from '@osf/features/registry/store/registry-overview';
11+
import { MetaTagsService } from '@osf/shared/services';
12+
import { DataciteService } from '@shared/services/datacite/datacite.service';
213

314
import { RegistryComponent } from './registry.component';
415

516
describe('RegistryComponent', () => {
6-
let component: RegistryComponent;
7-
let fixture: ComponentFixture<RegistryComponent>;
17+
let fixture: any;
18+
let dataciteService: jest.Mocked<DataciteService>;
19+
20+
const registrySignal = signal<any | null>(null);
821

922
beforeEach(async () => {
23+
dataciteService = {
24+
logView: jest.fn().mockReturnValue(of(void 0)),
25+
} as unknown as jest.Mocked<DataciteService>;
26+
27+
const mockStore = {
28+
selectSignal: jest.fn((selector: any) => {
29+
if (selector === RegistryOverviewSelectors.getRegistry) {
30+
return registrySignal; // return a signal, not an observable
31+
}
32+
return signal(null);
33+
}),
34+
};
35+
1036
await TestBed.configureTestingModule({
11-
imports: [RegistryComponent],
37+
imports: [RegistryComponent], // standalone component
38+
providers: [
39+
{ provide: Store, useValue: mockStore },
40+
DatePipe,
41+
{ provide: DataciteService, useValue: dataciteService },
42+
{
43+
provide: MetaTagsService,
44+
useValue: { updateMetaTagsForRoute: jest.fn() },
45+
},
46+
],
1247
}).compileComponents();
1348

1449
fixture = TestBed.createComponent(RegistryComponent);
15-
component = fixture.componentInstance;
16-
fixture.detectChanges();
50+
TestBed.inject(MetaTagsService);
1751
});
1852

19-
it('should create', () => {
20-
expect(component).toBeTruthy();
53+
it('reacts to sequence of state changes', () => {
54+
registrySignal.set(null);
55+
fixture.detectChanges();
56+
expect(dataciteService.logView).toHaveBeenCalledTimes(0);
57+
58+
registrySignal.set(getRegistry([]));
59+
60+
fixture.detectChanges();
61+
expect(dataciteService.logView).toHaveBeenCalledTimes(0);
62+
63+
registrySignal.set(getRegistry([{ category: 'dio', value: '123', id: '', type: 'identifier' }]));
64+
fixture.detectChanges();
65+
expect(dataciteService.logView).toHaveBeenCalledTimes(0);
66+
67+
registrySignal.set(getRegistry([{ category: 'doi', value: '123', id: '', type: 'identifier' }]));
68+
69+
fixture.detectChanges();
70+
expect(dataciteService.logView).toHaveBeenCalled();
71+
72+
registrySignal.set(getRegistry([{ category: 'doi', value: '456', id: '', type: 'identifier' }]));
73+
fixture.detectChanges();
74+
expect(dataciteService.logView).toHaveBeenLastCalledWith('123');
2175
});
2276
});
77+
78+
function getRegistry(identifiers: ProjectIdentifiers[]) {
79+
return {
80+
id: 'r1',
81+
title: 'Mock Registry',
82+
description: 'Test description',
83+
dateRegistered: new Date('2023-01-01'),
84+
dateModified: new Date('2023-02-01'),
85+
doi: '10.1000/mockdoi',
86+
tags: ['angular', 'jest'],
87+
license: { name: 'MIT' },
88+
contributors: [
89+
{ givenName: 'Alice', familyName: 'Smith' },
90+
{ givenName: 'Bob', familyName: 'Brown' },
91+
],
92+
identifiers: identifiers,
93+
};
94+
}

src/app/shared/components/datacite-tracker/datacite-tracker.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export abstract class DataciteTrackerComponent {
2828
return this.trackable.pipe(
2929
filter((item) => item != null),
3030
map((item) => item?.identifiers?.find((identifier) => identifier.category == 'doi')?.value ?? null),
31-
take(1),
3231
filter((doi): doi is string => !!doi),
32+
take(1),
3333
switchMap((doi) => this.dataciteService.logView(doi))
3434
);
3535
}

src/app/shared/services/datacite/datacite.service.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { EMPTY, map, Observable } from 'rxjs';
1+
import { EMPTY, filter, map, Observable, switchMap, take } from 'rxjs';
22

33
import { HttpClient } from '@angular/common/http';
44
import { inject, Injectable } from '@angular/core';
55

66
import { ENVIRONMENT } from '@core/constants/environment.token';
7+
import { ProjectIdentifiers } from '@osf/features/project/overview/models';
78
import { DataciteEvent } from '@shared/models/datacite/datacite-event.enum';
89

910
@Injectable({
@@ -13,6 +14,15 @@ export class DataciteService {
1314
#http: HttpClient = inject(HttpClient);
1415
#environment = inject(ENVIRONMENT);
1516

17+
watchIdentifiable(trackable: Observable<{ identifiers?: ProjectIdentifiers[] } | null>): Observable<void> {
18+
return trackable.pipe(
19+
filter((item) => item != null),
20+
map((item) => item?.identifiers?.find((identifier) => identifier.category == 'doi')?.value ?? null),
21+
filter((doi): doi is string => !!doi),
22+
take(1),
23+
switchMap((doi) => this.logView(doi))
24+
);
25+
}
1626
/**
1727
* Logs a "view" event for a given DOI to the Datacite tracker.
1828
* If the DOI is null/empty or the tracker repository ID is not configured,

0 commit comments

Comments
 (0)