Skip to content

Commit

Permalink
chore(test): use real search-insights to test the insights middleware (
Browse files Browse the repository at this point in the history
  • Loading branch information
Eunjae Lee authored and Haroenv committed Apr 30, 2021
1 parent 34a8e6b commit a6fe0ce
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 92 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
transformIgnorePatterns: ['node_modules/(?!(search-insights)/)'],
globals: {
__DEV__: true,
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 22 additions & 19 deletions src/middlewares/__tests__/createInsightsMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
});
Expand All @@ -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'),
});
Expand All @@ -54,6 +52,7 @@ describe('insights', () => {
getHelper: () => helper,
};
return {
analytics,
insightsClient,
libraryLoadedAndProcessQueue,
instantSearchInstance,
Expand Down Expand Up @@ -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,
Expand All @@ -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([
Expand All @@ -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'],
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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,
Expand All @@ -331,7 +330,7 @@ describe('insights', () => {
insightsClient,
})({ instantSearchInstance });
middleware.subscribe();
expect(getUserToken()).toEqual(ANONYMOUS_TOKEN);
expect(getUserToken()).toEqual('token-from-queue-before-init');
});
});
});
Expand All @@ -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'],
});
});

Expand Down
108 changes: 37 additions & 71 deletions test/mock/createInsightsClient.ts
Original file line number Diff line number Diff line change
@@ -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;
}
10 changes: 9 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
7 changes: 6 additions & 1 deletion tsconfig.v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit a6fe0ce

Please sign in to comment.