Skip to content

Commit 548abbd

Browse files
committed
Merge remote-tracking branch 'upstream/feature/pbs-25-24' into fix/ENG-9164
2 parents e01711f + 1f6bece commit 548abbd

24 files changed

+699
-164
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
"@centerforopenscience/markdown-it-atrules",

package-lock.json

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

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
"chart.js": "^4.4.9",
6161
"diff": "^8.0.2",
6262
"markdown-it": "^14.1.0",
63+
"markdown-it-anchor": "^9.2.0",
64+
"markdown-it-toc-done-right": "^4.2.0",
6365
"markdown-it-video": "^0.6.3",
6466
"ngx-captcha": "^13.0.0",
6567
"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/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
}

src/app/features/project/overview/components/link-resource-dialog/link-resource-dialog.component.html

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,14 @@
3939
<p-table
4040
class="link-project-table"
4141
[value]="isCurrentTableLoading() ? skeletonData : currentTableItems()"
42-
[rows]="tableRows"
43-
[first]="(currentPage() - 1) * 10"
44-
[paginator]="currentTotalCount() > tableRows"
45-
[totalRecords]="currentTotalCount()"
42+
[rows]="tableParams().rows"
43+
[first]="tableParams().firstRowIndex"
44+
[paginator]="tableParams().paginator"
45+
[totalRecords]="tableParams().totalRecords"
4646
paginatorDropdownAppendTo="body"
4747
[resizableColumns]="true"
4848
[autoLayout]="true"
49-
[scrollable]="true"
50-
[sortMode]="'single'"
49+
[scrollable]="tableParams().scrollable"
5150
[lazy]="true"
5251
[lazyLoadOnInit]="true"
5352
(onPage)="onPageChange($event)"
@@ -74,7 +73,7 @@
7473
<p-checkbox
7574
[disabled]="isNodeLinksSubmitting()"
7675
[inputId]="item.id"
77-
[ngModel]="isItemLinked()(item.id)"
76+
[ngModel]="isItemLinked(item.id)"
7877
[binary]="true"
7978
(onChange)="handleToggleNodeLink(item)"
8079
/>
@@ -84,15 +83,19 @@
8483
<td>{{ item.dateCreated | date: 'MMM d, y' }}</td>
8584
<td>{{ item.dateModified | date: 'MMM d, y' }}</td>
8685
<td>
87-
@for (contributor of item.contributors; track contributor.id) {
86+
@for (contributor of item.contributors.slice(0, 3); track contributor.id) {
8887
{{ contributor.fullName }}{{ $last ? '' : ', ' }}
8988
}
89+
@if (item.contributors.length > 3) {
90+
, {{ 'common.labels.and' | translate }} {{ item.contributors.length - 3 }}
91+
{{ 'common.labels.more' | translate }}
92+
}
9093
</td>
9194
</tr>
9295
} @else {
9396
<tr class="loading-row">
9497
<td colspan="4">
95-
<p-skeleton width="100%" height="3.3rem" borderRadius="0" />
98+
<p-skeleton width="100%" height="2.75rem" borderRadius="0" />
9699
</td>
97100
</tr>
98101
}

0 commit comments

Comments
 (0)