@nyaruka/temba-components 0.132.0 → 0.134.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.
- package/CHANGELOG.md +31 -1
- package/demo/components/flow/example.html +1 -0
- package/demo/components/webchat/example.html +1 -1
- package/demo/static/css/tailwind.css +30019 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +2 -11
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +555 -476
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +248 -95
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +4 -4
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/TembaUser.js +3 -3
- package/out-tsc/src/display/TembaUser.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +132 -58
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +183 -58
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/utils.js +141 -0
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/FloatingWindow.js +1 -2
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
- package/out-tsc/src/list/ContentMenu.js +1 -0
- package/out-tsc/src/list/ContentMenu.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +3 -2
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +184 -205
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +2 -11
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/store/AppState.js +34 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/store/Store.js +5 -5
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/utils.js +3 -3
- package/out-tsc/src/utils.js.map +1 -1
- package/out-tsc/src/webchat/WebChat.js +22 -9
- package/out-tsc/src/webchat/WebChat.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +6 -5
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/actions/send_broadcast.test.js +9 -4
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
- package/out-tsc/test/temba-contact-chat.test.js +1 -1
- package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
- package/out-tsc/test/temba-floating-window.test.js +0 -2
- package/out-tsc/test/temba-floating-window.test.js.map +1 -1
- package/out-tsc/test/temba-flow-collision.test.js +673 -0
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +195 -0
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-utils-uuid.test.js +45 -1
- package/out-tsc/test/temba-utils-uuid.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -2
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/contacts/chat-failure.png +0 -0
- package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
- package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
- package/screenshots/truth/floating-tab/default.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/hover.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/display/Chat.ts +331 -135
- package/src/display/FloatingTab.ts +4 -4
- package/src/display/TembaUser.ts +3 -2
- package/src/events.ts +12 -12
- package/src/flow/CanvasNode.ts +140 -57
- package/src/flow/Editor.ts +240 -58
- package/src/flow/utils.ts +207 -1
- package/src/interfaces.ts +7 -0
- package/src/layout/FloatingWindow.ts +1 -3
- package/src/list/ContentMenu.ts +1 -0
- package/src/list/SortableList.ts +3 -2
- package/src/live/ContactChat.ts +195 -221
- package/src/locales/es.ts +13 -18
- package/src/locales/fr.ts +13 -18
- package/src/locales/locale-codes.ts +2 -11
- package/src/locales/pt.ts +13 -18
- package/src/store/AppState.ts +43 -0
- package/src/store/Store.ts +5 -5
- package/src/utils.ts +3 -3
- package/src/webchat/WebChat.ts +24 -10
- package/test/ActionHelper.ts +13 -5
- package/test/actions/send_broadcast.test.ts +4 -2
- package/test/temba-contact-chat.test.ts +1 -1
- package/test/temba-floating-window.test.ts +0 -2
- package/test/temba-flow-collision.test.ts +833 -0
- package/test/temba-flow-editor-node.test.ts +224 -0
- package/test/temba-utils-uuid.test.ts +61 -1
- package/test/utils.test.ts +7 -2
- package/test-assets/contacts/history.json +22 -9
- package/web-test-runner.config.mjs +3 -3
|
@@ -1200,6 +1200,230 @@ describe('EditorNode', () => {
|
|
|
1200
1200
|
// 3. New JSPlumb connections are created with connectIds
|
|
1201
1201
|
// This sequence ensures JSPlumb visuals stay in sync with the flow definition
|
|
1202
1202
|
});
|
|
1203
|
+
|
|
1204
|
+
it('reroutes connections when removing node with multiple exits pointing to same destination', async () => {
|
|
1205
|
+
// Test case: node with multiple exits, but all point to the same destination
|
|
1206
|
+
const mockNode: Node = {
|
|
1207
|
+
uuid: 'test-node',
|
|
1208
|
+
actions: [
|
|
1209
|
+
{
|
|
1210
|
+
type: 'send_msg',
|
|
1211
|
+
uuid: 'action-1',
|
|
1212
|
+
text: 'Hello',
|
|
1213
|
+
quick_replies: []
|
|
1214
|
+
} as any
|
|
1215
|
+
],
|
|
1216
|
+
exits: [
|
|
1217
|
+
{ uuid: 'exit-1', destination_uuid: 'node-after' },
|
|
1218
|
+
{ uuid: 'exit-2', destination_uuid: 'node-after' },
|
|
1219
|
+
{ uuid: 'exit-3', destination_uuid: 'node-after' }
|
|
1220
|
+
]
|
|
1221
|
+
};
|
|
1222
|
+
|
|
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',
|
|
1231
|
+
nodes: [
|
|
1232
|
+
{
|
|
1233
|
+
uuid: 'node-before',
|
|
1234
|
+
actions: [],
|
|
1235
|
+
exits: [{ uuid: 'exit-before', destination_uuid: 'test-node' }]
|
|
1236
|
+
},
|
|
1237
|
+
mockNode,
|
|
1238
|
+
{
|
|
1239
|
+
uuid: 'node-after',
|
|
1240
|
+
actions: [],
|
|
1241
|
+
exits: []
|
|
1242
|
+
}
|
|
1243
|
+
],
|
|
1244
|
+
_ui: {
|
|
1245
|
+
nodes: {},
|
|
1246
|
+
languages: []
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
|
|
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
|
+
});
|
|
1255
|
+
|
|
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');
|
|
1261
|
+
|
|
1262
|
+
// Call removeNodes to trigger the rerouting logic
|
|
1263
|
+
zustand.getState().removeNodes(['test-node']);
|
|
1264
|
+
|
|
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
|
+
|
|
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
|
+
);
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
it('does not reroute connections when node has exits with different destinations', async () => {
|
|
1280
|
+
// Test case: node with multiple exits pointing to different destinations
|
|
1281
|
+
const mockNode: Node = {
|
|
1282
|
+
uuid: 'test-node',
|
|
1283
|
+
actions: [
|
|
1284
|
+
{
|
|
1285
|
+
type: 'send_msg',
|
|
1286
|
+
uuid: 'action-1',
|
|
1287
|
+
text: 'Hello',
|
|
1288
|
+
quick_replies: []
|
|
1289
|
+
} as any
|
|
1290
|
+
],
|
|
1291
|
+
exits: [
|
|
1292
|
+
{ uuid: 'exit-1', destination_uuid: 'node-after-1' },
|
|
1293
|
+
{ uuid: 'exit-2', destination_uuid: 'node-after-2' }
|
|
1294
|
+
]
|
|
1295
|
+
};
|
|
1296
|
+
|
|
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
|
+
});
|
|
1334
|
+
|
|
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;
|
|
1348
|
+
|
|
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;
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
it('does not reroute connections when node has exits with null destinations', async () => {
|
|
1357
|
+
// Test case: node with some exits having null destinations
|
|
1358
|
+
const mockNode: Node = {
|
|
1359
|
+
uuid: 'test-node',
|
|
1360
|
+
actions: [
|
|
1361
|
+
{
|
|
1362
|
+
type: 'send_msg',
|
|
1363
|
+
uuid: 'action-1',
|
|
1364
|
+
text: 'Hello',
|
|
1365
|
+
quick_replies: []
|
|
1366
|
+
} as any
|
|
1367
|
+
],
|
|
1368
|
+
exits: [
|
|
1369
|
+
{ uuid: 'exit-1', destination_uuid: 'node-after' },
|
|
1370
|
+
{ uuid: 'exit-2', destination_uuid: null }
|
|
1371
|
+
]
|
|
1372
|
+
};
|
|
1373
|
+
|
|
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');
|
|
1412
|
+
|
|
1413
|
+
// Call removeNodes
|
|
1414
|
+
zustand.getState().removeNodes(['test-node']);
|
|
1415
|
+
|
|
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;
|
|
1426
|
+
});
|
|
1203
1427
|
});
|
|
1204
1428
|
|
|
1205
1429
|
describe('add action button', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert } from '@open-wc/testing';
|
|
2
|
-
import { generateUUID } from '../src/utils';
|
|
2
|
+
import { generateUUID, generateUUIDv7 } from '../src/utils';
|
|
3
3
|
|
|
4
4
|
describe('UUID Generation', () => {
|
|
5
5
|
it('generates a valid UUID v4 format', () => {
|
|
@@ -45,4 +45,64 @@ describe('UUID Generation', () => {
|
|
|
45
45
|
// All should be unique
|
|
46
46
|
assert.equal(uuids.size, count, 'All generated UUIDs should be unique');
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
it('generates a valid UUID v7 format', () => {
|
|
50
|
+
const uuid = generateUUIDv7();
|
|
51
|
+
|
|
52
|
+
// check that it's a string
|
|
53
|
+
assert.isString(uuid);
|
|
54
|
+
|
|
55
|
+
// check UUID v7 format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
|
|
56
|
+
const uuidPattern =
|
|
57
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
58
|
+
assert.match(uuid, uuidPattern, 'Should match UUID v7 format');
|
|
59
|
+
|
|
60
|
+
// check length
|
|
61
|
+
assert.equal(uuid.length, 36);
|
|
62
|
+
|
|
63
|
+
// check that it contains hyphens in the right places
|
|
64
|
+
assert.equal(uuid[8], '-');
|
|
65
|
+
assert.equal(uuid[13], '-');
|
|
66
|
+
assert.equal(uuid[18], '-');
|
|
67
|
+
assert.equal(uuid[23], '-');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('generates unique UUIDs v7', () => {
|
|
71
|
+
const uuid1 = generateUUIDv7();
|
|
72
|
+
const uuid2 = generateUUIDv7();
|
|
73
|
+
const uuid3 = generateUUIDv7();
|
|
74
|
+
|
|
75
|
+
// all should be different
|
|
76
|
+
assert.notEqual(uuid1, uuid2);
|
|
77
|
+
assert.notEqual(uuid2, uuid3);
|
|
78
|
+
assert.notEqual(uuid1, uuid3);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('generates time-ordered UUIDs v7', () => {
|
|
82
|
+
const uuid1 = generateUUIDv7();
|
|
83
|
+
// small delay to ensure different timestamp
|
|
84
|
+
const delayPromise = new Promise((resolve) => setTimeout(resolve, 5));
|
|
85
|
+
return delayPromise.then(() => {
|
|
86
|
+
const uuid2 = generateUUIDv7();
|
|
87
|
+
|
|
88
|
+
// uuid v7 should be sortable by timestamp
|
|
89
|
+
// the first uuid should come before the second when compared as strings
|
|
90
|
+
assert.isTrue(
|
|
91
|
+
uuid1 < uuid2,
|
|
92
|
+
'Earlier UUID should be lexicographically smaller'
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('generates many unique UUIDs v7', () => {
|
|
98
|
+
const uuids = new Set();
|
|
99
|
+
const count = 1000;
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < count; i++) {
|
|
102
|
+
uuids.add(generateUUIDv7());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// all should be unique
|
|
106
|
+
assert.equal(uuids.size, count, 'All generated UUIDs should be unique');
|
|
107
|
+
});
|
|
48
108
|
});
|
package/test/utils.test.ts
CHANGED
|
@@ -240,7 +240,11 @@ export const waitForCondition = async (
|
|
|
240
240
|
}
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
-
export const assertScreenshot = async (
|
|
243
|
+
export const assertScreenshot = async (
|
|
244
|
+
filename: string,
|
|
245
|
+
clip: Clip,
|
|
246
|
+
waitForNetwork: boolean = false
|
|
247
|
+
) => {
|
|
244
248
|
// detect if we're running in copilot's environment and use adaptive threshold
|
|
245
249
|
const isCopilotEnvironment = (window as any).isCopilotEnvironment;
|
|
246
250
|
const threshold = isCopilotEnvironment ? 1.0 : 0.1;
|
|
@@ -251,7 +255,8 @@ export const assertScreenshot = async (filename: string, clip: Clip) => {
|
|
|
251
255
|
`${filename}.png`,
|
|
252
256
|
clip,
|
|
253
257
|
exclude,
|
|
254
|
-
threshold
|
|
258
|
+
threshold,
|
|
259
|
+
waitForNetwork
|
|
255
260
|
);
|
|
256
261
|
} catch (error) {
|
|
257
262
|
if (error.message) {
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"recent_only": false,
|
|
4
|
-
"next_before": 1617135091567814,
|
|
5
|
-
"next_after": 1609359091567814,
|
|
2
|
+
"next": "01997d74-bf67-749a-8440-688a41c3b270",
|
|
6
3
|
"events": [
|
|
7
4
|
{
|
|
8
5
|
"uuid": "01997d74-bf67-749a-8440-688a41c3b275",
|
|
@@ -54,7 +51,11 @@
|
|
|
54
51
|
"name": "SMS Channel"
|
|
55
52
|
}
|
|
56
53
|
},
|
|
57
|
-
"_status": {
|
|
54
|
+
"_status": {
|
|
55
|
+
"created_on": "2025-09-24T20:40:28.239437+00:00",
|
|
56
|
+
"status": "failed",
|
|
57
|
+
"reason": "error_limit"
|
|
58
|
+
},
|
|
58
59
|
"_logs_url": null
|
|
59
60
|
},
|
|
60
61
|
{
|
|
@@ -89,7 +90,10 @@
|
|
|
89
90
|
"name": "SMS Channel"
|
|
90
91
|
}
|
|
91
92
|
},
|
|
92
|
-
"_status": {
|
|
93
|
+
"_status": {
|
|
94
|
+
"created_on": "2025-09-23T20:40:28.239434+00:00",
|
|
95
|
+
"status": "wired"
|
|
96
|
+
},
|
|
93
97
|
"_logs_url": "/channels/channellog/read/1478/"
|
|
94
98
|
},
|
|
95
99
|
{
|
|
@@ -118,7 +122,10 @@
|
|
|
118
122
|
"name": "SMS Channel"
|
|
119
123
|
}
|
|
120
124
|
},
|
|
121
|
-
"_status": {
|
|
125
|
+
"_status": {
|
|
126
|
+
"created_on": "2025-09-23T20:40:28.239434+00:00",
|
|
127
|
+
"status": "wired"
|
|
128
|
+
},
|
|
122
129
|
"_logs_url": "/channels/channellog/read/1476/"
|
|
123
130
|
},
|
|
124
131
|
{
|
|
@@ -147,7 +154,10 @@
|
|
|
147
154
|
"name": "SMS Channel"
|
|
148
155
|
}
|
|
149
156
|
},
|
|
150
|
-
"_status": {
|
|
157
|
+
"_status": {
|
|
158
|
+
"created_on": "2025-09-23T20:40:28.239434+00:00",
|
|
159
|
+
"status": "wired"
|
|
160
|
+
},
|
|
151
161
|
"_logs_url": "/channels/channellog/read/1474/"
|
|
152
162
|
},
|
|
153
163
|
{
|
|
@@ -198,7 +208,10 @@
|
|
|
198
208
|
"name": "SMS Channel"
|
|
199
209
|
}
|
|
200
210
|
},
|
|
201
|
-
"_status": {
|
|
211
|
+
"_status": {
|
|
212
|
+
"created_on": "2025-09-23T20:40:28.239434+00:00",
|
|
213
|
+
"status": "wired"
|
|
214
|
+
},
|
|
202
215
|
"_logs_url": "/channels/channellog/read/1472/"
|
|
203
216
|
},
|
|
204
217
|
{
|
|
@@ -145,15 +145,15 @@ const wireScreenshots = async (page, context, wait, replaceScreenshots) => {
|
|
|
145
145
|
|
|
146
146
|
page.exposeFunction(
|
|
147
147
|
'matchPageSnapshot',
|
|
148
|
-
(filename, clip, excluded, threshold) => {
|
|
148
|
+
(filename, clip, excluded, threshold, waitForNetwork = false) => {
|
|
149
149
|
return new Promise(async (resolve, reject) => {
|
|
150
150
|
// const start = Date.now();
|
|
151
151
|
const testFile = await getPath(TEST, filename);
|
|
152
152
|
const truthFile = await getPath(TRUTH, filename);
|
|
153
153
|
|
|
154
|
-
//
|
|
154
|
+
// Wait for network idle - use per-call parameter or fall back to global wait flag
|
|
155
155
|
try{
|
|
156
|
-
if (wait) {
|
|
156
|
+
if (waitForNetwork || wait) {
|
|
157
157
|
await page.waitForNetworkIdle({idleTime: 500, timeout: 2000});
|
|
158
158
|
} else {
|
|
159
159
|
await page.waitForNetworkIdle({ idleTime: 100, timeout: 1000 });
|