-
Notifications
You must be signed in to change notification settings - Fork 841
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
matching_options
converted to TS+tests
- Loading branch information
Theo
committed
Apr 12, 2019
1 parent
ce003eb
commit 8343f4a
Showing
2 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { | ||
EuiComboBoxOption, | ||
flattenOptionGroups, | ||
getSelectedOptionForSearchValue, | ||
getMatchingOptions, | ||
} from './matching_options'; | ||
|
||
const options: EuiComboBoxOption[] = [ | ||
{ | ||
label: 'Titan', | ||
'data-test-subj': 'titanOption', | ||
}, | ||
{ | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}, | ||
{ | ||
label: 'Mimas', | ||
}, | ||
]; | ||
|
||
describe('flattenOptionGroups', () => { | ||
test('it flattens one level of options', () => { | ||
// Assemble | ||
const input = [ | ||
{ | ||
label: 'Titan', | ||
'data-test-subj': 'titanOption', | ||
}, | ||
{ | ||
label: 'Enceladus', | ||
options: [ | ||
{ | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}, | ||
], | ||
}, | ||
{ | ||
label: 'Mimas', | ||
}, | ||
]; | ||
const expected = options; | ||
// Act | ||
const got = flattenOptionGroups(input); | ||
// Assert | ||
expect(got).toMatchObject(expected); | ||
}); | ||
}); | ||
|
||
describe('getSelectedOptionForSearchValue', () => { | ||
test('gets the first matching selected option for search value', () => { | ||
// Assemble | ||
const expected: EuiComboBoxOption = { | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}; | ||
// Act | ||
const got = getSelectedOptionForSearchValue('saturn', options); | ||
// Assert | ||
expect(got).toMatchObject(expected); | ||
}); | ||
}); | ||
|
||
describe('getSelectedOptionForSearchValue', () => { | ||
test('returns undefined when no matching option found for search value', () => { | ||
// Act | ||
const got = getSelectedOptionForSearchValue('Pluto', options); | ||
// Assert | ||
expect(got).toBeUndefined(); | ||
}); | ||
test('gets the first matching selected option for search value', () => { | ||
// Assemble | ||
const expected: EuiComboBoxOption = { | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}; | ||
// Act | ||
const got = getSelectedOptionForSearchValue('saturn', options); | ||
// Assert | ||
expect(got).toMatchObject(expected); | ||
}); | ||
}); | ||
|
||
interface GetMatchingOptionsTestCase { | ||
options: EuiComboBoxOption[]; | ||
selectedOptions: EuiComboBoxOption[]; | ||
searchValue: string; | ||
isPreFiltered: boolean; | ||
showPrevSelected: boolean; | ||
expected: EuiComboBoxOption[]; | ||
} | ||
|
||
const testCases: GetMatchingOptionsTestCase[] = [ | ||
{ | ||
options, | ||
selectedOptions: [ | ||
{ | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}, | ||
], | ||
searchValue: 'saturn', | ||
isPreFiltered: false, | ||
showPrevSelected: false, | ||
expected: [], | ||
}, | ||
{ | ||
options, | ||
selectedOptions: [ | ||
{ | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}, | ||
], | ||
searchValue: 'saturn', | ||
isPreFiltered: true, | ||
showPrevSelected: false, | ||
expected: [ | ||
{ 'data-test-subj': 'titanOption', label: 'Titan' }, | ||
{ label: 'Mimas' }, | ||
], | ||
}, | ||
{ | ||
options, | ||
selectedOptions: [ | ||
{ | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}, | ||
], | ||
searchValue: 'saturn', | ||
isPreFiltered: false, | ||
showPrevSelected: true, | ||
expected: [{ 'data-test-subj': 'saturnOption', label: 'Saturn' }], | ||
}, | ||
{ | ||
options, | ||
selectedOptions: [ | ||
{ | ||
label: 'Saturn', | ||
'data-test-subj': 'saturnOption', | ||
}, | ||
], | ||
searchValue: 'saturn', | ||
isPreFiltered: true, | ||
showPrevSelected: true, | ||
expected: [ | ||
{ 'data-test-subj': 'titanOption', label: 'Titan' }, | ||
{ 'data-test-subj': 'saturnOption', label: 'Saturn' }, | ||
{ label: 'Mimas' }, | ||
], | ||
}, | ||
]; | ||
|
||
describe('getMatchingOptions', () => { | ||
test.each(testCases)( | ||
'.getMatchingOptions(%o)', | ||
(testCase: GetMatchingOptionsTestCase) => { | ||
expect( | ||
getMatchingOptions( | ||
testCase.options, | ||
testCase.selectedOptions, | ||
testCase.searchValue, | ||
testCase.isPreFiltered, | ||
testCase.showPrevSelected | ||
) | ||
).toMatchObject(testCase.expected); | ||
} | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
export interface EuiComboBoxOption { | ||
label: string; | ||
[prop: string]: any; | ||
} | ||
|
||
export const flattenOptionGroups = (optionsOrGroups: EuiComboBoxOption[]) => { | ||
return optionsOrGroups.reduce( | ||
(options: EuiComboBoxOption[], optionOrGroup: EuiComboBoxOption) => { | ||
if (optionOrGroup.options) { | ||
options.push(...optionOrGroup.options); | ||
} else { | ||
options.push(optionOrGroup); | ||
} | ||
return options; | ||
}, | ||
[] | ||
); | ||
}; | ||
|
||
export const getSelectedOptionForSearchValue = ( | ||
searchValue: string, | ||
selectedOptions: EuiComboBoxOption[] | ||
) => { | ||
const normalizedSearchValue = searchValue.toLowerCase(); | ||
return selectedOptions.find( | ||
(option: any) => option.label.toLowerCase() === normalizedSearchValue | ||
); | ||
}; | ||
|
||
const collectMatchingOption = ( | ||
accumulator: EuiComboBoxOption[], | ||
option: EuiComboBoxOption, | ||
selectedOptions: EuiComboBoxOption[], | ||
normalizedSearchValue: string, | ||
isPreFiltered: boolean, | ||
showPrevSelected: boolean | ||
) => { | ||
// Only show options which haven't yet been selected unless requested. | ||
const selectedOption = getSelectedOptionForSearchValue( | ||
option.label, | ||
selectedOptions | ||
); | ||
if (selectedOption && !showPrevSelected) { | ||
return false; | ||
} | ||
|
||
// If the options have already been pre-filtered then we can skip filtering against the search value. | ||
if (isPreFiltered) { | ||
accumulator.push(option); | ||
return; | ||
} | ||
|
||
if (!normalizedSearchValue) { | ||
accumulator.push(option); | ||
return; | ||
} | ||
|
||
const normalizedOption = option.label.trim().toLowerCase(); | ||
if (normalizedOption.includes(normalizedSearchValue)) { | ||
accumulator.push(option); | ||
} | ||
}; | ||
|
||
export const getMatchingOptions = ( | ||
options: EuiComboBoxOption[], | ||
selectedOptions: EuiComboBoxOption[], | ||
searchValue: string, | ||
isPreFiltered: boolean, | ||
showPrevSelected: boolean | ||
) => { | ||
const normalizedSearchValue = searchValue.trim().toLowerCase(); | ||
const matchingOptions: EuiComboBoxOption[] = []; | ||
|
||
options.forEach(option => { | ||
if (option.options) { | ||
const matchingOptionsForGroup: EuiComboBoxOption[] = []; | ||
option.options.forEach((groupOption: EuiComboBoxOption) => { | ||
collectMatchingOption( | ||
matchingOptionsForGroup, | ||
groupOption, | ||
selectedOptions, | ||
normalizedSearchValue, | ||
isPreFiltered, | ||
showPrevSelected | ||
); | ||
}); | ||
if (matchingOptionsForGroup.length > 0) { | ||
// Add option for group label | ||
matchingOptions.push({ label: option.label, isGroupLabelOption: true }); | ||
// Add matching options for group | ||
matchingOptions.push(...matchingOptionsForGroup); | ||
} | ||
} else { | ||
collectMatchingOption( | ||
matchingOptions, | ||
option, | ||
selectedOptions, | ||
normalizedSearchValue, | ||
isPreFiltered, | ||
showPrevSelected | ||
); | ||
} | ||
}); | ||
return matchingOptions; | ||
}; |