@nyaruka/temba-components 0.41.7 → 0.42.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 (122) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/demo/index.html +22 -0
  3. package/dist/{973a6cc2.js → dae3a0ce.js} +398 -158
  4. package/dist/index.js +398 -158
  5. package/dist/sw.js +1 -1
  6. package/dist/sw.js.map +1 -1
  7. package/dist/templates/components-body.html +1 -1
  8. package/dist/templates/components-head.html +1 -1
  9. package/out-tsc/src/aliaseditor/AliasEditor.js +2 -1
  10. package/out-tsc/src/aliaseditor/AliasEditor.js.map +1 -1
  11. package/out-tsc/src/button/Button.js +3 -3
  12. package/out-tsc/src/button/Button.js.map +1 -1
  13. package/out-tsc/src/charcount/CharCount.js +5 -4
  14. package/out-tsc/src/charcount/CharCount.js.map +1 -1
  15. package/out-tsc/src/completion/Completion.js +5 -0
  16. package/out-tsc/src/completion/Completion.js.map +1 -1
  17. package/out-tsc/src/compose/Compose.js +532 -0
  18. package/out-tsc/src/compose/Compose.js.map +1 -0
  19. package/out-tsc/src/contacts/ContactChat.js +73 -69
  20. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  21. package/out-tsc/src/contacts/ContactHistory.js +1 -1
  22. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  23. package/out-tsc/src/contacts/events.js +1 -0
  24. package/out-tsc/src/contacts/events.js.map +1 -1
  25. package/out-tsc/src/interfaces.js +2 -0
  26. package/out-tsc/src/interfaces.js.map +1 -1
  27. package/out-tsc/src/textinput/TextInput.js +3 -1
  28. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  29. package/out-tsc/src/utils/index.js +10 -0
  30. package/out-tsc/src/utils/index.js.map +1 -1
  31. package/out-tsc/src/vectoricon/index.js +1 -0
  32. package/out-tsc/src/vectoricon/index.js.map +1 -1
  33. package/out-tsc/temba-modules.js +2 -0
  34. package/out-tsc/temba-modules.js.map +1 -1
  35. package/out-tsc/test/temba-compose.test.js +432 -0
  36. package/out-tsc/test/temba-compose.test.js.map +1 -0
  37. package/out-tsc/test/temba-contact-chat.test.js +219 -47
  38. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  39. package/out-tsc/test/temba-contact-history.test.js +7 -3
  40. package/out-tsc/test/temba-contact-history.test.js.map +1 -1
  41. package/out-tsc/test/utils.test.js +12 -8
  42. package/out-tsc/test/utils.test.js.map +1 -1
  43. package/package.json +1 -1
  44. package/screenshots/truth/compose/attachments-and-send-button.png +0 -0
  45. package/screenshots/truth/compose/attachments-no-send-button.png +0 -0
  46. package/screenshots/truth/compose/attachments-with-all-files-and-click-send.png +0 -0
  47. package/screenshots/truth/compose/attachments-with-all-files.png +0 -0
  48. package/screenshots/truth/compose/attachments-with-failure-files.png +0 -0
  49. package/screenshots/truth/compose/attachments-with-success-files-and-click-send.png +0 -0
  50. package/screenshots/truth/compose/attachments-with-success-files.png +0 -0
  51. package/screenshots/truth/compose/chatbox-attachments-counter-and-send-button.png +0 -0
  52. package/screenshots/truth/compose/chatbox-attachments-counter-no-send-button.png +0 -0
  53. package/screenshots/truth/compose/chatbox-attachments-no-counter-and-send-button.png +0 -0
  54. package/screenshots/truth/compose/chatbox-attachments-no-counter-no-send-button.png +0 -0
  55. package/screenshots/truth/compose/chatbox-counter-and-send-button.png +0 -0
  56. package/screenshots/truth/compose/chatbox-counter-no-send-button.png +0 -0
  57. package/screenshots/truth/compose/chatbox-no-counter-and-send-button.png +0 -0
  58. package/screenshots/truth/compose/chatbox-no-counter-no-send-button.png +0 -0
  59. package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files-and-click-send.png +0 -0
  60. package/screenshots/truth/compose/chatbox-no-text-attachments-with-all-files.png +0 -0
  61. package/screenshots/truth/compose/chatbox-no-text-attachments-with-failure-files.png +0 -0
  62. package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files-and-click-send.png +0 -0
  63. package/screenshots/truth/compose/chatbox-no-text-attachments-with-success-files.png +0 -0
  64. package/screenshots/truth/compose/chatbox-with-text-and-click-send.png +0 -0
  65. package/screenshots/truth/compose/chatbox-with-text-and-hit-enter.png +0 -0
  66. package/screenshots/truth/compose/chatbox-with-text-and-spaces.png +0 -0
  67. package/screenshots/truth/compose/chatbox-with-text-and-url.png +0 -0
  68. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-click-send.png +0 -0
  69. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files-and-hit-enter.png +0 -0
  70. package/screenshots/truth/compose/chatbox-with-text-attachments-no-files.png +0 -0
  71. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-click-send.png +0 -0
  72. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files-and-hit-enter.png +0 -0
  73. package/screenshots/truth/compose/chatbox-with-text-attachments-with-all-files.png +0 -0
  74. package/screenshots/truth/compose/chatbox-with-text-attachments-with-failure-files.png +0 -0
  75. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-click-send.png +0 -0
  76. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files-and-hit-enter.png +0 -0
  77. package/screenshots/truth/compose/chatbox-with-text-attachments-with-success-files.png +0 -0
  78. package/screenshots/truth/compose/chatbox-with-text-no-spaces.png +0 -0
  79. package/screenshots/truth/compose/chatbox-with-text.png +0 -0
  80. package/screenshots/truth/contacts/badges.png +0 -0
  81. package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
  82. package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
  83. package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
  84. package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
  85. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
  86. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
  87. package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
  88. package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
  89. package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
  90. package/screenshots/truth/contacts/contact-active-default.png +0 -0
  91. package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
  92. package/screenshots/truth/contacts/contact-active-ticket-closed-show-reopen-button.png +0 -0
  93. package/screenshots/truth/contacts/contact-active-ticket-open-show-chatbox.png +0 -0
  94. package/screenshots/truth/contacts/contact-archived-hide-chatbox.png +0 -0
  95. package/screenshots/truth/contacts/contact-archived-ticket-closed-hide-chatbox.png +0 -0
  96. package/screenshots/truth/contacts/contact-blocked-hide-chatbox.png +0 -0
  97. package/screenshots/truth/contacts/contact-stopped-hide-chatbox.png +0 -0
  98. package/screenshots/truth/contacts/history.png +0 -0
  99. package/screenshots/truth/counter/summary.png +0 -0
  100. package/screenshots/truth/counter/text.png +0 -0
  101. package/screenshots/truth/counter/unicode-variables.png +0 -0
  102. package/screenshots/truth/counter/unicode.png +0 -0
  103. package/screenshots/truth/counter/variable.png +0 -0
  104. package/src/aliaseditor/AliasEditor.ts +2 -1
  105. package/src/button/Button.ts +3 -3
  106. package/src/charcount/CharCount.ts +5 -4
  107. package/src/completion/Completion.ts +4 -0
  108. package/src/compose/Compose.ts +585 -0
  109. package/src/contacts/ContactChat.ts +75 -78
  110. package/src/contacts/ContactHistory.ts +1 -1
  111. package/src/contacts/events.ts +1 -0
  112. package/src/interfaces.ts +2 -0
  113. package/src/textinput/TextInput.ts +3 -1
  114. package/src/utils/index.ts +12 -0
  115. package/src/vectoricon/index.ts +1 -0
  116. package/static/css/temba-components.css +6 -0
  117. package/temba-modules.ts +2 -0
  118. package/test/temba-compose.test.ts +633 -0
  119. package/test/temba-contact-chat.test.ts +354 -110
  120. package/test/temba-contact-history.test.ts +10 -5
  121. package/test/utils.test.ts +24 -9
  122. package/screenshots/truth/contacts/history-expanded.png +0 -0
