From a5645b716f1be9cc6a946b5b2223f56bc59c317e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 15 Dec 2020 14:01:59 +0100 Subject: [PATCH 01/14] feat(js): change renderer implementation to virtual DOM --- bundlesize.config.json | 2 +- examples/js/app.ts | 39 ----- examples/js/app.tsx | 96 +++++++++++++ examples/js/index.html | 2 +- examples/js/package.json | 2 + packages/autocomplete-js/package.json | 3 +- .../src/__tests__/autocomplete.test.ts | 2 + packages/autocomplete-js/src/autocomplete.ts | 28 ++-- .../autocomplete-js/src/components/Element.ts | 20 --- .../autocomplete-js/src/components/Input.ts | 15 +- .../autocomplete-js/src/components/index.ts | 1 - .../src/createAutocompleteDom.ts | 61 ++++---- .../autocomplete-js/src/createDomElement.ts | 16 +++ .../autocomplete-js/src/defaultRenderer.ts | 3 - .../autocomplete-js/src/getDefaultOptions.ts | 42 ++++-- packages/autocomplete-js/src/render.ts | 133 +++++++++--------- .../autocomplete-js/src/renderTemplate.ts | 25 ---- .../src/types/AutocompleteOptions.ts | 39 ++--- .../src/types/AutocompleteRenderer.ts | 20 +++ .../src/types/AutocompleteSource.ts | 8 +- packages/autocomplete-js/src/types/index.ts | 1 + .../src/getTemplates.ts | 95 +++++++++---- .../src/icons/index.ts | 2 - .../src/icons/searchIcon.ts | 13 -- .../src/icons/tapAheadIcon.ts | 11 -- .../src/getTemplates.ts | 96 +++++++++---- .../src/icons/index.ts | 2 - .../src/icons/recentIcon.ts | 13 -- .../src/icons/resetIcon.ts | 13 -- packages/website/docs/autocomplete-js.md | 26 +++- yarn.lock | 5 + 31 files changed, 470 insertions(+), 364 deletions(-) delete mode 100644 examples/js/app.ts create mode 100644 examples/js/app.tsx delete mode 100644 packages/autocomplete-js/src/components/Element.ts create mode 100644 packages/autocomplete-js/src/createDomElement.ts delete mode 100644 packages/autocomplete-js/src/defaultRenderer.ts delete mode 100644 packages/autocomplete-js/src/renderTemplate.ts create mode 100644 packages/autocomplete-js/src/types/AutocompleteRenderer.ts delete mode 100644 packages/autocomplete-plugin-query-suggestions/src/icons/index.ts delete mode 100644 packages/autocomplete-plugin-query-suggestions/src/icons/searchIcon.ts delete mode 100644 packages/autocomplete-plugin-query-suggestions/src/icons/tapAheadIcon.ts delete mode 100644 packages/autocomplete-plugin-recent-searches/src/icons/index.ts delete mode 100644 packages/autocomplete-plugin-recent-searches/src/icons/recentIcon.ts delete mode 100644 packages/autocomplete-plugin-recent-searches/src/icons/resetIcon.ts diff --git a/bundlesize.config.json b/bundlesize.config.json index 6b011793d..8ae7c08a9 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -6,7 +6,7 @@ }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", - "maxSize": "10 kB" + "maxSize": "13.25 kB" }, { "path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js", diff --git a/examples/js/app.ts b/examples/js/app.ts deleted file mode 100644 index 82e36304a..000000000 --- a/examples/js/app.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { autocomplete } from '@algolia/autocomplete-js'; -import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; -import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; -import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; -import algoliasearch from 'algoliasearch'; -import insightsClient from 'search-insights'; - -import '@algolia/autocomplete-theme-classic'; - -const appId = 'latency'; -const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; -const searchClient = algoliasearch(appId, apiKey); -insightsClient('init', { appId, apiKey }); - -const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); -const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ - key: 'search', - limit: 3, -}); -const querySuggestionsPlugin = createQuerySuggestionsPlugin({ - searchClient, - indexName: 'instant_search_demo_query_suggestions', - getSearchParams() { - return recentSearchesPlugin.data.getAlgoliaSearchParams({ - clickAnalytics: true, - }); - }, -}); - -autocomplete({ - container: '#autocomplete', - placeholder: 'Search', - openOnFocus: true, - plugins: [ - algoliaInsightsPlugin, - recentSearchesPlugin, - querySuggestionsPlugin, - ], -}); diff --git a/examples/js/app.tsx b/examples/js/app.tsx new file mode 100644 index 000000000..fff96d711 --- /dev/null +++ b/examples/js/app.tsx @@ -0,0 +1,96 @@ +/** @jsx h */ +import { + autocomplete, + getAlgoliaHits, + highlightHit, +} from '@algolia/autocomplete-js'; +import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import { Hit } from '@algolia/client-search'; +import algoliasearch from 'algoliasearch'; +import { h } from 'preact'; +import insightsClient from 'search-insights'; + +import '@algolia/autocomplete-theme-classic'; + +type Product = { name: string; image: string }; +type ProductHit = Hit; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); +insightsClient('init', { appId, apiKey }); + +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'search', + limit: 3, +}); +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams({ state }) { + return recentSearchesPlugin.data.getAlgoliaSearchParams({ + clickAnalytics: true, + hitsPerPage: state.query ? 5 : 10, + }); + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + debug: true, + openOnFocus: true, + plugins: [ + algoliaInsightsPlugin, + recentSearchesPlugin, + querySuggestionsPlugin, + ], + getSources({ query }) { + if (!query) { + return []; + } + + return [ + { + getItems() { + return getAlgoliaHits({ + searchClient, + queries: [{ indexName: 'instant_search', query }], + }); + }, + templates: { + item({ item }) { + return ; + }, + }, + }, + ]; + }, +}); + +type ProductItemProps = { + hit: ProductHit; +}; + +function ProductItem({ hit }: ProductItemProps) { + return ( +
+
+ {hit.name} +
+ +
({ + hit, + attribute: 'name', + }), + }} + /> +
+ ); +} diff --git a/examples/js/index.html b/examples/js/index.html index 6863d7e4d..0d7316c1f 100644 --- a/examples/js/index.html +++ b/examples/js/index.html @@ -47,6 +47,6 @@
- + diff --git a/examples/js/package.json b/examples/js/package.json index 3b7e16c44..bad32f2fd 100644 --- a/examples/js/package.json +++ b/examples/js/package.json @@ -16,7 +16,9 @@ "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.38", "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.38", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.38", + "@algolia/client-search": "4.8.3", "algoliasearch": "4.8.3", + "preact": "10.5.7", "search-insights": "1.6.3" }, "devDependencies": { diff --git a/packages/autocomplete-js/package.json b/packages/autocomplete-js/package.json index 08dfa95c0..720e8c17a 100644 --- a/packages/autocomplete-js/package.json +++ b/packages/autocomplete-js/package.json @@ -33,7 +33,8 @@ "dependencies": { "@algolia/autocomplete-core": "1.0.0-alpha.38", "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.38", - "@algolia/autocomplete-shared": "1.0.0-alpha.38" + "@algolia/autocomplete-shared": "1.0.0-alpha.38", + "preact": "^10.0.0" }, "devDependencies": { "@algolia/client-search": "4.8.3" diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 1ee83b137..794b04539 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -75,6 +75,7 @@ describe('autocomplete-js', () => { -
({ - hit, - attribute: 'name', - }), - }} - /> +
+ {highlightHit({ hit, attribute: 'name' })} +
); } diff --git a/packages/autocomplete-js/src/__tests__/reverseHighlightHit.test.ts b/packages/autocomplete-js/src/__tests__/reverseHighlightHit.test.ts index 95abd50ee..92c411bac 100644 --- a/packages/autocomplete-js/src/__tests__/reverseHighlightHit.test.ts +++ b/packages/autocomplete-js/src/__tests__/reverseHighlightHit.test.ts @@ -19,7 +19,23 @@ describe('reverseHighlightHit', () => { }, attribute: 'query', }) - ).toMatchInlineSnapshot(`"amazon fire tablets"`); + ).toEqual([ + expect.objectContaining({ + type: 'mark', + props: { + children: 'amazon ', + }, + }), + 'fire', + ' ', + 'tablet', + expect.objectContaining({ + type: 'mark', + props: { + children: 's', + }, + }), + ]); }); test('returns a reversed fully highlighted hit', () => { @@ -40,7 +56,7 @@ describe('reverseHighlightHit', () => { }, attribute: 'query', }) - ).toMatchInlineSnapshot(`"amazon fire tablets"`); + ).toEqual(['amazon', ' ', 'fire', ' ', 'tablets']); }); test('returns a reversed empty highlighted query hit', () => { @@ -60,6 +76,6 @@ describe('reverseHighlightHit', () => { }, attribute: 'query', }) - ).toMatchInlineSnapshot(`"amazon fire tablets"`); + ).toEqual(['amazon fire tablets']); }); }); diff --git a/packages/autocomplete-js/src/highlight.ts b/packages/autocomplete-js/src/highlight.ts index 4deee863f..f1c683080 100644 --- a/packages/autocomplete-js/src/highlight.ts +++ b/packages/autocomplete-js/src/highlight.ts @@ -1,36 +1,19 @@ import { parseAlgoliaHitHighlight, parseAlgoliaHitReverseHighlight, - parseAlgoliaHitSnippet, parseAlgoliaHitReverseSnippet, + parseAlgoliaHitSnippet, } from '@algolia/autocomplete-preset-algolia'; import { Hit } from '@algolia/client-search'; +import { createElement as preactCreateElement } from 'preact'; -type ParsedAttribute = { - value: string; - isHighlighted: boolean; -}; - -function concatParts( - parts: ParsedAttribute[], - { highlightPreTag, highlightPostTag } -) { - return parts.reduce((acc, current) => { - return ( - acc + - (current.isHighlighted - ? `${highlightPreTag}${current.value}${highlightPostTag}` - : current.value) - ); - }, ''); -} +import { AutocompleteRenderer } from './types'; type HighlightItemParams = { hit: TItem; attribute: keyof TItem; - highlightPreTag?: string; - highlightPostTag?: string; - ignoreEscape?: string[]; + tagName?: string; + createElement?: AutocompleteRenderer['createElement']; }; /** @@ -39,17 +22,11 @@ type HighlightItemParams = { export function highlightHit>({ hit, attribute, - highlightPreTag = '', - highlightPostTag = '', - ignoreEscape, + tagName = 'mark', + createElement = preactCreateElement, }: HighlightItemParams) { - return concatParts( - parseAlgoliaHitHighlight({ - hit, - attribute, - ignoreEscape, - }), - { highlightPreTag, highlightPostTag } + return parseAlgoliaHitHighlight({ hit, attribute }).map((x) => + x.isHighlighted ? createElement(tagName, { children: x.value }) : x.value ); } @@ -61,17 +38,11 @@ export function highlightHit>({ export function reverseHighlightHit>({ hit, attribute, - highlightPreTag = '', - highlightPostTag = '', - ignoreEscape, + tagName = 'mark', + createElement = preactCreateElement, }: HighlightItemParams) { - return concatParts( - parseAlgoliaHitReverseHighlight({ - hit, - attribute, - ignoreEscape, - }), - { highlightPreTag, highlightPostTag } + return parseAlgoliaHitReverseHighlight({ hit, attribute }).map((x) => + x.isHighlighted ? createElement(tagName, { children: x.value }) : x.value ); } @@ -81,17 +52,11 @@ export function reverseHighlightHit>({ export function snippetHit>({ hit, attribute, - highlightPreTag = '', - highlightPostTag = '', - ignoreEscape, + tagName = 'mark', + createElement = preactCreateElement, }: HighlightItemParams) { - return concatParts( - parseAlgoliaHitSnippet({ - hit, - attribute, - ignoreEscape, - }), - { highlightPreTag, highlightPostTag } + return parseAlgoliaHitSnippet({ hit, attribute }).map((x) => + x.isHighlighted ? createElement(tagName, { children: x.value }) : x.value ); } @@ -103,16 +68,10 @@ export function snippetHit>({ export function reverseSnippetHit>({ hit, attribute, - highlightPreTag = '', - highlightPostTag = '', - ignoreEscape, + tagName = 'mark', + createElement = preactCreateElement, }: HighlightItemParams) { - return concatParts( - parseAlgoliaHitReverseSnippet({ - hit, - attribute, - ignoreEscape, - }), - { highlightPreTag, highlightPostTag } + return parseAlgoliaHitReverseSnippet({ hit, attribute }).map((x) => + x.isHighlighted ? createElement(tagName, { children: x.value }) : x.value ); } diff --git a/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts b/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts index 960923629..74ba8280a 100644 --- a/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts +++ b/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts @@ -1,5 +1,4 @@ -import { SourceTemplates } from '@algolia/autocomplete-js'; -import { parseAlgoliaHitReverseHighlight } from '@algolia/autocomplete-preset-algolia'; +import { reverseHighlightHit, SourceTemplates } from '@algolia/autocomplete-js'; import { QuerySuggestionsHit } from './types'; @@ -42,14 +41,10 @@ export function getTemplates({ }), createElement('div', { className: 'aa-ItemTitle', - children: parseAlgoliaHitReverseHighlight({ + children: reverseHighlightHit({ hit: item, attribute: 'query', - }).map((x) => - x.isHighlighted - ? createElement('mark', { children: x.value }) - : x.value - ), + }), }), ], }), diff --git a/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts b/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts index b9d654b52..b30c696df 100644 --- a/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts +++ b/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts @@ -1,5 +1,4 @@ -import { SourceTemplates } from '@algolia/autocomplete-js'; -import { parseAlgoliaHitReverseHighlight } from '@algolia/autocomplete-preset-algolia'; +import { reverseHighlightHit, SourceTemplates } from '@algolia/autocomplete-js'; import { RecentSearchesItem } from './types'; @@ -41,14 +40,10 @@ export function getTemplates({ }), createElement('div', { className: 'aa-ItemTitle', - children: parseAlgoliaHitReverseHighlight({ + children: reverseHighlightHit({ hit: item, attribute: 'query', - }).map((x) => - x.isHighlighted - ? createElement('mark', { children: x.value }) - : x.value - ), + }), }), ], }), diff --git a/packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts b/packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts index 3c97d8b1a..24c24eb9a 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts @@ -1,5 +1,4 @@ export type ParseAlgoliaHitParams = { hit: TItem; attribute: keyof TItem; - ignoreEscape?: string[]; }; diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts index dc31e492f..b3a56017c 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitHighlight.test.ts @@ -47,63 +47,6 @@ describe('parseAlgoliaHitHighlight', () => { `); }); - test('escapes characters', () => { - expect( - parseAlgoliaHitHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('does not escape ignored characters', () => { - expect( - parseAlgoliaHitHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - test('returns the attribute value if the attribute cannot be highlighted', () => { expect( parseAlgoliaHitHighlight({ @@ -131,7 +74,7 @@ describe('parseAlgoliaHitHighlight', () => { ]); }); - test('returns empty string if the attribute does not exist', () => { + test('returns empty parts if the attribute does not exist', () => { expect( parseAlgoliaHitHighlight({ // @ts-ignore diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts index 210d47cbd..c95d006e8 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseHighlight.test.ts @@ -64,63 +64,6 @@ describe('parseAlgoliaHitReverseHighlight', () => { `); }); - test('escapes characters', () => { - expect( - parseAlgoliaHitReverseHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('does not escape ignored characters', () => { - expect( - parseAlgoliaHitReverseHighlight({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _highlightResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - test('returns the attribute value if the attribute cannot be highlighted', () => { expect( parseAlgoliaHitReverseHighlight({ @@ -148,7 +91,7 @@ describe('parseAlgoliaHitReverseHighlight', () => { ]); }); - test('returns empty string if the attribute does not exist', () => { + test('returns empty parts if the attribute does not exist', () => { expect( parseAlgoliaHitReverseHighlight({ // @ts-ignore diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts index 4eeee204f..cf37cdee7 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitReverseSnippet.test.ts @@ -44,63 +44,6 @@ describe('parseAlgoliaHitReverseSnippet', () => { `); }); - test('escapes characters', () => { - expect( - parseAlgoliaHitReverseSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('does not escape ignored characters', () => { - expect( - parseAlgoliaHitReverseSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": false, - "value": "Food", - }, - Object { - "isHighlighted": true, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - test('returns the attribute value if the attribute cannot be snippeted', () => { expect( parseAlgoliaHitReverseSnippet({ @@ -125,7 +68,7 @@ describe('parseAlgoliaHitReverseSnippet', () => { ]); }); - test('returns empty string if the attribute does not exist', () => { + test('returns empty parts if the attribute does not exist', () => { expect( parseAlgoliaHitReverseSnippet({ // @ts-ignore diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts index b1403aff4..adc517d71 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAlgoliaHitSnippet.test.ts @@ -44,63 +44,6 @@ describe('parseAlgoliaHitSnippet', () => { `); }); - test('escapes characters', () => { - expect( - parseAlgoliaHitSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - - test('does not escape ignored characters', () => { - expect( - parseAlgoliaHitSnippet({ - attribute: 'title', - hit: { - objectID: '1', - title: 'Hello there', - _snippetResult: { - title: { - value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, - }, - }, - }, - ignoreEscape: ["'"], - }) - ).toMatchInlineSnapshot(` - Array [ - Object { - "isHighlighted": true, - "value": "Food", - }, - Object { - "isHighlighted": false, - "value": " & <Drinks> 'n' "Music"", - }, - ] - `); - }); - test('returns the attribute value if the attribute cannot be snippeted', () => { expect( parseAlgoliaHitSnippet({ @@ -125,7 +68,7 @@ describe('parseAlgoliaHitSnippet', () => { ]); }); - test('returns empty string if the attribute does not exist', () => { + test('returns empty parts if the attribute does not exist', () => { expect( parseAlgoliaHitSnippet({ // @ts-ignore diff --git a/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAttribute.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAttribute.test.ts new file mode 100644 index 000000000..36698a84c --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/parseAttribute.test.ts @@ -0,0 +1,55 @@ +import { parseAttribute } from '../parseAttribute'; + +describe('parseAttribute', () => { + test('returns highlighting parts', () => { + expect( + parseAttribute({ + highlightedValue: + '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + }) + ).toEqual([ + { + isHighlighted: true, + value: 'He', + }, + { + isHighlighted: false, + value: 'llo t', + }, + { + isHighlighted: true, + value: 'he', + }, + { + isHighlighted: false, + value: 're', + }, + ]); + }); + + test('concatenates similar consecutive highlighting parts', () => { + expect( + parseAttribute({ + highlightedValue: + '__aa-highlight__Hello __/aa-highlight____aa-highlight__the__/aa-highlight__re __aa-highlight__people__/aa-highlight__ from earth', + }) + ).toEqual([ + { + isHighlighted: true, + value: 'Hello the', + }, + { + isHighlighted: false, + value: 're ', + }, + { + isHighlighted: true, + value: 'people', + }, + { + isHighlighted: false, + value: ' from earth', + }, + ]); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts index 987c5d67f..0f7acf106 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts @@ -9,7 +9,6 @@ import { ParsedAttribute } from './ParsedAttribute'; export function parseAlgoliaHitHighlight>({ hit, attribute, - ignoreEscape, }: ParseAlgoliaHitParams): ParsedAttribute[] { const path = `_highlightResult.${attribute}.value`; let highlightedValue = getAttributeValueByPath(hit, path); @@ -26,8 +25,5 @@ export function parseAlgoliaHitHighlight>({ highlightedValue = getAttributeValueByPath(hit, attribute as string) || ''; } - return parseAttribute({ - highlightedValue, - ignoreEscape, - }); + return parseAttribute({ highlightedValue }); } diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts index d8a729ab6..d4c9d1445 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts @@ -9,7 +9,6 @@ import { ParsedAttribute } from './ParsedAttribute'; export function parseAlgoliaHitSnippet>({ hit, attribute, - ignoreEscape, }: ParseAlgoliaHitParams): ParsedAttribute[] { const path = `_snippetResult.${attribute}.value`; let highlightedValue = getAttributeValueByPath(hit, path); @@ -26,8 +25,5 @@ export function parseAlgoliaHitSnippet>({ highlightedValue = getAttributeValueByPath(hit, attribute as string) || ''; } - return parseAttribute({ - highlightedValue, - ignoreEscape, - }); + return parseAttribute({ highlightedValue }); } diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts index 8e98af687..c15ee961b 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts @@ -2,58 +2,60 @@ import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from '../constants'; import { ParsedAttribute } from './ParsedAttribute'; -const htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', -}; +/** + * Creates a data structure that allows to concatenate similar highlighting + * parts in a single value. + */ +function createAttributeSet(initialValue: ParsedAttribute[] = []) { + const value = initialValue; + + return { + get() { + return value; + }, + add(part: ParsedAttribute) { + const lastPart: ParsedAttribute | undefined = value[value.length - 1]; + + if (lastPart?.isHighlighted === part.isHighlighted) { + value[value.length - 1] = { + value: lastPart.value + part.value, + isHighlighted: lastPart.isHighlighted, + }; + } else { + value.push(part); + } + }, + }; +} type ParseAttributeParams = { highlightedValue: string; - ignoreEscape?: string[]; }; export function parseAttribute({ highlightedValue, - ignoreEscape = [], }: ParseAttributeParams): ParsedAttribute[] { - const unescapedHtmlRegex = new RegExp( - `[${Object.keys(htmlEscapes) - .filter((character) => ignoreEscape.indexOf(character) === -1) - .join('')}]`, - 'g' + const preTagParts = highlightedValue.split(HIGHLIGHT_PRE_TAG); + const firstValue = preTagParts.shift(); + const parts = createAttributeSet( + firstValue ? [{ value: firstValue, isHighlighted: false }] : [] ); - const hasUnescapedHtmlRegex = RegExp(unescapedHtmlRegex.source); - - function escape(value: string) { - return hasUnescapedHtmlRegex.test(value) - ? value.replace(unescapedHtmlRegex, (key) => htmlEscapes[key]) - : value; - } - - const splitByPreTag = highlightedValue.split(HIGHLIGHT_PRE_TAG); - const firstValue = splitByPreTag.shift(); - const elements = !firstValue - ? [] - : [{ value: escape(firstValue), isHighlighted: false }]; - splitByPreTag.forEach((split) => { - const splitByPostTag = split.split(HIGHLIGHT_POST_TAG); + preTagParts.forEach((part) => { + const postTagParts = part.split(HIGHLIGHT_POST_TAG); - elements.push({ - value: escape(splitByPostTag[0]), + parts.add({ + value: postTagParts[0], isHighlighted: true, }); - if (splitByPostTag[1] !== '') { - elements.push({ - value: escape(splitByPostTag[1]), + if (postTagParts[1] !== '') { + parts.add({ + value: postTagParts[1], isHighlighted: false, }); } }); - return elements; + return parts.get(); } diff --git a/packages/website/docs/highlightHit.md b/packages/website/docs/highlightHit.md index 88f606cb6..625f8921c 100644 --- a/packages/website/docs/highlightHit.md +++ b/packages/website/docs/highlightHit.md @@ -2,7 +2,7 @@ id: highlightHit --- -Returns a string with highlighted and escaped matching parts of an Algolia hit. +Returns a virtual node with highlighted matching parts of an Algolia hit. ## Example @@ -30,20 +30,8 @@ The Algolia hit to retrieve the attribute value from. The attribute to retrieve the highlight value from. -### `highlightPreTag` +### `tagName` -> `string` | defaults to `` +> `string` | defaults to `mark` -The HTML tag to prefix the value with. - -### `highlightPostTag` - -> `string` | defaults to `` - -The HTML tag to suffix the value with. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. +The tag name of the virtual node. diff --git a/packages/website/docs/parseAlgoliaHitHighlight.md b/packages/website/docs/parseAlgoliaHitHighlight.md index 31710e871..a3fe2a8cc 100644 --- a/packages/website/docs/parseAlgoliaHitHighlight.md +++ b/packages/website/docs/parseAlgoliaHitHighlight.md @@ -4,11 +4,6 @@ id: parseAlgoliaHitHighlight Returns the highlighted parts of an Algolia hit. - -:::info -This function escapes characters. -::: - ## Example ```js @@ -44,9 +39,3 @@ The Algolia hit to retrieve the attribute value from. > `string` | required The attribute to retrieve the reverse highlight value from. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. diff --git a/packages/website/docs/parseAlgoliaHitReverseHighlight.md b/packages/website/docs/parseAlgoliaHitReverseHighlight.md index 56b1b030c..66c3fb672 100644 --- a/packages/website/docs/parseAlgoliaHitReverseHighlight.md +++ b/packages/website/docs/parseAlgoliaHitReverseHighlight.md @@ -6,11 +6,6 @@ Returns the highlighted parts of an Algolia hit. This is a common pattern for Query Suggestions. - -:::info -This function escapes characters. -::: - # Example ```js @@ -48,9 +43,3 @@ The Algolia hit to retrieve the attribute value from. > `string` | required The attribute to retrieve the highlight value from. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. diff --git a/packages/website/docs/parseAlgoliaHitReverseSnippet.md b/packages/website/docs/parseAlgoliaHitReverseSnippet.md index 1a1c7f21b..8b8017441 100644 --- a/packages/website/docs/parseAlgoliaHitReverseSnippet.md +++ b/packages/website/docs/parseAlgoliaHitReverseSnippet.md @@ -6,11 +6,6 @@ Returns the non-matching parts of an Algolia hit snippet. This is a common pattern for Query Suggestions. - -:::info -This function escapes characters. -::: - ## Example ```js @@ -46,9 +41,3 @@ The Algolia hit to retrieve the attribute value from. > `string` | required The attribute to retrieve the reverse snippet value from. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. diff --git a/packages/website/docs/parseAlgoliaHitSnippet.md b/packages/website/docs/parseAlgoliaHitSnippet.md index cf4627e7e..33e85a2f6 100644 --- a/packages/website/docs/parseAlgoliaHitSnippet.md +++ b/packages/website/docs/parseAlgoliaHitSnippet.md @@ -4,11 +4,6 @@ id: parseAlgoliaHitSnippet Returns the snippeted parts of an Algolia hit. - -:::info -This function escapes characters. -::: - ## Example ```js @@ -44,9 +39,3 @@ The Algolia hit to retrieve the attribute value from. > `string` | required The attribute to retrieve the snippet value from. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. diff --git a/packages/website/docs/reverseHighlightHit.md b/packages/website/docs/reverseHighlightHit.md index ed6d9bb39..15bf02423 100644 --- a/packages/website/docs/reverseHighlightHit.md +++ b/packages/website/docs/reverseHighlightHit.md @@ -2,7 +2,7 @@ id: reverseHighlightHit --- -Returns a string with highlighted and escaped non-matching parts of an Algolia hit. +Returns a virtual node with non-matching parts of an Algolia hit. ## Example @@ -30,20 +30,8 @@ The Algolia hit to retrieve the attribute value from. The attribute to retrieve the highlight value from. -### `highlightPreTag` +### `tagName` -> `string` | defaults to `` +> `string` | defaults to `mark` -The HTML tag to prefix the value with. - -### `highlightPostTag` - -> `string` | defaults to `` - -The HTML tag to suffix the value with. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. +The tag name of the virtual node. diff --git a/packages/website/docs/reverseSnippetHit.md b/packages/website/docs/reverseSnippetHit.md index 650231189..1770fb6fa 100644 --- a/packages/website/docs/reverseSnippetHit.md +++ b/packages/website/docs/reverseSnippetHit.md @@ -2,7 +2,7 @@ id: reverseSnippetHit --- -Returns a string with highlighted and escaped non-matching parts of an Algolia hit snippet. +Returns a virtual node with non-matching parts of an Algolia hit snippet. ## Example @@ -30,20 +30,8 @@ The Algolia hit to retrieve the attribute value from. The attribute to retrieve the snippet value from. -### `highlightPreTag` +### `tagName` -> `string` | defaults to `` +> `string` | defaults to `mark` -The HTML tag to prefix the value with. - -### `highlightPostTag` - -> `string` | defaults to `` - -The HTML tag to suffix the value with. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. +The tag name of the virtual node. diff --git a/packages/website/docs/snippetHit.md b/packages/website/docs/snippetHit.md index 0624c8312..7ebabd1c5 100644 --- a/packages/website/docs/snippetHit.md +++ b/packages/website/docs/snippetHit.md @@ -2,7 +2,7 @@ id: snippetHit --- -Returns a string with highlighted and escaped matching parts of an Algolia hit snippet. +Returns a virtual node with matching parts of an Algolia hit snippet. ## Example @@ -36,14 +36,8 @@ The attribute to retrieve the snippet value from. The HTML tag to prefix the value with. -### `highlightPostTag` +### `tagName` -> `string` | defaults to `` +> `string` | defaults to `mark` -The HTML tag to suffix the value with. - -### `ignoreEscape` - -> `string[]` | defaults to `[]` - -The characters to skip from escaping. +The tag name of the virtual node. From 49da8429e40b0c3364c9cffa6619d4d17025c897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 21 Jan 2021 11:29:28 +0100 Subject: [PATCH 11/14] feat(js): update params passed to render functions --- examples/js/app.ts | 94 ------------------- .../src/__tests__/autocomplete.test.ts | 11 ++- packages/autocomplete-js/src/render.ts | 2 +- .../src/types/AutocompleteRender.ts | 4 +- packages/website/docs/autocomplete-js.md | 6 +- 5 files changed, 14 insertions(+), 103 deletions(-) delete mode 100644 examples/js/app.ts diff --git a/examples/js/app.ts b/examples/js/app.ts deleted file mode 100644 index 14402393e..000000000 --- a/examples/js/app.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - autocomplete, - getAlgoliaHits, - reverseHighlightHit, -} from '@algolia/autocomplete-js'; -import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; -import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; -import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; -import algoliasearch from 'algoliasearch'; -import insightsClient from 'search-insights'; - -import '@algolia/autocomplete-theme-classic'; - -const appId = 'latency'; -const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; -const searchClient = algoliasearch(appId, apiKey); -insightsClient('init', { appId, apiKey }); - -const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); -const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ - key: 'search', - limit: 3, -}); -const querySuggestionsPlugin = createQuerySuggestionsPlugin({ - searchClient, - indexName: 'instant_search_demo_query_suggestions', - getSearchParams() { - return recentSearchesPlugin.data.getAlgoliaSearchParams({ - clickAnalytics: true, - }); - }, -}); - -autocomplete({ - container: '#autocomplete', - placeholder: 'Search', - openOnFocus: true, - plugins: [ - algoliaInsightsPlugin, - recentSearchesPlugin, - querySuggestionsPlugin, - ], - getSources({ query }) { - if (!query) { - return []; - } - - return [ - { - getItems() { - return getAlgoliaHits({ - searchClient, - queries: [{ indexName: 'instant_search', query }], - }); - }, - templates: { - item({ item, root }) { - const itemContent = document.createElement('div'); - const ItemSourceIcon = document.createElement('div'); - const itemTitle = document.createElement('div'); - const sourceIcon = document.createElement('img'); - - sourceIcon.width = 20; - sourceIcon.height = 20; - sourceIcon.src = item.image; - - ItemSourceIcon.classList.add('aa-ItemSourceIcon'); - ItemSourceIcon.appendChild(sourceIcon); - - itemTitle.innerHTML = reverseHighlightHit({ - hit: item, - attribute: 'name', - }); - itemTitle.classList.add('aa-ItemTitle'); - - itemContent.classList.add('aa-ItemContent'); - itemContent.appendChild(ItemSourceIcon); - itemContent.appendChild(itemTitle); - - root.appendChild(itemContent); - }, - empty({ root }) { - const itemContent = document.createElement('div'); - - itemContent.innerHTML = 'No results for this query'; - itemContent.classList.add('aa-ItemContent'); - - root.appendChild(itemContent); - }, - }, - }, - ]; - }, -}); diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 259b1b62a..2ef19c2c0 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -93,8 +93,8 @@ describe('autocomplete-js', () => { stroke-dasharray="164.93361431346415 56.97787143782138" stroke-width="6" > - - + + { type="rotate" values="0 50 50;90 50 50;180 50 50;360 50 50" /> - + @@ -252,8 +252,11 @@ describe('autocomplete-js', () => { { state: expect.anything(), children: expect.anything(), + sections: expect.any(Array), + createElement: expect.anything(), + Fragment: expect.anything(), }, - expect.anything() + expect.any(HTMLElement) ); expect( diff --git a/packages/autocomplete-js/src/render.ts b/packages/autocomplete-js/src/render.ts index 296fbd740..d3cbd02ca 100644 --- a/packages/autocomplete-js/src/render.ts +++ b/packages/autocomplete-js/src/render.ts @@ -178,5 +178,5 @@ export function renderPanel( children: sections, }); - render({ children, state, sections }, dom.panel); + render({ children, state, sections, createElement, Fragment }, dom.panel); } diff --git a/packages/autocomplete-js/src/types/AutocompleteRender.ts b/packages/autocomplete-js/src/types/AutocompleteRender.ts index 1ca5c156f..66d331cdf 100644 --- a/packages/autocomplete-js/src/types/AutocompleteRender.ts +++ b/packages/autocomplete-js/src/types/AutocompleteRender.ts @@ -1,6 +1,6 @@ import { BaseItem } from '@algolia/autocomplete-core'; -import { VNode } from './AutocompleteRenderer'; +import { Pragma, PragmaFrag, VNode } from './AutocompleteRenderer'; import { AutocompleteState } from './AutocompleteState'; export type AutocompleteRender = ( @@ -8,6 +8,8 @@ export type AutocompleteRender = ( children: VNode; state: AutocompleteState; sections: VNode[]; + createElement: Pragma; + Fragment: PragmaFrag; }, root: HTMLElement ) => void; diff --git a/packages/website/docs/autocomplete-js.md b/packages/website/docs/autocomplete-js.md index 5b91885b4..f6ee5f2bb 100644 --- a/packages/website/docs/autocomplete-js.md +++ b/packages/website/docs/autocomplete-js.md @@ -123,7 +123,7 @@ type ClassNames = Partial<{ ### `render` -> `(params: { children: VNode, state: AutocompleteState }) => void` +> `(params: { children: VNode, state: AutocompleteState, sections: VNode[], createElement: Pragma, Fragment: PragmaFrag }) => void` Function called to render the autocomplete panel. It is useful for rendering sections in different row or column layouts. @@ -142,7 +142,7 @@ autocomplete({ ### `renderEmpty` -> `(params: { root: HTMLElement, sections: HTMLElement[], state: AutocompleteState }) => void` +> `(params: { root: HTMLElement, state: AutocompleteState, sections: VNode[], createElement: Pragma, Fragment: PragmaFrag }) => void` Function called to render an empty section when no hits are returned. It is useful for letting the user know that the query returned no results. @@ -151,7 +151,7 @@ There is no default implementation, which closes the panel when there's no resul ````js autocomplete({ // ... - renderEmpty({ root }) { + renderEmpty(_params, root) { const div = document.createElement('div'); div.innerHTML = 'Your query returned no results'; From 230dbcbc47c6687e3affac65fde8ca63efe9c321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 21 Jan 2021 12:06:34 +0100 Subject: [PATCH 12/14] feat(js): update `hasEmptySourceTemplateRef` initial value --- .../src/__tests__/autocomplete.test.ts | 28 ++++--------------- packages/autocomplete-js/src/autocomplete.ts | 23 +++++++-------- test/utils/index.ts | 1 - test/utils/wait.ts | 5 ---- 4 files changed, 15 insertions(+), 42 deletions(-) delete mode 100644 test/utils/wait.ts diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 2ef19c2c0..7d87b9289 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -1,6 +1,5 @@ import { fireEvent, waitFor } from '@testing-library/dom'; -import { wait } from '../../../../test/utils'; import { autocomplete } from '../autocomplete'; describe('autocomplete-js', () => { @@ -188,10 +187,7 @@ describe('autocomplete-js', () => { const input = container.querySelector('.aa-Input'); - fireEvent.input(input, { - target: { value: 'aasdjfaisdf' }, - }); - input.focus(); + fireEvent.input(input, { target: { value: 'a' } }); await waitFor(() => { expect( @@ -237,10 +233,7 @@ describe('autocomplete-js', () => { const input = container.querySelector('.aa-Input'); - fireEvent.input(input, { - target: { value: 'aasdjfaisdf' }, - }); - input.focus(); + fireEvent.input(input, { target: { value: 'a' } }); await waitFor(() => { expect( @@ -299,10 +292,7 @@ describe('autocomplete-js', () => { const input = container.querySelector('.aa-Input'); - fireEvent.input(input, { - target: { value: 'aasdjfaisdf' }, - }); - input.focus(); + fireEvent.input(input, { target: { value: 'a' } }); await waitFor(() => { expect( @@ -346,12 +336,7 @@ describe('autocomplete-js', () => { const input = container.querySelector('.aa-Input'); - fireEvent.input(input, { - target: { value: 'aasdjfaisdf' }, - }); - input.focus(); - - await wait(50); + fireEvent.input(input, { target: { value: 'a' } }); expect( panelContainer.querySelector('.aa-Panel') @@ -480,10 +465,7 @@ describe('autocomplete-js', () => { const input = container.querySelector('.aa-Input'); - fireEvent.input(input, { - target: { value: 'a' }, - }); - input.focus(); + fireEvent.input(input, { target: { value: 'a' } }); expect(input).toHaveValue('a'); }); diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index 7c552935a..3bed25162 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -20,6 +20,7 @@ import { AutocompleteApi, AutocompleteOptions, AutocompletePropGetters, + AutocompleteSource, AutocompleteState, } from './types'; import { mergeDeep, setProperties } from './utils'; @@ -30,7 +31,7 @@ export function autocomplete( const { runEffect, cleanupEffects, runEffects } = createEffectWrapper(); const { reactive, runReactives } = createReactiveWrapper(); - const hasEmptySourceTemplateRef = createRef(true); + const hasEmptySourceTemplateRef = createRef(false); const optionsRef = createRef(options); const onStateChangeRef = createRef< AutocompleteOptions['onStateChange'] @@ -40,6 +41,10 @@ export function autocomplete( createAutocomplete({ ...props.value.core, onStateChange(options) { + hasEmptySourceTemplateRef.current = options.state.collections.some( + (collection) => + (collection.source as AutocompleteSource).templates.empty + ); onStateChangeRef.current?.(options as any); props.value.core.onStateChange?.(options as any); }, @@ -116,7 +121,9 @@ export function autocomplete( }); } - function runRender() { + function scheduleRender(state: AutocompleteState) { + lastStateRef.current = state; + const renderProps = { autocomplete: autocomplete.value, autocompleteScopeApi, @@ -132,13 +139,8 @@ export function autocomplete( propGetters, state: lastStateRef.current, }; - - hasEmptySourceTemplateRef.current = renderProps.state.collections.some( - (collection) => collection.source.templates.empty - ); - const render = - (!getItemsCount(renderProps.state) && + (!getItemsCount(state) && !hasEmptySourceTemplateRef.current && props.value.renderer.renderEmpty) || props.value.renderer.render; @@ -147,11 +149,6 @@ export function autocomplete( renderPanel(render, renderProps); } - function scheduleRender(state: AutocompleteState) { - lastStateRef.current = state; - runRender(); - } - runEffect(() => { const environmentProps = autocomplete.value.getEnvironmentProps({ formElement: dom.value.form, diff --git a/test/utils/index.ts b/test/utils/index.ts index 77cc9c684..4755910bc 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -5,4 +5,3 @@ export * from './createSource'; export * from './createState'; export * from './defer'; export * from './runAllMicroTasks'; -export * from './wait'; diff --git a/test/utils/wait.ts b/test/utils/wait.ts deleted file mode 100644 index a68b7baa2..000000000 --- a/test/utils/wait.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function wait(time: number) { - return new Promise((resolve) => { - setTimeout(resolve, time); - }); -} From 12e0de32531bea633b7e3b611740d722c860b326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 21 Jan 2021 12:12:32 +0100 Subject: [PATCH 13/14] chore(lint): remove `async` --- .../autocomplete-js/src/__tests__/autocomplete.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 7d87b9289..a35511ce9 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -92,8 +92,8 @@ describe('autocomplete-js', () => { stroke-dasharray="164.93361431346415 56.97787143782138" stroke-width="6" > - - + + { type="rotate" values="0 50 50;90 50 50;180 50 50;360 50 50" /> - + @@ -305,7 +305,7 @@ describe('autocomplete-js', () => { ).toHaveTextContent('No results template'); }); - test('allows user-provided shouldPanelShow', async () => { + test('allows user-provided shouldPanelShow', () => { const container = document.createElement('div'); const panelContainer = document.createElement('div'); From 4a3d11c87eef8fa132ca6e63dc1e86dc04a46865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 21 Jan 2021 12:28:26 +0100 Subject: [PATCH 14/14] test(js): update snapshots --- packages/autocomplete-js/src/__tests__/autocomplete.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index a35511ce9..b13f2177c 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -92,8 +92,8 @@ describe('autocomplete-js', () => { stroke-dasharray="164.93361431346415 56.97787143782138" stroke-width="6" > - - + + { type="rotate" values="0 50 50;90 50 50;180 50 50;360 50 50" /> - +