@nyaruka/temba-components 0.122.0 → 0.123.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 +163 -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 +33 -0
- package/demo/index.html +42 -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 +59 -72
- 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/list/RunList.js +13 -8
- package/out-tsc/src/list/RunList.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/options/Options.js +36 -13
- package/out-tsc/src/options/Options.js.map +1 -1
- package/out-tsc/src/select/Select.js +28 -5
- 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/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-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 +16 -0
- package/out-tsc/test/temba-select.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.test.js +816 -0
- package/out-tsc/test/temba-webchat.test.js.map +1 -0
- package/out-tsc/test/utils.test.js +3 -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/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-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/list/RunList.ts +11 -8
- package/src/locales/es.ts +1 -0
- package/src/locales/fr.ts +1 -0
- package/src/locales/pt.ts +1 -0
- package/src/options/Options.ts +38 -13
- package/src/select/Select.ts +32 -5
- package/src/store/AppState.ts +3 -3
- package/src/utils/index.ts +17 -5
- package/src/vectoricon/VectorIcon.ts +2 -1
- 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-run-list.test.ts +774 -0
- package/test/temba-select.test.ts +27 -0
- package/test/temba-toast.test.ts +386 -0
- package/test/temba-utils-index.test.ts +1547 -0
- package/test/temba-webchat.test.ts +1095 -0
- package/test/utils.test.ts +4 -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,4 +1,5 @@
|
|
|
1
1
|
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { formatDurationFromSeconds } from '../src/chart/TembaChart';
|
|
2
3
|
import { getComponent } from './utils.test';
|
|
3
4
|
const sampleData = {
|
|
4
5
|
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
|
@@ -42,5 +43,129 @@ describe('temba-chart', () => {
|
|
|
42
43
|
// now others should be everything but general and support
|
|
43
44
|
expect(chart.datasets[2].data).to.deep.equal([58, 28, 10, 1, 0, 19, 20]);
|
|
44
45
|
});
|
|
46
|
+
it('supports duration formatting', async () => {
|
|
47
|
+
const chart = await getChart();
|
|
48
|
+
// Test that formatDuration property exists and defaults to false
|
|
49
|
+
expect(chart.formatDuration).to.equal(false);
|
|
50
|
+
// Test that we can set formatDuration to true
|
|
51
|
+
chart.formatDuration = true;
|
|
52
|
+
expect(chart.formatDuration).to.equal(true);
|
|
53
|
+
});
|
|
54
|
+
it('formats duration values correctly', async () => {
|
|
55
|
+
const chart = await getChart();
|
|
56
|
+
// Access the formatDurationFromSeconds function through the chart's module
|
|
57
|
+
// We need to test the duration formatting logic
|
|
58
|
+
const durationData = {
|
|
59
|
+
labels: ['Day 1', 'Day 2', 'Day 3'],
|
|
60
|
+
datasets: [
|
|
61
|
+
{
|
|
62
|
+
label: 'Process Time',
|
|
63
|
+
data: [68787, 958000, 3661] // seconds that should be formatted as durations
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
chart.formatDuration = true;
|
|
68
|
+
chart.data = durationData;
|
|
69
|
+
await chart.updateComplete;
|
|
70
|
+
// Wait for the chart to be created after data is set
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
72
|
+
// Test that the chart was created and has the duration formatting enabled
|
|
73
|
+
expect(chart.formatDuration).to.equal(true);
|
|
74
|
+
expect(chart.chart).to.exist;
|
|
75
|
+
// Test that the chart configuration includes the duration formatting
|
|
76
|
+
const chartConfig = chart.chart.options;
|
|
77
|
+
expect(chartConfig.scales.y.ticks).to.exist;
|
|
78
|
+
expect(chartConfig.scales.y.ticks.callback).to.be.a('function');
|
|
79
|
+
// Test the tick callback function formatting
|
|
80
|
+
const tickCallback = chartConfig.scales.y.ticks.callback;
|
|
81
|
+
expect(tickCallback.call({}, 68787, 0, [])).to.equal('19h 6m');
|
|
82
|
+
expect(tickCallback.call({}, 958000, 1, [])).to.equal('11d 2h');
|
|
83
|
+
expect(tickCallback.call({}, 3661, 2, [])).to.equal('1h 1m');
|
|
84
|
+
expect(tickCallback.call({}, 120, 3, [])).to.equal('2m');
|
|
85
|
+
expect(tickCallback.call({}, 0, 4, [])).to.equal('0s');
|
|
86
|
+
// Test tooltip formatting
|
|
87
|
+
expect(chartConfig.plugins.tooltip.callbacks.label).to.be.a('function');
|
|
88
|
+
const tooltipCallback = chartConfig.plugins.tooltip.callbacks.label;
|
|
89
|
+
const mockContext = {
|
|
90
|
+
dataset: { label: 'Process Time' },
|
|
91
|
+
parsed: { y: 68787 }
|
|
92
|
+
};
|
|
93
|
+
expect(tooltipCallback.call({}, mockContext)).to.equal('Process Time: 19h 6m');
|
|
94
|
+
});
|
|
95
|
+
it('formats various duration edge cases correctly', async () => {
|
|
96
|
+
const chart = await getChart();
|
|
97
|
+
chart.formatDuration = true;
|
|
98
|
+
chart.data = sampleData;
|
|
99
|
+
await chart.updateComplete;
|
|
100
|
+
// Wait for the chart to be created after data is set
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
102
|
+
expect(chart.chart).to.exist;
|
|
103
|
+
const tickCallback = chart.chart.options.scales.y.ticks.callback;
|
|
104
|
+
// Test edge cases for duration formatting
|
|
105
|
+
expect(tickCallback.call({}, 0, 0, [])).to.equal('0s');
|
|
106
|
+
expect(tickCallback.call({}, 1, 1, [])).to.equal('1s');
|
|
107
|
+
expect(tickCallback.call({}, 59, 2, [])).to.equal('59s');
|
|
108
|
+
expect(tickCallback.call({}, 60, 3, [])).to.equal('1m');
|
|
109
|
+
expect(tickCallback.call({}, 61, 4, [])).to.equal('1m 1s');
|
|
110
|
+
expect(tickCallback.call({}, 3600, 5, [])).to.equal('1h');
|
|
111
|
+
expect(tickCallback.call({}, 3661, 6, [])).to.equal('1h 1m');
|
|
112
|
+
expect(tickCallback.call({}, 86400, 7, [])).to.equal('1d');
|
|
113
|
+
expect(tickCallback.call({}, 90061, 8, [])).to.equal('1d 1h'); // 1 day, 1 hour, 1 minute, 1 second - should show only first two units
|
|
114
|
+
expect(tickCallback.call({}, 604800, 9, [])).to.equal('7d'); // 1 week in seconds
|
|
115
|
+
expect(tickCallback.call({}, 1209600, 10, [])).to.equal('14d'); // 2 weeks in seconds
|
|
116
|
+
});
|
|
117
|
+
it('respects formatDuration property state', async () => {
|
|
118
|
+
const chart = await getChart();
|
|
119
|
+
// Test default state
|
|
120
|
+
expect(chart.formatDuration).to.equal(false);
|
|
121
|
+
chart.data = sampleData;
|
|
122
|
+
await chart.updateComplete;
|
|
123
|
+
// Test that formatDuration property can be toggled
|
|
124
|
+
chart.formatDuration = true;
|
|
125
|
+
expect(chart.formatDuration).to.equal(true);
|
|
126
|
+
chart.formatDuration = false;
|
|
127
|
+
expect(chart.formatDuration).to.equal(false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('formatDurationFromSeconds', () => {
|
|
131
|
+
it('formats zero correctly', () => {
|
|
132
|
+
expect(formatDurationFromSeconds(0)).to.equal('0s');
|
|
133
|
+
});
|
|
134
|
+
it('formats seconds only', () => {
|
|
135
|
+
expect(formatDurationFromSeconds(1)).to.equal('1s');
|
|
136
|
+
expect(formatDurationFromSeconds(30)).to.equal('30s');
|
|
137
|
+
expect(formatDurationFromSeconds(59)).to.equal('59s');
|
|
138
|
+
});
|
|
139
|
+
it('formats minutes and seconds', () => {
|
|
140
|
+
expect(formatDurationFromSeconds(60)).to.equal('1m');
|
|
141
|
+
expect(formatDurationFromSeconds(61)).to.equal('1m 1s');
|
|
142
|
+
expect(formatDurationFromSeconds(90)).to.equal('1m 30s');
|
|
143
|
+
expect(formatDurationFromSeconds(120)).to.equal('2m');
|
|
144
|
+
expect(formatDurationFromSeconds(3599)).to.equal('59m 59s');
|
|
145
|
+
});
|
|
146
|
+
it('formats hours and minutes', () => {
|
|
147
|
+
expect(formatDurationFromSeconds(3600)).to.equal('1h');
|
|
148
|
+
expect(formatDurationFromSeconds(3661)).to.equal('1h 1m');
|
|
149
|
+
expect(formatDurationFromSeconds(7200)).to.equal('2h');
|
|
150
|
+
expect(formatDurationFromSeconds(68787)).to.equal('19h 6m');
|
|
151
|
+
});
|
|
152
|
+
it('formats days and hours', () => {
|
|
153
|
+
expect(formatDurationFromSeconds(86400)).to.equal('1d');
|
|
154
|
+
expect(formatDurationFromSeconds(90000)).to.equal('1d 1h');
|
|
155
|
+
expect(formatDurationFromSeconds(958000)).to.equal('11d 2h');
|
|
156
|
+
});
|
|
157
|
+
it('shows only two most significant units', () => {
|
|
158
|
+
// 1 day, 1 hour, 1 minute, 1 second - should show only "1d 1h"
|
|
159
|
+
expect(formatDurationFromSeconds(90061)).to.equal('1d 1h');
|
|
160
|
+
// 2 hours, 30 minutes, 45 seconds - should show only "2h 30m"
|
|
161
|
+
expect(formatDurationFromSeconds(9045)).to.equal('2h 30m');
|
|
162
|
+
// 5 minutes, 30 seconds - should show "5m 30s"
|
|
163
|
+
expect(formatDurationFromSeconds(330)).to.equal('5m 30s');
|
|
164
|
+
});
|
|
165
|
+
it('handles large durations', () => {
|
|
166
|
+
expect(formatDurationFromSeconds(604800)).to.equal('7d'); // 1 week
|
|
167
|
+
expect(formatDurationFromSeconds(1209600)).to.equal('14d'); // 2 weeks
|
|
168
|
+
expect(formatDurationFromSeconds(2678400)).to.equal('31d'); // ~1 month
|
|
169
|
+
});
|
|
45
170
|
});
|
|
46
171
|
//# sourceMappingURL=temba-chart.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temba-chart.test.js","sourceRoot":"","sources":["../../test/temba-chart.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,UAAU,GAAmB;IACjC,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;IACxE,QAAQ,EAAE;QACR;YACE,KAAK,EAAE,SAAS;YAChB,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;SACnC;QACD;YACE,KAAK,EAAE,SAAS;YAChB,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;SACnC;QACD;YACE,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC;SACjC;KACF;CACF,CAAC;AAEF,MAAM,GAAG,GAAG,aAAa,CAAC;AAC1B,MAAM,QAAQ,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE,EAAE;IACzC,MAAM,MAAM,GAAG,CAAC,MAAM,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,CAAC,CAAe,CAAC;IACvE,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;QACjC,MAAM,KAAK,GAAe,MAAM,QAAQ,EAAE,CAAC;QAE3C,KAAK,CAAC,IAAI,GAAG,UAAU,CAAC;QACxB,MAAM,KAAK,CAAC,cAAc,CAAC;QAE3B,qEAAqE;QACrE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;YAC3C,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;SAClC,CAAC,CAAC;QAEH,cAAc;QACd,KAAK,CAAC,MAAM,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3B,MAAM,KAAK,CAAC,cAAc,CAAC;QAE3B,wCAAwC;QACxC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QAE5E,oBAAoB;QACpB,KAAK,CAAC,MAAM,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACtC,MAAM,KAAK,CAAC,cAAc,CAAC;QAE3B,0DAA0D;QAC1D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { RapidChartData, TembaChart } from '../src/chart/TembaChart';\nimport { getComponent } from './utils.test';\n\nconst sampleData: RapidChartData = {\n labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n datasets: [\n {\n label: 'General',\n data: [65, 59, 80, 81, 56, 55, 40]\n },\n {\n label: 'Support',\n data: [28, 48, 40, 19, 86, 27, 90]\n },\n {\n label: 'VIPs',\n data: [58, 28, 10, 1, 0, 19, 20]\n }\n ]\n};\n\nconst TAG = 'temba-chart';\nconst getChart = async (attrs: any = {}) => {\n const picker = (await getComponent(TAG, attrs, '', 400)) as TembaChart;\n return picker;\n};\n\ndescribe('temba-chart', () => {\n it('calculates others', async () => {\n const chart: TembaChart = await getChart();\n\n chart.data = sampleData;\n await chart.updateComplete;\n\n // if we haven't set any splits, everything should be summed as \"All\"\n expect(chart.datasets[0].data).to.deep.equal([\n 151, 135, 130, 101, 142, 101, 150\n ]);\n\n // add a split\n chart.splits = ['General'];\n await chart.updateComplete;\n\n // now we should get an \"Others\" dataset\n expect(chart.datasets[1].data).to.deep.equal([86, 76, 50, 20, 86, 46, 110]);\n\n // add another split\n chart.splits = ['General', 'Support'];\n await chart.updateComplete;\n\n // now others should be everything but general and support\n expect(chart.datasets[2].data).to.deep.equal([58, 28, 10, 1, 0, 19, 20]);\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"temba-chart.test.js","sourceRoot":"","sources":["../../test/temba-chart.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport {\n RapidChartData,\n TembaChart,\n formatDurationFromSeconds\n} from '../src/chart/TembaChart';\nimport { getComponent } from './utils.test';\n\nconst sampleData: RapidChartData = {\n labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n datasets: [\n {\n label: 'General',\n data: [65, 59, 80, 81, 56, 55, 40]\n },\n {\n label: 'Support',\n data: [28, 48, 40, 19, 86, 27, 90]\n },\n {\n label: 'VIPs',\n data: [58, 28, 10, 1, 0, 19, 20]\n }\n ]\n};\n\nconst TAG = 'temba-chart';\nconst getChart = async (attrs: any = {}) => {\n const picker = (await getComponent(TAG, attrs, '', 400)) as TembaChart;\n return picker;\n};\n\ndescribe('temba-chart', () => {\n it('calculates others', async () => {\n const chart: TembaChart = await getChart();\n\n chart.data = sampleData;\n await chart.updateComplete;\n\n // if we haven't set any splits, everything should be summed as \"All\"\n expect(chart.datasets[0].data).to.deep.equal([\n 151, 135, 130, 101, 142, 101, 150\n ]);\n\n // add a split\n chart.splits = ['General'];\n await chart.updateComplete;\n\n // now we should get an \"Others\" dataset\n expect(chart.datasets[1].data).to.deep.equal([86, 76, 50, 20, 86, 46, 110]);\n\n // add another split\n chart.splits = ['General', 'Support'];\n await chart.updateComplete;\n\n // now others should be everything but general and support\n expect(chart.datasets[2].data).to.deep.equal([58, 28, 10, 1, 0, 19, 20]);\n });\n\n it('supports duration formatting', async () => {\n const chart: TembaChart = await getChart();\n\n // Test that formatDuration property exists and defaults to false\n expect(chart.formatDuration).to.equal(false);\n\n // Test that we can set formatDuration to true\n chart.formatDuration = true;\n expect(chart.formatDuration).to.equal(true);\n });\n\n it('formats duration values correctly', async () => {\n const chart: TembaChart = await getChart();\n\n // Access the formatDurationFromSeconds function through the chart's module\n // We need to test the duration formatting logic\n const durationData: RapidChartData = {\n labels: ['Day 1', 'Day 2', 'Day 3'],\n datasets: [\n {\n label: 'Process Time',\n data: [68787, 958000, 3661] // seconds that should be formatted as durations\n }\n ]\n };\n\n chart.formatDuration = true;\n chart.data = durationData;\n await chart.updateComplete;\n\n // Wait for the chart to be created after data is set\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Test that the chart was created and has the duration formatting enabled\n expect(chart.formatDuration).to.equal(true);\n expect(chart.chart).to.exist;\n\n // Test that the chart configuration includes the duration formatting\n const chartConfig = chart.chart.options;\n expect(chartConfig.scales.y.ticks).to.exist;\n expect(chartConfig.scales.y.ticks.callback).to.be.a('function');\n\n // Test the tick callback function formatting\n const tickCallback = chartConfig.scales.y.ticks.callback;\n expect(tickCallback.call({}, 68787, 0, [])).to.equal('19h 6m');\n expect(tickCallback.call({}, 958000, 1, [])).to.equal('11d 2h');\n expect(tickCallback.call({}, 3661, 2, [])).to.equal('1h 1m');\n expect(tickCallback.call({}, 120, 3, [])).to.equal('2m');\n expect(tickCallback.call({}, 0, 4, [])).to.equal('0s');\n\n // Test tooltip formatting\n expect(chartConfig.plugins.tooltip.callbacks.label).to.be.a('function');\n const tooltipCallback = chartConfig.plugins.tooltip.callbacks.label;\n\n const mockContext = {\n dataset: { label: 'Process Time' },\n parsed: { y: 68787 }\n };\n expect(tooltipCallback.call({}, mockContext)).to.equal(\n 'Process Time: 19h 6m'\n );\n });\n\n it('formats various duration edge cases correctly', async () => {\n const chart: TembaChart = await getChart();\n chart.formatDuration = true;\n chart.data = sampleData;\n await chart.updateComplete;\n\n // Wait for the chart to be created after data is set\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n expect(chart.chart).to.exist;\n const tickCallback = chart.chart.options.scales.y.ticks.callback;\n\n // Test edge cases for duration formatting\n expect(tickCallback.call({}, 0, 0, [])).to.equal('0s');\n expect(tickCallback.call({}, 1, 1, [])).to.equal('1s');\n expect(tickCallback.call({}, 59, 2, [])).to.equal('59s');\n expect(tickCallback.call({}, 60, 3, [])).to.equal('1m');\n expect(tickCallback.call({}, 61, 4, [])).to.equal('1m 1s');\n expect(tickCallback.call({}, 3600, 5, [])).to.equal('1h');\n expect(tickCallback.call({}, 3661, 6, [])).to.equal('1h 1m');\n expect(tickCallback.call({}, 86400, 7, [])).to.equal('1d');\n expect(tickCallback.call({}, 90061, 8, [])).to.equal('1d 1h'); // 1 day, 1 hour, 1 minute, 1 second - should show only first two units\n expect(tickCallback.call({}, 604800, 9, [])).to.equal('7d'); // 1 week in seconds\n expect(tickCallback.call({}, 1209600, 10, [])).to.equal('14d'); // 2 weeks in seconds\n });\n\n it('respects formatDuration property state', async () => {\n const chart: TembaChart = await getChart();\n\n // Test default state\n expect(chart.formatDuration).to.equal(false);\n\n chart.data = sampleData;\n await chart.updateComplete;\n\n // Test that formatDuration property can be toggled\n chart.formatDuration = true;\n expect(chart.formatDuration).to.equal(true);\n\n chart.formatDuration = false;\n expect(chart.formatDuration).to.equal(false);\n });\n});\n\ndescribe('formatDurationFromSeconds', () => {\n it('formats zero correctly', () => {\n expect(formatDurationFromSeconds(0)).to.equal('0s');\n });\n\n it('formats seconds only', () => {\n expect(formatDurationFromSeconds(1)).to.equal('1s');\n expect(formatDurationFromSeconds(30)).to.equal('30s');\n expect(formatDurationFromSeconds(59)).to.equal('59s');\n });\n\n it('formats minutes and seconds', () => {\n expect(formatDurationFromSeconds(60)).to.equal('1m');\n expect(formatDurationFromSeconds(61)).to.equal('1m 1s');\n expect(formatDurationFromSeconds(90)).to.equal('1m 30s');\n expect(formatDurationFromSeconds(120)).to.equal('2m');\n expect(formatDurationFromSeconds(3599)).to.equal('59m 59s');\n });\n\n it('formats hours and minutes', () => {\n expect(formatDurationFromSeconds(3600)).to.equal('1h');\n expect(formatDurationFromSeconds(3661)).to.equal('1h 1m');\n expect(formatDurationFromSeconds(7200)).to.equal('2h');\n expect(formatDurationFromSeconds(68787)).to.equal('19h 6m');\n });\n\n it('formats days and hours', () => {\n expect(formatDurationFromSeconds(86400)).to.equal('1d');\n expect(formatDurationFromSeconds(90000)).to.equal('1d 1h');\n expect(formatDurationFromSeconds(958000)).to.equal('11d 2h');\n });\n\n it('shows only two most significant units', () => {\n // 1 day, 1 hour, 1 minute, 1 second - should show only \"1d 1h\"\n expect(formatDurationFromSeconds(90061)).to.equal('1d 1h');\n\n // 2 hours, 30 minutes, 45 seconds - should show only \"2h 30m\"\n expect(formatDurationFromSeconds(9045)).to.equal('2h 30m');\n\n // 5 minutes, 30 seconds - should show \"5m 30s\"\n expect(formatDurationFromSeconds(330)).to.equal('5m 30s');\n });\n\n it('handles large durations', () => {\n expect(formatDurationFromSeconds(604800)).to.equal('7d'); // 1 week\n expect(formatDurationFromSeconds(1209600)).to.equal('14d'); // 2 weeks\n expect(formatDurationFromSeconds(2678400)).to.equal('31d'); // ~1 month\n });\n});\n"]}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { assert, expect } from '@open-wc/testing';
|
|
2
|
+
import { Dropdown } from '../src/dropdown/Dropdown';
|
|
3
|
+
import { assertScreenshot, getClip, getComponent } from './utils.test';
|
|
4
|
+
const TAG = 'temba-dropdown';
|
|
5
|
+
// Helper function to wait for stable rendering
|
|
6
|
+
const waitForStableRender = async (dropdown, timeoutMs = 200) => {
|
|
7
|
+
await dropdown.updateComplete;
|
|
8
|
+
// Double wait to ensure any async positioning is complete
|
|
9
|
+
await new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
10
|
+
await dropdown.updateComplete;
|
|
11
|
+
};
|
|
12
|
+
// Helper function to get expanded clip that includes dropdown content when open
|
|
13
|
+
const getDropdownClip = (dropdown) => {
|
|
14
|
+
if (!dropdown.open) {
|
|
15
|
+
// If closed, use regular clipping
|
|
16
|
+
return getClip(dropdown);
|
|
17
|
+
}
|
|
18
|
+
// For open dropdowns, include the positioned dropdown content
|
|
19
|
+
const dropdownDiv = dropdown.shadowRoot.querySelector('.dropdown');
|
|
20
|
+
const dropdownBounds = dropdownDiv.getBoundingClientRect();
|
|
21
|
+
const componentBounds = dropdown.getBoundingClientRect();
|
|
22
|
+
// If dropdown content has no meaningful size, fall back to regular clip
|
|
23
|
+
if (dropdownBounds.width < 10 || dropdownBounds.height < 10) {
|
|
24
|
+
return getClip(dropdown);
|
|
25
|
+
}
|
|
26
|
+
// Create a clipping region that includes both the component and the dropdown content
|
|
27
|
+
const minX = Math.min(componentBounds.x, dropdownBounds.x);
|
|
28
|
+
const minY = Math.min(componentBounds.y, dropdownBounds.y);
|
|
29
|
+
const maxX = Math.max(componentBounds.right, dropdownBounds.right);
|
|
30
|
+
const maxY = Math.max(componentBounds.bottom, dropdownBounds.bottom);
|
|
31
|
+
// Clamp to reasonable bounds to avoid excessive screenshot sizes
|
|
32
|
+
const x = Math.max(0, minX - 10);
|
|
33
|
+
const y = Math.max(0, minY - 10);
|
|
34
|
+
const width = Math.min(1000, maxX - minX + 20);
|
|
35
|
+
const height = Math.min(800, maxY - minY + 20);
|
|
36
|
+
return { x, y, width, height };
|
|
37
|
+
};
|
|
38
|
+
const getDropdown = async (attrs = {}, toggleSlot = '<button slot="toggle">Toggle</button>', dropdownSlot = '<div slot="dropdown">Dropdown content</div>') => {
|
|
39
|
+
const dropdown = (await getComponent(TAG, attrs, `${toggleSlot}${dropdownSlot}`, 400, 300));
|
|
40
|
+
await dropdown.updateComplete;
|
|
41
|
+
return dropdown;
|
|
42
|
+
};
|
|
43
|
+
describe(TAG, () => {
|
|
44
|
+
it('can be created', async () => {
|
|
45
|
+
const dropdown = await getDropdown();
|
|
46
|
+
assert.instanceOf(dropdown, Dropdown);
|
|
47
|
+
});
|
|
48
|
+
it('has correct default properties', async () => {
|
|
49
|
+
const dropdown = await getDropdown();
|
|
50
|
+
// Test expected values first
|
|
51
|
+
expect(dropdown.open).to.equal(false);
|
|
52
|
+
expect(dropdown.dormant).to.equal(true);
|
|
53
|
+
expect(dropdown.arrowSize).to.equal(8);
|
|
54
|
+
expect(dropdown.margin).to.equal(10);
|
|
55
|
+
expect(dropdown.mask).to.equal(false);
|
|
56
|
+
// Position calculation happens automatically, so styles won't be empty
|
|
57
|
+
expect(typeof dropdown.dropdownStyle).to.equal('object');
|
|
58
|
+
expect(typeof dropdown.arrowStyle).to.equal('object');
|
|
59
|
+
// Then screenshot
|
|
60
|
+
await assertScreenshot('dropdown/default', getClip(dropdown));
|
|
61
|
+
});
|
|
62
|
+
it('renders with mask when enabled', async () => {
|
|
63
|
+
const dropdown = await getDropdown({ mask: true });
|
|
64
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
65
|
+
// Open the dropdown properly by clicking
|
|
66
|
+
toggle.click();
|
|
67
|
+
await waitForStableRender(dropdown);
|
|
68
|
+
// Test expected values first
|
|
69
|
+
expect(dropdown.mask).to.equal(true);
|
|
70
|
+
expect(dropdown.open).to.equal(true);
|
|
71
|
+
expect(dropdown.dormant).to.equal(false);
|
|
72
|
+
// Then screenshot
|
|
73
|
+
await assertScreenshot('dropdown/with-mask', getDropdownClip(dropdown));
|
|
74
|
+
});
|
|
75
|
+
it('handles toggle click and opens dropdown', async () => {
|
|
76
|
+
const dropdown = await getDropdown();
|
|
77
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
78
|
+
// Verify initial state
|
|
79
|
+
expect(dropdown.open).to.equal(false);
|
|
80
|
+
expect(dropdown.dormant).to.equal(true);
|
|
81
|
+
// Click the toggle
|
|
82
|
+
toggle.click();
|
|
83
|
+
await waitForStableRender(dropdown);
|
|
84
|
+
// Verify dropdown opened
|
|
85
|
+
expect(dropdown.open).to.equal(true);
|
|
86
|
+
expect(dropdown.dormant).to.equal(false);
|
|
87
|
+
// Screenshot the opened state with expanded clip
|
|
88
|
+
await assertScreenshot('dropdown/opened', getDropdownClip(dropdown));
|
|
89
|
+
});
|
|
90
|
+
it('handles custom arrow size', async () => {
|
|
91
|
+
const dropdown = await getDropdown({ arrowSize: 12 });
|
|
92
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
93
|
+
// Open the dropdown properly by clicking
|
|
94
|
+
toggle.click();
|
|
95
|
+
await waitForStableRender(dropdown);
|
|
96
|
+
// Test expected values first
|
|
97
|
+
expect(dropdown.arrowSize).to.equal(12);
|
|
98
|
+
expect(dropdown.open).to.equal(true);
|
|
99
|
+
expect(dropdown.dormant).to.equal(false);
|
|
100
|
+
// Then screenshot
|
|
101
|
+
await assertScreenshot('dropdown/custom-arrow-size', getDropdownClip(dropdown));
|
|
102
|
+
});
|
|
103
|
+
it('calculates position correctly', async () => {
|
|
104
|
+
const dropdown = await getDropdown();
|
|
105
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
106
|
+
// Open the dropdown properly by clicking
|
|
107
|
+
toggle.click();
|
|
108
|
+
await dropdown.updateComplete;
|
|
109
|
+
// Trigger position calculation
|
|
110
|
+
dropdown.calculatePosition();
|
|
111
|
+
await waitForStableRender(dropdown);
|
|
112
|
+
// Verify position styles were calculated
|
|
113
|
+
expect(Object.keys(dropdown.dropdownStyle).length).to.be.greaterThan(0);
|
|
114
|
+
expect(Object.keys(dropdown.arrowStyle).length).to.be.greaterThan(0);
|
|
115
|
+
expect(dropdown.open).to.equal(true);
|
|
116
|
+
expect(dropdown.dormant).to.equal(false);
|
|
117
|
+
// Screenshot positioned dropdown
|
|
118
|
+
await assertScreenshot('dropdown/positioned', getDropdownClip(dropdown));
|
|
119
|
+
});
|
|
120
|
+
it('handles blur events to close dropdown', async () => {
|
|
121
|
+
const dropdown = await getDropdown();
|
|
122
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
123
|
+
// Open the dropdown first
|
|
124
|
+
toggle.click();
|
|
125
|
+
await dropdown.updateComplete;
|
|
126
|
+
expect(dropdown.open).to.equal(true);
|
|
127
|
+
// Simulate blur event
|
|
128
|
+
const dropdownDiv = dropdown.shadowRoot.querySelector('.dropdown');
|
|
129
|
+
const blurEvent = new FocusEvent('blur', {
|
|
130
|
+
bubbles: true,
|
|
131
|
+
relatedTarget: document.body
|
|
132
|
+
});
|
|
133
|
+
dropdownDiv.dispatchEvent(blurEvent);
|
|
134
|
+
// Check that dropdown is closed after a short delay
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
136
|
+
expect(dropdown.open).to.equal(false);
|
|
137
|
+
// Screenshot closed state
|
|
138
|
+
await assertScreenshot('dropdown/after-blur', getClip(dropdown));
|
|
139
|
+
});
|
|
140
|
+
it('handles blur events when focus moves within dropdown', async () => {
|
|
141
|
+
const dropdown = await getDropdown();
|
|
142
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
143
|
+
// Open the dropdown first
|
|
144
|
+
toggle.click();
|
|
145
|
+
await dropdown.updateComplete;
|
|
146
|
+
expect(dropdown.open).to.equal(true);
|
|
147
|
+
// Create an element within the dropdown
|
|
148
|
+
const dropdownContent = dropdown.querySelector('[slot="dropdown"]');
|
|
149
|
+
const internalButton = document.createElement('button');
|
|
150
|
+
internalButton.textContent = 'Internal';
|
|
151
|
+
dropdownContent.appendChild(internalButton);
|
|
152
|
+
// Simulate blur event where focus moves to internal element
|
|
153
|
+
const dropdownDiv = dropdown.shadowRoot.querySelector('.dropdown');
|
|
154
|
+
const blurEvent = new FocusEvent('blur', {
|
|
155
|
+
bubbles: true,
|
|
156
|
+
relatedTarget: internalButton
|
|
157
|
+
});
|
|
158
|
+
dropdownDiv.dispatchEvent(blurEvent);
|
|
159
|
+
await dropdown.updateComplete;
|
|
160
|
+
// Dropdown should remain open since focus moved within it
|
|
161
|
+
expect(dropdown.open).to.equal(true);
|
|
162
|
+
});
|
|
163
|
+
it('prevents opening when already open', async () => {
|
|
164
|
+
const dropdown = await getDropdown();
|
|
165
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
166
|
+
// First, open the dropdown normally
|
|
167
|
+
toggle.click();
|
|
168
|
+
await dropdown.updateComplete;
|
|
169
|
+
expect(dropdown.open).to.equal(true);
|
|
170
|
+
expect(dropdown.dormant).to.equal(false);
|
|
171
|
+
// Now try to click toggle again - should not call openDropdown again
|
|
172
|
+
// since !dropdown.open is false
|
|
173
|
+
const originalOpen = dropdown.open;
|
|
174
|
+
toggle.click();
|
|
175
|
+
await dropdown.updateComplete;
|
|
176
|
+
// Should remain in the same state
|
|
177
|
+
expect(dropdown.open).to.equal(originalOpen);
|
|
178
|
+
});
|
|
179
|
+
it('handles position calculation with right edge collision', async () => {
|
|
180
|
+
// Create dropdown positioned near right edge
|
|
181
|
+
const dropdown = await getDropdown({}, '<button slot="toggle" style="position: fixed; right: 50px; top: 100px; width: 100px; height: 30px;">Toggle</button>', '<div slot="dropdown" style="width: 200px; height: 100px;">Wide content</div>');
|
|
182
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
183
|
+
// Open the dropdown properly by clicking
|
|
184
|
+
toggle.click();
|
|
185
|
+
await waitForStableRender(dropdown);
|
|
186
|
+
// Get actual element bounds to simulate collision
|
|
187
|
+
const dropdownDiv = dropdown.shadowRoot.querySelector('.dropdown');
|
|
188
|
+
const originalGetBoundingClientRect = dropdownDiv.getBoundingClientRect;
|
|
189
|
+
// Mock getBoundingClientRect to simulate right collision
|
|
190
|
+
dropdownDiv.getBoundingClientRect = function () {
|
|
191
|
+
return {
|
|
192
|
+
bottom: 200,
|
|
193
|
+
right: window.innerWidth + 100, // Extends beyond window
|
|
194
|
+
top: 100,
|
|
195
|
+
left: window.innerWidth - 50,
|
|
196
|
+
width: 200,
|
|
197
|
+
height: 100,
|
|
198
|
+
x: window.innerWidth - 50,
|
|
199
|
+
y: 100
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
try {
|
|
203
|
+
// Trigger position calculation
|
|
204
|
+
dropdown.calculatePosition();
|
|
205
|
+
await waitForStableRender(dropdown);
|
|
206
|
+
// Verify position was adjusted for right edge
|
|
207
|
+
expect(dropdown.dropdownStyle).to.have.property('left');
|
|
208
|
+
// Screenshot positioned dropdown
|
|
209
|
+
await assertScreenshot('dropdown/right-edge-collision', getDropdownClip(dropdown));
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
// Restore original method
|
|
213
|
+
dropdownDiv.getBoundingClientRect = originalGetBoundingClientRect;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
it('handles position calculation with bottom edge collision', async () => {
|
|
217
|
+
// Create dropdown positioned near bottom edge
|
|
218
|
+
const dropdown = await getDropdown({}, '<button slot="toggle" style="position: fixed; left: 100px; bottom: 50px; width: 100px; height: 30px;">Toggle</button>', '<div slot="dropdown" style="width: 200px; height: 100px; position: absolute;">Tall content</div>');
|
|
219
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
220
|
+
// Open the dropdown properly by clicking
|
|
221
|
+
toggle.click();
|
|
222
|
+
await waitForStableRender(dropdown);
|
|
223
|
+
// Get actual element bounds to simulate collision
|
|
224
|
+
const dropdownDiv = dropdown.shadowRoot.querySelector('.dropdown');
|
|
225
|
+
const originalGetBoundingClientRect = dropdownDiv.getBoundingClientRect;
|
|
226
|
+
// Mock getBoundingClientRect to simulate bottom collision
|
|
227
|
+
dropdownDiv.getBoundingClientRect = function () {
|
|
228
|
+
return {
|
|
229
|
+
bottom: window.innerHeight + 100, // Extends beyond window
|
|
230
|
+
right: 300,
|
|
231
|
+
top: window.innerHeight - 50,
|
|
232
|
+
left: 100,
|
|
233
|
+
width: 200,
|
|
234
|
+
height: 100,
|
|
235
|
+
x: 100,
|
|
236
|
+
y: window.innerHeight - 50
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
try {
|
|
240
|
+
// Trigger position calculation
|
|
241
|
+
dropdown.calculatePosition();
|
|
242
|
+
await waitForStableRender(dropdown);
|
|
243
|
+
// Verify position was adjusted for bottom edge (bumped up)
|
|
244
|
+
expect(dropdown.dropdownStyle).to.have.property('top');
|
|
245
|
+
expect(dropdown.arrowStyle).to.have.property('transform');
|
|
246
|
+
expect(dropdown.arrowStyle['transform']).to.include('rotate(180deg)');
|
|
247
|
+
// Screenshot positioned dropdown
|
|
248
|
+
await assertScreenshot('dropdown/bottom-edge-collision', getDropdownClip(dropdown));
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
// Restore original method
|
|
252
|
+
dropdownDiv.getBoundingClientRect = originalGetBoundingClientRect;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
it('handles arrow positioning when toggle is very narrow', async () => {
|
|
256
|
+
const dropdown = await getDropdown({}, '<button slot="toggle" style="width: 5px;">•</button>', '<div slot="dropdown">Content</div>');
|
|
257
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
258
|
+
// Open the dropdown properly by clicking
|
|
259
|
+
toggle.click();
|
|
260
|
+
await dropdown.updateComplete;
|
|
261
|
+
// Trigger position calculation
|
|
262
|
+
dropdown.calculatePosition();
|
|
263
|
+
await waitForStableRender(dropdown);
|
|
264
|
+
// Verify arrow positioning was adjusted for narrow toggle
|
|
265
|
+
expect(dropdown.dropdownStyle).to.have.property('marginLeft');
|
|
266
|
+
expect(dropdown.dropdownStyle['marginLeft']).to.equal('-10px');
|
|
267
|
+
expect(dropdown.open).to.equal(true);
|
|
268
|
+
expect(dropdown.dormant).to.equal(false);
|
|
269
|
+
// Screenshot with adjusted arrow
|
|
270
|
+
await assertScreenshot('dropdown/narrow-toggle', getDropdownClip(dropdown));
|
|
271
|
+
});
|
|
272
|
+
it('handles position calculation when toggle element is missing', async () => {
|
|
273
|
+
const dropdown = await getDropdown({ open: true, dormant: false }, '', // No toggle element
|
|
274
|
+
'<div slot="dropdown">Content</div>');
|
|
275
|
+
// Trigger position calculation - should handle gracefully
|
|
276
|
+
dropdown.calculatePosition();
|
|
277
|
+
await dropdown.updateComplete;
|
|
278
|
+
// Should not crash and should have basic styles
|
|
279
|
+
expect(typeof dropdown.dropdownStyle).to.equal('object');
|
|
280
|
+
expect(typeof dropdown.arrowStyle).to.equal('object');
|
|
281
|
+
});
|
|
282
|
+
it('handles resetBlurHandler when activeFocus exists', async () => {
|
|
283
|
+
const dropdown = await getDropdown();
|
|
284
|
+
const toggle = dropdown.querySelector('[slot="toggle"]');
|
|
285
|
+
// Open dropdown to trigger resetBlurHandler for the first time
|
|
286
|
+
toggle.click();
|
|
287
|
+
await dropdown.updateComplete;
|
|
288
|
+
expect(dropdown.open).to.equal(true);
|
|
289
|
+
// Now open it again - this should trigger the activeFocus cleanup branch
|
|
290
|
+
// First we need to close it
|
|
291
|
+
const dropdownDiv = dropdown.shadowRoot.querySelector('.dropdown');
|
|
292
|
+
const blurEvent = new FocusEvent('blur', {
|
|
293
|
+
bubbles: true,
|
|
294
|
+
relatedTarget: document.body
|
|
295
|
+
});
|
|
296
|
+
dropdownDiv.dispatchEvent(blurEvent);
|
|
297
|
+
// Wait for close
|
|
298
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
299
|
+
expect(dropdown.open).to.equal(false);
|
|
300
|
+
expect(dropdown.dormant).to.equal(true);
|
|
301
|
+
// Open again - this should trigger the cleanup in resetBlurHandler
|
|
302
|
+
toggle.click();
|
|
303
|
+
await dropdown.updateComplete;
|
|
304
|
+
expect(dropdown.open).to.equal(true);
|
|
305
|
+
});
|
|
306
|
+
it('renders without mask by default', async () => {
|
|
307
|
+
const dropdown = await getDropdown(); // No mask explicitly set
|
|
308
|
+
// Test expected values first - mask should be false by default
|
|
309
|
+
expect(dropdown.mask).to.equal(false);
|
|
310
|
+
// Look for mask element in shadow DOM - should not exist when mask is false
|
|
311
|
+
const maskElement = dropdown.shadowRoot.querySelector('.mask');
|
|
312
|
+
expect(maskElement).to.be.null;
|
|
313
|
+
// Screenshot default rendering
|
|
314
|
+
await assertScreenshot('dropdown/no-mask', getClip(dropdown));
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
//# sourceMappingURL=temba-dropdown.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temba-dropdown.test.js","sourceRoot":"","sources":["../../test/temba-dropdown.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert, expect } from '@open-wc/testing';\nimport { Dropdown } from '../src/dropdown/Dropdown';\nimport { assertScreenshot, getClip, getComponent } from './utils.test';\n\nconst TAG = 'temba-dropdown';\n\n// Helper function to wait for stable rendering\nconst waitForStableRender = async (dropdown: Dropdown, timeoutMs = 200) => {\n await dropdown.updateComplete;\n // Double wait to ensure any async positioning is complete\n await new Promise((resolve) => setTimeout(resolve, timeoutMs));\n await dropdown.updateComplete;\n};\n\n// Helper function to get expanded clip that includes dropdown content when open\nconst getDropdownClip = (dropdown: Dropdown) => {\n if (!dropdown.open) {\n // If closed, use regular clipping\n return getClip(dropdown);\n }\n\n // For open dropdowns, include the positioned dropdown content\n const dropdownDiv = dropdown.shadowRoot.querySelector(\n '.dropdown'\n ) as HTMLDivElement;\n const dropdownBounds = dropdownDiv.getBoundingClientRect();\n const componentBounds = dropdown.getBoundingClientRect();\n\n // If dropdown content has no meaningful size, fall back to regular clip\n if (dropdownBounds.width < 10 || dropdownBounds.height < 10) {\n return getClip(dropdown);\n }\n\n // Create a clipping region that includes both the component and the dropdown content\n const minX = Math.min(componentBounds.x, dropdownBounds.x);\n const minY = Math.min(componentBounds.y, dropdownBounds.y);\n const maxX = Math.max(componentBounds.right, dropdownBounds.right);\n const maxY = Math.max(componentBounds.bottom, dropdownBounds.bottom);\n\n // Clamp to reasonable bounds to avoid excessive screenshot sizes\n const x = Math.max(0, minX - 10);\n const y = Math.max(0, minY - 10);\n const width = Math.min(1000, maxX - minX + 20);\n const height = Math.min(800, maxY - minY + 20);\n\n return { x, y, width, height };\n};\n\nconst getDropdown = async (\n attrs: {\n open?: boolean;\n dormant?: boolean;\n arrowSize?: number;\n margin?: number;\n mask?: boolean;\n } = {},\n toggleSlot = '<button slot=\"toggle\">Toggle</button>',\n dropdownSlot = '<div slot=\"dropdown\">Dropdown content</div>'\n) => {\n const dropdown = (await getComponent(\n TAG,\n attrs,\n `${toggleSlot}${dropdownSlot}`,\n 400,\n 300\n )) as Dropdown;\n await dropdown.updateComplete;\n return dropdown;\n};\n\ndescribe(TAG, () => {\n it('can be created', async () => {\n const dropdown = await getDropdown();\n assert.instanceOf(dropdown, Dropdown);\n });\n\n it('has correct default properties', async () => {\n const dropdown = await getDropdown();\n\n // Test expected values first\n expect(dropdown.open).to.equal(false);\n expect(dropdown.dormant).to.equal(true);\n expect(dropdown.arrowSize).to.equal(8);\n expect(dropdown.margin).to.equal(10);\n expect(dropdown.mask).to.equal(false);\n // Position calculation happens automatically, so styles won't be empty\n expect(typeof dropdown.dropdownStyle).to.equal('object');\n expect(typeof dropdown.arrowStyle).to.equal('object');\n\n // Then screenshot\n await assertScreenshot('dropdown/default', getClip(dropdown));\n });\n\n it('renders with mask when enabled', async () => {\n const dropdown = await getDropdown({ mask: true });\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown properly by clicking\n toggle.click();\n await waitForStableRender(dropdown);\n\n // Test expected values first\n expect(dropdown.mask).to.equal(true);\n expect(dropdown.open).to.equal(true);\n expect(dropdown.dormant).to.equal(false);\n\n // Then screenshot\n await assertScreenshot('dropdown/with-mask', getDropdownClip(dropdown));\n });\n\n it('handles toggle click and opens dropdown', async () => {\n const dropdown = await getDropdown();\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Verify initial state\n expect(dropdown.open).to.equal(false);\n expect(dropdown.dormant).to.equal(true);\n\n // Click the toggle\n toggle.click();\n await waitForStableRender(dropdown);\n\n // Verify dropdown opened\n expect(dropdown.open).to.equal(true);\n expect(dropdown.dormant).to.equal(false);\n\n // Screenshot the opened state with expanded clip\n await assertScreenshot('dropdown/opened', getDropdownClip(dropdown));\n });\n\n it('handles custom arrow size', async () => {\n const dropdown = await getDropdown({ arrowSize: 12 });\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown properly by clicking\n toggle.click();\n await waitForStableRender(dropdown);\n\n // Test expected values first\n expect(dropdown.arrowSize).to.equal(12);\n expect(dropdown.open).to.equal(true);\n expect(dropdown.dormant).to.equal(false);\n\n // Then screenshot\n await assertScreenshot(\n 'dropdown/custom-arrow-size',\n getDropdownClip(dropdown)\n );\n });\n\n it('calculates position correctly', async () => {\n const dropdown = await getDropdown();\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown properly by clicking\n toggle.click();\n await dropdown.updateComplete;\n\n // Trigger position calculation\n dropdown.calculatePosition();\n await waitForStableRender(dropdown);\n\n // Verify position styles were calculated\n expect(Object.keys(dropdown.dropdownStyle).length).to.be.greaterThan(0);\n expect(Object.keys(dropdown.arrowStyle).length).to.be.greaterThan(0);\n expect(dropdown.open).to.equal(true);\n expect(dropdown.dormant).to.equal(false);\n\n // Screenshot positioned dropdown\n await assertScreenshot('dropdown/positioned', getDropdownClip(dropdown));\n });\n\n it('handles blur events to close dropdown', async () => {\n const dropdown = await getDropdown();\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown first\n toggle.click();\n await dropdown.updateComplete;\n expect(dropdown.open).to.equal(true);\n\n // Simulate blur event\n const dropdownDiv = dropdown.shadowRoot.querySelector(\n '.dropdown'\n ) as HTMLDivElement;\n const blurEvent = new FocusEvent('blur', {\n bubbles: true,\n relatedTarget: document.body\n });\n dropdownDiv.dispatchEvent(blurEvent);\n\n // Check that dropdown is closed after a short delay\n await new Promise((resolve) => setTimeout(resolve, 300));\n expect(dropdown.open).to.equal(false);\n\n // Screenshot closed state\n await assertScreenshot('dropdown/after-blur', getClip(dropdown));\n });\n\n it('handles blur events when focus moves within dropdown', async () => {\n const dropdown = await getDropdown();\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown first\n toggle.click();\n await dropdown.updateComplete;\n expect(dropdown.open).to.equal(true);\n\n // Create an element within the dropdown\n const dropdownContent = dropdown.querySelector(\n '[slot=\"dropdown\"]'\n ) as HTMLElement;\n const internalButton = document.createElement('button');\n internalButton.textContent = 'Internal';\n dropdownContent.appendChild(internalButton);\n\n // Simulate blur event where focus moves to internal element\n const dropdownDiv = dropdown.shadowRoot.querySelector(\n '.dropdown'\n ) as HTMLDivElement;\n const blurEvent = new FocusEvent('blur', {\n bubbles: true,\n relatedTarget: internalButton\n });\n dropdownDiv.dispatchEvent(blurEvent);\n await dropdown.updateComplete;\n\n // Dropdown should remain open since focus moved within it\n expect(dropdown.open).to.equal(true);\n });\n\n it('prevents opening when already open', async () => {\n const dropdown = await getDropdown();\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // First, open the dropdown normally\n toggle.click();\n await dropdown.updateComplete;\n expect(dropdown.open).to.equal(true);\n expect(dropdown.dormant).to.equal(false);\n\n // Now try to click toggle again - should not call openDropdown again\n // since !dropdown.open is false\n const originalOpen = dropdown.open;\n toggle.click();\n await dropdown.updateComplete;\n\n // Should remain in the same state\n expect(dropdown.open).to.equal(originalOpen);\n });\n\n it('handles position calculation with right edge collision', async () => {\n // Create dropdown positioned near right edge\n const dropdown = await getDropdown(\n {},\n '<button slot=\"toggle\" style=\"position: fixed; right: 50px; top: 100px; width: 100px; height: 30px;\">Toggle</button>',\n '<div slot=\"dropdown\" style=\"width: 200px; height: 100px;\">Wide content</div>'\n );\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown properly by clicking\n toggle.click();\n await waitForStableRender(dropdown);\n\n // Get actual element bounds to simulate collision\n const dropdownDiv = dropdown.shadowRoot.querySelector(\n '.dropdown'\n ) as HTMLDivElement;\n const originalGetBoundingClientRect = dropdownDiv.getBoundingClientRect;\n\n // Mock getBoundingClientRect to simulate right collision\n dropdownDiv.getBoundingClientRect = function () {\n return {\n bottom: 200,\n right: window.innerWidth + 100, // Extends beyond window\n top: 100,\n left: window.innerWidth - 50,\n width: 200,\n height: 100,\n x: window.innerWidth - 50,\n y: 100\n } as DOMRect;\n };\n\n try {\n // Trigger position calculation\n dropdown.calculatePosition();\n await waitForStableRender(dropdown);\n\n // Verify position was adjusted for right edge\n expect(dropdown.dropdownStyle).to.have.property('left');\n\n // Screenshot positioned dropdown\n await assertScreenshot(\n 'dropdown/right-edge-collision',\n getDropdownClip(dropdown)\n );\n } finally {\n // Restore original method\n dropdownDiv.getBoundingClientRect = originalGetBoundingClientRect;\n }\n });\n\n it('handles position calculation with bottom edge collision', async () => {\n // Create dropdown positioned near bottom edge\n const dropdown = await getDropdown(\n {},\n '<button slot=\"toggle\" style=\"position: fixed; left: 100px; bottom: 50px; width: 100px; height: 30px;\">Toggle</button>',\n '<div slot=\"dropdown\" style=\"width: 200px; height: 100px; position: absolute;\">Tall content</div>'\n );\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown properly by clicking\n toggle.click();\n await waitForStableRender(dropdown);\n\n // Get actual element bounds to simulate collision\n const dropdownDiv = dropdown.shadowRoot.querySelector(\n '.dropdown'\n ) as HTMLDivElement;\n const originalGetBoundingClientRect = dropdownDiv.getBoundingClientRect;\n\n // Mock getBoundingClientRect to simulate bottom collision\n dropdownDiv.getBoundingClientRect = function () {\n return {\n bottom: window.innerHeight + 100, // Extends beyond window\n right: 300,\n top: window.innerHeight - 50,\n left: 100,\n width: 200,\n height: 100,\n x: 100,\n y: window.innerHeight - 50\n } as DOMRect;\n };\n\n try {\n // Trigger position calculation\n dropdown.calculatePosition();\n await waitForStableRender(dropdown);\n\n // Verify position was adjusted for bottom edge (bumped up)\n expect(dropdown.dropdownStyle).to.have.property('top');\n expect(dropdown.arrowStyle).to.have.property('transform');\n expect(dropdown.arrowStyle['transform']).to.include('rotate(180deg)');\n\n // Screenshot positioned dropdown\n await assertScreenshot(\n 'dropdown/bottom-edge-collision',\n getDropdownClip(dropdown)\n );\n } finally {\n // Restore original method\n dropdownDiv.getBoundingClientRect = originalGetBoundingClientRect;\n }\n });\n\n it('handles arrow positioning when toggle is very narrow', async () => {\n const dropdown = await getDropdown(\n {},\n '<button slot=\"toggle\" style=\"width: 5px;\">•</button>',\n '<div slot=\"dropdown\">Content</div>'\n );\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open the dropdown properly by clicking\n toggle.click();\n await dropdown.updateComplete;\n\n // Trigger position calculation\n dropdown.calculatePosition();\n await waitForStableRender(dropdown);\n\n // Verify arrow positioning was adjusted for narrow toggle\n expect(dropdown.dropdownStyle).to.have.property('marginLeft');\n expect(dropdown.dropdownStyle['marginLeft']).to.equal('-10px');\n expect(dropdown.open).to.equal(true);\n expect(dropdown.dormant).to.equal(false);\n\n // Screenshot with adjusted arrow\n await assertScreenshot('dropdown/narrow-toggle', getDropdownClip(dropdown));\n });\n\n it('handles position calculation when toggle element is missing', async () => {\n const dropdown = await getDropdown(\n { open: true, dormant: false },\n '', // No toggle element\n '<div slot=\"dropdown\">Content</div>'\n );\n\n // Trigger position calculation - should handle gracefully\n dropdown.calculatePosition();\n await dropdown.updateComplete;\n\n // Should not crash and should have basic styles\n expect(typeof dropdown.dropdownStyle).to.equal('object');\n expect(typeof dropdown.arrowStyle).to.equal('object');\n });\n\n it('handles resetBlurHandler when activeFocus exists', async () => {\n const dropdown = await getDropdown();\n const toggle = dropdown.querySelector('[slot=\"toggle\"]') as HTMLElement;\n\n // Open dropdown to trigger resetBlurHandler for the first time\n toggle.click();\n await dropdown.updateComplete;\n expect(dropdown.open).to.equal(true);\n\n // Now open it again - this should trigger the activeFocus cleanup branch\n // First we need to close it\n const dropdownDiv = dropdown.shadowRoot.querySelector(\n '.dropdown'\n ) as HTMLDivElement;\n const blurEvent = new FocusEvent('blur', {\n bubbles: true,\n relatedTarget: document.body\n });\n dropdownDiv.dispatchEvent(blurEvent);\n\n // Wait for close\n await new Promise((resolve) => setTimeout(resolve, 300));\n expect(dropdown.open).to.equal(false);\n expect(dropdown.dormant).to.equal(true);\n\n // Open again - this should trigger the cleanup in resetBlurHandler\n toggle.click();\n await dropdown.updateComplete;\n expect(dropdown.open).to.equal(true);\n });\n\n it('renders without mask by default', async () => {\n const dropdown = await getDropdown(); // No mask explicitly set\n\n // Test expected values first - mask should be false by default\n expect(dropdown.mask).to.equal(false);\n\n // Look for mask element in shadow DOM - should not exist when mask is false\n const maskElement = dropdown.shadowRoot.querySelector('.mask');\n expect(maskElement).to.be.null;\n\n // Screenshot default rendering\n await assertScreenshot('dropdown/no-mask', getClip(dropdown));\n });\n});\n"]}
|