@capillarytech/creatives-library 8.0.236-alpha.4 → 8.0.236-alpha.6

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.236-alpha.4",
4
+ "version": "8.0.236-alpha.6",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -10,22 +10,32 @@ import '@testing-library/jest-dom';
10
10
  import { IntlProvider } from 'react-intl';
11
11
  import HTMLEditor from '../HTMLEditor';
12
12
 
13
+ // Options to control CodeEditorPane mock behavior
14
+ const mockCodeEditorOptions = {
15
+ includeInsertText: true,
16
+ insertTextThrows: false,
17
+ setRef: true,
18
+ navigateToLineThrows: false,
19
+ includeNavigateToLine: true,
20
+ };
21
+
22
+
13
23
  // Mock useLayoutEffect to behave like useEffect in tests
14
24
  React.useLayoutEffect = React.useEffect;
15
25
 
16
26
  // Mock browser APIs
17
27
  global.IntersectionObserver = class IntersectionObserver {
18
- constructor() {}
19
- disconnect() {}
20
- observe() {}
21
- unobserve() {}
28
+ constructor() { }
29
+ disconnect() { }
30
+ observe() { }
31
+ unobserve() { }
22
32
  };
23
33
 
24
34
  global.ResizeObserver = class ResizeObserver {
25
- constructor() {}
26
- disconnect() {}
27
- observe() {}
28
- unobserve() {}
35
+ constructor() { }
36
+ disconnect() { }
37
+ observe() { }
38
+ unobserve() { }
29
39
  };
30
40
 
31
41
  // Setup window.matchMedia mock
@@ -132,19 +142,42 @@ jest.mock('../components/SplitContainer', () => {
132
142
  };
133
143
  });
134
144
 
