Skip to content

Commit a758e12

Browse files
authored
Merge pull request #980 from nsemets/feat/ENG-10626
[ENG-10626] User unable to Update embargoed registration
2 parents 0f0f6b9 + 21dae65 commit a758e12

7 files changed

Lines changed: 201 additions & 150 deletions

File tree

src/app/shared/components/registration-card/registration-card.component.html

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="flex align-items-center flex-wrap gap-2 w-full">
66
<osf-icon [iconClass]="registrationData().public ? 'fas fa-lock-open' : 'fas fa-lock'"></osf-icon>
77
<h2 class="align-self-center word-break-word">
8-
{{ (registrationData().title | fixSpecialChar) || ('project.registrations.card.noTitle' | translate) }}
8+
{{ registrationData().title || ('project.registrations.card.noTitle' | translate) }}
99
</h2>
1010
@if (!isDraft()) {
1111
<osf-status-badge [status]="registrationData().status" />
@@ -49,20 +49,19 @@ <h2 class="align-self-center word-break-word">
4949
</div>
5050
<div class="flex gap-2 mt-1">
5151
@if (isDraft()) {
52-
@if (hasWriteAccess) {
52+
@if (hasWriteAccess()) {
5353
<p-button
5454
severity="primary"
5555
[label]="'common.buttons.review' | translate"
5656
[routerLink]="['/registries/drafts/', registrationData().id, 'review']"
57-
routerLinkActive="router-link-active"
5857
/>
5958
<p-button
6059
severity="secondary"
6160
[label]="'common.buttons.edit' | translate"
6261
[routerLink]="['/registries/drafts/', registrationData().id, 'metadata']"
6362
/>
6463
}
65-
@if (hasAdminAccess) {
64+
@if (hasAdminAccess()) {
6665
<p-button
6766
severity="danger"
6867
[label]="'common.buttons.delete' | translate"
@@ -77,15 +76,15 @@ <h2 class="align-self-center word-break-word">
7776
routerLinkActive="router-link-active"
7877
[label]="'common.buttons.view' | translate"
7978
></p-button>
80-
@if (showButtons) {
81-
@if (isApproved) {
79+
@if (showButtons()) {
80+
@if (isApproved()) {
8281
<p-button
8382
severity="secondary"
8483
(onClick)="updateRegistration(registrationData().id)"
8584
[label]="'common.buttons.update' | translate"
8685
></p-button>
8786
}
88-
@if (isInProgress || isUnapproved) {
87+
@if (isInProgress() || isUnapproved()) {
8988
<p-button
9089
severity="secondary"
9190
(onClick)="continueUpdateRegistration(registrationData().id)"
@@ -97,7 +96,7 @@ <h2 class="align-self-center word-break-word">
9796
</div>
9897
</div>
9998

100-
@if (isApproved) {
99+
@if (isApproved()) {
101100
<div class="flex flex-column min-w-max">
102101
<h3 class="mb-3">{{ 'shared.resources.title' | translate }}</h3>
103102
<osf-data-resources
Lines changed: 159 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
1-
import { MockComponents } from 'ng-mocks';
1+
import { Store } from '@ngxs/store';
2+
3+
import { MockComponents, MockProvider } from 'ng-mocks';
4+
5+
import { Mock } from 'vitest';
26

37
import { signal } from '@angular/core';
48
import { ComponentFixture, TestBed } from '@angular/core/testing';
5-
import { provideRouter } from '@angular/router';
9+
import { Router } from '@angular/router';
610

7-
import { RegistriesSelectors } from '@osf/features/registries/store';
11+
import { CreateSchemaResponse, FetchAllSchemaResponses, RegistriesSelectors } from '@osf/features/registries/store';
812
import { RegistrationReviewStates } from '@osf/shared/enums/registration-review-states.enum';
913
import { RevisionReviewStates } from '@osf/shared/enums/revision-review-states.enum';
14+
import { UserPermissions } from '@osf/shared/enums/user-permissions.enum';
1015
import { RegistrationCard } from '@shared/models/registration/registration-card.model';
1116

1217
import { MOCK_REGISTRATION } from '@testing/mocks/registration.mock';
1318
import { provideOSFCore } from '@testing/osf.testing.provider';
14-
import { provideMockStore } from '@testing/providers/store-provider.mock';
19+
import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
20+
import {
21+
BaseSetupOverrides,
22+
mergeSignalOverrides,
23+
provideMockStore,
24+
SignalOverride,
25+
} from '@testing/providers/store-provider.mock';
1526

1627
import { ContributorsListComponent } from '../contributors-list/contributors-list.component';
1728
import { DataResourcesComponent } from '../data-resources/data-resources.component';
@@ -23,144 +34,189 @@ import { RegistrationCardComponent } from './registration-card.component';
2334
describe('RegistrationCardComponent', () => {
2435
let component: RegistrationCardComponent;
2536
let fixture: ComponentFixture<RegistrationCardComponent>;
37+
let store: Store;
38+
let routerMock: RouterMockType;
2639

2740
const mockRegistrationData: RegistrationCard = MOCK_REGISTRATION;
2841

29-
beforeEach(() => {
42+
const defaultSignals: SignalOverride[] = [
43+
{ selector: RegistriesSelectors.getSchemaResponse, value: signal({ id: 'revision-id' }) },
44+
];
45+
46+
type SetupOverrides = BaseSetupOverrides & {
47+
registrationData?: RegistrationCard;
48+
isDraft?: boolean;
49+
};
50+
51+
function setup(overrides: SetupOverrides = {}): void {
52+
routerMock = RouterMockBuilder.create().build();
53+
3054
TestBed.configureTestingModule({
3155
imports: [
3256
RegistrationCardComponent,
3357
...MockComponents(StatusBadgeComponent, DataResourcesComponent, IconComponent, ContributorsListComponent),
3458
],
3559
providers: [
3660
provideOSFCore(),
37-
provideRouter([]),
38-
provideMockStore({
39-
signals: [{ selector: RegistriesSelectors.getSchemaResponse, value: signal(null) }],
40-
}),
61+
MockProvider(Router, routerMock),
62+
provideMockStore({ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides) }),
4163
],
4264
});
4365

66+
store = TestBed.inject(Store);
4467
fixture = TestBed.createComponent(RegistrationCardComponent);
4568
component = fixture.componentInstance;
46-
});
69+
fixture.componentRef.setInput('registrationData', overrides.registrationData ?? mockRegistrationData);
70+
fixture.componentRef.setInput('isDraft', overrides.isDraft ?? false);
71+
fixture.detectChanges();
72+
}
4773

4874
it('should create', () => {
49-
expect(component).toBeTruthy();
50-
});
75+
setup();
5176

52-
it('should have registrationData as required input', () => {
53-
fixture.componentRef.setInput('registrationData', mockRegistrationData);
54-
expect(component.registrationData()).toEqual(mockRegistrationData);
77+
expect(component).toBeTruthy();
5578
});
5679

57-
it('should compute isAccepted correctly when reviewsState is Accepted', () => {
58-
const testData = {
59-
...mockRegistrationData,
60-
reviewsState: RegistrationReviewStates.Accepted,
61-
};
62-
fixture.componentRef.setInput('registrationData', testData);
63-
fixture.detectChanges();
80+
it.each([
81+
[[UserPermissions.Read, UserPermissions.Write, UserPermissions.Admin], true, true],
82+
[[UserPermissions.Write], false, true],
83+
[[UserPermissions.Admin], true, false],
84+
] as [UserPermissions[], boolean, boolean][])(
85+
'should identify user permissions',
86+
(currentUserPermissions, hasAdminAccess, hasWriteAccess) => {
87+
setup({
88+
registrationData: {
89+
...mockRegistrationData,
90+
currentUserPermissions,
91+
},
92+
});
93+
94+
expect(component.hasAdminAccess()).toBe(hasAdminAccess);
95+
expect(component.hasWriteAccess()).toBe(hasWriteAccess);
96+
}
97+
);
98+
99+
it.each([
100+
[RegistrationReviewStates.Accepted, true],
101+
[RegistrationReviewStates.Pending, true],
102+
[RegistrationReviewStates.Embargo, true],
103+
[RegistrationReviewStates.Withdrawn, false],
104+
] as [RegistrationReviewStates, boolean][])(
105+
'should identify update-eligible review states',
106+
(reviewsState, eligible) => {
107+
setup({
108+
registrationData: {
109+
...mockRegistrationData,
110+
reviewsState,
111+
},
112+
});
113+
114+
expect(component.isAccepted()).toBe(reviewsState === RegistrationReviewStates.Accepted);
115+
expect(component.isPending()).toBe(reviewsState === RegistrationReviewStates.Pending);
116+
expect(component.isEmbargo()).toBe(reviewsState === RegistrationReviewStates.Embargo);
117+
expect(component.showButtons()).toBe(eligible);
118+
}
119+
);
120+
121+
it.each([
122+
[RevisionReviewStates.Approved, true, false, false],
123+
[RevisionReviewStates.Unapproved, false, true, false],
124+
[RevisionReviewStates.RevisionInProgress, false, false, true],
125+
] as [RevisionReviewStates, boolean, boolean, boolean][])(
126+
'should identify revision states',
127+
(revisionState, isApproved, isUnapproved, isInProgress) => {
128+
setup({
129+
registrationData: {
130+
...mockRegistrationData,
131+
revisionState,
132+
},
133+
});
134+
135+
expect(component.isApproved()).toBe(isApproved);
136+
expect(component.isUnapproved()).toBe(isUnapproved);
137+
expect(component.isInProgress()).toBe(isInProgress);
138+
}
139+
);
140+
141+
it.each([
142+
[null, true],
143+
[mockRegistrationData.id, true],
144+
['different-root-id', false],
145+
] as [string | null, boolean][])('should identify root registrations', (rootParentId, isRootRegistration) => {
146+
setup({
147+
registrationData: {
148+
...mockRegistrationData,
149+
rootParentId,
150+
},
151+
});
64152

65-
expect(component.isAccepted).toBe(true);
153+
expect(component.isRootRegistration()).toBe(isRootRegistration);
66154
});
67155

68-
it('should compute isPending correctly when reviewsState is Pending', () => {
69-
const testData = {
70-
...mockRegistrationData,
71-
reviewsState: RegistrationReviewStates.Pending,
72-
};
73-
fixture.componentRef.setInput('registrationData', testData);
74-
fixture.detectChanges();
156+
it.each([
157+
['updates are disabled', { allowUpdates: false }],
158+
['user lacks admin access', { currentUserPermissions: [UserPermissions.Write] }],
159+
['registration is not root', { rootParentId: 'different-root-id' }],
160+
] as [string, Partial<RegistrationCard>][])('should hide update buttons when %s', (_label, registrationData) => {
161+
setup({
162+
registrationData: {
163+
...mockRegistrationData,
164+
...registrationData,
165+
},
166+
});
75167

76-
expect(component.isPending).toBe(true);
168+
expect(component.showButtons()).toBe(false);
77169
});
78170

79-
it('should compute isApproved correctly when revisionState is Approved', () => {
80-
const testData = {
81-
...mockRegistrationData,
82-
revisionState: RevisionReviewStates.Approved,
83-
};
84-
fixture.componentRef.setInput('registrationData', testData);
85-
fixture.detectChanges();
171+
it('should dispatch create schema response and navigate to justification page on updateRegistration', () => {
172+
setup();
173+
(store.dispatch as Mock).mockClear();
86174

87-
expect(component.isApproved).toBe(true);
88-
});
175+
component.updateRegistration(mockRegistrationData.id);
89176

90-
it('should compute isUnapproved correctly when revisionState is Unapproved', () => {
91-
const testData = {
92-
...mockRegistrationData,
93-
revisionState: RevisionReviewStates.Unapproved,
94-
};
95-
fixture.componentRef.setInput('registrationData', testData);
96-
fixture.detectChanges();
97-
98-
expect(component.isUnapproved).toBe(true);
177+
expect(store.dispatch).toHaveBeenCalledWith(new CreateSchemaResponse(mockRegistrationData.id));
178+
expect(routerMock.navigate).toHaveBeenCalledWith(['/registries/revisions/revision-id/justification']);
99179
});
100180

101-
it('should compute isInProgress correctly when revisionState is RevisionInProgress', () => {
102-
const testData = {
103-
...mockRegistrationData,
104-
revisionState: RevisionReviewStates.RevisionInProgress,
105-
};
106-
fixture.componentRef.setInput('registrationData', testData);
107-
fixture.detectChanges();
108-
109-
expect(component.isInProgress).toBe(true);
110-
});
181+
it('should dispatch fetch schema responses and navigate to review page for unapproved revision', () => {
182+
setup({
183+
registrationData: {
184+
...mockRegistrationData,
185+
revisionState: RevisionReviewStates.Unapproved,
186+
},
187+
});
188+
(store.dispatch as Mock).mockClear();
111189

112-
it('should compute isAccepted as false when reviewsState is not Accepted', () => {
113-
const testData = {
114-
...mockRegistrationData,
115-
reviewsState: RegistrationReviewStates.Pending,
116-
};
117-
fixture.componentRef.setInput('registrationData', testData);
118-
fixture.detectChanges();
190+
component.continueUpdateRegistration(mockRegistrationData.id);
119191

120-
expect(component.isAccepted).toBe(false);
192+
expect(store.dispatch).toHaveBeenCalledWith(new FetchAllSchemaResponses(mockRegistrationData.id));
193+
expect(routerMock.navigate).toHaveBeenCalledWith(['/registries/revisions/revision-id/review']);
121194
});
122195

123-
it('should compute isPending as false when reviewsState is not Pending', () => {
124-
const testData = {
125-
...mockRegistrationData,
126-
reviewsState: RegistrationReviewStates.Accepted,
127-
};
128-
fixture.componentRef.setInput('registrationData', testData);
129-
fixture.detectChanges();
130-
131-
expect(component.isPending).toBe(false);
132-
});
196+
it('should dispatch fetch schema responses and navigate to justification page for non-unapproved revision', () => {
197+
setup({
198+
registrationData: {
199+
...mockRegistrationData,
200+
revisionState: RevisionReviewStates.RevisionInProgress,
201+
},
202+
});
203+
(store.dispatch as Mock).mockClear();
133204

134-
it('should compute isApproved as false when revisionState is not Approved', () => {
135-
const testData = {
136-
...mockRegistrationData,
137-
revisionState: RevisionReviewStates.Unapproved,
138-
};
139-
fixture.componentRef.setInput('registrationData', testData);
140-
fixture.detectChanges();
205+
component.continueUpdateRegistration(mockRegistrationData.id);
141206

142-
expect(component.isApproved).toBe(false);
207+
expect(store.dispatch).toHaveBeenCalledWith(new FetchAllSchemaResponses(mockRegistrationData.id));
208+
expect(routerMock.navigate).toHaveBeenCalledWith(['/registries/revisions/revision-id/justification']);
143209
});
144210

145-
it('should compute isUnapproved as false when revisionState is not Unapproved', () => {
146-
const testData = {
147-
...mockRegistrationData,
148-
revisionState: RevisionReviewStates.Approved,
149-
};
150-
fixture.componentRef.setInput('registrationData', testData);
151-
fixture.detectChanges();
152-
153-
expect(component.isUnapproved).toBe(false);
154-
});
211+
it('should not navigate when schema response is not present', () => {
212+
setup({
213+
selectorOverrides: [{ selector: RegistriesSelectors.getSchemaResponse, value: signal(null) }],
214+
});
215+
(store.dispatch as Mock).mockClear();
155216

156-
it('should compute isInProgress as false when revisionState is not RevisionInProgress', () => {
157-
const testData = {
158-
...mockRegistrationData,
159-
revisionState: RevisionReviewStates.Approved,
160-
};
161-
fixture.componentRef.setInput('registrationData', testData);
162-
fixture.detectChanges();
217+
component.updateRegistration(mockRegistrationData.id);
163218

164-
expect(component.isInProgress).toBe(false);
219+
expect(store.dispatch).toHaveBeenCalledWith(new CreateSchemaResponse(mockRegistrationData.id));
220+
expect(routerMock.navigate).not.toHaveBeenCalled();
165221
});
166222
});

0 commit comments

Comments
 (0)