@@ -1,17 +1,25 @@
1
- import { expect, waitUntil } from '@open-wc/testing';
2
1
  import { useFakeTimers } from 'sinon';
2
+ import { Button } from '../src/button/Button';
3
+ import { Compose } from '../src/compose/Compose';
3
4
  import { ContactChat } from '../src/contacts/ContactChat';
4
- import { ContactHistory } from '../src/contacts/ContactHistory';
5
5
  import { CustomEventType } from '../src/interfaces';
6
6
  import { TicketList } from '../src/list/TicketList';
7
7
  import {
8
8
  assertScreenshot,
9
+ clearMockPosts,
9
10
  getClip,
10
11
  getComponent,
11
12
  loadStore,
12
13
  mockGET,
13
14
  mockNow,
15
+ mockPOST,
14
16
  } from '../test/utils.test';
17
+ import {
18
+ getFailText,
19
+ getSuccessFiles,
20
+ getSuccessText,
21
+ updateComponent,
22
+ } from './temba-compose.test';
15
23
 
16
24
  let clock: any;
17
25
  mockNow('2021-03-31T00:31:00.000-00:00');
@@ -85,21 +93,6 @@ describe('temba-contact-chat - contact tests', () => {
85
93
  contact: 'contact-dave-active',
86
94
  });
