Skip to content

Commit 3998b81

Browse files
authored
fix: toolbox search final trigram (#2648)
* fix: include final trigram in search matching add regression test asserting long queries require their trailing trigram adjust trigram loop to cover the last trigram so partial matches stop passing * test: expand block search coverage cover trigram edge cases, underscore normalization, dropdown alt text, and disambiguation of similar blocks * test: verify block type underscore normalization * test: remove invalid image * chore: format file
1 parent a2e62db commit 3998b81

2 files changed

Lines changed: 124 additions & 1 deletion

File tree

plugins/toolbox-search/src/block_searcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export class BlockSearcher {
113113
if (normalizedInput.length <= 3) return [normalizedInput];
114114

115115
const trigrams: string[] = [];
116-
for (let start = 0; start < normalizedInput.length - 3; start++) {
116+
for (let start = 0; start <= normalizedInput.length - 3; start++) {
117117
trigrams.push(normalizedInput.substring(start, start + 3));
118118
}
119119

plugins/toolbox-search/test/tests.mocha.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ suite('Toolbox search', () => {
1515
});
1616

1717
suite('BlockSearcher', () => {
18+
test('generateTrigrams handles empty and short input', () => {
19+
const searcher = new BlockSearcher();
20+
const generateTrigrams = searcher.generateTrigrams.bind(searcher);
21+
22+
assert.deepEqual(generateTrigrams(''), []);
23+
assert.deepEqual(generateTrigrams('a'), ['a']);
24+
assert.deepEqual(generateTrigrams('abc'), ['abc']);
25+
});
26+
1827
test('indexes the default value of dropdown fields', () => {
1928
const searcher = new BlockSearcher();
2029
const blocks = [
@@ -57,6 +66,120 @@ suite('BlockSearcher', () => {
5766
assert.sameMembers(ransomNoteMatches, [listCreateWithBlock]);
5867
});
5968

69+
test('requires the final trigram when matching longer queries', () => {
70+
const searcher = new BlockSearcher();
71+
const mathConstrainBlock = {
72+
kind: 'block',
73+
type: 'math_constrain',
74+
};
75+
searcher.indexBlocks([mathConstrainBlock]);
76+
77+
const matches = searcher.blockTypesMatching('conso');
78+
79+
assert.notInclude(
80+
matches,
81+
mathConstrainBlock,
82+
'query missing trailing trigram should not match',
83+
);
84+
});
85+
86+
test('normalizes underscores in block types to spaces', () => {
87+
if (!Blockly.Blocks['searcher_underscore_block']) {
88+
Blockly.defineBlocksWithJsonArray([
89+
{
90+
type: 'searcher_underscore_block',
91+
message0: 'custom block with underscore',
92+
},
93+
]);
94+
}
95+
96+
const searcher = new BlockSearcher();
97+
const blockInfo = {
98+
kind: 'block',
99+
type: 'searcher_underscore_block',
100+
};
101+
searcher.indexBlocks([blockInfo]);
102+
103+
assert.sameMembers(
104+
searcher.blockTypesMatching('searcher underscore block'),
105+
[blockInfo],
106+
);
107+
assert.isEmpty(searcher.blockTypesMatching('searcher_underscore_block'));
108+
});
109+
110+
test('longer queries disambiguate similar blocks', () => {
111+
if (!Blockly.Blocks['searcher_charlie']) {
112+
Blockly.defineBlocksWithJsonArray([
113+
{
114+
type: 'searcher_charlie',
115+
message0: 'alpha bravo charlie',
116+
},
117+
{
118+
type: 'searcher_delta',
119+
message0: 'alpha bravo delta',
120+
},
121+
]);
122+
}
123+
124+
const searcher = new BlockSearcher();
125+
const blockA = {kind: 'block', type: 'searcher_charlie'};
126+
const blockB = {kind: 'block', type: 'searcher_delta'};
127+
128+
searcher.indexBlocks([blockA, blockB]);
129+
130+
const broadQueryMatches = searcher.blockTypesMatching('alpha bravo');
131+
assert.sameMembers(broadQueryMatches, [blockA, blockB]);
132+
133+
const specificQueryMatches = searcher.blockTypesMatching(
134+
'alpha bravo charlie',
135+
);
136+
assert.sameMembers(specificQueryMatches, [blockA]);
137+
});
138+
139+
test('indexes dropdown alt text options', () => {
140+
if (!Blockly.Blocks['searcher_dropdown_alt']) {
141+
Blockly.defineBlocksWithJsonArray([
142+
{
143+
type: 'searcher_dropdown_alt',
144+
message0: 'weather %1',
145+
args0: [
146+
{
147+
type: 'field_dropdown',
148+
name: 'WEATHER',
149+
options: [
150+
[
151+
{
152+
src: '',
153+
width: 1,
154+
height: 1,
155+
alt: 'Sunny',
156+
},
157+
'SUN',
158+
],
159+
[
160+
{
161+
src: '',
162+
width: 1,
163+
height: 1,
164+
alt: 'Cloudy',
165+
},
166+
'CLOUD',
167+
],
168+
],
169+
},
170+
],
171+
},
172+
]);
173+
}
174+
175+
const searcher = new BlockSearcher();
176+
const blockInfo = {kind: 'block', type: 'searcher_dropdown_alt'};
177+
searcher.indexBlocks([blockInfo]);
178+
179+
assert.sameMembers(searcher.blockTypesMatching('sunny'), [blockInfo]);
180+
assert.sameMembers(searcher.blockTypesMatching('cloudy'), [blockInfo]);
181+
});
182+
60183
test('returns an empty list when no matches are found', () => {
61184
const searcher = new BlockSearcher();
62185
assert.isEmpty(searcher.blockTypesMatching('abc123'));

0 commit comments

Comments
 (0)