From be9eed967e596c7e14f75ab91690403ad352ee1a Mon Sep 17 00:00:00 2001 From: Claas Ahlrichs Date: Mon, 2 Jul 2018 23:07:03 +0200 Subject: [PATCH] new package: "match-sorter" (#26968) * generated files for "match-sorter" package * added initial set of tests * drafted export for matchSorter function * added support for options.keys * added support for options.threshold * added support for options.keepDiacritics * removed caseRankings * fixed issues highlighted by: "npm run lint match-sorter" * switched to "export =", rather than "export default" --- types/match-sorter/index.d.ts | 52 +++++++++ types/match-sorter/match-sorter-tests.ts | 130 +++++++++++++++++++++++ types/match-sorter/tsconfig.json | 24 +++++ types/match-sorter/tslint.json | 1 + 4 files changed, 207 insertions(+) create mode 100644 types/match-sorter/index.d.ts create mode 100644 types/match-sorter/match-sorter-tests.ts create mode 100644 types/match-sorter/tsconfig.json create mode 100644 types/match-sorter/tslint.json diff --git a/types/match-sorter/index.d.ts b/types/match-sorter/index.d.ts new file mode 100644 index 0000000000..e1a7e8efe4 --- /dev/null +++ b/types/match-sorter/index.d.ts @@ -0,0 +1,52 @@ +// Type definitions for match-sorter 2.2 +// Project: https://github.com/kentcdodds/match-sorter#readme +// Definitions by: Claas Ahlrichs +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare namespace matchSorter { + namespace rankings { + const CASE_SENSITIVE_EQUAL: 9; + const EQUAL: 8; + const STARTS_WITH: 7; + const WORD_STARTS_WITH: 6; + const STRING_CASE: 5; + const STRING_CASE_ACRONYM: 4; + const CONTAINS: 3; + const ACRONYM: 2; + const MATCHES: 1; + const NO_MATCH: 0; + } +} + +interface MinRanking { + minRanking: number; + key: string; +} + +interface MaxRanking { + maxRanking: number; + key: string; +} + +interface MinMaxRanking { + minRanking: number; + maxRanking: number; + key: string; +} + +interface Options { + keys?: Array<(string | ((item: T) => string) | MinRanking | MaxRanking | MinMaxRanking)>; + threshold?: number; + keepDiacritics?: boolean; +} + +/** + * Takes an array of items and a value and returns a new array with the items that match the given value + * @param items - the items to sort + * @param value - the value to use for ranking + * @param options - Some options to configure the sorter + * @return the new sorted array + */ +declare function matchSorter(items: T[], value: string, options?: Options): T[]; + +export = matchSorter; diff --git a/types/match-sorter/match-sorter-tests.ts b/types/match-sorter/match-sorter-tests.ts new file mode 100644 index 0000000000..e1496cba9f --- /dev/null +++ b/types/match-sorter/match-sorter-tests.ts @@ -0,0 +1,130 @@ +import matchSorter = require('match-sorter'); + +// # Basic Sample +{ + const list = ['hi', 'hey', 'hello', 'sup', 'yo']; + matchSorter(list, 'h'); // ['hi', 'hey', 'hello'] + matchSorter(list, 'y'); // ['yo', 'hey'] + matchSorter(list, 'z'); // [] +} + +// # Advanced options + +// ## keys: [string] +{ + const objList = [ + {name: 'Janice', color: 'Green'}, + {name: 'Fred', color: 'Orange'}, + {name: 'George', color: 'Blue'}, + {name: 'Jen', color: 'Red'}, + ]; + matchSorter(objList, 'g', {keys: ['name', 'color']}); + // [{name: 'George', color: 'Blue'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}] + + matchSorter(objList, 're', {keys: ['color', 'name']}); + // [{name: 'Jen', color: 'Red'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}, {name: 'George', color: 'Blue'}] +} + +// ### Array of values +{ + const iceCreamYum = [ + {favoriteIceCream: ['mint', 'chocolate']}, + {favoriteIceCream: ['candy cane', 'brownie']}, + {favoriteIceCream: ['birthday cake', 'rocky road', 'strawberry']}, + ]; + matchSorter(iceCreamYum, 'cc', {keys: ['favoriteIceCream']}); + // [{favoriteIceCream: ['candy cane', 'brownie']}, {favoriteIceCream: ['mint', 'chocolate']}] +} + +// ### Nested Keys +{ + const nestedObjList = [ + {name: {first: 'Janice'}}, + {name: {first: 'Fred'}}, + {name: {first: 'George'}}, + {name: {first: 'Jen'}}, + ]; + matchSorter(nestedObjList, 'j', {keys: ['name.first']}); + // [{name: {first: 'Janice'}}, {name: {first: 'Jen'}}] +} +{ + const nestedObjList = [ + {name: [{first: 'Janice'}]}, + {name: [{first: 'Fred'}]}, + {name: [{first: 'George'}]}, + {name: [{first: 'Jen'}]}, + ]; + matchSorter(nestedObjList, 'j', {keys: ['name.0.first']}); + // [{name: {first: 'Janice'}}, {name: {first: 'Jen'}}] + // matchSorter(nestedObjList, 'j', {keys: ['name[0].first']}) does not work +} + +// ### Property Callbacks +{ + const list = [{name: 'Janice'}, {name: 'Fred'}, {name: 'George'}, {name: 'Jen'}]; + matchSorter(list, 'j', {keys: [item => item.name]}); + // [{name: 'Janice'}, {name: 'Jen'}] +} + +// ### Min and Max Ranking +{ + const tea = [ + {tea: 'Earl Grey', alias: 'A'}, + {tea: 'Assam', alias: 'B'}, + {tea: 'Black', alias: 'C'}, + ]; + matchSorter(tea, 'A', { + keys: ['tea', {maxRanking: matchSorter.rankings.STARTS_WITH, key: 'alias'}], + }); + // without maxRanking, Earl Grey would come first because the alias "A" would be CASE_SENSITIVE_EQUAL + // `tea` key comes before `alias` key, so Assam comes first even though both match as STARTS_WITH + // [{tea: 'Assam', alias: 'B'}, {tea: 'Earl Grey', alias: 'A'},{tea: 'Black', alias: 'C'}] +} +{ + const tea = [ + {tea: 'Milk', alias: 'moo'}, + {tea: 'Oolong', alias: 'B'}, + {tea: 'Green', alias: 'C'}, + ]; + matchSorter(tea, 'oo', { + keys: ['tea', {minRanking: matchSorter.rankings.EQUAL, key: 'alias'}], + }); + // minRanking bumps Milk up to EQUAL from CONTAINS (alias) + // Oolong matches as STARTS_WITH + // Green is missing due to no match + // [{tea: 'Milk', alias: 'moo'}, {tea: 'Oolong', alias: 'B'}] +} + +// ## threshold: number +{ + const fruit = ['orange', 'apple', 'grape', 'banana']; + matchSorter(fruit, 'ap', {threshold: matchSorter.rankings.NO_MATCH}); + // ['apple', 'grape', 'orange', 'banana'] (returns all items, just sorted by best match) + + const things = ['google', 'airbnb', 'apple', 'apply', 'app']; + matchSorter(things, 'app', {threshold: matchSorter.rankings.EQUAL}); + // ['app'] (only items that are equal) + + const otherThings = ['fiji apple', 'google', 'app', 'crabapple', 'apple', 'apply']; + matchSorter(otherThings, 'app', {threshold: matchSorter.rankings.WORD_STARTS_WITH}); + // ['app', 'apple', 'apply', 'fiji apple'] (everything that matches with "word starts with" or better) +} + +// ## keepDiacritics: boolean +{ + const thingsWithDiacritics = [ + 'jalapeño', + 'à la carte', + 'café', + 'papier-mâché', + 'à la mode', + ]; + matchSorter(thingsWithDiacritics, 'aa'); + // ['jalapeño', 'à la carte', 'à la mode', 'papier-mâché'] + + matchSorter(thingsWithDiacritics, 'aa', {keepDiacritics: true}); + // ['jalapeño', 'à la carte'] + + matchSorter(thingsWithDiacritics, 'à', {keepDiacritics: true}); + // ['à la carte', 'à la mode'] +} diff --git a/types/match-sorter/tsconfig.json b/types/match-sorter/tsconfig.json new file mode 100644 index 0000000000..7bdcd8a2d6 --- /dev/null +++ b/types/match-sorter/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "esModuleInterop": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "match-sorter-tests.ts" + ] +} diff --git a/types/match-sorter/tslint.json b/types/match-sorter/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/match-sorter/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }