Skip to content

Commit b425711

Browse files
committed
Merge remote-tracking branch 'upstream/feature/pbs-25-24' into test/new-unit-tests
2 parents a494527 + 1f6bece commit b425711

27 files changed

+765
-185
lines changed

angular.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
"cedar-embeddable-editor",
3030
"cedar-artifact-viewer",
3131
"markdown-it-video",
32+
"markdown-it-anchor",
33+
"markdown-it-toc-done-right",
3234
"ace-builds/src-noconflict/ext-language_tools",
3335
"@traptitech/markdown-it-katex",
3436
"@citation-js/core",

jest.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ module.exports = {
2424
},
2525
],
2626
},
27-
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$|@ngxs|@angular|@ngrx|parse5|entities|chart.js)'],
27+
transformIgnorePatterns: [
28+
'node_modules/(?!.*\\.mjs$|@ngxs|@angular|@ngrx|parse5|entities|chart.js|@mdit|@citation-js|@traptitech|@sentry|@primeng|@newrelic)',
29+
],
2830
testEnvironment: 'jsdom',
2931
moduleFileExtensions: ['ts', 'js', 'html', 'json', 'mjs'],
3032
coverageDirectory: 'coverage',

package-lock.json

Lines changed: 41 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@citation-js/core": "^0.7.18",
4545
"@citation-js/plugin-csl": "^0.7.18",
4646
"@fortawesome/fontawesome-free": "^6.7.2",
47+
"@mdit/plugin-img-size": "0.22.3",
4748
"@newrelic/browser-agent": "^1.301.0",
4849
"@ngx-translate/core": "^16.0.4",
4950
"@ngx-translate/http-loader": "^16.0.1",
@@ -58,6 +59,8 @@
5859
"chart.js": "^4.4.9",
5960
"diff": "^8.0.2",
6061
"markdown-it": "^14.1.0",
62+
"markdown-it-anchor": "^9.2.0",
63+
"markdown-it-toc-done-right": "^4.2.0",
6164
"markdown-it-video": "^0.6.3",
6265
"ngx-captcha": "^13.0.0",
6366
"ngx-cookie-service": "^19.1.2",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
declare module 'markdown-it-toc-done-right' {
2+
import { PluginWithOptions } from 'markdown-it';
3+
4+
export interface TocOptions {
5+
placeholder: string;
6+
slugify: (s: string) => string;
7+
uniqueSlugStartIndex: number;
8+
containerClass: string;
9+
containerId: string;
10+
listClass: string;
11+
itemClass: string;
12+
linkClass: string;
13+
level: number | number[];
14+
listType: 'ol' | 'ul';
15+
format: (s: string) => string;
16+
callback: (tocCode: string, ast: TocAst) => void;
17+
}
18+
19+
export interface TocAst {
20+
l: number;
21+
n: string;
22+
c: TocAst[];
23+
}
24+
25+
const markdownItTocDoneRight: PluginWithOptions<Partial<TocOptions>>;
26+
export default markdownItTocDoneRight;
27+
}

src/app/app.component.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import { Actions, createDispatchMap, ofActionSuccessful, select } from '@ngxs/store';
22

3-
import { filter, take } from 'rxjs';
3+
import { take, timer } from 'rxjs';
44

55
import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, OnInit } from '@angular/core';
66
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7-
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
7+
import {
8+
NavigationCancel,
9+
NavigationEnd,
10+
NavigationError,
11+
NavigationStart,
12+
Router,
13+
RouterOutlet,
14+
} from '@angular/router';
815

916
import { ENVIRONMENT } from '@core/provider/environment.provider';
1017
import { GetCurrentUser } from '@core/store/user';
@@ -14,6 +21,7 @@ import { ConfirmEmailComponent } from './shared/components/confirm-email/confirm
1421
import { FullScreenLoaderComponent } from './shared/components/full-screen-loader/full-screen-loader.component';
1522
import { ToastComponent } from './shared/components/toast/toast.component';
1623
import { CustomDialogService } from './shared/services/custom-dialog.service';
24+
import { LoaderService } from './shared/services/loader.service';
1725

1826
import { GoogleTagManagerService } from 'angular-google-tag-manager';
1927

