diff --git a/jest.config.js b/jest.config.js index 1871e21cf2..bb57b9e058 100644 --- a/jest.config.js +++ b/jest.config.js @@ -21,6 +21,7 @@ module.exports = { 'jest-watch-typeahead/filename', 'jest-watch-typeahead/testname', ], + transformIgnorePatterns: ['node_modules/(?!(search-insights)/)'], globals: { __DEV__: true, }, diff --git a/package.json b/package.json index aae3d71467..4844a0d189 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "rollup-plugin-replace": "2.2.0", "rollup-plugin-uglify": "6.0.4", "scriptjs": "2.5.9", + "search-insights": "1.7.2", "semver": "6.3.0", "shelljs": "0.8.3", "shipjs": "0.21.0", diff --git a/src/middlewares/__tests__/createInsightsMiddleware.ts b/src/middlewares/__tests__/createInsightsMiddleware.ts index 3f4beae978..c0040e5465 100644 --- a/src/middlewares/__tests__/createInsightsMiddleware.ts +++ b/src/middlewares/__tests__/createInsightsMiddleware.ts @@ -3,19 +3,15 @@ import algoliasearchHelper from 'algoliasearch-helper'; import { createInsightsMiddleware } from '../'; import { createInstantSearch } from '../../../test/mock/createInstantSearch'; import { - createAlgoliaAnalytics, - createInsightsClient, + createInsights, createInsightsUmdVersion, - AlgoliaAnalytics, - ANONYMOUS_TOKEN, } from '../../../test/mock/createInsightsClient'; import { warning } from '../../lib/utils'; import { SearchClient } from '../../types'; describe('insights', () => { const createTestEnvironment = () => { - const analytics = createAlgoliaAnalytics(); - const insightsClient = jest.fn(createInsightsClient(analytics)); + const { analytics, insightsClient } = createInsights(); const instantSearchInstance = createInstantSearch({ client: algoliasearch('myAppId', 'myApiKey'), }); @@ -37,11 +33,13 @@ describe('insights', () => { }; }; - const createUmdTestEnvironment = (algoliaAnalytics?: AlgoliaAnalytics) => { + const createUmdTestEnvironment = () => { const { + analytics, insightsClient, libraryLoadedAndProcessQueue, - } = createInsightsUmdVersion(algoliaAnalytics); + } = createInsightsUmdVersion(); + const instantSearchInstance = createInstantSearch({ client: algoliasearch('myAppId', 'myApiKey'), }); @@ -54,6 +52,7 @@ describe('insights', () => { getHelper: () => helper, }; return { + analytics, insightsClient, libraryLoadedAndProcessQueue, instantSearchInstance, @@ -105,12 +104,12 @@ describe('insights', () => { }); it('does not throw when an event is sent right after the creation in UMD', () => { - const algoliaAnalytics = createAlgoliaAnalytics(); const { + analytics, insightsClient, libraryLoadedAndProcessQueue, instantSearchInstance, - } = createUmdTestEnvironment(algoliaAnalytics); + } = createUmdTestEnvironment(); const middleware = createInsightsMiddleware({ insightsClient, @@ -128,7 +127,7 @@ describe('insights', () => { }, widgetType: 'ais.hits', }); - expect(algoliaAnalytics.viewedObjectIDs).toHaveBeenCalledTimes(0); + expect(analytics.viewedObjectIDs).toHaveBeenCalledTimes(0); // But, the library hasn't been loaded yet, so the event stays in the queue. expect(insightsClient.queue[insightsClient.queue.length - 1]).toEqual([ @@ -138,8 +137,8 @@ describe('insights', () => { // When the library is loaded later, it consumes the queue and sends the event. libraryLoadedAndProcessQueue(); - expect(algoliaAnalytics.viewedObjectIDs).toHaveBeenCalledTimes(1); - expect(algoliaAnalytics.viewedObjectIDs).toHaveBeenCalledWith({ + expect(analytics.viewedObjectIDs).toHaveBeenCalledTimes(1); + expect(analytics.viewedObjectIDs).toHaveBeenCalledWith({ eventName: 'Hits Viewed', index: '', objectIDs: ['1', '2'], @@ -247,7 +246,7 @@ describe('insights', () => { insightsClient, })({ instantSearchInstance }); middleware.subscribe(); - expect(getUserToken()).toEqual(ANONYMOUS_TOKEN); + expect(getUserToken()).toEqual(expect.stringMatching(/^anonymous-/)); }); it('applies userToken which was set before init', () => { @@ -315,7 +314,7 @@ describe('insights', () => { expect(getUserToken()).toEqual('token-from-queue'); }); - it('ignores userToken set before init', () => { + it('does not override userToken set before init with anonymous token', () => { const { insightsClient, instantSearchInstance, @@ -331,7 +330,7 @@ describe('insights', () => { insightsClient, })({ instantSearchInstance }); middleware.subscribe(); - expect(getUserToken()).toEqual(ANONYMOUS_TOKEN); + expect(getUserToken()).toEqual('token-from-queue-before-init'); }); }); }); @@ -352,14 +351,18 @@ describe('insights', () => { instantSearchInstance.sendEventToInsights({ insightsMethod: 'viewedObjectIDs', widgetType: 'ais.customWidget', - eventType: 'click', + eventType: 'view', payload: { - hello: 'world', + index: 'my-index', + eventName: 'My Hits Viewed', + objectIDs: ['obj1'], }, }); expect(analytics.viewedObjectIDs).toHaveBeenCalledTimes(1); expect(analytics.viewedObjectIDs).toHaveBeenCalledWith({ - hello: 'world', + index: 'my-index', + eventName: 'My Hits Viewed', + objectIDs: ['obj1'], }); }); diff --git a/test/mock/createInsightsClient.ts b/test/mock/createInsightsClient.ts index 89f6c7f51b..017f96079e 100644 --- a/test/mock/createInsightsClient.ts +++ b/test/mock/createInsightsClient.ts @@ -1,89 +1,55 @@ -export const ANONYMOUS_TOKEN = 'anonymous-user-id-1'; +import AlgoliaAnalytics from 'search-insights/lib/insights'; +import { processQueue } from 'search-insights/lib/_processQueue'; +import { getFunctionalInterface } from 'search-insights/lib/_getFunctionalInterface'; -export type AlgoliaAnalytics = { - setUserToken(userToken: string): void; - init({ appId, apiKey }): void; - getUserToken( - options: any, - callback: (error: any, userToken: string) => void - ): void; - onUserTokenChange( - callback: (value: string) => void, - options?: { immediate?: boolean } - ): void; - viewedObjectIDs(...args: any[]): void; -}; - -export function createAlgoliaAnalytics(): AlgoliaAnalytics { - let values: any = {}; - const setValues = obj => { - values = { - ...values, - ...obj, - }; - }; - let userTokenCallback; - const setUserToken = userToken => { - setValues({ _userToken: userToken }); - if (userTokenCallback) { - userTokenCallback(userToken); - } - }; - const init = ({ appId, apiKey }) => { - setValues({ _hasCredentials: true, _appId: appId, _apiKey: apiKey }); - setUserToken(ANONYMOUS_TOKEN); - }; - const getUserToken = (_options, callback) => - callback(null, values._userToken); - const onUserTokenChange = (callback, { immediate = false } = {}) => { - userTokenCallback = callback; - if (immediate) { - callback(values._userToken); - } - }; - const viewedObjectIDs = jest.fn(); +export function createInsights() { + const analytics = mockSendingEvents( + new AlgoliaAnalytics({ + requestFn: jest.fn(), + }) + ); + const insightsClient = jest.fn(getFunctionalInterface(analytics)); return { - setUserToken, - init, - getUserToken, - onUserTokenChange, - viewedObjectIDs, + analytics, + insightsClient, }; } -export function createInsightsClient(instance = createAlgoliaAnalytics()) { - return (methodName, ...args) => { - if (!instance[methodName]) { - throw new Error(`${methodName} doesn't exist in this mocked instance`); - } - instance[methodName](...args); - }; -} - -export function createInsightsUmdVersion( - algoliaAnalytics = createAlgoliaAnalytics() -) { +export function createInsightsUmdVersion() { const globalObject: any = {}; + globalObject.AlgoliaAnalyticsObject = 'aa'; globalObject.aa = (...args) => { globalObject.aa.queue = globalObject.aa.queue || []; globalObject.aa.queue.push(args); }; + const analytics = mockSendingEvents( + new AlgoliaAnalytics({ + requestFn: jest.fn(), + }) + ); return { + analytics, insightsClient: globalObject.aa, libraryLoadedAndProcessQueue: () => { - const _aa = createInsightsClient(algoliaAnalytics); - const queue = globalObject.aa.queue; - queue.forEach(([methodName, ...args]) => { - _aa(methodName, ...args); - }); - queue.push = ([methodName, ...args]) => { - _aa(methodName, ...args); - }; - return { - algoliaAnalytics, - }; + processQueue.call(analytics, globalObject); }, }; } + +function mockSendingEvents(analytics: AlgoliaAnalytics) { + analytics.viewedFilters = jest.fn(analytics.viewedFilters); + analytics.viewedObjectIDs = jest.fn(analytics.viewedObjectIDs); + analytics.clickedFilters = jest.fn(analytics.clickedFilters); + analytics.clickedObjectIDs = jest.fn(analytics.clickedObjectIDs); + analytics.clickedObjectIDsAfterSearch = jest.fn( + analytics.clickedObjectIDsAfterSearch + ); + analytics.convertedFilters = jest.fn(analytics.convertedFilters); + analytics.convertedObjectIDs = jest.fn(analytics.convertedObjectIDs); + analytics.convertedObjectIDsAfterSearch = jest.fn( + analytics.convertedObjectIDsAfterSearch + ); + return analytics; +} diff --git a/tsconfig.json b/tsconfig.json index cfb121d033..d3a717077f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,13 @@ "allowSyntheticDefaultImports": true, "skipLibCheck": true }, - "exclude": ["examples", "es"] + "exclude": [ + "examples", + "es", + // these two files are temporarily excluded because + // they import files from node_modules/search-insights directly + // and it causes the type-checking to fail. + "src/middlewares/__tests__/createInsightsMiddleware.ts", + "test/mock/createInsightsClient.ts" + ] } diff --git a/tsconfig.v3.json b/tsconfig.v3.json index 9ee91e3747..e97067fc3a 100644 --- a/tsconfig.v3.json +++ b/tsconfig.v3.json @@ -4,6 +4,11 @@ "examples", "es", // this test has specific code for v3 and v4, so already checked in the v4 test - "src/middlewares/__tests__/createMetadataMiddleware.ts" + "src/middlewares/__tests__/createMetadataMiddleware.ts", + // these two files are temporarily excluded because + // they import files from node_modules/search-insights directly + // and it causes the type-checking to fail. + "src/middlewares/__tests__/createInsightsMiddleware.ts", + "test/mock/createInsightsClient.ts" ] } diff --git a/yarn.lock b/yarn.lock index b0513df1f4..0ffb6ea1c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12801,6 +12801,11 @@ scriptjs@2.5.9: resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f" integrity sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg== +search-insights@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-1.7.2.tgz#85b9dd944d3107ad5f3c3833184a85b759240ee6" + integrity sha512-gOu7UtMJTaX/MEgMy1+HqUR/y08HBetYldknOZ2lkZr0EgAVxzg9cioQN3rzY45DBh9pXqaV+qCsLofcloPaiw== + select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"