1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {FuzzyFinder, fuzzyMatch} from './fuzzy'; 16 17describe('FuzzyFinder', () => { 18 const items = ['aaa', 'aba', 'zzz', 'c z d z e', 'CAPS', 'ababc']; 19 const finder = new FuzzyFinder(items, (x) => x); 20 21 it('finds all for empty search term', () => { 22 const result = finder.find(''); 23 // Expect all results are returned in original order. 24 expect(result).toEqual([ 25 {item: 'aaa', segments: [{matching: false, value: 'aaa'}]}, 26 {item: 'aba', segments: [{matching: false, value: 'aba'}]}, 27 {item: 'zzz', segments: [{matching: false, value: 'zzz'}]}, 28 {item: 'c z d z e', segments: [{matching: false, value: 'c z d z e'}]}, 29 {item: 'CAPS', segments: [{matching: false, value: 'CAPS'}]}, 30 {item: 'ababc', segments: [{matching: false, value: 'ababc'}]}, 31 ]); 32 }); 33 34 it('finds exact match', () => { 35 const result = finder.find('aaa'); 36 expect(result).toEqual( 37 expect.arrayContaining([ 38 {item: 'aaa', segments: [{matching: true, value: 'aaa'}]}, 39 ]), 40 ); 41 }); 42 43 it('finds approx matches', () => { 44 const result = finder.find('aa'); 45 // Allow finding results in any order. 46 expect(result).toEqual( 47 expect.arrayContaining([ 48 { 49 item: 'aaa', 50 // Either |aa|a or a|aa| is valid. 51 segments: expect.arrayContaining([ 52 {matching: true, value: 'aa'}, 53 {matching: false, value: 'a'}, 54 ]), 55 }, 56 { 57 item: 'aba', 58 segments: [ 59 {matching: true, value: 'a'}, 60 {matching: false, value: 'b'}, 61 {matching: true, value: 'a'}, 62 ], 63 }, 64 ]), 65 ); 66 }); 67 68 it('does not find completely unrelated items', () => { 69 // |zzz| looks nothing like |aa| and should not be returned. 70 const result = finder.find('aa'); 71 expect(result).not.toEqual( 72 expect.arrayContaining([expect.objectContaining({item: 'zzz'})]), 73 ); 74 }); 75 76 it('finds non-consecutive matches', () => { 77 const result = finder.find('cde'); 78 expect(result).toEqual( 79 expect.arrayContaining([ 80 { 81 item: 'c z d z e', 82 segments: [ 83 {matching: true, value: 'c'}, 84 {matching: false, value: ' z '}, 85 {matching: true, value: 'd'}, 86 {matching: false, value: ' z '}, 87 {matching: true, value: 'e'}, 88 ], 89 }, 90 ]), 91 ); 92 }); 93 94 it('finds caps match when search term is in lower case', () => { 95 const result = finder.find('caps'); 96 expect(result).toEqual( 97 expect.arrayContaining([ 98 {item: 'CAPS', segments: [{matching: true, value: 'CAPS'}]}, 99 ]), 100 ); 101 }); 102 103 it('finds match with false start', () => { 104 const result = finder.find('abc'); 105 expect(result).toEqual( 106 expect.arrayContaining([ 107 { 108 item: 'ababc', 109 segments: [ 110 {matching: true, value: 'ab'}, 111 {matching: false, value: 'ab'}, 112 {matching: true, value: 'c'}, 113 ], 114 }, 115 ]), 116 ); 117 }); 118}); 119 120test('fuzzyMatch', () => { 121 expect(fuzzyMatch('foo bar baz', 'foo')).toEqual({ 122 matches: true, 123 segments: [ 124 {matching: true, value: 'foo'}, 125 {matching: false, value: ' bar baz'}, 126 ], 127 }); 128 129 expect(fuzzyMatch('foo bar baz', 'qux')).toEqual({ 130 matches: false, 131 segments: [], 132 }); 133 134 expect(fuzzyMatch('bar baz', 'foo', 'bar')).toEqual({ 135 matches: true, 136 segments: [ 137 {matching: true, value: 'bar'}, 138 {matching: false, value: ' baz'}, 139 ], 140 }); 141}); 142