feat(suggest): school autosuggest, and the rate-limit fix it needed first #127
@@ -2111,3 +2111,66 @@ test('the rankings page still orders by score, not name', async ({ page }) => {
|
||||
.filter((v: number | null) => v != null);
|
||||
expect(scores).toEqual([...scores].sort((a: number, b: number) => b - a));
|
||||
});
|
||||
|
||||
/*
|
||||
* School autosuggest (spec 2026-08-26).
|
||||
*/
|
||||
async function autosuggestIsOn(page: Page): Promise<boolean> {
|
||||
await page.goto('/');
|
||||
return (await page.getByRole('combobox').count()) > 0;
|
||||
}
|
||||
|
||||
test('the suggest endpoint answers from Typesense', async ({ page }) => {
|
||||
// Not flagged — the endpoint is live even while the UI is dark, so it can
|
||||
// be smoke-tested before the feature is switched on.
|
||||
const res = await page.request.get('/api/suggest?q=brecknock');
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const { suggestions } = await res.json();
|
||||
expect(Array.isArray(suggestions)).toBeTruthy();
|
||||
if (suggestions.length) {
|
||||
// Local authority is what tells two "St Mary's" apart.
|
||||
expect(suggestions[0]).toHaveProperty('school_name');
|
||||
expect(suggestions[0]).toHaveProperty('local_authority');
|
||||
}
|
||||
});
|
||||
|
||||
test('a one-character query is answered, not rejected', async ({ page }) => {
|
||||
// The keystroke path never errors on ordinary input.
|
||||
const res = await page.request.get('/api/suggest?q=b');
|
||||
expect(res.status()).toBe(200);
|
||||
expect((await res.json()).suggestions).toEqual([]);
|
||||
});
|
||||
|
||||
test('the suggest response is cacheable', async ({ page }) => {
|
||||
const res = await page.request.get('/api/suggest?q=brecknock');
|
||||
expect(res.headers()['cache-control'] ?? '').toContain('s-maxage');
|
||||
});
|
||||
|
||||
test('typing a school name suggests it, and choosing it opens that school', async ({ page }) => {
|
||||
test.skip(!(await autosuggestIsOn(page)),
|
||||
'the school_autosuggest flag is off in this environment');
|
||||
|
||||
// A school certain to exist in any environment with data.
|
||||
const { schools } = await (await page.request.get('/api/schools?page_size=1')).json();
|
||||
test.skip(!schools?.length, 'no schools in this environment');
|
||||
const name = schools[0].school_name as string;
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByRole('combobox').first().fill(name.slice(0, 12));
|
||||
const option = page.getByRole('option').first();
|
||||
await expect(option).toBeVisible();
|
||||
await option.click();
|
||||
await expect(page).toHaveURL(/\/school\/\d+/);
|
||||
});
|
||||
|
||||
test('with autosuggest off, the search box is a plain input', async ({ page }) => {
|
||||
test.skip(await autosuggestIsOn(page),
|
||||
'the school_autosuggest flag is on in this environment');
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByRole('combobox')).toHaveCount(0);
|
||||
// And the box still works: the existing search must be untouched.
|
||||
await page.getByPlaceholder(/School name or postcode/i).first().fill('abbey');
|
||||
await page.getByRole('button', { name: /Search/i }).first().click();
|
||||
await expect(page).toHaveURL(/search=abbey/);
|
||||
});
|
||||
Reference in new issue
Block a user