2022-01-31 20:28:38 +01:00
|
|
|
import { LocalAutocompleter } from '../local-autocompleter';
|
|
|
|
import { promises } from 'fs';
|
|
|
|
import { join } from 'path';
|
|
|
|
import { TextDecoder } from 'util';
|
2025-03-17 23:05:51 +00:00
|
|
|
import { MatchPart } from 'utils/suggestions-model';
|
2022-01-31 20:28:38 +01:00
|
|
|
|
2025-03-14 23:29:52 +00:00
|
|
|
describe('LocalAutocompleter', () => {
|
2022-01-31 20:28:38 +01:00
|
|
|
let mockData: ArrayBuffer;
|
|
|
|
|
2024-07-03 20:27:59 -04:00
|
|
|
beforeAll(async () => {
|
2022-01-31 20:28:38 +01:00
|
|
|
const mockDataPath = join(__dirname, 'autocomplete-compiled-v2.bin');
|
|
|
|
/**
|
|
|
|
* Read pre-generated binary autocomplete data
|
|
|
|
*
|
|
|
|
* Contains the tags: safe (6), forest (3), flower (1), flowers -> flower, fog (1),
|
|
|
|
* force field (1), artist:test (1), explicit (0), grimdark (0),
|
|
|
|
* grotesque (0), questionable (0), semi-grimdark (0), suggestive (0)
|
|
|
|
*/
|
|
|
|
mockData = (await promises.readFile(mockDataPath, { encoding: null })).buffer;
|
|
|
|
|
|
|
|
// Polyfills for jsdom
|
|
|
|
global.TextDecoder = TextDecoder as unknown as typeof global.TextDecoder;
|
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(() => {
|
|
|
|
delete (global as Partial<typeof global>).TextEncoder;
|
|
|
|
delete (global as Partial<typeof global>).TextDecoder;
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('instantiation', () => {
|
2024-06-18 13:53:42 -04:00
|
|
|
it('should be constructible with compatible data', () => {
|
2022-01-31 20:28:38 +01:00
|
|
|
const result = new LocalAutocompleter(mockData);
|
|
|
|
expect(result).toBeInstanceOf(LocalAutocompleter);
|
|
|
|
});
|
|
|
|
|
2024-06-18 13:53:42 -04:00
|
|
|
it('should NOT be constructible with incompatible data', () => {
|
2022-01-31 20:28:38 +01:00
|
|
|
const versionDataOffset = 12;
|
|
|
|
const mockIncompatibleDataArray = new Array(versionDataOffset).fill(0);
|
|
|
|
// Set data version to 1
|
|
|
|
mockIncompatibleDataArray[mockIncompatibleDataArray.length - versionDataOffset] = 1;
|
|
|
|
const mockIncompatibleData = new Uint32Array(mockIncompatibleDataArray).buffer;
|
|
|
|
|
|
|
|
expect(() => new LocalAutocompleter(mockIncompatibleData)).toThrow('Incompatible autocomplete format version');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2025-03-14 23:29:52 +00:00
|
|
|
describe('matchPrefix', () => {
|
2024-06-18 13:53:42 -04:00
|
|
|
const termStem = ['f', 'o'].join('');
|
|
|
|
|
2025-03-14 23:29:52 +00:00
|
|
|
function expectLocalAutocomplete(term: string, topK = 5) {
|
|
|
|
const localAutocomplete = new LocalAutocompleter(mockData);
|
|
|
|
const results = localAutocomplete.matchPrefix(term, topK);
|
2025-03-17 23:05:51 +00:00
|
|
|
|
|
|
|
const joinMatchParts = (parts: MatchPart[]) =>
|
|
|
|
parts.map(part => (typeof part === 'string' ? part : `{${part.matched}}`)).join('');
|
|
|
|
|
2025-03-14 23:29:52 +00:00
|
|
|
const actual = results.map(result => {
|
2025-03-17 23:05:51 +00:00
|
|
|
if (result.alias) {
|
|
|
|
return `${joinMatchParts(result.alias)} -> ${result.canonical} (${result.images})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return `${joinMatchParts(result.canonical)} (${result.images})`;
|
2025-03-14 23:29:52 +00:00
|
|
|
});
|
2022-01-31 20:28:38 +01:00
|
|
|
|
2025-03-14 23:29:52 +00:00
|
|
|
return expect(actual);
|
|
|
|
}
|
2022-01-31 20:28:38 +01:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
window.booru.hiddenTagList = [];
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return suggestions for exact tag name match', () => {
|
2025-03-14 23:29:52 +00:00
|
|
|
expectLocalAutocomplete('safe').toMatchInlineSnapshot(`
|
|
|
|
[
|
2025-03-17 23:05:51 +00:00
|
|
|
"{safe} (6)",
|
2025-03-14 23:29:52 +00:00
|
|
|
]
|
|
|
|
`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return suggestion for an alias', () => {
|
|
|
|
expectLocalAutocomplete('flowers').toMatchInlineSnapshot(`
|
|
|
|
[
|
2025-03-17 23:05:51 +00:00
|
|
|
"{flowers} -> flower (1)",
|
2025-03-14 23:29:52 +00:00
|
|
|
]
|
|
|
|
`);
|
2022-01-31 20:28:38 +01:00
|
|
|
});
|
|
|
|
|
2025-03-14 23:29:52 +00:00
|
|
|
it('should prefer canonical tag over an alias when both match', () => {
|
|
|
|
expectLocalAutocomplete('flo').toMatchInlineSnapshot(`
|
|
|
|
[
|
2025-03-17 23:05:51 +00:00
|
|
|
"{flo}wer (1)",
|
2025-03-14 23:29:52 +00:00
|
|
|
]
|
|
|
|
`);
|
2022-01-31 20:28:38 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return suggestions sorted by image count', () => {
|
2025-03-14 23:29:52 +00:00
|
|
|
expectLocalAutocomplete(termStem).toMatchInlineSnapshot(`
|
|
|
|
[
|
2025-03-17 23:05:51 +00:00
|
|
|
"{fo}rest (3)",
|
|
|
|
"{fo}g (1)",
|
|
|
|
"{fo}rce field (1)",
|
2025-03-14 23:29:52 +00:00
|
|
|
]
|
|
|
|
`);
|
2022-01-31 20:28:38 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return namespaced suggestions without including namespace', () => {
|
2025-03-14 23:29:52 +00:00
|
|
|
expectLocalAutocomplete('test').toMatchInlineSnapshot(`
|
|
|
|
[
|
2025-03-17 23:05:51 +00:00
|
|
|
"artist:{test} (1)",
|
2025-03-14 23:29:52 +00:00
|
|
|
]
|
|
|
|
`);
|
2022-01-31 20:28:38 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return only the required number of suggestions', () => {
|
2025-03-14 23:29:52 +00:00
|
|
|
expectLocalAutocomplete(termStem, 1).toMatchInlineSnapshot(`
|
|
|
|
[
|
2025-03-17 23:05:51 +00:00
|
|
|
"{fo}rest (3)",
|
2025-03-14 23:29:52 +00:00
|
|
|
]
|
|
|
|
`);
|
2022-01-31 20:28:38 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should NOT return suggestions associated with hidden tags', () => {
|
|
|
|
window.booru.hiddenTagList = [1];
|
2025-03-14 23:29:52 +00:00
|
|
|
expectLocalAutocomplete(termStem).toMatchInlineSnapshot(`[]`);
|
2022-01-31 20:28:38 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return empty array for empty prefix', () => {
|
2025-03-14 23:29:52 +00:00
|
|
|
expectLocalAutocomplete('').toMatchInlineSnapshot(`[]`);
|
2022-01-31 20:28:38 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|