Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): introduce metadata #774

Merged
merged 14 commits into from
Oct 19, 2021
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports = {
'@typescript-eslint/camelcase': [
ERROR,
{
allow: ['__autocomplete_'],
allow: ['__autocomplete_', 'aa_core', 'aa_js'],
},
],
// Useful to call functions like `nodeItem?.scrollIntoView()`.
Expand Down
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"files": [
{
"path": "packages/autocomplete-core/dist/umd/index.production.js",
"maxSize": "5.5 kB"
"maxSize": "5.75 kB"
},
{
"path": "packages/autocomplete-js/dist/umd/index.production.js",
"maxSize": "16 kB"
"maxSize": "16.25 kB"
},
{
"path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js",
Expand Down
169 changes: 169 additions & 0 deletions packages/autocomplete-core/src/__tests__/metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights';
import { noop, version } from '@algolia/autocomplete-shared';
import insightsClient from 'search-insights';

import { createPlayground, defer } from '../../../../test/utils';
import { createAutocomplete } from '../createAutocomplete';

beforeEach(() => {
document.head.innerHTML = '';
});

const algoliaCrawlerEnvironment = createEnvironmentWithUserAgent(
'Algolia Crawler 2.303.5'
);

describe('metadata', () => {
test('does not enable metadata with regular user agents', async () => {
createPlayground(createAutocomplete, {
environment: createEnvironmentWithUserAgent(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:93.0) Gecko/20100101 Firefox/93.0'
),
});

await defer(noop, 0);

expect(document.head).toMatchInlineSnapshot(`<head />`);
});

test('does not enable metadata with no window', async () => {
createPlayground(createAutocomplete, {
environment: {},
});

await defer(noop, 0);

expect(document.head).toMatchInlineSnapshot(`<head />`);
});

test('enables metadata with Algolia Crawler user agents', async () => {
createPlayground(createAutocomplete, {
environment: algoliaCrawlerEnvironment,
});

await defer(noop, 0);

expect(
JSON.parse(
document.head.querySelector<HTMLMetaElement>(
'meta[name="algolia:metadata"]'
).content
)
).toEqual({
options: { 'autocomplete-core': ['environment'] },
plugins: [],
ua: [{ segment: 'autocomplete-core', version }],
});
});

test('exposes user agents', async () => {
createPlayground(createAutocomplete, {
environment: algoliaCrawlerEnvironment,
});

await defer(noop, 0);

expect(
JSON.parse(
document.head.querySelector<HTMLMetaElement>(
'meta[name="algolia:metadata"]'
).content
).ua
).toEqual([{ segment: 'autocomplete-core', version }]);
});

test('exposes passed options', async () => {
createPlayground(createAutocomplete, {
openOnFocus: true,
placeholder: 'Start searching',
environment: algoliaCrawlerEnvironment,
});

await defer(noop, 0);

expect(
JSON.parse(
document.head.querySelector<HTMLMetaElement>(
'meta[name="algolia:metadata"]'
).content
).options
).toEqual({
'autocomplete-core': ['openOnFocus', 'placeholder', 'environment'],
});
});

test('exposes passed plugins and their passed options', async () => {
const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({
insightsClient,
onItemsChange: noop,
});

createPlayground(createAutocomplete, {
environment: algoliaCrawlerEnvironment,
plugins: [algoliaInsightsPlugin],
});

await defer(noop, 0);

expect(
JSON.parse(
document.head.querySelector<HTMLMetaElement>(
'meta[name="algolia:metadata"]'
).content
).plugins
).toEqual([
{
name: 'aa.algoliaInsightsPlugin',
options: ['insightsClient', 'onItemsChange'],
},
]);
});

test('exposes custom plugins', async () => {
createPlayground(createAutocomplete, {
environment: algoliaCrawlerEnvironment,
plugins: [
{
name: 'customPlugin',
},
],
});

await defer(noop, 0);

expect(
JSON.parse(
document.head.querySelector<HTMLMetaElement>(
'meta[name="algolia:metadata"]'
).content
).plugins
).toEqual([{ name: 'customPlugin', options: [] }]);
});

test('does not set a fallback name for unnamed custom plugins', async () => {
createPlayground(createAutocomplete, {
environment: algoliaCrawlerEnvironment,
plugins: [{}],
});

await defer(noop, 0);

expect(
JSON.parse(
document.head.querySelector<HTMLMetaElement>(
'meta[name="algolia:metadata"]'
).content
).plugins
).toEqual([{ options: [] }]);
});
});

function createEnvironmentWithUserAgent(userAgent: string) {
return {
...global,
navigator: {
...global.navigator,
userAgent,
},
};
}
10 changes: 8 additions & 2 deletions packages/autocomplete-core/src/createAutocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createStore } from './createStore';
import { getAutocompleteSetters } from './getAutocompleteSetters';
import { getDefaultProps } from './getDefaultProps';
import { getPropGetters } from './getPropGetters';
import { getMetadata, injectMetadata } from './metadata';
import { onInput } from './onInput';
import { stateReducer } from './stateReducer';
import {
Expand All @@ -12,7 +13,7 @@ import {
AutocompleteSubscribers,
} from './types';

interface AutocompleteOptions<TItem extends BaseItem>
export interface AutocompleteOptionsWithMetadata<TItem extends BaseItem>
extends AutocompleteCoreOptions<TItem> {
/**
* @internal
Expand All @@ -26,7 +27,7 @@ export function createAutocomplete<
TMouseEvent = MouseEvent,
TKeyboardEvent = KeyboardEvent
>(
options: AutocompleteOptions<TItem>
options: AutocompleteOptionsWithMetadata<TItem>
): AutocompleteApi<TItem, TEvent, TMouseEvent, TKeyboardEvent> {
checkOptions(options);

Expand Down Expand Up @@ -71,6 +72,11 @@ export function createAutocomplete<
})
);

injectMetadata({
metadata: getMetadata({ plugins: props.plugins, options }),
environment: props.environment,
});

return {
refresh,
...propGetters,
Expand Down
80 changes: 80 additions & 0 deletions packages/autocomplete-core/src/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { UserAgent, userAgents } from '@algolia/autocomplete-shared';

import {
AutocompleteEnvironment,
AutocompleteOptions,
AutocompleteOptionsWithMetadata,
AutocompletePlugin,
BaseItem,
} from '.';

type AutocompleteMetadata = {
plugins: Array<{
name: string | undefined;
options: string[];
}>;
options: Record<string, string[]>;
ua: UserAgent[];
};

type GetMetadataParams<TItem extends BaseItem, TData = unknown> = {
plugins: Array<AutocompletePlugin<TItem, TData>>;
options: AutocompleteOptionsWithMetadata<TItem>;
};

export function getMetadata<TItem extends BaseItem, TData = unknown>({
plugins,
options,
}: GetMetadataParams<TItem, TData>) {
const optionsKey = ((options.__autocomplete_metadata
?.userAgents as UserAgent[]) || [])[0]?.segment;

const extraOptions = optionsKey
? {
[optionsKey]: Object.keys(
(options.__autocomplete_metadata
?.options as AutocompleteOptions<TItem>) || {}
),
}
: {};

return {
plugins: plugins.map((plugin) => ({
name: plugin.name,
options: Object.keys(plugin.__autocomplete_pluginOptions || []),
})),
options: {
'autocomplete-core': Object.keys(options),
...extraOptions,
},
ua: userAgents.concat(
(options.__autocomplete_metadata?.userAgents as any) || []
),
};
}

type InlineMetadataParams = {
metadata: AutocompleteMetadata;
environment: AutocompleteEnvironment;
};

export function injectMetadata({
metadata,
environment,
}: InlineMetadataParams) {
const isMetadataEnabled = environment.navigator?.userAgent.includes(
'Algolia Crawler'
);

if (isMetadataEnabled) {
const metadataContainer = environment.document.createElement('meta');
const headRef = environment.document.querySelector('head');

metadataContainer.name = 'algolia:metadata';

setTimeout(() => {
metadataContainer.content = JSON.stringify(metadata);
headRef!.appendChild(metadataContainer);
}, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export type AutocompleteEnvironment =
assign: Location['assign'];
};
open: Window['open'];
navigator: Window['navigator'];
};
Loading