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

chore(test): use real search-insights to test the insights middleware #4712

Merged
merged 11 commits into from Apr 26, 2021
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -136,6 +136,7 @@
"rollup-plugin-replace": "2.2.0",
"rollup-plugin-uglify": "6.0.4",
"scriptjs": "2.5.9",
"search-insights": "https://pkg.csb.dev/algolia/search-insights.js/commit/0b3c26c5/search-insights",
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
"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
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 @@ -124,12 +123,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 @@ -147,7 +146,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 @@ -157,8 +156,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 @@ -266,7 +265,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 @@ -334,7 +333,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 @@ -350,7 +349,7 @@ describe('insights', () => {
insightsClient,
})({ instantSearchInstance });
middleware.subscribe();
expect(getUserToken()).toEqual(ANONYMOUS_TOKEN);
expect(getUserToken()).toEqual('token-from-queue-before-init');
});
Copy link
Contributor Author

@eunjae-lee eunjae-lee Apr 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change in this test case is technically a breaking change, but the previous behavior was wrong. If user calls init twice, the second init shouldn't override the previous usertoken with anonymous usertoken (ref: algolia/search-insights.js#251)

});
});
Expand All @@ -371,14 +370,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
107 changes: 40 additions & 67 deletions test/mock/createInsightsClient.ts
@@ -1,85 +1,58 @@
export const ANONYMOUS_TOKEN = 'anonymous-user-id-1';
import {
AlgoliaAnalytics,
getFunctionalInterface,
getRequesterForBrowser,
processQueue,
} from 'search-insights/index-browser.cjs';
Haroenv marked this conversation as resolved.
Show resolved Hide resolved

export type AlgoliaAnalytics = {
setUserToken(userToken: string): void;
init({ appId, apiKey }): void;
_get(key: string, callback: (value: any) => 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 _get = (key, callback) => callback(values[key]);
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: getRequesterForBrowser(),
})
);
const insightsClient = jest.fn(getFunctionalInterface(analytics));

return {
setUserToken,
init,
_get,
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: getRequesterForBrowser(),
})
);

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;
}
27 changes: 5 additions & 22 deletions yarn.lock
Expand Up @@ -3409,7 +3409,7 @@ algoliasearch-helper@^3.4.4:
dependencies:
events "^1.1.1"

"algoliasearch-v3@npm:algoliasearch@3.35.1":
"algoliasearch-v3@npm:algoliasearch@3.35.1", algoliasearch@^3.35.1:
name algoliasearch-v3
version "3.35.1"
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.35.1.tgz#297d15f534a3507cab2f5dfb996019cac7568f0c"
Expand All @@ -3431,27 +3431,6 @@ algoliasearch-helper@^3.4.4:
semver "^5.1.0"
tunnel-agent "^0.6.0"

algoliasearch@^3.35.1:
version "3.35.1"
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.35.1.tgz#297d15f534a3507cab2f5dfb996019cac7568f0c"
integrity sha512-K4yKVhaHkXfJ/xcUnil04xiSrB8B8yHZoFEhWNpXg23eiCnqvTZw1tn/SqvdsANlYHLJlKl0qi3I/Q2Sqo7LwQ==
dependencies:
agentkeepalive "^2.2.0"
debug "^2.6.9"
envify "^4.0.0"
es6-promise "^4.1.0"
events "^1.1.0"
foreach "^2.0.5"
global "^4.3.2"
inherits "^2.0.1"
isarray "^2.0.1"
load-script "^1.0.0"
object-keys "^1.0.11"
querystring-es3 "^0.2.1"
reduce "^1.0.1"
semver "^5.1.0"
tunnel-agent "^0.6.0"

algoliasearch@4.8.6:
version "4.8.6"
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.8.6.tgz#8d6d7d2315bb052705a8ef5c8dbf57a19d357c2b"
Expand Down Expand Up @@ -12802,6 +12781,10 @@ scriptjs@2.5.9:
resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f"
integrity sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==

"search-insights@https://pkg.csb.dev/algolia/search-insights.js/commit/0b3c26c5/search-insights":
version "1.7.1"
resolved "https://pkg.csb.dev/algolia/search-insights.js/commit/0b3c26c5/search-insights#2b2df1b689b2fa61e7ac6008bab62e8e34332cff"

select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
Expand Down