Tofu/tests/text/rankRecipeSuggestions.test.js

83 lines
3.6 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { rankRecipeSuggestions } from '#tofu/tools/text/rankRecipeSuggestions.js'
// A plausible household library, so the reference case proves a ranking and not a lucky draw.
const RECIPES = [
{ id: 'gratin-dauphinois', name: 'Gratin dauphinois', ingredientNames: ['pommes de terre', 'creme', 'ail'] },
{ id: 'poulet-roti', name: 'Poulet rôti', ingredientNames: ['poulet', 'thym'] },
{ id: 'salade-de-lentilles', name: 'Salade de lentilles', ingredientNames: ['lentilles', 'echalote'] },
{ id: 'gnocchis-a-la-sauge', name: 'Gnocchis à la sauge', ingredientNames: ['gnocchis', 'beurre', 'sauge', 'parmesan'] },
{ id: 'curry-de-pois-chiches', name: 'Curry de pois chiches', ingredientNames: ['pois chiches', 'lait de coco'] },
{ id: 'crepes', name: 'Crêpes', ingredientNames: ['farine', 'oeufs', 'lait'] },
{ id: 'soupe-de-potiron', name: 'Soupe de potiron', ingredientNames: ['potiron', 'oignon'] }
]
function idsFor(query, limit) {
return rankRecipeSuggestions(query, RECIPES, limit).map((recipe) => recipe.id)
}
test('brings "Gnocchis à la sauge" first for the misspelled query "knoci"', () => {
const ids = idsFor('knoci')
assert.equal(ids[0], 'gnocchis-a-la-sauge', `got ${JSON.stringify(ids)}`)
})
test('ignores the accents of the recipe name', () => {
assert.equal(idsFor('crepes')[0], 'crepes')
assert.equal(idsFor('poulet roti')[0], 'poulet-roti')
})
test('ignores the case of the query', () => {
assert.equal(idsFor('GNOCCHIS')[0], 'gnocchis-a-la-sauge')
assert.equal(idsFor('GnOcChIs À La SaUgE')[0], 'gnocchis-a-la-sauge')
})
test('still matches when the query is longer than the recipe name', () => {
assert.equal(idsFor('gnocchis a la sauge maison')[0], 'gnocchis-a-la-sauge')
})
test('returns nothing for an empty or blank query', () => {
assert.deepEqual(rankRecipeSuggestions('', RECIPES), [])
assert.deepEqual(rankRecipeSuggestions(' ', RECIPES), [])
assert.deepEqual(rankRecipeSuggestions(null, RECIPES), [])
})
test('returns nothing when no recipe reaches the threshold', () => {
assert.deepEqual(rankRecipeSuggestions('zzzqwx', RECIPES), [])
})
test('returns nothing when there is no recipe at all', () => {
assert.deepEqual(rankRecipeSuggestions('gnocchis', []), [])
assert.deepEqual(rankRecipeSuggestions('gnocchis', null), [])
})
test('sorts equal scores by name and honours the limit', () => {
assert.deepEqual(idsFor('de'), ['curry-de-pois-chiches', 'salade-de-lentilles', 'soupe-de-potiron'])
assert.deepEqual(idsFor('de', 2), ['curry-de-pois-chiches', 'salade-de-lentilles'])
assert.deepEqual(idsFor('de', 0), [])
})
test('returns the recipe objects themselves, untouched', () => {
const [first] = rankRecipeSuggestions('gnocchis', RECIPES)
assert.equal(first, RECIPES[3])
})
test('a literal match hides the approximate ones', () => {
// « gratin » is spelled out in « Gratin dauphinois »; « Ratatouille » only
// shares a couple of bigrams and would be noise next to it.
const recipes = [
{ name: 'Gratin dauphinois' },
{ name: 'Ratatouille' },
{ name: 'Quiche lorraine' },
{ name: 'Poulet rôti aux herbes' },
]
const suggestions = rankRecipeSuggestions('gratin', recipes)
assert.deepEqual(suggestions.map((recipe) => recipe.name), ['Gratin dauphinois'])
})
test('a misspelled query still gets its approximate alternatives', () => {
const recipes = [{ name: 'Gnocchis à la sauge' }, { name: 'Gratin dauphinois' }]
const suggestions = rankRecipeSuggestions('knoci', recipes)
assert.equal(suggestions[0].name, 'Gnocchis à la sauge')
assert.ok(suggestions.length >= 1, 'an approximate query keeps offering candidates')
})