11import { Store } from '@ngxs/store' ;
22
3- import { TranslatePipe } from '@ngx-translate/core' ;
4- import { MockPipe , MockProvider } from 'ng-mocks' ;
3+ import { TranslateService } from '@ngx-translate/core' ;
4+ import { MockProvider } from 'ng-mocks' ;
55
66import { DialogService , DynamicDialogRef } from 'primeng/dynamicdialog' ;
77
@@ -12,14 +12,17 @@ import { ReactiveFormsModule } from '@angular/forms';
1212import { ActivatedRoute , Router } from '@angular/router' ;
1313
1414import { TokenCreatedDialogComponent } from '@osf/features/settings/tokens/components' ;
15+ import { InputLimits } from '@osf/shared/constants' ;
1516import { MOCK_STORE , TranslateServiceMock } from '@shared/mocks' ;
1617import { ToastService } from '@shared/services' ;
1718
1819import { TokenFormControls , TokenModel } from '../../models' ;
19- import { TokensSelectors } from '../../store' ;
20+ import { CreateToken , TokensSelectors } from '../../store' ;
2021
2122import { TokenAddEditFormComponent } from './token-add-edit-form.component' ;
2223
24+ import { OSFTestingStoreModule } from '@testing/osf.testing.module' ;
25+
2326describe ( 'TokenAddEditFormComponent' , ( ) => {
2427 let component : TokenAddEditFormComponent ;
2528 let fixture : ComponentFixture < TokenAddEditFormComponent > ;
@@ -32,9 +35,7 @@ describe('TokenAddEditFormComponent', () => {
3235 const MOCK_TOKEN : TokenModel = {
3336 id : '1' ,
3437 name : 'Test Token' ,
35- tokenId : 'token1' ,
3638 scopes : [ 'read' , 'write' ] ,
37- ownerId : 'user1' ,
3839 } ;
3940
4041 const MOCK_SCOPES = [
@@ -78,7 +79,7 @@ describe('TokenAddEditFormComponent', () => {
7879 } ;
7980
8081 await TestBed . configureTestingModule ( {
81- imports : [ TokenAddEditFormComponent , MockPipe ( TranslatePipe ) , ReactiveFormsModule ] ,
82+ imports : [ TokenAddEditFormComponent , ReactiveFormsModule , OSFTestingStoreModule ] ,
8283 providers : [
8384 TranslateServiceMock ,
8485 MockProvider ( Store , MOCK_STORE ) ,
@@ -107,6 +108,34 @@ describe('TokenAddEditFormComponent', () => {
107108 expect ( component . tokenForm . get ( TokenFormControls . Scopes ) ?. value ) . toEqual ( MOCK_TOKEN . scopes ) ;
108109 } ) ;
109110
111+ it ( 'should call patchValue with initial values on ngOnInit' , ( ) => {
112+ fixture . componentRef . setInput ( 'initialValues' , MOCK_TOKEN ) ;
113+ const patchSpy = jest . spyOn ( component . tokenForm , 'patchValue' ) ;
114+
115+ component . ngOnInit ( ) ;
116+
117+ expect ( patchSpy ) . toHaveBeenCalledWith (
118+ expect . objectContaining ( {
119+ [ TokenFormControls . TokenName ] : MOCK_TOKEN . name ,
120+ [ TokenFormControls . Scopes ] : MOCK_TOKEN . scopes ,
121+ } )
122+ ) ;
123+ } ) ;
124+
125+ it ( 'should not patch form when initialValues are not provided' , ( ) => {
126+ fixture . componentRef . setInput ( 'initialValues' , null ) ;
127+
128+ component . tokenForm . patchValue ( {
129+ [ TokenFormControls . TokenName ] : 'Existing Name' ,
130+ [ TokenFormControls . Scopes ] : [ 'read' ] ,
131+ } ) ;
132+
133+ component . ngOnInit ( ) ;
134+
135+ expect ( component . tokenForm . get ( TokenFormControls . TokenName ) ?. value ) . toBe ( 'Existing Name' ) ;
136+ expect ( component . tokenForm . get ( TokenFormControls . Scopes ) ?. value ) . toEqual ( [ 'read' ] ) ;
137+ } ) ;
138+
110139 it ( 'should not submit when form is invalid' , ( ) => {
111140 component . tokenForm . patchValue ( {
112141 [ TokenFormControls . TokenName ] : '' ,
@@ -115,11 +144,13 @@ describe('TokenAddEditFormComponent', () => {
115144
116145 const markAllAsTouchedSpy = jest . spyOn ( component . tokenForm , 'markAllAsTouched' ) ;
117146 const markAsDirtySpy = jest . spyOn ( component . tokenForm . get ( TokenFormControls . TokenName ) ! , 'markAsDirty' ) ;
147+ const markScopesAsDirtySpy = jest . spyOn ( component . tokenForm . get ( TokenFormControls . Scopes ) ! , 'markAsDirty' ) ;
118148
119149 component . handleSubmitForm ( ) ;
120150
121151 expect ( markAllAsTouchedSpy ) . toHaveBeenCalled ( ) ;
122152 expect ( markAsDirtySpy ) . toHaveBeenCalled ( ) ;
153+ expect ( markScopesAsDirtySpy ) . toHaveBeenCalled ( ) ;
123154 expect ( MOCK_STORE . dispatch ) . not . toHaveBeenCalled ( ) ;
124155 } ) ;
125156
@@ -145,32 +176,60 @@ describe('TokenAddEditFormComponent', () => {
145176 expect ( MOCK_STORE . dispatch ) . not . toHaveBeenCalled ( ) ;
146177 } ) ;
147178
148- it ( 'should create token when not in edit mode' , ( ) => {
149- fixture . componentRef . setInput ( 'isEditMode' , false ) ;
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+
150209 component . tokenForm . patchValue ( {
151210 [ TokenFormControls . TokenName ] : 'Test Token' ,
152- [ TokenFormControls . Scopes ] : [ 'read' , 'write' ] ,
211+ [ TokenFormControls . Scopes ] : undefined as unknown as string [ ] ,
153212 } ) ;
154213
155- MOCK_STORE . dispatch . mockReturnValue ( of ( undefined ) ) ;
214+ expect ( component . tokenForm . valid ) . toBe ( true ) ;
156215
157216 component . handleSubmitForm ( ) ;
158217
159- expect ( MOCK_STORE . dispatch ) . toHaveBeenCalled ( ) ;
218+ expect ( MOCK_STORE . dispatch ) . not . toHaveBeenCalled ( ) ;
160219 } ) ;
161220
162- it ( 'should update token when in edit mode' , ( ) => {
163- fixture . componentRef . setInput ( 'isEditMode' , true ) ;
221+ it ( 'should create token when not in edit mode' , ( ) => {
222+ fixture . componentRef . setInput ( 'isEditMode' , false ) ;
164223 component . tokenForm . patchValue ( {
165- [ TokenFormControls . TokenName ] : 'Updated Token' ,
166- [ TokenFormControls . Scopes ] : [ 'read' , 'write' , 'delete' ] ,
224+ [ TokenFormControls . TokenName ] : 'Test Token' ,
225+ [ TokenFormControls . Scopes ] : [ 'read' , 'write' ] ,
167226 } ) ;
168227
169228 MOCK_STORE . dispatch . mockReturnValue ( of ( undefined ) ) ;
170229
171230 component . handleSubmitForm ( ) ;
172231
173- expect ( MOCK_STORE . dispatch ) . toHaveBeenCalled ( ) ;
232+ expect ( MOCK_STORE . dispatch ) . toHaveBeenCalledWith ( new CreateToken ( 'Test Token' , [ 'read' , 'write' ] ) ) ;
174233 } ) ;
175234
176235 it ( 'should show success toast and close dialog after creating token' , ( ) => {
@@ -188,46 +247,35 @@ describe('TokenAddEditFormComponent', () => {
188247 expect ( dialogRef . close ) . toHaveBeenCalled ( ) ;
189248 } ) ;
190249
191- it ( 'should show success toast and navigate after updating token ' , ( ) => {
192- fixture . componentRef . setInput ( 'isEditMode' , true ) ;
250+ it ( 'should open created dialog with new token name and value after create ' , ( ) => {
251+ fixture . componentRef . setInput ( 'isEditMode' , false ) ;
193252 component . tokenForm . patchValue ( {
194- [ TokenFormControls . TokenName ] : 'Updated Token' ,
253+ [ TokenFormControls . TokenName ] : 'Test Token' ,
195254 [ TokenFormControls . Scopes ] : [ 'read' , 'write' ] ,
196255 } ) ;
197256
257+ const showDialogSpy = jest . spyOn ( component , 'showTokenCreatedDialog' ) ;
258+
198259 MOCK_STORE . dispatch . mockReturnValue ( of ( undefined ) ) ;
199260
200261 component . handleSubmitForm ( ) ;
201262
202- expect ( toastService . showSuccess ) . toHaveBeenCalledWith ( 'settings.tokens.toastMessage.successEdit' ) ;
203- expect ( router . navigate ) . toHaveBeenCalledWith ( [ 'settings/tokens' ] ) ;
263+ expect ( showDialogSpy ) . toHaveBeenCalledWith ( MOCK_TOKEN . name , MOCK_TOKEN . id ) ;
204264 } ) ;
205265
206- it ( 'should show token created dialog with new token data after successful creation ' , ( ) => {
207- fixture . componentRef . setInput ( 'isEditMode' , false ) ;
266+ it ( 'should show success toast and navigate after updating token ' , ( ) => {
267+ fixture . componentRef . setInput ( 'isEditMode' , true ) ;
208268 component . tokenForm . patchValue ( {
209- [ TokenFormControls . TokenName ] : 'Test Token' ,
269+ [ TokenFormControls . TokenName ] : 'Updated Token' ,
210270 [ TokenFormControls . Scopes ] : [ 'read' , 'write' ] ,
211271 } ) ;
212272
213273 MOCK_STORE . dispatch . mockReturnValue ( of ( undefined ) ) ;
214274
215275 component . handleSubmitForm ( ) ;
216276
217- expect ( dialogService . open ) . toHaveBeenCalledWith (
218- TokenCreatedDialogComponent ,
219- expect . objectContaining ( {
220- width : '500px' ,
221- header : 'settings.tokens.createdDialog.title' ,
222- closeOnEscape : true ,
223- modal : true ,
224- closable : true ,
225- data : {
226- tokenName : MOCK_TOKEN . name ,
227- tokenValue : MOCK_TOKEN . tokenId ,
228- } ,
229- } )
230- ) ;
277+ expect ( toastService . showSuccess ) . toHaveBeenCalledWith ( 'settings.tokens.toastMessage.successEdit' ) ;
278+ expect ( router . navigate ) . toHaveBeenCalledWith ( [ 'settings/tokens' ] ) ;
231279 } ) ;
232280
233281 it ( 'should open dialog with correct configuration' , ( ) => {
@@ -252,6 +300,31 @@ describe('TokenAddEditFormComponent', () => {
252300 ) ;
253301 } ) ;
254302
303+ it ( 'should use TranslateService.instant for dialog header' , ( ) => {
304+ const translate = TestBed . inject ( TranslateService ) as unknown as { instant : jest . Mock } ;
305+ component . showTokenCreatedDialog ( 'Name' , 'Value' ) ;
306+ expect ( translate . instant ) . toHaveBeenCalledWith ( 'settings.tokens.createdDialog.title' ) ;
307+ } ) ;
308+
309+ it ( 'should read tokens via selectSignal after create' , ( ) => {
310+ fixture . componentRef . setInput ( 'isEditMode' , false ) ;
311+ component . tokenForm . patchValue ( {
312+ [ TokenFormControls . TokenName ] : 'Test Token' ,
313+ [ TokenFormControls . Scopes ] : [ 'read' ] ,
314+ } ) ;
315+
316+ const selectSpy = jest . spyOn ( MOCK_STORE , 'selectSignal' ) ;
317+ MOCK_STORE . dispatch . mockReturnValue ( of ( undefined ) ) ;
318+
319+ component . handleSubmitForm ( ) ;
320+
321+ expect ( selectSpy ) . toHaveBeenCalledWith ( TokensSelectors . getTokens ) ;
322+ } ) ;
323+
324+ it ( 'should expose the same inputLimits as InputLimits.fullName' , ( ) => {
325+ expect ( component . inputLimits ) . toBe ( InputLimits . fullName ) ;
326+ } ) ;
327+
255328 it ( 'should require token name' , ( ) => {
256329 const tokenNameControl = component . tokenForm . get ( TokenFormControls . TokenName ) ;
257330 expect ( tokenNameControl ?. hasError ( 'required' ) ) . toBe ( true ) ;
@@ -274,4 +347,30 @@ describe('TokenAddEditFormComponent', () => {
274347 it ( 'should have correct input limits for token name' , ( ) => {
275348 expect ( component . inputLimits ) . toBeDefined ( ) ;
276349 } ) ;
350+
351+ it ( 'should expose tokenId from route params' , ( ) => {
352+ expect ( component . tokenId ( ) ) . toBe ( MOCK_TOKEN . id ) ;
353+ } ) ;
354+
355+ it ( 'should expose scopes from store via tokenScopes signal' , ( ) => {
356+ expect ( component . tokenScopes ( ) ) . toEqual ( MOCK_SCOPES ) ;
357+ } ) ;
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+ } ) ;
277376} ) ;
0 commit comments