@nyaruka/temba-components 0.133.0 → 0.134.1

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 (72) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/demo/components/webchat/example.html +1 -1
  3. package/dist/locales/es.js +5 -5
  4. package/dist/locales/es.js.map +1 -1
  5. package/dist/locales/fr.js +5 -5
  6. package/dist/locales/fr.js.map +1 -1
  7. package/dist/locales/locale-codes.js +2 -11
  8. package/dist/locales/locale-codes.js.map +1 -1
  9. package/dist/locales/pt.js +5 -5
  10. package/dist/locales/pt.js.map +1 -1
  11. package/dist/temba-components.js +307 -259
  12. package/dist/temba-components.js.map +1 -1
  13. package/out-tsc/src/display/Chat.js +223 -90
  14. package/out-tsc/src/display/Chat.js.map +1 -1
  15. package/out-tsc/src/display/TembaUser.js +3 -3
  16. package/out-tsc/src/display/TembaUser.js.map +1 -1
  17. package/out-tsc/src/events.js.map +1 -1
  18. package/out-tsc/src/flow/CanvasNode.js +8 -0
  19. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  20. package/out-tsc/src/flow/Editor.js +117 -28
  21. package/out-tsc/src/flow/Editor.js.map +1 -1
  22. package/out-tsc/src/flow/utils.js +141 -0
  23. package/out-tsc/src/flow/utils.js.map +1 -1
  24. package/out-tsc/src/interfaces.js.map +1 -1
  25. package/out-tsc/src/live/ContactChat.js +122 -170
  26. package/out-tsc/src/live/ContactChat.js.map +1 -1
  27. package/out-tsc/src/locales/es.js +5 -5
  28. package/out-tsc/src/locales/es.js.map +1 -1
  29. package/out-tsc/src/locales/fr.js +5 -5
  30. package/out-tsc/src/locales/fr.js.map +1 -1
  31. package/out-tsc/src/locales/locale-codes.js +2 -11
  32. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  33. package/out-tsc/src/locales/pt.js +5 -5
  34. package/out-tsc/src/locales/pt.js.map +1 -1
  35. package/out-tsc/src/store/AppState.js +3 -0
  36. package/out-tsc/src/store/AppState.js.map +1 -1
  37. package/out-tsc/src/store/Store.js +5 -5
  38. package/out-tsc/src/store/Store.js.map +1 -1
  39. package/out-tsc/src/webchat/WebChat.js +22 -9
  40. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  41. package/out-tsc/test/actions/send_broadcast.test.js +9 -4
  42. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  43. package/out-tsc/test/temba-flow-collision.test.js +673 -0
  44. package/out-tsc/test/temba-flow-collision.test.js.map +1 -0
  45. package/out-tsc/test/temba-flow-editor-node.test.js +128 -42
  46. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  47. package/package.json +1 -1
  48. package/screenshots/truth/contacts/chat-failure.png +0 -0
  49. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  50. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  51. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  52. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  53. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  54. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  55. package/src/display/Chat.ts +303 -129
  56. package/src/display/TembaUser.ts +3 -2
  57. package/src/events.ts +11 -8
  58. package/src/flow/CanvasNode.ts +10 -0
  59. package/src/flow/Editor.ts +156 -28
  60. package/src/flow/utils.ts +207 -1
  61. package/src/interfaces.ts +7 -0
  62. package/src/live/ContactChat.ts +129 -180
  63. package/src/locales/es.ts +13 -18
  64. package/src/locales/fr.ts +13 -18
  65. package/src/locales/locale-codes.ts +2 -11
  66. package/src/locales/pt.ts +13 -18
  67. package/src/store/AppState.ts +2 -0
  68. package/src/store/Store.ts +5 -5
  69. package/src/webchat/WebChat.ts +24 -10
  70. package/test/actions/send_broadcast.test.ts +2 -1
  71. package/test/temba-flow-collision.test.ts +833 -0
  72. package/test/temba-flow-editor-node.test.ts +142 -47
@@ -1220,55 +1220,60 @@ describe('EditorNode', () => {
1220
1220
  ]
1221
1221
  };
1222
1222
 
