Skip to content

Commit bfd89aa

Browse files
committed
test: add case for no stale result leak on refocus
Removes need for inline comment. == npm run size == transerSize (compressed): * Before: JS 2209 * After: JS 2175 (-34 B, -1.5%) == npm test == ``` File | % Stmts | % Branch | % Funcs | % Lines | ----------------------|---------|----------|---------|---------| before 94.33 | 81.94 | 90.47 | 95.83 | after | 94.28 | 84.28 | 90.47 | 95.78 | ```
1 parent 250e633 commit bfd89aa

File tree

5 files changed

+52
-11
lines changed

5 files changed

+52
-11
lines changed

CONTRIBUTING.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
# Contribute to typesense-minibar
22

3-
## How it works
4-
5-
* Cut the mustard using `type="module"`.
6-
73
## Internal API
84

95
```js

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ Refer to [Style API](./API-Style.md) for the CSS variable names and selectors.
125125

126126
| typesense-minibar | typesense-server | typesense-docsearch-scraper
127127
|--|--|--
128-
| 1.0.x | >= 0.24 | 0.6.0.rc1 <!-- adds "group_by=url_without_anchor" -->
128+
| 1.0.x | >= 0.24 | >= 0.6.0.rc1 <!-- adds "group_by=url_without_anchor" --> (Tested upto: 0.9.1)
129129

130130
### Browser support
131131

@@ -166,6 +166,12 @@ Notes:
166166
* [Firefox 48 last to support OS X 10.6-10.8](https://www.mozilla.org/en-US/firefox/48.0/releasenotes/)
167167
* [Firefox 78 last to support OS X 10.9-10.11](https://www.mozilla.org/en-US/firefox/78.0/releasenotes/)
168168

169+
## FAQ: Troubleshooting
170+
171+
* How does this prevent JavaScript errors in older browsers? What about ES5?
172+
173+
If you load typesense-minibar.js standalone, make sure you have the `type="module"` attribute on the `<script>` tag. Scripts with this type are naturally ignored by older browsers. The element works fine **without JavaScript**, following the principles of [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement). This technique is analogous to "[cutting the mustard](https://responsivenews.tumblr.com/post/18948466399/cutting-the-mustard)".
174+
169175
## Feedback
170176

171177
For questions, bug reports, or feature requests, use the [Issue tracker](https://github.com/jquery/typesense-minibar/issues).

demo/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</header>
2121
<header>
2222
<span class="demo-logo"></span>
23-
<!-- data-group=true and right-aligned -->
23+
<!-- With data-group=true and right aligned. -->
2424
<form role="search" class="tsmb-form demo-right-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
2525
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
2626
<input type="hidden" name="sites" value="jquery.com">

test/test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,47 @@ QUnit.module('typesense-minibar', hooks => {
363363
assert.false(listbox.hidden, 'listbox re-opened');
364364
});
365365

366+
QUnit.test('listbox [no stale result leak on refocus]', async assert => {
367+
const form = parseHTML('<form><input type="search"></form>');
368+
const input = form.firstChild;
369+
bar = tsminibar(form);
370+
const listbox = form.querySelector('[role=listbox]');
371+
372+
mockFetchResponse = API_RESP_FULL_MATCH_SOMETHING;
373+
input.value = 'something';
374+
await expectRender(form, () => {
375+
simulate(input, 'input');
376+
});
377+
assert.false(listbox.hidden, 'listbox not hidden');
378+
assert.equal(listbox.querySelector('mark').outerHTML, '<mark>something</mark>', 'snippet');
379+
380+
// When backspacing and making the input empty, we hide the listbox.
381+
// Given that empty string isn't a valid query, this means no results
382+
// are rendered. If we later re-focus the input field, we should not leak
383+
// results from the last query (i.e. before the last backspace), as those
384+
// are now unrelated.
385+
mockFetchResponse = null;
386+
input.value = '';
387+
await expectRender(form, () => {
388+
simulate(input, 'input');
389+
});
390+
assert.true(listbox.hidden, 'listbox hidden');
391+
392+
mockFetchResponse = null;
393+
simulate(document.body, 'click', { bubbles: true });
394+
assert.true(listbox.hidden, 'listbox remains hidden (document)');
395+
396+
simulate(input, 'click', { bubbles: true });
397+
assert.true(listbox.hidden, 'listbox remains hidden (refocus)');
398+
// It would be fine if render() was more lazy and left innerHTML populated
399+
// when rendering a close() that sets `state.open = false`. It only matters
400+
// that state.hits is cleared and that any future render() call will not make
401+
// the element visible, unless it also replaces innerHTML then.
402+
// But.. for simplicity, right now, we do clear the HTML unconditonally,
403+
// so let's assert that, and detect potentially unintended changes in the future.
404+
assert.equal(listbox.querySelector('mark')?.textContent, null, 'stale snippet gone');
405+
});
406+
366407
QUnit.test('listbox [arrow key cursor]', async assert => {
367408
const form = parseHTML('<form><input type="search"></form>');
368409
const input = form.firstChild;

typesense-minibar.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ globalThis.tsminibar = function tsminibar (form) {
4242
input.addEventListener('input', async function () {
4343
const query = state.query = input.value;
4444
if (!query) {
45-
state.hits = []; // don't leak old hits on focus
45+
state.hits = [];
4646
state.cursor = -1;
4747
return close();
4848
}
@@ -72,7 +72,7 @@ globalThis.tsminibar = function tsminibar (form) {
7272
}
7373
});
7474
form.addEventListener('submit', function (e) {
75-
e.preventDefault(); // disable fallback
75+
e.preventDefault();
7676
});
7777
form.insertAdjacentHTML('beforeend', '<svg viewBox="0 0 12 12" width="20" height="20" aria-hidden="true" class="tsmb-icon-close" style="display: none;"><path d="M9 3L3 9M3 3L9 9"/></svg>');
7878
form.querySelector('.tsmb-icon-close').addEventListener('click', function () {
@@ -151,9 +151,7 @@ globalThis.tsminibar = function tsminibar (form) {
151151
function render () {
152152
listbox.hidden = !state.open;
153153
form.classList.toggle('tsmb-form--open', state.open);
154-
if (state.open) {
155-
listbox.innerHTML = (state.hits.map((hit, i) => `<div role="option"${i === state.cursor ? ' aria-selected="true"' : ''}>${hit.lvl0 ? `<div class="tsmb-suggestion_group">${hit.lvl0}</div>` : ''}<a href="${hit.url}" tabindex="-1"><div class="tsmb-suggestion_title">${hit.title}</div><div class="tsmb-suggestion_content">${hit.content}</div></a></div>`).join('') || `<div class="tsmb-empty">${noResults.replace('{}', escape(state.query))}</div>`) + (form.dataset.foot ? '<a href="https://typesense.org" class="tsmb-foot" title="Search by Typesense"></a>' : '');
156-
}
154+
listbox.innerHTML = (state.hits.map((hit, i) => `<div role="option"${i === state.cursor ? ' aria-selected="true"' : ''}>${hit.lvl0 ? `<div class="tsmb-suggestion_group">${hit.lvl0}</div>` : ''}<a href="${hit.url}" tabindex="-1"><div class="tsmb-suggestion_title">${hit.title}</div><div class="tsmb-suggestion_content">${hit.content}</div></a></div>`).join('') || `<div class="tsmb-empty">${noResults.replace('{}', escape(state.query))}</div>`) + (form.dataset.foot ? '<a href="https://typesense.org" class="tsmb-foot" title="Search by Typesense"></a>' : '');
157155
}
158156

159157
function moveCursor (offset) {

0 commit comments

Comments
 (0)