Skip to content

Commit 8145da8

Browse files
authored
chore: Adds property filter stories unit tests (#3751)
1 parent 2bd2d2f commit 8145da8

8 files changed

+1175
-2
lines changed
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import createWrapper, {
5+
AutosuggestWrapper,
6+
FormFieldWrapper,
7+
MultiselectWrapper,
8+
SelectWrapper,
9+
} from '../../../lib/components/test-utils/dom';
10+
11+
import itemStyles from '../../../lib/components/internal/components/selectable-item/styles.css.js';
12+
import selectableStyles from '../../../lib/components/internal/components/selectable-item/styles.selectors.js';
13+
import propertyFilterStyles from '../../../lib/components/property-filter/styles.selectors.js';
14+
import selectStyles from '../../../lib/components/select/parts/styles.selectors.js';
15+
16+
export function createExtendedWrapper() {
17+
const wrapper = createWrapper().findPropertyFilter()!;
18+
function findEditorDropdown() {
19+
return wrapper
20+
.findTokens()
21+
.map(w => w.findEditorDropdown())
22+
.filter(Boolean)[0]!;
23+
}
24+
function findControl(fieldIndex: number, fieldType: 'property' | 'operator' | 'value') {
25+
const dropdown = findEditorDropdown();
26+
const field = {
27+
property: dropdown.findPropertyField(fieldIndex),
28+
operator: dropdown.findOperatorField(fieldIndex),
29+
value: dropdown.findValueField(fieldIndex),
30+
}[fieldType];
31+
return field.findControl()!;
32+
}
33+
34+
return {
35+
input: {
36+
focus() {
37+
wrapper.focus();
38+
},
39+
value(text: string) {
40+
wrapper.setInputValue(text);
41+
},
42+
date(text: string) {
43+
wrapper.findDropdown().findOpenDropdown()!.findDateInput()!.setInputValue(text);
44+
},
45+
keys(...codes: number[]) {
46+
codes.forEach(code => wrapper.findNativeInput().keydown(code));
47+
},
48+
submit() {
49+
wrapper.findPropertySubmitButton()!.click();
50+
},
51+
cancel() {
52+
wrapper.findPropertyCancelButton()!.click();
53+
},
54+
selectByValue(value: null | string) {
55+
wrapper.selectSuggestionByValue(value as any);
56+
},
57+
selectAll() {
58+
wrapper
59+
.findDropdown()
60+
.findOpenDropdown()!
61+
.findByClassName(selectableStyles['selectable-item'])!
62+
.fireEvent(new MouseEvent('mouseup', { bubbles: true }));
63+
},
64+
dropdown() {
65+
return !!wrapper.findDropdown().findOpenDropdown();
66+
},
67+
status() {
68+
return wrapper.findDropdown().findFooterRegion()?.getElement().textContent ?? '';
69+
},
70+
options() {
71+
const enteredTextOption = wrapper.findEnteredTextOption()?.getElement().textContent;
72+
const otherOptions = printOptions(wrapper);
73+
return [enteredTextOption, ...otherOptions].filter(Boolean);
74+
},
75+
},
76+
tokens: {
77+
token(index = 1) {
78+
const token = wrapper.findTokens()[index - 1];
79+
return {
80+
nested(index = 1) {
81+
const nested = token.findGroupTokens()[index - 1];
82+
return {
83+
operation(value: 'and' | 'or') {
84+
nested.findTokenOperation()!.openDropdown();
85+
nested.findTokenOperation()?.selectOptionByValue(value);
86+
},
87+
};
88+
},
89+
operation(value: 'and' | 'or') {
90+
token.findTokenOperation()!.openDropdown();
91+
token.findTokenOperation()?.selectOptionByValue(value);
92+
},
93+
};
94+
},
95+
list() {
96+
return wrapper.findTokens().map(w => {
97+
const operation = w.findTokenOperation()?.find('button')?.getElement().textContent;
98+
const value = w.findLabel()?.getElement().textContent;
99+
const nested = w
100+
.findGroupTokens()
101+
.map(w => {
102+
const operation = w.findTokenOperation()?.find('button')?.getElement().textContent;
103+
const value = w.findLabel()?.getElement().textContent;
104+
return [operation, value].filter(Boolean).join(' ');
105+
})
106+
.join(' ');
107+
return [operation, value, nested].filter(Boolean).join(' ');
108+
});
109+
},
110+
},
111+
editor: {
112+
dropdown() {
113+
return !!findEditorDropdown();
114+
},
115+
open(index: number) {
116+
const token = wrapper.findTokens()[index - 1];
117+
const target = token.findEditButton() ?? token.findLabel();
118+
target.click();
119+
},
120+
property(fieldIndex = 1) {
121+
const select = findControl(fieldIndex, 'property').findSelect()!;
122+
return {
123+
open() {
124+
dropdownOpen(select);
125+
},
126+
value(option: null | string) {
127+
dropdownOpen(select);
128+
select.selectOptionByValue(option as any);
129+
},
130+
options() {
131+
dropdownOpen(select);
132+
return printOptions(select);
133+
},
134+
};
135+
},
136+
operator(fieldIndex = 1) {
137+
const select = findControl(fieldIndex, 'operator').findSelect()!;
138+
return {
139+
open() {
140+
dropdownOpen(select);
141+
},
142+
value(option: null | string) {
143+
dropdownOpen(select);
144+
select.selectOptionByValue(option as any);
145+
},
146+
options() {
147+
dropdownOpen(select);
148+
return printOptions(select);
149+
},
150+
};
151+
},
152+
value(fieldIndex = 1) {
153+
return {
154+
autosuggest() {
155+
const autosuggest = findControl(fieldIndex, 'value').findAutosuggest()!;
156+
return {
157+
focus() {
158+
autosuggest.focus();
159+
},
160+
option(value: string) {
161+
autosuggest.focus();
162+
autosuggest.selectSuggestionByValue(value);
163+
},
164+
input(text: string) {
165+
autosuggest.setInputValue(text);
166+
},
167+
options() {
168+
return printOptions(autosuggest);
169+
},
170+
};
171+
},
172+
multiselect() {
173+
const multiselect = findControl(fieldIndex, 'value').findMultiselect()!;
174+
return {
175+
open() {
176+
dropdownOpen(multiselect);
177+
},
178+
filter(text: string) {
179+
dropdownOpen(multiselect);
180+
multiselect.findFilteringInput()!.setInputValue(text);
181+
},
182+
value(options: (null | string)[]) {
183+
dropdownOpen(multiselect);
184+
options.forEach(option => multiselect.selectOptionByValue(option as any));
185+
},
186+
options() {
187+
dropdownOpen(multiselect);
188+
return printOptions(multiselect);
189+
},
190+
};
191+
},
192+
dateInput() {
193+
const dateInput = findControl(fieldIndex, 'value').findDateInput()!;
194+
return {
195+
value(value: string) {
196+
dateInput.setInputValue(value);
197+
},
198+
};
199+
},
200+
};
201+
},
202+
remove(rowIndex: number) {
203+
findEditorDropdown().findTokenRemoveActions(rowIndex)!.openDropdown();
204+
findEditorDropdown().findTokenRemoveActions(rowIndex)!.findItemById('remove')!.click();
205+
},
206+
addFilter() {
207+
findEditorDropdown().findTokenAddActions()!.findMainAction()!.click();
208+
},
209+
submit() {
210+
findEditorDropdown().findSubmitButton()!.click();
211+
},
212+
cancel() {
213+
findEditorDropdown().findCancelButton()!.click();
214+
},
215+
form() {
216+
const dropdown = findEditorDropdown();
217+
const rows: string[] = [];
218+
for (let index = 1; index <= 10; index++) {
219+
const property = printField(dropdown.findPropertyField(index), 'property');
220+
const operator = printField(dropdown.findOperatorField(index), 'operator');
221+
const value = printField(dropdown.findValueField(index), 'value');
222+
if (property || operator || value) {
223+
rows.push([property, operator, value].join(' '));
224+
} else {
225+
break;
226+
}
227+
}
228+
return rows;
229+
},
230+
},
231+
};
232+
}
233+
234+
function dropdownOpen(wrapper: SelectWrapper | MultiselectWrapper) {
235+
if (!wrapper.findDropdown().findOpenDropdown()) {
236+
wrapper.openDropdown();
237+
}
238+
}
239+
240+
function printOptions(wrapper: AutosuggestWrapper | SelectWrapper | MultiselectWrapper) {
241+
const options = wrapper
242+
.findDropdown()
243+
.findOptions()
244+
.flatMap(w => {
245+
const selectOptions = w.findAllByClassName(itemStyles['option-content']);
246+
const options = selectOptions.length > 0 ? selectOptions : [w];
247+
return options.map(w => w.getElement().textContent);
248+
});
249+
return options.filter(Boolean);
250+
}
251+
252+
function printField(wrapper: null | FormFieldWrapper, type: 'property' | 'operator' | 'value') {
253+
if (!wrapper) {
254+
return null;
255+
}
256+
const headers = createWrapper().findAllByClassName(propertyFilterStyles['token-editor-grid-header']);
257+
const header = headers[['property', 'operator', 'value'].indexOf(type)];
258+
const formFieldLabel = wrapper.findLabel()?.getElement().textContent ?? header?.getElement().textContent;
259+
const autosuggest = wrapper.findControl()!.findAutosuggest();
260+
const dateInput = wrapper.findControl()?.findDateInput();
261+
if (autosuggest || dateInput) {
262+
const input = autosuggest?.findNativeInput() ?? dateInput?.findNativeInput();
263+
const value = input!.getElement().value;
264+
return `${formFieldLabel}[${value}]`;
265+
}
266+
const select = wrapper.findControl()!.findSelect();
267+
if (select) {
268+
const value = select.getElement().textContent;
269+
return `${formFieldLabel}[${value}]`;
270+
}
271+
const multiselect = wrapper.findControl()!.findMultiselect();
272+
if (multiselect) {
273+
const tokens = multiselect.findAllByClassName(selectStyles['inline-token']);
274+
const value = tokens.map(w => w.getElement().textContent).join(', ');
275+
return `${formFieldLabel}[${value}]`;
276+
}
277+
return `${formFieldLabel}[]`;
278+
}

src/property-filter/__tests__/property-filter-i18n.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ describe('i18n', () => {
300300
enableTokenGroups={true}
301301
filteringOptions={[]}
302302
customGroupsText={[]}
303-
disableFreeTextFiltering={false}
304303
/>
305304
</TestI18nProvider>
306305
);
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { KeyCode } from '@cloudscape-design/test-utils-core/utils';
5+
6+
import { PropertyFilterProps } from '../interfaces';
7+
import { createRenderer } from './stories-components';
8+
9+
const defaultProps: Partial<PropertyFilterProps> = {
10+
filteringProperties: [
11+
{
12+
key: 'id',
13+
propertyLabel: 'ID',
14+
operators: ['=', '!='],
15+
groupValuesLabel: 'ID values',
16+
},
17+
{
18+
key: 'status',
19+
propertyLabel: 'Status',
20+
operators: [
21+
{ operator: '=', tokenType: 'enum' },
22+
{ operator: '!=', tokenType: 'enum' },
23+
],
24+
groupValuesLabel: 'Status values',
25+
},
26+
],
27+
filteringOptions: [
28+
{ propertyKey: 'id', value: 'x-1' },
29+
{ propertyKey: 'id', value: 'x-2' },
30+
{ propertyKey: 'id', value: 'x-3' },
31+
{ propertyKey: 'id', value: 'x-4' },
32+
{ propertyKey: 'status', value: 'Active' },
33+
{ propertyKey: 'status', value: 'Activating' },
34+
{ propertyKey: 'status', value: 'Inactive' },
35+
],
36+
};
37+
const render = createRenderer(defaultProps, { async: true });
38+
39+
describe('Property filter stories: tokens creation, async', () => {
40+
test('waits and selects property, then waits and selects option', async () => {
41+
const { wrapper } = render({ asyncProperties: true });
42+
43+
wrapper.input.focus();
44+
expect(wrapper.input.dropdown()).toBe(true);
45+
expect(wrapper.input.options()).toEqual([]);
46+
expect(wrapper.input.status()).toBe('Loading status');
47+
48+
await new Promise(resolve => setTimeout(resolve, 1));
49+
expect(wrapper.input.options()).toEqual(['ID', 'Status']);
50+
expect(wrapper.input.status()).toBe('Finished status');
51+
52+
wrapper.input.value('ID');
53+
expect(wrapper.input.options()).toEqual(['Use: "ID"', 'ID =Equals', 'ID !=Does not equal']);
54+
55+
wrapper.input.selectByValue('ID = ');
56+
expect(wrapper.input.options()).toEqual(['Use: "ID = "', 'ID = x-1', 'ID = x-2', 'ID = x-3', 'ID = x-4']);
57+
expect(wrapper.input.status()).toBe('Loading status');
58+
59+
await new Promise(resolve => setTimeout(resolve, 1));
60+
expect(wrapper.input.options()).toEqual(['Use: "ID = "', 'ID = x-1', 'ID = x-2', 'ID = x-3', 'ID = x-4']);
61+
expect(wrapper.input.status()).toBe('Finished status');
62+
63+
wrapper.input.selectByValue('ID = x-2');
64+
expect(wrapper.input.dropdown()).toBe(false);
65+
expect(wrapper.tokens.list()).toEqual(['ID = x-2']);
66+
});
67+
68+
test('searches token by value, waits and selects the matched option', async () => {
69+
const { wrapper } = render({ asyncProperties: false });
70+
71+
wrapper.input.focus();
72+
expect(wrapper.input.dropdown()).toBe(true);
73+
expect(wrapper.input.options()).toEqual(['ID', 'Status']);
74+
expect(wrapper.input.status()).toBe('');
75+
76+
wrapper.input.value('x-3');
77+
expect(wrapper.input.options()).toEqual(['Use: "x-3"']);
78+
expect(wrapper.input.status()).toBe('Loading status');
79+
80+
await new Promise(resolve => setTimeout(resolve, 1));
81+
expect(wrapper.input.options()).toEqual(['Use: "x-3"', 'ID = x-3']);
82+
expect(wrapper.input.status()).toBe('Finished status');
83+
84+
wrapper.input.keys(KeyCode.down, KeyCode.down, KeyCode.enter);
85+
expect(wrapper.tokens.list()).toEqual(['ID = x-3']);
86+
});
87+
88+
test('searches enum token value, waits and selects all matched options', async () => {
89+
const { wrapper } = render({ asyncProperties: false });
90+
91+
wrapper.input.focus();
92+
expect(wrapper.input.dropdown()).toBe(true);
93+
expect(wrapper.input.options()).toEqual(['ID', 'Status']);
94+
expect(wrapper.input.status()).toBe('');
95+
96+
wrapper.input.value('Status = Activ');
97+
expect(wrapper.input.options()).toEqual([]);
98+
expect(wrapper.input.status()).toBe('Loading status');
99+
100+
await new Promise(resolve => setTimeout(resolve, 1));
101+
expect(wrapper.input.options()).toEqual(['Active', 'Activating']);
102+
expect(wrapper.input.status()).toBe('Finished status');
103+
104+
wrapper.input.selectAll();
105+
wrapper.input.submit();
106+
expect(wrapper.input.dropdown()).toBe(false);
107+
expect(wrapper.tokens.list()).toEqual(['Status = Active, Activating']);
108+
});
109+
});

0 commit comments

Comments
 (0)