@genspectrum/dashboard-components 0.11.7 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +16 -16
- package/dist/{dateRangeOption-Bh2p78z0.js → LocationChangedEvent-CORvQvXv.js} +11 -1
- package/dist/LocationChangedEvent-CORvQvXv.js.map +1 -0
- package/dist/components.d.ts +27 -33
- package/dist/components.js +4034 -655
- package/dist/components.js.map +1 -1
- package/dist/style.css +151 -4
- package/dist/util.d.ts +32 -26
- package/dist/util.js +2 -1
- package/package.json +2 -1
- package/src/preact/components/csv-download-button.tsx +2 -2
- package/src/preact/downshift_types.d.ts +3 -0
- package/src/preact/locationFilter/LocationChangedEvent.ts +11 -0
- package/src/preact/locationFilter/fetchAutocompletionList.spec.ts +5 -5
- package/src/preact/locationFilter/fetchAutocompletionList.ts +9 -2
- package/src/preact/locationFilter/location-filter.stories.tsx +94 -10
- package/src/preact/locationFilter/location-filter.tsx +183 -62
- package/src/preact/map/sequences-by-location-map.tsx +3 -3
- package/src/preact/mutationFilter/mutation-filter-info.tsx +73 -10
- package/src/preact/textInput/TextInputChangedEvent.ts +11 -0
- package/src/preact/textInput/fetchAutocompleteList.ts +1 -1
- package/src/preact/textInput/text-input.stories.tsx +20 -3
- package/src/preact/textInput/text-input.tsx +139 -36
- package/src/utilEntrypoint.ts +2 -0
- package/src/web-components/input/gs-location-filter.stories.ts +34 -29
- package/src/web-components/input/gs-location-filter.tsx +6 -13
- package/src/web-components/input/gs-text-input.stories.ts +30 -7
- package/standalone-bundle/dashboard-components.js +11073 -8625
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/dateRangeOption-Bh2p78z0.js.map +0 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { useCombobox } from 'downshift/preact';
|
|
1
2
|
import { type FunctionComponent } from 'preact';
|
|
2
|
-
import { useContext, useRef } from 'preact/hooks';
|
|
3
|
+
import { useContext, useRef, useState } from 'preact/hooks';
|
|
3
4
|
import z from 'zod';
|
|
4
5
|
|
|
5
6
|
import { fetchAutocompleteList } from './fetchAutocompleteList';
|
|
6
7
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
8
|
+
import { TextInputChangedEvent } from './TextInputChangedEvent';
|
|
7
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
8
10
|
import { LoadingDisplay } from '../components/loading-display';
|
|
9
11
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
@@ -36,11 +38,9 @@ export const TextInput: FunctionComponent<TextInputProps> = (props) => {
|
|
|
36
38
|
);
|
|
37
39
|
};
|
|
38
40
|
|
|
39
|
-
const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, placeholderText
|
|
41
|
+
const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ initialValue, lapisField, placeholderText }) => {
|
|
40
42
|
const lapis = useContext(LapisUrlContext);
|
|
41
43
|
|
|
42
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
43
|
-
|
|
44
44
|
const { data, error, isLoading } = useQuery(() => fetchAutocompleteList(lapis, lapisField), [lapisField, lapis]);
|
|
45
45
|
|
|
46
46
|
if (isLoading) {
|
|
@@ -55,43 +55,146 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl
|
|
|
55
55
|
return <NoDataDisplay />;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
58
|
+
return (
|
|
59
|
+
<TextSelector
|
|
60
|
+
lapisField={lapisField}
|
|
61
|
+
initialValue={initialValue}
|
|
62
|
+
placeholderText={placeholderText}
|
|
63
|
+
data={data}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const TextSelector = ({
|
|
69
|
+
lapisField,
|
|
70
|
+
initialValue,
|
|
71
|
+
placeholderText,
|
|
72
|
+
data,
|
|
73
|
+
}: TextInputInnerProps & {
|
|
74
|
+
data: string[];
|
|
75
|
+
}) => {
|
|
76
|
+
const [items, setItems] = useState(data.filter((item) => filterByInputValue(item, initialValue)));
|
|
77
|
+
|
|
78
|
+
const divRef = useRef<HTMLDivElement>(null);
|
|
79
|
+
|
|
80
|
+
const shadowRoot = divRef.current?.shadowRoot ?? undefined;
|
|
81
|
+
|
|
82
|
+
const environment =
|
|
83
|
+
shadowRoot !== undefined
|
|
84
|
+
? {
|
|
85
|
+
addEventListener: window.addEventListener.bind(window),
|
|
86
|
+
removeEventListener: window.removeEventListener.bind(window),
|
|
87
|
+
document: shadowRoot.ownerDocument,
|
|
88
|
+
Node: window.Node,
|
|
89
|
+
}
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
92
|
+
function filterByInputValue(item: string, inputValue: string | undefined | null) {
|
|
93
|
+
if (inputValue === undefined || inputValue === null || inputValue === '') {
|
|
74
94
|
return true;
|
|
75
95
|
}
|
|
76
|
-
return
|
|
96
|
+
return item?.toLowerCase().includes(inputValue?.toLowerCase() || '');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const {
|
|
100
|
+
isOpen,
|
|
101
|
+
getToggleButtonProps,
|
|
102
|
+
getMenuProps,
|
|
103
|
+
getInputProps,
|
|
104
|
+
highlightedIndex,
|
|
105
|
+
getItemProps,
|
|
106
|
+
selectedItem,
|
|
107
|
+
inputValue,
|
|
108
|
+
selectItem,
|
|
109
|
+
setInputValue,
|
|
110
|
+
closeMenu,
|
|
111
|
+
} = useCombobox({
|
|
112
|
+
onInputValueChange({ inputValue }) {
|
|
113
|
+
setItems(data.filter((item) => filterByInputValue(item, inputValue)));
|
|
114
|
+
},
|
|
115
|
+
onSelectedItemChange({ selectedItem }) {
|
|
116
|
+
if (selectedItem !== null) {
|
|
117
|
+
divRef.current?.dispatchEvent(new TextInputChangedEvent({ [lapisField]: selectedItem }));
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
items,
|
|
121
|
+
itemToString(item) {
|
|
122
|
+
return item ?? '';
|
|
123
|
+
},
|
|
124
|
+
initialSelectedItem: initialValue,
|
|
125
|
+
environment,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const onInputBlur = () => {
|
|
129
|
+
if (inputValue === '') {
|
|
130
|
+
divRef.current?.dispatchEvent(new TextInputChangedEvent({ [lapisField]: undefined }));
|
|
131
|
+
selectItem(null);
|
|
132
|
+
} else if (inputValue !== selectedItem) {
|
|
133
|
+
setInputValue(selectedItem ?? '');
|
|
134
|
+
}
|
|
77
135
|
};
|
|
78
136
|
|
|
137
|
+
const clearInput = () => {
|
|
138
|
+
divRef.current?.dispatchEvent(new TextInputChangedEvent({ [lapisField]: undefined }));
|
|
139
|
+
selectItem(null);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const buttonRef = useRef(null);
|
|
143
|
+
|
|
79
144
|
return (
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
145
|
+
<div ref={divRef} className={'relative w-full'}>
|
|
146
|
+
<div className='w-full flex flex-col gap-1'>
|
|
147
|
+
<div
|
|
148
|
+
className='flex gap-0.5 input input-bordered min-w-32'
|
|
149
|
+
onBlur={(event) => {
|
|
150
|
+
if (event.relatedTarget != buttonRef.current) {
|
|
151
|
+
closeMenu();
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<input
|
|
156
|
+
placeholder={placeholderText}
|
|
157
|
+
className='w-full p-1.5'
|
|
158
|
+
{...getInputProps()}
|
|
159
|
+
onBlur={onInputBlur}
|
|
160
|
+
/>
|
|
161
|
+
<button
|
|
162
|
+
aria-label='clear selection'
|
|
163
|
+
className={`px-2 ${inputValue === '' && 'hidden'}`}
|
|
164
|
+
type='button'
|
|
165
|
+
onClick={clearInput}
|
|
166
|
+
tabIndex={-1}
|
|
167
|
+
>
|
|
168
|
+
×
|
|
169
|
+
</button>
|
|
170
|
+
<button
|
|
171
|
+
aria-label='toggle menu'
|
|
172
|
+
className='px-2'
|
|
173
|
+
type='button'
|
|
174
|
+
{...getToggleButtonProps()}
|
|
175
|
+
ref={buttonRef}
|
|
176
|
+
>
|
|
177
|
+
{isOpen ? <>↑</> : <>↓</>}
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<ul
|
|
182
|
+
className={`absolute bg-white mt-1 shadow-md max-h-80 overflow-scroll z-10 w-full min-w-32 ${
|
|
183
|
+
!(isOpen && items.length > 0) && 'hidden'
|
|
184
|
+
}`}
|
|
185
|
+
{...getMenuProps()}
|
|
186
|
+
>
|
|
187
|
+
{isOpen &&
|
|
188
|
+
items.map((item, index) => (
|
|
189
|
+
<li
|
|
190
|
+
className={`${highlightedIndex === index && 'bg-blue-300'} ${selectedItem !== null} py-2 px-3 shadow-sm flex flex-col`}
|
|
191
|
+
key={item}
|
|
192
|
+
{...getItemProps({ item, index })}
|
|
193
|
+
>
|
|
194
|
+
<span>{item}</span>
|
|
195
|
+
</li>
|
|
196
|
+
))}
|
|
197
|
+
</ul>
|
|
198
|
+
</div>
|
|
96
199
|
);
|
|
97
200
|
};
|
package/src/utilEntrypoint.ts
CHANGED
|
@@ -32,3 +32,5 @@ export type { MapSource } from './preact/map/loadMapSource';
|
|
|
32
32
|
export type { ConfidenceIntervalMethod } from './preact/shared/charts/confideceInterval';
|
|
33
33
|
|
|
34
34
|
export type { AxisMax, YAxisMaxConfig } from './preact/shared/charts/getYAxisMax';
|
|
35
|
+
|
|
36
|
+
export { LocationChangedEvent } from './preact/locationFilter/LocationChangedEvent';
|
|
@@ -15,7 +15,7 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
|
15
15
|
const codeExample = String.raw`
|
|
16
16
|
<gs-location-filter
|
|
17
17
|
fields='["region", "country"]'
|
|
18
|
-
|
|
18
|
+
value='{ "region": "Europe", "country": null}'
|
|
19
19
|
width="100%"
|
|
20
20
|
placeholderText="Enter a location"
|
|
21
21
|
></gs-location-filter>`;
|
|
@@ -39,9 +39,9 @@ const meta: Meta = {
|
|
|
39
39
|
type: 'object',
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
|
-
|
|
42
|
+
value: {
|
|
43
43
|
control: {
|
|
44
|
-
type: '
|
|
44
|
+
type: 'object',
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
width: {
|
|
@@ -66,7 +66,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
66
66
|
<div class="max-w-screen-lg">
|
|
67
67
|
<gs-location-filter
|
|
68
68
|
.fields=${args.fields}
|
|
69
|
-
|
|
69
|
+
.value=${args.value}
|
|
70
70
|
.width=${args.width}
|
|
71
71
|
placeholderText=${ifDefined(args.placeholderText)}
|
|
72
72
|
></gs-location-filter>
|
|
@@ -75,7 +75,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
75
75
|
},
|
|
76
76
|
args: {
|
|
77
77
|
fields: ['region', 'country', 'division', 'location'],
|
|
78
|
-
|
|
78
|
+
value: undefined,
|
|
79
79
|
width: '100%',
|
|
80
80
|
placeholderText: 'Enter a location',
|
|
81
81
|
},
|
|
@@ -107,10 +107,7 @@ export const LocationFilter: StoryObj<LocationFilterProps> = {
|
|
|
107
107
|
play: async ({ canvasElement }) => {
|
|
108
108
|
const canvas = await withinShadowRoot(canvasElement, 'gs-location-filter');
|
|
109
109
|
await waitFor(() => {
|
|
110
|
-
return expect(canvas.
|
|
111
|
-
});
|
|
112
|
-
await waitFor(() => {
|
|
113
|
-
return expect(canvas.getByPlaceholderText('Enter a location')).toBeInTheDocument();
|
|
110
|
+
return expect(canvas.getByPlaceholderText('Enter a location')).toBeVisible();
|
|
114
111
|
});
|
|
115
112
|
},
|
|
116
113
|
};
|
|
@@ -199,36 +196,44 @@ export const FiresEvent: StoryObj<LocationFilterProps> = {
|
|
|
199
196
|
|
|
200
197
|
await step('Empty input', async () => {
|
|
201
198
|
await userEvent.type(inputField(), '{backspace>18/}');
|
|
202
|
-
await
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
199
|
+
await userEvent.click(canvas.getByLabelText('toggle menu'));
|
|
200
|
+
|
|
201
|
+
await waitFor(() => {
|
|
202
|
+
return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
203
|
+
region: undefined,
|
|
204
|
+
country: undefined,
|
|
205
|
+
division: undefined,
|
|
206
|
+
location: undefined,
|
|
207
|
+
});
|
|
207
208
|
});
|
|
208
209
|
});
|
|
209
210
|
|
|
210
211
|
await step('Select Asia', async () => {
|
|
211
212
|
await userEvent.type(inputField(), 'Asia');
|
|
212
|
-
await
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
await userEvent.click(canvas.getByRole('option', { name: 'Asia Asia' }));
|
|
214
|
+
|
|
215
|
+
await waitFor(() => {
|
|
216
|
+
return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
217
|
+
region: 'Asia',
|
|
218
|
+
country: undefined,
|
|
219
|
+
division: undefined,
|
|
220
|
+
location: undefined,
|
|
221
|
+
});
|
|
217
222
|
});
|
|
218
223
|
});
|
|
219
224
|
|
|
220
225
|
await step('Select Asia / Bangladesh / Rajshahi / Chapainawabgonj', async () => {
|
|
221
226
|
await userEvent.type(inputField(), ' / Bangladesh / Rajshahi / Chapainawabgonj');
|
|
222
|
-
await
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
})
|
|
231
|
-
);
|
|
227
|
+
await userEvent.click(canvas.getByText('Asia / Bangladesh / Rajshahi / Chapainawabgonj'));
|
|
228
|
+
|
|
229
|
+
await waitFor(() => {
|
|
230
|
+
return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
231
|
+
region: 'Asia',
|
|
232
|
+
country: 'Bangladesh',
|
|
233
|
+
division: 'Rajshahi',
|
|
234
|
+
location: 'Chapainawabgonj',
|
|
235
|
+
});
|
|
236
|
+
});
|
|
232
237
|
});
|
|
233
238
|
},
|
|
234
239
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { customElement, property } from 'lit/decorators.js';
|
|
2
2
|
import type { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
3
3
|
|
|
4
|
+
import { type LocationChangedEvent } from '../../preact/locationFilter/LocationChangedEvent';
|
|
4
5
|
import { LocationFilter, type LocationFilterProps } from '../../preact/locationFilter/location-filter';
|
|
5
6
|
import type { Equals, Expect } from '../../utils/typeAssertions';
|
|
6
7
|
import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
@@ -14,11 +15,6 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
14
15
|
* The component retrieves a list of all possible values for these fields from the Lapis instance.
|
|
15
16
|
* This list is then utilized to display autocomplete suggestions and to validate the input.
|
|
16
17
|
*
|
|
17
|
-
* Given `fields` are `['field1', 'field2', ..., 'fieldN']`,
|
|
18
|
-
* then valid values for the location filter must be in the form `valueForField1 / valueForField2 / ... / valueForFieldK`,
|
|
19
|
-
* where `1 <= K <= N`.
|
|
20
|
-
* Values for the fields `i > K` are considered `undefined`.
|
|
21
|
-
*
|
|
22
18
|
* @fires {CustomEvent<Record<string, string>>} gs-location-changed
|
|
23
19
|
* Fired when a value from the datalist is selected or when a valid value is typed into the field.
|
|
24
20
|
* The `details` of this event contain an object with all `fields` as keys
|
|
@@ -37,10 +33,9 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
37
33
|
export class LocationFilterComponent extends PreactLitAdapter {
|
|
38
34
|
/**
|
|
39
35
|
* The initial value to use for this location filter.
|
|
40
|
-
* Must be of the form `valueForField1 / valueForField2 / ... / valueForFieldN`.
|
|
41
36
|
*/
|
|
42
|
-
@property()
|
|
43
|
-
|
|
37
|
+
@property({ type: Object })
|
|
38
|
+
value: Record<string, string | null | undefined> | undefined = undefined;
|
|
44
39
|
|
|
45
40
|
/**
|
|
46
41
|
* Required.
|
|
@@ -70,7 +65,7 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
70
65
|
override render() {
|
|
71
66
|
return (
|
|
72
67
|
<LocationFilter
|
|
73
|
-
|
|
68
|
+
value={this.value}
|
|
74
69
|
fields={this.fields}
|
|
75
70
|
width={this.width}
|
|
76
71
|
placeholderText={this.placeholderText}
|
|
@@ -85,7 +80,7 @@ declare global {
|
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
interface HTMLElementEventMap {
|
|
88
|
-
'gs-location-changed':
|
|
83
|
+
'gs-location-changed': LocationChangedEvent;
|
|
89
84
|
}
|
|
90
85
|
}
|
|
91
86
|
|
|
@@ -99,9 +94,7 @@ declare global {
|
|
|
99
94
|
}
|
|
100
95
|
|
|
101
96
|
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
102
|
-
type InitialValueMatches = Expect<
|
|
103
|
-
Equals<typeof LocationFilterComponent.prototype.initialValue, LocationFilterProps['initialValue']>
|
|
104
|
-
>;
|
|
97
|
+
type InitialValueMatches = Expect<Equals<typeof LocationFilterComponent.prototype.value, LocationFilterProps['value']>>;
|
|
105
98
|
type FieldsMatches = Expect<Equals<typeof LocationFilterComponent.prototype.fields, LocationFilterProps['fields']>>;
|
|
106
99
|
type PlaceholderTextMatches = Expect<
|
|
107
100
|
Equals<typeof LocationFilterComponent.prototype.placeholderText, LocationFilterProps['placeholderText']>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect, fn, userEvent, waitFor } from '@storybook/test';
|
|
1
|
+
import { expect, fireEvent, fn, userEvent, waitFor } from '@storybook/test';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
3
3
|
import { html } from 'lit';
|
|
4
4
|
|
|
@@ -97,7 +97,7 @@ export const Default: StoryObj<Required<TextInputProps>> = {
|
|
|
97
97
|
},
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
export const
|
|
100
|
+
export const FiresEvents: StoryObj<Required<TextInputProps>> = {
|
|
101
101
|
...Default,
|
|
102
102
|
play: async ({ canvasElement, step }) => {
|
|
103
103
|
const canvas = await withinShadowRoot(canvasElement, 'gs-text-input');
|
|
@@ -121,22 +121,45 @@ export const FiresEvent: StoryObj<Required<TextInputProps>> = {
|
|
|
121
121
|
|
|
122
122
|
await step('Empty input', async () => {
|
|
123
123
|
await userEvent.type(inputField(), '{backspace>9/}');
|
|
124
|
-
await expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
125
|
-
host: undefined,
|
|
126
|
-
});
|
|
127
124
|
});
|
|
128
125
|
|
|
129
126
|
await step('Enter a valid host name', async () => {
|
|
130
|
-
await userEvent.type(inputField(), 'Homo');
|
|
127
|
+
await userEvent.type(inputField(), 'Homo s');
|
|
128
|
+
|
|
129
|
+
const dropdownOption = await canvas.findByText('Homo sapiens');
|
|
130
|
+
await userEvent.click(dropdownOption);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await step('Verify event is fired with correct detail', async () => {
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(listenerMock).toHaveBeenCalledWith(
|
|
136
|
+
expect.objectContaining({
|
|
137
|
+
detail: {
|
|
138
|
+
host: 'Homo sapiens',
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await step('Remove initial value', async () => {
|
|
146
|
+
await fireEvent.click(canvas.getByRole('button', { name: 'clear selection' }));
|
|
131
147
|
|
|
132
148
|
await expect(listenerMock).toHaveBeenCalledWith(
|
|
133
149
|
expect.objectContaining({
|
|
134
150
|
detail: {
|
|
135
|
-
host:
|
|
151
|
+
host: undefined,
|
|
136
152
|
},
|
|
137
153
|
}),
|
|
138
154
|
);
|
|
139
155
|
});
|
|
156
|
+
|
|
157
|
+
await step('Empty input', async () => {
|
|
158
|
+
inputField().blur();
|
|
159
|
+
await expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
160
|
+
host: undefined,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
140
163
|
},
|
|
141
164
|
args: {
|
|
142
165
|
...Default.args,
|