1223
- editorNode['node'] = mockNode;
1224
-
1225
1223
  const mockFlowDefinition = {
1224
+ language: 'en',
1225
+ localization: {},
1226
+ name: 'Test Flow',
1227
+ type: 'messaging' as const,
1228
+ uuid: 'test-uuid',
1229
+ revision: 1,
1230
+ spec_version: '14.3',
1226
1231
  nodes: [
1227
1232
  {
1228
1233
  uuid: 'node-before',
1234
+ actions: [],
1229
1235
  exits: [{ uuid: 'exit-before', destination_uuid: 'test-node' }]
1230
1236
  },
1231
1237
  mockNode,
1232
1238
  {
1233
1239
  uuid: 'node-after',
1240
+ actions: [],
1234
1241
  exits: []
1235
1242
  }
1236
- ]
1243
+ ],
1244
+ _ui: {
1245
+ nodes: {},
1246
+ languages: []
1247
+ }
1237
1248
  };
1238
1249
 
1239
- // Verify all exits point to the same destination
1240
- const destinations = mockNode.exits
1241
- .map((exit) => exit.destination_uuid)
1242
- .filter((dest) => dest);
1243
-
1244
- expect(destinations).to.have.length(3);
1245
- expect(destinations.every((dest) => dest === 'node-after')).to.be.true;
1250
+ // Set up the zustand store with our test flow
1251
+ const { zustand } = await import('../src/store/AppState');
1252
+ zustand.setState({
1253
+ flowDefinition: mockFlowDefinition as any
1254
+ });
1246
1255
 
1247
- // Find incoming connections
1248
- const incomingConnections: {
1249
- exitUuid: string;
1250
- sourceNodeUuid: string;
1251
- }[] = [];
1256
+ // Verify initial state - node-before points to test-node
1257
+ const initialNodeBefore = zustand
1258
+ .getState()
1259
+ .flowDefinition.nodes.find((n) => n.uuid === 'node-before');
1260
+ expect(initialNodeBefore.exits[0].destination_uuid).to.equal('test-node');
1252
1261
 
1253
- for (const node of mockFlowDefinition.nodes) {
1254
- if (node.uuid !== mockNode.uuid) {
1255
- for (const exit of node.exits) {
1256
- if (exit.destination_uuid === mockNode.uuid) {
1257
- incomingConnections.push({
1258
- exitUuid: exit.uuid,
1259
- sourceNodeUuid: node.uuid
1260
- });
1261
- }
1262
- }
1263
- }
1264
- }
1262
+ // Call removeNodes to trigger the rerouting logic
1263
+ zustand.getState().removeNodes(['test-node']);
1265
1264
 
1266
- // Verify we found incoming connections
1267
- expect(incomingConnections).to.have.length(1);
1268
- expect(incomingConnections[0].exitUuid).to.equal('exit-before');
1265
+ // Verify that the node was removed
1266
+ const remainingNodes = zustand.getState().flowDefinition.nodes;
1267
+ expect(remainingNodes.find((n) => n.uuid === 'test-node')).to.be
1268
+ .undefined;
1269
1269
 
1270
- // This validates that when a node has multiple exits but they all point
1271
- // to the same destination, the rerouting logic should still apply
1270
+ // Verify that node-before's exit was rerouted to node-after
1271
+ const updatedNodeBefore = remainingNodes.find(
1272
+ (n) => n.uuid === 'node-before'
1273
+ );
1274
+ expect(updatedNodeBefore.exits[0].destination_uuid).to.equal(
1275
+ 'node-after'
1276
+ );
1272
1277
  });
1273
1278
 
