@nyaruka/temba-components 0.136.1 → 0.138.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 +19 -0
- package/demo/components/webchat/example.html +2 -2
- package/dist/temba-components.js +692 -622
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +123 -44
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +2 -2
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/events/eventRenderers.js +442 -0
- package/out-tsc/src/events/eventRenderers.js.map +1 -0
- package/out-tsc/src/flow/CanvasNode.js +45 -24
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +308 -18
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +0 -1
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +110 -64
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/list/ShortcutList.js +1 -1
- package/out-tsc/src/list/ShortcutList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +12 -321
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +439 -575
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +12 -2
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +2 -1
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-revisions.test.js +106 -0
- package/out-tsc/test/temba-flow-editor-revisions.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor.test.js +14 -10
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +7 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +6 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-simulator.test.js +51 -32
- package/out-tsc/test/temba-simulator.test.js.map +1 -1
- package/package.json +1 -1
- 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/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/simulator/after-message-sent.png +0 -0
- package/screenshots/truth/simulator/after-reset.png +0 -0
- package/screenshots/truth/simulator/attachment-menu.png +0 -0
- package/screenshots/truth/simulator/context-expanded.png +0 -0
- package/screenshots/truth/simulator/context-explorer-open.png +0 -0
- package/screenshots/truth/simulator/event-info.png +0 -0
- package/screenshots/truth/simulator/image-attachment.png +0 -0
- package/screenshots/truth/simulator/open-initial.png +0 -0
- package/screenshots/truth/simulator/quick-replies.png +0 -0
- package/src/display/Chat.ts +123 -44
- package/src/display/FloatingTab.ts +2 -2
- package/src/events/eventRenderers.ts +527 -0
- package/src/flow/CanvasNode.ts +54 -29
- package/src/flow/Editor.ts +360 -19
- package/src/flow/NodeEditor.ts +0 -1
- package/src/flow/Plumber.ts +123 -69
- package/src/list/ShortcutList.ts +1 -1
- package/src/live/ContactChat.ts +17 -376
- package/src/simulator/Simulator.ts +498 -617
- package/src/store/AppState.ts +13 -2
- package/test/temba-flow-editor-node.test.ts +2 -1
- package/test/temba-flow-editor-revisions.test.ts +134 -0
- package/test/temba-flow-editor.test.ts +16 -10
- package/test/temba-flow-plumber-connections.test.ts +7 -1
- package/test/temba-flow-plumber.test.ts +6 -0
- package/test/temba-simulator.test.ts +64 -34
package/src/store/AppState.ts
CHANGED
|
@@ -105,6 +105,7 @@ export interface AppState {
|
|
|
105
105
|
languageNames: { [code: string]: Language };
|
|
106
106
|
workspace: Workspace;
|
|
107
107
|
isTranslating: boolean;
|
|
108
|
+
viewingRevision: boolean;
|
|
108
109
|
|
|
109
110
|
dirtyDate: Date | null;
|
|
110
111
|
|
|
@@ -174,6 +175,7 @@ export const zustand = createStore<AppState>()(
|
|
|
174
175
|
flowDefinition: null,
|
|
175
176
|
flowInfo: null,
|
|
176
177
|
isTranslating: false,
|
|
178
|
+
viewingRevision: false,
|
|
177
179
|
dirtyDate: null,
|
|
178
180
|
activity: null,
|
|
179
181
|
simulatorActivity: null,
|
|
@@ -187,6 +189,7 @@ export const zustand = createStore<AppState>()(
|
|
|
187
189
|
},
|
|
188
190
|
|
|
189
191
|
fetchRevision: async (endpoint: string, id: string = null) => {
|
|
192
|
+
const viewingRevision = !!id && id !== 'latest';
|
|
190
193
|
if (!id) {
|
|
191
194
|
id = 'latest';
|
|
192
195
|
}
|
|
@@ -198,7 +201,11 @@ export const zustand = createStore<AppState>()(
|
|
|
198
201
|
throw new Error('Network response was not ok');
|
|
199
202
|
}
|
|
200
203
|
const data = (await response.json()) as FlowContents;
|
|
201
|
-
set({
|
|
204
|
+
set({
|
|
205
|
+
flowInfo: data.info,
|
|
206
|
+
flowDefinition: data.definition,
|
|
207
|
+
viewingRevision
|
|
208
|
+
});
|
|
202
209
|
},
|
|
203
210
|
|
|
204
211
|
fetchWorkspace: async (endpoint) => {
|
|
@@ -282,7 +289,11 @@ export const zustand = createStore<AppState>()(
|
|
|
282
289
|
setFlowContents: (flow: FlowContents) => {
|
|
283
290
|
set((state: AppState) => {
|
|
284
291
|
const flowLang = flow.definition.language;
|
|
285
|
-
|
|
292
|
+
// Clone to ensure mutable for sorting
|
|
293
|
+
state.flowDefinition = {
|
|
294
|
+
...flow.definition,
|
|
295
|
+
nodes: [...(flow.definition.nodes || [])]
|
|
296
|
+
};
|
|
286
297
|
state.flowInfo = flow.info;
|
|
287
298
|
// Reset to the flow's default language when loading a new flow
|
|
288
299
|
state.languageCode = flowLang;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { html, fixture, expect } from '@open-wc/testing';
|
|
2
|
+
import { Editor } from '../src/flow/Editor';
|
|
3
|
+
import { stub, restore, SinonStub } from 'sinon';
|
|
4
|
+
|
|
5
|
+
customElements.define('temba-flow-editor-revisions', Editor);
|
|
6
|
+
|
|
7
|
+
describe('Editor Revisions', () => {
|
|
8
|
+
let element: Editor;
|
|
9
|
+
let fetchStub: SinonStub;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
restore();
|
|
13
|
+
fetchStub = stub(window, 'fetch');
|
|
14
|
+
// Initialize without 'flow' attribute to prevent firstUpdated from calling getStore().getState()
|
|
15
|
+
element = await fixture(
|
|
16
|
+
html`<temba-flow-editor-revisions></temba-flow-editor-revisions>`
|
|
17
|
+
);
|
|
18
|
+
element.flow = 'test-flow';
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
restore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should exclude the most recent revision from the list', async () => {
|
|
26
|
+
const mockRevisions = [
|
|
27
|
+
{
|
|
28
|
+
id: 3,
|
|
29
|
+
created_on: '2023-01-03',
|
|
30
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 2,
|
|
34
|
+
created_on: '2023-01-02',
|
|
35
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 1,
|
|
39
|
+
created_on: '2023-01-01',
|
|
40
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Mock the fetch response for the revisions list
|
|
45
|
+
const mockResponse = new Response(
|
|
46
|
+
JSON.stringify({ results: mockRevisions }),
|
|
47
|
+
{
|
|
48
|
+
status: 200,
|
|
49
|
+
headers: { 'Content-Type': 'application/json' }
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
fetchStub.resolves(mockResponse);
|
|
54
|
+
|
|
55
|
+
// Call fetchRevisions (private)
|
|
56
|
+
// Note: fetchRevisions calls fetchResults -> fetchResultsPage -> fetch
|
|
57
|
+
await (element as any).fetchRevisions();
|
|
58
|
+
|
|
59
|
+
const revisions = (element as any).revisions;
|
|
60
|
+
expect(revisions.length).to.equal(2);
|
|
61
|
+
expect(revisions[0].id).to.equal(2);
|
|
62
|
+
expect(revisions[1].id).to.equal(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle empty revisions list', async () => {
|
|
66
|
+
const mockResponse = new Response(JSON.stringify({ results: [] }), {
|
|
67
|
+
status: 200
|
|
68
|
+
});
|
|
69
|
+
fetchStub.resolves(mockResponse);
|
|
70
|
+
|
|
71
|
+
await (element as any).fetchRevisions();
|
|
72
|
+
const revisions = (element as any).revisions;
|
|
73
|
+
expect(revisions.length).to.equal(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle single revision in list', async () => {
|
|
77
|
+
const mockRevisions = [
|
|
78
|
+
{
|
|
79
|
+
id: 1,
|
|
80
|
+
created_on: '2023-01-01',
|
|
81
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
82
|
+
}
|
|
83
|
+
];
|
|
84
|
+
const mockResponse = new Response(
|
|
85
|
+
JSON.stringify({ results: mockRevisions }),
|
|
86
|
+
{ status: 200 }
|
|
87
|
+
);
|
|
88
|
+
fetchStub.resolves(mockResponse);
|
|
89
|
+
|
|
90
|
+
await (element as any).fetchRevisions();
|
|
91
|
+
const revisions = (element as any).revisions;
|
|
92
|
+
expect(revisions.length).to.equal(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should have purple color for revisions tab and blue for selected item', async () => {
|
|
96
|
+
// Force revisions window to show
|
|
97
|
+
(element as any).revisionsWindowHidden = false;
|
|
98
|
+
(element as any).localizationWindowHidden = true;
|
|
99
|
+
|
|
100
|
+
// Mock revisions so we can see list items
|
|
101
|
+
const mockRevisions = [
|
|
102
|
+
{
|
|
103
|
+
id: 2,
|
|
104
|
+
created_on: '2023-01-02',
|
|
105
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 1,
|
|
109
|
+
created_on: '2023-01-01',
|
|
110
|
+
user: { id: 1, first_name: 'A', last_name: 'B', username: 'ab' }
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
(element as any).revisions = mockRevisions;
|
|
114
|
+
(element as any).viewingRevision = mockRevisions[0]; // Select the first one
|
|
115
|
+
|
|
116
|
+
await element.requestUpdate();
|
|
117
|
+
|
|
118
|
+
// Check tab color
|
|
119
|
+
const tab = element.querySelector('#revisions-tab');
|
|
120
|
+
expect(tab).to.exist;
|
|
121
|
+
expect(tab.getAttribute('color')).to.equal('rgb(142, 94, 167)');
|
|
122
|
+
|
|
123
|
+
// Check selected item styles
|
|
124
|
+
const selectedItem = element.querySelector(
|
|
125
|
+
'.revision-item.selected'
|
|
126
|
+
) as HTMLElement;
|
|
127
|
+
expect(selectedItem).to.exist;
|
|
128
|
+
|
|
129
|
+
// We need to check inline styles because they are set in the template
|
|
130
|
+
const style = selectedItem.getAttribute('style');
|
|
131
|
+
expect(style).to.contain('#f0f6ff'); // Blue background
|
|
132
|
+
expect(style).to.contain('#a4cafe'); // Blue border
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -653,11 +653,14 @@ describe('Editor', () => {
|
|
|
653
653
|
|
|
654
654
|
await editor.updateComplete;
|
|
655
655
|
|
|
656
|
-
// node-2
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
expect(
|
|
656
|
+
// node-2 is at top (top: 100 < top: 200) so it should be flow-start
|
|
657
|
+
const node1 = editor.querySelector('temba-flow-node[uuid="node-1"]');
|
|
658
|
+
const node2 = editor.querySelector('temba-flow-node[uuid="node-2"]');
|
|
659
|
+
|
|
660
|
+
expect(node2).to.exist;
|
|
661
|
+
expect(node1).to.exist;
|
|
662
|
+
expect(node2.classList.contains('flow-start')).to.be.true;
|
|
663
|
+
expect(node1.classList.contains('flow-start')).to.be.false;
|
|
661
664
|
|
|
662
665
|
// move node-1 to the top
|
|
663
666
|
zustand.getState().updateCanvasPositions({
|
|
@@ -720,7 +723,7 @@ describe('Editor', () => {
|
|
|
720
723
|
|
|
721
724
|
await editor.updateComplete;
|
|
722
725
|
|
|
723
|
-
|
|
726
|
+
const flowNodes = editor.querySelectorAll('temba-flow-node');
|
|
724
727
|
expect(flowNodes[0].classList.contains('flow-start')).to.be.true;
|
|
725
728
|
|
|
726
729
|
// add a new node at the top
|
|
@@ -741,10 +744,13 @@ describe('Editor', () => {
|
|
|
741
744
|
await editor.updateComplete;
|
|
742
745
|
|
|
743
746
|
// new node should now be the flow-start
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
expect(
|
|
747
|
+
const node1 = editor.querySelector('temba-flow-node[uuid="node-1"]');
|
|
748
|
+
const node2 = editor.querySelector('temba-flow-node[uuid="node-2"]');
|
|
749
|
+
|
|
750
|
+
expect(node2).to.exist;
|
|
751
|
+
expect(node1).to.exist;
|
|
752
|
+
expect(node2.classList.contains('flow-start')).to.be.true;
|
|
753
|
+
expect(node1.classList.contains('flow-start')).to.be.false;
|
|
748
754
|
});
|
|
749
755
|
|
|
750
756
|
it('should handle flow-start when first node is removed', async () => {
|
|
@@ -29,7 +29,14 @@ describe('Plumber - Connection Management', () => {
|
|
|
29
29
|
removeClass: stub(),
|
|
30
30
|
batch: stub().callsFake((fn: any) => fn()),
|
|
31
31
|
addEndpoint: stub().returns({}),
|
|
32
|
+
revalidate: stub(),
|
|
32
33
|
connect: stub(),
|
|
34
|
+
getEndpoints: stub().returns([
|
|
35
|
+
{ elementId: 'test-from', addClass: stub() }
|
|
36
|
+
]),
|
|
37
|
+
select: stub().returns({
|
|
38
|
+
deleteAll: stub()
|
|
39
|
+
}),
|
|
33
40
|
selectEndpoints: stub().returns({
|
|
34
41
|
deleteAll: stub()
|
|
35
42
|
}),
|
|
@@ -131,7 +138,6 @@ describe('Plumber - Connection Management', () => {
|
|
|
131
138
|
expect(result).to.be.true;
|
|
132
139
|
expect((plumber as any).jsPlumb.deleteConnection).to.have.been
|
|
133
140
|
.calledTwice;
|
|
134
|
-
expect((plumber as any).jsPlumb.removeAllEndpoints).to.have.been.called;
|
|
135
141
|
});
|
|
136
142
|
|
|
137
143
|
it('returns false when no connections exist', () => {
|
|
@@ -31,6 +31,12 @@ describe('Plumber', () => {
|
|
|
31
31
|
batch: stub().callsFake((fn) => fn()),
|
|
32
32
|
addEndpoint: stub().returns({}),
|
|
33
33
|
connect: stub(),
|
|
34
|
+
getEndpoints: stub().returns([
|
|
35
|
+
{ elementId: 'test-from', addClass: stub() }
|
|
36
|
+
]),
|
|
37
|
+
select: stub().returns({
|
|
38
|
+
deleteAll: stub()
|
|
39
|
+
}),
|
|
34
40
|
selectEndpoints: stub().returns({
|
|
35
41
|
deleteAll: stub()
|
|
36
42
|
}),
|
|
@@ -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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
359
|
-
expect(
|
|
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
|
-
|
|
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
|
-
//
|
|
401
|
-
|
|
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
|
-
|
|
405
|
-
expect(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
516
|
-
const
|
|
517
|
-
|
|
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
|
-
|
|
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
|
-
|
|
721
|
-
expect(
|
|
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
|
|
807
|
-
const
|
|
808
|
-
expect(
|
|
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
|
});
|