Skip to content

Commit 2f10d24

Browse files
committed
Merge remote-tracking branch 'upstream/feature/pbs-25-24' into feat/ENG-10048
2 parents c4c7d05 + af287e0 commit 2f10d24

File tree

5 files changed

+186
-53
lines changed

5 files changed

+186
-53
lines changed

src/app/features/registries/pages/my-registrations/my-registrations.component.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</div>
1111

1212
<div class="flex-column flex flex-1 w-full">
13-
<p-tabs [value]="selectedTab()" (valueChange)="selectedTab.set(+$event)" class="flex-1">
13+
<p-tabs [value]="selectedTab()" (valueChange)="onTabChange(+$event)" class="flex-1">
1414
@if (!isMobile()) {
1515
<p-tablist class="px-4">
1616
@for (tab of tabOptions; track tab.value) {
@@ -25,7 +25,8 @@
2525
class="block mb-4"
2626
[options]="tabOptions"
2727
[fullWidth]="true"
28-
[(selectedValue)]="selectedTab"
28+
[selectedValue]="selectedTab()"
29+
(selectedValueChange)="onTabChange($event)"
2930
></osf-select>
3031
}
3132

src/app/features/registries/pages/my-registrations/my-registrations.component.spec.ts

Lines changed: 147 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { MockComponents, MockProvider } from 'ng-mocks';
22

3+
import { of } from 'rxjs';
4+
35
import { ComponentFixture, TestBed } from '@angular/core/testing';
46
import { ActivatedRoute, Router } from '@angular/router';
57

68
import { UserSelectors } from '@core/store/user';
9+
import { RegistrationTab } from '@osf/features/registries/enums';
710
import { RegistriesSelectors } from '@osf/features/registries/store';
811
import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
912
import { RegistrationCardComponent } from '@osf/shared/components/registration-card/registration-card.component';
@@ -23,6 +26,8 @@ describe('MyRegistrationsComponent', () => {
2326
let fixture: ComponentFixture<MyRegistrationsComponent>;
2427
let mockRouter: ReturnType<RouterMockBuilder['build']>;
2528
let mockActivatedRoute: Partial<ActivatedRoute>;
29+
let customConfirmationService: jest.Mocked<CustomConfirmationService>;
30+
let toastService: jest.Mocked<ToastService>;
2631

2732
beforeEach(async () => {
2833
mockRouter = RouterMockBuilder.create().withUrl('/registries/me').build();
@@ -55,32 +60,101 @@ describe('MyRegistrationsComponent', () => {
5560

5661
fixture = TestBed.createComponent(MyRegistrationsComponent);
5762
component = fixture.componentInstance;
63+
customConfirmationService = TestBed.inject(CustomConfirmationService) as jest.Mocked<CustomConfirmationService>;
64+
toastService = TestBed.inject(ToastService) as jest.Mocked<ToastService>;
5865
fixture.detectChanges();
5966
});
6067

6168
it('should create', () => {
6269
expect(component).toBeTruthy();
6370
});
6471

65-
it('should default to submitted tab and fetch submitted registrations', () => {
72+
it('should default to submitted tab when no query param', () => {
73+
expect(component.selectedTab()).toBe(RegistrationTab.Submitted);
74+
});
75+
76+
it('should switch to drafts tab when query param is drafts', () => {
77+
(mockActivatedRoute.snapshot as any).queryParams = { tab: 'drafts' };
78+
79+
fixture = TestBed.createComponent(MyRegistrationsComponent);
80+
component = fixture.componentInstance;
81+
fixture.detectChanges();
82+
83+
expect(component.selectedTab()).toBe(RegistrationTab.Drafts);
84+
});
85+
86+
it('should switch to submitted tab when query param is submitted', () => {
87+
(mockActivatedRoute.snapshot as any).queryParams = { tab: 'submitted' };
88+
89+
fixture = TestBed.createComponent(MyRegistrationsComponent);
90+
component = fixture.componentInstance;
91+
fixture.detectChanges();
92+
93+
expect(component.selectedTab()).toBe(RegistrationTab.Submitted);
94+
});
95+
96+
it('should handle tab change and update query params', () => {
6697
const actionsMock = {
6798
getDraftRegistrations: jest.fn(),
6899
getSubmittedRegistrations: jest.fn(),
69100
deleteDraft: jest.fn(),
70101
} as any;
71102
Object.defineProperty(component, 'actions', { value: actionsMock });
103+
const navigateSpy = jest.spyOn(mockRouter, 'navigate');
104+
105+
component.onTabChange(RegistrationTab.Drafts);
106+
107+
expect(component.selectedTab()).toBe(RegistrationTab.Drafts);
108+
expect(component.draftFirst).toBe(0);
109+
expect(actionsMock.getDraftRegistrations).toHaveBeenCalledWith();
110+
expect(navigateSpy).toHaveBeenCalledWith([], {
111+
relativeTo: mockActivatedRoute,
112+
queryParams: { tab: 'drafts' },
113+
queryParamsHandling: 'merge',
114+
});
115+
});
72116

73-
component.selectedTab.set(component.RegistrationTab.Drafts);
74-
fixture.detectChanges();
75-
component.selectedTab.set(component.RegistrationTab.Submitted);
76-
fixture.detectChanges();
77-
expect(actionsMock.getSubmittedRegistrations).toHaveBeenCalledWith('user-1');
117+
it('should handle tab change to submitted and update query params', () => {
118+
const actionsMock = {
119+
getDraftRegistrations: jest.fn(),
120+
getSubmittedRegistrations: jest.fn(),
121+
deleteDraft: jest.fn(),
122+
} as any;
123+
Object.defineProperty(component, 'actions', { value: actionsMock });
124+
const navigateSpy = jest.spyOn(mockRouter, 'navigate');
125+
126+
component.onTabChange(RegistrationTab.Submitted);
127+
128+
expect(component.selectedTab()).toBe(RegistrationTab.Submitted);
129+
expect(component.submittedFirst).toBe(0);
130+
expect(actionsMock.getSubmittedRegistrations).toHaveBeenCalledWith();
131+
expect(navigateSpy).toHaveBeenCalledWith([], {
132+
relativeTo: mockActivatedRoute,
133+
queryParams: { tab: 'submitted' },
134+
queryParamsHandling: 'merge',
135+
});
136+
});
137+
138+
it('should not process tab change if tab is not a number', () => {
139+
const actionsMock = {
140+
getDraftRegistrations: jest.fn(),
141+
getSubmittedRegistrations: jest.fn(),
142+
deleteDraft: jest.fn(),
143+
} as any;
144+
Object.defineProperty(component, 'actions', { value: actionsMock });
145+
const initialTab = component.selectedTab();
146+
147+
component.onTabChange('invalid' as any);
148+
149+
expect(component.selectedTab()).toBe(initialTab);
150+
expect(actionsMock.getDraftRegistrations).not.toHaveBeenCalled();
151+
expect(actionsMock.getSubmittedRegistrations).not.toHaveBeenCalled();
78152
});
79153

80154
it('should navigate to create registration page', () => {
81-
const navSpy = jest.spyOn(TestBed.inject(Router), 'navigate');
155+
const navSpy = jest.spyOn(mockRouter, 'navigate');
82156
component.goToCreateRegistration();
83-
expect(navSpy).toHaveBeenCalledWith(['/registries/osf/new']);
157+
expect(navSpy).toHaveBeenLastCalledWith(['/registries', 'osf', 'new']);
84158
});
85159

86160
it('should handle drafts pagination', () => {
@@ -95,23 +169,75 @@ describe('MyRegistrationsComponent', () => {
95169
const actionsMock = { getSubmittedRegistrations: jest.fn() } as any;
96170
Object.defineProperty(component, 'actions', { value: actionsMock });
97171
component.onSubmittedPageChange({ page: 1, first: 10 } as any);
98-
expect(actionsMock.getSubmittedRegistrations).toHaveBeenCalledWith('user-1', 2);
172+
expect(actionsMock.getSubmittedRegistrations).toHaveBeenCalledWith(2);
99173
expect(component.submittedFirst).toBe(10);
100174
});
101175

102-
it('should switch to drafts tab based on query param and fetch drafts', async () => {
103-
(mockActivatedRoute.snapshot as any).queryParams = { tab: 'drafts' };
104-
const actionsMock = { getDraftRegistrations: jest.fn(), getSubmittedRegistrations: jest.fn() } as any;
105-
fixture = TestBed.createComponent(MyRegistrationsComponent);
106-
component = fixture.componentInstance;
176+
it('should delete draft after confirmation', () => {
177+
const actionsMock = {
178+
getDraftRegistrations: jest.fn(),
179+
getSubmittedRegistrations: jest.fn(),
180+
deleteDraft: jest.fn(() => of({})),
181+
} as any;
107182
Object.defineProperty(component, 'actions', { value: actionsMock });
108-
fixture.detectChanges();
109-
110-
expect(component.selectedTab()).toBe(0);
111-
component.selectedTab.set(component.RegistrationTab.Submitted);
112-
fixture.detectChanges();
113-
component.selectedTab.set(component.RegistrationTab.Drafts);
114-
fixture.detectChanges();
183+
customConfirmationService.confirmDelete.mockImplementation(({ onConfirm }) => {
184+
onConfirm();
185+
});
186+
187+
component.onDeleteDraft('draft-123');
188+
189+
expect(customConfirmationService.confirmDelete).toHaveBeenCalledWith({
190+
headerKey: 'registries.deleteDraft',
191+
messageKey: 'registries.confirmDeleteDraft',
192+
onConfirm: expect.any(Function),
193+
});
194+
expect(actionsMock.deleteDraft).toHaveBeenCalledWith('draft-123');
115195
expect(actionsMock.getDraftRegistrations).toHaveBeenCalled();
196+
expect(toastService.showSuccess).toHaveBeenCalledWith('registries.successDeleteDraft');
197+
});
198+
199+
it('should not delete draft if confirmation is cancelled', () => {
200+
const actionsMock = {
201+
getDraftRegistrations: jest.fn(),
202+
getSubmittedRegistrations: jest.fn(),
203+
deleteDraft: jest.fn(),
204+
} as any;
205+
Object.defineProperty(component, 'actions', { value: actionsMock });
206+
customConfirmationService.confirmDelete.mockImplementation(() => {});
207+
208+
component.onDeleteDraft('draft-123');
209+
210+
expect(customConfirmationService.confirmDelete).toHaveBeenCalled();
211+
expect(actionsMock.deleteDraft).not.toHaveBeenCalled();
212+
expect(actionsMock.getDraftRegistrations).not.toHaveBeenCalled();
213+
expect(toastService.showSuccess).not.toHaveBeenCalled();
214+
});
215+
216+
it('should reset draftFirst when switching to drafts tab', () => {
217+
component.draftFirst = 20;
218+
const actionsMock = {
219+
getDraftRegistrations: jest.fn(),
220+
getSubmittedRegistrations: jest.fn(),
221+
deleteDraft: jest.fn(),
222+
} as any;
223+
Object.defineProperty(component, 'actions', { value: actionsMock });
224+
225+
component.onTabChange(RegistrationTab.Drafts);
226+
227+
expect(component.draftFirst).toBe(0);
228+
});
229+
230+
it('should reset submittedFirst when switching to submitted tab', () => {
231+
component.submittedFirst = 20;
232+
const actionsMock = {
233+
getDraftRegistrations: jest.fn(),
234+
getSubmittedRegistrations: jest.fn(),
235+
deleteDraft: jest.fn(),
236+
} as any;
237+
Object.defineProperty(component, 'actions', { value: actionsMock });
238+
239+
component.onTabChange(RegistrationTab.Submitted);
240+
241+
expect(component.submittedFirst).toBe(0);
116242
});
117243
});

src/app/features/registries/pages/my-registrations/my-registrations.component.ts

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ import { Skeleton } from 'primeng/skeleton';
88
import { TabsModule } from 'primeng/tabs';
99

1010
import { NgTemplateOutlet } from '@angular/common';
11-
import { ChangeDetectionStrategy, Component, effect, inject, signal } from '@angular/core';
11+
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
1212
import { toSignal } from '@angular/core/rxjs-interop';
1313
import { FormsModule } from '@angular/forms';
1414
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
1515

1616
import { ENVIRONMENT } from '@core/provider/environment.provider';
17-
import { UserSelectors } from '@core/store/user';
1817
import { CustomPaginatorComponent } from '@osf/shared/components/custom-paginator/custom-paginator.component';
1918
import { RegistrationCardComponent } from '@osf/shared/components/registration-card/registration-card.component';
2019
import { SelectComponent } from '@osf/shared/components/select/select.component';
2120
import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component';
2221
import { IS_XSMALL } from '@osf/shared/helpers/breakpoints.tokens';
22+
import { Primitive } from '@osf/shared/helpers/types.helper';
2323
import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
2424
import { ToastService } from '@osf/shared/services/toast.service';
2525

@@ -57,7 +57,6 @@ export class MyRegistrationsComponent {
5757
readonly isMobile = toSignal(inject(IS_XSMALL));
5858
readonly tabOptions = REGISTRATIONS_TABS;
5959

60-
private currentUser = select(UserSelectors.getCurrentUser);
6160
draftRegistrations = select(RegistriesSelectors.getDraftRegistrations);
6261
draftRegistrationsTotalCount = select(RegistriesSelectors.getDraftRegistrationsTotalCount);
6362
isDraftRegistrationsLoading = select(RegistriesSelectors.isDraftRegistrationsLoading);
@@ -83,33 +82,37 @@ export class MyRegistrationsComponent {
8382

8483
constructor() {
8584
const initialTab = this.route.snapshot.queryParams['tab'];
86-
if (initialTab == 'drafts') {
87-
this.selectedTab.set(RegistrationTab.Drafts);
88-
} else {
89-
this.selectedTab.set(RegistrationTab.Submitted);
85+
const selectedTab = initialTab == 'drafts' ? RegistrationTab.Drafts : RegistrationTab.Submitted;
86+
this.onTabChange(selectedTab);
87+
}
88+
89+
onTabChange(tab: Primitive): void {
90+
if (typeof tab !== 'number') {
91+
return;
9092
}
9193

92-
effect(() => {
93-
const tab = this.selectedTab();
94-
95-
if (tab === 0) {
96-
this.draftFirst = 0;
97-
this.actions.getDraftRegistrations();
98-
} else {
99-
this.submittedFirst = 0;
100-
this.actions.getSubmittedRegistrations(this.currentUser()?.id);
101-
}
102-
103-
this.router.navigate([], {
104-
relativeTo: this.route,
105-
queryParams: { tab: tab === RegistrationTab.Drafts ? 'drafts' : 'submitted' },
106-
queryParamsHandling: 'merge',
107-
});
94+
this.selectedTab.set(tab);
95+
this.loadTabData(tab);
96+
97+
this.router.navigate([], {
98+
relativeTo: this.route,
99+
queryParams: { tab: tab === RegistrationTab.Drafts ? 'drafts' : 'submitted' },
100+
queryParamsHandling: 'merge',
108101
});
109102
}
110103

104+
private loadTabData(tab: number): void {
105+
if (tab === RegistrationTab.Drafts) {
106+
this.draftFirst = 0;
107+
this.actions.getDraftRegistrations();
108+
} else {
109+
this.submittedFirst = 0;
110+
this.actions.getSubmittedRegistrations();
111+
}
112+
}
113+
111114
goToCreateRegistration(): void {
112-
this.router.navigate([`/registries/${this.provider}/new`]);
115+
this.router.navigate(['/registries', this.provider, 'new']);
113116
}
114117

115118
onDeleteDraft(id: string): void {
@@ -133,7 +136,7 @@ export class MyRegistrationsComponent {
133136
}
134137

135138
onSubmittedPageChange(event: PaginatorState): void {
136-
this.actions.getSubmittedRegistrations(this.currentUser()?.id, event.page! + 1);
139+
this.actions.getSubmittedRegistrations(event.page! + 1);
137140
this.submittedFirst = event.first!;
138141
}
139142
}

src/app/features/registries/store/registries.actions.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ export class FetchSubmittedRegistrations {
111111
static readonly type = '[Registries] Fetch Submitted Registrations';
112112

113113
constructor(
114-
public userId: string | undefined,
115114
public page = 1,
116115
public pageSize = 10
117116
) {}

src/app/features/registries/store/registries.state.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Action, State, StateContext } from '@ngxs/store';
1+
import { Action, State, StateContext, Store } from '@ngxs/store';
22

33
import { catchError, tap } from 'rxjs/operators';
44

55
import { inject, Injectable } from '@angular/core';
66

77
import { ENVIRONMENT } from '@core/provider/environment.provider';
8+
import { UserSelectors } from '@core/store/user';
89
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
910
import { getResourceTypeStringFromEnum } from '@osf/shared/helpers/get-resource-types.helper';
1011
import { handleSectionError } from '@osf/shared/helpers/state-error.handler';
@@ -56,6 +57,7 @@ export class RegistriesState {
5657
searchService = inject(GlobalSearchService);
5758
registriesService = inject(RegistriesService);
5859
private readonly environment = inject(ENVIRONMENT);
60+
private readonly store = inject(Store);
5961

6062
providersHandler = inject(ProvidersHandlers);
6163
projectsHandler = inject(ProjectsHandlers);
@@ -311,18 +313,20 @@ export class RegistriesState {
311313
@Action(FetchSubmittedRegistrations)
312314
fetchSubmittedRegistrations(
313315
ctx: StateContext<RegistriesStateModel>,
314-
{ userId, page, pageSize }: FetchSubmittedRegistrations
316+
{ page, pageSize }: FetchSubmittedRegistrations
315317
) {
316318
const state = ctx.getState();
319+
const currentUser = this.store.selectSnapshot(UserSelectors.getCurrentUser);
320+
317321
ctx.patchState({
318322
submittedRegistrations: { ...state.submittedRegistrations, isLoading: true, error: null },
319323
});
320324

321-
if (!userId) {
325+
if (!currentUser) {
322326
return;
323327
}
324328

325-
return this.registriesService.getSubmittedRegistrations(userId, page, pageSize).pipe(
329+
return this.registriesService.getSubmittedRegistrations(currentUser.id, page, pageSize).pipe(
326330
tap((submittedRegistrations) => {
327331
ctx.patchState({
328332
submittedRegistrations: {

0 commit comments

Comments
 (0)