diff --git a/bundlesize.config.json b/bundlesize.config.json index 40af35a20..154e4dce4 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -14,7 +14,7 @@ }, { "path": "packages/autocomplete-plugin-algolia-insights/dist/umd/index.production.js", - "maxSize": "2.1 kB" + "maxSize": "2.5 kB" }, { "path": "packages/autocomplete-plugin-redirect-url/dist/umd/index.production.js", diff --git a/package.json b/package.json index 1823c6a60..1c8521a14 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "rollup-plugin-filesize": "9.1.2", "rollup-plugin-license": "2.9.1", "rollup-plugin-terser": "7.0.2", + "search-insights": "2.3.0", "shipjs": "0.24.1", "start-server-and-test": "1.15.2", "stylelint": "13.13.1", diff --git a/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts b/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts index 00dcad110..9b31d7170 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/__tests__/createAlgoliaInsightsPlugin.test.ts @@ -4,6 +4,7 @@ import { getAlgoliaResults, } from '@algolia/autocomplete-preset-algolia'; import { noop } from '@algolia/autocomplete-shared'; +import { fireEvent } from '@testing-library/dom'; import userEvent from '@testing-library/user-event'; import insightsClient from 'search-insights'; @@ -12,11 +13,17 @@ import { createPlayground, createSearchClient, createSource, + defer, runAllMicroTasks, } from '../../../../test/utils'; import { createAlgoliaInsightsPlugin } from '../createAlgoliaInsightsPlugin'; -jest.useFakeTimers(); +beforeEach(() => { + (window as any).AlgoliaAnalyticsObject = undefined; + (window as any).aa = undefined; + + document.body.innerHTML = ''; +}); describe('createAlgoliaInsightsPlugin', () => { test('has a name', () => { @@ -70,7 +77,7 @@ describe('createAlgoliaInsightsPlugin', () => { ); }); - test('sets a user agent on the Insights client on subscribe', () => { + test('sets a user agent on on subscribe', () => { const insightsClient = jest.fn(); const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); @@ -167,7 +174,129 @@ describe('createAlgoliaInsightsPlugin', () => { ]); }); + describe('automatic pulling', () => { + const consoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + + afterAll(() => { + consoleError.mockReset(); + }); + + it('does not load the script when the Insights client is passed', async () => { + createPlayground(createAutocomplete, { + plugins: [createAlgoliaInsightsPlugin({ insightsClient: noop })], + }); + + await defer(noop, 0); + + expect(document.body).toMatchInlineSnapshot(` +
+ + + `); + expect((window as any).AlgoliaAnalyticsObject).toBeUndefined(); + expect((window as any).aa).toBeUndefined(); + }); + + it('does not load the script when the Insights client is present in the page', async () => { + (window as any).AlgoliaAnalyticsObject = 'aa'; + const aa = noop; + (window as any).aa = aa; + + createPlayground(createAutocomplete, { + plugins: [createAlgoliaInsightsPlugin({})], + }); + + await defer(noop, 0); + + expect(document.body).toMatchInlineSnapshot(` + + + + `); + expect((window as any).AlgoliaAnalyticsObject).toBe('aa'); + expect((window as any).aa).toBe(aa); + expect((window as any).aa.version).toBeUndefined(); + }); + + it('loads the script when the Insights client is not passed and not present in the page', async () => { + createPlayground(createAutocomplete, { + plugins: [createAlgoliaInsightsPlugin({})], + }); + + await defer(noop, 0); + + expect(document.body).toMatchInlineSnapshot(` + + + + + `); + expect((window as any).AlgoliaAnalyticsObject).toBe('aa'); + expect((window as any).aa).toEqual(expect.any(Function)); + expect((window as any).aa.version).toBe('2.3.0'); + }); + + it('notifies when the script fails to be added', () => { + // @ts-ignore `createElement` is a class method can thus only be called on + // an instance of `Document`, not as a standalone function. + // This is needed to call the actual implementation later in the test. + document.originalCreateElement = document.createElement; + + document.createElement = (tagName) => { + if (tagName === 'script') { + throw new Error('error'); + } + + // @ts-ignore + return document.originalCreateElement(tagName); + }; + + createPlayground(createAutocomplete, { + plugins: [createAlgoliaInsightsPlugin({})], + }); + + expect(consoleError).toHaveBeenCalledWith( + '[Autocomplete]: Could not load search-insights.js. Please load it manually following https://alg.li/insights-autocomplete' + ); + + // @ts-ignore + document.createElement = document.originalCreateElement; + }); + + it('notifies when the script fails to load', async () => { + createPlayground(createAutocomplete, { + plugins: [createAlgoliaInsightsPlugin({})], + }); + + await defer(noop, 0); + + fireEvent(document.querySelector('script')!, new ErrorEvent('error')); + + expect(consoleError).toHaveBeenCalledWith( + '[Autocomplete]: Could not load search-insights.js. Please load it manually following https://alg.li/insights-autocomplete' + ); + }); + }); + describe('onItemsChange', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + test('sends a `viewedObjectIDs` event by default', async () => { const insightsClient = jest.fn(); const insightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); diff --git a/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts index b58102b37..686c43fdc 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts @@ -7,6 +7,7 @@ import { debounce, isEqual, noop, + safelyRunOnBrowser, } from '@algolia/autocomplete-shared'; import { createClickedEvent } from './createClickedEvent'; @@ -23,6 +24,8 @@ import { } from './types'; const VIEW_EVENT_DELAY = 400; +const ALGOLIA_INSIGHTS_VERSION = '2.3.0'; +const ALGOLIA_INSIGHTS_SRC = `https://cdn.jsdelivr.net/npm/search-insights@${ALGOLIA_INSIGHTS_VERSION}/dist/search-insights.min.js`; type SendViewedObjectIDsParams = { onItemsChange(params: OnItemsChangeParams): void; @@ -51,7 +54,7 @@ export type CreateAlgoliaInsightsPluginParams = { * * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights/createAlgoliaInsightsPlugin/#param-insightsclient */ - insightsClient: InsightsClient; + insightsClient?: InsightsClient; /** * Hook to send an Insights event when the items change. * @@ -84,11 +87,43 @@ export function createAlgoliaInsightsPlugin( options: CreateAlgoliaInsightsPluginParams ): AutocompletePlugin