1274
1279
  it('does not reroute connections when node has exits with different destinations', async () => {
@@ -1289,16 +1294,63 @@ describe('EditorNode', () => {
1289
1294
  ]
1290
1295
  };
1291
1296
 
1292
- const destinations = mockNode.exits
1293
- .map((exit) => exit.destination_uuid)
1294
- .filter((dest) => dest);
1297
+ const mockFlowDefinition = {
1298
+ language: 'en',
1299
+ localization: {},
1300
+ name: 'Test Flow',
1301
+ type: 'messaging' as const,
1302
+ uuid: 'test-uuid',
1303
+ revision: 1,
1304
+ spec_version: '14.3',
1305
+ nodes: [
1306
+ {
1307
+ uuid: 'node-before',
1308
+ actions: [],
1309
+ exits: [{ uuid: 'exit-before', destination_uuid: 'test-node' }]
1310
+ },
1311
+ mockNode,
1312
+ {
1313
+ uuid: 'node-after-1',
1314
+ actions: [],
1315
+ exits: []
1316
+ },
1317
+ {
1318
+ uuid: 'node-after-2',
1319
+ actions: [],
1320
+ exits: []
1321
+ }
1322
+ ],
1323
+ _ui: {
1324
+ nodes: {},
1325
+ languages: []
1326
+ }
1327
+ };
1328
+
1329
+ // Set up the zustand store with our test flow
1330
+ const { zustand } = await import('../src/store/AppState');
1331
+ zustand.setState({
1332
+ flowDefinition: mockFlowDefinition as any
1333
+ });
1295
1334
 
1296
- // Verify exits point to different destinations
1297
- expect(destinations).to.have.length(2);
1298
- expect(destinations.every((dest) => dest === destinations[0])).to.be
1299
- .false;
1335
+ // Verify initial state
1336
+ const initialNodeBefore = zustand
1337
+ .getState()
1338
+ .flowDefinition.nodes.find((n) => n.uuid === 'node-before');
1339
+ expect(initialNodeBefore.exits[0].destination_uuid).to.equal('test-node');
1340
+
1341
+ // Call removeNodes
1342
+ zustand.getState().removeNodes(['test-node']);
1343
+
1344
+ // Verify that the node was removed
1345
+ const remainingNodes = zustand.getState().flowDefinition.nodes;
1346
+ expect(remainingNodes.find((n) => n.uuid === 'test-node')).to.be
1347
+ .undefined;
1300
1348
 
1301
- // This validates that rerouting does NOT apply when exits point to different places
1349
+ // Verify that incoming connection was cleared (set to null), not rerouted
1350
+ const updatedNodeBefore = remainingNodes.find(
1351
+ (n) => n.uuid === 'node-before'
1352
+ );
1353
+ expect(updatedNodeBefore.exits[0].destination_uuid).to.be.null;
1302
1354
  });
1303
1355
 
1304
1356
  it('does not reroute connections when node has exits with null destinations', async () => {
@@ -1319,15 +1371,58 @@ describe('EditorNode', () => {
1319
1371
  ]
1320
1372
  };
1321
1373
 
1322
- const destinations = mockNode.exits
1323
- .map((exit) => exit.destination_uuid)
1324
- .filter((dest) => dest);
1374
+ const mockFlowDefinition = {
1375
+ language: 'en',
1376
+ localization: {},
1377
+ name: 'Test Flow',
1378
+ type: 'messaging' as const,
1379
+ uuid: 'test-uuid',
1380
+ revision: 1,
1381
+ spec_version: '14.3',
1382
+ nodes: [
1383
+ {
1384
+ uuid: 'node-before',
1385
+ actions: [],
1386
+ exits: [{ uuid: 'exit-before', destination_uuid: 'test-node' }]
1387
+ },
1388
+ mockNode,
1389
+ {
1390
+ uuid: 'node-after',
1391
+ actions: [],
1392
+ exits: []
1393
+ }
1394
+ ],
1395
+ _ui: {
1396
+ nodes: {},
1397
+ languages: []
1398
+ }
1399
+ };
1400
+
1401
+ // Set up the zustand store with our test flow
1402
+ const { zustand } = await import('../src/store/AppState');
1403
+ zustand.setState({
1404
+ flowDefinition: mockFlowDefinition as any
1405
+ });
1406
+
1407
+ // Verify initial state
1408
+ const initialNodeBefore = zustand
1409
+ .getState()
1410
+ .flowDefinition.nodes.find((n) => n.uuid === 'node-before');
1411
+ expect(initialNodeBefore.exits[0].destination_uuid).to.equal('test-node');
1325
1412
 
1326
- // Verify not all exits have destinations
1327
- expect(destinations).to.have.length(1);
1328
- expect(destinations.length).to.not.equal(mockNode.exits.length);
1413
+ // Call removeNodes
1414
+ zustand.getState().removeNodes(['test-node']);
1329
1415
 
1330
- // This validates that rerouting does NOT apply when some exits have no destination
1416
+ // Verify that the node was removed
1417
+ const remainingNodes = zustand.getState().flowDefinition.nodes;
1418
+ expect(remainingNodes.find((n) => n.uuid === 'test-node')).to.be
1419
+ .undefined;
1420
+
1421
+ // Verify that incoming connection was cleared (set to null), not rerouted
1422
+ const updatedNodeBefore = remainingNodes.find(
1423
+ (n) => n.uuid === 'node-before'
1424
+ );
1425
+ expect(updatedNodeBefore.exits[0].destination_uuid).to.be.null;
1331
1426
  });
1332
1427
  });
1333
1428