Add integration tests for server-side completions cancellation with Escape

This commit is contained in:
MareStare 2025-03-19 01:52:19 +00:00
parent 96d32d863d
commit 57781a1aa0
4 changed files with 95 additions and 55 deletions

View file

@ -6,7 +6,7 @@ import { fireEvent } from '@testing-library/dom';
import { assertNotNull } from '../../utils/assert';
import { TextInputElement } from '../input';
import store from '../../utils/store';
import { GetTagSuggestionsResponse } from 'autocomplete/client';
import { GetTagSuggestionsResponse, TagSuggestion } from 'autocomplete/client';
/**
* A reusable test environment for autocompletion tests. Note that it does no
@ -44,27 +44,30 @@ export class TestContext {
}
const url = new URL(request.url);
if (url.searchParams.get('term')?.toLowerCase() !== 'mar') {
const suggestions: GetTagSuggestionsResponse = { suggestions: [] };
return JSON.stringify(suggestions);
}
const term = url.searchParams.get('term');
const termLower = assertNotNull(term).toLowerCase();
const fakeSuggestions: TagSuggestion[] = [
{
alias: 'marvelous',
canonical: 'beautiful',
images: 30,
},
{
canonical: 'mare',
images: 20,
},
{
canonical: 'market',
images: 10,
},
];
const suggestions: GetTagSuggestionsResponse = {
suggestions: [
{
alias: 'marvelous',
canonical: 'beautiful',
images: 30,
},
{
canonical: 'mare',
images: 20,
},
{
canonical: 'market',
images: 10,
},
],
suggestions: fakeSuggestions.filter(suggestion =>
(suggestion.alias || suggestion.canonical).startsWith(termLower),
),
};
return JSON.stringify(suggestions);

View file

@ -1,34 +0,0 @@
import { init } from './context';
it('ignores the autocompletion results if Escape was pressed', async () => {
const ctx = await init();
await Promise.all([ctx.setInput('mar'), ctx.keyDown('Escape')]);
// The input must be empty because the user typed `mar` and pressed `Escape` right after that
ctx.expectUi().toMatchInlineSnapshot(`
{
"input": "mar<>",
"suggestions": [],
}
`);
// First request for the local autocomplete index.
expect(fetch).toHaveBeenCalledTimes(1);
await ctx.setInput('mar');
ctx.expectUi().toMatchInlineSnapshot(`
{
"input": "mar<>",
"suggestions": [
"marvelous → beautiful 30",
"mare 20",
"market 10",
],
}
`);
// Second request for the server-side suggestions.
expect(fetch).toHaveBeenCalledTimes(2);
});

View file

@ -0,0 +1,71 @@
import { init } from '../context';
it('ignores the autocompletion results if Escape was pressed', async () => {
const ctx = await init();
// First request for the local autocomplete index was done
expect(fetch).toHaveBeenCalledTimes(1);
await Promise.all([ctx.setInput('mar'), ctx.keyDown('Escape')]);
// The input must be empty because the user typed `mar` and pressed `Escape` right after that
ctx.expectUi().toMatchInlineSnapshot(`
{
"input": "mar<>",
"suggestions": [],
}
`);
// No new requests must've been sent because the input was debounced early
expect(fetch).toHaveBeenCalledTimes(1);
await ctx.setInput('mar');
ctx.expectUi().toMatchInlineSnapshot(`
{
"input": "mar<>",
"suggestions": [
"marvelous → beautiful 30",
"mare 20",
"market 10",
],
}
`);
// Second request for the server-side suggestions.
expect(fetch).toHaveBeenCalledTimes(2);
ctx.setInput('mare');
// After 300 milliseconds the debounce threshold is over, and the server-side
// completions request is issued.
vi.advanceTimersByTime(300);
await ctx.keyDown('Escape');
expect(fetch).toHaveBeenCalledTimes(3);
ctx.expectUi().toMatchInlineSnapshot(`
{
"input": "mare<>",
"suggestions": [],
}
`);
ctx.setInput('mare');
// Make sure that the user gets the results immediately without any debouncing (0 ms)
await vi.advanceTimersByTimeAsync(0);
ctx.expectUi().toMatchInlineSnapshot(`
{
"input": "mare<>",
"suggestions": [
"mare 20",
],
}
`);
// The results must come from the cache, so no new fetch calls must have been made
expect(fetch).toHaveBeenCalledTimes(3);
});

View file

@ -1,4 +1,4 @@
import { init } from './context';
import { init } from '../context';
it('requests server-side autocomplete if local autocomplete returns no results', async () => {
const ctx = await init();