@nyaruka/temba-components 0.122.0 → 0.124.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/.github/copilot-instructions.md +181 -0
- package/.github/workflows/build.yml +3 -3
- package/.github/workflows/cla.yml +6 -6
- package/.github/workflows/copilot-setup-steps.yml +86 -0
- package/CHANGELOG.md +44 -0
- package/demo/drag-drop-demo.html +141 -0
- package/demo/index.html +57 -0
- package/demo/test-drag-drop.html +94 -0
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +1 -0
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/pt.js +1 -0
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +366 -247
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/chart/TembaChart.js +81 -14
- package/out-tsc/src/chart/TembaChart.js.map +1 -1
- package/out-tsc/src/fields/FieldManager.js +27 -34
- package/out-tsc/src/fields/FieldManager.js.map +1 -1
- package/out-tsc/src/list/RunList.js +13 -8
- package/out-tsc/src/list/RunList.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +257 -60
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/locales/es.js +1 -0
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +1 -0
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/pt.js +1 -0
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/omnibox/Omnibox.js +1 -1
- package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
- package/out-tsc/src/options/Options.js +36 -13
- package/out-tsc/src/options/Options.js.map +1 -1
- package/out-tsc/src/select/Select.js +226 -43
- package/out-tsc/src/select/Select.js.map +1 -1
- package/out-tsc/src/store/AppState.js +3 -3
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/utils/index.js +6 -1
- package/out-tsc/src/utils/index.js.map +1 -1
- package/out-tsc/src/vectoricon/VectorIcon.js +2 -1
- package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
- package/out-tsc/src/webchat/WebChat.js +5 -2
- package/out-tsc/src/webchat/WebChat.js.map +1 -1
- package/out-tsc/temba-modules.js +0 -2
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-appstate-language.test.js +176 -0
- package/out-tsc/test/temba-appstate-language.test.js.map +1 -0
- package/out-tsc/test/temba-chart.test.js +125 -0
- package/out-tsc/test/temba-chart.test.js.map +1 -1
- package/out-tsc/test/temba-dropdown.test.js +317 -0
- package/out-tsc/test/temba-dropdown.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +273 -0
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor.test.js +244 -0
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -0
- package/out-tsc/test/temba-flow-plumber.test.js +145 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -0
- package/out-tsc/test/temba-flow-render.test.js +171 -0
- package/out-tsc/test/temba-flow-render.test.js.map +1 -0
- package/out-tsc/test/temba-omnibox.test.js +2 -3
- package/out-tsc/test/temba-omnibox.test.js.map +1 -1
- package/out-tsc/test/temba-run-list.test.js +588 -0
- package/out-tsc/test/temba-run-list.test.js.map +1 -0
- package/out-tsc/test/temba-select.test.js +149 -52
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/temba-sortable-list.test.js +91 -15
- package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
- package/out-tsc/test/temba-toast.test.js +299 -0
- package/out-tsc/test/temba-toast.test.js.map +1 -0
- package/out-tsc/test/temba-utils-index.test.js +1178 -0
- package/out-tsc/test/temba-utils-index.test.js.map +1 -0
- package/out-tsc/test/temba-webchat-lightbox-fix.test.js +42 -0
- package/out-tsc/test/temba-webchat-lightbox-fix.test.js.map +1 -0
- package/out-tsc/test/temba-webchat.test.js +816 -0
- package/out-tsc/test/temba-webchat.test.js.map +1 -0
- package/out-tsc/test/utils.test.js +33 -1
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +6 -8
- package/screenshots/truth/alert/error.png +0 -0
- package/screenshots/truth/alert/info.png +0 -0
- package/screenshots/truth/alert/warning.png +0 -0
- package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
- package/screenshots/truth/checkbox/checked.png +0 -0
- package/screenshots/truth/checkbox/default.png +0 -0
- package/screenshots/truth/colorpicker/default.png +0 -0
- package/screenshots/truth/colorpicker/focused.png +0 -0
- package/screenshots/truth/colorpicker/initialized.png +0 -0
- package/screenshots/truth/colorpicker/selected.png +0 -0
- package/screenshots/truth/compose/attachments-tab.png +0 -0
- package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
- package/screenshots/truth/compose/attachments-with-files.png +0 -0
- package/screenshots/truth/compose/intial-text.png +0 -0
- package/screenshots/truth/compose/no-counter.png +0 -0
- package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
- package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
- package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
- package/screenshots/truth/contacts/badges.png +0 -0
- package/screenshots/truth/contacts/chat-failure.png +0 -0
- package/screenshots/truth/contacts/chat-for-active-contact.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/content-menu/button-no-items.png +0 -0
- package/screenshots/truth/content-menu/items-and-buttons.png +0 -0
- package/screenshots/truth/counter/summary.png +0 -0
- package/screenshots/truth/counter/text.png +0 -0
- package/screenshots/truth/counter/unicode-variables.png +0 -0
- package/screenshots/truth/counter/unicode.png +0 -0
- package/screenshots/truth/counter/variable.png +0 -0
- package/screenshots/truth/date/date-inline.png +0 -0
- package/screenshots/truth/date/date.png +0 -0
- package/screenshots/truth/date/datetime.png +0 -0
- package/screenshots/truth/date/duration.png +0 -0
- package/screenshots/truth/date/timedate.png +0 -0
- package/screenshots/truth/datepicker/date-truncated-time.png +0 -0
- package/screenshots/truth/datepicker/date.png +0 -0
- package/screenshots/truth/datepicker/initial-timezone.png +0 -0
- package/screenshots/truth/datepicker/updated-keyboard-date.png +0 -0
- package/screenshots/truth/dialog/focused.png +0 -0
- package/screenshots/truth/dropdown/after-blur.png +0 -0
- package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
- package/screenshots/truth/dropdown/custom-arrow-size.png +0 -0
- package/screenshots/truth/dropdown/default.png +0 -0
- package/screenshots/truth/dropdown/narrow-toggle.png +0 -0
- package/screenshots/truth/dropdown/no-mask.png +0 -0
- package/screenshots/truth/dropdown/opened.png +0 -0
- package/screenshots/truth/dropdown/positioned.png +0 -0
- package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
- package/screenshots/truth/dropdown/with-mask.png +0 -0
- package/screenshots/truth/flow/editor-basic.png +0 -0
- package/screenshots/truth/label/custom.png +0 -0
- package/screenshots/truth/label/danger.png +0 -0
- package/screenshots/truth/label/dark.png +0 -0
- package/screenshots/truth/label/default-icon.png +0 -0
- package/screenshots/truth/label/no-icon.png +0 -0
- package/screenshots/truth/label/primary.png +0 -0
- package/screenshots/truth/label/secondary.png +0 -0
- package/screenshots/truth/label/shadow.png +0 -0
- package/screenshots/truth/label/tertiary.png +0 -0
- package/screenshots/truth/lightbox/img-zoomed.png +0 -0
- package/screenshots/truth/list/fields-dragging.png +0 -0
- package/screenshots/truth/list/fields-filtered.png +0 -0
- package/screenshots/truth/list/fields-hovered.png +0 -0
- package/screenshots/truth/list/fields.png +0 -0
- package/screenshots/truth/list/items-selected.png +0 -0
- package/screenshots/truth/list/items-updated.png +0 -0
- package/screenshots/truth/list/items.png +0 -0
- package/screenshots/truth/list/sortable-dragging.png +0 -0
- package/screenshots/truth/list/sortable-dropped.png +0 -0
- package/screenshots/truth/list/sortable.png +0 -0
- package/screenshots/truth/menu/menu-focused-with items.png +0 -0
- package/screenshots/truth/menu/menu-refresh-1.png +0 -0
- package/screenshots/truth/menu/menu-refresh-2.png +0 -0
- package/screenshots/truth/menu/menu-root.png +0 -0
- package/screenshots/truth/menu/menu-submenu.png +0 -0
- package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
- package/screenshots/truth/menu/menu-tasks.png +0 -0
- package/screenshots/truth/modax/form.png +0 -0
- package/screenshots/truth/modax/simple.png +0 -0
- package/screenshots/truth/omnibox/selected.png +0 -0
- package/screenshots/truth/options/block.png +0 -0
- package/screenshots/truth/run-list/basic.png +0 -0
- package/screenshots/truth/select/disabled-multi-selection.png +0 -0
- package/screenshots/truth/select/disabled-selection.png +0 -0
- package/screenshots/truth/select/disabled.png +0 -0
- package/screenshots/truth/select/embedded.png +0 -0
- package/screenshots/truth/select/empty-options.png +0 -0
- package/screenshots/truth/select/expression-selected.png +0 -0
- package/screenshots/truth/select/expressions.png +0 -0
- package/screenshots/truth/select/functions.png +0 -0
- package/screenshots/truth/select/local-options.png +0 -0
- package/screenshots/truth/select/multi-reorder-final.png +0 -0
- package/screenshots/truth/select/multi-reorder-initial.png +0 -0
- package/screenshots/truth/select/multi-with-endpoint.png +0 -0
- package/screenshots/truth/select/multiple-initial-values.png +0 -0
- package/screenshots/truth/select/remote-options.png +0 -0
- package/screenshots/truth/select/search-enabled.png +0 -0
- package/screenshots/truth/select/search-multi-no-matches.png +0 -0
- package/screenshots/truth/select/search-selected-focus.png +0 -0
- package/screenshots/truth/select/search-selected.png +0 -0
- package/screenshots/truth/select/search-with-selected.png +0 -0
- package/screenshots/truth/select/searching.png +0 -0
- package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
- package/screenshots/truth/select/selected-multi.png +0 -0
- package/screenshots/truth/select/selected-single.png +0 -0
- package/screenshots/truth/select/selection-clearable.png +0 -0
- package/screenshots/truth/select/static-initial-value.png +0 -0
- package/screenshots/truth/select/static-initial-via-selected.png +0 -0
- package/screenshots/truth/select/truncated-selection.png +0 -0
- package/screenshots/truth/select/with-placeholder.png +0 -0
- package/screenshots/truth/select/without-placeholder.png +0 -0
- package/screenshots/truth/slider/custom-min-custom-max-valid-value.png +0 -0
- package/screenshots/truth/slider/custom-min-default-max-no-value.png +0 -0
- package/screenshots/truth/slider/default-min-custom-max-no-value.png +0 -0
- package/screenshots/truth/slider/default-min-default-max-invalid-value.png +0 -0
- package/screenshots/truth/slider/default-min-default-max-valid-value.png +0 -0
- package/screenshots/truth/slider/update-slider-on-value-change.png +0 -0
- package/screenshots/truth/templates/default.png +0 -0
- package/screenshots/truth/templates/unapproved.png +0 -0
- package/screenshots/truth/textinput/input-disabled.png +0 -0
- package/screenshots/truth/textinput/input-focused.png +0 -0
- package/screenshots/truth/textinput/input-form.png +0 -0
- package/screenshots/truth/textinput/input-inserted.png +0 -0
- package/screenshots/truth/textinput/input-placeholder.png +0 -0
- package/screenshots/truth/textinput/input-updated.png +0 -0
- package/screenshots/truth/textinput/input.png +0 -0
- package/screenshots/truth/textinput/textarea-focused.png +0 -0
- package/screenshots/truth/textinput/textarea.png +0 -0
- package/screenshots/truth/tip/bottom.png +0 -0
- package/screenshots/truth/tip/left.png +0 -0
- package/screenshots/truth/tip/right.png +0 -0
- package/screenshots/truth/tip/top.png +0 -0
- package/screenshots/truth/webchat/closed-widget.png +0 -0
- package/screenshots/truth/webchat/connected-state.png +0 -0
- package/screenshots/truth/webchat/connecting-state.png +0 -0
- package/screenshots/truth/webchat/disconnected-state.png +0 -0
- package/screenshots/truth/webchat/opened-widget.png +0 -0
- package/src/chart/TembaChart.ts +86 -15
- package/src/fields/FieldManager.ts +30 -38
- package/src/list/RunList.ts +11 -8
- package/src/list/SortableList.ts +291 -67
- package/src/locales/es.ts +1 -0
- package/src/locales/fr.ts +1 -0
- package/src/locales/pt.ts +1 -0
- package/src/omnibox/Omnibox.ts +1 -1
- package/src/options/Options.ts +38 -13
- package/src/select/Select.ts +245 -47
- package/src/store/AppState.ts +3 -3
- package/src/utils/index.ts +17 -5
- package/src/vectoricon/VectorIcon.ts +2 -1
- package/src/webchat/WebChat.ts +5 -2
- package/temba-modules.ts +0 -2
- package/test/temba-appstate-language.test.ts +218 -0
- package/test/temba-chart.test.ts +161 -1
- package/test/temba-dropdown.test.ts +444 -0
- package/test/temba-flow-editor-node.test.ts +344 -0
- package/test/temba-flow-editor.test.ts +301 -0
- package/test/temba-flow-plumber.test.ts +189 -0
- package/test/temba-flow-render.test.ts +220 -0
- package/test/temba-omnibox.test.ts +2 -3
- package/test/temba-run-list.test.ts +774 -0
- package/test/temba-select.test.ts +206 -78
- package/test/temba-sortable-list.test.ts +108 -15
- package/test/temba-toast.test.ts +386 -0
- package/test/temba-utils-index.test.ts +1547 -0
- package/test/temba-webchat-lightbox-fix.test.ts +57 -0
- package/test/temba-webchat.test.ts +1095 -0
- package/test/utils.test.ts +56 -2
- package/test-assets/list/flow-results.json +17 -0
- package/test-assets/list/runs.json +126 -0
- package/test-assets/style.css +23 -0
- package/web-test-runner.config.mjs +33 -7
- package/xliff/es.xlf +3 -0
- package/xliff/fr.xlf +3 -0
- package/xliff/pt.xlf +3 -0
- package/out-tsc/src/outboxmonitor/OutboxMonitor.js +0 -136
- package/out-tsc/src/outboxmonitor/OutboxMonitor.js.map +0 -1
- package/src/outboxmonitor/OutboxMonitor.ts +0 -148
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import * as sinon from 'sinon';
|
|
1
|
+
import Sinon, * as sinon from 'sinon';
|
|
2
2
|
import { fixture, expect, assert } from '@open-wc/testing';
|
|
3
3
|
import { useFakeTimers } from 'sinon';
|
|
4
4
|
import { Options } from '../src/options/Options';
|
|
5
5
|
import { Select, SelectOption } from '../src/select/Select';
|
|
6
6
|
import {
|
|
7
7
|
assertScreenshot,
|
|
8
|
-
checkTimers,
|
|
9
8
|
getClip,
|
|
9
|
+
getOptions,
|
|
10
10
|
loadStore,
|
|
11
|
-
|
|
11
|
+
openAndClick,
|
|
12
|
+
openSelect
|
|
12
13
|
} from './utils.test';
|
|
13
|
-
import { CustomEventType } from '../src/interfaces';
|
|
14
14
|
|
|
15
15
|
const colors = [
|
|
16
16
|
{ name: 'Red', value: '0' },
|
|
@@ -20,7 +20,7 @@ const colors = [
|
|
|
20
20
|
|
|
21
21
|
export const createSelect = async (clock, def: string) => {
|
|
22
22
|
const parentNode = document.createElement('div');
|
|
23
|
-
parentNode.setAttribute('style', 'width:
|
|
23
|
+
parentNode.setAttribute('style', 'width: 400px;');
|
|
24
24
|
|
|
25
25
|
const select: Select<SelectOption> = await fixture(def, { parentNode });
|
|
26
26
|
clock.runAll();
|
|
@@ -28,66 +28,10 @@ export const createSelect = async (clock, def: string) => {
|
|
|
28
28
|
return select;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
export const open = async (clock, select: Select<SelectOption>) => {
|
|
32
|
-
if (!select.endpoint) {
|
|
33
|
-
await mouseClickElement(select);
|
|
34
|
-
await clock.runAll();
|
|
35
|
-
await clock.runAll();
|
|
36
|
-
return select;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const promise = new Promise<Select<SelectOption>>((resolve) => {
|
|
40
|
-
select.addEventListener(
|
|
41
|
-
CustomEventType.FetchComplete,
|
|
42
|
-
async () => {
|
|
43
|
-
await clock.runAll();
|
|
44
|
-
resolve(select);
|
|
45
|
-
},
|
|
46
|
-
{ once: true }
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
await mouseClickElement(select);
|
|
51
|
-
await clock.runAll();
|
|
52
|
-
|
|
53
|
-
return promise;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
31
|
export const clear = (select: Select<SelectOption>) => {
|
|
57
32
|
(select.shadowRoot.querySelector('.clear-button') as HTMLDivElement).click();
|
|
58
33
|
};
|
|
59
34
|
|
|
60
|
-
export const getOptions = (select: Select<SelectOption>): Options => {
|
|
61
|
-
return select.shadowRoot.querySelector('temba-options[visible]');
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const clickOption = async (
|
|
65
|
-
clock: any,
|
|
66
|
-
select: Select<SelectOption>,
|
|
67
|
-
index: number
|
|
68
|
-
) => {
|
|
69
|
-
const options = getOptions(select);
|
|
70
|
-
const option = options.shadowRoot.querySelector(
|
|
71
|
-
`[data-option-index="${index}"]`
|
|
72
|
-
) as HTMLDivElement;
|
|
73
|
-
|
|
74
|
-
await mouseClickElement(option);
|
|
75
|
-
await options.updateComplete;
|
|
76
|
-
await select.updateComplete;
|
|
77
|
-
await clock.runAll();
|
|
78
|
-
|
|
79
|
-
checkTimers(clock);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export const openAndClick = async (
|
|
83
|
-
clock: any,
|
|
84
|
-
select: Select<SelectOption>,
|
|
85
|
-
index: number
|
|
86
|
-
) => {
|
|
87
|
-
await open(clock, select);
|
|
88
|
-
await clickOption(clock, select, index);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
35
|
export const getSelectHTML = (
|
|
92
36
|
options: SelectOption[] = colors,
|
|
93
37
|
attrs: any = { placeholder: 'Select a color', name: 'color' },
|
|
@@ -143,7 +87,7 @@ const getClipWithOptions = (select: Select<any>) => {
|
|
|
143
87
|
};
|
|
144
88
|
|
|
145
89
|
describe('temba-select', () => {
|
|
146
|
-
let clock:
|
|
90
|
+
let clock: Sinon.SinonFakeTimers;
|
|
147
91
|
beforeEach(function () {
|
|
148
92
|
clock = useFakeTimers();
|
|
149
93
|
clock.tick(400);
|
|
@@ -190,7 +134,7 @@ describe('temba-select', () => {
|
|
|
190
134
|
expect(select.disabled).to.equal(true);
|
|
191
135
|
|
|
192
136
|
// make sure we can't select anymore
|
|
193
|
-
await
|
|
137
|
+
await openSelect(clock, select);
|
|
194
138
|
expect(select.isOpen()).to.equal(false);
|
|
195
139
|
await assertScreenshot('select/disabled-multi-selection', getClip(select));
|
|
196
140
|
});
|
|
@@ -211,7 +155,7 @@ describe('temba-select', () => {
|
|
|
211
155
|
|
|
212
156
|
it('shows options when opened', async () => {
|
|
213
157
|
const select = await createSelect(clock, getSelectHTML());
|
|
214
|
-
await
|
|
158
|
+
await openSelect(clock, select);
|
|
215
159
|
const options = getOptions(select);
|
|
216
160
|
assert.instanceOf(options, Options);
|
|
217
161
|
|
|
@@ -234,6 +178,33 @@ describe('temba-select', () => {
|
|
|
234
178
|
await assertScreenshot('select/embedded', getClipWithOptions(select));
|
|
235
179
|
});
|
|
236
180
|
|
|
181
|
+
it('shows no options message when opening with empty options', async () => {
|
|
182
|
+
const select = await createSelect(
|
|
183
|
+
clock,
|
|
184
|
+
getSelectHTML([], { placeholder: 'Select an option' })
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// attempt to open the select with no options
|
|
188
|
+
await openSelect(clock, select);
|
|
189
|
+
|
|
190
|
+
// should show options dropdown even though there are no options
|
|
191
|
+
const options = getOptions(select);
|
|
192
|
+
assert.instanceOf(options, Options);
|
|
193
|
+
|
|
194
|
+
// the options dropdown should be visible
|
|
195
|
+
assert.isTrue(
|
|
196
|
+
options.shadowRoot
|
|
197
|
+
.querySelector('.options-container')
|
|
198
|
+
.classList.contains('show')
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// should contain a "No options" message
|
|
202
|
+
const noOptionsText = options.shadowRoot.textContent;
|
|
203
|
+
assert.include(noOptionsText.toLowerCase(), 'no options');
|
|
204
|
+
|
|
205
|
+
await assertScreenshot('select/empty-options', getClipWithOptions(select));
|
|
206
|
+
});
|
|
207
|
+
|
|
237
208
|
describe('single selection', () => {
|
|
238
209
|
it('can select a single option', async () => {
|
|
239
210
|
const select = await createSelect(clock, getSelectHTML());
|
|
@@ -270,13 +241,13 @@ describe('temba-select', () => {
|
|
|
270
241
|
expect(select.values[0].name).to.equal('Green');
|
|
271
242
|
|
|
272
243
|
// for single selection our current selection should be in the list and focused
|
|
273
|
-
await
|
|
244
|
+
await openSelect(clock, select);
|
|
274
245
|
assert.equal(select.cursorIndex, 1);
|
|
275
246
|
assert.equal(select.visibleOptions.length, 3);
|
|
276
247
|
|
|
277
248
|
// now lets do a search, we should see our selection (green) and one other (red)
|
|
278
249
|
await typeInto('temba-select', 're', false);
|
|
279
|
-
await
|
|
250
|
+
await openSelect(clock, select);
|
|
280
251
|
assert.equal(select.visibleOptions.length, 2);
|
|
281
252
|
|
|
282
253
|
await assertScreenshot(
|
|
@@ -342,7 +313,7 @@ describe('temba-select', () => {
|
|
|
342
313
|
assert(changeEvent.called, 'change event not fired');
|
|
343
314
|
|
|
344
315
|
changeEvent.resetHistory();
|
|
345
|
-
await
|
|
316
|
+
await openSelect(clock, select);
|
|
346
317
|
assert.equal(select.visibleOptions.length, 0);
|
|
347
318
|
assert(!changeEvent.called, 'change event should not be fired');
|
|
348
319
|
|
|
@@ -377,6 +348,163 @@ describe('temba-select', () => {
|
|
|
377
348
|
});
|
|
378
349
|
});
|
|
379
350
|
|
|
351
|
+
describe('drag and drop reordering', () => {
|
|
352
|
+
it('handles drag and drop with swap-based logic', async () => {
|
|
353
|
+
const select = await createSelect(
|
|
354
|
+
clock,
|
|
355
|
+
getSelectHTML(
|
|
356
|
+
[
|
|
357
|
+
{ name: 'Red', value: '0', selected: true },
|
|
358
|
+
{ name: 'Green', value: '1', selected: true },
|
|
359
|
+
{ name: 'Blue', value: '2', selected: true }
|
|
360
|
+
],
|
|
361
|
+
{
|
|
362
|
+
placeholder: 'Select colors',
|
|
363
|
+
multi: true
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Verify initial order: Red, Green, Blue
|
|
369
|
+
expect(select.values.length).to.equal(3);
|
|
370
|
+
expect(select.values[0].name).to.equal('Red');
|
|
371
|
+
expect(select.values[1].name).to.equal('Green');
|
|
372
|
+
expect(select.values[2].name).to.equal('Blue');
|
|
373
|
+
|
|
374
|
+
const sortableList = select.shadowRoot.querySelector(
|
|
375
|
+
'temba-sortable-list'
|
|
376
|
+
);
|
|
377
|
+
expect(sortableList).to.not.be.null;
|
|
378
|
+
|
|
379
|
+
// Example 1: Pick up Blue (index 2), drop between Red and Green
|
|
380
|
+
// Expected result: Red, Blue, Green (swap [1,2])
|
|
381
|
+
const blueItem = sortableList.querySelector('#selected-2');
|
|
382
|
+
const greenItem = sortableList.querySelector('#selected-1');
|
|
383
|
+
expect(blueItem).to.not.be.null;
|
|
384
|
+
expect(greenItem).to.not.be.null;
|
|
385
|
+
|
|
386
|
+
const blueBounds = blueItem.getBoundingClientRect();
|
|
387
|
+
const greenBounds = greenItem.getBoundingClientRect();
|
|
388
|
+
|
|
389
|
+
// Start drag from Blue item
|
|
390
|
+
await moveMouse(blueBounds.left + 10, blueBounds.top + 10);
|
|
391
|
+
await mouseDown();
|
|
392
|
+
|
|
393
|
+
// Drag to position between Red and Green (left side of Green)
|
|
394
|
+
await moveMouse(greenBounds.left - 5, greenBounds.top + 10);
|
|
395
|
+
await waitFor(100);
|
|
396
|
+
await mouseUp();
|
|
397
|
+
clock.runAll();
|
|
398
|
+
|
|
399
|
+
// Verify result: Red, Blue, Green (Green and Blue swapped)
|
|
400
|
+
expect(select.values.length).to.equal(3);
|
|
401
|
+
expect(select.values[0].name).to.equal('Red');
|
|
402
|
+
expect(select.values[1].name).to.equal('Blue');
|
|
403
|
+
expect(select.values[2].name).to.equal('Green');
|
|
404
|
+
|
|
405
|
+
// Reset for next test
|
|
406
|
+
select.values = [
|
|
407
|
+
{ name: 'Red', value: '0', selected: true },
|
|
408
|
+
{ name: 'Green', value: '1', selected: true },
|
|
409
|
+
{ name: 'Blue', value: '2', selected: true }
|
|
410
|
+
];
|
|
411
|
+
await select.updateComplete;
|
|
412
|
+
|
|
413
|
+
// Example 2: Pick up Red (index 0), drop at end
|
|
414
|
+
// Expected result: Green, Blue, Red (swap [0,2])
|
|
415
|
+
const redItem = sortableList.querySelector('#selected-0');
|
|
416
|
+
const redBounds = redItem.getBoundingClientRect();
|
|
417
|
+
const blueItemBounds = sortableList
|
|
418
|
+
.querySelector('#selected-2')
|
|
419
|
+
.getBoundingClientRect();
|
|
420
|
+
|
|
421
|
+
// Start drag from Red item
|
|
422
|
+
await moveMouse(redBounds.left + 10, redBounds.top + 10);
|
|
423
|
+
await mouseDown();
|
|
424
|
+
|
|
425
|
+
// Drag to end position (right side of Blue)
|
|
426
|
+
await moveMouse(blueItemBounds.right + 5, blueItemBounds.top + 10);
|
|
427
|
+
await waitFor(100);
|
|
428
|
+
await mouseUp();
|
|
429
|
+
clock.runAll();
|
|
430
|
+
|
|
431
|
+
// Verify result: Green, Blue, Red (Red and Blue swapped)
|
|
432
|
+
expect(select.values.length).to.equal(3);
|
|
433
|
+
expect(select.values[0].name).to.equal('Green');
|
|
434
|
+
expect(select.values[1].name).to.equal('Blue');
|
|
435
|
+
expect(select.values[2].name).to.equal('Red');
|
|
436
|
+
|
|
437
|
+
// Reset for next test
|
|
438
|
+
select.values = [
|
|
439
|
+
{ name: 'Red', value: '0', selected: true },
|
|
440
|
+
{ name: 'Green', value: '1', selected: true },
|
|
441
|
+
{ name: 'Blue', value: '2', selected: true }
|
|
442
|
+
];
|
|
443
|
+
await select.updateComplete;
|
|
444
|
+
|
|
445
|
+
// Example 3: Pick up Green (index 1), drop at same position
|
|
446
|
+
// Expected result: No change, no event
|
|
447
|
+
const greenItemNew = sortableList.querySelector('#selected-1');
|
|
448
|
+
const greenBoundsNew = greenItemNew.getBoundingClientRect();
|
|
449
|
+
|
|
450
|
+
// Start drag from Green item
|
|
451
|
+
await moveMouse(greenBoundsNew.left + 10, greenBoundsNew.top + 10);
|
|
452
|
+
await mouseDown();
|
|
453
|
+
|
|
454
|
+
// Drag slightly but return to same position
|
|
455
|
+
await moveMouse(greenBoundsNew.left + 15, greenBoundsNew.top + 10);
|
|
456
|
+
await moveMouse(greenBoundsNew.left + 10, greenBoundsNew.top + 10);
|
|
457
|
+
await waitFor(100);
|
|
458
|
+
await mouseUp();
|
|
459
|
+
clock.runAll();
|
|
460
|
+
|
|
461
|
+
// Verify result: No change
|
|
462
|
+
expect(select.values.length).to.equal(3);
|
|
463
|
+
expect(select.values[0].name).to.equal('Red');
|
|
464
|
+
expect(select.values[1].name).to.equal('Green');
|
|
465
|
+
expect(select.values[2].name).to.equal('Blue');
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('does not show sortable list for single item', async () => {
|
|
469
|
+
const select = await createSelect(
|
|
470
|
+
clock,
|
|
471
|
+
getSelectHTML([{ name: 'Red', value: '0', selected: true }], {
|
|
472
|
+
placeholder: 'Select a color',
|
|
473
|
+
multi: true
|
|
474
|
+
})
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// Should not have a sortable list with only one item
|
|
478
|
+
const sortableList = select.shadowRoot.querySelector(
|
|
479
|
+
'temba-sortable-list'
|
|
480
|
+
);
|
|
481
|
+
expect(sortableList).to.be.null;
|
|
482
|
+
|
|
483
|
+
// Should still show the selected item normally
|
|
484
|
+
expect(select.values.length).to.equal(1);
|
|
485
|
+
expect(select.values[0].name).to.equal('Red');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('does not show sortable list for non-multi select', async () => {
|
|
489
|
+
const select = await createSelect(
|
|
490
|
+
clock,
|
|
491
|
+
getSelectHTML([{ name: 'Red', value: '0', selected: true }], {
|
|
492
|
+
placeholder: 'Select a color',
|
|
493
|
+
multi: false
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// Should not have a sortable list for single select
|
|
498
|
+
const sortableList = select.shadowRoot.querySelector(
|
|
499
|
+
'temba-sortable-list'
|
|
500
|
+
);
|
|
501
|
+
expect(sortableList).to.be.null;
|
|
502
|
+
|
|
503
|
+
expect(select.values.length).to.equal(1);
|
|
504
|
+
expect(select.values[0].name).to.equal('Red');
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
380
508
|
describe('static options', () => {
|
|
381
509
|
it('accepts an initial value', async () => {
|
|
382
510
|
const select = await createSelect(
|
|
@@ -407,7 +535,7 @@ describe('temba-select', () => {
|
|
|
407
535
|
})
|
|
408
536
|
);
|
|
409
537
|
|
|
410
|
-
await
|
|
538
|
+
await openSelect(clock, select);
|
|
411
539
|
await assertScreenshot(
|
|
412
540
|
'select/remote-options',
|
|
413
541
|
getClipWithOptions(select)
|
|
@@ -426,7 +554,7 @@ describe('temba-select', () => {
|
|
|
426
554
|
);
|
|
427
555
|
|
|
428
556
|
await typeInto('temba-select', 're', false);
|
|
429
|
-
await
|
|
557
|
+
await openSelect(clock, select);
|
|
430
558
|
assert.equal(select.visibleOptions.length, 2);
|
|
431
559
|
|
|
432
560
|
await assertScreenshot('select/searching', getClipWithOptions(select));
|
|
@@ -463,7 +591,7 @@ describe('temba-select', () => {
|
|
|
463
591
|
})
|
|
464
592
|
);
|
|
465
593
|
|
|
466
|
-
await
|
|
594
|
+
await openSelect(clock, select);
|
|
467
595
|
|
|
468
596
|
// should have all three pages visible right away
|
|
469
597
|
assert.equal(select.visibleOptions.length, 15);
|
|
@@ -481,14 +609,14 @@ describe('temba-select', () => {
|
|
|
481
609
|
);
|
|
482
610
|
|
|
483
611
|
// wait for updates from fetching three pages
|
|
484
|
-
await
|
|
612
|
+
await openSelect(clock, select);
|
|
485
613
|
assert.equal(select.visibleOptions.length, 15);
|
|
486
614
|
|
|
487
615
|
// close and reopen
|
|
488
616
|
select.blur();
|
|
489
617
|
await clock.tick(250);
|
|
490
618
|
|
|
491
|
-
await
|
|
619
|
+
await openSelect(clock, select);
|
|
492
620
|
assert.equal(select.visibleOptions.length, 15);
|
|
493
621
|
|
|
494
622
|
// close and reopen once more (previous bug failed on third opening)
|
|
@@ -509,7 +637,7 @@ describe('temba-select', () => {
|
|
|
509
637
|
);
|
|
510
638
|
|
|
511
639
|
await typeInto('temba-select', 'Hi there @contact', false);
|
|
512
|
-
await
|
|
640
|
+
await openSelect(clock, select);
|
|
513
641
|
|
|
514
642
|
assert.equal(select.completionOptions.length, 14);
|
|
515
643
|
await assertScreenshot('select/expressions', getClipWithOptions(select));
|
|
@@ -569,7 +697,7 @@ describe('temba-select', () => {
|
|
|
569
697
|
await openAndClick(clock, select, 1);
|
|
570
698
|
|
|
571
699
|
// now open and look at focus
|
|
572
|
-
await
|
|
700
|
+
await openSelect(clock, select);
|
|
573
701
|
await assertScreenshot(
|
|
574
702
|
'select/search-selected-focus',
|
|
575
703
|
getClipWithOptions(select)
|
|
@@ -589,11 +717,11 @@ describe('temba-select', () => {
|
|
|
589
717
|
// select the first option
|
|
590
718
|
await openAndClick(clock, select, 0);
|
|
591
719
|
await openAndClick(clock, select, 0);
|
|
592
|
-
await
|
|
720
|
+
await openSelect(clock, select);
|
|
593
721
|
|
|
594
722
|
// now lets do a search, we should see our selection (green) and one other (red)
|
|
595
723
|
await typeInto('temba-select', 're', false);
|
|
596
|
-
await
|
|
724
|
+
await openSelect(clock, select);
|
|
597
725
|
|
|
598
726
|
// should have two things selected and active query and no matching options
|
|
599
727
|
await assertScreenshot(
|
|
@@ -615,7 +743,7 @@ describe('temba-select', () => {
|
|
|
615
743
|
);
|
|
616
744
|
|
|
617
745
|
await typeInto('temba-select', 'look at @(max(m', false);
|
|
618
|
-
await
|
|
746
|
+
await openSelect(clock, select);
|
|
619
747
|
|
|
620
748
|
await assertScreenshot('select/functions', getClipWithOptions(select));
|
|
621
749
|
});
|
|
@@ -623,7 +751,7 @@ describe('temba-select', () => {
|
|
|
623
751
|
it('should truncate selection if necessesary', async () => {
|
|
624
752
|
const options = [
|
|
625
753
|
{
|
|
626
|
-
name: '
|
|
754
|
+
name: 'this_is_a_long_selection_to_make_sure_it_truncates_but_it_needs_to_be_longer',
|
|
627
755
|
value: '0'
|
|
628
756
|
}
|
|
629
757
|
];
|
|
@@ -3,52 +3,145 @@ import { html, TemplateResult } from 'lit';
|
|
|
3
3
|
import { CustomEventType } from '../src/interfaces';
|
|
4
4
|
import { SortableList } from '../src/list/SortableList';
|
|
5
5
|
import { assertScreenshot, getClip } from './utils.test';
|
|
6
|
+
import Sinon, { useFakeTimers } from 'sinon';
|
|
6
7
|
|
|
7
8
|
const BORING_LIST = html`
|
|
8
9
|
<temba-sortable-list>
|
|
9
|
-
<
|
|
10
|
-
|
|
10
|
+
<style>
|
|
11
|
+
.sortable {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
text-align: center;
|
|
16
|
+
height: 20px;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
19
|
+
<div class="sortable" id="chicken" style="">Chicken</div>
|
|
20
|
+
<div class="sortable" id="fish">Fish</div>
|
|
21
|
+
</temba-sortable-list>
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const HORIZONTAL_LIST = html`
|
|
25
|
+
<temba-sortable-list horizontal>
|
|
26
|
+
<style>
|
|
27
|
+
.sortable {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
text-align: center;
|
|
32
|
+
height: 20px;
|
|
33
|
+
width: 50px;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
<div class="sortable" id="red">Red</div>
|
|
37
|
+
<div class="sortable" id="blue">Blue</div>
|
|
38
|
+
<div class="sortable" id="green">Green</div>
|
|
11
39
|
</temba-sortable-list>
|
|
12
40
|
`;
|
|
13
41
|
|
|
14
42
|
const createSorter = async (def: TemplateResult) => {
|
|
15
43
|
const parentNode = document.createElement('div');
|
|
16
|
-
parentNode.setAttribute('style', 'width:
|
|
44
|
+
parentNode.setAttribute('style', 'width: 100px;');
|
|
17
45
|
return (await fixture(def, { parentNode })) as SortableList;
|
|
18
46
|
};
|
|
19
47
|
|
|
20
48
|
describe('temba-sortable-list', () => {
|
|
49
|
+
let clock: Sinon.SinonFakeTimers;
|
|
50
|
+
beforeEach(function () {
|
|
51
|
+
clock = useFakeTimers();
|
|
52
|
+
clock.runAll();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(function () {
|
|
56
|
+
clock.restore();
|
|
57
|
+
});
|
|
58
|
+
|
|
21
59
|
it('renders default', async () => {
|
|
22
60
|
const list: SortableList = await createSorter(BORING_LIST);
|
|
23
61
|
await assertScreenshot('list/sortable', getClip(list));
|
|
24
62
|
});
|
|
25
63
|
|
|
26
|
-
it('
|
|
64
|
+
it('can get ids of sortable elements', async () => {
|
|
27
65
|
const list: SortableList = await createSorter(BORING_LIST);
|
|
28
|
-
|
|
29
|
-
|
|
66
|
+
await list.updateComplete;
|
|
67
|
+
|
|
68
|
+
const ids = list.getIds();
|
|
69
|
+
expect(ids).to.deep.equal(['chicken', 'fish']);
|
|
70
|
+
});
|
|
30
71
|
|
|
72
|
+
it('works with horizontal layout', async () => {
|
|
73
|
+
const list: SortableList = await createSorter(HORIZONTAL_LIST);
|
|
74
|
+
await list.updateComplete;
|
|
75
|
+
|
|
76
|
+
const ids = list.getIds();
|
|
77
|
+
expect(ids).to.deep.equal(['red', 'blue', 'green']);
|
|
78
|
+
|
|
79
|
+
// Test horizontal drag behavior
|
|
31
80
|
const bounds = list.getBoundingClientRect();
|
|
81
|
+
const orderChanged = oneEvent(list, CustomEventType.OrderChanged, false);
|
|
32
82
|
|
|
33
|
-
|
|
83
|
+
// Drag the first item (red) to after the second item (blue)
|
|
84
|
+
await moveMouse(bounds.left + 10, bounds.top + 10);
|
|
34
85
|
await mouseDown();
|
|
35
|
-
await moveMouse(bounds.left +
|
|
86
|
+
await moveMouse(bounds.left + 80, bounds.top + 10);
|
|
87
|
+
await mouseUp();
|
|
88
|
+
clock.runAll();
|
|
36
89
|
|
|
37
|
-
// we should fire an order changed event
|
|
38
90
|
const orderEvent = await orderChanged;
|
|
39
91
|
expect(orderEvent.detail).to.deep.equal({
|
|
40
|
-
|
|
41
|
-
to: 'chicken',
|
|
42
|
-
fromIdx: 1,
|
|
43
|
-
toIdx: 0
|
|
92
|
+
swap: [0, 2]
|
|
44
93
|
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('handles prepareGhost callback', async () => {
|
|
97
|
+
const list: SortableList = await createSorter(BORING_LIST);
|
|
98
|
+
let ghostPrepared = false;
|
|
99
|
+
|
|
100
|
+
list.prepareGhost = (ghost: HTMLElement) => {
|
|
101
|
+
ghostPrepared = true;
|
|
102
|
+
ghost.style.backgroundColor = 'red';
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const bounds = list.getBoundingClientRect();
|
|
106
|
+
|
|
107
|
+
// Start dragging to trigger ghost creation
|
|
108
|
+
await moveMouse(bounds.left + 20, bounds.bottom - 10);
|
|
109
|
+
await mouseDown();
|
|
110
|
+
await moveMouse(bounds.left + 30, bounds.bottom - 10);
|
|
111
|
+
|
|
112
|
+
expect(ghostPrepared).to.be.true;
|
|
113
|
+
|
|
114
|
+
// Clean up
|
|
115
|
+
await mouseUp();
|
|
116
|
+
clock.runAll();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('drags', async () => {
|
|
120
|
+
const list: SortableList = await createSorter(BORING_LIST);
|
|
121
|
+
const updated = oneEvent(list, 'change', false);
|
|
122
|
+
|
|
123
|
+
const bounds = list.getBoundingClientRect();
|
|
124
|
+
|
|
125
|
+
await moveMouse(bounds.left + 20, bounds.bottom - 10);
|
|
126
|
+
await mouseDown();
|
|
127
|
+
await moveMouse(bounds.left + 20, bounds.top + 5);
|
|
45
128
|
|
|
46
129
|
// should be hovered
|
|
47
130
|
await assertScreenshot('list/sortable-dragging', getClip(list));
|
|
48
131
|
|
|
49
|
-
// now lets drop
|
|
50
|
-
|
|
132
|
+
// now lets drop - this will fire the order changed event
|
|
133
|
+
const orderChanged = oneEvent(list, CustomEventType.OrderChanged, false);
|
|
51
134
|
await mouseUp();
|
|
135
|
+
clock.runAll();
|
|
136
|
+
await list.updateComplete;
|
|
137
|
+
clock.runAll();
|
|
138
|
+
|
|
139
|
+
// we should fire an order changed event on drop
|
|
140
|
+
const orderEvent = await orderChanged;
|
|
141
|
+
expect(orderEvent.detail).to.deep.equal({
|
|
142
|
+
swap: [1, 0]
|
|
143
|
+
});
|
|
144
|
+
|
|
52
145
|
await assertScreenshot('list/sortable-dropped', getClip(list));
|
|
53
146
|
|
|
54
147
|
// we should fire a change event
|