87
95
 
88
- const chatHistoryEl = chat.shadowRoot.querySelector(
89
- 'temba-contact-history'
90
- ) as ContactHistory;
91
- expect(chatHistoryEl).to.not.equal(null);
92
-
93
- const chatboxDivEl = chat.shadowRoot.querySelector(
94
- '.chatbox'
95
- ) as HTMLDivElement;
96
- expect(chatboxDivEl).to.not.equal(null);
97
-
98
- const reopenButton = chat.shadowRoot.querySelector(
99
- 'temba-button#reopen-button'
100
- ) as HTMLDivElement;
101
- expect(reopenButton).to.equal(null);
102
-
103
96
  await assertScreenshot(
104
97
  'contacts/contact-active-show-chatbox',
105
98
  getClip(chat)
@@ -113,21 +106,6 @@ describe('temba-contact-chat - contact tests', () => {
113
106
  contact: 'contact-barack-archived',
114
107
  });
115
108
 
116
- const chatHistoryEl = chat.shadowRoot.querySelector(
117
- 'temba-contact-history'
118
- ) as ContactHistory;
119
- expect(chatHistoryEl).to.not.equal(null);
120
-
121
- const chatboxDiv = chat.shadowRoot.querySelector(
122
- '.chatbox'
123
- ) as HTMLDivElement;
124
- expect(chatboxDiv).to.equal(null);
125
-
126
- const reopenButton = chat.shadowRoot.querySelector(
127
- 'temba-button#reopen-button'
128
- ) as HTMLDivElement;
129
- expect(reopenButton).to.equal(null);
130
-
131
109
  await assertScreenshot(
132
110
  'contacts/contact-archived-hide-chatbox',
133
111
  getClip(chat)
@@ -141,21 +119,6 @@ describe('temba-contact-chat - contact tests', () => {
141
119
  contact: 'contact-michelle-blocked',
142
120
  });
143
121
 
144
- const chatHistoryEl = chat.shadowRoot.querySelector(
145
- 'temba-contact-history'
146
- ) as ContactHistory;
147
- expect(chatHistoryEl).to.not.equal(null);
148
-
149
- const chatboxDiv = chat.shadowRoot.querySelector(
150
- '.chatbox'
151
- ) as HTMLDivElement;
152
- expect(chatboxDiv).to.equal(null);
153
-
154
- const reopenButton = chat.shadowRoot.querySelector(
155
- 'temba-button#reopen-button'
156
- ) as HTMLDivElement;
157
- expect(reopenButton).to.equal(null);
158
-
159
122
  await assertScreenshot(
160
123
  'contacts/contact-blocked-hide-chatbox',
161
124
  getClip(chat)
@@ -169,23 +132,338 @@ describe('temba-contact-chat - contact tests', () => {
169
132
  contact: 'contact-tim-stopped',
170
133
  });
171
134
 
172
- const chatHistoryEl = chat.shadowRoot.querySelector(
173
- 'temba-contact-history'
174
- ) as ContactHistory;
175
- expect(chatHistoryEl).to.not.equal(null, 'Chat history missing');
135
+ await assertScreenshot(
136
+ 'contacts/contact-stopped-hide-chatbox',
137
+ getClip(chat)
138
+ );
139
+ });
140
+ });
141
+
142
+ describe('temba-contact-chat - contact tests - handle send tests - text no attachments', () => {
143
+ beforeEach(() => {
144
+ clearMockPosts();
145
+ mockGET(
146
+ /\/contact\/history\/contact-.*/,
147
+ '/test-assets/contacts/history.json'
148
+ );
149
+ clock = useFakeTimers();
150
+ });
151
+
152
+ afterEach(function () {
153
+ clock.restore();
154
+ });
155
+
156
+ it('with text no attachments - success', async () => {
157
+ // we are a StoreElement, so load a store first
158
+ await loadStore();
159
+ const chat: ContactChat = await getContactChat({
160
+ contact: 'contact-dave-active',
161
+ });
162
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
163
+ await updateComponent(compose, getSuccessText());
176
164
 
177
- const chatboxDiv = chat.shadowRoot.querySelector(
178
- '.chatbox'
179
- ) as HTMLDivElement;
180
- expect(chatboxDiv).to.equal(null);
165
+ const response_body = {
166
+ contacts: [{ uuid: 'contact-dave-active', name: 'Dave Matthews' }],
167
+ text: { eng: 'sà-wàd-dee!' },
168
+ attachments: { eng: [] },
169
+ };
170
+ mockPOST(/api\/v2\/broadcasts\.json/, response_body);
181
171
 
182
- const reopenButton = chat.shadowRoot.querySelector(
183
- 'temba-button#reopen-button'
184
- ) as HTMLDivElement;
185
- expect(reopenButton).to.equal(null);
172
+ const send = compose.shadowRoot.querySelector(
173
+ 'temba-button#send-button'
174
+ ) as Button;
175
+ send.click();
186
176
 
187
177
  await assertScreenshot(
188
- 'contacts/contact-stopped-hide-chatbox',
178
+ 'contacts/compose-text-no-attachments-success',
179
+ getClip(chat)
180
+ );
181
+ });
182
+
183
+ it('with text no attachments - failure - more than 640 chars', async () => {
184
+ // we are a StoreElement, so load a store first
185
+ await loadStore();
186
+ const chat: ContactChat = await getContactChat({
187
+ contact: 'contact-dave-active',
188
+ });
189
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
190
+ // set the chatbox to a string that is 640+ chars
191
+ await updateComponent(compose, getFailText());
192
+
193
+ const response_body = {
194
+ text: { eng: ['Ensure this field has no more than 640 characters.'] },
195
+ };
196
+ const response_headers = {};
197
+ const response_status = '400';
198
+ mockPOST(
199
+ /api\/v2\/broadcasts\.json/,
200
+ response_body,
201
+ response_headers,
202
+ response_status
203
+ );
204
+
205
+ const send = compose.shadowRoot.querySelector(
206
+ 'temba-button#send-button'
207
+ ) as Button;
208
+ send.click();
209
+
210
+ await assertScreenshot(
211
+ 'contacts/compose-text-no-attachments-failure',
212
+ getClip(chat)
213
+ );
214
+ });
215
+ });
216
+
217
+ describe('temba-contact-chat - contact tests - handle send tests - attachments no text', () => {
218
+ beforeEach(() => {
219
+ clearMockPosts();
220
+ mockGET(
221
+ /\/contact\/history\/contact-.*/,
222
+ '/test-assets/contacts/history.json'
223
+ );
224
+ clock = useFakeTimers();
225
+ });
226
+
227
+ afterEach(function () {
228
+ clock.restore();
229
+ });
230
+
231
+ it('with attachments no text - success', async () => {
232
+ // we are a StoreElement, so load a store first
233
+ await loadStore();
234
+ const chat: ContactChat = await getContactChat({
235
+ contact: 'contact-dave-active',
236
+ });
237
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
238
+ const attachments = getSuccessFiles();
239
+ await updateComponent(compose, null, attachments);
240
+ const response_body = {
241
+ contacts: [{ uuid: 'contact-dave-active', name: 'Dave Matthews' }],
242
+ text: { eng: '' },
243
+ attachments: { eng: attachments },
244
+ };
245
+ const response_headers = {};
246
+ const response_status = '200';
247
+ mockPOST(
248
+ /api\/v2\/broadcasts\.json/,
249
+ response_body,
250
+ response_headers,
251
+ response_status
252
+ );
253
+
254
+ const send = compose.shadowRoot.querySelector(
255
+ 'temba-button#send-button'
256
+ ) as Button;
257
+ send.click();
258
+
259
+ await assertScreenshot(
260
+ 'contacts/compose-attachments-no-text-success',
261
+ getClip(chat)
262
+ );
263
+ });
264
+ it('with attachments no text - failure - more than 10 files', async () => {
265
+ // we are a StoreElement, so load a store first
266
+ await loadStore();
267
+ const chat: ContactChat = await getContactChat({
268
+ contact: 'contact-dave-active',
269
+ });
270
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
271
+ // set the attachments to a list that is 10+ items
272
+ await updateComponent(compose, null, getSuccessFiles(11));
273
+
274
+ const response_body = {
275
+ attachments: { eng: ['Ensure this field has no more than 10 elements.'] },
276
+ };
277
+ const response_headers = {};
278
+ const response_status = '400';
279
+ mockPOST(
280
+ /api\/v2\/broadcasts\.json/,
281
+ response_body,
282
+ response_headers,
283
+ response_status
284
+ );
285
+
286
+ const send = compose.shadowRoot.querySelector(
287
+ 'temba-button#send-button'
288
+ ) as Button;
289
+ send.click();
290
+
291
+ await assertScreenshot(
292
+ 'contacts/compose-attachments-no-text-failure',
293
+ getClip(chat)
294
+ );
295
+ });
296
+ });
297
+
298
+ describe('temba-contact-chat - contact tests - handle send tests - text and attachments', () => {
299
+ beforeEach(() => {
300
+ clearMockPosts();
301
+ mockGET(
302
+ /\/contact\/history\/contact-.*/,
303
+ '/test-assets/contacts/history.json'
304
+ );
305
+ clock = useFakeTimers();
306
+ });
307
+
308
+ afterEach(function () {
309
+ clock.restore();
310
+ });
311
+
312
+ it('with text and attachments - success', async () => {
313
+ // we are a StoreElement, so load a store first
314
+ await loadStore();
315
+ const chat: ContactChat = await getContactChat({
316
+ contact: 'contact-dave-active',
317
+ });
318
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
319
+ await updateComponent(compose, getSuccessText(), getSuccessFiles());
320
+
321
+ const text = getSuccessText();
322
+ const attachments = getSuccessFiles();
323
+ const response_body = {
324
+ contacts: [{ uuid: 'contact-dave-active', name: 'Dave Matthews' }],
325
+ text: { eng: text },
326
+ attachments: { eng: attachments },
327
+ };
328
+ mockPOST(/api\/v2\/broadcasts\.json/, response_body);
329
+
330
+ const send = compose.shadowRoot.querySelector(
331
+ 'temba-button#send-button'
332
+ ) as Button;
333
+ send.click();
334
+
335
+ await assertScreenshot(
336
+ 'contacts/compose-text-and-attachments-success',
337
+ getClip(chat)
338
+ );
339
+ });
340
+
341
+ it('with text and attachments - failure - more than 640 chars', async () => {
342
+ // we are a StoreElement, so load a store first
343
+ await loadStore();
344
+ const chat: ContactChat = await getContactChat({
345
+ contact: 'contact-dave-active',
346
+ });
347
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
348
+ // set the chatbox to a string that is 640+ chars
349
+ await updateComponent(compose, getFailText(), getSuccessFiles());
350
+
351
+ const response_body = {
352
+ text: { eng: ['Ensure this field has no more than 640 characters.'] },
353
+ };
354
+ const response_headers = {};
355
+ const response_status = '400';
356
+ mockPOST(
357
+ /api\/v2\/broadcasts\.json/,
358
+ response_body,
359
+ response_headers,
360
+ response_status
361
+ );
362
+
363
+ const send = compose.shadowRoot.querySelector(
364
+ 'temba-button#send-button'
365
+ ) as Button;
366
+ send.click();
367
+
368
+ await assertScreenshot(
369
+ 'contacts/compose-text-and-attachments-failure-text',
370
+ getClip(chat)
371
+ );
372
+ });
373
+
374
+ it('with text and attachments - failure - more than 10 files', async () => {
375
+ // we are a StoreElement, so load a store first
376
+ await loadStore();
377
+ const chat: ContactChat = await getContactChat({
378
+ contact: 'contact-dave-active',
379
+ });
380
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
381
+ // set the attachments to a list that is 10+ items
382
+ await updateComponent(compose, getSuccessText(), getSuccessFiles(11));
383
+
384
+ const response_body = {
385
+ attachments: { eng: ['Ensure this field has no more than 10 elements.'] },
386
+ };
387
+ const response_headers = {};
388
+ const response_status = '400';
389
+ mockPOST(
390
+ /api\/v2\/broadcasts\.json/,
391
+ response_body,
392
+ response_headers,
393
+ response_status
394
+ );
395
+
396
+ const send = compose.shadowRoot.querySelector(
397
+ 'temba-button#send-button'
398
+ ) as Button;
399
+ send.click();
400
+
401
+ await assertScreenshot(
402
+ 'contacts/compose-text-and-attachments-failure-attachments',
403
+ getClip(chat)
404
+ );
405
+ });
406
+
407
+ it('with text and attachments - failure - more than 640 chars and more than 10 files', async () => {
408
+ // we are a StoreElement, so load a store first
409
+ await loadStore();
410
+ const chat: ContactChat = await getContactChat({
411
+ contact: 'contact-dave-active',
412
+ });
413
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
414
+ // set the chatbox to a string that is 640+ chars
415
+ // set the attachments to a list that is 10+ items
416
+ await updateComponent(compose, getFailText(), getSuccessFiles(11));
417
+
418
+ const response_body = {
419
+ text: { eng: ['Ensure this field has no more than 640 characters.'] },
420
+ };
421
+ const response_headers = {};
422
+ const response_status = '400';
423
+ mockPOST(
424
+ /api\/v2\/broadcasts\.json/,
425
+ response_body,
426
+ response_headers,
427
+ response_status
428
+ );
429
+
430
+ const send = compose.shadowRoot.querySelector(
431
+ 'temba-button#send-button'
432
+ ) as Button;
433
+ send.click();
434
+
435
+ await assertScreenshot(
436
+ 'contacts/compose-text-and-attachments-failure-text-and-attachments',
437
+ getClip(chat)
438
+ );
439
+ });
440
+
441
+ it('with text and attachments - failure - generic', async () => {
442
+ // we are a StoreElement, so load a store first
443
+ await loadStore();
444
+ const chat: ContactChat = await getContactChat({
445
+ contact: 'contact-dave-active',
446
+ });
447
+ const compose = chat.shadowRoot.querySelector('temba-compose') as Compose;
448
+ await updateComponent(compose, getSuccessText(), getSuccessFiles());
449
+
450
+ const response_body = {};
451
+ const response_headers = {};
452
+ const response_status = '500';
453
+ mockPOST(
454
+ /api\/v2\/broadcasts\.json/,
455
+ response_body,
456
+ response_headers,
457
+ response_status
458
+ );
459
+
460
+ const send = compose.shadowRoot.querySelector(
461
+ 'temba-button#send-button'
462
+ ) as Button;
463
+ send.click();
464
+
465
+ await assertScreenshot(
466
+ 'contacts/compose-text-and-attachments-failure-generic',
189
467
  getClip(chat)
190
468
  );
191
469
  });
@@ -221,29 +499,25 @@ describe('temba-contact-chat - ticket tests', () => {
221
499
 
222
500
  chat.currentTicket = tickets.items[0];
223
501
  chat.refresh();
502
+ await chat.httpComplete;
224
503
 
225
- const chatHistoryEl = chat.shadowRoot.querySelector(
226
- 'temba-contact-history'
227
- ) as ContactHistory;
228
- expect(chatHistoryEl).to.not.equal(null);
229
-
230
- const chatboxDiv = chat.shadowRoot.querySelector(
231
- '.chatbox'
232
- ) as HTMLDivElement;
233
- expect(chatboxDiv).to.not.equal(null);
234
-
235
- const reopenButton = chat.shadowRoot.querySelector(
236
- 'temba-button#reopen-button'
237
- ) as HTMLDivElement;
238
- expect(reopenButton).to.equal(null);
239
-
504
+ // we want to wait until our scroll is finished before taking our screenshot
505
+ // once we have two scrollTops that are the same, we'll assume we're ready
506
+ let lastScroll = 0;
240
507
  await assertScreenshot(
241
508
  'contacts/contact-active-ticket-open-show-chatbox',
242
509
  getClip(chat),
243
510
  {
244
511
  clock: clock,
245
- predicate: () =>
246
- chat.getContactHistory().getEventsPane().scrollTop === 982,
512
+ predicate: () => {
513
+ const currentScroll = chat
514
+ .getContactHistory()
515
+ .getEventsPane().scrollTop;
516
+ if (currentScroll !== 0 && currentScroll === lastScroll) {
517
+ return true;
518
+ }
519
+ lastScroll = currentScroll;
520
+ },
247
521
  }
248
522
  );
249
523
  });
@@ -260,31 +534,15 @@ describe('temba-contact-chat - ticket tests', () => {
260
534
  });
261
535
  chat.currentTicket = tickets.items[0];
262
536
  chat.refresh();
263
-
264
537
  await chat.httpComplete;
265
538
 
266
- const chatHistoryEl = chat.shadowRoot.querySelector(
267
- 'temba-contact-history'
268
- ) as ContactHistory;
269
- expect(chatHistoryEl).to.not.equal(null);
270
-
271
- const chatboxDiv = chat.shadowRoot.querySelector(
272
- '.chatbox'
273
- ) as HTMLDivElement;
274
- expect(chatboxDiv).to.equal(null);
275
-
276
- const reopenButton = chat.shadowRoot.querySelector(
277
- 'temba-button#reopen-button'
278
- ) as HTMLDivElement;
279
- expect(reopenButton).to.not.equal(null);
280
-
281
539
  await assertScreenshot(
282
540
  'contacts/contact-active-ticket-closed-show-reopen-button',
283
541
  getClip(chat),
284
542
  {
285
543
  clock: clock,
286
544
  predicate: () => {
287
- return chat.getContactHistory().getEventsPane().scrollTop === 918;
545
+ return chat.getContactHistory().getEventsPane().scrollTop === 921;
288
546
  },
289
547
  }
290
548
  );
@@ -303,21 +561,7 @@ describe('temba-contact-chat - ticket tests', () => {
303
561
  });
304
562
  chat.currentTicket = tickets.items[0];
305
563
  chat.refresh();
306
-
307
- const chatHistoryEl = chat.shadowRoot.querySelector(
308
- 'temba-contact-history'
309
- ) as ContactHistory;
310
- expect(chatHistoryEl).to.not.equal(null);
311
-
312
- const chatboxDiv = chat.shadowRoot.querySelector(
313
- '.chatbox'
314
- ) as HTMLDivElement;
315
- expect(chatboxDiv).to.equal(null);
316
-
317
- const reopenButton = chat.shadowRoot.querySelector(
318
- 'temba-button#reopen-button'
319
- ) as HTMLDivElement;
320
- expect(reopenButton).to.equal(null);
564
+ await chat.httpComplete;
321
565
 
322
566
  await assertScreenshot(
323
567
  'contacts/contact-archived-ticket-closed-hide-chatbox',
@@ -325,7 +569,7 @@ describe('temba-contact-chat - ticket tests', () => {
325
569
  {
326
570
  clock: clock,
327
571
  predicate: () => {
328
- return chat.getContactHistory().getEventsPane().scrollTop === 867;
572
+ return chat.getContactHistory().getEventsPane().scrollTop === 870;
329
573
  },
330
574
  }
331
575
  );
@@ -18,6 +18,10 @@ export const createHistory = async (def: string) => {
18
18
  'width: 500px;height:750px;display:flex;flex-direction:column;flex-grow:1;min-height:0;'
19
19
  );
20
20
  const history = (await fixture(def, { parentNode })) as ContactHistory;
21
+
22
+ // make sure our initial fetch is made
23
+ await history.httpComplete;
24
+
21
25
  return history;
22
26
  };
23
27
 
@@ -57,7 +61,7 @@ describe('temba-contact-history', () => {
57
61
  clock.restore();
58
62
  });
59
63
 
60
- it.only('can be created', async () => {
64
+ it('can be created', async () => {
61
65
  const history = await createHistory(getHistoryHTML());
62
66
  assert.instanceOf(history, ContactHistory);
63
67
  });
@@ -69,13 +73,14 @@ describe('temba-contact-history', () => {
69
73
  })
70
74
  );
71
75
 
72
- // we should have scrolled to the bottom
76
+ // we should have scrolled to the bottom, but the scroll
77
+ // happens in a window timeout
78
+ await clock.runAllAsync();
79
+ await history.updateComplete;
80
+
73
81
  const events = history.shadowRoot.querySelector('.events');
74
82
  const top = events.scrollHeight - events.getBoundingClientRect().height;
75
-
76
83
  expect(top).to.equal(549);
77
- await clock.runAllAsync();
78
-
79
84
  // make sure we actually scrolled to there
80
85
  expect(events.scrollTop).to.equal(top);
81
86
  await assertScreenshot('contacts/history', getHistoryClip(history));
@@ -16,10 +16,11 @@ export interface CodeMock {
16
16
  endpoint: RegExp;
17
17
  body: string;
18
18
  headers: any;
19
+ status: string;
19
20
  }
20
21
 
21
22
  const gets: CodeMock[] = [];
22
- const posts: CodeMock[] = [];
23
+ let posts: CodeMock[] = [];
23
24
  let normalFetch;
24
25
 
25
26
  export const showMouse = async () => {
@@ -53,14 +54,14 @@ export const getComponent = async (
53
54
  ${height > 0 ? `height:${height}px;` : ``}
54
55
  ${style ? style : ``}
55
56
  `;
56
-
57
57
  parentNode.setAttribute('style', styleAttribute);
58
- return await fixture(spec, { parentNode });
58
+ const component = await fixture(spec, { parentNode });
59
+ return component;
59
60
  };
60
61
 
61
62
  const createResponse = mocked => {
62
63
  const mockResponse = new window.Response(mocked.body, {
63
- status: 200,
64
+ status: mocked.status,
64
65
  headers: {
65
66
  'Content-type': 'text/html',
66
67
  ...mocked.headers,
@@ -72,7 +73,7 @@ const createResponse = mocked => {
72
73
 
73
74
  const createJSONResponse = mocked => {
74
75
  const mockResponse = new window.Response(JSON.stringify(mocked.body), {
75
- status: 200,
76
+ status: mocked.status,
76
77
  headers: {
77
78
  'Content-type': 'application/json',
78
79
  ...mocked.headers,
@@ -114,12 +115,26 @@ after(() => {
114
115
  (window.fetch as any).restore();
115
116
  });
116
117
 
117
- export const mockGET = (endpoint: RegExp, body: any, headers: any = {}) => {
118
- gets.push({ endpoint, body, headers });
118
+ export const mockGET = (
119
+ endpoint: RegExp,
120
+ body: any,
121
+ headers: any = {},
122
+ status = '200'
123
+ ) => {
124
+ gets.push({ endpoint, body, headers, status });
125
+ };
126
+
127
+ export const mockPOST = (
128
+ endpoint: RegExp,
129
+ body: any,
130
+ headers: any = {},
131
+ status = '200'
132
+ ) => {
133
+ posts.push({ endpoint, body, headers, status });
119
134
  };
120
135
 
121
- export const mockPOST = (endpoint: RegExp, body: any, headers: any = {}) => {
122
- posts.push({ endpoint, body, headers });
136
+ export const clearMockPosts = () => {
137
+ posts = [];
123
138
  };
124
139
 
125
140
  export const checkTimers = (clock: any) => {