diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index fce802ad..dee82e1c 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -8490,6 +8490,780 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); }); + describe('trackQuizTriggerShow', () => { + const requiredParameters = { + quizId: 'coffee-quiz', + searchQuery: 'coff', + matchedFacet: 'coffee_facet', + matchedQuery: 'nescafe', + }; + const optionalParameters = { + section: 'Products', + }; + + it('Backwards Compatibility - Should respond with a valid response when snake cased parameters are provided', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const snakeCaseParameters = { + quiz_id: 'coffee-quiz', + search_query: 'nescafe', + matched_facet: 'coffee_facet', + matched_query: 'nescafe', + }; + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('key'); + expect(requestParams).to.have.property('i'); + expect(requestParams).to.have.property('s'); + expect(requestParams).to.have.property('c').to.equal(clientVersion); + expect(requestParams).to.have.property('_dt'); + validateOriginReferrer(requestParams); + expect(requestParams).to.have.property('quiz_id').to.equal(snakeCaseParameters.quiz_id); + expect(requestParams).to.have.property('search_query').to.equal(snakeCaseParameters.search_query); + expect(requestParams).to.have.property('matched_facet').to.equal(snakeCaseParameters.matched_facet); + expect(requestParams).to.have.property('matched_query').to.equal(snakeCaseParameters.matched_query); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(snakeCaseParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required parameters are provided', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('key'); + expect(requestParams).to.have.property('i'); + expect(requestParams).to.have.property('s'); + expect(requestParams).to.have.property('c').to.equal(clientVersion); + expect(requestParams).to.have.property('_dt'); + validateOriginReferrer(requestParams); + expect(requestParams).to.have.property('quiz_id').to.equal(requiredParameters.quizId); + expect(requestParams).to.have.property('search_query').to.equal(requiredParameters.searchQuery); + expect(requestParams).to.have.property('matched_facet').to.equal(requiredParameters.matchedFacet); + expect(requestParams).to.have.property('matched_query').to.equal(requiredParameters.matchedQuery); + expect(requestParams).to.have.property('section').to.equal(optionalParameters.section); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow({ ...requiredParameters, ...optionalParameters })).to.equal(true); + }); + + it('Should respond with a valid response when required parameters and segments are provided', (done) => { + const segments = ['foo', 'bar']; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + segments, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('us').to.deep.equal(segments); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required parameters and userId are provided', (done) => { + const userId = 'user-id'; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + userId, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('ui').to.equal(userId); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required parameters and testCells are provided', (done) => { + const testCells = { foo: 'bar' }; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + testCells, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property(`ef-${Object.keys(testCells)[0]}`).to.equal(Object.values(testCells)[0]); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required and optional parameters are provided', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('search_query').to.equal(requiredParameters.searchQuery); + expect(requestParams).to.have.property('matched_facet').to.equal(requiredParameters.matchedFacet); + expect(requestParams).to.have.property('matched_query').to.equal(requiredParameters.matchedQuery); + expect(requestParams).to.have.property('section').to.equal(optionalParameters.section); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(Object.assign(requiredParameters, optionalParameters))) + .to.equal(true); + }); + + it('Should throw an error when invalid quizId is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerShow({ ...requiredParameters, quizId: 1 })).to.be.an('error'); + }); + + it('Should throw an error when invalid searchQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerShow({ ...requiredParameters, searchQuery: null })).to.be.an('error'); + }); + + it('Should throw an error when invalid matchedFacet is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerShow({ ...requiredParameters, matchedFacet: 1 })).to.be.an('error'); + }); + + it('Should throw an error when invalid matchedQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerShow({ ...requiredParameters, matchedQuery: 1 })).to.be.an('error'); + }); + + it('Should throw an error when invalid section is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerShow({ ...requiredParameters, section: 1 })).to.be.an('error'); + }); + + it('Should throw an error when no quizId is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const { quizId: _, ...rest } = requiredParameters; + expect(tracker.trackQuizTriggerShow(rest)).to.be.an('error'); + }); + + it('Should throw an error when no searchQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const { searchQuery: _, ...rest } = requiredParameters; + expect(tracker.trackQuizTriggerShow(rest)).to.be.an('error'); + }); + + it('Should throw an error when either matchedFacet or matchedQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const { matchedFacet: _, matchedQuery: __, ...rest } = requiredParameters; + expect(tracker.trackQuizTriggerShow(rest)).to.be.an('error'); + }); + + it('Should throw an error when invalid parameters are provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerShow([])).to.be.an('error'); + }); + + it('Should throw an error when no parameters are provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerShow()).to.be.an('error'); + }); + + it('Should send along origin_referrer query param if sendReferrerWithTrackingEvents is true', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + sendReferrerWithTrackingEvents: true, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + validateOriginReferrer(requestParams); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + + it('Should not send along origin_referrer query param if sendReferrerWithTrackingEvents is false', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + sendReferrerWithTrackingEvents: false, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.not.have.property('origin_referrer'); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + + if (!skipNetworkTimeoutTests) { + it('Should be rejected when network request timeout is provided and reached', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + ...requestQueueOptions, + }); + + tracker.on('error', ({ message }) => { + expect(message).to.equal(timeoutRejectionMessage); + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters, { timeout: 10 })).to.equal(true); + }); + + it('Should be rejected when global network request timeout is provided and reached', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + networkParameters: { + timeout: 20, + }, + ...requestQueueOptions, + }); + + tracker.on('error', ({ message }) => { + expect(message).to.equal(timeoutRejectionMessage); + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + } + + it('Should properly encode query parameters', (done) => { + const specialCharacters = '+[]&'; + const userId = `user-id ${specialCharacters}`; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + userId, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('ui').to.equal(userId); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + + it('Should properly transform non-breaking spaces in parameters', (done) => { + const breakingSpaces = '   '; + const userId = `user-id ${breakingSpaces} user-id`; + const userIdExpected = 'user-id user-id'; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + userId, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('ui').to.equal(userIdExpected); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerShow(requiredParameters)).to.equal(true); + }); + }); + + describe('trackQuizTriggerClick', () => { + const requiredParameters = { + quizId: 'coffee-quiz', + searchQuery: 'coff', + matchedFacet: 'coffee_facet', + matchedQuery: 'nescafe', + }; + const optionalParameters = { + section: 'Products', + }; + + it('Backwards Compatibility - Should respond with a valid response when snake cased parameters are provided', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const snakeCaseParameters = { + quiz_id: 'coffee-quiz', + search_query: 'nescafe', + matched_facet: 'coffee_facet', + matched_query: 'nescafe', + }; + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('key'); + expect(requestParams).to.have.property('i'); + expect(requestParams).to.have.property('s'); + expect(requestParams).to.have.property('c').to.equal(clientVersion); + expect(requestParams).to.have.property('_dt'); + validateOriginReferrer(requestParams); + expect(requestParams).to.have.property('quiz_id').to.equal(snakeCaseParameters.quiz_id); + expect(requestParams).to.have.property('search_query').to.equal(snakeCaseParameters.search_query); + expect(requestParams).to.have.property('matched_facet').to.equal(snakeCaseParameters.matched_facet); + expect(requestParams).to.have.property('matched_query').to.equal(snakeCaseParameters.matched_query); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(snakeCaseParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required parameters are provided', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('key'); + expect(requestParams).to.have.property('i'); + expect(requestParams).to.have.property('s'); + expect(requestParams).to.have.property('c').to.equal(clientVersion); + expect(requestParams).to.have.property('_dt'); + validateOriginReferrer(requestParams); + expect(requestParams).to.have.property('quiz_id').to.equal(requiredParameters.quizId); + expect(requestParams).to.have.property('search_query').to.equal(requiredParameters.searchQuery); + expect(requestParams).to.have.property('matched_facet').to.equal(requiredParameters.matchedFacet); + expect(requestParams).to.have.property('matched_query').to.equal(requiredParameters.matchedQuery); + expect(requestParams).to.have.property('section').to.equal(optionalParameters.section); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick({ ...requiredParameters, ...optionalParameters })).to.equal(true); + }); + + it('Should respond with a valid response when required parameters and segments are provided', (done) => { + const segments = ['foo', 'bar']; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + segments, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('us').to.deep.equal(segments); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required parameters and userId are provided', (done) => { + const userId = 'user-id'; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + userId, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('ui').to.equal(userId); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required parameters and testCells are provided', (done) => { + const testCells = { foo: 'bar' }; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + testCells, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property(`ef-${Object.keys(testCells)[0]}`).to.equal(Object.values(testCells)[0]); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + + it('Should respond with a valid response when required and optional parameters are provided', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('search_query').to.equal(requiredParameters.searchQuery); + expect(requestParams).to.have.property('matched_facet').to.equal(requiredParameters.matchedFacet); + expect(requestParams).to.have.property('matched_query').to.equal(requiredParameters.matchedQuery); + expect(requestParams).to.have.property('section').to.equal(optionalParameters.section); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(Object.assign(requiredParameters, optionalParameters))) + .to.equal(true); + }); + + it('Should throw an error when invalid quizId is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerClick({ ...requiredParameters, quizId: 1 })).to.be.an('error'); + }); + + it('Should throw an error when invalid searchQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerClick({ ...requiredParameters, searchQuery: null })).to.be.an('error'); + }); + + it('Should throw an error when invalid matchedFacet is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerClick({ ...requiredParameters, matchedFacet: 1 })).to.be.an('error'); + }); + + it('Should throw an error when invalid matchedQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerClick({ ...requiredParameters, matchedQuery: 1 })).to.be.an('error'); + }); + + it('Should throw an error when invalid section is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerClick({ ...requiredParameters, section: 1 })).to.be.an('error'); + }); + + it('Should throw an error when no quizId is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const { quizId: _, ...rest } = requiredParameters; + expect(tracker.trackQuizTriggerClick(rest)).to.be.an('error'); + }); + + it('Should throw an error when no searchQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const { searchQuery: _, ...rest } = requiredParameters; + expect(tracker.trackQuizTriggerClick(rest)).to.be.an('error'); + }); + + it('Should throw an error when either matchedFacet or matchedQuery is provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const { matchedFacet: _, matchedQuery: __, ...rest } = requiredParameters; + expect(tracker.trackQuizTriggerClick(rest)).to.be.an('error'); + }); + + it('Should throw an error when invalid parameters are provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerClick([])).to.be.an('error'); + }); + + it('Should throw an error when no parameters are provided', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + + expect(tracker.trackQuizTriggerClick()).to.be.an('error'); + }); + + it('Should send along origin_referrer query param if sendReferrerWithTrackingEvents is true', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + sendReferrerWithTrackingEvents: true, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + validateOriginReferrer(requestParams); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + + it('Should not send along origin_referrer query param if sendReferrerWithTrackingEvents is false', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + sendReferrerWithTrackingEvents: false, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.not.have.property('origin_referrer'); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + + if (!skipNetworkTimeoutTests) { + it('Should be rejected when network request timeout is provided and reached', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + ...requestQueueOptions, + }); + + tracker.on('error', ({ message }) => { + expect(message).to.equal(timeoutRejectionMessage); + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters, { timeout: 10 })).to.equal(true); + }); + + it('Should be rejected when global network request timeout is provided and reached', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + networkParameters: { + timeout: 20, + }, + ...requestQueueOptions, + }); + + tracker.on('error', ({ message }) => { + expect(message).to.equal(timeoutRejectionMessage); + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + } + + it('Should properly encode query parameters', (done) => { + const specialCharacters = '+[]&'; + const userId = `user-id ${specialCharacters}`; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + userId, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('ui').to.equal(userId); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + + it('Should properly transform non-breaking spaces in parameters', (done) => { + const breakingSpaces = '   '; + const userId = `user-id ${breakingSpaces} user-id`; + const userIdExpected = 'user-id user-id'; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + userId, + fetch: fetchSpy, + ...requestQueueOptions, + }); + + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('ui').to.equal(userIdExpected); + + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message').to.equal('ok'); + + done(); + }); + + expect(tracker.trackQuizTriggerClick(requiredParameters)).to.equal(true); + }); + }); + describe('on', () => { it('Should throw an error when providing an invalid messageType parameter', () => { const { tracker } = new ConstructorIO({ apiKey: testApiKey }); diff --git a/src/modules/tracker.js b/src/modules/tracker.js index 702e3a05..b74b75c7 100644 --- a/src/modules/tracker.js +++ b/src/modules/tracker.js @@ -2430,6 +2430,196 @@ class Tracker { return new Error('parameters are required of type object'); } + /** + * Send quiz trigger show event to API + * + * @function trackQuizTriggerShow + * @param {object} parameters - Additional parameters to be sent with request + * @param {string} parameters.quizId - Quiz identifier + * @param {string} parameters.matchedFacet - Matched facet for this quiz + * @param {string} parameters.matchedQuery - Matched query for this quiz + * @param {string} parameters.searchQuery - Typed search query for this event to be triggered + * @param {string} [parameters.section='Products'] - Index section + * @param {object} [networkParameters] - Parameters relevant to the network request + * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) + * @returns {(true|Error)} + * @description User typed a search query and it caused a quiz trigger to pop up + * @example + * constructorio.tracker.trackQuizTriggerShow( + * { + * quizId: 'coffee-quiz', + * matchedFacet: 'coffee_facet', + * matchedQuery: 'nescafe', + * searchQuery: 'coff', + * }, + * ); + */ + trackQuizTriggerShow(parameters, networkParameters = {}) { + // Ensure parameters are provided (required) + if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) { + const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/quiz_trigger_show?`; + const { + quiz_id, + quizId = quiz_id, + section = 'Products', + matched_facet, + matchedFacet = matched_facet, + search_query, + searchQuery = search_query, + matched_query, + matchedQuery = matched_query, + } = parameters; + const queryParams = {}; + const bodyParams = {}; + + if (typeof quizId !== 'string') { + return new Error('"quizId" is a required parameter of type string'); + } + + if (typeof searchQuery !== 'string') { + return new Error('"searchQuery" is a required parameter of type string'); + } + + if (typeof matchedFacet !== 'string' && typeof matchedQuery !== 'string') { + return new Error('"Either matchedFacet or matchedQuery" is a required parameter of type string'); + } + + if (matchedFacet && typeof matchedFacet !== 'string') { + return new Error('If matchedFacet is present, it has to be a string'); + } + + if (matchedQuery && typeof matchedQuery !== 'string') { + return new Error('If matchedQuery is present, it has to be a string'); + } + + bodyParams.quiz_id = quizId; + bodyParams.search_query = searchQuery; + bodyParams.matched_facet = matchedFacet; + bodyParams.matched_query = matchedQuery; + + if (!helpers.isNil(section)) { + if (typeof section !== 'string') { + return new Error('"section" must be a string'); + } + queryParams.section = section; + bodyParams.section = section; + } + + const requestURL = `${requestPath}${applyParamsAsString(queryParams, this.options)}`; + const requestMethod = 'POST'; + const requestBody = applyParams(bodyParams, { ...this.options, requestMethod }); + + this.requests.queue( + requestURL, + requestMethod, + requestBody, + networkParameters, + ); + this.requests.send(); + + return true; + } + + this.requests.send(); + + return new Error('parameters are required of type object'); + } + + /** + * Send quiz trigger click event to API + * + * @function trackQuizTriggerClick + * @param {object} parameters - Additional parameters to be sent with request + * @param {string} parameters.quizId - Quiz identifier + * @param {string} parameters.matchedFacet - Matched facet for this quiz + * @param {string} parameters.matchedQuery - Matched query for this quiz + * @param {string} parameters.searchQuery - Typed search query for this event to be triggered + * @param {string} [parameters.section='Products'] - Index section + * @param {object} [networkParameters] - Parameters relevant to the network request + * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) + * @returns {(true|Error)} + * @description Triggered whenever user clicks on a quiz trigger popup + * @example + * constructorio.tracker.trackQuizTriggerClick( + * { + * quizId: 'coffee-quiz', + * matchedFacet: 'coffee_facet', + * matchedQuery: 'nescafe', + * searchQuery: 'coff', + * }, + * ); + */ + trackQuizTriggerClick(parameters, networkParameters = {}) { + // Ensure parameters are provided (required) + if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) { + const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/quiz_trigger_click?`; + const { + quiz_id, + quizId = quiz_id, + section = 'Products', + matched_facet, + matchedFacet = matched_facet, + search_query, + searchQuery = search_query, + matched_query, + matchedQuery = matched_query, + } = parameters; + const queryParams = {}; + const bodyParams = {}; + + if (typeof quizId !== 'string') { + return new Error('"quizId" is a required parameter of type string'); + } + + if (typeof searchQuery !== 'string') { + return new Error('"searchQuery" is a required parameter of type string'); + } + + if (typeof matchedFacet !== 'string' && typeof matchedQuery !== 'string') { + return new Error('"Either matchedFacet or matchedQuery" is a required parameter of type string'); + } + + if (matchedFacet && typeof matchedFacet !== 'string') { + return new Error('If matchedFacet is present, it has to be a string'); + } + + if (matchedQuery && typeof matchedQuery !== 'string') { + return new Error('If matchedQuery is present, it has to be a string'); + } + + bodyParams.quiz_id = quizId; + bodyParams.matched_facet = matchedFacet; + bodyParams.matched_query = matchedQuery; + bodyParams.search_query = searchQuery; + + if (!helpers.isNil(section)) { + if (typeof section !== 'string') { + return new Error('"section" must be a string'); + } + queryParams.section = section; + bodyParams.section = section; + } + + const requestURL = `${requestPath}${applyParamsAsString(queryParams, this.options)}`; + const requestMethod = 'POST'; + const requestBody = applyParams(bodyParams, { ...this.options, requestMethod }); + + this.requests.queue( + requestURL, + requestMethod, + requestBody, + networkParameters, + ); + this.requests.send(); + + return true; + } + + this.requests.send(); + + return new Error('parameters are required of type object'); + } + /** * Send ASA request submitted event * diff --git a/src/types/tracker.d.ts b/src/types/tracker.d.ts index 7f2d16e3..1a420435 100644 --- a/src/types/tracker.d.ts +++ b/src/types/tracker.d.ts @@ -270,6 +270,28 @@ declare class Tracker { networkParameters?: NetworkParameters ): true | Error; + trackQuizTriggerShow( + parameters: { + quizId: string; + searchQuery: string; + section?: string; + matchedFacet?: string; + matchedQuery?: string; + }, + networkParameters?: NetworkParameters + ): true | Error; + + trackQuizTriggerClick( + parameters: { + quizId: string; + searchQuery: string; + section?: string; + matchedFacet?: string; + matchedQuery?: string; + }, + networkParameters?: NetworkParameters + ): true | Error; + trackAgentSubmit( parameters: { intent: string;