@nyaruka/temba-components 0.136.1 → 0.137.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/demo/components/webchat/example.html +2 -2
  3. package/dist/temba-components.js +487 -551
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/display/Chat.js +123 -44
  6. package/out-tsc/src/display/Chat.js.map +1 -1
  7. package/out-tsc/src/events/eventRenderers.js +442 -0
  8. package/out-tsc/src/events/eventRenderers.js.map +1 -0
  9. package/out-tsc/src/flow/Editor.js +3 -2
  10. package/out-tsc/src/flow/Editor.js.map +1 -1
  11. package/out-tsc/src/flow/NodeEditor.js +0 -1
  12. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  13. package/out-tsc/src/list/ShortcutList.js +1 -1
  14. package/out-tsc/src/list/ShortcutList.js.map +1 -1
  15. package/out-tsc/src/live/ContactChat.js +12 -321
  16. package/out-tsc/src/live/ContactChat.js.map +1 -1
  17. package/out-tsc/src/simulator/Simulator.js +428 -571
  18. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  19. package/out-tsc/test/temba-simulator.test.js +51 -32
  20. package/out-tsc/test/temba-simulator.test.js.map +1 -1
  21. package/package.json +1 -1
  22. package/screenshots/truth/contacts/chat-failure.png +0 -0
  23. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  24. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  25. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  26. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  27. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  28. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  29. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  30. package/screenshots/truth/simulator/after-message-sent.png +0 -0
  31. package/screenshots/truth/simulator/after-reset.png +0 -0
  32. package/screenshots/truth/simulator/attachment-menu.png +0 -0
  33. package/screenshots/truth/simulator/context-expanded.png +0 -0
  34. package/screenshots/truth/simulator/context-explorer-open.png +0 -0
  35. package/screenshots/truth/simulator/event-info.png +0 -0
  36. package/screenshots/truth/simulator/image-attachment.png +0 -0
  37. package/screenshots/truth/simulator/open-initial.png +0 -0
  38. package/screenshots/truth/simulator/quick-replies.png +0 -0
  39. package/src/display/Chat.ts +123 -44
  40. package/src/events/eventRenderers.ts +527 -0
  41. package/src/flow/Editor.ts +3 -2
  42. package/src/flow/NodeEditor.ts +0 -1
  43. package/src/list/ShortcutList.ts +1 -1
  44. package/src/live/ContactChat.ts +17 -376
  45. package/src/simulator/Simulator.ts +487 -612
  46. package/test/temba-simulator.test.ts +64 -34
@@ -48,7 +48,7 @@ const openSimulator = async (simulator: Simulator) => {
48
48
  tab.dispatchEvent(new CustomEvent('temba-button-clicked', { bubbles: true }));
49
49
 
50
50
  await simulator.updateComplete;
51
- // brief delay for async API mock processing
51
+ // brief delay for async API response processing
52
52
  await delay(50);
53
53
  };
54
54
 
@@ -148,16 +148,33 @@ const getSimulatorClip = (
148
148
 
149
149
  const frameBounds = phoneFrame.getBoundingClientRect();
150
150
 
151
- // add padding around the phone frame
151
+ // clip to just the phone frame area
152
152
  const padding = 10;
153
+
153
154
  return {
154
155
  x: frameBounds.x - padding,
155
156
  y: frameBounds.y - padding,
156
- width: frameBounds.width + padding * 2,
157
+ // only add padding to the left to avoid capturing option pane on right
158
+ width: frameBounds.width + padding,
157
159
  height: frameBounds.height + padding * 2
158
160
  };
159
161
  };
160
162
 
163
+ // helper to get message count from chat component
164
+ const getMessageCount = (simulator: Simulator): number => {
165
+ const chat = simulator.shadowRoot.querySelector('temba-chat') as any;
166
+ if (!chat) {
167
+ return 0;
168
+ }
169
+ // check how many messages are in the chat component
170
+ // the chat component stores messages in its internal state
171
+ return (
172
+ chat.messageGroups?.reduce((total: number, group: any) => {
173
+ return total + (group.messages?.length || 0);
174
+ }, 0) || 0
175
+ );
176
+ };
177
+
161
178
  // mock responses for simulation endpoints