145
+
135
146
  jest.mock('../components/CodeEditorPane', () => {
136
147
  const React = require('react');
137
148
  return React.forwardRef(function MockCodeEditorPane(props, ref) {
138
149
  const [value, setValue] = React.useState('');
139
150
 
140
- React.useImperativeHandle(ref, () => ({
141
- insertText: jest.fn(),
142
- focus: jest.fn(),
143
- getCursor: jest.fn(() => 0),
144
- getValue: jest.fn(() => value),
145
- setValue: jest.fn((newValue) => setValue(newValue)),
146
- navigateToLine: jest.fn(),
147
- }));
151
+ React.useImperativeHandle(ref, () => {
152
+ if (!mockCodeEditorOptions.setRef) {
153
+ return null;
154
+ }
155
+
156
+ const methods = {
157
+ focus: jest.fn(),
158
+ getCursor: jest.fn(() => 0),
159
+ getValue: jest.fn(() => value),
160
+ setValue: jest.fn((newValue) => setValue(newValue)),
161
+ };
162
+
163
+ if (mockCodeEditorOptions.includeNavigateToLine) {
164
+ methods.navigateToLine = jest.fn(() => {
165
+ if (mockCodeEditorOptions.navigateToLineThrows) {
166
+ throw new Error('Navigation failed');
167
+ }
168
+ });
169
+ }
170
+
171
+ if (mockCodeEditorOptions.includeInsertText) {
172
+ methods.insertText = jest.fn(() => {
173
+ if (mockCodeEditorOptions.insertTextThrows) {
174
+ throw new Error('Insert failed');
175
+ }
176
+ });
177
+ }
178
+
179
+ return methods;
180
+ });
148
181
 
149
182
  return (
150
183
  <div data-testid="code-editor-pane">
@@ -158,6 +191,12 @@ jest.mock('../components/CodeEditorPane', () => {
158
191
  }}
159
192
  data-testid="editor-textarea"
160
193
  />
194
+ <button
195
+ onClick={() => props.onContextChange && props.onContextChange('test-context')}
196
+ data-testid="trigger-context-change"
197
+ >
198
+ Trigger Context Change
199
+ </button>
161
200
  </div>
162
201
  );
163
202
  });
@@ -480,7 +519,7 @@ describe('HTMLEditor', () => {
480
519
  // This test covers lines 104-109 in HTMLEditor.js
481
520
  const mockUseInAppContent = require('../hooks/useInAppContent').useInAppContent;
482
521
  const originalUseInAppContent = jest.requireActual('../hooks/useInAppContent').useInAppContent;
483
-
522
+
484
523
  let capturedInitialContent = null;
485
524
  jest.spyOn(require('../hooks/useInAppContent'), 'useInAppContent').mockImplementation((initialContent) => {
486
525
  capturedInitialContent = initialContent;
@@ -542,7 +581,7 @@ describe('HTMLEditor', () => {
542
581
 
543
582
  const mockUseInAppContent = require('../hooks/useInAppContent').useInAppContent;
544
583
  const originalUseInAppContent = jest.requireActual('../hooks/useInAppContent').useInAppContent;
545
-
584
+
546
585
  let capturedInitialContent = null;
547
586
  jest.spyOn(require('../hooks/useInAppContent'), 'useInAppContent').mockImplementation((initialContent) => {
548
587
  capturedInitialContent = initialContent;
@@ -930,7 +969,7 @@ describe('HTMLEditor', () => {
930
969
  describe('Error Handling', () => {
931
970
  it('handles missing editorRef gracefully', () => {
932
971
  // Mock console.warn to avoid noise in tests
933
- const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
972
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
934
973
 
935
974
  render(
936
975
  <TestWrapper>
@@ -1399,8 +1438,8 @@ describe('HTMLEditor', () => {
1399
1438
  <div className="html-editor html-editor--email">
1400
1439
  <CustomToolbar
1401
1440
  onLabelInsert={handleLabelInsert}
1402
- onToggleFullscreen={() => {}}
1403
- onSave={() => {}}
1441
+ onToggleFullscreen={() => { }}
1442
+ onSave={() => { }}
1404
1443
  />
1405
1444
  <div data-testid="split-container">
1406
1445
  <div data-testid="code-editor-pane" ref={editorRef}>
@@ -1970,7 +2009,7 @@ describe('HTMLEditor', () => {
1970
2009
  // Wait for component to render - might be in loading state
1971
2010
  const toolbar = screen.queryByTestId('editor-toolbar');
1972
2011
  const loading = screen.queryByText('Initializing HTML Editor...');
1973
-
2012
+
1974
2013
  // Component should render (either loaded or loading)
1975
2014
  expect(toolbar || loading).toBeTruthy();
1976
2015
  });
@@ -1999,7 +2038,7 @@ describe('HTMLEditor', () => {
1999
2038
  // Wait for component to render
2000
2039
  const deviceToggle = screen.queryByTestId('device-toggle');
2001
2040
  const loading = screen.queryByText('Initializing HTML Editor...');
2002
-
2041
+
2003
2042
  // Component should render (either loaded or loading)
2004
2043
  expect(deviceToggle || loading).toBeTruthy();
2005
2044
  });
@@ -2075,7 +2114,7 @@ describe('HTMLEditor', () => {
2075
2114
  const insertButton = screen.queryByText('Insert Label (Null Position)');
2076
2115
  if (insertButton) {
2077
2116
  fireEvent.click(insertButton);
2078
-
2117
+
2079
2118
  // Should attempt to insert via editor ref
2080
2119
  // The mock editor has insertText method, so it should work
2081
2120
  expect(CapNotification.success).toHaveBeenCalled();
@@ -2314,6 +2353,8 @@ describe('HTMLEditor', () => {
2314
2353
  });
2315
2354
 
2316
2355
  it('focuses editor when navigateToLine is not available', () => {
2356
+ mockCodeEditorOptions.includeNavigateToLine = false;
2357
+
2317
2358
  mockUseValidationImpl.mockReturnValueOnce({
2318
2359
  isValidating: false,
2319
2360
  getAllIssues: () => [{ message: 'Test error', line: 5, column: 10, severity: 'error' }],
@@ -2337,5 +2378,345 @@ describe('HTMLEditor', () => {
2337
2378
  expect(codeEditorPane || loading).toBeTruthy();
2338
2379
  });
2339
2380
  });
2381
+
2382
+
2383
+ describe('Additional Coverage Tests', () => {
2384
+ const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2385
+
2386
+ beforeEach(() => {
2387
+ // Reset options to default
2388
+ mockCodeEditorOptions.includeInsertText = true;
2389
+ mockCodeEditorOptions.insertTextThrows = false;
2390
+ mockCodeEditorOptions.setRef = true;
2391
+ mockCodeEditorOptions.navigateToLineThrows = false;
2392
+ mockCodeEditorOptions.includeNavigateToLine = true;
2393
+
2394
+ // Clear specific mocks instead of all to avoid breaking other mocks
2395
+ CapNotification.warning.mockClear();
2396
+ CapNotification.error.mockClear();
2397
+ CapNotification.success.mockClear();
2398
+
2399
+ // Restore hooks that might have been corrupted by Loading State Coverage tests
2400
+ // This is necessary because Loading State Coverage tests modify the require cache
2401
+ // and do not restore the original mocks
2402
+ require('../hooks/useEditorContent').useEditorContent = () => ({
2403
+ content: '<p>Test content</p>',
2404
+ updateContent: jest.fn(),
2405
+ saveContent: jest.fn(),
2406
+ markAsSaved: jest.fn(),
2407
+ isLoading: false,
2408
+ isDirty: false,
2409
+ hasContent: true,
2410
+ getContentSize: jest.fn(() => 20)
2411
+ });
2412
+
2413
+ require('../hooks/useInAppContent').useInAppContent = () => ({
2414
+ content: '<p>Android content</p>',
2415
+ deviceContent: {
2416
+ android: '<p>Android content</p>',
2417
+ ios: '<p>iOS content</p>'
2418
+ },
2419
+ activeDevice: 'android',
2420
+ keepContentSame: false,
2421
+ updateContent: jest.fn(),
2422
+ saveContent: jest.fn(),
2423
+ markAsSaved: jest.fn(),
2424
+ switchDevice: jest.fn(),
2425
+ toggleContentSync: jest.fn(),
2426
+ getDeviceContent: (device) => `<p>${device} content</p>`,
2427
+ setDeviceContent: jest.fn(),
2428
+ getContentSize: () => 20,
2429
+ isLoading: false,
2430
+ isDirty: false,
2431
+ hasContent: true
2432
+ });
2433
+
2434
+ require('../hooks/useLayoutState').useLayoutState = () => ({
2435
+ splitSizes: [50, 50],
2436
+ splitSize: 50,
2437
+ viewMode: 'desktop',
2438
+ mobileWidth: 375,
2439
+ isFullscreen: false,
2440
+ isResizing: false,
2441
+ updateSplitSizes: jest.fn(),
2442
+ setSplitSize: jest.fn(),
2443
+ setViewMode: jest.fn(),
2444
+ toggleViewMode: jest.fn(),
2445
+ setMobileWidth: jest.fn(),
2446
+ toggleFullscreen: jest.fn(),
2447
+ resetLayout: jest.fn(),
2448
+ setResizingState: jest.fn(),
2449
+ handleResize: jest.fn(),
2450
+ handleKeyboardShortcut: jest.fn(),
2451
+ isMobileView: false,
2452
+ isDesktopView: true,
2453
+ minPaneSize: 20,
2454
+ maxPaneSize: 80,
2455
+ gutterSize: 10
2456
+ });
2457
+ });
2458
+
2459
+ describe('handleContextChange', () => {
2460
+ it('calls onContextChange prop when provided', () => {
2461
+ const onContextChange = jest.fn();
2462
+ const globalActions = { fetchSchemaForEntity: jest.fn() };
2463
+
2464
+ render(
2465
+ <TestWrapper>
2466
+ <HTMLEditor
2467
+ {...defaultProps}
2468
+ onContextChange={onContextChange}
2469
+ globalActions={globalActions}
2470
+ />
2471
+ </TestWrapper>
2472
+ );
2473
+
2474
+ act(() => {
2475
+ jest.runAllTimers();
2476
+ });
2477
+
2478
+ fireEvent.click(screen.getByTestId('trigger-context-change'));
2479
+
2480
+ expect(onContextChange).toHaveBeenCalledWith('test-context');
2481
+ expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2482
+ });
2483
+
2484
+ it('calls globalActions.fetchSchemaForEntity when onContextChange is NOT provided', () => {
2485
+ const globalActions = { fetchSchemaForEntity: jest.fn() };
2486
+ const location = { query: { type: 'embedded' } };
2487
+
2488
+ render(
2489
+ <TestWrapper>
2490
+ <HTMLEditor
2491
+ {...defaultProps}
2492
+ globalActions={globalActions}
2493
+ location={location}
2494
+ variant="email"
2495
+ />
2496
+ </TestWrapper>
2497
+ );
2498
+
2499
+ act(() => {
2500
+ jest.runAllTimers();
2501
+ });
2502
+
2503
+ fireEvent.click(screen.getByTestId('trigger-context-change'));
2504
+
2505
+ expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2506
+ layout: 'EMAIL',
2507
+ type: 'TAG',
2508
+ context: 'test-context',
2509
+ embedded: 'embedded',
2510
+ });
2511
+ });
2512
+
2513
+ it('handles INAPP variant in handleContextChange', () => {
2514
+ const globalActions = { fetchSchemaForEntity: jest.fn() };
2515
+ const location = { query: { type: 'full' } };
2516
+
2517
+ render(
2518
+ <TestWrapper>
2519
+ <HTMLEditor
2520
+ {...defaultProps}
2521
+ globalActions={globalActions}
2522
+ location={location}
2523
+ variant="inapp"
2524
+ />
2525
+ </TestWrapper>
2526
+ );
2527
+
2528
+ act(() => {
2529
+ jest.runAllTimers();
2530
+ });
2531
+
2532
+ fireEvent.click(screen.getByTestId('trigger-context-change'));
2533
+
2534
+ expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2535
+ layout: 'SMS', // INAPP uses SMS layout
2536
+ type: 'TAG',
2537
+ context: 'test-context',
2538
+ embedded: 'full',
2539
+ });
2540
+ });
2541
+
2542
+ it('handles missing globalActions or location', () => {
2543
+ const globalActions = { fetchSchemaForEntity: jest.fn() };
2544
+
2545
+ // Case 1: No globalActions
2546
+ const { unmount } = render(
2547
+ <TestWrapper>
2548
+ <HTMLEditor
2549
+ {...defaultProps}
2550
+ globalActions={null}
2551
+ location={{}}
2552
+ />
2553
+ </TestWrapper>
2554
+ );
2555
+
2556
+ act(() => {
2557
+ jest.runAllTimers();
2558
+ });
2559
+
2560
+ fireEvent.click(screen.getByTestId('trigger-context-change'));
2561
+ expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2562
+ unmount();
2563
+
2564
+ // Case 2: No location
2565
+ render(
2566
+ <TestWrapper>
2567
+ <HTMLEditor
2568
+ {...defaultProps}
2569
+ globalActions={globalActions}
2570
+ location={null}
2571
+ />
2572
+ </TestWrapper>
2573
+ );
2574
+
2575
+ act(() => {
2576
+ jest.runAllTimers();
2577
+ });
2578
+
2579
+ fireEvent.click(screen.getByTestId('trigger-context-change'));
2580
+ expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2581
+ });
2582
+ });
2583
+
2584
+ describe('handleLabelInsert', () => {
2585
+ it('shows warning when editor is not ready (position null, no editor)', () => {
2586
+ mockCodeEditorOptions.setRef = false;
2587
+
2588
+ render(
2589
+ <TestWrapper>
2590
+ <HTMLEditor {...defaultProps} />
2591
+ </TestWrapper>
2592
+ );
2593
+
2594
+ act(() => {
2595
+ jest.runAllTimers();
2596
+ });
2597
+
2598
+ // Click the button that passes null position
2599
+ const insertButton = screen.getByText('Insert Label (Null Position)');
2600
+ fireEvent.click(insertButton);
2601
+
2602
+ expect(CapNotification.warning).toHaveBeenCalledWith(
2603
+ expect.objectContaining({
2604
+ message: 'Failed to insert label',
2605
+ description: 'Editor is not ready. Please try again.',
2606
+ })
2607
+ );
2608
+ });
2609
+
2610
+ it('shows error when editor method insertText is not available', () => {
2611
+ mockCodeEditorOptions.includeInsertText = false;
2612
+
2613
+ render(
2614
+ <TestWrapper>
2615
+ <HTMLEditor {...defaultProps} />
2616
+ </TestWrapper>
2617
+ );
2618
+
2619
+ act(() => {
2620
+ jest.runAllTimers();
2621
+ });
2622
+
2623
+ const insertButton = screen.getByText('Insert Label (Null Position)');
2624
+ fireEvent.click(insertButton);
2625
+
2626
+ // Should show error when method is missing
2627
+ expect(CapNotification.error).toHaveBeenCalledWith(
2628
+ expect.objectContaining({
2629
+ message: 'Failed to insert label',
2630
+ })
2631
+ );
2632
+ });
2633
+
2634
+ it('successfully inserts label when position is null', () => {
2635
+ mockCodeEditorOptions.includeInsertText = true;
2636
+
2637
+ render(
2638
+ <TestWrapper>
2639
+ <HTMLEditor {...defaultProps} />
2640
+ </TestWrapper>
2641
+ );
2642
+
2643
+ act(() => {
2644
+ jest.runAllTimers();
2645
+ });
2646
+
2647
+ const insertButton = screen.getByText('Insert Label (Null Position)');
2648
+ fireEvent.click(insertButton);
2649
+
2650
+ expect(CapNotification.success).toHaveBeenCalled();
2651
+ });
2652
+
2653
+ it('shows error when insertText throws', () => {
2654
+ mockCodeEditorOptions.includeInsertText = true;
2655
+ mockCodeEditorOptions.insertTextThrows = true;
2656
+
2657
+ render(
2658
+ <TestWrapper>
2659
+ <HTMLEditor {...defaultProps} />
2660
+ </TestWrapper>
2661
+ );
2662
+
2663
+ act(() => {
2664
+ jest.runAllTimers();
2665
+ });
2666
+
2667
+ const insertButton = screen.getByText('Insert Label (Null Position)');
2668
+ fireEvent.click(insertButton);
2669
+
2670
+ expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
2671
+ description: 'Insert failed'
2672
+ }));
2673
+ });
2674
+
2675
+ it('shows success notification when position is provided (handled by CodeEditorPane)', () => {
2676
+ render(
2677
+ <TestWrapper>
2678
+ <HTMLEditor {...defaultProps} />
2679
+ </TestWrapper>
2680
+ );
2681
+
2682
+ act(() => {
2683
+ jest.runAllTimers();
2684
+ });
2685
+
2686
+ // Click the button that passes a position
2687
+ const insertButton = screen.getByText('Insert Label');
2688
+ fireEvent.click(insertButton);
2689
+
2690
+ expect(CapNotification.success).toHaveBeenCalled();
2691
+ });
2692
+ });
2693
+
2694
+ describe('handleValidationErrorClick', () => {
2695
+ it('handles error when navigateToLine throws', () => {
2696
+ mockCodeEditorOptions.navigateToLineThrows = true;
2697
+
2698
+ mockUseValidationImpl.mockReturnValueOnce({
2699
+ isValidating: false,
2700
+ getAllIssues: () => [{ message: 'Test error', line: 5, column: 10, severity: 'error' }],
2701
+ isClean: () => false,
2702
+ summary: { totalErrors: 1, totalWarnings: 0 }
2703
+ });
2704
+
2705
+ render(
2706
+ <TestWrapper>
2707
+ <HTMLEditor {...defaultProps} />
2708
+ </TestWrapper>
2709
+ );
2710
+
2711
+ act(() => {
2712
+ jest.runAllTimers();
2713
+ });
2714
+
2715
+ // Click error button
2716
+ const errorButton = screen.getByText('Error at Line 5');
2717
+ fireEvent.click(errorButton);
2718
+ });
2719
+ });
2720
+ });
2340
2721
  });
2341
2722
 
@@ -40,7 +40,7 @@
40
40
  overflow: auto;
41
41
  position: relative;
42
42
  height: 100%;
43
- max-height: 31.25rem;
43
+ max-height: 34.25rem;
44
44
  background-color: $CAP_G01;
45
45
  }
46
46
 
@@ -150,7 +150,7 @@
150
150
  .codemirror-wrapper {
151
151
  position: relative;
152
152
  height: 100%;
153
- max-height: 31.25rem;
153
+ max-height: 34.25rem;
154
154
  background-color: $CAP_G01;
155
155
  border-radius: 0;
156
156
  overflow: hidden;
@@ -926,20 +926,24 @@ describe('useInAppContent', () => {
926
926
 
927
927
  describe('setDeviceContent State Updates (L95-L109)', () => {
928
928
  it('updates Android content when newAndroidContent differs from current', () => {
929
+ // With the fix, initialContent effect only runs on first load or when transitioning from empty to content
930
+ // After initial load, changing initialContent won't update content (prevents overriding user edits)
929
931
  const initialContent = {
930
- [DEVICE_TYPES.ANDROID]: 'initial android',
931
- [DEVICE_TYPES.IOS]: 'initial ios',
932
+ [DEVICE_TYPES.ANDROID]: '', // Start with empty
933
+ [DEVICE_TYPES.IOS]: '',
932
934
  };
933
935
 
934
936
  const { rerender } = render(
935
937
  <TestComponent initialContent={initialContent} />
936
938
  );
937
939
 
938
- expect(screen.getByTestId('android-content')).toHaveTextContent('initial android');
940
+ // Initially empty
941
+ expect(screen.getByTestId('android-content')).toHaveTextContent('');
939
942
 
943
+ // Transition from empty to meaningful content - this SHOULD update
940
944
  const updatedContent = {
941
945
  [DEVICE_TYPES.ANDROID]: 'updated android',
942
- [DEVICE_TYPES.IOS]: 'initial ios',
946
+ [DEVICE_TYPES.IOS]: '',
943
947
  };
944
948
 
945
949
  rerender(<TestComponent initialContent={updatedContent} />);
@@ -948,19 +952,21 @@ describe('useInAppContent', () => {
948
952
  });
949
953
 
950
954
  it('updates iOS content when newIosContent differs from current', () => {
955
+ // With the fix, initialContent effect only runs on first load or when transitioning from empty to content
951
956
  const initialContent = {
952
- [DEVICE_TYPES.ANDROID]: 'initial android',
953
- [DEVICE_TYPES.IOS]: 'initial ios',
957
+ [DEVICE_TYPES.ANDROID]: '',
958
+ [DEVICE_TYPES.IOS]: '', // Start with empty
954
959
  };
955
960
 
956
961
  const { rerender } = render(
957
962
  <TestComponent initialContent={initialContent} />
958
963
  );
959
964
 
960
- expect(screen.getByTestId('ios-content')).toHaveTextContent('initial ios');
965
+ expect(screen.getByTestId('ios-content')).toHaveTextContent('');
961
966
 
967
+ // Transition from empty to meaningful content - this SHOULD update
962
968
  const updatedContent = {
963
- [DEVICE_TYPES.ANDROID]: 'initial android',
969
+ [DEVICE_TYPES.ANDROID]: '',
964
970
  [DEVICE_TYPES.IOS]: 'updated ios',
965
971
  };
966
972
 
@@ -970,18 +976,20 @@ describe('useInAppContent', () => {
970
976
  });
971
977
 
972
978
  it('updates both Android and iOS content when both differ', () => {
979
+ // With the fix, initialContent effect only runs on first load or when transitioning from empty to content
973
980
  const initialContent = {
974
- [DEVICE_TYPES.ANDROID]: 'initial android',
975
- [DEVICE_TYPES.IOS]: 'initial ios',
981
+ [DEVICE_TYPES.ANDROID]: '',
982
+ [DEVICE_TYPES.IOS]: '',
976
983
  };
977
984
 
978
985
  const { rerender } = render(
979
986
  <TestComponent initialContent={initialContent} />
980
987
  );
981
988
 
982
- expect(screen.getByTestId('android-content')).toHaveTextContent('initial android');
983
- expect(screen.getByTestId('ios-content')).toHaveTextContent('initial ios');
989
+ expect(screen.getByTestId('android-content')).toHaveTextContent('');
990
+ expect(screen.getByTestId('ios-content')).toHaveTextContent('');
984
991
 
992
+ // Transition from empty to meaningful content - this SHOULD update
985
993
  const updatedContent = {
986
994
  [DEVICE_TYPES.ANDROID]: 'updated android',
987
995
  [DEVICE_TYPES.IOS]: 'updated ios',
@@ -1016,6 +1024,8 @@ describe('useInAppContent', () => {
1016
1024
  });
1017
1025
 
1018
1026
  it('handles undefined newAndroidContent gracefully', () => {
1027
+ // With the fix, after initial load, rerendering with different initialContent won't update
1028
+ // This test now verifies that content persists even when initialContent changes
1019
1029
  const initialContent = {
1020
1030
  [DEVICE_TYPES.ANDROID]: 'initial android',
1021
1031
  [DEVICE_TYPES.IOS]: 'initial ios',
@@ -1026,17 +1036,18 @@ describe('useInAppContent', () => {
1026
1036
  );
1027
1037
 
1028
1038
  const updatedContent = {
1029
- [DEVICE_TYPES.IOS]: 'updated ios',
1039
+ [DEVICE_TYPES.IOS]: 'updated ios', // Android is undefined
1030
1040
  };
1031
1041
 
1032
1042
  rerender(<TestComponent initialContent={updatedContent} />);
1033
1043
 
1034
- // Android shouldn't change when newAndroidContent is undefined
1044
+ // With the fix, content should NOT change after initial load (prevents overriding user edits)
1035
1045
  expect(screen.getByTestId('android-content')).toHaveTextContent('initial android');
1036
- expect(screen.getByTestId('ios-content')).toHaveTextContent('updated ios');
1046
+ expect(screen.getByTestId('ios-content')).toHaveTextContent('initial ios');
1037
1047
  });
1038
1048
 
1039
1049
  it('handles undefined newIosContent gracefully', () => {
1050
+ // With the fix, after initial load, rerendering with different initialContent won't update
1040
1051
  const initialContent = {
1041
1052
  [DEVICE_TYPES.ANDROID]: 'initial android',
1042
1053
  [DEVICE_TYPES.IOS]: 'initial ios',
@@ -1047,26 +1058,28 @@ describe('useInAppContent', () => {
1047
1058
  );
1048
1059
 
1049
1060
  const updatedContent = {
1050
- [DEVICE_TYPES.ANDROID]: 'updated android',
1061
+ [DEVICE_TYPES.ANDROID]: 'updated android', // iOS is undefined
1051
1062
  };
1052
1063
 
1053
1064
  rerender(<TestComponent initialContent={updatedContent} />);
1054
1065
 
1055
- // iOS shouldn't change when newIosContent is undefined
1056
- expect(screen.getByTestId('android-content')).toHaveTextContent('updated android');
1066
+ // With the fix, content should NOT change after initial load (prevents overriding user edits)
1067
+ expect(screen.getByTestId('android-content')).toHaveTextContent('initial android');
1057
1068
  expect(screen.getByTestId('ios-content')).toHaveTextContent('initial ios');
1058
1069
  });
1059
1070
 
1060
1071
  it('returns updated content object when changes are made', () => {
1072
+ // Test that the transition from empty to content works correctly
1061
1073
  const initialContent = {
1062
- [DEVICE_TYPES.ANDROID]: 'initial',
1063
- [DEVICE_TYPES.IOS]: 'initial',
1074
+ [DEVICE_TYPES.ANDROID]: '',
1075
+ [DEVICE_TYPES.IOS]: '',
1064
1076
  };
1065
1077
 
1066
1078
  const { rerender } = render(
1067
1079
  <TestComponent initialContent={initialContent} />
1068
1080
  );
1069
1081
 
1082
+ // Transition from empty to content - this SHOULD update
1070
1083
  const updatedContent = {
1071
1084
  [DEVICE_TYPES.ANDROID]: 'updated',
1072
1085
  [DEVICE_TYPES.IOS]: 'updated',