@nyaruka/temba-components 0.127.0 → 0.129.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 +25 -0
- package/demo/chart/horizontal-demo.html +81 -0
- package/demo/components/datepicker/example.html +63 -0
- package/demo/components/datepicker/range-picker-demo.html +161 -0
- package/demo/data/flows/sample-flow.json +127 -100
- package/demo/index.html +8 -0
- package/demo/static/css/prism.css +2 -0
- package/demo/static/js/prism-loader.js +12 -0
- package/demo/sticky-note-demo.html +152 -0
- package/demo/styles.css +71 -1
- 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 +11 -2
- 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 +509 -87
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/chart/TembaChart.js +136 -62
- package/out-tsc/src/chart/TembaChart.js.map +1 -1
- package/out-tsc/src/datepicker/DatePicker.js +11 -1
- package/out-tsc/src/datepicker/DatePicker.js.map +1 -1
- package/out-tsc/src/datepicker/RangePicker.js +595 -0
- package/out-tsc/src/datepicker/RangePicker.js.map +1 -0
- package/out-tsc/src/flow/Editor.js +210 -1
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/EditorNode.js +98 -139
- package/out-tsc/src/flow/EditorNode.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +272 -0
- package/out-tsc/src/flow/StickyNote.js.map +1 -0
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/RunList.js +2 -1
- package/out-tsc/src/list/RunList.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +9 -0
- package/out-tsc/src/list/SortableList.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 +11 -2
- 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 +33 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/vectoricon/index.js +2 -1
- package/out-tsc/src/vectoricon/index.js.map +1 -1
- package/out-tsc/temba-modules.js +5 -1
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-chart.test.js +36 -0
- package/out-tsc/test/temba-chart.test.js.map +1 -1
- package/out-tsc/test/temba-datepicker.test.js +1 -1
- package/out-tsc/test/temba-datepicker.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor-node.test.js +249 -5
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-range-picker.test.js +123 -0
- package/out-tsc/test/temba-range-picker.test.js.map +1 -0
- package/out-tsc/test/temba-select.test.js +10 -16
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-webchat.test.js +4 -0
- package/out-tsc/test/temba-webchat.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +62 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/datepicker/range-picker-all.png +0 -0
- package/screenshots/truth/datepicker/range-picker-button-states.png +0 -0
- package/screenshots/truth/datepicker/range-picker-default.png +0 -0
- package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
- package/screenshots/truth/datepicker/range-picker-initial-values.png +0 -0
- package/screenshots/truth/datepicker/range-picker-min-max.png +0 -0
- package/screenshots/truth/datepicker/range-picker-week.png +0 -0
- package/screenshots/truth/datepicker/range-picker-year.png +0 -0
- package/screenshots/truth/sticky-note/blue.png +0 -0
- package/screenshots/truth/sticky-note/gray.png +0 -0
- package/screenshots/truth/sticky-note/green.png +0 -0
- package/screenshots/truth/sticky-note/pink.png +0 -0
- package/screenshots/truth/sticky-note/yellow.png +0 -0
- package/screenshots/truth/webchat/connected-state.png +0 -0
- package/src/chart/TembaChart.ts +144 -66
- package/src/datepicker/DatePicker.ts +9 -1
- package/src/datepicker/RangePicker.ts +602 -0
- package/src/flow/Editor.ts +252 -2
- package/src/flow/EditorNode.ts +98 -156
- package/src/flow/StickyNote.ts +284 -0
- package/src/interfaces.ts +2 -1
- package/src/list/RunList.ts +2 -1
- package/src/list/SortableList.ts +11 -0
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/store/AppState.ts +51 -1
- package/src/store/flow-definition.d.ts +8 -0
- package/src/vectoricon/index.ts +2 -1
- package/static/svg/index.pdf +137 -0
- package/temba-modules.ts +5 -1
- package/test/temba-chart.test.ts +47 -0
- package/test/temba-datepicker.test.ts +1 -1
- package/test/temba-flow-editor-node.test.ts +322 -6
- package/test/temba-range-picker.test.ts +193 -0
- package/test/temba-select.test.ts +11 -19
- package/test/temba-webchat.test.ts +7 -0
- package/test/utils.test.ts +98 -0
- package/web-dev-server.config.mjs +30 -22
- package/web-test-runner.config.mjs +2 -0
- package/demo/datepicker/example.html +0 -69
|
@@ -5,12 +5,12 @@ import { Options } from '../src/options/Options';
|
|
|
5
5
|
import { Select, SelectOption } from '../src/select/Select';
|
|
6
6
|
import {
|
|
7
7
|
assertScreenshot,
|
|
8
|
-
delay,
|
|
9
8
|
getClip,
|
|
10
9
|
getOptions,
|
|
11
10
|
loadStore,
|
|
12
11
|
openAndClick,
|
|
13
|
-
openSelect
|
|
12
|
+
openSelect,
|
|
13
|
+
waitForSelectPagination
|
|
14
14
|
} from './utils.test';
|
|
15
15
|
|
|
16
16
|
const colors = [
|
|
@@ -649,7 +649,6 @@ describe('temba-select', () => {
|
|
|
649
649
|
// assert.equal(select.visibleOptions.length, 2);
|
|
650
650
|
});
|
|
651
651
|
|
|
652
|
-
// this test is flaky on CI, skip it for now
|
|
653
652
|
xit('pages through cursor results', async () => {
|
|
654
653
|
const select = await createSelect(
|
|
655
654
|
clock,
|
|
@@ -662,27 +661,15 @@ describe('temba-select', () => {
|
|
|
662
661
|
|
|
663
662
|
await openSelect(clock, select);
|
|
664
663
|
|
|
665
|
-
// Wait for pagination to complete
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
const maxAttempts = 10;
|
|
669
|
-
while (select.fetching || select.visibleOptions.length < 15) {
|
|
670
|
-
if (attempts >= maxAttempts) {
|
|
671
|
-
throw new Error(
|
|
672
|
-
`Pagination did not complete after ${maxAttempts} attempts. fetching: ${select.fetching}, visibleOptions: ${select.visibleOptions.length}`
|
|
673
|
-
);
|
|
674
|
-
}
|
|
675
|
-
await select.updateComplete;
|
|
676
|
-
clock.runAll();
|
|
677
|
-
attempts++;
|
|
678
|
-
await delay(100);
|
|
679
|
-
}
|
|
664
|
+
// Wait for pagination to complete using our improved helper
|
|
665
|
+
// Use more attempts for this test since pagination can be slow in CI
|
|
666
|
+
await waitForSelectPagination(select, clock, 15, 50);
|
|
680
667
|
|
|
681
668
|
// should have all three pages visible right away
|
|
682
669
|
assert.equal(select.visibleOptions.length, 15);
|
|
683
670
|
});
|
|
684
671
|
|
|
685
|
-
|
|
672
|
+
it('shows cached results', async () => {
|
|
686
673
|
const select = await createSelect(
|
|
687
674
|
clock,
|
|
688
675
|
getSelectHTML([], {
|
|
@@ -695,13 +682,18 @@ describe('temba-select', () => {
|
|
|
695
682
|
|
|
696
683
|
// wait for updates from fetching three pages
|
|
697
684
|
await openSelect(clock, select);
|
|
685
|
+
await waitForSelectPagination(select, clock, 15, 50);
|
|
698
686
|
assert.equal(select.visibleOptions.length, 15);
|
|
699
687
|
|
|
700
688
|
// close and reopen
|
|
701
689
|
select.blur();
|
|
702
690
|
await clock.tick(250);
|
|
691
|
+
// Ensure the select is properly closed before reopening
|
|
692
|
+
await select.updateComplete;
|
|
703
693
|
|
|
704
694
|
await openSelect(clock, select);
|
|
695
|
+
// Cached results should be available immediately, but give some time for rendering
|
|
696
|
+
await waitForSelectPagination(select, clock, 15, 30);
|
|
705
697
|
assert.equal(select.visibleOptions.length, 15);
|
|
706
698
|
|
|
707
699
|
// close and reopen once more (previous bug failed on third opening)
|
|
@@ -210,6 +210,13 @@ describe('temba-webchat', () => {
|
|
|
210
210
|
status: 'connected'
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
// the cursor is blinking, we need to account for it in our screenshot by making it transparent
|
|
214
|
+
const inputField = webChat.shadowRoot.querySelector(
|
|
215
|
+
'.input'
|
|
216
|
+
) as HTMLInputElement;
|
|
217
|
+
expect(inputField).to.exist;
|
|
218
|
+
inputField.style.caretColor = 'transparent';
|
|
219
|
+
|
|
213
220
|
await assertScreenshot('webchat/connected-state', getClip(webChat));
|
|
214
221
|
});
|
|
215
222
|
});
|
package/test/utils.test.ts
CHANGED
|
@@ -180,6 +180,26 @@ export const delay = (millis: number) => {
|
|
|
180
180
|
});
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
// Enhanced wait utility for more robust testing
|
|
184
|
+
export const waitForCondition = async (
|
|
185
|
+
predicate: () => boolean,
|
|
186
|
+
maxAttempts: number = 20,
|
|
187
|
+
delayMs: number = 50
|
|
188
|
+
): Promise<void> => {
|
|
189
|
+
let attempts = 0;
|
|
190
|
+
while (!predicate() && attempts < maxAttempts) {
|
|
191
|
+
await delay(delayMs);
|
|
192
|
+
attempts++;
|
|
193
|
+
}
|
|
194
|
+
if (!predicate()) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Condition not met after ${maxAttempts} attempts (${
|
|
197
|
+
maxAttempts * delayMs
|
|
198
|
+
}ms)`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
183
203
|
export const assertScreenshot = async (
|
|
184
204
|
filename: string,
|
|
185
205
|
clip: Clip,
|
|
@@ -294,6 +314,31 @@ export const clickOption = async (
|
|
|
294
314
|
index: number
|
|
295
315
|
) => {
|
|
296
316
|
const options = getOptions(select);
|
|
317
|
+
if (!options) {
|
|
318
|
+
throw new Error('No options element found');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Wait for the specific option to be available, but only if it's not already there
|
|
322
|
+
const existingOption = options.shadowRoot?.querySelector(
|
|
323
|
+
`[data-option-index="${index}"]`
|
|
324
|
+
);
|
|
325
|
+
if (!existingOption) {
|
|
326
|
+
try {
|
|
327
|
+
await waitForCondition(
|
|
328
|
+
() => {
|
|
329
|
+
const option = options.shadowRoot?.querySelector(
|
|
330
|
+
`[data-option-index="${index}"]`
|
|
331
|
+
);
|
|
332
|
+
return !!option;
|
|
333
|
+
},
|
|
334
|
+
10,
|
|
335
|
+
25
|
|
336
|
+
);
|
|
337
|
+
} catch (e) {
|
|
338
|
+
throw new Error(`Option at index ${index} not found after waiting`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
297
342
|
const option = options.shadowRoot.querySelector(
|
|
298
343
|
`[data-option-index="${index}"]`
|
|
299
344
|
) as HTMLDivElement;
|
|
@@ -318,6 +363,27 @@ export const openSelect = async (clock: any, select: Select<SelectOption>) => {
|
|
|
318
363
|
// reduce wait time for options to become visible
|
|
319
364
|
await waitFor(25);
|
|
320
365
|
clock.runAll();
|
|
366
|
+
|
|
367
|
+
// For non-endpoint selects, options might be immediately available
|
|
368
|
+
// For endpoint selects, we need to wait for them to load
|
|
369
|
+
const hasEndpoint = select.getAttribute('endpoint');
|
|
370
|
+
if (hasEndpoint) {
|
|
371
|
+
try {
|
|
372
|
+
// Wait for options to be properly rendered and visible (but only for endpoint selects)
|
|
373
|
+
await waitForCondition(
|
|
374
|
+
() => {
|
|
375
|
+
const options = select.shadowRoot.querySelector(
|
|
376
|
+
'temba-options[visible]'
|
|
377
|
+
);
|
|
378
|
+
return options && options.isConnected;
|
|
379
|
+
},
|
|
380
|
+
10,
|
|
381
|
+
25
|
|
382
|
+
);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
// If condition fails, continue - some tests might not need options to be visible immediately
|
|
385
|
+
}
|
|
386
|
+
}
|
|
321
387
|
};
|
|
322
388
|
|
|
323
389
|
export const openAndClick = async (
|
|
@@ -367,3 +433,35 @@ export const updateComponent = async (
|
|
|
367
433
|
export const getValidText = () => {
|
|
368
434
|
return 'sà-wàd-dee!';
|
|
369
435
|
};
|
|
436
|
+
|
|
437
|
+
// Helper for waiting for select pagination to complete
|
|
438
|
+
export const waitForSelectPagination = async (
|
|
439
|
+
select: Select<SelectOption>,
|
|
440
|
+
clock: any,
|
|
441
|
+
expectedCount: number,
|
|
442
|
+
maxAttempts: number = 30
|
|
443
|
+
): Promise<void> => {
|
|
444
|
+
let attempts = 0;
|
|
445
|
+
while (attempts < maxAttempts) {
|
|
446
|
+
// Ensure we're not still fetching
|
|
447
|
+
if (!select.fetching && select.visibleOptions.length >= expectedCount) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
await select.updateComplete;
|
|
452
|
+
clock.runAll();
|
|
453
|
+
|
|
454
|
+
// Give more time between attempts for slow CI environments
|
|
455
|
+
await delay(75);
|
|
456
|
+
|
|
457
|
+
attempts++;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Pagination did not complete after ${maxAttempts} attempts (${
|
|
462
|
+
maxAttempts * 75
|
|
463
|
+
}ms). ` +
|
|
464
|
+
`Expected ${expectedCount} options, got ${select.visibleOptions.length}. ` +
|
|
465
|
+
`Fetching: ${select.fetching}`
|
|
466
|
+
);
|
|
467
|
+
};
|
|
@@ -15,30 +15,38 @@ export default {
|
|
|
15
15
|
{
|
|
16
16
|
name: 'flow-files',
|
|
17
17
|
serve(context) {
|
|
18
|
-
if (context.request.method === 'POST') {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
body += chunk.toString();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
context.req.on('end', () => {
|
|
18
|
+
if (context.request.method === 'POST' && context.path.startsWith('/flow/revisions/')) {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
let body = '';
|
|
25
21
|
const parts = context.path.split('/');
|
|
26
22
|
const uuid = parts[3];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
23
|
+
context.req.on('data', chunk => {
|
|
24
|
+
body += chunk.toString();
|
|
25
|
+
});
|
|
26
|
+
context.req.on('end', () => {
|
|
27
|
+
context.contentType = 'application/json';
|
|
28
|
+
if (body) {
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
path.resolve(`./demo/data/flows/${uuid}.json`),
|
|
31
|
+
JSON.stringify({ definition: JSON.parse(body) }, null, 2)
|
|
32
|
+
);
|
|
33
|
+
console.log(`Flow ${uuid} saved successfully.`);
|
|
34
|
+
context.body = {
|
|
35
|
+
status: 'success',
|
|
36
|
+
message: `Flow ${uuid} saved successfully.`,
|
|
37
|
+
definition: JSON.parse(body),
|
|
38
|
+
};
|
|
39
|
+
context.status = 200;
|
|
40
|
+
} else {
|
|
41
|
+
console.log(`No body received for flow ${uuid}.`);
|
|
42
|
+
context.body = {
|
|
43
|
+
status: 'error',
|
|
44
|
+
message: `No body received for flow ${uuid}.`,
|
|
45
|
+
};
|
|
46
|
+
context.status = 400;
|
|
47
|
+
}
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
42
50
|
});
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -152,6 +152,8 @@ const wireScreenshots = async (page, context, wait, replaceScreenshots) => {
|
|
|
152
152
|
|
|
153
153
|
// Only wait for network idle if explicitly requested
|
|
154
154
|
if (wait) {
|
|
155
|
+
await page.waitForNetworkIdle();
|
|
156
|
+
} else {
|
|
155
157
|
await page.waitForNetworkIdle({ idleTime: 100, timeout: 1000 });
|
|
156
158
|
}
|
|
157
159
|
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en-GB">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<title>Date Picker Examples</title>
|
|
6
|
-
<link
|
|
7
|
-
href="/static/css/temba-components.css"
|
|
8
|
-
rel="stylesheet"
|
|
9
|
-
type="text/css"
|
|
10
|
-
/>
|
|
11
|
-
<link
|
|
12
|
-
href="https://fonts.googleapis.com/css?family=Roboto+Mono:300|Roboto:300,400,500"
|
|
13
|
-
rel="stylesheet"
|
|
14
|
-
/>
|
|
15
|
-
<link
|
|
16
|
-
href="../styles.css"
|
|
17
|
-
rel="stylesheet"
|
|
18
|
-
type="text/css"
|
|
19
|
-
/>
|
|
20
|
-
</head>
|
|
21
|
-
<body>
|
|
22
|
-
<h1>Date Picker Examples</h1>
|
|
23
|
-
<p><a href="../index.html">← Back to main demo</a></p>
|
|
24
|
-
|
|
25
|
-
<div class="example">
|
|
26
|
-
<h3>Date and Time Picker</h3>
|
|
27
|
-
<p>A date picker that includes time selection</p>
|
|
28
|
-
<temba-datepicker
|
|
29
|
-
time
|
|
30
|
-
value="2020-01-20T14:00+00:00"
|
|
31
|
-
timezone="UTC"
|
|
32
|
-
></temba-datepicker>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<div class="example">
|
|
36
|
-
<h3>Date Only Picker</h3>
|
|
37
|
-
<p>A date picker without time selection</p>
|
|
38
|
-
<temba-datepicker
|
|
39
|
-
value="2022-10-09"
|
|
40
|
-
></temba-datepicker>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<div class="example">
|
|
44
|
-
<h3>Date Picker with Timezone</h3>
|
|
45
|
-
<p>A date picker with a specific timezone</p>
|
|
46
|
-
<temba-datepicker
|
|
47
|
-
time
|
|
48
|
-
value="2022-10-02 01:00:00+00:00"
|
|
49
|
-
timezone="Africa/Lagos"
|
|
50
|
-
></temba-datepicker>
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
<div class="example">
|
|
54
|
-
<h3>Empty Date Picker</h3>
|
|
55
|
-
<p>A date picker with no initial value</p>
|
|
56
|
-
<temba-datepicker></temba-datepicker>
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<script>
|
|
60
|
-
function handleDateChange(event) {
|
|
61
|
-
console.log('Date changed:', event.target.value);
|
|
62
|
-
}
|
|
63
|
-
</script>
|
|
64
|
-
|
|
65
|
-
<script type="module">
|
|
66
|
-
import '../../out-tsc/temba-modules.js';
|
|
67
|
-
</script>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|