Skip to content

Commit ad16a8a

Browse files
committed
test(tokens): fixed pr comments
1 parent 3dd9aa5 commit ad16a8a

6 files changed

Lines changed: 76 additions & 142 deletions

File tree

src/app/features/settings/tokens/components/token-add-edit-form/token-add-edit-form.component.spec.ts

Lines changed: 35 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ActivatedRoute, Router } from '@angular/router';
1313

1414
import { TokenCreatedDialogComponent } from '@osf/features/settings/tokens/components';
1515
import { InputLimits } from '@osf/shared/constants';
16-
import { MOCK_STORE, TranslateServiceMock } from '@shared/mocks';
16+
import { MOCK_SCOPES, MOCK_STORE, MOCK_TOKEN, TranslateServiceMock } from '@shared/mocks';
1717
import { ToastService } from '@shared/services';
1818

1919
import { TokenFormControls, TokenModel } from '../../models';
@@ -30,29 +30,25 @@ describe('TokenAddEditFormComponent', () => {
3030
let dialogRef: Partial<DynamicDialogRef>;
3131
let activatedRoute: Partial<ActivatedRoute>;
3232
let router: Partial<Router>;
33-
let toastService: Partial<ToastService>;
33+
let toastService: jest.Mocked<ToastService>;
34+
let translateService: jest.Mocked<TranslateService>;
3435

35-
const MOCK_TOKEN: TokenModel = {
36-
id: '1',
37-
name: 'Test Token',
38-
scopes: ['read', 'write'],
39-
};
40-
41-
const MOCK_SCOPES = [
42-
{ id: 'read', description: 'Read access' },
43-
{ id: 'write', description: 'Write access' },
44-
{ id: 'delete', description: 'Delete access' },
45-
];
36+
const mockTokens: TokenModel[] = [MOCK_TOKEN];
4637

47-
const MOCK_TOKENS = [MOCK_TOKEN];
38+
const fillForm = (tokenName: string = MOCK_TOKEN.name, scopes: string[] = MOCK_TOKEN.scopes): void => {
39+
component.tokenForm.patchValue({
40+
[TokenFormControls.TokenName]: tokenName,
41+
[TokenFormControls.Scopes]: scopes,
42+
});
43+
};
4844

4945
beforeEach(async () => {
5046
(MOCK_STORE.selectSignal as jest.Mock).mockImplementation((selector) => {
5147
if (selector === TokensSelectors.getScopes) return () => MOCK_SCOPES;
5248
if (selector === TokensSelectors.isTokensLoading) return () => false;
53-
if (selector === TokensSelectors.getTokens) return () => MOCK_TOKENS;
49+
if (selector === TokensSelectors.getTokens) return () => mockTokens;
5450
if (selector === TokensSelectors.getTokenById) {
55-
return () => (id: string) => MOCK_TOKENS.find((token) => token.id === id);
51+
return () => (id: string) => mockTokens.find((token) => token.id === id);
5652
}
5753
return () => null;
5854
});
@@ -73,11 +69,6 @@ describe('TokenAddEditFormComponent', () => {
7369
navigate: jest.fn(),
7470
};
7571

76-
toastService = {
77-
showSuccess: jest.fn(),
78-
showError: jest.fn(),
79-
};
80-
8172
await TestBed.configureTestingModule({
8273
imports: [TokenAddEditFormComponent, ReactiveFormsModule, OSFTestingStoreModule],
8374
providers: [
@@ -87,28 +78,28 @@ describe('TokenAddEditFormComponent', () => {
8778
MockProvider(DynamicDialogRef, dialogRef),
8879
MockProvider(ActivatedRoute, activatedRoute),
8980
MockProvider(Router, router),
90-
MockProvider(ToastService, toastService),
81+
MockProvider(ToastService, {
82+
showSuccess: jest.fn(),
83+
showWarn: jest.fn(),
84+
showError: jest.fn(),
85+
}),
9186
],
9287
}).compileComponents();
9388

9489
fixture = TestBed.createComponent(TokenAddEditFormComponent);
9590
component = fixture.componentInstance;
91+
92+
toastService = TestBed.inject(ToastService) as jest.Mocked<ToastService>;
93+
translateService = TestBed.inject(TranslateService) as jest.Mocked<TranslateService>;
94+
9695
fixture.detectChanges();
9796
});
9897

9998
it('should create', () => {
10099
expect(component).toBeTruthy();
101100
});
102101

103-
it('should patch form with initial values when provided', () => {
104-
fixture.componentRef.setInput('initialValues', MOCK_TOKEN);
105-
component.ngOnInit();
106-
107-
expect(component.tokenForm.get(TokenFormControls.TokenName)?.value).toBe(MOCK_TOKEN.name);
108-
expect(component.tokenForm.get(TokenFormControls.Scopes)?.value).toEqual(MOCK_TOKEN.scopes);
109-
});
110-
111-
it('should call patchValue with initial values on ngOnInit', () => {
102+
it('should patch form with initial values on init', () => {
112103
fixture.componentRef.setInput('initialValues', MOCK_TOKEN);
113104
const patchSpy = jest.spyOn(component.tokenForm, 'patchValue');
114105

@@ -120,15 +111,14 @@ describe('TokenAddEditFormComponent', () => {
120111
[TokenFormControls.Scopes]: MOCK_TOKEN.scopes,
121112
})
122113
);
114+
expect(component.tokenForm.get(TokenFormControls.TokenName)?.value).toBe(MOCK_TOKEN.name);
115+
expect(component.tokenForm.get(TokenFormControls.Scopes)?.value).toEqual(MOCK_TOKEN.scopes);
123116
});
124117

125118
it('should not patch form when initialValues are not provided', () => {
126119
fixture.componentRef.setInput('initialValues', null);
127120

128-
component.tokenForm.patchValue({
129-
[TokenFormControls.TokenName]: 'Existing Name',
130-
[TokenFormControls.Scopes]: ['read'],
131-
});
121+
fillForm('Existing Name', ['read']);
132122

133123
component.ngOnInit();
134124

@@ -137,10 +127,7 @@ describe('TokenAddEditFormComponent', () => {
137127
});
138128

139129
it('should not submit when form is invalid', () => {
140-
component.tokenForm.patchValue({
141-
[TokenFormControls.TokenName]: '',
142-
[TokenFormControls.Scopes]: [],
143-
});
130+
fillForm('', []);
144131

145132
const markAllAsTouchedSpy = jest.spyOn(component.tokenForm, 'markAllAsTouched');
146133
const markAsDirtySpy = jest.spyOn(component.tokenForm.get(TokenFormControls.TokenName)!, 'markAsDirty');
@@ -155,63 +142,15 @@ describe('TokenAddEditFormComponent', () => {
155142
});
156143

157144
it('should return early when tokenName is missing', () => {
158-
component.tokenForm.patchValue({
159-
[TokenFormControls.TokenName]: '',
160-
[TokenFormControls.Scopes]: ['read'],
161-
});
145+
fillForm('', ['read']);
162146

163147
component.handleSubmitForm();
164148

165149
expect(MOCK_STORE.dispatch).not.toHaveBeenCalled();
166150
});
167151

168152
it('should return early when scopes is missing', () => {
169-
component.tokenForm.patchValue({
170-
[TokenFormControls.TokenName]: 'Test Token',
171-
[TokenFormControls.Scopes]: [],
172-
});
173-
174-
component.handleSubmitForm();
175-
176-
expect(MOCK_STORE.dispatch).not.toHaveBeenCalled();
177-
});
178-
179-
it('should early-return when tokenName is falsy even if form is valid', () => {
180-
const tokenNameControl = component.tokenForm.get(TokenFormControls.TokenName)!;
181-
const scopesControl = component.tokenForm.get(TokenFormControls.Scopes)!;
182-
183-
tokenNameControl.clearValidators();
184-
scopesControl.clearValidators();
185-
tokenNameControl.updateValueAndValidity();
186-
scopesControl.updateValueAndValidity();
187-
188-
component.tokenForm.patchValue({
189-
[TokenFormControls.TokenName]: undefined as unknown as string,
190-
[TokenFormControls.Scopes]: ['read'],
191-
});
192-
193-
expect(component.tokenForm.valid).toBe(true);
194-
195-
component.handleSubmitForm();
196-
197-
expect(MOCK_STORE.dispatch).not.toHaveBeenCalled();
198-
});
199-
200-
it('should early-return when scopes is falsy even if form is valid', () => {
201-
const tokenNameControl = component.tokenForm.get(TokenFormControls.TokenName)!;
202-
const scopesControl = component.tokenForm.get(TokenFormControls.Scopes)!;
203-
204-
tokenNameControl.clearValidators();
205-
scopesControl.clearValidators();
206-
tokenNameControl.updateValueAndValidity();
207-
scopesControl.updateValueAndValidity();
208-
209-
component.tokenForm.patchValue({
210-
[TokenFormControls.TokenName]: 'Test Token',
211-
[TokenFormControls.Scopes]: undefined as unknown as string[],
212-
});
213-
214-
expect(component.tokenForm.valid).toBe(true);
153+
fillForm('Test Token', []);
215154

216155
component.handleSubmitForm();
217156

@@ -220,10 +159,7 @@ describe('TokenAddEditFormComponent', () => {
220159

221160
it('should create token when not in edit mode', () => {
222161
fixture.componentRef.setInput('isEditMode', false);
223-
component.tokenForm.patchValue({
224-
[TokenFormControls.TokenName]: 'Test Token',
225-
[TokenFormControls.Scopes]: ['read', 'write'],
226-
});
162+
fillForm('Test Token', ['read', 'write']);
227163

228164
MOCK_STORE.dispatch.mockReturnValue(of(undefined));
229165

@@ -234,10 +170,7 @@ describe('TokenAddEditFormComponent', () => {
234170

235171
it('should show success toast and close dialog after creating token', () => {
236172
fixture.componentRef.setInput('isEditMode', false);
237-
component.tokenForm.patchValue({
238-
[TokenFormControls.TokenName]: 'Test Token',
239-
[TokenFormControls.Scopes]: ['read', 'write'],
240-
});
173+
fillForm('Test Token', ['read', 'write']);
241174

242175
MOCK_STORE.dispatch.mockReturnValue(of(undefined));
243176

@@ -249,10 +182,7 @@ describe('TokenAddEditFormComponent', () => {
249182

250183
it('should open created dialog with new token name and value after create', () => {
251184
fixture.componentRef.setInput('isEditMode', false);
252-
component.tokenForm.patchValue({
253-
[TokenFormControls.TokenName]: 'Test Token',
254-
[TokenFormControls.Scopes]: ['read', 'write'],
255-
});
185+
fillForm('Test Token', ['read', 'write']);
256186

257187
const showDialogSpy = jest.spyOn(component, 'showTokenCreatedDialog');
258188

@@ -265,10 +195,7 @@ describe('TokenAddEditFormComponent', () => {
265195

266196
it('should show success toast and navigate after updating token', () => {
267197
fixture.componentRef.setInput('isEditMode', true);
268-
component.tokenForm.patchValue({
269-
[TokenFormControls.TokenName]: 'Updated Token',
270-
[TokenFormControls.Scopes]: ['read', 'write'],
271-
});
198+
fillForm('Updated Token', ['read', 'write']);
272199

273200
MOCK_STORE.dispatch.mockReturnValue(of(undefined));
274201

@@ -301,17 +228,13 @@ describe('TokenAddEditFormComponent', () => {
301228
});
302229

303230
it('should use TranslateService.instant for dialog header', () => {
304-
const translate = TestBed.inject(TranslateService) as unknown as { instant: jest.Mock };
305231
component.showTokenCreatedDialog('Name', 'Value');
306-
expect(translate.instant).toHaveBeenCalledWith('settings.tokens.createdDialog.title');
232+
expect(translateService.instant).toHaveBeenCalledWith('settings.tokens.createdDialog.title');
307233
});
308234

309235
it('should read tokens via selectSignal after create', () => {
310236
fixture.componentRef.setInput('isEditMode', false);
311-
component.tokenForm.patchValue({
312-
[TokenFormControls.TokenName]: 'Test Token',
313-
[TokenFormControls.Scopes]: ['read'],
314-
});
237+
fillForm('Test Token', ['read']);
315238

316239
const selectSpy = jest.spyOn(MOCK_STORE, 'selectSignal');
317240
MOCK_STORE.dispatch.mockReturnValue(of(undefined));
@@ -336,10 +259,7 @@ describe('TokenAddEditFormComponent', () => {
336259
});
337260

338261
it('should be valid when both fields are filled', () => {
339-
component.tokenForm.patchValue({
340-
[TokenFormControls.TokenName]: 'Test Token',
341-
[TokenFormControls.Scopes]: ['read'],
342-
});
262+
fillForm('Test Token', ['read']);
343263

344264
expect(component.tokenForm.valid).toBe(true);
345265
});
@@ -355,22 +275,4 @@ describe('TokenAddEditFormComponent', () => {
355275
it('should expose scopes from store via tokenScopes signal', () => {
356276
expect(component.tokenScopes()).toEqual(MOCK_SCOPES);
357277
});
358-
359-
it('should disable form when isLoading is true', () => {
360-
(MOCK_STORE.selectSignal as jest.Mock).mockImplementation((selector) => {
361-
if (selector === TokensSelectors.getScopes) return () => MOCK_SCOPES;
362-
if (selector === TokensSelectors.isTokensLoading) return () => true;
363-
if (selector === TokensSelectors.getTokens) return () => MOCK_TOKENS;
364-
if (selector === TokensSelectors.getTokenById) {
365-
return () => (id: string) => MOCK_TOKENS.find((token) => token.id === id);
366-
}
367-
return () => null;
368-
});
369-
370-
const loadingFixture = TestBed.createComponent(TokenAddEditFormComponent);
371-
const loadingComponent = loadingFixture.componentInstance;
372-
loadingFixture.detectChanges();
373-
374-
expect(loadingComponent.tokenForm.disabled).toBe(true);
375-
});
376278
});

src/app/features/settings/tokens/components/token-created-dialog/token-created-dialog.component.spec.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { MockComponent, MockProvider } from 'ng-mocks';
22

33
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
44

5+
import { NgZone } from '@angular/core';
56
import { ComponentFixture, TestBed } from '@angular/core/testing';
67

78
import { CopyButtonComponent } from '@shared/components';
9+
import { MOCK_TOKEN } from '@shared/mocks';
810

911
import { TokenCreatedDialogComponent } from './token-created-dialog.component';
1012

@@ -14,18 +16,15 @@ describe('TokenCreatedDialogComponent', () => {
1416
let component: TokenCreatedDialogComponent;
1517
let fixture: ComponentFixture<TokenCreatedDialogComponent>;
1618

17-
const mockTokenName = 'Test Token';
18-
const mockTokenValue = 'test-token-value';
19-
2019
beforeEach(async () => {
2120
await TestBed.configureTestingModule({
2221
imports: [TokenCreatedDialogComponent, OSFTestingModule, MockComponent(CopyButtonComponent)],
2322
providers: [
2423
MockProvider(DynamicDialogRef, { close: jest.fn() }),
2524
MockProvider(DynamicDialogConfig, {
2625
data: {
27-
tokenName: mockTokenName,
28-
tokenValue: mockTokenValue,
26+
tokenName: MOCK_TOKEN.name,
27+
tokenValue: MOCK_TOKEN.scopes[0],
2928
},
3029
}),
3130
],
@@ -39,4 +38,22 @@ describe('TokenCreatedDialogComponent', () => {
3938
it('should create', () => {
4039
expect(component).toBeTruthy();
4140
});
41+
42+
it('should initialize inputs from dialog config', () => {
43+
expect(component.tokenName()).toBe(MOCK_TOKEN.name);
44+
expect(component.tokenId()).toBe(MOCK_TOKEN.scopes[0]);
45+
});
46+
47+
it('should set selection range after render', () => {
48+
const fixture = TestBed.createComponent(TokenCreatedDialogComponent);
49+
const zone = TestBed.inject(NgZone);
50+
const spy = jest.spyOn(HTMLInputElement.prototype, 'setSelectionRange');
51+
52+
zone.run(() => {
53+
fixture.autoDetectChanges(true);
54+
fixture.detectChanges();
55+
});
56+
57+
expect(spy).toHaveBeenCalledWith(0, 0);
58+
});
4259
});

src/app/shared/mocks/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ export { MOCK_PROVIDER } from './provider.mock';
1919
export { MOCK_REGISTRATION } from './registration.mock';
2020
export * from './resource.mock';
2121
export { MOCK_REVIEW } from './review.mock';
22+
export { MOCK_SCOPES } from './scope.mock';
23+
export { MOCK_TOKEN } from './token.mock';
2224
export { TranslateServiceMock } from './translate.service.mock';

src/app/shared/mocks/scope.mock.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ScopeModel } from '@osf/features/settings/tokens/models';
2+
3+
export const MOCK_SCOPES: ScopeModel[] = [
4+
{ id: 'read', description: 'Read access' },
5+
{ id: 'write', description: 'Write access' },
6+
{ id: 'delete', description: 'Delete access' },
7+
];

src/app/shared/mocks/token.mock.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { TokenModel } from '@osf/features/settings/tokens/models';
2+
3+
export const MOCK_TOKEN: TokenModel = {
4+
id: '1',
5+
name: 'Test Token',
6+
scopes: ['read', 'write'],
7+
};

src/testing/mocks/toast.service.mock.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import { ToastService } from '@osf/shared/services';
33
export const ToastServiceMock = {
44
provide: ToastService,
55
useValue: {
6-
success: jest.fn(),
7-
error: jest.fn(),
8-
info: jest.fn(),
9-
warning: jest.fn(),
6+
showSuccess: jest.fn(),
7+
showError: jest.fn(),
8+
showWarn: jest.fn(),
109
},
1110
};

0 commit comments

Comments
 (0)