From 810ae1275ed2bb6c24eda38e3391790db1e0b25d Mon Sep 17 00:00:00 2001 From: Anatoliy Yastreb Date: Fri, 20 Oct 2017 09:59:34 +0900 Subject: [PATCH] perf(customPropTypes): Memoize `suggest` (#1184) (#2149) cache finding suggestions for prop words --- src/lib/customPropTypes.js | 49 +++++++++++++------------- test/specs/lib/customPropTypes-test.js | 43 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 test/specs/lib/customPropTypes-test.js diff --git a/src/lib/customPropTypes.js b/src/lib/customPropTypes.js index 12ff5665ea..ec50dc438d 100644 --- a/src/lib/customPropTypes.js +++ b/src/lib/customPropTypes.js @@ -12,6 +12,30 @@ export const as = (...args) => PropTypes.oneOfType([ PropTypes.func, ])(...args) +/* eslint-disable max-nested-callbacks */ +const findBestSuggestions = _.memoize((propValueWords, suggestions) => _.flow( + _.map((suggestion) => { + const suggestionWords = suggestion.split(' ') + + const propValueScore = _.flow( + _.map(x => _.map(y => leven(x, y), suggestionWords)), + _.map(_.min), + _.sum, + )(propValueWords) + + const suggestionScore = _.flow( + _.map(x => _.map(y => leven(x, y), propValueWords)), + _.map(_.min), + _.sum, + )(suggestionWords) + + return { suggestion, score: propValueScore + suggestionScore } + }), + _.sortBy(['score', 'suggestion']), + _.take(3), +)(suggestions)) +/* eslint-enable max-nested-callbacks */ + /** * Similar to PropTypes.oneOf but shows closest matches. * Word order is ignored allowing `left chevron` to match `chevron left`. @@ -32,30 +56,7 @@ export const suggest = suggestions => (props, propName, componentName) => { // find best suggestions const propValueWords = propValue.split(' ') - - /* eslint-disable max-nested-callbacks */ - const bestMatches = _.flow( - _.map((suggestion) => { - const suggestionWords = suggestion.split(' ') - - const propValueScore = _.flow( - _.map(x => _.map(y => leven(x, y), suggestionWords)), - _.map(_.min), - _.sum, - )(propValueWords) - - const suggestionScore = _.flow( - _.map(x => _.map(y => leven(x, y), propValueWords)), - _.map(_.min), - _.sum, - )(suggestionWords) - - return { suggestion, score: propValueScore + suggestionScore } - }), - _.sortBy(['score', 'suggestion']), - _.take(3), - )(suggestions) - /* eslint-enable max-nested-callbacks */ + const bestMatches = findBestSuggestions(propValueWords, suggestions) // skip if a match scored 0 // since we're matching on words (classNames) this allows any word order to pass validation diff --git a/test/specs/lib/customPropTypes-test.js b/test/specs/lib/customPropTypes-test.js new file mode 100644 index 0000000000..2058e834b3 --- /dev/null +++ b/test/specs/lib/customPropTypes-test.js @@ -0,0 +1,43 @@ +import { customPropTypes } from 'src/lib' + +describe('suggest prop type', () => { + it('should throw error when non-array argument given', () => { + const propType = customPropTypes.suggest('foo') + expect(() => propType({ name: 'bar' }, 'name', 'FooComponent')).to.throw( + Error, + /Invalid argument supplied to suggest, expected an instance of array./, + ) + }) + + it('should return undefined when prop is valid', () => { + const propType = customPropTypes.suggest(['foo', 'bar', 'baz']) + expect(propType({ name: 'bar' }, 'name', 'FooComponent')).to.equal(undefined) + }) + + it('should return Error with suggestions when prop is invalid', () => { + const propType = customPropTypes.suggest(['foo', 'bar', 'baz']) + const props = { name: 'bad', title: 'bat words' } + + const resultFooComponent = propType(props, 'name', 'FooComponent') + expect(resultFooComponent).to.be.an.instanceof(Error) + expect(resultFooComponent.message).to + .equal(`Invalid prop \`name\` of value \`bad\` supplied to \`FooComponent\`. + +Instead of \`bad\`, did you mean: + - bar + - baz + - foo +`) + + const resultBarComponent = propType(props, 'title', 'BarComponent') + expect(resultBarComponent).to.be.an.instanceof(Error) + expect(resultBarComponent.message).to + .equal(`Invalid prop \`title\` of value \`bat words\` supplied to \`BarComponent\`. + +Instead of \`bat words\`, did you mean: + - bar + - baz + - foo +`) + }) +})