Skip to content

Commit

Permalink
feat(js): change renderer implementation to virtual DOM (#381)
Browse files Browse the repository at this point in the history
* feat(js): change renderer implementation to virtual DOM

* feat(highlighting): revamp highlighting system to VDOM (#399)
  • Loading branch information
francoischalifour authored Jan 21, 2021
1 parent 3475d12 commit 5a1efc2
Show file tree
Hide file tree
Showing 54 changed files with 669 additions and 894 deletions.
6 changes: 3 additions & 3 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
{
"path": "packages/autocomplete-js/dist/umd/index.production.js",
"maxSize": "10.2 kB"
"maxSize": "13.5 kB"
},
{
"path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js",
Expand All @@ -18,11 +18,11 @@
},
{
"path": "packages/autocomplete-plugin-recent-searches/dist/umd/index.production.js",
"maxSize": "3.25 kB"
"maxSize": "3.5 kB"
},
{
"path": "packages/autocomplete-plugin-query-suggestions/dist/umd/index.production.js",
"maxSize": "2.3 kB"
"maxSize": "2.5 kB"
}
]
}
69 changes: 35 additions & 34 deletions examples/js/app.ts → examples/js/app.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
/** @jsx h */
import {
autocomplete,
getAlgoliaHits,
reverseHighlightHit,
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<Product>;

const appId = 'latency';
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
const searchClient = algoliasearch(appId, apiKey);
Expand All @@ -24,16 +30,18 @@ const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
const querySuggestionsPlugin = createQuerySuggestionsPlugin({
searchClient,
indexName: 'instant_search_demo_query_suggestions',
getSearchParams() {
getSearchParams({ state }) {
return recentSearchesPlugin.data.getAlgoliaSearchParams({
clickAnalytics: true,
hitsPerPage: state.query ? 5 : 10,
});
},
});

autocomplete({
container: '#autocomplete',
placeholder: 'Search',
debug: true,
openOnFocus: true,
plugins: [
algoliaInsightsPlugin,
Expand All @@ -48,47 +56,40 @@ autocomplete({
return [
{
getItems() {
return getAlgoliaHits({
return getAlgoliaHits<Product>({
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);
item({ item }) {
return <ProductItem hit={item} />;
},
empty({ root }) {
const itemContent = document.createElement('div');

itemContent.innerHTML = 'No results for this query';
itemContent.classList.add('aa-ItemContent');

root.appendChild(itemContent);
empty() {
return (
<div className="aa-ItemContent">No results for this query.</div>
);
},
},
},
];
},
});

type ProductItemProps = {
hit: ProductHit;
};

function ProductItem({ hit }: ProductItemProps) {
return (
<div className="aa-ItemContent">
<div className="aa-ItemSourceIcon">
<img src={hit.image} alt={hit.name} width="20" height="20" />
</div>

<div className="aa-ItemTitle">
{highlightHit<ProductHit>({ hit, attribute: 'name' })}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion examples/js/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@
</div>

<script src="env.ts"></script>
<script src="app.ts"></script>
<script src="app.tsx"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions examples/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 2 additions & 1 deletion packages/autocomplete-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
51 changes: 20 additions & 31 deletions packages/autocomplete-js/src/__tests__/autocomplete.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { fireEvent, waitFor } from '@testing-library/dom';

import { wait } from '../../../../test/utils';
import { autocomplete } from '../autocomplete';

describe('autocomplete-js', () => {
Expand Down Expand Up @@ -76,6 +75,7 @@ describe('autocomplete-js', () => {
</label>
<div
class="aa-LoadingIndicator"
hidden=""
>
<svg
class="aa-LoadingIcon"
Expand Down Expand Up @@ -131,6 +131,7 @@ describe('autocomplete-js', () => {
>
<button
class="aa-ResetButton"
hidden=""
type="reset"
>
<svg
Expand Down Expand Up @@ -186,10 +187,7 @@ describe('autocomplete-js', () => {

const input = container.querySelector<HTMLInputElement>('.aa-Input');

fireEvent.input(input, {
target: { value: 'aasdjfaisdf' },
});
input.focus();
fireEvent.input(input, { target: { value: 'a' } });

await waitFor(() => {
expect(
Expand All @@ -205,7 +203,7 @@ describe('autocomplete-js', () => {
test('calls renderEmpty without empty template on no results', async () => {
const container = document.createElement('div');
const panelContainer = document.createElement('div');
const renderEmpty = jest.fn(({ root }) => {
const renderEmpty = jest.fn((_params, root) => {
const div = document.createElement('div');
div.innerHTML = 'No results render';

Expand Down Expand Up @@ -235,22 +233,24 @@ describe('autocomplete-js', () => {

const input = container.querySelector<HTMLInputElement>('.aa-Input');

fireEvent.input(input, {
target: { value: 'aasdjfaisdf' },
});
input.focus();
fireEvent.input(input, { target: { value: 'a' } });

await waitFor(() => {
expect(
panelContainer.querySelector<HTMLElement>('.aa-Panel')
).toBeInTheDocument();
});

expect(renderEmpty).toHaveBeenCalledWith({
root: expect.anything(),
state: expect.anything(),
sections: expect.anything(),
});
expect(renderEmpty).toHaveBeenCalledWith(
{
state: expect.anything(),
children: expect.anything(),
sections: expect.any(Array),
createElement: expect.anything(),
Fragment: expect.anything(),
},
expect.any(HTMLElement)
);

expect(
panelContainer.querySelector<HTMLElement>('.aa-Panel')
Expand Down Expand Up @@ -282,7 +282,7 @@ describe('autocomplete-js', () => {
},
];
},
renderEmpty({ root }) {
renderEmpty(_params, root) {
const div = document.createElement('div');
div.innerHTML = 'No results render';

Expand All @@ -292,10 +292,7 @@ describe('autocomplete-js', () => {

const input = container.querySelector<HTMLInputElement>('.aa-Input');

fireEvent.input(input, {
target: { value: 'aasdjfaisdf' },
});
input.focus();
fireEvent.input(input, { target: { value: 'a' } });

await waitFor(() => {
expect(
Expand All @@ -308,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');

Expand Down Expand Up @@ -339,12 +336,7 @@ describe('autocomplete-js', () => {

const input = container.querySelector<HTMLInputElement>('.aa-Input');

fireEvent.input(input, {
target: { value: 'aasdjfaisdf' },
});
input.focus();

await wait(50);
fireEvent.input(input, { target: { value: 'a' } });

expect(
panelContainer.querySelector<HTMLElement>('.aa-Panel')
Expand Down Expand Up @@ -473,10 +465,7 @@ describe('autocomplete-js', () => {

const input = container.querySelector<HTMLInputElement>('.aa-Input');

fireEvent.input(input, {
target: { value: 'a' },
});
input.focus();
fireEvent.input(input, { target: { value: 'a' } });

expect(input).toHaveValue('a');
});
Expand Down
22 changes: 19 additions & 3 deletions packages/autocomplete-js/src/__tests__/reverseHighlightHit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,23 @@ describe('reverseHighlightHit', () => {
},
attribute: 'query',
})
).toMatchInlineSnapshot(`"<mark>amazon </mark>fire tablet<mark>s</mark>"`);
).toEqual([
expect.objectContaining({
type: 'mark',
props: {
children: 'amazon ',
},
}),
'fire',
' ',
'tablet',
expect.objectContaining({
type: 'mark',
props: {
children: 's',
},
}),
]);
});

test('returns a reversed fully highlighted hit', () => {
Expand All @@ -40,7 +56,7 @@ describe('reverseHighlightHit', () => {
},
attribute: 'query',
})
).toMatchInlineSnapshot(`"amazon fire tablets"`);
).toEqual(['amazon', ' ', 'fire', ' ', 'tablets']);
});

test('returns a reversed empty highlighted query hit', () => {
Expand All @@ -60,6 +76,6 @@ describe('reverseHighlightHit', () => {
},
attribute: 'query',
})
).toMatchInlineSnapshot(`"amazon fire tablets"`);
).toEqual(['amazon fire tablets']);
});
});
Loading

0 comments on commit 5a1efc2

Please sign in to comment.