1
1
import { describe , it , expect , vi , beforeEach , afterEach } from 'vitest' ;
2
2
import { ClientSDK } from '../client' ;
3
- import { CoreSDK , GenericRequestData } from '@sitecore-marketplace-sdk/core' ;
3
+ import { CoreSDK , GenericRequestData } from '@sitecore-marketplace-sdk/core' ;
4
4
import { StateManager } from '../state' ;
5
5
import { logger } from '../logger' ;
6
6
@@ -15,7 +15,7 @@ describe('ClientSDK', () => {
15
15
origin : 'https://host.app' ,
16
16
target : window . parent ,
17
17
targetOrigin : 'https://target.app' ,
18
- selfOrigin : 'https://self.app'
18
+ selfOrigin : 'https://self.app' ,
19
19
} ;
20
20
21
21
const mockData = {
@@ -59,7 +59,7 @@ describe('ClientSDK', () => {
59
59
target : window . parent ,
60
60
targetOrigin : 'https://target.app' ,
61
61
selfOrigin : 'https://self.app' ,
62
- modules : [ { namespace : 'testModule' , invokeOperation : vi . fn ( ) } ]
62
+ modules : [ { namespace : 'testModule' , invokeOperation : vi . fn ( ) } ] ,
63
63
} ;
64
64
65
65
client = await ClientSDK . init ( clientConfig ) ;
@@ -111,7 +111,7 @@ describe('ClientSDK', () => {
111
111
it ( 'should call registerHandlers if config.events is defined' , async ( ) => {
112
112
const clientConfig = {
113
113
...config ,
114
- events : { onPageContextUpdate : vi . fn ( ) }
114
+ events : { onPageContextUpdate : vi . fn ( ) } ,
115
115
} ;
116
116
const registerHandlersSpy = vi . spyOn ( ClientSDK . prototype as any , 'registerHandlers' ) ;
117
117
client = await ClientSDK . init ( clientConfig ) ;
@@ -130,7 +130,7 @@ describe('ClientSDK', () => {
130
130
const payload = { some : 'data' } ;
131
131
const clientConfig = {
132
132
...config ,
133
- events : { onPageContextUpdate : vi . fn ( ) }
133
+ events : { onPageContextUpdate : vi . fn ( ) } ,
134
134
} ;
135
135
( CoreSDK as any ) . mockImplementation ( ( ) => ( {
136
136
connect : vi . fn ( ) ,
@@ -171,6 +171,36 @@ describe('ClientSDK', () => {
171
171
expect ( logger . debug ) . toHaveBeenCalledWith ( 'onPageContextUpdate event listener is not set.' ) ;
172
172
} ) ;
173
173
174
+ it ( 'should use key as hashedKey and call handleSubscription when subscribe is true' , async ( ) => {
175
+ client = await ClientSDK . init ( config ) ;
176
+ const key = 'host.state' ;
177
+ const options = { subscribe : true } ;
178
+ const handleSubscriptionSpy = vi
179
+ . spyOn ( client as any , 'handleSubscription' )
180
+ . mockReturnValue ( ( ) => { } ) ;
181
+ const generateKeyWithHashSpy = vi . spyOn ( client as any , 'generateKeyWithHash' ) ;
182
+
183
+ await client . query ( key , options ) ;
184
+
185
+ expect ( handleSubscriptionSpy ) . toHaveBeenCalledWith ( key , undefined , undefined ) ;
186
+ expect ( generateKeyWithHashSpy ) . not . toHaveBeenCalled ( ) ;
187
+ } ) ;
188
+
189
+ it ( 'should generate hashedKey and NOT call handleSubscription when subscribe is false' , async ( ) => {
190
+ client = await ClientSDK . init ( config ) ;
191
+ const key = 'host.state' ;
192
+ const options = { subscribe : false } ;
193
+ const handleSubscriptionSpy = vi . spyOn ( client as any , 'handleSubscription' ) ;
194
+ const generateKeyWithHashSpy = vi
195
+ . spyOn ( client as any , 'generateKeyWithHash' )
196
+ . mockResolvedValue ( 'hashed-key' ) ;
197
+
198
+ await client . query ( key , options ) ;
199
+
200
+ expect ( generateKeyWithHashSpy ) . toHaveBeenCalledWith ( key , options ) ;
201
+ expect ( handleSubscriptionSpy ) . not . toHaveBeenCalled ( ) ;
202
+ } ) ;
203
+
174
204
it ( 'should generate unique keys for different query options' , async ( ) => {
175
205
const key = 'host.user' ;
176
206
const options1 = { subscribe : true } ;
@@ -183,8 +213,8 @@ describe('ClientSDK', () => {
183
213
} ) ;
184
214
185
215
it ( 'should update query state with unique keys' , async ( ) => {
186
- const key = 'host.user ' ;
187
- const options = { subscribe : true } ;
216
+ const key = 'xmc.request ' ;
217
+ const options = { subscribe : false } ;
188
218
const hashedKey = await client [ 'generateKeyWithHash' ] ( key , options ) ;
189
219
190
220
await client . query ( key , options ) ;
@@ -194,16 +224,19 @@ describe('ClientSDK', () => {
194
224
} ) ;
195
225
} ) ;
196
226
197
- it ( 'should handle query success with unique keys' , async ( ) => {
227
+ it ( 'should handle query success with unique keys when subscribe is false ' , async ( ) => {
198
228
client = await ClientSDK . init ( config ) ;
199
- const key = 'host.request' ;
200
- const options = { subscribe : true , params : {
229
+ const key = 'xmc.request' ;
230
+ const options = {
231
+ subscribe : false ,
232
+ params : {
201
233
path : '/api/resource' ,
202
234
method : 'GET' ,
203
235
headers : { 'Content-Type' : 'application/json' } ,
204
236
query : { id : '123' } ,
205
- requiresAuth : true
206
- } } ;
237
+ requiresAuth : true ,
238
+ } ,
239
+ } ;
207
240
const hashedKey = await client [ 'generateKeyWithHash' ] ( key , options ) ;
208
241
const responseData = { id : 1 , name : 'User' } ;
209
242
@@ -223,6 +256,38 @@ describe('ClientSDK', () => {
223
256
expect ( result . data ) . toEqual ( responseData ) ;
224
257
} ) ;
225
258
259
+ it ( 'should handle query success with event key when subscribe is true' , async ( ) => {
260
+ client = await ClientSDK . init ( config ) ;
261
+ const key = 'host.request' ;
262
+ const options = {
263
+ subscribe : true ,
264
+ params : {
265
+ path : '/api/resource' ,
266
+ method : 'GET' ,
267
+ headers : { 'Content-Type' : 'application/json' } ,
268
+ query : { id : '123' } ,
269
+ requiresAuth : true ,
270
+ } ,
271
+ } ;
272
+ const hashedKey = key ;
273
+ const responseData = { id : 1 , name : 'User' } ;
274
+
275
+ ( CoreSDK . prototype . request as vi . Mock ) . mockResolvedValue ( responseData ) ;
276
+ ( StateManager . prototype . getQueryState as vi . Mock ) . mockReturnValue ( {
277
+ status : 'success' ,
278
+ data : responseData ,
279
+ } ) ;
280
+
281
+ const result = await client . query ( key , options ) ;
282
+
283
+ expect ( StateManager . prototype . updateQueryState ) . toHaveBeenCalledWith ( hashedKey , {
284
+ status : 'success' ,
285
+ data : responseData ,
286
+ } ) ;
287
+ expect ( StateManager . prototype . getQueryState ) . toHaveBeenCalledWith ( hashedKey ) ;
288
+ expect ( result . data ) . toEqual ( responseData ) ;
289
+ } ) ;
290
+
226
291
it ( 'should handle query error with correct state update and logging' , async ( ) => {
227
292
const key = 'host.request' ;
228
293
const options = { subscribe : true } ;
@@ -251,8 +316,8 @@ describe('ClientSDK', () => {
251
316
252
317
it ( 'should handle query success' , async ( ) => {
253
318
StateManager . prototype . getQueryState = vi
254
- . fn ( )
255
- . mockReturnValue ( { status : 'idle' , subscriptionCount : 0 , data : mockData } ) ;
319
+ . fn ( )
320
+ . mockReturnValue ( { status : 'idle' , subscriptionCount : 0 , data : mockData } ) ;
256
321
CoreSDK . prototype . request = vi . fn ( ) . mockReturnValue ( mockData ) ;
257
322
client = await ClientSDK . init ( config ) ;
258
323
@@ -274,7 +339,7 @@ describe('ClientSDK', () => {
274
339
} ) ;
275
340
276
341
it ( 'should handle mutation success' , async ( ) => {
277
- const mutationKey = 'host.request' ;
342
+ const mutationKey = 'host.request' ;
278
343
const params : GenericRequestData = { path : '/some-path' , requiresAuth : true } ;
279
344
const timeout = 5000 ;
280
345
CoreSDK . prototype . request = vi . fn ( ) . mockReturnValue ( mockData ) ;
@@ -284,9 +349,9 @@ describe('ClientSDK', () => {
284
349
285
350
expect ( result ) . toEqual ( mockData ) ;
286
351
expect ( logger . debug ) . toHaveBeenCalledWith (
287
- `Mutation (${ mutationKey } ) initiated with params:` ,
288
- params ,
289
- `timeoutMs: ${ timeout } ` ,
352
+ `Mutation (${ mutationKey } ) initiated with params:` ,
353
+ params ,
354
+ `timeoutMs: ${ timeout } ` ,
290
355
) ;
291
356
expect ( logger . info ) . toHaveBeenCalledWith ( `Mutation (${ mutationKey } ) success:` , mockData ) ;
292
357
} ) ;
@@ -305,7 +370,7 @@ describe('ClientSDK', () => {
305
370
it ( 'should clean up all queries on destroy' , ( ) => {
306
371
StateManager . prototype . getQueryKeys = vi . fn ( ) . mockReturnValue ( [ 'query1' , 'query2' ] ) ;
307
372
const clientSDK = new ClientSDK ( config ) ;
308
- const unsubscribeSpy = vi . spyOn ( clientSDK as any , 'unsubscribe ' ) ;
373
+ const unsubscribeSpy = vi . spyOn ( clientSDK as any , '_unsubscribe ' ) ;
309
374
310
375
clientSDK . destroy ( ) ;
311
376
@@ -355,7 +420,9 @@ describe('ClientSDK', () => {
355
420
} ) ;
356
421
357
422
// Test with missing URL
358
- await expect ( client . navigateToExternalUrl ( '' ) ) . rejects . toThrow ( 'URL is required for navigateToExternalUrl' ) ;
423
+ await expect ( client . navigateToExternalUrl ( '' ) ) . rejects . toThrow (
424
+ 'URL is required for navigateToExternalUrl' ,
425
+ ) ;
359
426
} ) ;
360
427
361
428
it ( 'should unsubscribe correctly' , async ( ) => {
@@ -371,9 +438,176 @@ describe('ClientSDK', () => {
371
438
StateManager . prototype . removeQuery = vi . fn ( ) ;
372
439
373
440
client = await ClientSDK . init ( config ) ;
374
- client [ 'unsubscribe ' ] ( hashedKey ) ;
441
+ client [ '_unsubscribe ' ] ( hashedKey ) ;
375
442
376
443
expect ( StateManager . prototype . decrementSubscriptionCount ) . toHaveBeenCalledWith ( hashedKey ) ;
377
444
expect ( StateManager . prototype . removeQuery ) . toHaveBeenCalledWith ( hashedKey ) ;
378
445
} ) ;
379
- } ) ;
446
+
447
+ it ( 'should handle subscription: call onSuccess and onError, manage subscription count' , ( ) => {
448
+ const clientSDK = new ClientSDK ( config ) ;
449
+ const hashedKey = 'test.key' ;
450
+ const mockOnSuccess = vi . fn ( ) ;
451
+ const mockOnError = vi . fn ( ) ;
452
+ // Mock stateManager methods
453
+ const subscribeMock = vi . fn ( ( _ , cb ) => {
454
+ // Simulate state change with data
455
+ cb ( { data : 'test-data' , error : undefined } ) ;
456
+ // Simulate state change with error
457
+ cb ( { data : undefined , error : new Error ( 'test-error' ) } ) ;
458
+ return vi . fn ( ) ; // unsubscribe function
459
+ } ) ;
460
+ const incrementMock = vi . fn ( ) ;
461
+ const getCountMock = vi . fn ( ) . mockReturnValue ( 1 ) ;
462
+ const updateQueryStateMock = vi . fn ( ) ;
463
+ // Mock coreSdk.on to return an unsubscribe function
464
+ clientSDK [ 'coreSdk' ] = {
465
+ on : vi . fn ( ) . mockReturnValue ( vi . fn ( ) ) ,
466
+ } as any ;
467
+ clientSDK [ 'stateManager' ] = {
468
+ subscribe : subscribeMock ,
469
+ incrementSubscriptionCount : incrementMock ,
470
+ getSubscriptionCount : getCountMock ,
471
+ updateQueryState : updateQueryStateMock ,
472
+ } as any ;
473
+ // Call handleSubscription
474
+ const unsubscribe = ( clientSDK as any ) . handleSubscription (
475
+ hashedKey ,
476
+ mockOnSuccess ,
477
+ mockOnError ,
478
+ ) ;
479
+ // Should call subscribe and increment
480
+ expect ( subscribeMock ) . toHaveBeenCalledWith ( hashedKey , expect . any ( Function ) ) ;
481
+ expect ( incrementMock ) . toHaveBeenCalledWith ( hashedKey ) ;
482
+ expect ( getCountMock ) . toHaveBeenCalledWith ( hashedKey ) ;
483
+ // Should call onSuccess and onError
484
+ expect ( mockOnSuccess ) . toHaveBeenCalledWith ( 'test-data' ) ;
485
+ expect ( mockOnError ) . toHaveBeenCalledWith ( new Error ( 'test-error' ) ) ;
486
+ // Should return an unsubscribe function
487
+ expect ( typeof unsubscribe ) . toBe ( 'function' ) ;
488
+ } ) ;
489
+
490
+ it ( 'should not call coreSdk.on if subscription count > 1' , ( ) => {
491
+ const clientSDK = new ClientSDK ( config ) ;
492
+ const hashedKey = 'test.key' ;
493
+ const mockOnSuccess = vi . fn ( ) ;
494
+ const mockOnError = vi . fn ( ) ;
495
+ // Mock stateManager methods
496
+ const subscribeMock = vi . fn ( ) ;
497
+ const incrementMock = vi . fn ( ) ;
498
+ const getCountMock = vi . fn ( ) . mockReturnValue ( 2 ) ; // > 1
499
+ const updateQueryStateMock = vi . fn ( ) ;
500
+ clientSDK [ 'coreSdk' ] = {
501
+ on : vi . fn ( ) ,
502
+ } as any ;
503
+ clientSDK [ 'stateManager' ] = {
504
+ subscribe : subscribeMock ,
505
+ incrementSubscriptionCount : incrementMock ,
506
+ getSubscriptionCount : getCountMock ,
507
+ updateQueryState : updateQueryStateMock ,
508
+ } as any ;
509
+ ( clientSDK as any ) . handleSubscription ( hashedKey , mockOnSuccess , mockOnError ) ;
510
+ expect ( clientSDK [ 'coreSdk' ] . on ) . not . toHaveBeenCalled ( ) ;
511
+ } ) ;
512
+
513
+ it ( 'should call only onSuccess if state has data, only onError if state has error, and neither if state is empty' , ( ) => {
514
+ const clientSDK = new ClientSDK ( config ) ;
515
+ const hashedKey = 'test.key' ;
516
+ const mockOnSuccess = vi . fn ( ) ;
517
+ const mockOnError = vi . fn ( ) ;
518
+ let stateCallback : any ;
519
+ const subscribeMock = vi . fn ( ( _ , cb ) => {
520
+ stateCallback = cb ;
521
+ return vi . fn ( ) ;
522
+ } ) ;
523
+ const incrementMock = vi . fn ( ) ;
524
+ const getCountMock = vi . fn ( ) . mockReturnValue ( 2 ) ;
525
+ const updateQueryStateMock = vi . fn ( ) ;
526
+ clientSDK [ 'coreSdk' ] = { on : vi . fn ( ) } as any ;
527
+ clientSDK [ 'stateManager' ] = {
528
+ subscribe : subscribeMock ,
529
+ incrementSubscriptionCount : incrementMock ,
530
+ getSubscriptionCount : getCountMock ,
531
+ updateQueryState : updateQueryStateMock ,
532
+ } as any ;
533
+ ( clientSDK as any ) . handleSubscription ( hashedKey , mockOnSuccess , mockOnError ) ;
534
+ // Only data
535
+ stateCallback ( { data : 'data' , error : undefined } ) ;
536
+ expect ( mockOnSuccess ) . toHaveBeenCalledWith ( 'data' ) ;
537
+ // Only error
538
+ stateCallback ( { data : undefined , error : new Error ( 'err' ) } ) ;
539
+ expect ( mockOnError ) . toHaveBeenCalledWith ( new Error ( 'err' ) ) ;
540
+ // Neither
541
+ stateCallback ( { data : undefined , error : undefined } ) ;
542
+ // Should not throw
543
+ } ) ;
544
+
545
+ it ( 'should not throw if onSuccess/onError are not provided' , ( ) => {
546
+ const clientSDK = new ClientSDK ( config ) ;
547
+ const hashedKey = 'test.key' ;
548
+ let stateCallback : any ;
549
+ const subscribeMock = vi . fn ( ( _ , cb ) => {
550
+ stateCallback = cb ;
551
+ return vi . fn ( ) ;
552
+ } ) ;
553
+ const incrementMock = vi . fn ( ) ;
554
+ const getCountMock = vi . fn ( ) . mockReturnValue ( 2 ) ;
555
+ const updateQueryStateMock = vi . fn ( ) ;
556
+ clientSDK [ 'coreSdk' ] = { on : vi . fn ( ) } as any ;
557
+ clientSDK [ 'stateManager' ] = {
558
+ subscribe : subscribeMock ,
559
+ incrementSubscriptionCount : incrementMock ,
560
+ getSubscriptionCount : getCountMock ,
561
+ updateQueryState : updateQueryStateMock ,
562
+ } as any ;
563
+ ( clientSDK as any ) . handleSubscription ( hashedKey ) ;
564
+ expect ( ( ) => stateCallback ( { data : 'data' , error : undefined } ) ) . not . toThrow ( ) ;
565
+ expect ( ( ) => stateCallback ( { data : undefined , error : new Error ( 'err' ) } ) ) . not . toThrow ( ) ;
566
+ } ) ;
567
+
568
+ it ( 'should call both stateChangeUnsubscribe and _unsubscribe on returned unsubscribe' , ( ) => {
569
+ const clientSDK = new ClientSDK ( config ) ;
570
+ const hashedKey = 'test.key' ;
571
+ const stateChangeUnsubscribe = vi . fn ( ) ;
572
+ const subscribeMock = vi . fn ( ( ) => stateChangeUnsubscribe ) ;
573
+ const incrementMock = vi . fn ( ) ;
574
+ const getCountMock = vi . fn ( ) . mockReturnValue ( 2 ) ;
575
+ const updateQueryStateMock = vi . fn ( ) ;
576
+ const _unsubscribeMock = vi . spyOn ( clientSDK as any , '_unsubscribe' ) ;
577
+ clientSDK [ 'coreSdk' ] = { on : vi . fn ( ) } as any ;
578
+ clientSDK [ 'stateManager' ] = {
579
+ subscribe : subscribeMock ,
580
+ incrementSubscriptionCount : incrementMock ,
581
+ getSubscriptionCount : getCountMock ,
582
+ updateQueryState : updateQueryStateMock ,
583
+ getQueryState : vi . fn ( ) . mockReturnValue ( { } ) ,
584
+ decrementSubscriptionCount : vi . fn ( ) ,
585
+ } as any ;
586
+ const unsub = ( clientSDK as any ) . handleSubscription ( hashedKey ) ;
587
+ unsub ( ) ;
588
+ expect ( stateChangeUnsubscribe ) . toHaveBeenCalled ( ) ;
589
+ expect ( _unsubscribeMock ) . toHaveBeenCalledWith ( hashedKey ) ;
590
+ } ) ;
591
+
592
+ it ( 'should store and call coreSdk.on unsubscribe when subscription count is 1' , ( ) => {
593
+ const clientSDK = new ClientSDK ( config ) ;
594
+ const hashedKey = 'test.key' ;
595
+ const subscribeMock = vi . fn ( ( ) => vi . fn ( ) ) ;
596
+ const incrementMock = vi . fn ( ) ;
597
+ const getCountMock = vi . fn ( ) . mockReturnValue ( 1 ) ;
598
+ const updateQueryStateMock = vi . fn ( ) ;
599
+ const coreUnsubscribe = vi . fn ( ) ;
600
+ clientSDK [ 'coreSdk' ] = {
601
+ on : vi . fn ( ) . mockReturnValue ( coreUnsubscribe ) ,
602
+ } as any ;
603
+ clientSDK [ 'stateManager' ] = {
604
+ subscribe : subscribeMock ,
605
+ incrementSubscriptionCount : incrementMock ,
606
+ getSubscriptionCount : getCountMock ,
607
+ updateQueryState : updateQueryStateMock ,
608
+ } as any ;
609
+ ( clientSDK as any ) . handleSubscription ( hashedKey ) ;
610
+ expect ( clientSDK [ 'coreSdk' ] . on ) . toHaveBeenCalledWith ( hashedKey , expect . any ( Function ) ) ;
611
+ expect ( updateQueryStateMock ) . toHaveBeenCalledWith ( hashedKey , { unsubscribe : coreUnsubscribe } ) ;
612
+ } ) ;
613
+ } ) ;
0 commit comments