162
179
  const mockSimulatorStart = () => {
163
180
  const response = {
@@ -340,8 +357,6 @@ describe('temba-simulator', () => {
340
357
  mockSimulatorStart();
341
358
 
342
359
  const simulator: Simulator = await createSimulator();
343
- // ensure consistent size for screenshot
344
- simulator.size = 'medium';
345
360
  await simulator.updateComplete;
346
361
  await openSimulator(simulator);
347
362
 
@@ -354,9 +369,9 @@ describe('temba-simulator', () => {
354
369
  const phoneScreen = simulator.shadowRoot.querySelector('.phone-screen');
355
370
  expect(phoneScreen).to.exist;
356
371
 
357
- // verify initial message is displayed
358
- const messages = simulator.shadowRoot.querySelectorAll('.message');
359
- expect(messages.length).to.be.greaterThan(0);
372
+ // verify initial message is displayed via chat component
373
+ const messageCount = getMessageCount(simulator);
374
+ expect(messageCount).to.be.greaterThan(0);
360
375
 
361
376
  await assertScreenshot(
362
377
  'simulator/open-initial',
@@ -368,13 +383,11 @@ describe('temba-simulator', () => {
368
383
  mockSimulatorStart();
369
384
 
370
385
  const simulator: Simulator = await createSimulator();
371
- simulator.size = 'medium';
372
386
  await simulator.updateComplete;
373
387
  await openSimulator(simulator);
374
388
 
375
389
  // count initial messages
376
- let messages = simulator.shadowRoot.querySelectorAll('.message');
377
- const initialCount = messages.length;
390
+ const initialCount = getMessageCount(simulator);
378
391
 
379
392
  // mock the resume response
380
393
  mockSimulatorResume('Thanks for your message!');
@@ -396,13 +409,23 @@ describe('temba-simulator', () => {
396
409
  });
397
410
  input.dispatchEvent(enterEvent);
398
411
 
412
+ // wait for the message to be sent and response to come back
413
+ await waitForCondition(
414
+ () => getMessageCount(simulator) > initialCount,
415
+ 40,
416
+ 50
417
+ );
418
+
399
419
  await simulator.updateComplete;
400
- // brief delay for async API mock processing
401
- await delay(100);
420
+ // wait for chat component to update
421
+ const chat = simulator.shadowRoot.querySelector('temba-chat') as any;
422
+ if (chat) {
423
+ await chat.updateComplete;
424
+ }
402
425
 
403
426
  // verify we have more messages than before
404
- messages = simulator.shadowRoot.querySelectorAll('.message');
405
- expect(messages.length).to.be.greaterThan(initialCount);
427
+ const newCount = getMessageCount(simulator);
428
+ expect(newCount).to.be.greaterThan(initialCount);
406
429
 
407
430
  // ensure DOM is settled
408
431
  await simulator.updateComplete;
@@ -417,7 +440,6 @@ describe('temba-simulator', () => {
417
440
  mockSimulatorStart();
418
441
 
419
442
  const simulator: Simulator = await createSimulator();
420
- simulator.size = 'medium';
421
443
  await simulator.updateComplete;
422
444
  await openSimulator(simulator);
423
445
 
@@ -442,9 +464,10 @@ describe('temba-simulator', () => {
442
464
  await waitForCondition(
443
465
  () =>
444
466
  simulator.shadowRoot.querySelectorAll('.quick-reply-btn').length > 0,
445
- 2000
467
+ 5000
446
468
  );
447
469
  await simulator.updateComplete;
470
+ await delay(100); // extra delay for rendering
448
471
 
449
472
  // take screenshot with quick replies
450
473
  await assertScreenshot(
@@ -457,7 +480,6 @@ describe('temba-simulator', () => {
457
480
  mockSimulatorStart();
458
481
 
459
482
  const simulator: Simulator = await createSimulator();
460
- simulator.size = 'medium';
461
483
  await simulator.updateComplete;
462
484
  await openSimulator(simulator);
463
485
 
@@ -486,12 +508,14 @@ describe('temba-simulator', () => {
486
508
  mockSimulatorStart();
487
509
 
488
510
  const simulator: Simulator = await createSimulator();
489
- simulator.size = 'medium';
490
511
  await simulator.updateComplete;
491
512
  // reset attachment indices for deterministic testing
492
513
  simulator.resetAttachmentIndices();
493
514
  await openSimulator(simulator);
494
515
 
516
+ // count initial messages
517
+ const initialCount = getMessageCount(simulator);
518
+
495
519
  // mock the response for image attachment
496
520
  mockSimulatorResume('Nice picture!');
497
521
 
@@ -509,14 +533,23 @@ describe('temba-simulator', () => {
509
533
  expect(imageMenuItem).to.exist;
510
534
  imageMenuItem.click();
511
535
 
512
- await delay(100);
536
+ // wait for the attachment to be sent and response to come back
537
+ await waitForCondition(
538
+ () => getMessageCount(simulator) > initialCount,
539
+ 40,
540
+ 50
541
+ );
542
+
513
543
  await simulator.updateComplete;
544
+ // wait for chat component to update
545
+ const chat = simulator.shadowRoot.querySelector('temba-chat') as any;
546
+ if (chat) {
547
+ await chat.updateComplete;
548
+ }
514
549
 
515
- // verify attachment wrapper is displayed (image attachments show in attachments not messages)
516
- const attachmentWrappers = simulator.shadowRoot.querySelectorAll(
517
- '.attachment-wrapper'
518
- );
519
- expect(attachmentWrappers.length).to.be.greaterThan(0);
550
+ // verify message with attachment was added via chat component
551
+ const newCount = getMessageCount(simulator);
552
+ expect(newCount).to.be.greaterThan(initialCount);
520
553
 
521
554
  await assertScreenshot(
522
555
  'simulator/image-attachment',
@@ -674,7 +707,6 @@ describe('temba-simulator', () => {
674
707
  mockSimulatorStart();
675
708
 
676
709
  const simulator: Simulator = await createSimulator();
677
- simulator.size = 'medium';
678
710
  await simulator.updateComplete;
679
711
  await openSimulator(simulator);
680
712
 
@@ -696,8 +728,7 @@ describe('temba-simulator', () => {
696
728
  await simulator.updateComplete;
697
729
 
698
730
  // verify we have multiple messages
699
- let messages = simulator.shadowRoot.querySelectorAll('.message');
700
- const messageCountBefore = messages.length;
731
+ const messageCountBefore = getMessageCount(simulator);
701
732
  expect(messageCountBefore).to.be.greaterThan(1);
702
733
 
703
734
  // mock the start response for reset
@@ -717,8 +748,8 @@ describe('temba-simulator', () => {
717
748
  await simulator.updateComplete;
718
749
 
719
750
  // verify messages are reset - should go back to just initial message
720
- messages = simulator.shadowRoot.querySelectorAll('.message');
721
- expect(messages.length).to.be.lessThan(messageCountBefore);
751
+ const messageCountAfter = getMessageCount(simulator);
752
+ expect(messageCountAfter).to.be.lessThan(messageCountBefore);
722
753
 
723
754
  await assertScreenshot(
724
755
  'simulator/after-reset',
@@ -799,13 +830,12 @@ describe('temba-simulator', () => {
799
830
  mockPOST(/\/flow\/simulate\/.*\//, responseWithEvents);
800
831
 
801
832
  const simulator: Simulator = await createSimulator();
802
- simulator.size = 'medium';
803
833
  await simulator.updateComplete;
804
834
  await openSimulator(simulator);
805
835
 
806
- // verify event info is displayed
807
- const eventInfo = simulator.shadowRoot.querySelectorAll('.event-info');
808
- expect(eventInfo.length).to.be.greaterThan(0);
836
+ // verify events are displayed via chat component (field change + message = 2 events)
837
+ const messageCount = getMessageCount(simulator);
838
+ expect(messageCount).to.be.greaterThan(0);
809
839
 
810
840
  await assertScreenshot('simulator/event-info', getSimulatorClip(simulator));
811
841
  });