UNPKG

@committed/trie-search

Version:

A trie implementation that maps keys to objects for rapid retrieval by phrases. Most common use will be for typeahead searches.

1,051 lines (889 loc) 34.8 kB
import assert from "assert" import { TrieSearch } from "./trie-search" describe("TrieSearch", function () { describe("new TrieSearch(keyFields) should work", function () { it("should set keyfields (1)", function () { var ts = new TrieSearch(["key"]) assert.equal(ts.keyFields.length, 1) }) it("should set keyfields (2)", function () { var ts = new TrieSearch("key") assert.equal(ts.keyFields.length, 1) }) }) describe("new TrieSearch(keyFields).keyToArr should work", function () { it("'key' -> ['k', 'e', 'y']", function () { var ts = new TrieSearch() assert.equal(ts.keyToArr("key")[0], "k") assert.equal(ts.keyToArr("key")[1], "e") assert.equal(ts.keyToArr("key")[2], "y") }) it("for options.min == 2, 'key' -> ['ke', 'y']", function () { var ts = new TrieSearch("blah", { min: 2 }) assert.equal(ts.keyToArr("key")[0], "ke") assert.equal(ts.keyToArr("key")[1], "y") }) it("for options.min == 2, 'keyset' -> ['ke', 'y', 's', 'e', 't']", function () { var ts = new TrieSearch("blah", { min: 2 }) assert.equal(ts.keyToArr("keyset")[0], "ke") assert.equal(ts.keyToArr("keyset")[1], "y") assert.equal(ts.keyToArr("keyset")[2], "s") assert.equal(ts.keyToArr("keyset")[3], "e") assert.equal(ts.keyToArr("keyset")[4], "t") }) it("for options.min == 3, 'key' -> ['key']", function () { var ts = new TrieSearch("blah", { min: 3 }) assert.equal(ts.keyToArr("key")[0], "key") }) it("for options.min == 4, 'key' -> []", function () { var ts = new TrieSearch("blah", { min: 4 }) assert.equal(ts.keyToArr("key").length, 0) }) }) describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item", function () { const ts = new TrieSearch("key"), item = { key: "blah" } ts.add(item) it("add('blah') should build map of nodes", function () { assert(ts.root["b"] !== undefined, "b does not exist") // @ts-ignore assert(ts.root["b"]["l"] !== undefined, "bl does not exist") // @ts-ignore assert(ts.root["b"]["l"]["a"] !== undefined, "bla does not exist") // @ts-ignore assert(ts.root["b"]["l"]["a"]["h"] !== undefined, "blah does not exist") }) it("get('blah') for each subkey should work", function () { assert.equal(ts.get("b")[0], item) assert.equal(ts.get("bl")[0], item) assert.equal(ts.get("bla")[0], item) assert.equal(ts.get("blah")[0], item) assert.equal(ts.get("blab").length, 0) assert.equal(ts.get("nope").length, 0) }) }) describe("TrieSearch::addAll(...)should work for an array", function () { var ts = new TrieSearch("key"), items = [{ key: "addendum" }, { key: "banana" }, { key: "cat" }] ts.addAll(items) it("get('blah') for each subkey should work", function () { assert.equal(ts.get("b")[0], items[1]) assert.equal(ts.get("ba")[0], items[1]) assert.equal(ts.get("ban")[0], items[1]) assert.equal(ts.get("bana")[0], items[1]) assert.equal(ts.get("banana")[0], items[1]) assert.equal(ts.get("a")[0], items[0]) assert.equal(ts.get("ad")[0], items[0]) assert.equal(ts.get("add")[0], items[0]) assert.equal(ts.get("adde")[0], items[0]) assert.equal(ts.get("addendum")[0], items[0]) assert.equal(ts.get("c")[0], items[2]) assert.equal(ts.get("ca")[0], items[2]) assert.equal(ts.get("cat")[0], items[2]) }) }) describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item with a numeric key", function () { var ts = new TrieSearch("key"), item = { key: 1234567890 } ts.add(item) it("add('1234567890') should build map of nodes", function () { assert(ts.root["1"] !== undefined, "1 does not exist") // @ts-ignore assert(ts.root["1"]["2"] !== undefined, "12 does not exist") // @ts-ignore assert(ts.root["1"]["2"]["3"] !== undefined, "123 does not exist") // @ts-ignore assert(ts.root["1"]["2"]["3"]["4"] !== undefined, "1234 does not exist") }) it("get('1234567890') for each subkey should work", function () { assert.equal(ts.get("1")[0], item) assert.equal(ts.get("12")[0], item) assert.equal(ts.get("123")[0], item) assert.equal(ts.get("1234")[0], item) assert.equal(ts.get("1234")[0], item) assert.equal(ts.get("12345")[0], item) assert.equal(ts.get("123456")[0], item) assert.equal(ts.get("1234567")[0], item) assert.equal(ts.get("nope").length, 0) }) }) describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item with no split and whitespace", function () { var ts = new TrieSearch("key", { splitOnRegEx: undefined, }), item = { key: "hello world" } ts.add(item) it("add('hello world') should build map of nodes", function () { // @ts-ignore assert(ts.root.h.e.l.l.o !== undefined, "'hello' does not exist") // @ts-ignore assert(ts.root.h.e.l.l.o[" "] !== undefined, "'hello ' does not exist") assert( // @ts-ignore ts.root.h.e.l.l.o[" "].w.o.r.l.d !== undefined, "'hello world' does not exist" ) }) it("get('hello world') for each subkey should work", function () { assert.equal(ts.get("h")[0], item) assert.equal(ts.get("he")[0], item) assert.equal(ts.get("hel")[0], item) assert.equal(ts.get("hell")[0], item) assert.equal(ts.get("hello")[0], item) assert.equal(ts.get("hello ")[0], item) assert.equal(ts.get("hello w")[0], item) assert.equal(ts.get("hello wo")[0], item) assert.equal(ts.get("hello wor")[0], item) assert.equal(ts.get("hello worl")[0], item) assert.equal(ts.get("hello world")[0], item) assert.equal(ts.get("nope").length, 0) }) }) describe("TrieSearch::get(...) should work for multiple keys and union the result", function () { var ts = new TrieSearch("key", { min: 2 }), item1 = { key: "the quick brown fox" }, item2 = { key: "the quick brown" }, item3 = { key: "the quick fox" }, item4 = { key: "the fox" } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) it("get('the quick') should return all entries", function () { expect(ts.get("th").length).toBe(4) expect(ts.get("the").length).toBe(4) expect(ts.get("the quick").length).toBe(3) }) it("get('the brown') should return 2 entries", function () { expect(ts.get("the brown").length).toBe(2) }) it("get('the fox') should return 3 entries", function () { expect(ts.get("the fox").length).toBe(3) }) it("get('fox brown') should return 1 entry", function () { expect(ts.get("fox brown").length).toBe(1) }) it("get('brown fox') should return 1 entry", function () { expect(ts.get("brown fox").length).toBe(1) }) it("get('brown f') should return 2 entry, ignoring the shortness of the second word", function () { expect(ts.get("brown f").length).toBe(2) }) it("get('br f') should return 1 entry, ignoring the shortness of the second word", function () { expect(ts.get("br f").length).toBe(2) }) it("get('qui b c d e f g h') should return 3 entries, ignoring the shortness of all subsequent words", function () { expect(ts.get("qui b c d e f g h").length).toBe(3) }) }) describe("TrieSearch::get(...) should work for array of phrases", function () { var ts = new TrieSearch("key", { min: 2 }), item1 = { key: "the quick brown fox" }, item2 = { key: "the quick brown" }, item3 = { key: "the quick fox" }, item4 = { key: "the fox" } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) it("get(['the brown', 'quick']) should return 3 entries", function () { expect(ts.get(["the brown", "quick"]).length).toBe(3) }) }) describe("TrieSearch::get(...) should work with cache==true", function () { var ts = new TrieSearch("key", { min: 2 }), item1 = { key: "the quick brown fox" }, item2 = { key: "the quick brown" }, item3 = { key: "the quick fox" }, item4 = { key: "the fox" } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) it("_get() should return identical array for the same request", function () { var f1 = ts._get("the brown"), f2 = ts._get("the brown") assert(f1 === f2, "was not the same array.") }) it("_get() should clear cache when clearCache() is called", function () { var f1 = ts._get("the brown") ts.clearCache() var f2 = ts._get("the brown") assert(f1 !== f2, "cache was not cleared.") }) }) describe("TrieSearch::get(...) should work with cache==true and maxCacheSize == X", function () { var ts = new TrieSearch("key", { min: 2, maxCacheSize: 2, }), item1 = { key: "the quick brown fox" }, item2 = { key: "the quick brown" }, item3 = { key: "the quick fox" }, item4 = { key: "the fox" } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) it("cache size should increment appropriately and cap at maxCacheSize.", function () { var f1 = ts._get("the brown") assert(ts.getCache.all.length == 1, "Cache size was not size 1.") ts._get("the quick") assert( ts.getCache.all.length == 2, "Cache size was not size 2 (this quick)." ) // Should cap at 2. ts._get("the fox") assert( ts.getCache.all.length == 2, "Cache size was not size 2 (the fox)." ) // Should remain at 2. var f2 = ts._get("the brown") // This should return different array. assert(f1 !== f2, "cache did not clear out old value!") }) }) describe("TrieSearch::get(...) should work with cache==false", function () { var ts = new TrieSearch("key", { min: 2, cache: false }), item1 = { key: "the quick brown fox" }, item2 = { key: "the quick brown" }, item3 = { key: "the quick fox" }, item4 = { key: "the fox" } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) it("_get() should return DIFFERENT array for the same request", function () { var f1 = ts._get("the brown"), f2 = ts._get("the brown") assert(f1 !== f2, "was not the same array.") }) }) describe("TrieSearch::get(...) should work for multiple keys and union the result with an indexField", function () { var ts = new TrieSearch(["key", "key2"], { min: 2, indexField: "ix" }), item1 = { key: "the quick brown fox", key2: "jumped", ix: 1 }, item2 = { key: "the quick brown", key2: "jumped", ix: 2 }, item3 = { key: "the quick fox", key2: "brown", ix: 3 }, item4 = { key: "the fox", key2: "quick brown", ix: 4 } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) it("get('the quick') should return all 4 entries", function () { expect(ts.get("the quick").length).toBe(4) }) it("get('the brown') should return all 4 entries", function () { expect(ts.get("the brown").length).toBe(4) }) it("get('the fox') should return 3 entries", function () { expect(ts.get("the fox").length).toBe(3) }) it("get('fox brown') should return 3 entries", function () { expect(ts.get("fox brown").length).toBe(3) }) it("get('brown fox') should return 3 entries", function () { expect(ts.get("brown fox").length).toBe(3) }) it("get('brown z') should return 4 entries", function () { expect(ts.get("brown z").length).toBe(4) }) it("get('br f') should return all entries", function () { expect(ts.get("br f").length).toBe(4) }) it("get('jum b c d e f g h') should return 2 entries, ignoring the shortness of all subsequent words", function () { expect(ts.get("jum b c d e f g h").length).toBe(2) }) }) describe("TrieSearch::get(...) should work for a deep key combined with a non-deep key", function () { var ts = new TrieSearch(["key", ["key2", "key3"]], { min: 2, indexField: "ix", }), item1 = { key: "the quick brown fox", key2: { key3: "jumped" }, ix: 1 }, item2 = { key: "the quick brown", key2: { key3: "jumped" }, ix: 2 }, item3 = { key: "the quick fox", key2: { key3: "brown" }, ix: 3 }, item4 = { key: "the fox", key2: { key3: "quick brown" }, ix: 4 } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) it("get('the quick') should return all 4 entries", function () { expect(ts.get("the quick").length).toBe(4) }) it("get('the brown') should return all 4 entries", function () { expect(ts.get("the brown").length).toBe(4) }) it("get('the fox') should return 3 entries", function () { expect(ts.get("the fox").length).toBe(3) }) it("get('fox brown') should return 3 entries", function () { expect(ts.get("fox brown").length).toBe(3) }) it("get('brown fox') should return 3 entries", function () { expect(ts.get("brown fox").length).toBe(3) }) it("get('brown z') should return 4 entries", function () { expect(ts.get("brown z").length).toBe(4) }) it("get('br f') should return all entries", function () { expect(ts.get("br f").length).toBe(4) }) it("get('jum b c d e f g h') should return 2 entries, ignoring the shortness of all subsequent words", function () { expect(ts.get("jum b c d e f g h").length).toBe(2) }) }) describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item with multiple subphrases", function () { var ts = new TrieSearch("key"), item = { key: "blah whatever yeah man" } ts.add(item) it("add('blah') should build map of nodes", function () { assert(ts.root["b"] !== undefined, "b does not exist") // @ts-ignore assert(ts.root["b"]["l"] !== undefined, "bl does not exist") // @ts-ignore assert(ts.root["b"]["l"]["a"] !== undefined, "bla does not exist") // @ts-ignore assert(ts.root["b"]["l"]["a"]["h"] !== undefined, "blah does not exist") }) it("get('blah') and get('whatever') for each subkey should work", function () { assert.equal(ts.get("b")[0], item) assert.equal(ts.get("bl")[0], item) assert.equal(ts.get("bla")[0], item) assert.equal(ts.get("blah")[0], item) assert.equal(ts.get("w")[0], item) assert.equal(ts.get("wh")[0], item) assert.equal(ts.get("whatever")[0], item) }) it("get('whatever') for each subkey should work", function () { assert.equal(ts.get("w")[0], item) assert.equal(ts.get("what")[0], item) assert.equal(ts.get("whatever")[0], item) }) it("get('yeah') for each subkey should work", function () { assert.equal(ts.get("y")[0], item) assert.equal(ts.get("ye")[0], item) assert.equal(ts.get("yea")[0], item) assert.equal(ts.get("yeah")[0], item) }) it("get('man') for each subkey should work", function () { assert.equal(ts.get("m")[0], item) assert.equal(ts.get("ma")[0], item) assert.equal(ts.get("man")[0], item) }) }) describe("TrieSearch::add(...) and TrieSearch::get(...) should work for multiple items", function () { var ts = new TrieSearch("key"), item1 = { key: "I am item1!" }, item2 = { key: "I am item2!" } ts.add(item1) ts.add(item2) it("add(item1) and add(item2) should build map of nodes", function () { assert(ts.root["i"] !== undefined, "I does not exist") // @ts-ignore assert(ts.root["a"]["m"] !== undefined, "am does not exist") assert( // @ts-ignore ts.root["i"]["t"]["e"]["m"]["1"] !== undefined, "item1 does not exist" ) assert( // @ts-ignore ts.root["i"]["t"]["e"]["m"]["2"] !== undefined, "item2 does not exist" ) }) it("get('i') should return 2 items", function () { assert(ts.get("i").length == 2, "did not return 2 items!") assert(ts.get("item").length == 2, "did not return 2 items!") }) it("get('item1') and get('item2') should return 1 item", function () { assert(ts.get("item1").length == 1, "did not return 1 item!") assert(ts.get("item2").length == 1, "did not return 1 item!") }) }) describe("TrieSearch::add(...) and TrieSearch::get(...) should work with options.min", function () { var ts = new TrieSearch("key", { min: 2 }), item1 = { key: "I am item1!" }, item2 = { key: "I am item2!" } ts.add(item1) ts.add(item2) it("add(item1) and add(item2) should build map of nodes", function () { assert(ts.root["i"] === undefined, "I should not exist!") assert(ts.root["am"] !== undefined, "am does not exist") assert( // @ts-ignore ts.root["it"]["e"]["m"]["1"] !== undefined, "item1 does not exist" ) assert( // @ts-ignore ts.root["it"]["e"]["m"]["2"] !== undefined, "item2 does not exist" ) }) it("get('i') should return 0 items", function () { assert(ts.get("i").length == 0, "did not return 0 items!") assert(ts.get("item").length == 2, "did not return 2 items!") }) it("get('item') should return 2 items", function () { assert(ts.get("item").length == 2, "did not return 2 items!") }) it("get('item1') and get('item2') should return 1 item", function () { assert(ts.get("item1").length == 1, "did not return 1 item!") assert(ts.get("item2").length == 1, "did not return 1 item!") }) }) describe("TrieSearch::add(...) and TrieSearch::get(...) should work with customKeys", function () { var ts = new TrieSearch("key", { min: 2 }), item1 = { customKey1: "I am item1!", customKey2: "123" }, item2 = { customKey1: "I am item2!", customKey2: "456" } ts.add(item1, ["customKey1"]) ts.add(item2, ["customKey1", "customKey2"]) it("add(item1) and add(item2) should build map of nodes", function () { assert(ts.root["i"] === undefined, "I should not exist!") assert(ts.root["am"] !== undefined, "am does not exist") assert( // @ts-ignore ts.root["it"]["e"]["m"]["1"] !== undefined, "item1 does not exist" ) assert( // @ts-ignore ts.root["it"]["e"]["m"]["2"] !== undefined, "item2 does not exist" ) assert( ts.root["12"] === undefined, "item1 should not exist on search for 123" ) assert( // @ts-ignore ts.root["45"]["6"] !== undefined, "item2 does not exist on search for 456" ) }) it("get('i') should return 0 items", function () { assert(ts.get("i").length == 0, "did not return 0 items!") assert(ts.get("item").length == 2, "did not return 2 items!") }) it("get('123') should return 0 items", function () { assert(ts.get("123").length == 0, "did not return 0 items!") }) it("get('45') should return 1 items", function () { assert(ts.get("456").length == 1, "did not return 0 items!") }) }) describe("TrieSearch::get(...) should work with a custom reducer and accumulator", function () { var ts = new TrieSearch("key", { min: 2, idFieldOrFunction: "key", }), item1 = { key: "I am red robin!" }, item2 = { key: "I am red cockatiel!" }, item3 = { key: "I am green cardinal!" }, item4 = { key: "I am green owl!" }, item5 = { key: "robin cockatiel cardinal owl!" } ts.add(item1) ts.add(item2) ts.add(item3) ts.add(item4) ts.add(item5) it("get('robin', [reducer])", function () { let result = ts.get( "robin", function (_accumulator, phrase, phraseMatches, trie) { assert( _accumulator === undefined, "accumulator should be undefined on first pass for first phrase" ) assert(phrase === "robin", "phrase was incorrect") assert( phraseMatches.length === 2, "phraseMatches length is incorrect" ) assert(phraseMatches[0] === item5, "wrong phraseMatches returned") assert(phraseMatches[1] === item1, "wrong phraseMatches returned") assert(trie === ts, "Trie did not equal ts") _accumulator = _accumulator || [] _accumulator.push(phraseMatches[1]) _accumulator.push(phraseMatches[0]) return _accumulator } ) assert( result.length === 2, "result has incorrect length: " + result.length ) assert(result[0] === item1, "result has incorrect items") assert(result[1] === item5, "result has incorrect items") }) it("get(['red', 'robin'], TrieSearch.UNION_REDUCER)", function () { let result = ts.get(["red", "robin"], TrieSearch.UNION_REDUCER) assert(result.length, "result has incorrect length: " + result.length) assert(result[0] === item1, "result has incorrect items") }) it("get(['green'], TrieSearch.UNION_REDUCER)", function () { let result = ts.get(["green"], TrieSearch.UNION_REDUCER) assert(result.length === 2, "result has incorrect length") assert(result[0] === item3, "result has incorrect items") assert(result[1] === item4, "result has incorrect items") }) it("get('green', TrieSearch.UNION_REDUCER)", function () { let result = ts.get("green", TrieSearch.UNION_REDUCER) assert(result.length === 2, "result has incorrect length") assert(result[0] === item3, "result has incorrect items") assert(result[1] === item4, "result has incorrect items") }) it("get('blue', TrieSearch.UNION_REDUCER)", function () { let result = ts.get("blue", TrieSearch.UNION_REDUCER) assert(result.length === 0, "result has incorrect length") }) it("get('am', TrieSearch.UNION_REDUCER)", function () { let result = ts.get("am", TrieSearch.UNION_REDUCER) assert(result.length === 4, "result has incorrect length") }) it("get(['owl', 'card', 'cock', 'rob'], TrieSearch.UNION_REDUCER)", function () { let result = ts.get( ["owl", "card", "cock", "rob"], TrieSearch.UNION_REDUCER ) assert( result.length === 1, "result has incorrect length: " + result.length ) }) it("get(['owl', 'card', 'cock', 'rob', 'fubar'], TrieSearch.UNION_REDUCER)", function () { let result = ts.get( ["owl", "card", "cock", "rob", "fubar"], TrieSearch.UNION_REDUCER ) assert( result.length === 0, "result has incorrect length: " + result.length ) }) }) describe("TrieSearch::get(...) with internationalization turned on (default) should work", function () { let as = "åäàáâã".split("") let es = "èéêë".split("") let is = "ìíîï".split("") let os = "òóôõö".split("") let us = "ùúûü".split("") let aes = "æ".split("") let ts = new TrieSearch<{ key: string; arr: string[] }>("key"), As_items = as.map((letter) => ({ key: letter, arr: as })), Es_items = es.map((letter) => ({ key: letter, arr: es })), Is_items = is.map((letter) => ({ key: letter, arr: is })), Os_items = os.map((letter) => ({ key: letter, arr: os })), Us_items = us.map((letter) => ({ key: letter, arr: us })), AEs_items = aes.map((letter) => ({ key: letter, arr: aes })) ts.addAll(As_items) ts.addAll(Es_items) ts.addAll(Is_items) ts.addAll(Os_items) ts.addAll(Us_items) ts.addAll(AEs_items) it(`Should return international items for "a" -> any of "${as}"`, function () { let items = ts.get("a") // Note this will include overlap with the ae! expect(items.length).toBe(7) items.forEach((i) => { assert(i.arr === as || i.arr === aes) }) }) it(`Should return international items for "e" -> any of "${es}"`, function () { let items = ts.get("e") expect(items.length).toBe(4) items.forEach((i) => { assert(i.arr === es) }) }) it(`Should return international items for "i" -> any of "${is}"`, function () { let items = ts.get("i") expect(items.length).toBe(4) items.forEach((i) => { assert(i.arr === is) }) }) it(`Should return international items for "o" -> any of "${os}"`, function () { let items = ts.get("o") expect(items.length).toBe(5) items.forEach((i) => { assert(i.arr === os) }) }) it(`Should return international items for "u" -> any of "${us}"`, function () { let items = ts.get("u") expect(items.length).toBe(4) items.forEach((i) => { assert(i.arr === us) }) }) it(`Should return international items for Swedish as an example with ''godis på sötdag är bra''`, function () { let swedishSentence = { key: "godis på sötdag är bra" } const tss = new TrieSearch<{ key: string }>("key") tss.add(swedishSentence) expect(tss.get("pa").length).toBe(1) expect(tss.get("sot").length).toBe(1) expect(tss.get("ar").length).toBe(1) }) }) describe("TrieSearch::map(...) works with RegEx with positive lookahead (e.g. split on capital letters)", function () { it("should not error", function () { try { var ts = new TrieSearch("key", { splitOnRegEx: /([.\-\s']|(?=[A-Z]))/, splitOnGetRegEx: false, }), item = { someValue: 12345 } ts.map("This IsSome.Phrase-Whatever", item) } catch (error) { assert(false, error ? error.toString() : "") } }) it("should match capital letter breaks", function () { var ts = new TrieSearch("key", { splitOnRegEx: /([.\-\s'_]|(?=[A-Z]))/, splitOnGetRegEx: false, insertFullUnsplitKey: true, }), item = { someValue: 12345 }, item2 = { someValue: 67890 } ts.map("It'sOnlyA_Flesh Wound", item) ts.map("WhatIsYourFavoriteColor", item2) assert(ts.get("It")[0] === item, "Did not properly match It") assert(ts.get("s")[0] === item, "Did not properly match s") assert(ts.get("Only")[0] === item, "Did not properly match Only") assert(ts.get("A")[0] === item, "Did not properly match A") assert(ts.get("Flesh")[0] === item, "Did not properly match Flesh") assert(ts.get("Wound")[0] === item, "Did not properly match Wound") assert( ts.get("It'sOnlyA_Flesh Wound")[0] === item, "Did not properly match full phrase It'sOnlyAFlesh Wound" ) assert(ts.get("What")[0] === item2, "Did not properly match What") assert(ts.get("Is")[0] === item2, "Did not properly match Is") assert(ts.get("Your")[0] === item2, "Did not properly match Your") assert(ts.get("Fav")[0] === item2, "Did not properly match Fav") assert(ts.get("Favorite")[0] === item2, "Did not properly match Favorite") assert(ts.get("Color")[0] === item2, "Did not properly match Color") assert( ts.get("WhatIsYourFavoriteColor")[0] === item2, "Did not properly match full phrase WhatIsYourFavoriteColor" ) }) it("should match capital letter breaks", function () { var ts = new TrieSearch("someValue", { splitOnRegEx: /([.\-\s'_]|(?=[A-Z]))/, splitOnGetRegEx: /[\s]/, }), item2 = { someValue: 67890 } ts.map("WhatIsYourFavoriteColor", item2) assert(ts.get("What Is")[0] === item2, "Did not properly match What") assert(ts.get("Color Favorite")[0] === item2, "Did not properly match Is") }) }) describe("TrieSearch:TrieSearch::get(...) should work with limits", function () { // NOTE: Cache is set to true since caching also needs to be tested var ts = new TrieSearch<{ _key_: string; value: string }>(["_key_"], { cache: true, }) ts.addAll([ { _key_: "a", value: "data", }, { _key_: "ab", value: "data", }, { _key_: "abc", value: "data", }, { _key_: "abcd", value: "data", }, { _key_: "abcde", value: "data", }, { _key_: "abcdef", value: "data", }, ]) it("Get with limits and get without limits should work properly", function () { var getWithoutLimit = ts.get("a") assert(getWithoutLimit.length === 6, "Expected 6 in without-limit get") var getWithLimitResp = ts.get("a", undefined, 4) assert(getWithLimitResp.length === 4, "Expected 4 in with-limit get") }) it("Failure case with limits should work properly", function () { var getWithLimit = ts.get("b", undefined, 4) assert(getWithLimit.length === 0, "Expected 0 in with-limit get") }) it("A bigger limit value than the actual amount of data must work properly", function () { var getWithLimit = ts.get("a", undefined, 100) assert(getWithLimit.length === 6, "Expected 6 in with-limit get") }) }) describe("usage test", () => { const ts = new TrieSearch<{ class: string; label: string }>("label", { min: 2, }) ts.add({ class: "http://dbpedia.org/ontology/AcademicConference", label: "Academic Conference", }) ts.add({ class: "http://dbpedia.org/ontology/AcademicJournal", label: "Academic Journal", }) ts.add({ class: "http://dbpedia.org/ontology/AcademicSubject", label: "Academic Subject", }) ts.add({ class: "http://dbpedia.org/ontology/Activity", label: "Activity", }) ts.add({ class: "http://dbpedia.org/ontology/Actor", label: "Actor" }) ts.add({ class: "http://dbpedia.org/ontology/AdministrativeRegion", label: "Administrative Region", }) ts.add({ class: "http://dbpedia.org/ontology/AdultActor", label: "Adult Actor", }) ts.add({ class: "http://dbpedia.org/ontology/Agent", label: "Agent" }) ts.add({ class: "http://dbpedia.org/ontology/Agglomeration", label: "Agglomeration", }) ts.add({ class: "http://dbpedia.org/ontology/Aircraft", label: "Aircraft", }) ts.add({ class: "http://dbpedia.org/ontology/Airline", label: "Airline", }) ts.add({ class: "http://dbpedia.org/ontology/Airport", label: "Airport", }) ts.add({ class: "http://dbpedia.org/ontology/Album", label: "Album" }) ts.add({ class: "http://dbpedia.org/ontology/Algorithm", label: "Algorithm", }) ts.add({ class: "http://dbpedia.org/ontology/Altitude", label: "Altitude", }) ts.add({ class: "http://dbpedia.org/ontology/AmateurBoxer", label: "Amateur Boxer", }) ts.add({ class: "http://dbpedia.org/ontology/Ambassador", label: "Ambassador", }) ts.add({ class: "http://dbpedia.org/ontology/AmericanFootballCoach", label: "American Football Coach", }) ts.add({ class: "http://dbpedia.org/ontology/AmericanFootballLeague", label: "American Football League", }) ts.add({ class: "http://dbpedia.org/ontology/AmericanFootballPlayer", label: "American Football Player", }) it("does not match below min char length", () => { expect(ts.get("a").length).toBe(0) }) it("prefix matches", () => { expect(ts.get("aca").length).toBe(3) expect(ts.get("amer").length).toBe(3) }) // matches on word it("matches on word", () => { expect(ts.get("football").length).toBe(3) }) }) const randomChar = () => Math.floor(Math.random() * 26) + 97 const randomWord = (length: number) => { const c: number[] = [] for (let i = 0; i < length; i++) c.push(randomChar()) return String.fromCharCode(...c) } describe("stress test", () => { const ts = new TrieSearch<{ key: string }>("key") const arr = new Array(1000000) .fill(null) .map((_) => ({ key: randomWord(3) })) ts.addAll(arr) it("adds 1 million entries", () => { const tss = new TrieSearch<{ key: string }>("key", { min: 3 }) const arr = new Array(10000) .fill(null) .map((_) => ({ key: randomWord(10) })) tss.addAll(arr) console.log( `${ (JSON.stringify(ts.root).length * 2) / 1024 / 1024 } MB (assuming no unicode escapes)` ) expect(Object.keys(ts.root).length > 0).toBeTruthy() }) it("gets matches on 1 char with 1 million entries", () => { expect(ts.get("a").length > 0).toBeTruthy() }) it("gets matches on 2 chars with 1 million entries", () => { expect(ts.get("aa").length > 0).toBeTruthy() }) }) describe("serialise json", () => { var tsOriginal = new TrieSearch(["key", "key2"], { min: 2, indexField: "ix", }), item1 = { key: "the quick brown fox", key2: "jumped", ix: 1 }, item2 = { key: "the quick brown", key2: "jumped", ix: 2 }, item3 = { key: "the quick fox", key2: "brown", ix: 3 }, item4 = { key: "the fox", key2: "quick brown", ix: 4 } tsOriginal.add(item1) tsOriginal.add(item2) tsOriginal.add(item3) tsOriginal.add(item4) const ts = TrieSearch.fromJson(tsOriginal.toJson()) it("get('the quick') should return all 4 entries", function () { expect(ts.get("the quick").length).toBe(4) }) it("get('the brown') should return all 4 entries", function () { expect(ts.get("the brown").length).toBe(4) }) it("get('the fox') should return 3 entries", function () { expect(ts.get("the fox").length).toBe(3) }) it("get('fox brown') should return 3 entries", function () { expect(ts.get("fox brown").length).toBe(3) }) it("get('brown fox') should return 3 entries", function () { expect(ts.get("brown fox").length).toBe(3) }) it("get('brown z') should return 4 entries", function () { expect(ts.get("brown z").length).toBe(4) }) it("get('br f') should return all entries", function () { expect(ts.get("br f").length).toBe(4) }) it("get('jum b c d e f g h') should return 2 entries, ignoring the shortness of all subsequent words", function () { expect(ts.get("jum b c d e f g h").length).toBe(2) }) }) })