@@ -32,6 +40,7 @@ export class AppComponent implements OnInit {
3240
private readonly environment = inject(ENVIRONMENT);
3341
private readonly actions$ = inject(Actions);
3442
private readonly actions = createDispatchMap({ getCurrentUser: GetCurrentUser, getEmails: GetEmails });
43+
private readonly loaderService = inject(LoaderService);
3544

3645
unverifiedEmails = select(UserEmailsSelectors.getUnverifiedEmails);
3746

@@ -50,19 +59,26 @@ export class AppComponent implements OnInit {
5059
this.actions.getEmails();
5160
});
5261

53-
if (this.environment.googleTagManagerId) {
54-
this.router.events
55-
.pipe(
56-
filter((event) => event instanceof NavigationEnd),
57-
takeUntilDestroyed(this.destroyRef)
58-
)
59-
.subscribe((event: NavigationEnd) => {
60-
this.googleTagManagerService.pushTag({
61-
event: 'page',
62-
pageName: event.urlAfterRedirects,
63-
});
62+
this.router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
63+
if (event instanceof NavigationStart) {
64+
this.loaderService.show();
65+
} else if (
66+
event instanceof NavigationEnd ||
67+
event instanceof NavigationCancel ||
68+
event instanceof NavigationError
69+
) {
70+
timer(500)
71+
.pipe(takeUntilDestroyed(this.destroyRef))
72+
.subscribe(() => this.loaderService.hide());
73+
}
74+
75+
if (this.environment.googleTagManagerId && event instanceof NavigationEnd) {
76+
this.googleTagManagerService.pushTag({
77+
event: 'page',
78+
pageName: event.urlAfterRedirects,
6479
});
65-
}
80+
}
81+
});
6682
}
6783

6884
private showEmailDialog() {

src/app/features/project/overview/components/component-card/component-card.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ <h2 class="flex align-items-center gap-2">
3535
}
3636
</div>
3737

38-
<div class="flex flex-wrap gap-1">
38+
<div class="flex gap-1">
3939
<p class="font-bold">{{ 'common.labels.contributors' | translate }}:</p>
4040

4141
<osf-contributors-list
Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,104 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
1+
import { Store } from '@ngxs/store';
2+
3+
import { MockProvider } from 'ng-mocks';
4+
5+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
6+
7+
import { of } from 'rxjs';
8+
9+
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
10+
11+
import { ToastService } from '@osf/shared/services/toast.service';
12+
import { DeleteNodeLink, NodeLinksSelectors } from '@osf/shared/stores/node-links';
13+
14+
import { ProjectOverviewSelectors } from '../../store';
215

316
import { DeleteNodeLinkDialogComponent } from './delete-node-link-dialog.component';
417

5-
describe.skip('DeleteNodeLinkDialogComponent', () => {
18+
import { DynamicDialogRefMock } from '@testing/mocks/dynamic-dialog-ref.mock';
19+
import { MOCK_NODE_WITH_ADMIN } from '@testing/mocks/node.mock';
20+
import { ToastServiceMock } from '@testing/mocks/toast.service.mock';
21+
import { OSFTestingModule } from '@testing/osf.testing.module';
22+
import { provideMockStore } from '@testing/providers/store-provider.mock';
23+
24+
describe('DeleteNodeLinkDialogComponent', () => {
625
let component: DeleteNodeLinkDialogComponent;
726
let fixture: ComponentFixture<DeleteNodeLinkDialogComponent>;
27+
let store: jest.Mocked<Store>;
28+
let dialogRef: jest.Mocked<DynamicDialogRef>;
29+
let dialogConfig: jest.Mocked<DynamicDialogConfig>;
30+
let toastService: jest.Mocked<ToastService>;
31+
32+
const mockProject = { ...MOCK_NODE_WITH_ADMIN, id: 'test-project-id' };
33+
const mockCurrentLink = { ...MOCK_NODE_WITH_ADMIN, id: 'linked-resource-id', title: 'Linked Resource' };
834

935
beforeEach(async () => {
36+
dialogConfig = {
37+
data: { currentLink: mockCurrentLink },
38+
} as jest.Mocked<DynamicDialogConfig>;
39+
1040
await TestBed.configureTestingModule({
11-
imports: [DeleteNodeLinkDialogComponent],
41+
imports: [DeleteNodeLinkDialogComponent, OSFTestingModule],
42+
providers: [
43+
DynamicDialogRefMock,
44+
ToastServiceMock,
45+
MockProvider(DynamicDialogConfig, dialogConfig),
46+
provideMockStore({
47+
signals: [
48+
{ selector: ProjectOverviewSelectors.getProject, value: mockProject },
49+
{ selector: NodeLinksSelectors.getNodeLinksSubmitting, value: false },
50+
],
51+
}),
52+
],
1253
}).compileComponents();
1354

55+
store = TestBed.inject(Store) as jest.Mocked<Store>;
56+
store.dispatch = jest.fn().mockReturnValue(of(true));
1457
fixture = TestBed.createComponent(DeleteNodeLinkDialogComponent);
1558
component = fixture.componentInstance;
59+
dialogRef = TestBed.inject(DynamicDialogRef) as jest.Mocked<DynamicDialogRef>;
60+
toastService = TestBed.inject(ToastService) as jest.Mocked<ToastService>;
1661
fixture.detectChanges();
1762
});
1863

19-
it('should create', () => {
20-
expect(component).toBeTruthy();
64+
afterEach(() => {
65+
jest.clearAllMocks();
66+
});
67+
68+
it('should initialize currentProject selector', () => {
69+
expect(component.currentProject()).toEqual(mockProject);
2170
});
71+
72+
it('should initialize isSubmitting selector', () => {
73+
expect(component.isSubmitting()).toBe(false);
74+
});
75+
76+
it('should initialize actions with deleteNodeLink mapping', () => {
77+
expect(component.actions.deleteNodeLink).toBeDefined();
78+
});
79+
80+
it('should dispatch DeleteNodeLink action with correct parameters on successful deletion', () => {
81+
component.handleDeleteNodeLink();
82+
83+
expect(store.dispatch).toHaveBeenCalledWith(expect.any(DeleteNodeLink));
84+
const call = (store.dispatch as jest.Mock).mock.calls.find((call) => call[0] instanceof DeleteNodeLink);
85+
expect(call).toBeDefined();
86+
const action = call[0] as DeleteNodeLink;
87+
expect(action.projectId).toBe('test-project-id');
88+
expect(action.linkedResource).toEqual(mockCurrentLink);
89+
});
90+
91+
it('should show success toast on successful deletion', fakeAsync(() => {
92+
component.handleDeleteNodeLink();
93+
tick();
94+
95+
expect(toastService.showSuccess).toHaveBeenCalledWith('project.overview.dialog.toast.deleteNodeLink.success');
96+
}));
97+
98+
it('should close dialog with hasChanges true on successful deletion', fakeAsync(() => {
99+
component.handleDeleteNodeLink();
100+
tick();
101+
102+
expect(dialogRef.close).toHaveBeenCalledWith({ hasChanges: true });
103+
}));
22104
});

src/app/features/project/overview/components/delete-node-link-dialog/delete-node-link-dialog.component.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
88
import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular/core';
99

1010
import { ToastService } from '@osf/shared/services/toast.service';
11-
import { DeleteNodeLink, GetLinkedResources, NodeLinksSelectors } from '@osf/shared/stores/node-links';
11+
import { DeleteNodeLink, NodeLinksSelectors } from '@osf/shared/stores/node-links';
1212

1313
import { ProjectOverviewSelectors } from '../../store';
1414

@@ -28,7 +28,7 @@ export class DeleteNodeLinkDialogComponent {
2828
currentProject = select(ProjectOverviewSelectors.getProject);
2929
isSubmitting = select(NodeLinksSelectors.getNodeLinksSubmitting);
3030

31-
actions = createDispatchMap({ deleteNodeLink: DeleteNodeLink, getLinkedResources: GetLinkedResources });
31+
actions = createDispatchMap({ deleteNodeLink: DeleteNodeLink });
3232

3333
handleDeleteNodeLink(): void {
3434
const project = this.currentProject();
@@ -38,9 +38,8 @@ export class DeleteNodeLinkDialogComponent {
3838

3939
this.actions.deleteNodeLink(project.id, currentLink).subscribe({
4040
next: () => {
41-
this.dialogRef.close();
42-
this.actions.getLinkedResources(project.id);
4341
this.toastService.showSuccess('project.overview.dialog.toast.deleteNodeLink.success');
42+
this.dialogRef.close({ hasChanges: true });
4443
},
4544
});
4645
}

0 commit comments

Comments
 (0)