diff --git a/src/sidebar/components/tag-editor.js b/src/sidebar/components/tag-editor.js index 0280cef66cc..b470ecf24c8 100644 --- a/src/sidebar/components/tag-editor.js +++ b/src/sidebar/components/tag-editor.js @@ -244,15 +244,32 @@ function TagEditor({ * @param {string} item - Suggested tag * @return {JSXElement} - Formatted tag for use in list */ - const formatSuggestItem = item => { - const curVal = pendingTag(); - const prefix = item.slice(0, item.indexOf(curVal)); - const suffix = item.slice(item.indexOf(curVal) + curVal.length); + const formatSuggestedItem = item => { + // filtering of tags is case-insensitive + const curVal = pendingTag().toLowerCase(); + const suggestedTag = item.toLowerCase(); + const matchIndex = suggestedTag.indexOf(curVal); + + // If the current input doesn't seem to match the suggested tag, + // just render the tag as-is. + if (matchIndex === -1) { + return {item}; + } + + // Break the suggested tag into three parts: + // 1. Substring of the suggested tag that occurs before the match + // with the current input + const prefix = item.slice(0, matchIndex); + // 2. Substring of the suggested tag that matches the input text. NB: + // This may be in a different case than the input text. + const matchString = item.slice(matchIndex, matchIndex + curVal.length); + // 3. Substring of the suggested tag that occurs after the matched input + const suffix = item.slice(matchIndex + curVal.length); return ( {prefix} - {curVal} + {matchString} {suffix} ); @@ -324,7 +341,7 @@ function TagEditor({ { + fakeTagsService.filter.returns(['fine AArdvark', 'AAArgh']); + const wrapper = createComponent(); + wrapper.find('input').instance().value = 'aa'; + typeInput(wrapper); + + const formattingFn = wrapper.find('AutocompleteList').prop('listFormatter'); + const tagList = wrapper.find('AutocompleteList').prop('list'); + + const firstSuggestedTag = mount(formattingFn(tagList[0])) + .find('span') + .text(); + const secondSuggestedTag = mount(formattingFn(tagList[1])) + .find('span') + .text(); + + // Even though the entered text was lower case ('aa'), the suggested tag + // should be rendered with its original casing (upper-case here) + assert.equal(firstSuggestedTag, 'AAArgh'); + assert.equal(secondSuggestedTag, 'fine AArdvark'); + }); + + it('shows suggested tags as-is if they do not seem to match the input', () => { + // This case addresses a situation in which a substring match isn't found + // for the current input text against a given suggested tag. This should not + // happen in practice—i.e. filtered tags should match the current input— + // but there is no contract that the tags service filtering uses the same + // "matching" as the component, so we should be able to handle cases where + // there doesn't "seem" to be a match by just rendering the suggested tag + // as-is. + fakeTagsService.filter.returns(['fine AArdvark', 'AAArgh']); + const wrapper = createComponent(); + wrapper.find('input').instance().value = 'bb'; + typeInput(wrapper); + + const formattingFn = wrapper.find('AutocompleteList').prop('listFormatter'); + const tagList = wrapper.find('AutocompleteList').prop('list'); + + const firstSuggestedTag = mount(formattingFn(tagList[0])) + .find('span') + .text(); + const secondSuggestedTag = mount(formattingFn(tagList[1])) + .find('span') + .text(); + + // Obviously, these don't have a `bb` substring; we'll just render them... + assert.equal(firstSuggestedTag, 'AAArgh'); + assert.equal(secondSuggestedTag, 'fine AArdvark'); + }); + it('passes the text value to filter() after receiving input', () => { const wrapper = createComponent(); wrapper.find('input').instance().value = 'tag3';