@zeix/cause-effect 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,719 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import {
3
+ computed,
4
+ effect,
5
+ isStore,
6
+ type StoreAddEvent,
7
+ type StoreChangeEvent,
8
+ type StoreRemoveEvent,
9
+ state,
10
+ store,
11
+ UNSET,
12
+ } from '..'
13
+
14
+ describe('store', () => {
15
+ describe('creation and basic operations', () => {
16
+ test('creates a store with initial values', () => {
17
+ const user = store({ name: 'Hannah', email: 'hannah@example.com' })
18
+
19
+ expect(user.name.get()).toBe('Hannah')
20
+ expect(user.email.get()).toBe('hannah@example.com')
21
+ })
22
+
23
+ test('has Symbol.toStringTag of Store', () => {
24
+ const s = store({ a: 1 })
25
+ expect(s[Symbol.toStringTag]).toBe('Store')
26
+ })
27
+
28
+ test('isStore identifies store instances correctly', () => {
29
+ const s = store({ a: 1 })
30
+ const st = state(1)
31
+ const c = computed(() => 1)
32
+
33
+ expect(isStore(s)).toBe(true)
34
+ expect(isStore(st)).toBe(false)
35
+ expect(isStore(c)).toBe(false)
36
+ expect(isStore({})).toBe(false)
37
+ expect(isStore(null)).toBe(false)
38
+ })
39
+
40
+ test('get() returns the complete store value', () => {
41
+ const user = store({ name: 'Hannah', email: 'hannah@example.com' })
42
+
43
+ expect(user.get()).toEqual({
44
+ name: 'Hannah',
45
+ email: 'hannah@example.com',
46
+ })
47
+ })
48
+ })
49
+
50
+ describe('proxy data access and modification', () => {
51
+ test('properties can be accessed and modified via signals', () => {
52
+ const user = store({ name: 'Hannah', age: 25 })
53
+
54
+ // Get signals from store proxy
55
+ expect(user.name.get()).toBe('Hannah')
56
+ expect(user.age.get()).toBe(25)
57
+
58
+ // Set values via signals
59
+ user.name.set('Alice')
60
+ user.age.set(30)
61
+
62
+ expect(user.name.get()).toBe('Alice')
63
+ expect(user.age.get()).toBe(30)
64
+ })
65
+
66
+ test('returns undefined for non-existent properties', () => {
67
+ const user = store({ name: 'Hannah' })
68
+
69
+ // @ts-expect-error accessing non-existent property
70
+ expect(user.nonExistent).toBeUndefined()
71
+ })
72
+
73
+ test('supports numeric key access', () => {
74
+ const items = store({ '0': 'first', '1': 'second' })
75
+
76
+ expect(items[0].get()).toBe('first')
77
+ expect(items['0'].get()).toBe('first')
78
+ expect(items[1].get()).toBe('second')
79
+ expect(items['1'].get()).toBe('second')
80
+ })
81
+
82
+ test('can add new properties via add method', () => {
83
+ const user = store<{ name: string; email?: string }>({
84
+ name: 'Hannah',
85
+ })
86
+
87
+ user.add('email', 'hannah@example.com')
88
+
89
+ expect(user.email?.get()).toBe('hannah@example.com')
90
+ expect(user.get()).toEqual({
91
+ name: 'Hannah',
92
+ email: 'hannah@example.com',
93
+ })
94
+ })
95
+
96
+ test('can remove existing properties via remove method', () => {
97
+ const user = store<{ name: string; email?: string }>({
98
+ name: 'Hannah',
99
+ email: 'hannah@example.com',
100
+ })
101
+
102
+ expect(user.email?.get()).toBe('hannah@example.com')
103
+
104
+ user.remove('email')
105
+
106
+ expect(user.email).toBeUndefined()
107
+ expect(user.get()).toEqual({
108
+ name: 'Hannah',
109
+ })
110
+ })
111
+ })
112
+
113
+ describe('nested stores', () => {
114
+ test('creates nested stores for object properties', () => {
115
+ const user = store({
116
+ name: 'Hannah',
117
+ preferences: {
118
+ theme: 'dark',
119
+ notifications: true,
120
+ },
121
+ })
122
+
123
+ expect(isStore(user.preferences)).toBe(true)
124
+ expect(user.preferences.theme?.get()).toBe('dark')
125
+ expect(user.preferences.notifications?.get()).toBe(true)
126
+ })
127
+
128
+ test('nested properties are reactive', () => {
129
+ const user = store({
130
+ preferences: {
131
+ theme: 'dark',
132
+ },
133
+ })
134
+
135
+ user.preferences.theme.set('light')
136
+ expect(user.preferences.theme.get()).toBe('light')
137
+ expect(user.get().preferences.theme).toBe('light')
138
+ })
139
+
140
+ test('deeply nested stores work correctly', () => {
141
+ const config = store({
142
+ ui: {
143
+ theme: {
144
+ colors: {
145
+ primary: 'blue',
146
+ },
147
+ },
148
+ },
149
+ })
150
+
151
+ expect(config.ui.theme.colors.primary.get()).toBe('blue')
152
+ config.ui.theme.colors.primary.set('red')
153
+ expect(config.ui.theme.colors.primary.get()).toBe('red')
154
+ })
155
+ })
156
+
157
+ describe('set() and update() methods', () => {
158
+ test('set() replaces entire store value', () => {
159
+ const user = store({ name: 'Hannah', email: 'hannah@example.com' })
160
+
161
+ user.set({ name: 'Alice', email: 'alice@example.com' })
162
+
163
+ expect(user.get()).toEqual({
164
+ name: 'Alice',
165
+ email: 'alice@example.com',
166
+ })
167
+ })
168
+
169
+ test('update() modifies store using function', () => {
170
+ const user = store({ name: 'Hannah', age: 25 })
171
+
172
+ user.update(prev => ({ ...prev, age: prev.age + 1 }))
173
+
174
+ expect(user.get()).toEqual({
175
+ name: 'Hannah',
176
+ age: 26,
177
+ })
178
+ })
179
+ })
180
+
181
+ describe('iterator protocol', () => {
182
+ test('supports for...of iteration', () => {
183
+ const user = store({ name: 'Hannah', age: 25 })
184
+ const entries: Array<[string, unknown & {}]> = []
185
+
186
+ for (const [key, signal] of user) {
187
+ entries.push([key, signal.get()])
188
+ }
189
+
190
+ expect(entries).toContainEqual(['name', 'Hannah'])
191
+ expect(entries).toContainEqual(['age', 25])
192
+ })
193
+ })
194
+
195
+ describe('change tracking', () => {
196
+ test('tracks size changes', () => {
197
+ const user = store<{ name: string; email?: string }>({
198
+ name: 'Hannah',
199
+ })
200
+
201
+ expect(user.size.get()).toBe(1)
202
+
203
+ user.add('email', 'hannah@example.com')
204
+ expect(user.size.get()).toBe(2)
205
+
206
+ user.remove('email')
207
+ expect(user.size.get()).toBe(1)
208
+ })
209
+
210
+ test('dispatches store-add event on initial creation', async () => {
211
+ let addEvent: StoreAddEvent<{ name: string }> | null = null
212
+ const user = store({ name: 'Hannah' })
213
+
214
+ user.addEventListener('store-add', event => {
215
+ addEvent = event
216
+ })
217
+
218
+ // Wait for the async initial event
219
+ await new Promise(resolve => setTimeout(resolve, 10))
220
+
221
+ expect(addEvent).toBeTruthy()
222
+ // biome-ignore lint/style/noNonNullAssertion: test
223
+ expect(addEvent!.detail).toEqual({ name: 'Hannah' })
224
+ })
225
+
226
+ test('dispatches store-add event for new properties', () => {
227
+ const user = store<{ name: string; email?: string }>({
228
+ name: 'Hannah',
229
+ })
230
+
231
+ let addEvent: StoreAddEvent<{
232
+ name: string
233
+ email?: string
234
+ }> | null = null
235
+ user.addEventListener('store-add', event => {
236
+ addEvent = event
237
+ })
238
+
239
+ user.update(v => ({ ...v, email: 'hannah@example.com' }))
240
+
241
+ expect(addEvent).toBeTruthy()
242
+ // biome-ignore lint/style/noNonNullAssertion: test
243
+ expect(addEvent!.detail).toEqual({
244
+ email: 'hannah@example.com',
245
+ })
246
+ })
247
+
248
+ test('dispatches store-change event for property changes', () => {
249
+ const user = store({ name: 'Hannah' })
250
+
251
+ let changeEvent: StoreChangeEvent<{ name: string }> | null = null
252
+ user.addEventListener('store-change', event => {
253
+ changeEvent = event
254
+ })
255
+
256
+ user.set({ name: 'Alice' })
257
+
258
+ expect(changeEvent).toBeTruthy()
259
+ // biome-ignore lint/style/noNonNullAssertion: test
260
+ expect(changeEvent!.detail).toEqual({
261
+ name: 'Alice',
262
+ })
263
+ })
264
+
265
+ test('dispatches store-change event for signal changes', () => {
266
+ const user = store({ name: 'Hannah' })
267
+
268
+ let changeEvent: StoreChangeEvent<{ name: string }> | null = null
269
+ user.addEventListener('store-change', event => {
270
+ changeEvent = event
271
+ })
272
+
273
+ user.name.set('Bob')
274
+
275
+ expect(changeEvent).toBeTruthy()
276
+ // biome-ignore lint/style/noNonNullAssertion: test
277
+ expect(changeEvent!.detail).toEqual({
278
+ name: 'Bob',
279
+ })
280
+ })
281
+
282
+ test('dispatches store-remove event for removed properties', () => {
283
+ const user = store<{ name: string; email?: string }>({
284
+ name: 'Hannah',
285
+ email: 'hannah@example.com',
286
+ })
287
+
288
+ let removeEvent: StoreRemoveEvent<{
289
+ name: string
290
+ email?: string
291
+ }> | null = null
292
+ user.addEventListener('store-remove', event => {
293
+ removeEvent = event
294
+ })
295
+
296
+ user.remove('email')
297
+
298
+ expect(removeEvent).toBeTruthy()
299
+ // biome-ignore lint/style/noNonNullAssertion: test
300
+ expect(removeEvent!.detail.email).toBe(UNSET)
301
+ })
302
+
303
+ test('dispatches store-add event when using add method', () => {
304
+ const user = store<{ name: string; email?: string }>({
305
+ name: 'Hannah',
306
+ })
307
+
308
+ let addEvent: StoreAddEvent<{
309
+ name: string
310
+ email?: string
311
+ }> | null = null
312
+ user.addEventListener('store-add', event => {
313
+ addEvent = event
314
+ })
315
+
316
+ user.add('email', 'hannah@example.com')
317
+
318
+ expect(addEvent).toBeTruthy()
319
+ // biome-ignore lint/style/noNonNullAssertion: test
320
+ expect(addEvent!.detail).toEqual({
321
+ email: 'hannah@example.com',
322
+ })
323
+ })
324
+
325
+ test('can remove event listeners', () => {
326
+ const user = store({ name: 'Hannah' })
327
+
328
+ let eventCount = 0
329
+ const listener = () => {
330
+ eventCount++
331
+ }
332
+
333
+ user.addEventListener('store-change', listener)
334
+ user.name.set('Alice')
335
+ expect(eventCount).toBe(1)
336
+
337
+ user.removeEventListener('store-change', listener)
338
+ user.name.set('Bob')
339
+ expect(eventCount).toBe(1) // Should not increment
340
+ })
341
+
342
+ test('supports multiple event listeners for the same event', () => {
343
+ const user = store({ name: 'Hannah' })
344
+
345
+ let listener1Called = false
346
+ let listener2Called = false
347
+
348
+ user.addEventListener('store-change', () => {
349
+ listener1Called = true
350
+ })
351
+
352
+ user.addEventListener('store-change', () => {
353
+ listener2Called = true
354
+ })
355
+
356
+ user.name.set('Alice')
357
+
358
+ expect(listener1Called).toBe(true)
359
+ expect(listener2Called).toBe(true)
360
+ })
361
+ })
362
+
363
+ describe('reactivity', () => {
364
+ test('store-level get() is reactive', () => {
365
+ const user = store({ name: 'Hannah', email: 'hannah@example.com' })
366
+ let lastValue = { name: '', email: '' }
367
+
368
+ effect(() => {
369
+ lastValue = user.get()
370
+ })
371
+
372
+ user.name.set('Alice')
373
+
374
+ expect(lastValue).toEqual({
375
+ name: 'Alice',
376
+ email: 'hannah@example.com',
377
+ })
378
+ })
379
+
380
+ test('individual signal reactivity works', () => {
381
+ const user = store({ name: 'Hannah', email: 'hannah@example.com' })
382
+ let lastName = ''
383
+ let nameEffectRuns = 0
384
+
385
+ // Get signal for name property directly
386
+ const nameSignal = user.name
387
+
388
+ effect(() => {
389
+ lastName = nameSignal.get()
390
+ nameEffectRuns++
391
+ })
392
+
393
+ // Change name should trigger effect
394
+ user.name.set('Alice')
395
+ expect(lastName).toBe('Alice')
396
+ expect(nameEffectRuns).toBe(2) // Initial + update
397
+ })
398
+
399
+ test('nested store changes propagate to parent', () => {
400
+ const user = store({
401
+ preferences: {
402
+ theme: 'dark',
403
+ },
404
+ })
405
+ let effectRuns = 0
406
+
407
+ effect(() => {
408
+ user.get() // Watch entire store
409
+ effectRuns++
410
+ })
411
+
412
+ user.preferences.theme.set('light')
413
+ expect(effectRuns).toBe(2) // Initial + nested change
414
+ })
415
+
416
+ test('updates are reactive', () => {
417
+ const user = store<{ name: string; email?: string }>({
418
+ name: 'Hannah',
419
+ })
420
+ let lastValue = {}
421
+ let effectRuns = 0
422
+
423
+ effect(() => {
424
+ lastValue = user.get()
425
+ effectRuns++
426
+ })
427
+
428
+ user.add('email', 'hannah@example.com')
429
+ expect(lastValue).toEqual({
430
+ name: 'Hannah',
431
+ email: 'hannah@example.com',
432
+ })
433
+ expect(effectRuns).toBe(2)
434
+ })
435
+
436
+ test('remove method is reactive', () => {
437
+ const user = store<{ name: string; email?: string }>({
438
+ name: 'Hannah',
439
+ email: 'hannah@example.com',
440
+ })
441
+ let lastValue = {}
442
+ let effectRuns = 0
443
+
444
+ effect(() => {
445
+ lastValue = user.get()
446
+ effectRuns++
447
+ })
448
+
449
+ expect(effectRuns).toBe(1)
450
+
451
+ user.remove('email')
452
+ expect(lastValue).toEqual({
453
+ name: 'Hannah',
454
+ })
455
+ expect(effectRuns).toBe(2)
456
+ })
457
+
458
+ test('add method does not overwrite existing properties', () => {
459
+ const user = store<{ name: string; email?: string }>({
460
+ name: 'Hannah',
461
+ email: 'original@example.com',
462
+ })
463
+
464
+ const originalSize = user.size.get()
465
+ user.add('email', 'new@example.com')
466
+
467
+ expect(user.email?.get()).toBe('original@example.com')
468
+ expect(user.size.get()).toBe(originalSize)
469
+ })
470
+
471
+ test('remove method has no effect on non-existent properties', () => {
472
+ const user = store<{ name: string; email?: string }>({
473
+ name: 'Hannah',
474
+ })
475
+
476
+ const originalSize = user.size.get()
477
+ user.remove('email')
478
+
479
+ expect(user.size.get()).toBe(originalSize)
480
+ })
481
+ })
482
+
483
+ describe('computed integration', () => {
484
+ test('works with computed signals', () => {
485
+ const user = store({ firstName: 'Hannah', lastName: 'Smith' })
486
+
487
+ const fullName = computed(() => {
488
+ return `${user.firstName.get()} ${user.lastName.get()}`
489
+ })
490
+
491
+ expect(fullName.get()).toBe('Hannah Smith')
492
+
493
+ user.firstName.set('Alice')
494
+ expect(fullName.get()).toBe('Alice Smith')
495
+ })
496
+
497
+ test('computed reacts to nested store changes', () => {
498
+ const config = store({
499
+ ui: {
500
+ theme: 'dark',
501
+ },
502
+ })
503
+
504
+ const themeDisplay = computed(() => {
505
+ return `Theme: ${config.ui.theme.get()}`
506
+ })
507
+
508
+ expect(themeDisplay.get()).toBe('Theme: dark')
509
+
510
+ config.ui.theme.set('light')
511
+ expect(themeDisplay.get()).toBe('Theme: light')
512
+ })
513
+ })
514
+
515
+ describe('arrays and edge cases', () => {
516
+ test('handles arrays as store values', () => {
517
+ const data = store({ items: [1, 2, 3] })
518
+
519
+ // Arrays become stores with string indices
520
+ expect(isStore(data.items)).toBe(true)
521
+ expect(data.items['0'].get()).toBe(1)
522
+ expect(data.items['1'].get()).toBe(2)
523
+ expect(data.items['2'].get()).toBe(3)
524
+ })
525
+
526
+ test('array-derived nested stores have correct type inference', () => {
527
+ const todoApp = store({
528
+ todos: ['Buy milk', 'Walk the dog', 'Write code'],
529
+ users: [
530
+ { name: 'Alice', active: true },
531
+ { name: 'Bob', active: false },
532
+ ],
533
+ numbers: [1, 2, 3, 4, 5],
534
+ })
535
+
536
+ // Arrays should become stores
537
+ expect(isStore(todoApp.todos)).toBe(true)
538
+ expect(isStore(todoApp.users)).toBe(true)
539
+ expect(isStore(todoApp.numbers)).toBe(true)
540
+
541
+ // String array elements should be State<string>
542
+ expect(todoApp.todos['0'].get()).toBe('Buy milk')
543
+ expect(todoApp.todos['1'].get()).toBe('Walk the dog')
544
+ expect(todoApp.todos['2'].get()).toBe('Write code')
545
+
546
+ // Should be able to modify string elements
547
+ todoApp.todos['0'].set('Buy groceries')
548
+ expect(todoApp.todos['0'].get()).toBe('Buy groceries')
549
+
550
+ // Object array elements should be Store<T>
551
+ expect(isStore(todoApp.users[0])).toBe(true)
552
+ expect(isStore(todoApp.users[1])).toBe(true)
553
+
554
+ // Should be able to access nested properties in object array elements
555
+ expect(todoApp.users[0].name.get()).toBe('Alice')
556
+ expect(todoApp.users[0].active.get()).toBe(true)
557
+ expect(todoApp.users[1].name.get()).toBe('Bob')
558
+ expect(todoApp.users[1].active.get()).toBe(false)
559
+
560
+ // Should be able to modify nested properties
561
+ todoApp.users[0].name.set('Alice Smith')
562
+ todoApp.users[0].active.set(false)
563
+ expect(todoApp.users[0].name.get()).toBe('Alice Smith')
564
+ expect(todoApp.users[0].active.get()).toBe(false)
565
+
566
+ // Number array elements should be State<number>
567
+ expect(todoApp.numbers[0].get()).toBe(1)
568
+ expect(todoApp.numbers[4].get()).toBe(5)
569
+
570
+ // Should be able to modify number elements
571
+ todoApp.numbers[0].set(10)
572
+ todoApp.numbers[4].set(50)
573
+ expect(todoApp.numbers[0].get()).toBe(10)
574
+ expect(todoApp.numbers[4].get()).toBe(50)
575
+
576
+ // Store-level access should reflect all changes
577
+ const currentState = todoApp.get()
578
+ expect(currentState.todos[0]).toBe('Buy groceries')
579
+ expect(currentState.users[0].name).toBe('Alice Smith')
580
+ expect(currentState.users[0].active).toBe(false)
581
+ expect(currentState.numbers[0]).toBe(10)
582
+ expect(currentState.numbers[4]).toBe(50)
583
+ })
584
+
585
+ test('handles UNSET values', () => {
586
+ const data = store({ value: UNSET as string })
587
+
588
+ expect(data.value.get()).toBe(UNSET)
589
+ data.value.set('some string')
590
+ expect(data.value.get()).toBe('some string')
591
+ })
592
+
593
+ test('handles primitive values', () => {
594
+ const data = store({
595
+ str: 'hello',
596
+ num: 42,
597
+ bool: true,
598
+ })
599
+
600
+ expect(data.str.get()).toBe('hello')
601
+ expect(data.num.get()).toBe(42)
602
+ expect(data.bool.get()).toBe(true)
603
+ })
604
+ })
605
+
606
+ describe('proxy behavior', () => {
607
+ test('Object.keys returns property keys', () => {
608
+ const user = store({ name: 'Hannah', email: 'hannah@example.com' })
609
+
610
+ expect(Object.keys(user)).toEqual(['name', 'email'])
611
+ })
612
+
613
+ test('property enumeration works', () => {
614
+ const user = store({ name: 'Hannah', email: 'hannah@example.com' })
615
+ const keys: string[] = []
616
+
617
+ for (const key in user) {
618
+ keys.push(key)
619
+ }
620
+
621
+ expect(keys).toEqual(['name', 'email'])
622
+ })
623
+
624
+ test('in operator works', () => {
625
+ const user = store({ name: 'Hannah' })
626
+
627
+ expect('name' in user).toBe(true)
628
+ expect('email' in user).toBe(false)
629
+ })
630
+
631
+ test('Object.getOwnPropertyDescriptor works', () => {
632
+ const user = store({ name: 'Hannah' })
633
+
634
+ const descriptor = Object.getOwnPropertyDescriptor(user, 'name')
635
+ expect(descriptor).toEqual({
636
+ enumerable: true,
637
+ configurable: true,
638
+ writable: true,
639
+ value: user.name,
640
+ })
641
+ })
642
+ })
643
+
644
+ describe('type conversion via toSignal', () => {
645
+ test('arrays are converted to stores', () => {
646
+ const fruits = store({ items: ['apple', 'banana', 'cherry'] })
647
+
648
+ expect(isStore(fruits.items)).toBe(true)
649
+ expect(fruits.items['0'].get()).toBe('apple')
650
+ expect(fruits.items['1'].get()).toBe('banana')
651
+ expect(fruits.items['2'].get()).toBe('cherry')
652
+ })
653
+
654
+ test('nested objects become nested stores', () => {
655
+ const config = store({
656
+ database: {
657
+ host: 'localhost',
658
+ port: 5432,
659
+ },
660
+ })
661
+
662
+ expect(isStore(config.database)).toBe(true)
663
+ expect(config.database.host.get()).toBe('localhost')
664
+ expect(config.database.port.get()).toBe(5432)
665
+ })
666
+ })
667
+
668
+ describe('spread operator behavior', () => {
669
+ test('spreading store spreads individual signals', () => {
670
+ const user = store({ name: 'Hannah', age: 25, active: true })
671
+
672
+ // Spread the store - should get individual signals
673
+ const spread = { ...user }
674
+
675
+ // Check that we get the signals themselves
676
+ expect('name' in spread).toBe(true)
677
+ expect('age' in spread).toBe(true)
678
+ expect('active' in spread).toBe(true)
679
+
680
+ // The spread should contain signals that can be called with .get()
681
+ expect(typeof spread.name?.get).toBe('function')
682
+ expect(typeof spread.age?.get).toBe('function')
683
+ expect(typeof spread.active?.get).toBe('function')
684
+
685
+ // The signals should return the correct values
686
+ expect(spread.name?.get()).toBe('Hannah')
687
+ expect(spread.age?.get()).toBe(25)
688
+ expect(spread.active?.get()).toBe(true)
689
+
690
+ // Modifying the original store should be reflected in the spread signals
691
+ user.name.set('Alice')
692
+ user.age.set(30)
693
+
694
+ expect(spread.name?.get()).toBe('Alice')
695
+ expect(spread.age?.get()).toBe(30)
696
+ })
697
+
698
+ test('spreading nested store works correctly', () => {
699
+ const config = store({
700
+ app: { name: 'MyApp', version: '1.0' },
701
+ settings: { theme: 'dark', debug: false },
702
+ })
703
+
704
+ const spread = { ...config }
705
+
706
+ // Should get nested store signals
707
+ expect(isStore(spread.app)).toBe(true)
708
+ expect(isStore(spread.settings)).toBe(true)
709
+
710
+ // Should be able to access nested properties
711
+ expect(spread.app.name.get()).toBe('MyApp')
712
+ expect(spread.settings.theme.get()).toBe('dark')
713
+
714
+ // Modifications should be reflected
715
+ config.app.name.set('UpdatedApp')
716
+ expect(spread.app.name.get()).toBe('UpdatedApp')
717
+ })
718
+ })
719
+ })