diff --git a/gulpfile.js b/gulpfile.js index b47ad562..29f5c1a1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -135,7 +135,7 @@ gulp.task( gulp.series('build:rollup', function () { return gulp .src('dist/pathfora.js') - .pipe(replace('`{{apiurl}}`', 'https://c.lytics.io')) + .pipe(replace('`{{apiurl}}`', APIURL)) .pipe(replace('`{{cssurl}}`', CSSURL)) .pipe(replace('`{{templates}}`', prepareTemplates())) .pipe(gulp.dest('dist')) @@ -176,7 +176,7 @@ gulp.task( return gulp .src('dist/pathfora.js') .pipe(replace('`{{apiurl}}`', TESTAPIURL)) - .pipe(replace('`{{cssurl}`}', TESTCSSURL)) + .pipe(replace('`{{cssurl}}`', TESTCSSURL)) .pipe(replace('`{{templates}}`', prepareTemplates())) .pipe(gulp.dest('dist')) .pipe(connect.reload()); diff --git a/test/acceptance/ab-testing.spec.js b/test/acceptance/ab-testing.spec.js index a540a7c4..d49e7d12 100644 --- a/test/acceptance/ab-testing.spec.js +++ b/test/acceptance/ab-testing.spec.js @@ -1,30 +1,28 @@ import globalReset from '../utils/global-reset'; +import { createMessageWidget } from '../utils/test-helpers'; -// ------------------------- -// A/B TESTING -// ------------------------- -describe('when performing AB testing', function () { +describe('a/b testing', function () { beforeEach(function () { globalReset(); }); it('should select only one A/B Test group to show', function () { - var widgetA = new pathfora.Message({ + var widgetA = createMessageWidget({ id: 'ab-widget1-a', msg: 'A', - layout: 'slideout' + layout: 'slideout', }); - var widgetB = new pathfora.Message({ + var widgetB = createMessageWidget({ id: 'ab-widget1-b', msg: 'B', - layout: 'slideout' + layout: 'slideout', }); var ab = new pathfora.ABTest({ id: 'ab-1', type: '50/50', - groups: [[widgetA], [widgetB]] + groups: [[widgetA], [widgetB]], }); pathfora.initializeABTesting([ab]); @@ -35,34 +33,37 @@ describe('when performing AB testing', function () { }); it('should show all widgets in an A/B test group', function () { - var widget1A = new pathfora.Message({ + var widget1A = createMessageWidget({ id: 'ab-widget2-1a', msg: 'A', - layout: 'slideout' + layout: 'slideout', }); - var widget2A = new pathfora.Message({ + var widget2A = createMessageWidget({ id: 'ab-widget2-2a', msg: 'A', - layout: 'slideout' + layout: 'slideout', }); - var widget1B = new pathfora.Message({ + var widget1B = createMessageWidget({ id: 'ab-widget2-1b', msg: 'B', - layout: 'slideout' + layout: 'slideout', }); - var widget2B = new pathfora.Message({ + var widget2B = createMessageWidget({ id: 'ab-widget2-2b', msg: 'B', - layout: 'slideout' + layout: 'slideout', }); var ab = new pathfora.ABTest({ id: 'ab-2', type: '50/50', - groups: [[widget1A, widget2A], [widget1B, widget2B]] + groups: [ + [widget1A, widget2A], + [widget1B, widget2B], + ], }); pathfora.initializeABTesting([ab]); @@ -73,10 +74,7 @@ describe('when performing AB testing', function () { var first = w.first(); expect(first.find('.pf-widget-message').text()).toEqual( - first - .next() - .find('.pf-widget-message') - .text() + first.next().find('.pf-widget-message').text() ); }); @@ -85,29 +83,29 @@ describe('when performing AB testing', function () { pathfora.utils.write('PathforaTest_' + id, 0.2164252290967852); - var widgetA = new pathfora.Message({ + var widgetA = createMessageWidget({ id: 'ab-widget3-a', msg: 'A', - layout: 'modal' + layout: 'modal', }); - var widgetB = new pathfora.Message({ + var widgetB = createMessageWidget({ id: 'ab-widget3-b', msg: 'B', - layout: 'modal' + layout: 'modal', }); var ab = new pathfora.ABTest({ id: id, type: '50/50', - groups: [[widgetA], [widgetB]] + groups: [[widgetA], [widgetB]], }); pathfora.initializeABTesting([ab]); pathfora.initializeWidgets([widgetA, widgetB]); var wB = $('#' + widgetB.id), - wA = $('#' + widgetA.id); + wA = $('#' + widgetA.id); expect(wB.length).toBe(1); expect(wA.length).toBe(0); }); @@ -116,77 +114,77 @@ describe('when performing AB testing', function () { var id = 'ab-4'; pathfora.utils.saveCookie('PathforaTest_' + id, 0.7077720651868731); - var widgetA = new pathfora.Message({ + var widgetA = createMessageWidget({ id: 'ab-widget4-a', msg: 'A', - layout: 'modal' + layout: 'modal', }); - var widgetB = new pathfora.Message({ + var widgetB = createMessageWidget({ id: 'ab-widget4-b', msg: 'B', - layout: 'modal' + layout: 'modal', }); var ab = new pathfora.ABTest({ id: id, type: '50/50', - groups: [[widgetA], [widgetB]] + groups: [[widgetA], [widgetB]], }); pathfora.initializeABTesting([ab]); pathfora.initializeWidgets([widgetA, widgetB]); var wB = $('#' + widgetB.id), - wA = $('#' + widgetA.id); + wA = $('#' + widgetA.id); expect(wA.length).toBe(1); expect(wB.length).toBe(0); }); it('should allow multiple A/B tests per page', function () { - var widgetA = new pathfora.Message({ + var widgetA = createMessageWidget({ id: 'ab-widget5-a', msg: 'A', - layout: 'modal' + layout: 'modal', }); - var widgetB = new pathfora.Message({ + var widgetB = createMessageWidget({ id: 'ab-widget5-b', msg: 'B', - layout: 'modal' + layout: 'modal', }); var ab = new pathfora.ABTest({ id: 'ab-5', type: '50/50', - groups: [[widgetA], [widgetB]] + groups: [[widgetA], [widgetB]], }); - var widgetC = new pathfora.Message({ + var widgetC = createMessageWidget({ id: 'ab-widget6-c', msg: 'C', - layout: 'modal' + layout: 'modal', }); - var widgetD = new pathfora.Message({ + var widgetD = createMessageWidget({ id: 'ab-widget6-d', msg: 'D', - layout: 'modal' + layout: 'modal', }); var ab2 = new pathfora.ABTest({ id: 'ab-6', type: '50/50', - groups: [[widgetC], [widgetD]] + groups: [[widgetC], [widgetD]], }); pathfora.initializeABTesting([ab, ab2]); pathfora.initializeWidgets([widgetA, widgetB, widgetC, widgetD]); var w = $('[id*="ab-widget"]'), - w5 = $('[id*="ab-widget5"]'), - w6 = $('[id*="ab-widget6"]'); + w5 = $('[id*="ab-widget5"]'), + w6 = $('[id*="ab-widget6"]'); expect(w.length).toBe(2); expect(w5.length).toBe(1); @@ -196,40 +194,40 @@ describe('when performing AB testing', function () { it('should handle A/B Tests in conjunction with audience targeting', function () { window.lio = { data: { - segments: ['all', 'smt_new'] + segments: ['all', 'smt_new'], }, account: { - id: '0' - } + id: '0', + }, }; window.lio.loaded = true; - var widgetA = new pathfora.Message({ + var widgetA = createMessageWidget({ id: 'ab-widget10-a', layout: 'slideout', - msg: 'A' + msg: 'A', }); - var widgetB = new pathfora.Message({ + var widgetB = createMessageWidget({ id: 'ab-widget10-b', layout: 'slideout', - msg: 'B' + msg: 'B', }); var ab = new pathfora.ABTest({ id: 'ab-10', type: '50/50', - groups: [[widgetA], [widgetB]] + groups: [[widgetA], [widgetB]], }); var widgets = { target: [ { segment: 'smt_new', - widgets: [widgetA, widgetB] - } - ] + widgets: [widgetA, widgetB], + }, + ], }; pathfora.initializeABTesting([ab]); @@ -243,16 +241,16 @@ describe('when performing AB testing', function () { var id = 'ab-11'; pathfora.utils.saveCookie('PathforaTest_' + id, 0.7077720651868731); - var widget = new pathfora.Message({ + var widget = createMessageWidget({ id: 'ab-widget11-a', msg: 'A', - layout: 'slideout' + layout: 'slideout', }); var ab = new pathfora.ABTest({ id: 'ab-11', type: '80/20', - groups: [[], [widget]] + groups: [[], [widget]], }); pathfora.initializeABTesting([ab]); @@ -263,22 +261,22 @@ describe('when performing AB testing', function () { }); it('should not allow a widget to be used in more than one A/B test', function () { - var widgetA = new pathfora.Message({ + var widgetA = createMessageWidget({ id: 'ab-widget8-a', msg: 'A', - layout: 'slideout' + layout: 'slideout', }); var ab = new pathfora.ABTest({ id: 'ab-7', type: '50/50', - groups: [[widgetA], []] + groups: [[widgetA], []], }); var ab2 = new pathfora.ABTest({ id: 'ab-8', type: '50/50', - groups: [[widgetA], []] + groups: [[widgetA], []], }); expect(function () { @@ -291,28 +289,28 @@ describe('when performing AB testing', function () { }); it('should not allow a widget to be used in more than one A/B test', function () { - var widgetA = new pathfora.Message({ + var widgetA = createMessageWidget({ id: 'ab-widget9-a', msg: 'A', - layout: 'slideout' + layout: 'slideout', }); - var widgetB = new pathfora.Message({ + var widgetB = createMessageWidget({ id: 'ab-widget9-b', msg: 'B', - layout: 'slideout' + layout: 'slideout', }); var ab = new pathfora.ABTest({ id: 'ab-9', type: '50/50', - groups: [[widgetA], []] + groups: [[widgetA], []], }); var ab2 = new pathfora.ABTest({ id: 'ab-9', type: '50/50', - groups: [[widgetB], []] + groups: [[widgetB], []], }); expect(function () { diff --git a/test/acceptance/callbacks.spec.js b/test/acceptance/callbacks.spec.js new file mode 100644 index 00000000..b0d55bd1 --- /dev/null +++ b/test/acceptance/callbacks.spec.js @@ -0,0 +1,215 @@ +import globalReset from '../utils/global-reset'; +import { createMessageWidget, createFormWidget } from '../utils/test-helpers'; + +describe('callbacks', function () { + beforeEach(function () { + globalReset(); + }); + + it('should trigger after pressing action button', function () { + var modal = createMessageWidget({ + id: 'confirm-action-test', + layout: 'modal', + msg: 'Confirm action test modal', + confirmAction: { + name: 'Test confirm action', + callback: function () { + alert('test confirmation'); + }, + }, + }); + + pathfora.initializeWidgets([modal]); + + var widget = $('#confirm-action-test'); + spyOn(modal.confirmAction, 'callback'); + expect(modal.confirmAction.callback).not.toHaveBeenCalled(); + widget.find('.pf-widget-ok').click(); + expect(modal.confirmAction.callback).toHaveBeenCalled(); + }); + + it('should trigger after pressing action with form data.', function () { + var modal = createFormWidget({ + id: 'confirm-action-form-test', + layout: 'modal', + msg: 'Confirm action test modal', + confirmAction: { + callback: function () { + alert('test confirmation'); + }, + }, + }); + + pathfora.initializeWidgets([modal]); + + var widget = $('#' + modal.id); + widget.find('input[name="username"]').val('test name'); + widget.find('input[name="email"]').val('test@example.com'); + spyOn(modal.confirmAction, 'callback'); + expect(modal.confirmAction.callback).not.toHaveBeenCalled(); + widget.find('.pf-widget-ok').click(); + expect(modal.confirmAction.callback).toHaveBeenCalledWith( + 'modalConfirm', + jasmine.objectContaining({ + data: [ + { name: 'username', value: 'test name' }, + { name: 'email', value: 'test@example.com' }, + { name: 'title', value: '' }, + { name: 'message', value: '' }, + ], + }) + ); + }); + + it('should trigger after pressing action with custom form data.', function () { + var modal = createFormWidget({ + id: 'custom-confirm-action-test', + layout: 'modal', + msg: 'Confirm action test modal', + formElements: [ + { + type: 'text', + required: true, + label: 'Email Address', + name: 'email', + }, + { + type: 'checkbox-group', + required: true, + label: 'Which feeds would you like to subscribe to?', + name: 'subscription_feeds', + values: [ + { + label: 'Beauty & Perfumes', + value: 'beauty', + }, + { + label: 'Electronics', + value: 'electronics', + }, + { + label: 'Fashion', + value: 'fashion', + }, + ], + }, + ], + confirmAction: { + callback: function () { + alert('test confirmation'); + }, + }, + }); + + pathfora.initializeWidgets([modal]); + + var widget = $('#' + modal.id); + widget.find('input[name="email"]').val('test@example.com'); + widget.find('input[name="subscription_feeds"]')[2].checked = true; + spyOn(modal.confirmAction, 'callback'); + expect(modal.confirmAction.callback).not.toHaveBeenCalled(); + widget.find('.pf-widget-ok').click(); + expect(modal.confirmAction.callback).toHaveBeenCalledWith( + 'modalConfirm', + jasmine.objectContaining({ + data: [ + { name: 'email', value: 'test@example.com' }, + { name: 'subscription_feeds', value: 'fashion' }, + ], + }) + ); + }); + + it('should not close the modal on a button action if specified', function (done) { + var modal = createMessageWidget({ + id: 'confirm-close-action-test', + layout: 'modal', + msg: 'Confirm action test modal', + confirmAction: { + name: 'Test confirm action', + close: false, + callback: function () { + // do something + }, + }, + cancelAction: { + close: false, + }, + }); + + pathfora.initializeWidgets([modal]); + + setTimeout(function () { + var widget = $('#' + modal.id); + expect(widget).toBeDefined(); + expect(widget.hasClass('opened')).toBeTruthy(); + + setTimeout(function () { + widget.find('.pf-widget-ok').click(); + widget.find('.pf-widget-cancel').click(); + expect(widget).toBeDefined(); + expect(widget.hasClass('opened')).toBeTruthy(); + done(); + }, 300); + }, 300); + }); + + it('should be able to trigger action on cancel', function () { + var modal = createMessageWidget({ + id: 'cancel-action-test', + layout: 'modal', + msg: 'Welcome to our website', + cancelAction: { + name: 'Test cancel action', + callback: function () { + alert('test cancel'); + }, + }, + }); + + pathfora.initializeWidgets([modal]); + + var widget = $('#cancel-action-test'); + spyOn(modal.cancelAction, 'callback'); + widget.find('.pf-widget-cancel').click(); + expect(modal.cancelAction.callback).toHaveBeenCalled(); + }); + + it("shouldn't fire submit function on cancel, and cancel functions on submit", function () { + var w1 = createMessageWidget({ + id: 'widget-with-action-callback', + msg: 'Cancel action negative test', + confirmAction: { + name: 'Test confirm action', + callback: function () { + alert('test confirmation'); + }, + }, + }); + + var w2 = createMessageWidget({ + id: 'widget-with-cancel-callback', + msg: 'Cancel action negative test', + cancelAction: { + name: 'Test cancel action', + callback: function () { + alert('test cancel'); + }, + }, + }); + + pathfora.initializeWidgets([w1, w2]); + + var widgetA = $('#widget-with-action-callback'), + widgetB = $('#widget-with-cancel-callback'); + + spyOn(w1.confirmAction, 'callback'); + spyOn(w2.cancelAction, 'callback'); + + widgetA.find('.pf-widget-cancel').click(); + expect(w1.confirmAction.callback).not.toHaveBeenCalled(); + + widgetB.find('.pf-widget-ok').click(); + expect(w2.cancelAction.callback).not.toHaveBeenCalled(); + }); +}); diff --git a/test/acceptance/content-recommendation.spec.js b/test/acceptance/content-recommendation.spec.js index c2b41bb7..1409044d 100644 --- a/test/acceptance/content-recommendation.spec.js +++ b/test/acceptance/content-recommendation.spec.js @@ -1,8 +1,6 @@ import globalReset from '../utils/global-reset'; +import { createMessageWidget } from '../utils/test-helpers'; -// ------------------------- -// CONTENT RECOMMENDATIONS -// ------------------------- describe('the content recommendation component', function () { beforeEach(function () { globalReset(); @@ -25,7 +23,7 @@ describe('the content recommendation component', function () { pathfora.acctid = 321; - var sampleModal = new pathfora.Message({ + var sampleModal = createMessageWidget({ id: 'recommendation-modal-sample1', msg: 'A', layout: 'modal', @@ -69,7 +67,7 @@ describe('the content recommendation component', function () { window.liosetup.value = 'customValue'; pathfora.acctid = 321; - var sampleModal = new pathfora.Message({ + var sampleModal = createMessageWidget({ id: 'recommendation-modal-sample2', msg: 'A', layout: 'modal', @@ -111,7 +109,7 @@ describe('the content recommendation component', function () { pathfora.acctid = 321; - var sampleModal = new pathfora.Message({ + var sampleModal = createMessageWidget({ id: 'recommendation-modal-sample3', msg: 'A', layout: 'modal', @@ -148,7 +146,7 @@ describe('the content recommendation component', function () { loaded: true, }; - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'recommendation-modal', msg: 'A', layout: 'modal', @@ -159,7 +157,7 @@ describe('the content recommendation component', function () { }, }); - var defaultModal = new pathfora.Message({ + var defaultModal = createMessageWidget({ id: 'recommendation-modal2', msg: 'A', layout: 'modal', @@ -255,7 +253,7 @@ describe('the content recommendation component', function () { loaded: true, }; - var errorModal = new pathfora.Message({ + var errorModal = createMessageWidget({ id: 'recommendation-modal4', msg: 'A', layout: 'modal', @@ -266,7 +264,7 @@ describe('the content recommendation component', function () { }, }); - var errorModal2 = new pathfora.Message({ + var errorModal2 = createMessageWidget({ id: 'recommendation-modal5', msg: 'A', layout: 'button', @@ -277,7 +275,7 @@ describe('the content recommendation component', function () { }, }); - var errorModal3 = new pathfora.Message({ + var errorModal3 = createMessageWidget({ id: 'recommendation-modal6', msg: 'A', layout: 'slideout', @@ -347,7 +345,7 @@ describe('the content recommendation component', function () { loaded: true, }; - var astModal = new pathfora.Message({ + var astModal = createMessageWidget({ id: 'ast-modal', msg: 'A', layout: 'modal', @@ -391,7 +389,7 @@ describe('the content recommendation component', function () { loaded: true, }; - var relativeModal = new pathfora.Message({ + var relativeModal = createMessageWidget({ id: 'relative-modal', msg: 'A', layout: 'modal', @@ -434,7 +432,7 @@ describe('the content recommendation component', function () { loaded: true, }; - var displayModal = new pathfora.Message({ + var displayModal = createMessageWidget({ id: 'recDisplayModal', msg: 'A', layout: 'modal', @@ -476,7 +474,7 @@ describe('the content recommendation component', function () { expect(info.html()).toBe('by Test Example | January 1, 2017'); expect(desc.html().length < 103).toBeTruthy(); - var displayModal2 = new pathfora.Message({ + var displayModal2 = createMessageWidget({ id: 'recDisplayModal2', msg: 'A', layout: 'modal', @@ -524,7 +522,7 @@ describe('the content recommendation component', function () { loaded: true, }; - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'modal1', layout: 'modal', msg: 'test', @@ -575,7 +573,7 @@ describe('the content recommendation component', function () { loaded: true, }; - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'modal1', layout: 'modal', msg: 'test', diff --git a/test/acceptance/cull-expired-localstorage-on-init.spec.js b/test/acceptance/cull-expired-localstorage-on-init.spec.js deleted file mode 100644 index 1a7b5bcd..00000000 --- a/test/acceptance/cull-expired-localstorage-on-init.spec.js +++ /dev/null @@ -1,20 +0,0 @@ -import globalReset from '../utils/global-reset'; - -describe('Culling expired localStorage on init', function () { - beforeEach(globalReset); - - it('should cull expired records from localStorage eagerly on init', function () { - var Pathfora = pathfora.constructor; - - pathfora.utils.store.ttl('expired', 'bonk', -10000); - pathfora.utils.store.ttl('current', 'bonk', 10000); - - expect(localStorage.getItem('expired')).not.toBe(null); - expect(localStorage.getItem('current')).not.toBe(null); - - new Pathfora(); - - expect(localStorage.getItem('expired')).toBe(null); - expect(localStorage.getItem('current')).not.toBe(null); - }); -}); diff --git a/test/acceptance/display-conditions-legacy.spec.js b/test/acceptance/display-conditions-legacy.spec.js index ff4895bf..1ec29802 100644 --- a/test/acceptance/display-conditions-legacy.spec.js +++ b/test/acceptance/display-conditions-legacy.spec.js @@ -1,9 +1,7 @@ import globalReset from '../utils/global-reset'; +import { createFormWidget } from '../utils/test-helpers'; -// ------------------------- -// DISPLAY CONDITIONS LEGACY -// ------------------------- -describe('when setting display conditions', function () { +describe('when setting legacy display conditions', function () { beforeEach(function () { globalReset(); }); @@ -12,7 +10,7 @@ describe('when setting display conditions', function () { var widgetId = 'legacyImpressionWidget1'; sessionStorage.setItem('PathforaImpressions_' + widgetId, 0); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -21,9 +19,9 @@ describe('when setting display conditions', function () { displayConditions: { impressions: { session: 1, - total: 5 - } - } + total: 5, + }, + }, }); pathfora.initializeWidgets([form]); @@ -36,7 +34,7 @@ describe('when setting display conditions', function () { var widgetId = 'legacyImpressionWidget2'; sessionStorage.setItem('PathforaImpressions_' + widgetId, 2); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -45,9 +43,9 @@ describe('when setting display conditions', function () { displayConditions: { impressions: { session: 1, - total: 5 - } - } + total: 5, + }, + }, }); pathfora.initializeWidgets([form]); @@ -58,12 +56,9 @@ describe('when setting display conditions', function () { it('should show if impression buffer met', function () { var widgetId = 'legacyImpressionWidget3'; - pathfora.utils.write( - 'PathforaImpressions_' + widgetId, - '2|' + Date.now() - ); + pathfora.utils.write('PathforaImpressions_' + widgetId, '2|' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -72,9 +67,9 @@ describe('when setting display conditions', function () { displayConditions: { impressions: { session: 3, - buffer: 2 - } - } + buffer: 2, + }, + }, }); setTimeout(function () { @@ -87,12 +82,9 @@ describe('when setting display conditions', function () { it('should not show if impression buffer not met', function () { var widgetId = 'legacyImpressionWidget3'; - pathfora.utils.write( - 'PathforaImpressions_' + widgetId, - '2|' + Date.now() - ); + pathfora.utils.write('PathforaImpressions_' + widgetId, '2|' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -101,9 +93,9 @@ describe('when setting display conditions', function () { displayConditions: { impressions: { session: 3, - buffer: 60 - } - } + buffer: 60, + }, + }, }); pathfora.initializeWidgets([form]); @@ -115,12 +107,9 @@ describe('when setting display conditions', function () { // NOTE Retain support for cookies with comma - can remove on 5/2/2016 it('should accept and parse impression cookies with comma values', function () { var widgetId = 'impressionComma'; - pathfora.utils.write( - 'PathforaImpressions_' + widgetId, - '2,' + Date.now() - ); + pathfora.utils.write('PathforaImpressions_' + widgetId, '2,' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -128,9 +117,9 @@ describe('when setting display conditions', function () { position: 'bottom-right', displayConditions: { impressions: { - total: 2 - } - } + total: 2, + }, + }, }); pathfora.initializeWidgets([form]); @@ -141,46 +130,46 @@ describe('when setting display conditions', function () { it('should consider multiple display conditions and watchers', function (done) { var id = 'multiple-conditions', - id2 = 'multiple-conditions-2', - id3 = 'multiple-conditions-3'; + id2 = 'multiple-conditions-2', + id3 = 'multiple-conditions-3'; - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', id: id, displayConditions: { impressions: { - session: 3 + session: 3, }, - manualTrigger: true - } + manualTrigger: true, + }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', id: id2, displayConditions: { impressions: { - session: 1 + session: 1, }, - manualTrigger: true - } + manualTrigger: true, + }, }); - var form3 = new pathfora.Form({ + var form3 = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', id: id3, displayConditions: { impressions: { - session: 3 + session: 3, }, - manualTrigger: true - } + manualTrigger: true, + }, }); sessionStorage.setItem('PathforaImpressions_' + id, 2); sessionStorage.setItem('PathforaImpressions_' + id2, 2); @@ -211,7 +200,7 @@ describe('when setting display conditions', function () { pathfora.utils.write('PathforaCancel_' + widgetId, '1,' + Date.now()); pathfora.utils.write('PathforaClosed_' + widgetId, '1,' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -221,16 +210,16 @@ describe('when setting display conditions', function () { hideAfterAction: { confirm: { hideCount: 3, - duration: 1440 + duration: 1440, }, cancel: { - hideCount: 1 + hideCount: 1, }, closed: { - duration: 30 - } - } - } + duration: 30, + }, + }, + }, }); pathfora.initializeWidgets([form]); @@ -242,12 +231,9 @@ describe('when setting display conditions', function () { // NOTE Retain support for cookies with comma - can remove on 5/2/2016 it('should accept and parse impression cookies with comma values', function () { var widgetId = 'impressionComma'; - pathfora.utils.write( - 'PathforaImpressions_' + widgetId, - '2,' + Date.now() - ); + pathfora.utils.write('PathforaImpressions_' + widgetId, '2,' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -256,10 +242,10 @@ describe('when setting display conditions', function () { displayConditions: { impressions: { widget: { - total: 2 - } - } - } + total: 2, + }, + }, + }, }); pathfora.initializeWidgets([form]); diff --git a/test/acceptance/display-conditions.spec.js b/test/acceptance/display-conditions.spec.js index a3855d08..29809fac 100644 --- a/test/acceptance/display-conditions.spec.js +++ b/test/acceptance/display-conditions.spec.js @@ -1,8 +1,6 @@ import globalReset from '../utils/global-reset'; +import { createMessageWidget, createFormWidget } from '../utils/test-helpers'; -// ------------------------- -// DISPLAY CONDITIONS -// ------------------------- function makeMouseEvent(type, params) { var evt; try { @@ -45,7 +43,7 @@ describe('when setting display conditions', function () { var height = $(document.body).height(); window.scroll(0, height); - var subscription = new pathfora.Message({ + var subscription = createMessageWidget({ layout: 'modal', id: 'scrollModal', headline: 'Heyyyy!', @@ -76,7 +74,7 @@ describe('when setting display conditions', function () { "
Test
" ); - var subscription = new pathfora.Message({ + var subscription = createMessageWidget({ layout: 'modal', id: 'scrollModal', headline: 'Heyyyy!', @@ -105,7 +103,7 @@ describe('when setting display conditions', function () { }); it('should show when all manualTrigger widgets are triggered', function () { - var customWidget = new pathfora.Message({ + var customWidget = createMessageWidget({ msg: 'custom trigger test', id: 'custom-widget', layout: 'modal', @@ -114,7 +112,7 @@ describe('when setting display conditions', function () { }, }); - var customWidget2 = new pathfora.Message({ + var customWidget2 = createMessageWidget({ msg: 'custom trigger test2', id: 'custom-widget2', layout: 'modal', @@ -141,7 +139,7 @@ describe('when setting display conditions', function () { }); it('should show all manualTrigger widgets on initialization if they have already been triggered', function () { - var customWidget3 = new pathfora.Message({ + var customWidget3 = createMessageWidget({ msg: 'custom trigger test3', id: 'custom-widget3', layout: 'modal', @@ -155,7 +153,7 @@ describe('when setting display conditions', function () { var widget = $('#' + customWidget3.id); expect(widget.length).toBe(1); - var customWidget4 = new pathfora.Message({ + var customWidget4 = createMessageWidget({ msg: 'custom trigger test4', id: 'custom-widget4', layout: 'modal', @@ -173,7 +171,7 @@ describe('when setting display conditions', function () { it('should be able to show after specified time', function () { jasmine.clock().install(); - var delayedWidget = new pathfora.Message({ + var delayedWidget = createMessageWidget({ msg: 'Delayed widget test', id: 'delayed-widget', layout: 'modal', @@ -196,7 +194,7 @@ describe('when setting display conditions', function () { it('should not show when page views requirement has not been reached', function () { pathfora.utils.saveCookie('PathforaPageView', 0); - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', id: 'page-view-widget-1', headline: 'Header', @@ -214,7 +212,7 @@ describe('when setting display conditions', function () { }); it('should show when page views requirement has been reached', function () { - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', id: 'page-view-widget-2', @@ -237,7 +235,7 @@ describe('when setting display conditions', function () { limitDate.setMonth(1); limitDate.setFullYear(2016); - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', id: 'date-widget-1', @@ -262,7 +260,7 @@ describe('when setting display conditions', function () { limitDate.setMonth(1); limitDate.setFullYear(2016); - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -285,7 +283,7 @@ describe('when setting display conditions', function () { var widgetId = 'hideAfterActionWidget1'; pathfora.utils.saveCookie('PathforaClosed_' + widgetId, '1|' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -310,7 +308,7 @@ describe('when setting display conditions', function () { var widgetId = 'hideAfterActionWidget2'; pathfora.utils.saveCookie('PathforaConfirm_' + widgetId, '1|' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -338,7 +336,7 @@ describe('when setting display conditions', function () { var widgetId = 'hideAfterActionWidget3'; pathfora.utils.saveCookie('PathforaCancel_' + widgetId, '2|' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -362,7 +360,7 @@ describe('when setting display conditions', function () { var widgetId = 'hideAfterActionWidget4'; pathfora.utils.saveCookie('PathforaConfirm_' + widgetId, '2|' + Date.now()); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -390,7 +388,7 @@ describe('when setting display conditions', function () { var widgetId = 'impressionWidget1'; sessionStorage.setItem('PathforaImpressions_' + widgetId, 0); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -419,7 +417,7 @@ describe('when setting display conditions', function () { '2|' + Date.now() ); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -450,7 +448,7 @@ describe('when setting display conditions', function () { '2|' + Date.now() ); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -478,7 +476,7 @@ describe('when setting display conditions', function () { var widgetId = 'impressionWidget2'; sessionStorage.setItem('PathforaImpressions_' + widgetId, 2); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -509,7 +507,7 @@ describe('when setting display conditions', function () { sessionStorage.setItem('PathforaImpressions_AnotherWidget', 0); sessionStorage.setItem('PathforaImpressions_' + widgetId, 0); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -536,7 +534,7 @@ describe('when setting display conditions', function () { sessionStorage.setItem('PathforaImpressions_b' + widgetId, 2); sessionStorage.setItem('PathforaImpressions_c' + widgetId, 2); - var form = new pathfora.Form({ + var form = createFormWidget({ id: widgetId, msg: 'subscription', headline: 'Header', @@ -565,7 +563,7 @@ describe('when setting display conditions', function () { m2; beforeEach(function () { - m1 = new pathfora.Form({ + m1 = createFormWidget({ id: m1id, msg: 'modal 1', layout: 'slideout', @@ -579,7 +577,7 @@ describe('when setting display conditions', function () { }, }); - m2 = new pathfora.Form({ + m2 = createFormWidget({ id: m2id, msg: 'modal 2', layout: 'slideout', @@ -630,7 +628,7 @@ describe('when setting display conditions', function () { m2; beforeEach(function () { - m1 = new pathfora.Form({ + m1 = createFormWidget({ id: m1id, msg: 'modal 1', layout: 'slideout', @@ -645,7 +643,7 @@ describe('when setting display conditions', function () { }, }); - m2 = new pathfora.Form({ + m2 = createFormWidget({ id: m2id, msg: 'modal 2', layout: 'slideout', @@ -733,7 +731,7 @@ describe('when setting display conditions', function () { }); it('should show when the url matches the display conditions', function () { - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -744,7 +742,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -764,7 +762,7 @@ describe('when setting display conditions', function () { }); it("should not show when the url doesn't match the display conditions", function () { - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -782,7 +780,7 @@ describe('when setting display conditions', function () { }); it('should show respect excluded matching rule', function () { - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -819,7 +817,7 @@ describe('when setting display conditions', function () { }); it('should show using simple match', function () { - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: '88ee86cf72b44e67bf758cc743ac1a5d', msg: 'subscription', headline: 'Header', @@ -835,7 +833,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: 'a793b7352c3346e493573a6827be7815', msg: 'subscription', headline: 'Header', @@ -861,7 +859,7 @@ describe('when setting display conditions', function () { }); it('should show using exact match', function () { - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: 'e71c5416ac7345bcba8c5330d14c4a2e', msg: 'subscription', headline: 'Header', @@ -877,7 +875,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: '3ef7653e7f5f4889a0f2f860a679639a', msg: 'subscription', headline: 'Header', @@ -902,7 +900,7 @@ describe('when setting display conditions', function () { }); it('should show using string match', function () { - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: '3044aae3e5ad463fbd868a626a7998ca', msg: 'subscription', headline: 'Header', @@ -918,7 +916,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: 'd66ec2855d284cb2b6ce3edd3c756a1b', msg: 'subscription', headline: 'Header', @@ -934,7 +932,7 @@ describe('when setting display conditions', function () { }, }); - var form3 = new pathfora.Form({ + var form3 = createFormWidget({ id: 'f3ededaa19fd4301b066b4da5758e16a', msg: 'subscription', headline: 'Header', @@ -963,7 +961,7 @@ describe('when setting display conditions', function () { }); it('should show using regex match', function () { - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: '87a84e6f0d5d480595eebaf5de76693f', msg: 'subscription', headline: 'Header', @@ -978,7 +976,7 @@ describe('when setting display conditions', function () { ], }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: '3ecbf9717fef4f7c80b2bbc70193ab64', msg: 'subscription', headline: 'Header', @@ -993,7 +991,7 @@ describe('when setting display conditions', function () { ], }, }); - var form3 = new pathfora.Form({ + var form3 = createFormWidget({ id: 'e9890969538c49d4ba9c7f516215fa61', msg: 'subscription', headline: 'Header', @@ -1008,7 +1006,7 @@ describe('when setting display conditions', function () { ], }, }); - var form4 = new pathfora.Form({ + var form4 = createFormWidget({ id: 'ad547747786249ae8ba9e1cc3f5b86cf', msg: 'subscription', headline: 'Header', @@ -1041,7 +1039,7 @@ describe('when setting display conditions', function () { it('should ignore trailing slashes for the exact match rule', function () { window.history.pushState({}, '', '/test/'); - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: 'e71c5416ac7345bcba8c5330d14c4a2e', msg: 'subscription', headline: 'Header', @@ -1056,7 +1054,7 @@ describe('when setting display conditions', function () { ], }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: '3ef7653e7f5f4889a0f2f860a679639a', msg: 'subscription', headline: 'Header', @@ -1085,7 +1083,7 @@ describe('when setting display conditions', function () { it('should ignore trailing slashes in the simple match rule', function () { window.history.pushState({}, '', '/test/'); - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: 'simple-match1', msg: 'subscription', headline: 'Header', @@ -1101,7 +1099,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: 'simple-match2', msg: 'subscription', headline: 'Header', @@ -1131,7 +1129,7 @@ describe('when setting display conditions', function () { it('should ignore order of query params for exact rule', function () { window.history.pushState({}, '', '/context.html?bar=2&foo=1'); - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: 'f41a595548c54321a4e12b613c466159', msg: 'subscription', headline: 'Header', @@ -1147,7 +1145,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: 'ef2848a4949d4474b3a5d12ba1017eb7', msg: 'subscription', headline: 'Header', @@ -1177,7 +1175,7 @@ describe('when setting display conditions', function () { it('should not ignore "?" if there are no queries', function () { window.history.pushState({}, '', '/context.html'); - var queryTest1 = new pathfora.Form({ + var queryTest1 = createFormWidget({ id: 'query-test1', headline: 'Header', layout: 'slideout', @@ -1195,7 +1193,7 @@ describe('when setting display conditions', function () { }, }); - var queryTest2 = new pathfora.Form({ + var queryTest2 = createFormWidget({ id: 'query-test2', headline: 'Header', layout: 'slideout', @@ -1229,7 +1227,7 @@ describe('when setting display conditions', function () { '/context.html?bar=2&foo=1&lytics_variation_preview_id=7b26ca56afb84669bba0bf0810ec459f' ); - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: '7b26ca56afb84669bba0bf0810ec459f', msg: 'subscription', headline: 'Header', @@ -1256,7 +1254,7 @@ describe('when setting display conditions', function () { it('should ignore order of query params and extra params for string rule', function () { window.history.pushState({}, '', '/context.html?bar=2&foo=1&baz=3'); - var form1 = new pathfora.Form({ + var form1 = createFormWidget({ id: '339f97d11af84630add78cfd39da1105', msg: 'subscription', headline: 'Header', @@ -1272,7 +1270,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ id: 'f8cc3cdf8a1c4532a1ebbc1e7af453b1', msg: 'subscription', headline: 'Header', @@ -1283,7 +1281,7 @@ describe('when setting display conditions', function () { }, }); - var form3 = new pathfora.Form({ + var form3 = createFormWidget({ id: '6372bf4e1acc45d695b45a8656dd19ec', msg: 'subscription', headline: 'Header', @@ -1294,7 +1292,7 @@ describe('when setting display conditions', function () { }, }); - var form4 = new pathfora.Form({ + var form4 = createFormWidget({ id: '9c353546a52843f9868ca1b3a1012f6e', msg: 'subscription', headline: 'Header', @@ -1328,7 +1326,7 @@ describe('when setting display conditions', function () { }); it('should consider multiple display conditions', function () { - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', id: 'display-widget-1', @@ -1339,7 +1337,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ msg: 'subscription', headline: 'Header', id: 'display-widget-2', @@ -1350,7 +1348,7 @@ describe('when setting display conditions', function () { }, }); - var form3 = new pathfora.Form({ + var form3 = createFormWidget({ msg: 'subscription', headline: 'Header', id: 'display-widget-3', @@ -1373,7 +1371,7 @@ describe('when setting display conditions', function () { id2 = 'multiple-conditions-2', id3 = 'multiple-conditions-3'; - var form = new pathfora.Form({ + var form = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -1388,7 +1386,7 @@ describe('when setting display conditions', function () { }, }); - var form2 = new pathfora.Form({ + var form2 = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -1403,7 +1401,7 @@ describe('when setting display conditions', function () { }, }); - var form3 = new pathfora.Form({ + var form3 = createFormWidget({ msg: 'subscription', headline: 'Header', layout: 'slideout', @@ -1460,7 +1458,7 @@ describe('when setting display conditions', function () { describe('by itself', function () { beforeEach(function () { - subscription = new pathfora.Message({ + subscription = createMessageWidget({ layout: 'modal', id: id, headline: "Don't leave yet!", @@ -1535,7 +1533,7 @@ describe('when setting display conditions', function () { window.scroll(0, 0); - subscription = new pathfora.Message({ + subscription = createMessageWidget({ layout: 'modal', id: id, headline: "Don't leave yet!", @@ -1591,7 +1589,7 @@ describe('when setting display conditions', function () { var modal; beforeEach(function () { - modal = new pathfora.Message({ + modal = createMessageWidget({ layout: 'modal', id: id, headline: 'This will show...', diff --git a/test/acceptance/entity-template.spec.js b/test/acceptance/entity-template.spec.js index 55b5cf13..5a44ed91 100644 --- a/test/acceptance/entity-template.spec.js +++ b/test/acceptance/entity-template.spec.js @@ -1,8 +1,6 @@ import globalReset from '../utils/global-reset'; +import { createMessageWidget, createFormWidget } from '../utils/test-helpers'; -// ------------------------- -// ENTITY FIELD TEMPLATES -// ------------------------- describe('the entity templates', function () { beforeEach(function () { globalReset(); @@ -12,82 +10,82 @@ describe('the entity templates', function () { window.lio = { data: { promoCode: '123FREE', - email: 'fake@gmail.com' + email: 'fake@gmail.com', }, account: { - id: '0' - } + id: '0', + }, }; window.lio.loaded = true; pathfora.customData = { - customField: 'test' + customField: 'test', }; - var fieldWidget1 = new pathfora.Message({ + var fieldWidget1 = createMessageWidget({ id: 'field-widget-1', layout: 'slideout', headline: 'Free shipping on your next purchase', - msg: 'Enter this promo code: {{promoCode}}' + msg: 'Enter this promo code: {{promoCode}}', }); - var fieldWidget2 = new pathfora.Form({ + var fieldWidget2 = createFormWidget({ id: 'field-widget-2', layout: 'slideout', headline: '{{email | no email provided}}', - msg: 'Sign up with your email.' + msg: 'Sign up with your email.', }); - var fieldWidget3 = new pathfora.Form({ + var fieldWidget3 = createFormWidget({ id: 'field-widget-3', layout: 'slideout', headline: 'Hi {{name}}', - msg: 'Sign up with your email.' + msg: 'Sign up with your email.', }); - var fieldWidget4 = new pathfora.Form({ + var fieldWidget4 = createFormWidget({ id: 'field-widget-4', layout: 'slideout', headline: 'Hi {{name}}', msg: 'Sign up with your email.', displayConditions: { - showOnMissingFields: true - } + showOnMissingFields: true, + }, }); - var fieldWidget5 = new pathfora.Form({ + var fieldWidget5 = createFormWidget({ id: 'field-widget-5', layout: 'slideout', headline: 'Welcome', - msg: 'Welcome {{name | No Name}}!' + msg: 'Welcome {{name | No Name}}!', }); - var fieldWidget6 = new pathfora.Form({ + var fieldWidget6 = createFormWidget({ id: 'field-widget-6', layout: 'slideout', headline: 'Welcome', - msg: 'Welcome {{myUrl | https://www.google.com/}}!' + msg: 'Welcome {{myUrl | https://www.google.com/}}!', }); - var fieldWidget7 = new pathfora.Form({ + var fieldWidget7 = createFormWidget({ id: 'field-widget-7', layout: 'slideout', headline: 'Welcome', - msg: 'Welcome {{customField | fail}}!' + msg: 'Welcome {{customField | fail}}!', }); - var fieldWidget8 = new pathfora.Form({ + var fieldWidget8 = createFormWidget({ id: 'fieldWidget8', layout: 'slideout', headline: 'Welcome', msg: 'my {{data | string}} will get {{data2}} defaulted wrong', displayConditions: { - showOnMissingFields: true - } + showOnMissingFields: true, + }, }); - var fieldWidget9 = new pathfora.Message({ + var fieldWidget9 = createMessageWidget({ id: 'fieldWidget9', layout: 'slideout', headline: 'Welcome', @@ -97,8 +95,8 @@ describe('the entity templates', function () { callback: function () { window.customvar = 'https://www.google.com/?promo={{promoCode}}&email={{email}}'; - } - } + }, + }, }); pathfora.initializeWidgets([ @@ -110,7 +108,7 @@ describe('the entity templates', function () { fieldWidget6, fieldWidget7, fieldWidget8, - fieldWidget9 + fieldWidget9, ]); var w1 = $('#' + fieldWidget1.id); @@ -170,30 +168,30 @@ describe('the entity templates', function () { data: { userName: 'John', userEmail: 'john@example.com', - promoCode: 'SAVE20' + promoCode: 'SAVE20', }, account: { - id: '0' - } + id: '0', + }, }; window.lio.loaded = true; - var templateWidget1 = new pathfora.Message({ + var templateWidget1 = createMessageWidget({ id: 'template-dependency-01', layout: 'modal', headline: 'Hello {{userName}}!', - msg: 'Welcome back!' + msg: 'Welcome back!', }); - var templateWidget2 = new pathfora.Form({ + var templateWidget2 = createFormWidget({ id: 'template-dependency-02', layout: 'slideout', headline: 'Special offer for {{userEmail}}', - msg: 'Use code {{promoCode}} for savings!' + msg: 'Use code {{promoCode}} for savings!', }); - var templateWidget3 = new pathfora.Message({ + var templateWidget3 = createMessageWidget({ id: 'template-dependency-03', layout: 'modal', headline: 'Hi there!', @@ -201,15 +199,16 @@ describe('the entity templates', function () { confirmAction: { name: 'confirm', callback: function () { - window.trackingUrl = 'https://analytics.com/track?user={{userName}}&promo={{promoCode}}'; - } - } + window.trackingUrl = + 'https://analytics.com/track?user={{userName}}&promo={{promoCode}}'; + }, + }, }); pathfora.initializeWidgets([ templateWidget1, templateWidget2, - templateWidget3 + templateWidget3, ]); var dependencies = pathfora.getWidgetDependencies(); @@ -223,7 +222,9 @@ describe('the entity templates', function () { expect(dependencies[templateWidget2.id].entityField).toContain('promoCode'); expect(dependencies[templateWidget3.id]).toBeDefined(); - expect(dependencies[templateWidget3.id].entityField).toContain('contactEmail'); + expect(dependencies[templateWidget3.id].entityField).toContain( + 'contactEmail' + ); expect(dependencies[templateWidget3.id].entityField).toContain('userName'); expect(dependencies[templateWidget3.id].entityField).toContain('promoCode'); }); diff --git a/test/acceptance/forms.spec.js b/test/acceptance/forms.spec.js new file mode 100644 index 00000000..7d6d2136 --- /dev/null +++ b/test/acceptance/forms.spec.js @@ -0,0 +1,962 @@ +import globalReset from '../utils/global-reset'; +import { + createMessageWidget, + createFormWidget, + createSubscriptionWidget, +} from '../utils/test-helpers'; + +describe('forms', function () { + beforeEach(function () { + globalReset(); + }); + + it('should show success or error state if waitForAsyncResponse is set', function (done) { + var formStatesWidget = createFormWidget({ + id: 'form-states', + msg: 'subscription', + headline: 'Header', + layout: 'slideout', + confirmAction: { + waitForAsyncResponse: true, + callback: function (name, payload, cb) { + if (payload.data[0].value === 'test') { + cb(true); + return; + } + cb(false); + }, + }, + formStates: { + success: { + headline: 'test', + msg: 'a custom success message', + delay: 0, + okShow: true, + okMessage: 'confirm success', + confirmAction: { + name: 'confirm success', + callback: function () { + alert('confirm success'); + }, + }, + cancelShow: true, + cancelMessage: 'cancel success', + cancelAction: { + name: 'cancel success', + callback: function () { + alert('cancel success'); + }, + }, + }, + error: { + headline: 'test', + msg: 'a custom error message', + delay: 0, + okShow: true, + okMessage: 'confirm error', + confirmAction: { + name: 'confirm error', + callback: function () { + alert('confirm error'); + }, + }, + cancelShow: true, + cancelMessage: 'cancel error', + cancelAction: { + name: 'cancel error', + callback: function () { + alert('cancel error'); + }, + }, + }, + }, + }); + window.pathfora.initializeWidgets([formStatesWidget]); + + var widget = $('#' + formStatesWidget.id), + form = widget.find('form'); + expect(form.length).toBe(1); + + var name = form.find('input[name="username"]'); + expect(name.length).toBe(1); + name.val('test'); + + var email = form.find('input[name="email"]'); + expect(email.length).toBe(1); + email.val('test@example.com'); + + spyOn(formStatesWidget.confirmAction, 'callback').and.callThrough(); + expect(formStatesWidget.confirmAction.callback).not.toHaveBeenCalled(); + + form.find('.pf-widget-ok').click(); + + expect(formStatesWidget.confirmAction.callback).toHaveBeenCalledWith( + 'modalConfirm', + jasmine.any(Object), + jasmine.any(Function) + ); + + var success = widget.find('.success-state'), + error = widget.find('.error-state'); + + expect(form.css('display')).toBe('none'); + expect(success.css('display')).toBe('block'); + expect(widget.hasClass('success')).toBeTruthy(); + expect(success.find('.pf-widget-headline').html()).toBe( + formStatesWidget.formStates.success.headline + ); + expect(success.find('.pf-widget-message').html()).toBe( + formStatesWidget.formStates.success.msg + ); + + expect(success.find('.pf-widget-ok').html()).toBe( + formStatesWidget.formStates.success.okMessage + ); + expect(success.find('.pf-widget-cancel').html()).toBe( + formStatesWidget.formStates.success.cancelMessage + ); + + spyOn(jstag, 'send'); + spyOn(formStatesWidget.formStates.success.confirmAction, 'callback'); + expect( + formStatesWidget.formStates.success.confirmAction.callback + ).not.toHaveBeenCalled(); + success.find('.pf-widget-ok').click(); + + expect( + formStatesWidget.formStates.success.confirmAction.callback + ).toHaveBeenCalled(); + expect(jstag.send).toHaveBeenCalledWith( + jasmine.objectContaining({ + 'pf-widget-id': formStatesWidget.id, + 'pf-widget-type': 'form', + 'pf-widget-layout': 'slideout', + 'pf-widget-event': 'success.confirm', + 'pf-widget-action': + formStatesWidget.formStates.success.confirmAction.name, + }) + ); + pathfora.clearAll(); + pathfora.closeWidget(formStatesWidget.id, true); + + setTimeout(function () { + window.pathfora.initializeWidgets([formStatesWidget]); + + widget = $('#' + formStatesWidget.id); + form = widget.find('form'); + expect(form.length).toBe(1); + + name = form.find('input[name="username"]'); + expect(name.length).toBe(1); + name.val('bad'); + + email = form.find('input[name="email"]'); + expect(email.length).toBe(1); + email.val('bad@example.com'); + form.find('.pf-widget-ok').click(); + + success = widget.find('.success-state'); + expect(success.length).toBe(1); + error = widget.find('.error-state'); + expect(error.length).toBe(1); + expect(form.css('display')).toBe('none'); + expect(success.css('display')).toBe('none'); + expect(error.css('display')).toBe('block'); + expect(widget.hasClass('error')).toBeTruthy(); + expect(error.find('.pf-widget-headline').html()).toBe( + formStatesWidget.formStates.error.headline + ); + expect(error.find('.pf-widget-message').html()).toBe( + formStatesWidget.formStates.error.msg + ); + expect(error.find('.pf-widget-ok').html()).toBe( + formStatesWidget.formStates.error.okMessage + ); + expect(error.find('.pf-widget-cancel').html()).toBe( + formStatesWidget.formStates.error.cancelMessage + ); + + spyOn(formStatesWidget.formStates.error.cancelAction, 'callback'); + expect( + formStatesWidget.formStates.error.cancelAction.callback + ).not.toHaveBeenCalled(); + error.find('.pf-widget-cancel').click(); + + expect( + formStatesWidget.formStates.error.cancelAction.callback + ).toHaveBeenCalled(); + expect(jstag.send).toHaveBeenCalledWith( + jasmine.objectContaining({ + 'pf-widget-id': formStatesWidget.id, + 'pf-widget-type': 'form', + 'pf-widget-layout': 'slideout', + 'pf-widget-event': 'error.cancel', + 'pf-widget-action': + formStatesWidget.formStates.error.cancelAction.name, + }) + ); + done(); + }, 600); + }); + + // ------------------------- + // LEGACY SUCCESS STATES + // ------------------------- + + it('should show success state if one is set by the user', function (done) { + var successForm = createSubscriptionWidget({ + id: 'success-form', + msg: 'subscription', + headline: 'Header', + layout: 'slideout', + success: { + msg: 'a custom success message', + delay: 2, + }, + }); + + pathfora.initializeWidgets([successForm]); + + var widget = $('#' + successForm.id); + var form = widget.find('form'); + expect(form.length).toBe(1); + + var email = form.find('input[name="email"]'); + expect(email.length).toBe(1); + email.val('test@example.com'); + form.find('.pf-widget-ok').click(); + + var success = widget.find('.success-state'); + + expect(form.css('display')).toBe('none'); + expect(success.css('display')).toBe('block'); + expect(widget.hasClass('success')).toBeTruthy(); + expect(success.find('.pf-widget-message').html()).toBe( + successForm.success.msg + ); + expect(success.find('.pf-widget-ok')).toBeUndefined; + expect(success.find('.pf-widget-cancel')).toBeUndefined; + + setTimeout(function () { + expect(widget.hasClass('opened')).toBeFalsy(); + done(); + }, 2000); + }); + + it('should not hide the module if the success state delay is 0', function (done) { + var successForm2 = createSubscriptionWidget({ + id: 'success-form-no-delay', + msg: 'subscription', + headline: 'Header', + layout: 'slideout', + success: { + msg: 'a custom success message', + delay: 0, + }, + }); + + pathfora.initializeWidgets([successForm2]); + + var widget = $('#' + successForm2.id); + var form = widget.find('form'); + expect(form.length).toBe(1); + + var email = form.find('input[name="email"]'); + expect(email.length).toBe(1); + email.val('test@example.com'); + form.find('.pf-widget-ok').click(); + + var success = widget.find('.success-state'); + + expect(form.css('display')).toBe('none'); + expect(success.css('display')).toBe('block'); + expect(widget.hasClass('success')).toBeTruthy(); + expect(success.find('.pf-widget-message').html()).toBe( + successForm2.success.msg + ); + expect(success.find('.pf-widget-ok')).toBeUndefined; + expect(success.find('.pf-widget-cancel')).toBeUndefined; + + setTimeout(function () { + expect(widget.hasClass('opened')).toBeTruthy(); + expect(widget.hasClass('success')).toBeTruthy(); + + done(); + }, 3000); + }); + + it('should recognize success state buttons and callbacks', function (done) { + var successForm3 = createSubscriptionWidget({ + id: 'success-form-cbs', + msg: 'subscription', + headline: 'Header', + layout: 'slideout', + success: { + headline: 'test', + msg: 'a custom success message', + okShow: true, + cancelShow: true, + cancelMessage: 'Custom Cancel', + confirmAction: { + name: 'test success confirmation', + callback: function () { + window.alert('confirmed'); + }, + }, + cancelAction: { + name: 'test success cancelation', + callback: function () { + window.alert('canceled'); + }, + }, + delay: 0, + }, + }); + + pathfora.initializeWidgets([successForm3]); + var widget = $('#' + successForm3.id); + var form = widget.find('form'); + form.find('input[name="email"]').val('test@example.com'); + form.find('.pf-widget-ok').click(); + + var success = widget.find('.success-state'); + expect(form.css('display')).toBe('none'); + expect(success.css('display')).toBe('block'); + expect(widget.hasClass('success')).toBeTruthy(); + expect(success.find('.pf-widget-headline').html()).toBe( + successForm3.success.headline + ); + expect(success.find('.pf-widget-message').html()).toBe( + successForm3.success.msg + ); + + expect(success.find('.pf-widget-ok').html()).toBe('Confirm'); + expect(success.find('.pf-widget-cancel').html()).toBe('Custom Cancel'); + + spyOn(jstag, 'send'); + spyOn(window, 'alert'); + success.find('.pf-widget-ok').click(); + + expect(jstag.send).toHaveBeenCalledWith( + jasmine.objectContaining({ + 'pf-widget-id': successForm3.id, + 'pf-widget-type': 'subscription', + 'pf-widget-layout': 'slideout', + 'pf-widget-event': 'success.confirm', + 'pf-widget-action': successForm3.success.confirmAction.name, + }) + ); + expect(window.alert).toHaveBeenCalledWith('confirmed'); + + setTimeout(function () { + pathfora.clearAll(); + pathfora.initializeWidgets([successForm3]); + + widget = $('#' + successForm3.id); + form = widget.find('form'); + form.find('input[name="email"]').val('test@example.com'); + form.find('.pf-widget-ok').click(); + + success = widget.find('.success-state'); + success.find('.pf-widget-cancel').click(); + + expect(jstag.send).toHaveBeenCalledWith( + jasmine.objectContaining({ + 'pf-widget-id': successForm3.id, + 'pf-widget-type': 'subscription', + 'pf-widget-layout': 'slideout', + 'pf-widget-event': 'success.cancel', + 'pf-widget-action': successForm3.success.cancelAction.name, + }) + ); + expect(window.alert).toHaveBeenCalledWith('canceled'); + + setTimeout(function () { + done(); + }, 1000); + }, 1000); + }); + + // ------------------------- + // CUSTOM BUTTONS + // ------------------------- + + it('should be able to configure custom text', function () { + var modal = createMessageWidget({ + id: 'custom-button-text-test', + layout: 'modal', + msg: 'Custom button text test', + headline: 'Hello', + okMessage: 'Confirm Here', + cancelMessage: 'Cancel Here', + }); + + pathfora.initializeWidgets([modal]); + + var widget = $('#' + modal.id), + actionBtn = widget.find('.pf-widget-ok'), + cancelBtn = widget.find('.pf-widget-cancel'); + + expect(actionBtn.html()).toBe('Confirm Here'); + expect(cancelBtn.html()).toBe('Cancel Here'); + }); + + // ------------------------- + // OLD CUSTOM FIELDS + // ------------------------- + + it('should be able to hide and show fields based on config', function () { + var formfields = createFormWidget({ + id: 'sample-form', + msg: 'subscription', + headline: 'Header', + layout: 'slideout', + fields: { + title: false, + username: false, + }, + required: { + message: true, + email: false, + }, + }); + + pathfora.initializeWidgets([formfields]); + + var theform = $('#' + formfields.id).find('form'); + expect(theform.length).toBe(1); + + for (var elem in theform[0].children) { + if (typeof theform[0].children[elem].getAttribute !== 'undefined') { + var inputname = theform[0].children[elem].getAttribute('name'), + inputrequired = + theform[0].children[elem].getAttribute('data-required'); + + if (inputname === 'message') { + expect(inputrequired).toBe('true'); + } else if (inputname !== null) { + expect(inputrequired).toBe(null); + } + + expect(inputname).not.toBe('username'); + expect(inputname).not.toBe('title'); + } + } + }); + + // ------------------------- + // FORM BUILDER + // ------------------------- + + it('should track custom fields to lytics', function (done) { + var customForm = createFormWidget({ + id: 'custom-form-1', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'input', + name: 'name', + placeholder: 'Your Name', + required: true, + }, + { + type: 'checkbox-group', + name: 'terms_agreement', + required: true, + values: [ + { + label: 'I agree', + value: 'agree', + }, + ], + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + widget.find('[name=terms_agreement]').click(); + widget.find('[name=name]').val('my name here'); + spyOn(jstag, 'send'); + + widget.find('form').find('.pf-widget-ok').click(); + + expect(jstag.send).toHaveBeenCalledWith( + jasmine.objectContaining({ + 'pf-widget-id': customForm.id, + 'pf-widget-type': 'form', + 'pf-widget-layout': 'slideout', + 'pf-widget-event': 'submit', + 'pf-custom-form': { + terms_agreement: ['agree'], + name: 'my name here', + }, + }) + ); + done(); + }); + + it('should add labels and placeholders for custom fields if defined', function () { + var customForm = createFormWidget({ + id: 'custom-form-2', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'select', + label: "What's your favorite animal?", + placeholder: 'Select an animal...', + name: 'favorite_animal', + required: true, + values: [ + { + label: 'Cat', + value: 'cat', + }, + { + label: 'Dog', + value: 'dog', + }, + { + label: 'Horse', + value: 'horse', + }, + ], + }, + { + type: 'checkbox-group', + label: 'Which ice cream flavors do you like the most?', + name: 'ice_cream_flavors', + required: true, + values: [ + { + label: 'Vanilla', + value: 'vanilla', + }, + { + label: 'Chocolate', + value: 'chocolate', + }, + { + label: 'Strawberry', + value: 'strawberry', + }, + ], + }, + { + type: 'textarea', + label: 'Comments', + name: 'comments', + placeholder: 'Any more comments?', + required: true, + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + var labels = widget.find('.pf-form-label'); + var divs = widget.find('.pf-has-label'); + + expect(labels.length).toBe(customForm.formElements.length); + expect(divs.length).toBe(customForm.formElements.length); + + var i; + + for (i = 0; i < labels.length; i++) { + expect( + labels[i].innerHTML.indexOf(customForm.formElements[i].label) !== -1 + ).toBeTruthy(); + } + + for (i = 0; i < divs.length; i++) { + var field = divs[i]; + var configElem = customForm.formElements[i]; + if (field.placeholder && configElem.placeholder) { + expect(field.placeholder).toBe(configElem.placeholder); + } + + if (configElem.type === 'select') { + expect(field.children[0].innerHTML).toBe(configElem.placeholder); + } + } + }); + + it('should not submit the form if required fields are not filled out', function (done) { + var customForm = new pathfora.Form({ + id: 'custom-form-3', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'input', + placeholder: "What's your favorite animal?", + name: 'favorite_animal', + required: true, + }, + { + type: 'radio-group', + label: 'Which ice cream flavors do you like the most?', + name: 'ice_cream_flavors', + required: true, + values: [ + { + label: 'Vanilla', + value: 'vanilla', + }, + { + label: 'Chocolate', + value: 'chocolate', + }, + { + label: 'Strawberry', + value: 'strawberry', + }, + ], + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + spyOn(jstag, 'send'); + + setTimeout(function () { + widget.find('form').find('.pf-widget-ok').click(); + expect(jstag.send).not.toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeTruthy(); + + var required = widget.find('[data-required=true]'); + expect(required.length).toBe(customForm.formElements.length); + + for (var i = 0; i < required.length; i++) { + var req = required[i].parentNode; + expect(req.className.indexOf('pf-form-required') !== -1).toBeTruthy(); + expect(req.className.indexOf('invalid') !== -1).toBeTruthy(); + } + done(); + }, 200); + }); + + it('should not submit the form if fields are invalid', function (done) { + var customForm = createFormWidget({ + id: 'custom-form-3', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'email', + placeholder: 'Email', + name: 'email', + required: true, + }, + { + type: 'radio-group', + label: 'Which ice cream flavors do you like the most?', + name: 'ice_cream_flavors', + values: [ + { + label: 'Vanilla', + value: 'vanilla', + }, + { + label: 'Chocolate', + value: 'chocolate', + }, + { + label: 'Strawberry', + value: 'strawberry', + }, + ], + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + spyOn(jstag, 'send'); + + setTimeout(function () { + widget.find('input[name=email]').val('zkjhfkdjh'); + widget.find('form').find('.pf-widget-ok').click(); + expect(jstag.send).not.toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeTruthy(); + + var invalid = widget.find('[data-validate=true]'); + expect(invalid.length).toBe(1); + + for (var i = 0; i < invalid.length; i++) { + var req = invalid[i].parentNode; + expect(req.className.indexOf('pf-form-required') !== -1).toBeTruthy(); + expect(req.className.indexOf('bad-validation') !== -1).toBeTruthy(); + } + + // also check required validation + widget.find('input[name=email]').val(''); + widget.find('form').find('.pf-widget-ok').click(); + expect(jstag.send).not.toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeTruthy(); + + invalid = widget.find('[data-required=true]'); + expect(invalid.length).toBe(1); + + for (var j = 0; j < invalid.length; j++) { + var reqField = invalid[j].parentNode; + expect( + reqField.className.indexOf('pf-form-required') !== -1 + ).toBeTruthy(); + expect(reqField.className.indexOf('invalid') !== -1).toBeTruthy(); + } + done(); + }, 200); + }); + + it('should not submit the form if a date field is invalid', function (done) { + var customForm = createFormWidget({ + id: 'custom-form-3', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'date', + name: 'birthday', + maxDate: 'today', + minDate: '01-01-2020', + }, + { + type: 'radio-group', + label: 'Which ice cream flavors do you like the most?', + name: 'ice_cream_flavors', + values: [ + { + label: 'Vanilla', + value: 'vanilla', + }, + { + label: 'Chocolate', + value: 'chocolate', + }, + { + label: 'Strawberry', + value: 'strawberry', + }, + ], + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + spyOn(jstag, 'send'); + + setTimeout(function () { + widget.find('input[name=birthday]').val('2010-10-10'); + widget.find('form').find('.pf-widget-ok').click(); + expect(jstag.send).not.toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeTruthy(); + + var invalid = widget.find('[data-validate=true]'); + expect(invalid.length).toBe(1); + + for (var i = 0; i < invalid.length; i++) { + var req = invalid[i].parentNode; + expect(req.className.indexOf('pf-form-required') !== -1).toBeTruthy(); + expect(req.className.indexOf('bad-validation') !== -1).toBeTruthy(); + } + done(); + }, 200); + }); + + // ------------------------- + // CUSTOM FORM VALIDATION + // ------------------------- + + it('should not submit the form if custom validation fails', function (done) { + var customForm = createFormWidget({ + id: 'custom-form-4', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'text', + placeholder: 'Only 5 Digits Allowed', + name: 'postal_code', + pattern: '^[0-9]{5}$', + required: true, + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + spyOn(jstag, 'send'); + + setTimeout(function () { + var form = widget.find('form'); + var field = form.find('input[name="postal_code"]'); + + field.val('notvalid'); + form.find('.pf-widget-ok').trigger('click'); + expect(jstag.send).not.toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeTruthy(); + + var required = widget.find('[data-required=true]'); + expect(required.length).toBe(customForm.formElements.length); + + for (var i = 0; i < required.length; i++) { + var req = required[i].parentNode; + expect(req.className.indexOf('invalid') !== -1).toBeFalsy(); + } + + done(); + }, 500); + }); + + it('should not submit the form if only 1 of 2 fields pass validation', function (done) { + var customForm = createFormWidget({ + id: 'custom-form-4', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'text', + placeholder: '6 Digits zbzbzb', + name: 'custom_field_1', + pattern: '^[zb]{6}$', + }, + { + type: 'text', + placeholder: 'Only 5 Digits Allowed', + name: 'custom_field_2', + pattern: '^[0-9]{5}$', + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + spyOn(jstag, 'send'); + + setTimeout(function () { + var form = widget.find('form'); + + var field1 = form.find('input[name="custom_field_1"]'); + field1.val('notvalid'); + + var field2 = form.find('input[name="custom_field_2"]'); + field2.val('12345'); + + form.find('.pf-widget-ok').trigger('click'); + + expect(jstag.send).not.toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeTruthy(); + + var validationRequirement = widget.find('[data-validate=true]'); + expect(validationRequirement.length).toBe(customForm.formElements.length); + + // expect field1 to be invalid + var req = validationRequirement[0].parentNode; + expect(req.className.indexOf('bad-validation') !== -1).toBeTruthy(); + + // expect field2 to be valid + req = validationRequirement[1].parentNode; + expect(req.className.indexOf('bad-validation') !== -1).toBeFalsy(); + + done(); + }, 500); + }); + + it('should submit the form if custom validation passes', function (done) { + var customForm = createFormWidget({ + id: 'custom-form-5', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'text', + placeholder: 'Only 5 Digits Allowed', + name: 'postal_code', + pattern: '^[0-9]{5}$', + required: true, + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + spyOn(jstag, 'send'); + + setTimeout(function () { + var form = widget.find('form'); + var field = form.find('input[name="postal_code"]'); + + field.val('12345'); + form.find('.pf-widget-ok').trigger('click'); + expect(jstag.send).toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeFalsy(); + + var required = widget.find('[data-required=true]'); + expect(required.length).toBe(customForm.formElements.length); + + for (var i = 0; i < required.length; i++) { + var req = required[i].parentNode; + expect(req.className.indexOf('invalid') !== -1).toBeFalsy(); + } + + done(); + }, 200); + }); + + it('should add validation parameters if special case of us-postal-code', function (done) { + var customForm = createFormWidget({ + id: 'custom-form-6', + msg: 'custom form', + layout: 'slideout', + formElements: [ + { + type: 'us-postal-code', + placeholder: 'Only 5 Digits Allowed', + name: 'postal_code', + required: true, + }, + ], + }); + + pathfora.initializeWidgets([customForm]); + + var widget = $('#' + customForm.id); + spyOn(jstag, 'send'); + + setTimeout(function () { + var form = widget.find('form'); + var field = form.find('input[name="postal_code"]'); + + var pattern = field.attr('enforcePattern'); + expect(pattern).toBe('^[0-9]{5}$'); + + field.val('1234a'); + form.find('.pf-widget-ok').trigger('click'); + expect(jstag.send).not.toHaveBeenCalled(); + expect(widget.hasClass('opened')).toBeTruthy(); + + var required = widget.find('[data-required=true]'); + expect(required.length).toBe(customForm.formElements.length); + + for (var i = 0; i < required.length; i++) { + var req = required[i].parentNode; + expect(req.className.indexOf('invalid') !== -1).toBeFalsy(); + } + + done(); + }, 500); + }); +}); diff --git a/test/acceptance/gate.spec.js b/test/acceptance/gate.spec.js index 4d225c1c..d85f5100 100644 --- a/test/acceptance/gate.spec.js +++ b/test/acceptance/gate.spec.js @@ -1,66 +1,62 @@ import globalReset from '../utils/global-reset'; +import { + createSiteGateWidget, + expectWidgetVisible, + expectWidgetClosed, +} from '../utils/test-helpers'; -// ------------------------- -// GATE -// ------------------------- describe('the gate component', function () { beforeEach(function () { globalReset(); }); it('should open gate when the record is not set', function (done) { - var gate = new pathfora.SiteGate({ + var gate = createSiteGateWidget({ headline: 'Blocking Widget', id: 'sitegate-widget-1', - msg: 'Submit this widget to access the website.' + msg: 'Submit this widget to access the website.', }); pathfora.initializeWidgets([gate]); - var widget = $('#' + gate.id); - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); + expectWidgetVisible(gate.id); done(); }, 200); }); it('should should work with showForm: false', function (done) { - var gate = new pathfora.SiteGate({ + var gate = createSiteGateWidget({ id: 'gate-hide-form', headline: 'Gated Site Feature', msg: 'Please agree to the terms to proceed.', showForm: false, - okMessage: 'I Agree' + okMessage: 'I Agree', }); expect(() => { pathfora.initializeWidgets([gate]); }).not.toThrow(); - var widget = $('#' + gate.id); - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); + expectWidgetVisible(gate.id); done(); }, 200); }); it('should not gate when the record is already set', function (done) { - var gate = new pathfora.SiteGate({ + var gate = createSiteGateWidget({ headline: 'Blocking Widget', id: 'sitegate-widget-2', - msg: 'Submit this widget to access the website.' + msg: 'Submit this widget to access the website.', }); pathfora.utils.write('PathforaUnlocked_' + gate.id, true); pathfora.initializeWidgets([gate]); - var widget = $('#' + gate.id); - setTimeout(function () { - expect(widget.hasClass('opened')).toBeFalsy(); + expectWidgetClosed(gate.id); done(); }, 200); }); diff --git a/test/acceptance/personalization.spec.js b/test/acceptance/inline-personalization.spec.js similarity index 97% rename from test/acceptance/personalization.spec.js rename to test/acceptance/inline-personalization.spec.js index abe293c3..a277cf02 100644 --- a/test/acceptance/personalization.spec.js +++ b/test/acceptance/inline-personalization.spec.js @@ -1,16 +1,11 @@ import globalReset from '../utils/global-reset'; +import { createMessageWidget } from '../utils/test-helpers'; -// ------------------------- -// INLINE PERSONALIZATION TEST -// ------------------------- -describe('Inline Personalization', function () { +describe('inline personalization', function () { beforeEach(function () { globalReset(); }); - // ------------------------- - // TRIGGER ELEMENTS - // ------------------------- describe('pftrigger elements', function () { it('should select to show the first matching element per group', function (done) { window.lio = { @@ -108,13 +103,13 @@ describe('Inline Personalization', function () { '
Has Email
' ); - var testModule = new pathfora.Message({ + var testModule = createMessageWidget({ id: '9ec53f71a1514339bb1552280ae76682', layout: 'slideout', msg: 'show this to people with an email', }); - var testModule2 = new pathfora.Message({ + var testModule2 = createMessageWidget({ id: 'ba6a6df43f774d769058950969b07a16', layout: 'slideout', msg: 'show this to people without an email', @@ -153,9 +148,6 @@ describe('Inline Personalization', function () { }); }); - // ------------------------- - // RECOMMENDATION ELEMENTS - // ------------------------- describe('pfrecommend elements', function () { beforeEach(function () { pathfora.acctid = 123; diff --git a/test/acceptance/pathfora.spec.js b/test/acceptance/pathfora.spec.js index cdb61598..bb1f7539 100644 --- a/test/acceptance/pathfora.spec.js +++ b/test/acceptance/pathfora.spec.js @@ -1,19 +1,17 @@ import createAndDispatchKeydown from '../utils/create-and-dispatch-keydown.js'; import globalReset from '../utils/global-reset'; +import { + createMessageWidget, + createSubscriptionWidget, +} from '../utils/test-helpers'; -('use strict'); - -// ------------------------- -// PATHFORA TESTS -// ------------------------- - -describe('Pathfora', function () { +describe('pathfora', function () { beforeEach(function () { globalReset(); }); it('should keep focus during a tab cycle in a modal or site gate', function (done) { - var modal = new pathfora.Message({ + var modal = createMessageWidget({ msg: 'msg', id: 'tab-cycle-test', layout: 'modal', @@ -45,7 +43,7 @@ describe('Pathfora', function () { describe('clearAll', function () { it('should clear delayed widgets', function () { jasmine.clock().install(); - var delayedWidget = new pathfora.Message({ + var delayedWidget = createMessageWidget({ msg: 'Delayed clear test', id: 'delayed-widget-clear', layout: 'modal', @@ -54,7 +52,7 @@ describe('Pathfora', function () { }, }); - var delayedWidget2 = new pathfora.Message({ + var delayedWidget2 = createMessageWidget({ msg: 'Delayed clear test', id: 'delayed-widget-clear2', layout: 'modal', @@ -63,7 +61,7 @@ describe('Pathfora', function () { }, }); - var delayedWidget3 = new pathfora.Message({ + var delayedWidget3 = createMessageWidget({ msg: 'Delayed clear test', id: 'delayed-widget-clear3', layout: 'modal', @@ -96,7 +94,7 @@ describe('Pathfora', function () { var addEventListenerSpy = spyOn(window, 'addEventListener'); var removeEventListenerSpy = spyOn(window, 'removeEventListener'); - var scrollWidget = new pathfora.Subscription({ + var scrollWidget = createSubscriptionWidget({ msg: 'Fake scroll widget', id: 'fake-scroll-widget', displayConditions: { @@ -123,7 +121,7 @@ describe('Pathfora', function () { var addEventListenerSpy = spyOn(document, 'addEventListener'); var removeEventListenerSpy = spyOn(document, 'removeEventListener'); - var exitIntentWidget = new pathfora.Subscription({ + var exitIntentWidget = createSubscriptionWidget({ msg: 'Fake exit-intent widget', id: 'fake-exit-intent-widget', displayConditions: { diff --git a/test/acceptance/prioritized-widgets.spec.js b/test/acceptance/prioritized-widgets.spec.js index ea7240c6..0cf1de60 100644 --- a/test/acceptance/prioritized-widgets.spec.js +++ b/test/acceptance/prioritized-widgets.spec.js @@ -1,19 +1,19 @@ import globalReset from '../utils/global-reset'; +import { createMessageWidget } from '../utils/test-helpers'; -// Prioritized Widget Tests -describe('Prioritized widgets', function () { +describe('prioritized widgets', function () { beforeEach(function () { globalReset(); }); describe('of type "ordered"', function () { it('should only show the first valid widget', function (done) { - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ id: 'messageBar1', layout: 'bar', msg: 'Welcome to our website', }); - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'modal2', layout: 'modal', }); @@ -36,7 +36,7 @@ describe('Prioritized widgets', function () { it('should account for display conditions when determining priority', function (done) { pathfora.utils.saveCookie('PathforaPageView', 2); - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ id: 'messageBar1', layout: 'bar', msg: 'Welcome to our website', @@ -45,12 +45,12 @@ describe('Prioritized widgets', function () { }, }); - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'modal2', layout: 'modal', }); - var anotherMessageBar = new pathfora.Message({ + var anotherMessageBar = createMessageWidget({ id: 'anotherMessageBar', layout: 'bar', msg: 'Welcome to our website', @@ -59,7 +59,7 @@ describe('Prioritized widgets', function () { }, }); - var anotherModal = new pathfora.Message({ + var anotherModal = createMessageWidget({ id: 'anotherModal', layout: 'modal', }); @@ -90,12 +90,12 @@ describe('Prioritized widgets', function () { it('should work with entity field templates regardless of load time', function (done) { window.lio = {}; - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ id: 'messageBar1', layout: 'bar', msg: 'Welcome to our website {{name}}', }); - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'modal2', layout: 'modal', msg: 'Welcome to our website', @@ -145,12 +145,12 @@ describe('Prioritized widgets', function () { loaded: true, }; - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ id: 'messageBar1', layout: 'bar', msg: 'Welcome to our website', }); - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'recommendation-modal', msg: 'A', layout: 'modal', @@ -196,12 +196,12 @@ describe('Prioritized widgets', function () { describe('with no priority defined', function () { it('should attempt to initialize widgets asyncronously', function (done) { - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ id: 'messageBar1noPriority', layout: 'bar', msg: 'Welcome to our website {{name}}', }); - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'modal2noPriority', layout: 'modal', msg: 'Welcome to our website', @@ -223,12 +223,12 @@ describe('Prioritized widgets', function () { describe('with unordered priority defined', function () { it('should attempt to initialize widgets asyncronously', function (done) { - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ id: 'messageBar1noPriority', layout: 'bar', msg: 'Welcome to our website {{name}}', }); - var modal = new pathfora.Message({ + var modal = createMessageWidget({ id: 'modal2noPriority', layout: 'modal', msg: 'Welcome to our website', diff --git a/test/acceptance/scaffolding.spec.js b/test/acceptance/scaffolding.spec.js index bdf41d35..c2ff8aad 100644 --- a/test/acceptance/scaffolding.spec.js +++ b/test/acceptance/scaffolding.spec.js @@ -1,8 +1,6 @@ import globalReset from '../utils/global-reset'; +import { createMessageWidget, createFormWidget } from '../utils/test-helpers'; -// ------------------------- -// SCAFFOLDING -// ------------------------- describe('when building a scaffolding component', function () { beforeEach(function () { globalReset(); @@ -18,7 +16,7 @@ describe('when building a scaffolding component', function () { it('should insert widget into config after building and inserting into scaffold', function () { var scaffold = pathfora.utils.initWidgetScaffold(); - var tester = new pathfora.Message({ + var tester = createMessageWidget({ id: 'tester123', headline: 'Sample Insert', msg: 'Sample insert message.', @@ -45,7 +43,7 @@ describe('when building a scaffolding component', function () { it('should insert multiple widgets into config binding to the same segment', function () { var scaffold = pathfora.utils.initWidgetScaffold(); - var tester1 = new pathfora.Message({ + var tester1 = createMessageWidget({ id: 'tester123', headline: 'Sample Insert', msg: 'Sample insert message.', @@ -56,7 +54,7 @@ describe('when building a scaffolding component', function () { }); pathfora.utils.insertWidget('target', 'smt_new', tester1, scaffold); - var tester2 = new pathfora.Form({ + var tester2 = createFormWidget({ id: 'tester456', headline: 'Sample Insert Two', msg: 'Sample insert message two.', @@ -85,7 +83,7 @@ describe('when building a scaffolding component', function () { it('should insert multiple widgets into config binding to the same segment but excluding', function () { var scaffold = pathfora.utils.initWidgetScaffold(); - var tester1 = new pathfora.Message({ + var tester1 = createMessageWidget({ id: 'tester123', headline: 'Sample Insert', msg: 'Sample insert message.', @@ -97,7 +95,7 @@ describe('when building a scaffolding component', function () { pathfora.utils.insertWidget('exclude', 'smt_new', tester1, scaffold); - var tester2 = new pathfora.Form({ + var tester2 = createFormWidget({ id: 'tester456', headline: 'Sample Insert Two', msg: 'Sample insert message two.', diff --git a/test/acceptance/targeting.spec.js b/test/acceptance/targeting.spec.js index a2609e37..387ce131 100644 --- a/test/acceptance/targeting.spec.js +++ b/test/acceptance/targeting.spec.js @@ -1,44 +1,38 @@ import globalReset from '../utils/global-reset'; +import { + createMessageWidget, + setupLioMock, + expectWidgetVisible, + expectWidgetHidden, +} from '../utils/test-helpers'; -// ------------------------- -// SEGMENTS -// ------------------------- describe('when targeting users by segment', function () { beforeEach(function () { globalReset(); }); it('should distinguish newcomers, subscribers and common users', function (done) { - window.lio = { - data: { - segments: ['all', 'b'], - }, - account: { - id: '0', - }, - }; + setupLioMock(['all', 'b']); - window.lio.loaded = true; - - var messageA = new pathfora.Message({ + var messageA = createMessageWidget({ id: 'test-bar-01', msg: 'A', layout: 'modal', }); - var messageB = new pathfora.Message({ + var messageB = createMessageWidget({ id: 'test-bar-02', msg: 'B', layout: 'modal', }); - var messageC = new pathfora.Message({ + var messageC = createMessageWidget({ id: 'test-bar-03', msg: 'C', layout: 'modal', }); - var messageD = new pathfora.Message({ + var messageD = createMessageWidget({ id: 'test-bar-04', msg: 'D', layout: 'modal', @@ -67,43 +61,25 @@ describe('when targeting users by segment', function () { pathfora.initializeWidgets(widgets); - var widget = $('#' + messageB.id); - expect(widget).toBeDefined(); - - var notOpenedA = $('#' + messageA.id); - var notOpenedC = $('#' + messageC.id); - var widgetB = $('#' + messageB.id); - var universalWidget = $('#' + messageD.id); - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); - expect(notOpenedA.length).toBe(0); - expect(notOpenedC.length).toBe(0); - expect(universalWidget.hasClass('opened')).toBeTruthy(); - expect(widgetB.hasClass('opened')).toBeTruthy(); + expectWidgetVisible(messageB.id); + expectWidgetVisible(messageD.id); + expectWidgetHidden(messageA.id); + expectWidgetHidden(messageC.id); done(); }, 200); }); it('should properly exclude users when their segment membership matches that of the exclude settings', function (done) { - window.lio = { - data: { - segments: ['a', 'b'], - }, - account: { - id: '0', - }, - }; - - window.lio.loaded = true; + setupLioMock(['a', 'b']); - var messageA = new pathfora.Message({ + var messageA = createMessageWidget({ id: 'test-bar-01', msg: 'A', layout: 'modal', }); - var messageB = new pathfora.Message({ + var messageB = createMessageWidget({ id: 'test-bar-02', msg: 'B', layout: 'modal', @@ -126,14 +102,9 @@ describe('when targeting users by segment', function () { pathfora.initializeWidgets(widgets); - var widgetA = $('#' + messageA.id), - widgetB = $('#' + messageB.id); - - expect(widgetB).toBeDefined(); - setTimeout(function () { - expect(widgetB.hasClass('opened')).toBeTruthy(); - expect(widgetA.length).toBe(0); + expectWidgetVisible(messageB.id); + expectWidgetHidden(messageA.id); done(); }, 200); }); @@ -150,13 +121,13 @@ describe('when targeting users by segment', function () { window.lio.loaded = true; - var messageA = new pathfora.Message({ + var messageA = createMessageWidget({ id: 'test-slideout-01', msg: 'A', layout: 'slideout', }); - var messageB = new pathfora.Message({ + var messageB = createMessageWidget({ id: 'test-bar-02', msg: 'B', layout: 'modal', @@ -202,19 +173,19 @@ describe('when targeting users by segment', function () { window.lio.loaded = true; - var messageA = new pathfora.Message({ + var messageA = createMessageWidget({ id: 'test-dependency-01', msg: 'A', layout: 'modal', }); - var messageB = new pathfora.Message({ + var messageB = createMessageWidget({ id: 'test-dependency-02', msg: 'B', layout: 'modal', }); - var messageC = new pathfora.Message({ + var messageC = createMessageWidget({ id: 'test-dependency-03', msg: 'C', layout: 'modal', @@ -255,9 +226,6 @@ describe('when targeting users by segment', function () { }); }); -// ------------------------- -// ATTRIBUTES -// ------------------------- describe('when targeting users by attributes', function () { beforeEach(function () { globalReset(); @@ -276,25 +244,25 @@ describe('when targeting users by attributes', function () { }; window.jstag.config.cid = '123'; - var messageA = new pathfora.Message({ + var messageA = createMessageWidget({ id: 'test-bar-01', msg: 'A', layout: 'modal', }); - var messageB = new pathfora.Message({ + var messageB = createMessageWidget({ id: 'test-bar-02', msg: 'B', layout: 'modal', }); - var messageC = new pathfora.Message({ + var messageC = createMessageWidget({ id: 'test-bar-03', msg: 'C', layout: 'modal', }); - var messageD = new pathfora.Message({ + var messageD = createMessageWidget({ id: 'test-bar-04', msg: 'D', layout: 'modal', @@ -358,13 +326,13 @@ describe('when targeting users by attributes', function () { }; window.jstag.config.cid = '123'; - var messageB = new pathfora.Message({ + var messageB = createMessageWidget({ id: 'test-attr-dependency-02', msg: 'B', layout: 'modal', }); - var messageC = new pathfora.Message({ + var messageC = createMessageWidget({ id: 'test-attr-dependency-03', msg: 'C', layout: 'modal', @@ -435,7 +403,7 @@ describe('pathfora helper rule functions', function () { it('should return true if the user is in the flow', function () { var rule = pathfora.rules.inFlow('test_slug'); var data = { - flows_step_slugs: { '12345': 'test_slug', '67890': 'test_slug_2' }, + flows_step_slugs: { 12345: 'test_slug', 67890: 'test_slug_2' }, }; expect(rule(data)).toBeTruthy(); }); @@ -443,7 +411,7 @@ describe('pathfora helper rule functions', function () { it('should return false otherwise', function () { var rule = pathfora.rules.inFlow('test_slug'); var data = { - flows_step_slugs: { '67890': 'test_slug_2' }, + flows_step_slugs: { 67890: 'test_slug_2' }, }; expect(rule(data)).toBeFalsy(); }); diff --git a/test/acceptance/tracking.spec.js b/test/acceptance/tracking.spec.js index 150b2ff5..be388984 100644 --- a/test/acceptance/tracking.spec.js +++ b/test/acceptance/tracking.spec.js @@ -1,11 +1,16 @@ import globalReset from '../utils/global-reset'; +import { + createMessageWidget, + setupTrackingSpy, + confirmWidget, + closeWidget, + expectTrackingEvent, + createFormWidget, +} from '../utils/test-helpers'; window.ga = function () {}; window.ga.getAll = function () {}; -// ------------------------- -// TRACKING -// ------------------------- describe('the tracking component', function () { beforeEach(function () { globalReset(); @@ -37,7 +42,7 @@ describe('the tracking component', function () { }); it('should know if users have interacted in the past', function () { - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ layout: 'bar', id: 'interest-widget1', msg: 'Message bar - interest test', @@ -49,7 +54,7 @@ describe('the tracking component', function () { }, }); - var messageModal = new pathfora.Message({ + var messageModal = createMessageWidget({ layout: 'modal', id: 'interest-widget2', msg: 'Message modal - interest test', @@ -61,12 +66,8 @@ describe('the tracking component', function () { expect(completedActions).toBe(0); expect(closedWidgets).toBe(0); - $('#' + messageBar.id) - .find('.pf-widget-ok') - .click(); - $('#' + messageModal.id) - .find('.pf-widget-close') - .click(); + confirmWidget(messageBar.id); + closeWidget(messageModal.id); completedActions = pathfora.getDataObject().completedActions.length; closedWidgets = pathfora.getDataObject().closedWidgets.length; @@ -91,24 +92,20 @@ describe('the tracking component', function () { it('should report displaying widgets', function () { jasmine.Ajax.install(); - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ layout: 'modal', msg: 'Message bar - reporting test', id: 'modal-display-report', }); - spyOn(jstag, 'send'); + setupTrackingSpy(); pathfora.initializeWidgets([messageBar]); - expect(jstag.send).toHaveBeenCalledWith( - jasmine.objectContaining({ - 'pf-widget-id': messageBar.id, - 'pf-widget-type': 'message', - 'pf-widget-layout': 'modal', - 'pf-widget-event': 'show', - }) - ); + expectTrackingEvent(messageBar.id, 'show', null, { + 'pf-widget-type': 'message', + 'pf-widget-layout': 'modal', + }); expect(window.ga).toHaveBeenCalledWith( 'gtm1.send', @@ -135,7 +132,7 @@ describe('the tracking component', function () { jasmine.Ajax.install(); jasmine.clock().install(); - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ layout: 'modal', msg: 'Message bar - close reporting', id: 'bar-close-report', @@ -143,19 +140,15 @@ describe('the tracking component', function () { pathfora.initializeWidgets([messageBar]); - spyOn(jstag, 'send'); - $('.pf-widget-close').click(); + setupTrackingSpy(); + closeWidget(messageBar.id); jasmine.clock().tick(1000); - expect(jstag.send).toHaveBeenCalledWith( - jasmine.objectContaining({ - 'pf-widget-id': messageBar.id, - 'pf-widget-type': 'message', - 'pf-widget-layout': 'modal', - 'pf-widget-event': 'close', - }) - ); + expectTrackingEvent(messageBar.id, 'close', null, { + 'pf-widget-type': 'message', + 'pf-widget-layout': 'modal', + }); expect(window.ga).toHaveBeenCalledWith( 'gtm1.send', @@ -182,7 +175,7 @@ describe('the tracking component', function () { it('should report completed actions to Lytics API', function (done) { jasmine.Ajax.install(); - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ layout: 'modal', id: 'tracking-widget1', msg: 'Message modal - action report test', @@ -220,7 +213,7 @@ describe('the tracking component', function () { it('should report cancelled actions to Lytics API', function (done) { jasmine.Ajax.install(); - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ layout: 'modal', id: 'tracking-widget2', msg: 'Message modal - cancel report test', @@ -259,7 +252,7 @@ describe('the tracking component', function () { }); it('should report submitting forms, with form data', function () { - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ layout: 'modal', msg: 'Message modal - form submit reports', id: 'ABCa', @@ -281,7 +274,7 @@ describe('the tracking component', function () { }); it('should report to Google Analytics API, when available', function (done) { - var messageBar = new pathfora.Message({ + var messageBar = createMessageWidget({ layout: 'modal', id: 'ga-widget', msg: 'Message modal - ga test', @@ -324,7 +317,7 @@ describe('the tracking component', function () { it('should report hover actions to Lytics API', function (done) { jasmine.Ajax.install(); - var messageModal = new pathfora.Message({ + var messageModal = createMessageWidget({ layout: 'modal', id: 'tracking-widget3', msg: 'Message modal - report test', @@ -379,7 +372,7 @@ describe('the tracking component', function () { it('should report form focus actions to Lytics API', function (done) { jasmine.Ajax.install(); - var formModal = new pathfora.Form({ + var formModal = createFormWidget({ layout: 'modal', id: 'tracking-widget4', msg: 'Form modal - report test', @@ -435,7 +428,7 @@ describe('the tracking component', function () { it('should report form started actions to Lytics API', function (done) { jasmine.Ajax.install(); - var formModal = new pathfora.Form({ + var formModal = createFormWidget({ layout: 'modal', id: 'tracking-widget5', msg: 'Form modal - report test', @@ -492,7 +485,7 @@ describe('the tracking component', function () { }); it('should call censorTrackingKeys with widget.censorTrackingKeys when defined', function (done) { - var formModal = new pathfora.Form({ + var formModal = createFormWidget({ layout: 'modal', id: 'tracking-widget-censored', msg: 'Form modal - report test', diff --git a/test/acceptance/utils.spec.js b/test/acceptance/utils.spec.js index 3be91c8d..cc6e9287 100644 --- a/test/acceptance/utils.spec.js +++ b/test/acceptance/utils.spec.js @@ -1,9 +1,6 @@ import globalReset from '../utils/global-reset'; -// ------------------------- -// UTIL TESTS -// ------------------------- -describe('Utils', function () { +describe('utils', function () { beforeEach(function () { globalReset(); }); @@ -37,8 +34,8 @@ describe('Utils', function () { it('should not double-encode URIs', function () { var unescaped = 'http://www.getlytics.com/?foo=a b c&bar=d e f', - escapedOnce = escapeURI(unescaped, { keepEscaped: true }), - escapedTwice = escapeURI(escapedOnce, { keepEscaped: true }); + escapedOnce = escapeURI(unescaped, { keepEscaped: true }), + escapedTwice = escapeURI(escapedOnce, { keepEscaped: true }); expect(escapedTwice).toBe(escapedOnce); }); @@ -51,7 +48,7 @@ describe('Utils', function () { var params = { key: 'value', anotherkey: true, - athirdkey: 1 + athirdkey: 1, }; var expected = '?key=value&anotherkey=true&athirdkey=1'; @@ -63,7 +60,7 @@ describe('Utils', function () { var params = { foo: 'value', bar: [1, 2, 3], - baz: ['test'] + baz: ['test'], }; var expected = '?foo=value&bar[]=1&bar[]=2&bar[]=3&baz[]=test'; @@ -102,7 +99,6 @@ describe('Utils', function () { }; it('should encode legacy Pathfora cookies', function () { - setCookie('PathforaImpressions_2', '1|293847239874932871'); sessionStorage.setItem('PathforaRecommend_2', '{"somejson": "here"}'); setCookie('PathforaImpressions_3', '%badval%'); @@ -115,9 +111,7 @@ describe('Utils', function () { expect(sessionStorage.getItem('PathforaRecommend_2')).toEqual( '%7B%22somejson%22%3A%20%22here%22%7D' ); - expect(pathfora.utils.read('PathforaImpressions_3')).toEqual( - '%badval%' - ); + expect(pathfora.utils.read('PathforaImpressions_3')).toEqual('%badval%'); }); it('should migrate existing cookies to localStorage', function () { @@ -128,4 +122,23 @@ describe('Utils', function () { expect(pathfora.utils.read('PathforaImpressions_2')).toBe('test'); }); }); + + describe('culling expired localStorage on init', function () { + beforeEach(globalReset); + + it('should cull expired records from localStorage eagerly on init', function () { + var Pathfora = pathfora.constructor; + + pathfora.utils.store.ttl('expired', 'bonk', -10000); + pathfora.utils.store.ttl('current', 'bonk', 10000); + + expect(localStorage.getItem('expired')).not.toBe(null); + expect(localStorage.getItem('current')).not.toBe(null); + + new Pathfora(); + + expect(localStorage.getItem('expired')).toBe(null); + expect(localStorage.getItem('current')).not.toBe(null); + }); + }); }); diff --git a/test/acceptance/widget-position.spec.js b/test/acceptance/widget-position.spec.js new file mode 100644 index 00000000..de018f77 --- /dev/null +++ b/test/acceptance/widget-position.spec.js @@ -0,0 +1,154 @@ +import globalReset from '../utils/global-reset'; +import { createMessageWidget } from '../utils/test-helpers'; + +describe('widget position', function () { + beforeEach(function () { + globalReset(); + }); + + it('should use default position if no position is specified', function () { + var w1 = createMessageWidget({ + msg: 'button - default pos test', + id: 'position-widget-1', + layout: 'button', + }); + + var w2 = createMessageWidget({ + msg: 'bar - default pos test', + id: 'position-widget-2', + layout: 'bar', + }); + + var w3 = createMessageWidget({ + msg: 'slideout - default pos test', + id: 'position-widget-3', + layout: 'slideout', + }); + + pathfora.initializeWidgets([w1, w2, w3]); + + var widget1 = $('#' + w1.id), + widget2 = $('#' + w2.id), + widget3 = $('#' + w3.id); + + expect(widget1.hasClass('pf-position-top-left')).toBeTruthy(); + expect(widget2.hasClass('pf-position-top-absolute')).toBeTruthy(); + expect(widget3.hasClass('pf-position-bottom-left')).toBeTruthy(); + }); + + it('should warn when invalid position', function () { + var w1 = createMessageWidget({ + msg: 'Widget positioning test', + layout: 'modal', + id: 'region-widget', + position: 'customPos', + }); + + spyOn(console, 'warn'); + pathfora.initializeWidgets([w1]); + + expect(console.warn).toHaveBeenCalledWith( + 'customPos is not a valid position for modal' + ); + }); + + it('should error when custom positionSelector does not exist in dom', function () { + var w1 = createMessageWidget({ + msg: 'Widget positioning test', + layout: 'modal', + id: 'custom-position-widget', + positionSelector: '.does-not-exist', + }); + + expect(function () { + pathfora.initializeWidgets([w1]); + }).toThrowError(/Widget could not be initialized in .does-not-exist/); + }); + + it('should append the widget to the positionSelector element if it does exist', function (done) { + var div = document.createElement('div'); + div.id = 'overlay'; + document.body.appendChild(div); + + var inline = createMessageWidget({ + headline: 'Position Custom', + layout: 'modal', + positionSelector: '#overlay', + id: 'custom-position-modal', + msg: 'yay', + }); + + pathfora.initializeWidgets([inline]); + + var parent = $(inline.positionSelector); + + setTimeout(function () { + var widget = parent.find('#' + inline.id); + expect(widget.length).toBe(1); + done(); + }, 200); + }); + + // ------------------------- + // INLINE MODULES + // ------------------------- + it('should throw error if inline positionSelector not found', function () { + // legacy position support + var inline = createMessageWidget({ + headline: 'Inline Widget', + layout: 'inline', + position: '.a-non-existent-div', + id: 'inline-1', + msg: 'inline', + }); + + var inlineCustom = createMessageWidget({ + headline: 'Inline Widget', + layout: 'inline', + positionSelector: '.a-non-existent-div', + id: 'inline-2', + msg: 'inline', + }); + + expect(function () { + pathfora.initializeWidgets([inline, inlineCustom]); + }).toThrow( + new Error('Widget could not be initialized in .a-non-existent-div') + ); + }); + + it('should append the inline widget to the positionSelector element', function (done) { + var div = document.createElement('div'); + div.id = 'a-real-div'; + document.body.appendChild(div); + + // legacy position support + var inline = createMessageWidget({ + headline: 'Inline Widget', + layout: 'inline', + position: '#a-real-div', + id: 'inline-1', + msg: 'inline', + }); + + var inlineCustom = createMessageWidget({ + headline: 'Inline Widget', + layout: 'inline', + positionSelector: '#a-real-div', + id: 'inline-2', + msg: 'inline', + }); + + pathfora.initializeWidgets([inline, inlineCustom]); + + var parent = $(inline.position); + + setTimeout(function () { + var widget = parent.find('#' + inline.id); + expect(widget.length).toBe(1); + var widget2 = parent.find('#' + inlineCustom.id); + expect(widget2.length).toBe(1); + done(); + }, 200); + }); +}); diff --git a/test/acceptance/widget-theme.spec.js b/test/acceptance/widget-theme.spec.js new file mode 100644 index 00000000..35f18d66 --- /dev/null +++ b/test/acceptance/widget-theme.spec.js @@ -0,0 +1,200 @@ +import globalReset from '../utils/global-reset'; +import { createMessageWidget, createFormWidget } from '../utils/test-helpers'; + +describe('widget themes', function () { + beforeEach(function () { + globalReset(); + }); + + it('should have correct theme configuration', function () { + var w1 = createMessageWidget({ + layout: 'button', + position: 'left', + msg: 'light button', + id: 'light-widget', + theme: 'light', + }); + + var w2 = createMessageWidget({ + layout: 'button', + position: 'right', + msg: 'dark button', + id: 'dark-widget', + theme: 'dark', + }); + + var w3 = createMessageWidget({ + layout: 'button', + position: 'top-left', + msg: 'custom color button', + id: 'custom-widget', + theme: 'custom', + }); + + var config = { + generic: { + colors: { + background: '#fff', + }, + }, + }; + + pathfora.initializeWidgets([w1, w2, w3], config); + + var light = $('#' + w1.id), + dark = $('#' + w2.id), + custom = $('#' + w3.id); + + expect(light.hasClass('pf-theme-light')).toBeTruthy(); + expect(dark.hasClass('pf-theme-dark')).toBeTruthy(); + expect(custom.hasClass('pf-theme-custom')).toBeTruthy(); + expect(custom.css('background-color')).toBe('rgb(255, 255, 255)'); + }); + + it('should fallback to CSS if theme value is "none"', function () { + var css = document.createElement('style'); + css.type = 'text/css'; + css.innerHTML = '.widget-no-theme-class { background-color: #59f442 }'; + document.body.appendChild(css); + + var w1 = createMessageWidget({ + layout: 'button', + position: 'left', + msg: 'light button', + id: 'widget-no-theme', + className: 'widget-no-theme-class', + theme: 'none', + }); + + pathfora.initializeWidgets([w1]); + + var w = $('#' + w1.id); + + expect(w.hasClass('pf-theme-none')).toBeTruthy(); + expect(w.css('background-color')).toBe('rgb(89, 244, 66)'); + }); + + it('can be hidden on initialization', function () { + var openedWidget = createMessageWidget({ + layout: 'modal', + msg: 'Displayed on init', + id: 'displayed-on-init', + }); + + var closedWidget = createMessageWidget({ + layout: 'modal', + msg: 'Hidden on init', + id: 'hidden-on-init', + displayConditions: { + showOnInit: false, + }, + }); + + pathfora.initializeWidgets([openedWidget, closedWidget]); + + expect($('#' + openedWidget.id)[0]).toBeDefined(); + expect($('#' + closedWidget.id)[0]).toBeUndefined(); + }); + + it('should be able to adapt colors', function () { + var modal = createMessageWidget({ + id: 'custom-style-test', + layout: 'modal', + msg: 'Custom style test', + headline: 'Hello', + theme: 'custom', + }); + + var config = { + generic: { + colors: { + background: '#eee', + headline: '#333', + text: '#333', + close: '#888', + actionText: '#ddd', + actionBackground: '#111', + cancelText: '#333', + cancelBackground: '#eee', + }, + }, + }; + + pathfora.initializeWidgets([modal], config); + + var widget = $('#' + modal.id); + var background = widget.find('.pf-widget-content'); + var headline = widget.find('.pf-widget-headline'); + var text = widget.find('.pf-widget-message'); + var closeBtn = widget.find('.pf-widget-close'); + var actionBtn = widget.find('.pf-widget-ok'); + var cancelBtn = widget.find('.pf-widget-cancel'); + + expect(background.css('background-color')).toBe('rgb(238, 238, 238)'); + expect(headline.css('color')).toBe('rgb(51, 51, 51)'); + expect(text.css('color')).toBe('rgb(51, 51, 51)'); + expect(closeBtn.css('color')).toBe('rgb(136, 136, 136)'); + expect(actionBtn.css('color')).toBe('rgb(221, 221, 221)'); + expect(actionBtn.css('background-color')).toBe('rgb(17, 17, 17)'); + expect(cancelBtn.css('color')).toBe('rgb(51, 51, 51)'); + expect(cancelBtn.css('background-color')).toBe('rgb(238, 238, 238)'); + }); + + it('should account for required colors on validation', function () { + var modal = createFormWidget({ + id: 'required-color-modal', + layout: 'modal', + msg: 'Custom style test', + headline: 'Hello', + theme: 'custom', + colors: { + required: '#ba00a6', + requiredText: '#ebcee8', + }, + formElements: [ + { + type: 'radio-group', + label: "What's your favorite color?", + name: 'favorite_color', + required: true, + values: [ + { + label: 'Red', + value: 'red', + }, + { + label: 'Blue', + value: 'blue', + }, + { + label: 'Green', + value: 'green', + }, + ], + }, + { + type: 'input', + name: 'name', + placeholder: 'Your Name', + required: true, + }, + ], + }); + + pathfora.initializeWidgets([modal]); + + var widget = $('#' + modal.id); + var asterisk = widget.find('.pf-form-label span.required'); + var flag = widget.find('.pf-required-flag'); + + expect(asterisk.css('color')).toBe('rgb(186, 0, 166)'); + expect(flag.css('background-color')).toBe('rgb(186, 0, 166)'); + expect(flag.find('span').css('border-right-color')).toBe( + 'rgb(186, 0, 166)' + ); + expect(flag.css('color')).toBe('rgb(235, 206, 232)'); + + var input = widget.find('input[data-required=true]:not(.pf-has-label)'); + expect(input.css('border-color')).toBe('rgb(186, 0, 166)'); + }); +}); diff --git a/test/acceptance/widget.spec.js b/test/acceptance/widget.spec.js deleted file mode 100644 index cfcca9f7..00000000 --- a/test/acceptance/widget.spec.js +++ /dev/null @@ -1,2070 +0,0 @@ -import createAndDispatchKeydown from '../utils/create-and-dispatch-keydown.js'; -import globalReset from '../utils/global-reset'; - -// ------------------------- -// WIDGET TESTS -// ------------------------- -describe('Widgets', function () { - beforeEach(function () { - globalReset(); - }); - - // ------------------------- - // GENERAL - // ------------------------- - - it('should not allow to register 2 widgets with the same id', function () { - var w1 = new pathfora.Message({ - msg: 'Duplicate id test1', - layout: 'modal', - id: 'asd', - }); - - var w2 = new pathfora.Form({ - msg: 'Duplcate id test2', - layout: 'slideout', - id: 'asd', - }); - - expect(function () { - pathfora.initializeWidgets([w1, w2]); - }).toThrow(new Error('Cannot add two widgets with the same id')); - }); - - it('should use specified global config for all widgets', function () { - var messageBar = new pathfora.Message({ - layout: 'bar', - id: 'global-config-1', - msg: 'test', - }); - - var config = { - generic: { - theme: 'light', - }, - }; - - pathfora.initializeWidgets([messageBar], config); - - var bar = $('#' + messageBar.id); - expect(bar.hasClass('pf-theme-default')).toBe(false); - expect(bar.hasClass('pf-theme-light')).toBe(true); - }); - - it('should be able to clear all widgets and handlers', function (done) { - var clearDataObject = { - pageViews: 0, - timeSpentOnPage: 0, - closedWidgets: [], - completedActions: [], - cancelledActions: [], - displayedWidgets: [], - abTestingGroups: [], - }; - - var form = new pathfora.Subscription({ - msg: 'test', - id: 'clear-widget', - layout: 'modal', - }); - - pathfora.initializeWidgets([form]); - - var widget = $('#' + form.id); - - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); - expect(pathfora.getDataObject()).not.toEqual(clearDataObject); - - pathfora.clearAll(); - expect(widget.hasClass('opened')).toBeFalsy(); - expect(pathfora.getDataObject()).toEqual(clearDataObject); - done(); - }, 200); - }); - - it('should be able to clear specific widgets by their IDs', function (done) { - var widget1 = new pathfora.Message({ - msg: 'Widget 1', - id: 'clear-by-id-1', - layout: 'modal', - }); - - var widget2 = new pathfora.Message({ - msg: 'Widget 2', - id: 'clear-by-id-2', - layout: 'slideout', - }); - - var widget3 = new pathfora.Message({ - msg: 'Widget 3', - id: 'clear-by-id-3', - layout: 'bar', - }); - - pathfora.initializeWidgets([widget1, widget2, widget3]); - - var element1 = $('#' + widget1.id); - var element2 = $('#' + widget2.id); - var element3 = $('#' + widget3.id); - - setTimeout(function () { - // All widgets should be opened initially - expect(element1.hasClass('opened')).toBeTruthy(); - expect(element2.hasClass('opened')).toBeTruthy(); - expect(element3.hasClass('opened')).toBeTruthy(); - - // Clear only widget1 and widget3 - pathfora.clearById(['clear-by-id-1', 'clear-by-id-3']); - - setTimeout(function () { - // Widget1 and widget3 should be closed and removed from DOM - expect(element1.hasClass('opened')).toBeFalsy(); - expect($('#' + widget1.id).length).toBe(0); - expect(element3.hasClass('opened')).toBeFalsy(); - expect($('#' + widget3.id).length).toBe(0); - - // Widget2 should still be opened - expect(element2.hasClass('opened')).toBeTruthy(); - expect($('#' + widget2.id).length).toBe(1); - - // Clear widget2 as well - pathfora.clearById(['clear-by-id-2']); - - setTimeout(function () { - // All widgets should now be closed and removed - expect($('#' + widget1.id).length).toBe(0); - expect($('#' + widget2.id).length).toBe(0); - expect($('#' + widget3.id).length).toBe(0); - done(); - }, 200); - }, 200); - }, 200); - }); - - it('should handle clearById with invalid input gracefully', function (done) { - var widget = new pathfora.Message({ - msg: 'Test widget', - id: 'invalid-input-test', - layout: 'modal', - }); - - pathfora.initializeWidgets([widget]); - - // Test with non-array input - spyOn(console, 'warn'); - pathfora.clearById('not-an-array'); - - expect(console.warn).toHaveBeenCalledWith( - 'clearById: widgetIds must be an array' - ); - - // Widget should still be opened - setTimeout(function () { - expect($('#' + widget.id).hasClass('opened')).toBeTruthy(); - done(); - }, 200); - }); - - it('should handle clearById with non-existent widget IDs', function (done) { - var widget = new pathfora.Message({ - msg: 'Test widget', - id: 'existing-widget', - layout: 'modal', - }); - - pathfora.initializeWidgets([widget]); - - var element = $('#' + widget.id); - - setTimeout(function () { - expect(element.hasClass('opened')).toBeTruthy(); - - // Try to clear non-existent widget IDs - pathfora.clearById(['non-existent-1', 'non-existent-2']); - - // Existing widget should still be opened - expect(element.hasClass('opened')).toBeTruthy(); - expect($('#' + widget.id).length).toBe(1); - - // Clear the existing widget - pathfora.clearById(['existing-widget']); - - setTimeout(function () { - expect(element.hasClass('opened')).toBeFalsy(); - expect($('#' + widget.id).length).toBe(0); - done(); - }, 200); - }, 200); - }); - - it('should be able to be displayed on document', function (done) { - var promoWidget = new pathfora.Message({ - layout: 'bar', - msg: 'Opening widget', - id: 'widget-1', - }); - - pathfora.initializeWidgets([promoWidget]); - - // should append element to DOM - var widget = $('#' + promoWidget.id); - expect(widget).toBeDefined(); - - // should have class 'opened' after while - pathfora.showWidget(promoWidget); - - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); - done(); - }, 200); - }); - - it('should have proper id specified', function (done) { - var w1 = new pathfora.Message({ - layout: 'slideout', - position: 'right', - msg: 'Welcome to our test website', - id: 'test-id-widget', - }); - - expect(function () { - return new pathfora.Message({ - layout: 'slideout', - position: 'left', - msg: 'Welcome to our test website', - }); - }).toThrow(new Error('All widgets must have an id value')); - - pathfora.initializeWidgets([w1]); - - setTimeout(function () { - var right = $('.pf-widget.pf-position-right'); - expect(right).toBeDefined(); - expect(right.attr('id')).toBe('test-id-widget'); - done(); - }, 200); - }); - - it("should not append widget second time if it's already opened", function (done) { - var openedWidget = new pathfora.Message({ - layout: 'modal', - id: 'append-widget', - msg: 'test widget', - }); - - pathfora.initializeWidgets([openedWidget]); - - var widget = $('#' + openedWidget.id); - - // timeouts gives some time for appending to DOM - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); - pathfora.showWidget(openedWidget); - - setTimeout(function () { - expect($('#' + openedWidget.id).length).toEqual(1); - done(); - }, 200); - }, 500); - }); - - it('should close when the x button is clicked', function (done) { - var testWidget = new pathfora.Message({ - layout: 'modal', - msg: 'Close widget test', - id: 'close-clear-widget', - }); - - pathfora.initializeWidgets([testWidget]); - - var widget = $('#' + testWidget.id); - expect(widget).toBeDefined(); - - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); - - widget.find('.pf-widget-close').click(); - expect(widget.hasClass('opened')).toBeFalsy(); - - setTimeout(function () { - expect($('#' + testWidget.id).length).toBe(0); - done(); - }, 600); - }, 200); - }); - - it('should close if the escape key is pressed and it is a modal', function (done) { - var modal = new pathfora.Message({ - id: 'modal-esc-test', - layout: 'modal', - headline: 'Message Title', - msg: 'test', - }); - - var gate = new pathfora.SiteGate({ - id: 'modal-esc-test2', - headline: 'Message Title', - msg: 'test', - }); - - pathfora.initializeWidgets([modal, gate]); - - var widget = $('#' + modal.id); - var widgetGate = $('#' + gate.id); - expect(widget).toBeDefined(); - expect(widgetGate).toBeDefined(); - - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); - expect(widgetGate.hasClass('opened')).toBeTruthy(); - - createAndDispatchKeydown(27, document); - - expect(widget.hasClass('opened')).toBeFalsy(); - expect(widgetGate.hasClass('opened')).toBeTruthy(); - - setTimeout(function () { - expect($('#' + modal.id).length).toBe(0); - expect($('#' + gate.id).length).toBe(1); - done(); - }, 600); - }, 200); - }); - - it('should handle missing values properly and never surface undefined', function () { - var message = new pathfora.Message({ - id: 'message-test-widget', - layout: 'slideout', - headline: 'Message Title', - theme: 'custom', - }); - - var form = new pathfora.Form({ - id: 'form-test-widget', - layout: 'modal', - headline: 'Headline Title', - theme: 'custom', - }); - - var subscription = new pathfora.Subscription({ - id: 'subscription-test-widget', - layout: 'bar', - theme: 'custom', - }); - - pathfora.initializeWidgets([message, form, subscription]); - - // test message - var mwidget = $('#' + message.id), - mheadline = mwidget.find('.pf-widget-headline'), - mtext = mwidget.find('.pf-widget-message'); - - expect(mheadline.html()).not.toEqual('undefined'); - expect(mtext.html()).not.toEqual('undefined'); - - // test form - var fwidget = $('#' + form.id), - fheadline = fwidget.find('.pf-widget-headline'), - ftext = fwidget.find('.pf-widget-message'); - - expect(fheadline.html()).not.toEqual('undefined'); - expect(ftext.html()).not.toEqual('undefined'); - - // test subscription - var swidget = $('#' + subscription.id); - var stext = swidget.find('.pf-widget-message'); - expect(stext.html()).not.toEqual('undefined'); - }); - - it('should not allow to be initialized without default properties', function () { - var missingParams = function () { - var promoWidget = new pathfora.Message(); - pathfora.initializeWidgets([promoWidget]); - }; - - expect(missingParams).toThrow(new Error('Config object is missing')); - }); - - it('should not show branding assets unless set otherwise', function () { - var w1 = new pathfora.Message({ - msg: 'test', - id: 'branding1', - layout: 'slideout', - branding: true, - }); - - var w2 = new pathfora.Message({ - msg: 'test', - id: 'branding2', - layout: 'modal', - }); - - pathfora.initializeWidgets([w1, w2]); - - var widget1 = $('#' + w1.id), - widget2 = $('#' + w2.id); - - expect(widget1.find('.branding svg').length).toBe(1); - expect(widget2.find('.branding svg').length).toBe(0); - }); - - it('should display footer when footerText setting is used', function () { - var modalFooter = new pathfora.Message({ - id: 'footer1', - msg: 'test', - layout: 'modal', - footerText: 'Footer text', - }); - - var modalNoFooter = new pathfora.Message({ - id: 'footer2', - msg: 'test', - layout: 'modal', - }); - - var slideoutFooter = new pathfora.Message({ - id: 'slidout1', - msg: 'test', - layout: 'slideout', - footerText: 'Footer text', - }); - - var slideoutNoFooter = new pathfora.Message({ - id: 'slideout2', - msg: 'test', - layout: 'slideout', - }); - - pathfora.initializeWidgets([ - modalFooter, - modalNoFooter, - slideoutFooter, - slideoutNoFooter, - ]); - - var modal1 = $('#' + modalFooter.id), - modal2 = $('#' + modalNoFooter.id), - slideout1 = $('#' + slideoutFooter.id), - slideout2 = $('#' + slideoutNoFooter.id); - expect(modal1.find('.pf-widget-footer').html()).toEqual('Footer text'); - expect(modal2.find('.pf-widget-footer').html()).toEqual(''); - expect(slideout1.find('.pf-widget-footer').html()).toEqual('Footer text'); - expect(slideout2.find('.pf-widget-footer').html()).toEqual(''); - }); - - it('should contain pf-widget-text div for inline and modal layouts', function () { - var modal = new pathfora.Message({ - id: 'modal', - msg: 'testmodal', - layout: 'modal', - }); - var div = document.createElement('div'); - div.className = 'some-dom-element'; - document.body.appendChild(div); - var inline = new pathfora.Message({ - id: 'inline', - layout: 'inline', - position: '.some-dom-element', - msg: 'testing', - }); - var slideout = new pathfora.Message({ - id: 'slideout', - msg: 'test', - layout: 'slideout', - }); - pathfora.initializeWidgets([modal, inline, slideout]); - var modalWidget = $('#' + modal.id), - inlineWidget = $('#' + inline.id), - slideoutWidget = $('#' + slideout.id); - expect(modalWidget.find('.pf-widget-text').html()).toBeDefined(); - expect(inlineWidget.find('.pf-widget-text').html()).toBeDefined(); - expect(slideoutWidget.find('.pf-widget-text').html()).toBeUndefined(); - }); - - it('should append pf-widget-img to pf-widget-content for modal and inline layouts', function () { - var modal = new pathfora.Message({ - id: 'modal', - msg: 'testmodal', - layout: 'modal', - image: 'https://lytics.github.io/pathforadocs/assets/lion.jpg', - }); - var div = document.createElement('div'); - div.className = 'some-dom-element'; - document.body.appendChild(div); - var inline = new pathfora.Message({ - id: 'inline', - layout: 'inline', - position: '.some-dom-element', - msg: 'testing', - image: 'https://lytics.github.io/pathforadocs/assets/lion.jpg', - }); - pathfora.initializeWidgets([modal, inline]); - var modalWidget = $('#' + modal.id), - inlineWidget = $('#' + inline.id); - expect( - modalWidget.find('.pf-widget-content').find('img').html() - ).toBeDefined(); - expect( - inlineWidget.find('.pf-widget-content').find('img').html() - ).toBeDefined(); - expect( - modalWidget.find('.pf-widget-text').find('img').html() - ).toBeUndefined(); - }); - - // ------------------------- - // COLORS/THEME - // ------------------------- - - it('should have correct theme configuration', function () { - var w1 = new pathfora.Message({ - layout: 'button', - position: 'left', - msg: 'light button', - id: 'light-widget', - theme: 'light', - }); - - var w2 = new pathfora.Message({ - layout: 'button', - position: 'right', - msg: 'dark button', - id: 'dark-widget', - theme: 'dark', - }); - - var w3 = new pathfora.Message({ - layout: 'button', - position: 'top-left', - msg: 'custom color button', - id: 'custom-widget', - theme: 'custom', - }); - - var config = { - generic: { - colors: { - background: '#fff', - }, - }, - }; - - pathfora.initializeWidgets([w1, w2, w3], config); - - var light = $('#' + w1.id), - dark = $('#' + w2.id), - custom = $('#' + w3.id); - - expect(light.hasClass('pf-theme-light')).toBeTruthy(); - expect(dark.hasClass('pf-theme-dark')).toBeTruthy(); - expect(custom.hasClass('pf-theme-custom')).toBeTruthy(); - expect(custom.css('background-color')).toBe('rgb(255, 255, 255)'); - }); - - it('should fallback to CSS if theme value is "none"', function () { - var css = document.createElement('style'); - css.type = 'text/css'; - css.innerHTML = '.widget-no-theme-class { background-color: #59f442 }'; - document.body.appendChild(css); - - var w1 = new pathfora.Message({ - layout: 'button', - position: 'left', - msg: 'light button', - id: 'widget-no-theme', - className: 'widget-no-theme-class', - theme: 'none', - }); - - pathfora.initializeWidgets([w1]); - - var w = $('#' + w1.id); - - expect(w.hasClass('pf-theme-none')).toBeTruthy(); - expect(w.css('background-color')).toBe('rgb(89, 244, 66)'); - }); - - it('can be hidden on initialization', function () { - var openedWidget = new pathfora.Message({ - layout: 'modal', - msg: 'Displayed on init', - id: 'displayed-on-init', - }); - - var closedWidget = new pathfora.Message({ - layout: 'modal', - msg: 'Hidden on init', - id: 'hidden-on-init', - displayConditions: { - showOnInit: false, - }, - }); - - pathfora.initializeWidgets([openedWidget, closedWidget]); - - expect($('#' + openedWidget.id)[0]).toBeDefined(); - expect($('#' + closedWidget.id)[0]).toBeUndefined(); - }); - - it('should be able to adapt colors', function () { - var modal = new pathfora.Message({ - id: 'custom-style-test', - layout: 'modal', - msg: 'Custom style test', - headline: 'Hello', - theme: 'custom', - }); - - var config = { - generic: { - colors: { - background: '#eee', - headline: '#333', - text: '#333', - close: '#888', - actionText: '#ddd', - actionBackground: '#111', - cancelText: '#333', - cancelBackground: '#eee', - }, - }, - }; - - pathfora.initializeWidgets([modal], config); - - var widget = $('#' + modal.id); - var background = widget.find('.pf-widget-content'); - var headline = widget.find('.pf-widget-headline'); - var text = widget.find('.pf-widget-message'); - var closeBtn = widget.find('.pf-widget-close'); - var actionBtn = widget.find('.pf-widget-ok'); - var cancelBtn = widget.find('.pf-widget-cancel'); - - expect(background.css('background-color')).toBe('rgb(238, 238, 238)'); - expect(headline.css('color')).toBe('rgb(51, 51, 51)'); - expect(text.css('color')).toBe('rgb(51, 51, 51)'); - expect(closeBtn.css('color')).toBe('rgb(136, 136, 136)'); - expect(actionBtn.css('color')).toBe('rgb(221, 221, 221)'); - expect(actionBtn.css('background-color')).toBe('rgb(17, 17, 17)'); - expect(cancelBtn.css('color')).toBe('rgb(51, 51, 51)'); - expect(cancelBtn.css('background-color')).toBe('rgb(238, 238, 238)'); - }); - - it('should account for required colors on validation', function () { - var modal = new pathfora.Form({ - id: 'required-color-modal', - layout: 'modal', - msg: 'Custom style test', - headline: 'Hello', - theme: 'custom', - colors: { - required: '#ba00a6', - requiredText: '#ebcee8', - }, - formElements: [ - { - type: 'radio-group', - label: "What's your favorite color?", - name: 'favorite_color', - required: true, - values: [ - { - label: 'Red', - value: 'red', - }, - { - label: 'Blue', - value: 'blue', - }, - { - label: 'Green', - value: 'green', - }, - ], - }, - { - type: 'input', - name: 'name', - placeholder: 'Your Name', - required: true, - }, - ], - }); - - pathfora.initializeWidgets([modal]); - - var widget = $('#' + modal.id); - var asterisk = widget.find('.pf-form-label span.required'); - var flag = widget.find('.pf-required-flag'); - - expect(asterisk.css('color')).toBe('rgb(186, 0, 166)'); - expect(flag.css('background-color')).toBe('rgb(186, 0, 166)'); - expect(flag.find('span').css('border-right-color')).toBe( - 'rgb(186, 0, 166)' - ); - expect(flag.css('color')).toBe('rgb(235, 206, 232)'); - - var input = widget.find('input[data-required=true]:not(.pf-has-label)'); - expect(input.css('border-color')).toBe('rgb(186, 0, 166)'); - }); - - // ------------------------- - // CALLBACKS - // ------------------------- - - it('should trigger callback function after pressing action button', function () { - var modal = new pathfora.Message({ - id: 'confirm-action-test', - layout: 'modal', - msg: 'Confirm action test modal', - confirmAction: { - name: 'Test confirm action', - callback: function () { - alert('test confirmation'); - }, - }, - }); - - pathfora.initializeWidgets([modal]); - - var widget = $('#confirm-action-test'); - spyOn(modal.confirmAction, 'callback'); - expect(modal.confirmAction.callback).not.toHaveBeenCalled(); - widget.find('.pf-widget-ok').click(); - expect(modal.confirmAction.callback).toHaveBeenCalled(); - }); - - it('should trigger callback function after pressing action with form data.', function () { - var modal = new pathfora.Form({ - id: 'confirm-action-form-test', - layout: 'modal', - msg: 'Confirm action test modal', - confirmAction: { - callback: function () { - alert('test confirmation'); - }, - }, - }); - - pathfora.initializeWidgets([modal]); - - var widget = $('#' + modal.id); - widget.find('input[name="username"]').val('test name'); - widget.find('input[name="email"]').val('test@example.com'); - spyOn(modal.confirmAction, 'callback'); - expect(modal.confirmAction.callback).not.toHaveBeenCalled(); - widget.find('.pf-widget-ok').click(); - expect(modal.confirmAction.callback).toHaveBeenCalledWith( - 'modalConfirm', - jasmine.objectContaining({ - data: [ - { name: 'username', value: 'test name' }, - { name: 'email', value: 'test@example.com' }, - { name: 'title', value: '' }, - { name: 'message', value: '' }, - ], - }) - ); - }); - - it('should trigger callback function after pressing action with custom form data.', function () { - var modal = new pathfora.Form({ - id: 'custom-confirm-action-test', - layout: 'modal', - msg: 'Confirm action test modal', - formElements: [ - { - type: 'text', - required: true, - label: 'Email Address', - name: 'email', - }, - { - type: 'checkbox-group', - required: true, - label: 'Which feeds would you like to subscribe to?', - name: 'subscription_feeds', - values: [ - { - label: 'Beauty & Perfumes', - value: 'beauty', - }, - { - label: 'Electronics', - value: 'electronics', - }, - { - label: 'Fashion', - value: 'fashion', - }, - ], - }, - ], - confirmAction: { - callback: function () { - alert('test confirmation'); - }, - }, - }); - - pathfora.initializeWidgets([modal]); - - var widget = $('#' + modal.id); - widget.find('input[name="email"]').val('test@example.com'); - widget.find('input[name="subscription_feeds"]')[2].checked = true; - spyOn(modal.confirmAction, 'callback'); - expect(modal.confirmAction.callback).not.toHaveBeenCalled(); - widget.find('.pf-widget-ok').click(); - expect(modal.confirmAction.callback).toHaveBeenCalledWith( - 'modalConfirm', - jasmine.objectContaining({ - data: [ - { name: 'email', value: 'test@example.com' }, - { name: 'subscription_feeds', value: 'fashion' }, - ], - }) - ); - }); - - it('should not close the modal on a button action if specified', function (done) { - var modal = new pathfora.Message({ - id: 'confirm-close-action-test', - layout: 'modal', - msg: 'Confirm action test modal', - confirmAction: { - name: 'Test confirm action', - close: false, - callback: function () { - // do something - }, - }, - cancelAction: { - close: false, - }, - }); - - pathfora.initializeWidgets([modal]); - - setTimeout(function () { - var widget = $('#' + modal.id); - expect(widget).toBeDefined(); - expect(widget.hasClass('opened')).toBeTruthy(); - - setTimeout(function () { - widget.find('.pf-widget-ok').click(); - widget.find('.pf-widget-cancel').click(); - expect(widget).toBeDefined(); - expect(widget.hasClass('opened')).toBeTruthy(); - done(); - }, 300); - }, 300); - }); - - it('should be able to trigger action on cancel', function () { - var modal = new pathfora.Message({ - id: 'cancel-action-test', - layout: 'modal', - msg: 'Welcome to our website', - cancelAction: { - name: 'Test cancel action', - callback: function () { - alert('test cancel'); - }, - }, - }); - - pathfora.initializeWidgets([modal]); - - var widget = $('#cancel-action-test'); - spyOn(modal.cancelAction, 'callback'); - widget.find('.pf-widget-cancel').click(); - expect(modal.cancelAction.callback).toHaveBeenCalled(); - }); - - it("shouldn't fire submit callbacks on cancel, and cancel callbacks on submit", function () { - var w1 = new pathfora.Message({ - id: 'widget-with-action-callback', - msg: 'Cancel action negative test', - confirmAction: { - name: 'Test confirm action', - callback: function () { - alert('test confirmation'); - }, - }, - }); - - var w2 = new pathfora.Message({ - id: 'widget-with-cancel-callback', - msg: 'Cancel action negative test', - cancelAction: { - name: 'Test cancel action', - callback: function () { - alert('test cancel'); - }, - }, - }); - - pathfora.initializeWidgets([w1, w2]); - - var widgetA = $('#widget-with-action-callback'), - widgetB = $('#widget-with-cancel-callback'); - - spyOn(w1.confirmAction, 'callback'); - spyOn(w2.cancelAction, 'callback'); - - widgetA.find('.pf-widget-cancel').click(); - expect(w1.confirmAction.callback).not.toHaveBeenCalled(); - - widgetB.find('.pf-widget-ok').click(); - expect(w2.cancelAction.callback).not.toHaveBeenCalled(); - }); - - // ------------------------- - // POSITION - // ------------------------- - - it('should use default position if no position is specified', function () { - var w1 = new pathfora.Message({ - msg: 'button - default pos test', - id: 'position-widget-1', - layout: 'button', - }); - - var w2 = new pathfora.Message({ - msg: 'bar - default pos test', - id: 'position-widget-2', - layout: 'bar', - }); - - var w3 = new pathfora.Message({ - msg: 'slideout - default pos test', - id: 'position-widget-3', - layout: 'slideout', - }); - - pathfora.initializeWidgets([w1, w2, w3]); - - var widget1 = $('#' + w1.id), - widget2 = $('#' + w2.id), - widget3 = $('#' + w3.id); - - expect(widget1.hasClass('pf-position-top-left')).toBeTruthy(); - expect(widget2.hasClass('pf-position-top-absolute')).toBeTruthy(); - expect(widget3.hasClass('pf-position-bottom-left')).toBeTruthy(); - }); - - it('should warn when invalid position', function () { - var w1 = new pathfora.Message({ - msg: 'Widget positioning test', - layout: 'modal', - id: 'region-widget', - position: 'customPos', - }); - - spyOn(console, 'warn'); - pathfora.initializeWidgets([w1]); - - expect(console.warn).toHaveBeenCalledWith( - 'customPos is not a valid position for modal' - ); - }); - - it('should error when custom positionSelector does not exist in dom', function () { - var w1 = new pathfora.Message({ - msg: 'Widget positioning test', - layout: 'modal', - id: 'custom-position-widget', - positionSelector: '.does-not-exist', - }); - - expect(function () { - pathfora.initializeWidgets([w1]); - }).toThrowError(/Widget could not be initialized in .does-not-exist/); - }); - - it('should append the widget to the positionSelector element if it does exist', function (done) { - var div = document.createElement('div'); - div.id = 'overlay'; - document.body.appendChild(div); - - var inline = new pathfora.Message({ - headline: 'Position Custom', - layout: 'modal', - positionSelector: '#overlay', - id: 'custom-position-modal', - msg: 'yay', - }); - - pathfora.initializeWidgets([inline]); - - var parent = $(inline.positionSelector); - - setTimeout(function () { - var widget = parent.find('#' + inline.id); - expect(widget.length).toBe(1); - done(); - }, 200); - }); - - // ------------------------- - // INLINE MODULES - // ------------------------- - it('should throw error if inline positionSelector not found', function () { - // legacy position support - var inline = new pathfora.Message({ - headline: 'Inline Widget', - layout: 'inline', - position: '.a-non-existent-div', - id: 'inline-1', - msg: 'inline', - }); - - var inlineCustom = new pathfora.Message({ - headline: 'Inline Widget', - layout: 'inline', - positionSelector: '.a-non-existent-div', - id: 'inline-2', - msg: 'inline', - }); - - expect(function () { - pathfora.initializeWidgets([inline, inlineCustom]); - }).toThrow( - new Error('Widget could not be initialized in .a-non-existent-div') - ); - }); - - it('should append the inline widget to the positionSelector element', function (done) { - var div = document.createElement('div'); - div.id = 'a-real-div'; - document.body.appendChild(div); - - // legacy position support - var inline = new pathfora.Message({ - headline: 'Inline Widget', - layout: 'inline', - position: '#a-real-div', - id: 'inline-1', - msg: 'inline', - }); - - var inlineCustom = new pathfora.Message({ - headline: 'Inline Widget', - layout: 'inline', - positionSelector: '#a-real-div', - id: 'inline-2', - msg: 'inline', - }); - - pathfora.initializeWidgets([inline, inlineCustom]); - - var parent = $(inline.position); - - setTimeout(function () { - var widget = parent.find('#' + inline.id); - expect(widget.length).toBe(1); - var widget2 = parent.find('#' + inlineCustom.id); - expect(widget2.length).toBe(1); - done(); - }, 200); - }); - - // ------------------------- - // FORM STATES - // ------------------------- - - it('should show success or error state if waitForAsyncResponse is set', function (done) { - var formStatesWidget = new pathfora.Form({ - id: 'form-states', - msg: 'subscription', - headline: 'Header', - layout: 'slideout', - confirmAction: { - waitForAsyncResponse: true, - callback: function (name, payload, cb) { - if (payload.data[0].value === 'test') { - cb(true); - return; - } - cb(false); - }, - }, - formStates: { - success: { - headline: 'test', - msg: 'a custom success message', - delay: 0, - okShow: true, - okMessage: 'confirm success', - confirmAction: { - name: 'confirm success', - callback: function () { - alert('confirm success'); - }, - }, - cancelShow: true, - cancelMessage: 'cancel success', - cancelAction: { - name: 'cancel success', - callback: function () { - alert('cancel success'); - }, - }, - }, - error: { - headline: 'test', - msg: 'a custom error message', - delay: 0, - okShow: true, - okMessage: 'confirm error', - confirmAction: { - name: 'confirm error', - callback: function () { - alert('confirm error'); - }, - }, - cancelShow: true, - cancelMessage: 'cancel error', - cancelAction: { - name: 'cancel error', - callback: function () { - alert('cancel error'); - }, - }, - }, - }, - }); - window.pathfora.initializeWidgets([formStatesWidget]); - - var widget = $('#' + formStatesWidget.id), - form = widget.find('form'); - expect(form.length).toBe(1); - - var name = form.find('input[name="username"]'); - expect(name.length).toBe(1); - name.val('test'); - - var email = form.find('input[name="email"]'); - expect(email.length).toBe(1); - email.val('test@example.com'); - - spyOn(formStatesWidget.confirmAction, 'callback').and.callThrough(); - expect(formStatesWidget.confirmAction.callback).not.toHaveBeenCalled(); - - form.find('.pf-widget-ok').click(); - - expect(formStatesWidget.confirmAction.callback).toHaveBeenCalledWith( - 'modalConfirm', - jasmine.any(Object), - jasmine.any(Function) - ); - - var success = widget.find('.success-state'), - error = widget.find('.error-state'); - - expect(form.css('display')).toBe('none'); - expect(success.css('display')).toBe('block'); - expect(widget.hasClass('success')).toBeTruthy(); - expect(success.find('.pf-widget-headline').html()).toBe( - formStatesWidget.formStates.success.headline - ); - expect(success.find('.pf-widget-message').html()).toBe( - formStatesWidget.formStates.success.msg - ); - - expect(success.find('.pf-widget-ok').html()).toBe( - formStatesWidget.formStates.success.okMessage - ); - expect(success.find('.pf-widget-cancel').html()).toBe( - formStatesWidget.formStates.success.cancelMessage - ); - - spyOn(jstag, 'send'); - spyOn(formStatesWidget.formStates.success.confirmAction, 'callback'); - expect( - formStatesWidget.formStates.success.confirmAction.callback - ).not.toHaveBeenCalled(); - success.find('.pf-widget-ok').click(); - - expect( - formStatesWidget.formStates.success.confirmAction.callback - ).toHaveBeenCalled(); - expect(jstag.send).toHaveBeenCalledWith( - jasmine.objectContaining({ - 'pf-widget-id': formStatesWidget.id, - 'pf-widget-type': 'form', - 'pf-widget-layout': 'slideout', - 'pf-widget-event': 'success.confirm', - 'pf-widget-action': - formStatesWidget.formStates.success.confirmAction.name, - }) - ); - pathfora.clearAll(); - pathfora.closeWidget(formStatesWidget.id, true); - - setTimeout(function () { - window.pathfora.initializeWidgets([formStatesWidget]); - - widget = $('#' + formStatesWidget.id); - form = widget.find('form'); - expect(form.length).toBe(1); - - name = form.find('input[name="username"]'); - expect(name.length).toBe(1); - name.val('bad'); - - email = form.find('input[name="email"]'); - expect(email.length).toBe(1); - email.val('bad@example.com'); - form.find('.pf-widget-ok').click(); - - success = widget.find('.success-state'); - expect(success.length).toBe(1); - error = widget.find('.error-state'); - expect(error.length).toBe(1); - expect(form.css('display')).toBe('none'); - expect(success.css('display')).toBe('none'); - expect(error.css('display')).toBe('block'); - expect(widget.hasClass('error')).toBeTruthy(); - expect(error.find('.pf-widget-headline').html()).toBe( - formStatesWidget.formStates.error.headline - ); - expect(error.find('.pf-widget-message').html()).toBe( - formStatesWidget.formStates.error.msg - ); - expect(error.find('.pf-widget-ok').html()).toBe( - formStatesWidget.formStates.error.okMessage - ); - expect(error.find('.pf-widget-cancel').html()).toBe( - formStatesWidget.formStates.error.cancelMessage - ); - - spyOn(formStatesWidget.formStates.error.cancelAction, 'callback'); - expect( - formStatesWidget.formStates.error.cancelAction.callback - ).not.toHaveBeenCalled(); - error.find('.pf-widget-cancel').click(); - - expect( - formStatesWidget.formStates.error.cancelAction.callback - ).toHaveBeenCalled(); - expect(jstag.send).toHaveBeenCalledWith( - jasmine.objectContaining({ - 'pf-widget-id': formStatesWidget.id, - 'pf-widget-type': 'form', - 'pf-widget-layout': 'slideout', - 'pf-widget-event': 'error.cancel', - 'pf-widget-action': - formStatesWidget.formStates.error.cancelAction.name, - }) - ); - done(); - }, 600); - }); - - // ------------------------- - // LEGACY SUCCESS STATES - // ------------------------- - - it('should show success state if one is set by the user', function (done) { - var successForm = new pathfora.Subscription({ - id: 'success-form', - msg: 'subscription', - headline: 'Header', - layout: 'slideout', - success: { - msg: 'a custom success message', - delay: 2, - }, - }); - - pathfora.initializeWidgets([successForm]); - - var widget = $('#' + successForm.id); - var form = widget.find('form'); - expect(form.length).toBe(1); - - var email = form.find('input[name="email"]'); - expect(email.length).toBe(1); - email.val('test@example.com'); - form.find('.pf-widget-ok').click(); - - var success = widget.find('.success-state'); - - expect(form.css('display')).toBe('none'); - expect(success.css('display')).toBe('block'); - expect(widget.hasClass('success')).toBeTruthy(); - expect(success.find('.pf-widget-message').html()).toBe( - successForm.success.msg - ); - expect(success.find('.pf-widget-ok')).toBeUndefined; - expect(success.find('.pf-widget-cancel')).toBeUndefined; - - setTimeout(function () { - expect(widget.hasClass('opened')).toBeFalsy(); - done(); - }, 2000); - }); - - it('should not hide the module if the success state delay is 0', function (done) { - var successForm2 = new pathfora.Subscription({ - id: 'success-form-no-delay', - msg: 'subscription', - headline: 'Header', - layout: 'slideout', - success: { - msg: 'a custom success message', - delay: 0, - }, - }); - - pathfora.initializeWidgets([successForm2]); - - var widget = $('#' + successForm2.id); - var form = widget.find('form'); - expect(form.length).toBe(1); - - var email = form.find('input[name="email"]'); - expect(email.length).toBe(1); - email.val('test@example.com'); - form.find('.pf-widget-ok').click(); - - var success = widget.find('.success-state'); - - expect(form.css('display')).toBe('none'); - expect(success.css('display')).toBe('block'); - expect(widget.hasClass('success')).toBeTruthy(); - expect(success.find('.pf-widget-message').html()).toBe( - successForm2.success.msg - ); - expect(success.find('.pf-widget-ok')).toBeUndefined; - expect(success.find('.pf-widget-cancel')).toBeUndefined; - - setTimeout(function () { - expect(widget.hasClass('opened')).toBeTruthy(); - expect(widget.hasClass('success')).toBeTruthy(); - - done(); - }, 3000); - }); - - it('should recognize success state buttons and callbacks', function (done) { - var successForm3 = new pathfora.Subscription({ - id: 'success-form-cbs', - msg: 'subscription', - headline: 'Header', - layout: 'slideout', - success: { - headline: 'test', - msg: 'a custom success message', - okShow: true, - cancelShow: true, - cancelMessage: 'Custom Cancel', - confirmAction: { - name: 'test success confirmation', - callback: function () { - window.alert('confirmed'); - }, - }, - cancelAction: { - name: 'test success cancelation', - callback: function () { - window.alert('canceled'); - }, - }, - delay: 0, - }, - }); - - pathfora.initializeWidgets([successForm3]); - var widget = $('#' + successForm3.id); - var form = widget.find('form'); - form.find('input[name="email"]').val('test@example.com'); - form.find('.pf-widget-ok').click(); - - var success = widget.find('.success-state'); - expect(form.css('display')).toBe('none'); - expect(success.css('display')).toBe('block'); - expect(widget.hasClass('success')).toBeTruthy(); - expect(success.find('.pf-widget-headline').html()).toBe( - successForm3.success.headline - ); - expect(success.find('.pf-widget-message').html()).toBe( - successForm3.success.msg - ); - - expect(success.find('.pf-widget-ok').html()).toBe('Confirm'); - expect(success.find('.pf-widget-cancel').html()).toBe('Custom Cancel'); - - spyOn(jstag, 'send'); - spyOn(window, 'alert'); - success.find('.pf-widget-ok').click(); - - expect(jstag.send).toHaveBeenCalledWith( - jasmine.objectContaining({ - 'pf-widget-id': successForm3.id, - 'pf-widget-type': 'subscription', - 'pf-widget-layout': 'slideout', - 'pf-widget-event': 'success.confirm', - 'pf-widget-action': successForm3.success.confirmAction.name, - }) - ); - expect(window.alert).toHaveBeenCalledWith('confirmed'); - - setTimeout(function () { - pathfora.clearAll(); - pathfora.initializeWidgets([successForm3]); - - widget = $('#' + successForm3.id); - form = widget.find('form'); - form.find('input[name="email"]').val('test@example.com'); - form.find('.pf-widget-ok').click(); - - success = widget.find('.success-state'); - success.find('.pf-widget-cancel').click(); - - expect(jstag.send).toHaveBeenCalledWith( - jasmine.objectContaining({ - 'pf-widget-id': successForm3.id, - 'pf-widget-type': 'subscription', - 'pf-widget-layout': 'slideout', - 'pf-widget-event': 'success.cancel', - 'pf-widget-action': successForm3.success.cancelAction.name, - }) - ); - expect(window.alert).toHaveBeenCalledWith('canceled'); - - setTimeout(function () { - done(); - }, 1000); - }, 1000); - }); - - // ------------------------- - // CUSTOM BUTTONS - // ------------------------- - - it('should be able to configure custom text', function () { - var modal = new pathfora.Message({ - id: 'custom-button-text-test', - layout: 'modal', - msg: 'Custom button text test', - headline: 'Hello', - okMessage: 'Confirm Here', - cancelMessage: 'Cancel Here', - }); - - pathfora.initializeWidgets([modal]); - - var widget = $('#' + modal.id), - actionBtn = widget.find('.pf-widget-ok'), - cancelBtn = widget.find('.pf-widget-cancel'); - - expect(actionBtn.html()).toBe('Confirm Here'); - expect(cancelBtn.html()).toBe('Cancel Here'); - }); - - // ------------------------- - // OLD CUSTOM FIELDS - // ------------------------- - - it('should be able to hide and show fields based on config', function () { - var formfields = new pathfora.Form({ - id: 'sample-form', - msg: 'subscription', - headline: 'Header', - layout: 'slideout', - fields: { - title: false, - username: false, - }, - required: { - message: true, - email: false, - }, - }); - - pathfora.initializeWidgets([formfields]); - - var theform = $('#' + formfields.id).find('form'); - expect(theform.length).toBe(1); - - for (var elem in theform[0].children) { - if (typeof theform[0].children[elem].getAttribute !== 'undefined') { - var inputname = theform[0].children[elem].getAttribute('name'), - inputrequired = - theform[0].children[elem].getAttribute('data-required'); - - if (inputname === 'message') { - expect(inputrequired).toBe('true'); - } else if (inputname !== null) { - expect(inputrequired).toBe(null); - } - - expect(inputname).not.toBe('username'); - expect(inputname).not.toBe('title'); - } - } - }); - - // ------------------------- - // FORM BUILDER - // ------------------------- - - it('should track custom fields to lytics', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-1', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'input', - name: 'name', - placeholder: 'Your Name', - required: true, - }, - { - type: 'checkbox-group', - name: 'terms_agreement', - required: true, - values: [ - { - label: 'I agree', - value: 'agree', - }, - ], - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - widget.find('[name=terms_agreement]').click(); - widget.find('[name=name]').val('my name here'); - spyOn(jstag, 'send'); - - widget.find('form').find('.pf-widget-ok').click(); - - expect(jstag.send).toHaveBeenCalledWith( - jasmine.objectContaining({ - 'pf-widget-id': customForm.id, - 'pf-widget-type': 'form', - 'pf-widget-layout': 'slideout', - 'pf-widget-event': 'submit', - 'pf-custom-form': { - terms_agreement: ['agree'], - name: 'my name here', - }, - }) - ); - done(); - }); - - it('should add labels and placeholders for custom fields if defined', function () { - var customForm = new pathfora.Form({ - id: 'custom-form-2', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'select', - label: "What's your favorite animal?", - placeholder: 'Select an animal...', - name: 'favorite_animal', - required: true, - values: [ - { - label: 'Cat', - value: 'cat', - }, - { - label: 'Dog', - value: 'dog', - }, - { - label: 'Horse', - value: 'horse', - }, - ], - }, - { - type: 'checkbox-group', - label: 'Which ice cream flavors do you like the most?', - name: 'ice_cream_flavors', - required: true, - values: [ - { - label: 'Vanilla', - value: 'vanilla', - }, - { - label: 'Chocolate', - value: 'chocolate', - }, - { - label: 'Strawberry', - value: 'strawberry', - }, - ], - }, - { - type: 'textarea', - label: 'Comments', - name: 'comments', - placeholder: 'Any more comments?', - required: true, - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - var labels = widget.find('.pf-form-label'); - var divs = widget.find('.pf-has-label'); - - expect(labels.length).toBe(customForm.formElements.length); - expect(divs.length).toBe(customForm.formElements.length); - - var i; - - for (i = 0; i < labels.length; i++) { - expect( - labels[i].innerHTML.indexOf(customForm.formElements[i].label) !== -1 - ).toBeTruthy(); - } - - for (i = 0; i < divs.length; i++) { - var field = divs[i]; - var configElem = customForm.formElements[i]; - if (field.placeholder && configElem.placeholder) { - expect(field.placeholder).toBe(configElem.placeholder); - } - - if (configElem.type === 'select') { - expect(field.children[0].innerHTML).toBe(configElem.placeholder); - } - } - }); - - it('should not submit the form if required fields are not filled out', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-3', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'input', - placeholder: "What's your favorite animal?", - name: 'favorite_animal', - required: true, - }, - { - type: 'radio-group', - label: 'Which ice cream flavors do you like the most?', - name: 'ice_cream_flavors', - required: true, - values: [ - { - label: 'Vanilla', - value: 'vanilla', - }, - { - label: 'Chocolate', - value: 'chocolate', - }, - { - label: 'Strawberry', - value: 'strawberry', - }, - ], - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - spyOn(jstag, 'send'); - - setTimeout(function () { - widget.find('form').find('.pf-widget-ok').click(); - expect(jstag.send).not.toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeTruthy(); - - var required = widget.find('[data-required=true]'); - expect(required.length).toBe(customForm.formElements.length); - - for (var i = 0; i < required.length; i++) { - var req = required[i].parentNode; - expect(req.className.indexOf('pf-form-required') !== -1).toBeTruthy(); - expect(req.className.indexOf('invalid') !== -1).toBeTruthy(); - } - done(); - }, 200); - }); - - it('should not submit the form if fields are invalid', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-3', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'email', - placeholder: 'Email', - name: 'email', - required: true, - }, - { - type: 'radio-group', - label: 'Which ice cream flavors do you like the most?', - name: 'ice_cream_flavors', - values: [ - { - label: 'Vanilla', - value: 'vanilla', - }, - { - label: 'Chocolate', - value: 'chocolate', - }, - { - label: 'Strawberry', - value: 'strawberry', - }, - ], - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - spyOn(jstag, 'send'); - - setTimeout(function () { - widget.find('input[name=email]').val('zkjhfkdjh'); - widget.find('form').find('.pf-widget-ok').click(); - expect(jstag.send).not.toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeTruthy(); - - var invalid = widget.find('[data-validate=true]'); - expect(invalid.length).toBe(1); - - for (var i = 0; i < invalid.length; i++) { - var req = invalid[i].parentNode; - expect(req.className.indexOf('pf-form-required') !== -1).toBeTruthy(); - expect(req.className.indexOf('bad-validation') !== -1).toBeTruthy(); - } - - // also check required validation - widget.find('input[name=email]').val(''); - widget.find('form').find('.pf-widget-ok').click(); - expect(jstag.send).not.toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeTruthy(); - - invalid = widget.find('[data-required=true]'); - expect(invalid.length).toBe(1); - - for (var j = 0; j < invalid.length; j++) { - var reqField = invalid[j].parentNode; - expect( - reqField.className.indexOf('pf-form-required') !== -1 - ).toBeTruthy(); - expect(reqField.className.indexOf('invalid') !== -1).toBeTruthy(); - } - done(); - }, 200); - }); - - it('should not submit the form if a date field is invalid', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-3', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'date', - name: 'birthday', - maxDate: 'today', - minDate: '01-01-2020', - }, - { - type: 'radio-group', - label: 'Which ice cream flavors do you like the most?', - name: 'ice_cream_flavors', - values: [ - { - label: 'Vanilla', - value: 'vanilla', - }, - { - label: 'Chocolate', - value: 'chocolate', - }, - { - label: 'Strawberry', - value: 'strawberry', - }, - ], - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - spyOn(jstag, 'send'); - - setTimeout(function () { - widget.find('input[name=birthday]').val('2010-10-10'); - widget.find('form').find('.pf-widget-ok').click(); - expect(jstag.send).not.toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeTruthy(); - - var invalid = widget.find('[data-validate=true]'); - expect(invalid.length).toBe(1); - - for (var i = 0; i < invalid.length; i++) { - var req = invalid[i].parentNode; - expect(req.className.indexOf('pf-form-required') !== -1).toBeTruthy(); - expect(req.className.indexOf('bad-validation') !== -1).toBeTruthy(); - } - done(); - }, 200); - }); - - // ------------------------- - // CUSTOM FORM VALIDATION - // ------------------------- - - it('should not submit the form if custom validation fails', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-4', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'text', - placeholder: 'Only 5 Digits Allowed', - name: 'postal_code', - pattern: '^[0-9]{5}$', - required: true, - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - spyOn(jstag, 'send'); - - setTimeout(function () { - var form = widget.find('form'); - var field = form.find('input[name="postal_code"]'); - - field.val('notvalid'); - form.find('.pf-widget-ok').trigger('click'); - expect(jstag.send).not.toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeTruthy(); - - var required = widget.find('[data-required=true]'); - expect(required.length).toBe(customForm.formElements.length); - - for (var i = 0; i < required.length; i++) { - var req = required[i].parentNode; - expect(req.className.indexOf('invalid') !== -1).toBeFalsy(); - } - - done(); - }, 500); - }); - - it('should not submit the form if only 1 of 2 fields pass validation', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-4', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'text', - placeholder: '6 Digits zbzbzb', - name: 'custom_field_1', - pattern: '^[zb]{6}$', - }, - { - type: 'text', - placeholder: 'Only 5 Digits Allowed', - name: 'custom_field_2', - pattern: '^[0-9]{5}$', - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - spyOn(jstag, 'send'); - - setTimeout(function () { - var form = widget.find('form'); - - var field1 = form.find('input[name="custom_field_1"]'); - field1.val('notvalid'); - - var field2 = form.find('input[name="custom_field_2"]'); - field2.val('12345'); - - form.find('.pf-widget-ok').trigger('click'); - - expect(jstag.send).not.toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeTruthy(); - - var validationRequirement = widget.find('[data-validate=true]'); - expect(validationRequirement.length).toBe(customForm.formElements.length); - - // expect field1 to be invalid - var req = validationRequirement[0].parentNode; - expect(req.className.indexOf('bad-validation') !== -1).toBeTruthy(); - - // expect field2 to be valid - req = validationRequirement[1].parentNode; - expect(req.className.indexOf('bad-validation') !== -1).toBeFalsy(); - - done(); - }, 500); - }); - - it('should submit the form if custom validation passes', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-5', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'text', - placeholder: 'Only 5 Digits Allowed', - name: 'postal_code', - pattern: '^[0-9]{5}$', - required: true, - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - spyOn(jstag, 'send'); - - setTimeout(function () { - var form = widget.find('form'); - var field = form.find('input[name="postal_code"]'); - - field.val('12345'); - form.find('.pf-widget-ok').trigger('click'); - expect(jstag.send).toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeFalsy(); - - var required = widget.find('[data-required=true]'); - expect(required.length).toBe(customForm.formElements.length); - - for (var i = 0; i < required.length; i++) { - var req = required[i].parentNode; - expect(req.className.indexOf('invalid') !== -1).toBeFalsy(); - } - - done(); - }, 200); - }); - - it('should add validation parameters if special case of us-postal-code', function (done) { - var customForm = new pathfora.Form({ - id: 'custom-form-6', - msg: 'custom form', - layout: 'slideout', - formElements: [ - { - type: 'us-postal-code', - placeholder: 'Only 5 Digits Allowed', - name: 'postal_code', - required: true, - }, - ], - }); - - pathfora.initializeWidgets([customForm]); - - var widget = $('#' + customForm.id); - spyOn(jstag, 'send'); - - setTimeout(function () { - var form = widget.find('form'); - var field = form.find('input[name="postal_code"]'); - - var pattern = field.attr('enforcePattern'); - expect(pattern).toBe('^[0-9]{5}$'); - - field.val('1234a'); - form.find('.pf-widget-ok').trigger('click'); - expect(jstag.send).not.toHaveBeenCalled(); - expect(widget.hasClass('opened')).toBeTruthy(); - - var required = widget.find('[data-required=true]'); - expect(required.length).toBe(customForm.formElements.length); - - for (var i = 0; i < required.length; i++) { - var req = required[i].parentNode; - expect(req.className.indexOf('invalid') !== -1).toBeFalsy(); - } - - done(); - }, 500); - }); - - // ------------------------- - // IGNORED - // ------------------------- - - // Future functionalities - xit('should allow custom messages on action buttons', function () { - throw 'pass'; - }); - - xit('should be able to show after specific number of visits', function () { - throw 'pass'; - }); - - xit('should be able to randomly choose one of available variations', function () { - throw 'pass'; - }); - - xit('should show warning when user tries to use an invalid position', function () { - spyOn(console, 'warn'); - - var w1 = new pathfora.Message({ - msg: 'test warning display', - id: 'position-widget', - layout: 'bar', - }); - - var w2 = new pathfora.Message({ - msg: 'invalid position test', - layout: 'bar', - id: 'wrong-position-2', - position: 'wrong-position', - }); - - pathfora.initializeWidgets([w1]); - // NOTE Will always fail agaist production env - // expect(console.warn).not.toHaveBeenCalled(); - - pathfora.clearAll(); - - pathfora.initializeWidgets([w2]); - // NOTE Will always fail agaist production env - // expect(console.warn).toHaveBeenCalledWith('wrong-position is not valid position for bar'); - }); -}); diff --git a/test/acceptance/widgets.spec.js b/test/acceptance/widgets.spec.js new file mode 100644 index 00000000..88c894e1 --- /dev/null +++ b/test/acceptance/widgets.spec.js @@ -0,0 +1,509 @@ +import createAndDispatchKeydown from '../utils/create-and-dispatch-keydown.js'; +import globalReset from '../utils/global-reset'; +import { + createMessageWidget, + createFormWidget, + createSubscriptionWidget, + expectWidgetVisible, + expectWidgetHidden, + expectWidgetClosed, + expectWidgetTheme, + createSiteGateWidget, +} from '../utils/test-helpers'; + +describe('widgets', function () { + beforeEach(function () { + globalReset(); + }); + + it('should not allow to register 2 widgets with the same id', function () { + var w1 = createMessageWidget({ + msg: 'Duplicate id test1', + layout: 'modal', + id: 'asd', + }); + + var w2 = createFormWidget({ + msg: 'Duplcate id test2', + layout: 'slideout', + id: 'asd', + }); + + expect(function () { + pathfora.initializeWidgets([w1, w2]); + }).toThrow(new Error('Cannot add two widgets with the same id')); + }); + + it('should use specified global config for all widgets', function () { + var messageBar = createMessageWidget({ + layout: 'bar', + id: 'global-config-1', + msg: 'test', + }); + + var config = { + generic: { + theme: 'light', + }, + }; + + pathfora.initializeWidgets([messageBar], config); + + expectWidgetTheme(messageBar.id, 'light'); + var bar = $('#' + messageBar.id); + expect(bar.hasClass('pf-theme-default')).toBe(false); + }); + + it('should be able to clear all widgets and handlers', function (done) { + var clearDataObject = { + pageViews: 0, + timeSpentOnPage: 0, + closedWidgets: [], + completedActions: [], + cancelledActions: [], + displayedWidgets: [], + abTestingGroups: [], + }; + + var form = createSubscriptionWidget({ + msg: 'test', + id: 'clear-widget', + layout: 'modal', + }); + + pathfora.initializeWidgets([form]); + + setTimeout(function () { + expectWidgetVisible(form.id); + expect(pathfora.getDataObject()).not.toEqual(clearDataObject); + + pathfora.clearAll(); + expectWidgetClosed(form.id); + expect(pathfora.getDataObject()).toEqual(clearDataObject); + done(); + }, 200); + }); + + it('should be able to clear specific widgets by their IDs', function (done) { + var widget1 = createMessageWidget({ + msg: 'Widget 1', + id: 'clear-by-id-1', + layout: 'modal', + }); + + var widget2 = createMessageWidget({ + msg: 'Widget 2', + id: 'clear-by-id-2', + layout: 'slideout', + }); + + var widget3 = createMessageWidget({ + msg: 'Widget 3', + id: 'clear-by-id-3', + layout: 'bar', + }); + + pathfora.initializeWidgets([widget1, widget2, widget3]); + + setTimeout(function () { + // All widgets should be opened initially + expectWidgetVisible(widget1.id); + expectWidgetVisible(widget2.id); + expectWidgetVisible(widget3.id); + + // Clear only widget1 and widget3 + pathfora.clearById(['clear-by-id-1', 'clear-by-id-3']); + + setTimeout(function () { + // Widget1 and widget3 should be closed and removed from DOM + expectWidgetHidden(widget1.id); + expectWidgetHidden(widget3.id); + + // Widget2 should still be opened + expectWidgetVisible(widget2.id); + + // Clear widget2 as well + pathfora.clearById(['clear-by-id-2']); + + setTimeout(function () { + // All widgets should now be closed and removed + expectWidgetHidden(widget1.id); + expectWidgetHidden(widget2.id); + expectWidgetHidden(widget3.id); + done(); + }, 200); + }, 200); + }, 200); + }); + + it('should handle clearById with invalid input gracefully', function (done) { + var widget = createMessageWidget({ + msg: 'Test widget', + id: 'invalid-input-test', + layout: 'modal', + }); + + pathfora.initializeWidgets([widget]); + + // Test with non-array input + spyOn(console, 'warn'); + pathfora.clearById('not-an-array'); + + expect(console.warn).toHaveBeenCalledWith( + 'clearById: widgetIds must be an array' + ); + + // Widget should still be opened + setTimeout(function () { + expect($('#' + widget.id).hasClass('opened')).toBeTruthy(); + done(); + }, 200); + }); + + it('should handle clearById with non-existent widget IDs', function (done) { + var widget = createMessageWidget({ + msg: 'Test widget', + id: 'existing-widget', + layout: 'modal', + }); + + pathfora.initializeWidgets([widget]); + + var element = $('#' + widget.id); + + setTimeout(function () { + expect(element.hasClass('opened')).toBeTruthy(); + + // Try to clear non-existent widget IDs + pathfora.clearById(['non-existent-1', 'non-existent-2']); + + // Existing widget should still be opened + expect(element.hasClass('opened')).toBeTruthy(); + expect($('#' + widget.id).length).toBe(1); + + // Clear the existing widget + pathfora.clearById(['existing-widget']); + + setTimeout(function () { + expect(element.hasClass('opened')).toBeFalsy(); + expect($('#' + widget.id).length).toBe(0); + done(); + }, 200); + }, 200); + }); + + it('should be able to be displayed on document', function (done) { + var promoWidget = createMessageWidget({ + layout: 'bar', + msg: 'Opening widget', + id: 'widget-1', + }); + + pathfora.initializeWidgets([promoWidget]); + + // should append element to DOM + var widget = $('#' + promoWidget.id); + expect(widget).toBeDefined(); + + // should have class 'opened' after while + pathfora.showWidget(promoWidget); + + setTimeout(function () { + expect(widget.hasClass('opened')).toBeTruthy(); + done(); + }, 200); + }); + + it('should have proper id specified', function (done) { + var w1 = createMessageWidget({ + layout: 'slideout', + position: 'right', + msg: 'Welcome to our test website', + id: 'test-id-widget', + }); + + expect(function () { + return new pathfora.Message({ + layout: 'slideout', + position: 'left', + msg: 'Welcome to our test website', + }); + }).toThrow(new Error('All widgets must have an id value')); + + pathfora.initializeWidgets([w1]); + + setTimeout(function () { + var right = $('.pf-widget.pf-position-right'); + expect(right).toBeDefined(); + expect(right.attr('id')).toBe('test-id-widget'); + done(); + }, 200); + }); + + it("should not append widget second time if it's already opened", function (done) { + var openedWidget = createMessageWidget({ + layout: 'modal', + id: 'append-widget', + msg: 'test widget', + }); + + pathfora.initializeWidgets([openedWidget]); + + var widget = $('#' + openedWidget.id); + + // timeouts gives some time for appending to DOM + setTimeout(function () { + expect(widget.hasClass('opened')).toBeTruthy(); + pathfora.showWidget(openedWidget); + + setTimeout(function () { + expect($('#' + openedWidget.id).length).toEqual(1); + done(); + }, 200); + }, 500); + }); + + it('should close when the x button is clicked', function (done) { + var testWidget = createMessageWidget({ + layout: 'modal', + msg: 'Close widget test', + id: 'close-clear-widget', + }); + + pathfora.initializeWidgets([testWidget]); + + var widget = $('#' + testWidget.id); + expect(widget).toBeDefined(); + + setTimeout(function () { + expect(widget.hasClass('opened')).toBeTruthy(); + + widget.find('.pf-widget-close').click(); + expect(widget.hasClass('opened')).toBeFalsy(); + + setTimeout(function () { + expect($('#' + testWidget.id).length).toBe(0); + done(); + }, 600); + }, 200); + }); + + it('should close if the escape key is pressed and it is a modal', function (done) { + var modal = createMessageWidget({ + id: 'modal-esc-test', + layout: 'modal', + headline: 'Message Title', + msg: 'test', + }); + + var gate = createSiteGateWidget({ + id: 'modal-esc-test2', + headline: 'Message Title', + msg: 'test', + }); + + pathfora.initializeWidgets([modal, gate]); + + var widget = $('#' + modal.id); + var widgetGate = $('#' + gate.id); + expect(widget).toBeDefined(); + expect(widgetGate).toBeDefined(); + + setTimeout(function () { + expect(widget.hasClass('opened')).toBeTruthy(); + expect(widgetGate.hasClass('opened')).toBeTruthy(); + + createAndDispatchKeydown(27, document); + + expect(widget.hasClass('opened')).toBeFalsy(); + expect(widgetGate.hasClass('opened')).toBeTruthy(); + + setTimeout(function () { + expect($('#' + modal.id).length).toBe(0); + expect($('#' + gate.id).length).toBe(1); + done(); + }, 600); + }, 200); + }); + + it('should handle missing values properly and never surface undefined', function () { + var message = createMessageWidget({ + id: 'message-test-widget', + layout: 'slideout', + headline: 'Message Title', + theme: 'custom', + }); + + var form = createFormWidget({ + id: 'form-test-widget', + layout: 'modal', + headline: 'Headline Title', + theme: 'custom', + }); + + var subscription = createSubscriptionWidget({ + id: 'subscription-test-widget', + layout: 'bar', + theme: 'custom', + }); + + pathfora.initializeWidgets([message, form, subscription]); + + // test message + var mwidget = $('#' + message.id), + mheadline = mwidget.find('.pf-widget-headline'), + mtext = mwidget.find('.pf-widget-message'); + + expect(mheadline.html()).not.toEqual('undefined'); + expect(mtext.html()).not.toEqual('undefined'); + + // test form + var fwidget = $('#' + form.id), + fheadline = fwidget.find('.pf-widget-headline'), + ftext = fwidget.find('.pf-widget-message'); + + expect(fheadline.html()).not.toEqual('undefined'); + expect(ftext.html()).not.toEqual('undefined'); + + // test subscription + var swidget = $('#' + subscription.id); + var stext = swidget.find('.pf-widget-message'); + expect(stext.html()).not.toEqual('undefined'); + }); + + it('should not allow to be initialized without default properties', function () { + var missingParams = function () { + var promoWidget = new pathfora.Message(); + pathfora.initializeWidgets([promoWidget]); + }; + + expect(missingParams).toThrow(new Error('Config object is missing')); + }); + + it('should not show branding assets unless set otherwise', function () { + var w1 = createMessageWidget({ + msg: 'test', + id: 'branding1', + layout: 'slideout', + branding: true, + }); + + var w2 = createMessageWidget({ + msg: 'test', + id: 'branding2', + layout: 'modal', + }); + + pathfora.initializeWidgets([w1, w2]); + + var widget1 = $('#' + w1.id), + widget2 = $('#' + w2.id); + + expect(widget1.find('.branding svg').length).toBe(1); + expect(widget2.find('.branding svg').length).toBe(0); + }); + + it('should display footer when footerText setting is used', function () { + var modalFooter = createMessageWidget({ + id: 'footer1', + msg: 'test', + layout: 'modal', + footerText: 'Footer text', + }); + + var modalNoFooter = createMessageWidget({ + id: 'footer2', + msg: 'test', + layout: 'modal', + }); + + var slideoutFooter = createMessageWidget({ + id: 'slidout1', + msg: 'test', + layout: 'slideout', + footerText: 'Footer text', + }); + + var slideoutNoFooter = createMessageWidget({ + id: 'slideout2', + msg: 'test', + layout: 'slideout', + }); + + pathfora.initializeWidgets([ + modalFooter, + modalNoFooter, + slideoutFooter, + slideoutNoFooter, + ]); + + var modal1 = $('#' + modalFooter.id), + modal2 = $('#' + modalNoFooter.id), + slideout1 = $('#' + slideoutFooter.id), + slideout2 = $('#' + slideoutNoFooter.id); + expect(modal1.find('.pf-widget-footer').html()).toEqual('Footer text'); + expect(modal2.find('.pf-widget-footer').html()).toEqual(''); + expect(slideout1.find('.pf-widget-footer').html()).toEqual('Footer text'); + expect(slideout2.find('.pf-widget-footer').html()).toEqual(''); + }); + + it('should contain pf-widget-text div for inline and modal layouts', function () { + var modal = createMessageWidget({ + id: 'modal', + msg: 'testmodal', + layout: 'modal', + }); + var div = document.createElement('div'); + div.className = 'some-dom-element'; + document.body.appendChild(div); + var inline = createMessageWidget({ + id: 'inline', + layout: 'inline', + position: '.some-dom-element', + msg: 'testing', + }); + var slideout = createMessageWidget({ + id: 'slideout', + msg: 'test', + layout: 'slideout', + }); + pathfora.initializeWidgets([modal, inline, slideout]); + var modalWidget = $('#' + modal.id), + inlineWidget = $('#' + inline.id), + slideoutWidget = $('#' + slideout.id); + expect(modalWidget.find('.pf-widget-text').html()).toBeDefined(); + expect(inlineWidget.find('.pf-widget-text').html()).toBeDefined(); + expect(slideoutWidget.find('.pf-widget-text').html()).toBeUndefined(); + }); + + it('should append pf-widget-img to pf-widget-content for modal and inline layouts', function () { + var modal = createMessageWidget({ + id: 'modal', + msg: 'testmodal', + layout: 'modal', + image: 'https://lytics.github.io/pathforadocs/assets/lion.jpg', + }); + var div = document.createElement('div'); + div.className = 'some-dom-element'; + document.body.appendChild(div); + var inline = createMessageWidget({ + id: 'inline', + layout: 'inline', + position: '.some-dom-element', + msg: 'testing', + image: 'https://lytics.github.io/pathforadocs/assets/lion.jpg', + }); + pathfora.initializeWidgets([modal, inline]); + var modalWidget = $('#' + modal.id), + inlineWidget = $('#' + inline.id); + expect( + modalWidget.find('.pf-widget-content').find('img').html() + ).toBeDefined(); + expect( + inlineWidget.find('.pf-widget-content').find('img').html() + ).toBeDefined(); + expect( + modalWidget.find('.pf-widget-text').find('img').html() + ).toBeUndefined(); + }); +}); diff --git a/test/utils/test-helpers.js b/test/utils/test-helpers.js new file mode 100644 index 00000000..95919690 --- /dev/null +++ b/test/utils/test-helpers.js @@ -0,0 +1,517 @@ +/** + * Create a Message widget with default test configuration + * @param {Object} overrides - Properties to override defaults + * @returns {Object} Message widget instance + */ +export function createMessageWidget(overrides = {}) { + const defaults = { + id: 'test-message-' + Date.now() + Math.random(), + msg: 'Test message', + layout: 'modal', + headline: 'Test Headline', + }; + return new pathfora.Message({ ...defaults, ...overrides }); +} + +/** + * Create a Form widget with default test configuration + * @param {Object} overrides - Properties to override defaults + * @returns {Object} Form widget instance + */ +export function createFormWidget(overrides = {}) { + const defaults = { + id: 'test-form-' + Date.now() + Math.random(), + msg: 'Test form message', + layout: 'modal', + headline: 'Test Form', + }; + return new pathfora.Form({ ...defaults, ...overrides }); +} + +/** + * Create a Subscription widget with default test configuration + * @param {Object} overrides - Properties to override defaults + * @returns {Object} Subscription widget instance + */ +export function createSubscriptionWidget(overrides = {}) { + const defaults = { + id: 'test-subscription-' + Date.now() + Math.random(), + msg: 'Subscribe to our newsletter', + layout: 'slideout', + headline: 'Stay Updated', + }; + return new pathfora.Subscription({ ...defaults, ...overrides }); +} + +/** + * Create a SiteGate widget with default test configuration + * @param {Object} overrides - Properties to override defaults + * @returns {Object} SiteGate widget instance + */ +export function createSiteGateWidget(overrides = {}) { + const defaults = { + id: 'test-gate-' + Date.now() + Math.random(), + headline: 'Welcome!', + msg: 'Please provide your email to continue', + }; + return new pathfora.SiteGate({ ...defaults, ...overrides }); +} + +/** + * Create form elements array with common test fields + * @param {string} type - Type of form elements ('basic', 'complex', 'custom') + * @param {Array} customFields - Custom fields to add + * @returns {Array} Form elements configuration + */ +export function createFormElements(type = 'basic', customFields = []) { + const formElements = { + basic: [ + { + type: 'input', + name: 'name', + placeholder: 'Your Name', + required: true, + }, + { + type: 'email', + name: 'email', + placeholder: 'Email Address', + required: true, + }, + ], + complex: [ + { + type: 'input', + name: 'name', + placeholder: 'Your Name', + required: true, + }, + { + type: 'email', + name: 'email', + placeholder: 'Email Address', + required: true, + }, + { + type: 'radio-group', + label: "What's your favorite color?", + name: 'favorite_color', + required: true, + values: [ + { label: 'Red', value: 'red' }, + { label: 'Blue', value: 'blue' }, + { label: 'Green', value: 'green' }, + ], + }, + { + type: 'checkbox-group', + label: 'Select your interests', + name: 'interests', + values: [ + { label: 'Technology', value: 'tech' }, + { label: 'Design', value: 'design' }, + { label: 'Marketing', value: 'marketing' }, + ], + }, + ], + custom: customFields, + }; + + return formElements[type] || formElements.basic; +} + +/** + * Create widget with callback actions + * @param {Function} WidgetConstructor - Widget constructor (e.g., pathfora.Message) + * @param {Function} confirmCallback - Confirm action callback + * @param {Function} cancelCallback - Cancel action callback + * @param {Object} overrides - Additional widget properties + * @returns {Object} Widget instance with callbacks + */ +export function createWidgetWithCallbacks( + WidgetConstructor, + confirmCallback, + cancelCallback, + overrides = {} +) { + const config = { + id: 'test-widget-callbacks-' + Date.now() + Math.random(), + msg: 'Test message with callbacks', + layout: 'modal', + ...overrides, + }; + + if (confirmCallback) { + config.confirmAction = { + name: 'Test confirm action', + callback: confirmCallback, + }; + } + + if (cancelCallback) { + config.cancelAction = { + name: 'Test cancel action', + callback: cancelCallback, + }; + } + + return new WidgetConstructor(config); +} + +/** + * Setup Lytics lio object with test data + * @param {Array} segments - Array of segment names + * @param {string} accountId - Account ID + * @param {Object} additionalData - Additional data to merge into lio.data + */ +export function setupLioMock( + segments = ['all'], + accountId = '0', + additionalData = {} +) { + window.lio = { + data: { + segments: segments, + ...additionalData, + }, + account: { + id: accountId, + }, + loaded: true, + }; +} + +/** + * Setup jstag mock with user entity data + * @param {Object} userData - User data object + * @param {string} cid - Client ID + */ +export function setupJstagMock(userData = {}, cid = '123') { + window.jstag.getEntity = function () { + return { + data: { + user: userData, + }, + }; + }; + window.jstag.config.cid = cid; +} + +/** + * Setup jstag tracking spy + * @returns {Object} Jasmine spy object + */ +export function setupTrackingSpy() { + return spyOn(window.jstag, 'send'); +} + +/** + * Mock AJAX response for recommendation widgets + * @param {Array} recommendations - Array of recommendation objects + * @returns {Object} Mock response object + */ +export function mockRecommendationResponse(recommendations = []) { + const defaultRecommendation = { + url: 'www.example.com/1', + title: 'Example Title', + description: 'An example description', + primary_image: + 'http://images.all-free-download.com/images/graphiclarge/blue_envelope_icon_vector_281117.jpg', + confidence: 0.499, + visited: false, + }; + + const data = + recommendations.length > 0 ? recommendations : [defaultRecommendation]; + + return { + status: 200, + contentType: 'application/json', + responseText: JSON.stringify({ data: data }), + }; +} + +/** + * Assert that a widget is visible in the DOM + * @param {string} widgetId - Widget ID + */ +export function expectWidgetVisible(widgetId) { + const widget = $('#' + widgetId); + expect(widget.length).toBe(1); + expect(widget.hasClass('opened')).toBeTruthy(); +} + +/** + * Assert that a widget is not visible in the DOM + * @param {string} widgetId - Widget ID + */ +export function expectWidgetHidden(widgetId) { + const widget = $('#' + widgetId); + expect(widget.length).toBe(0); +} + +/** + * Assert that a widget is not currently shown (closed) + * @param {string} widgetId - Widget ID + */ +export function expectWidgetClosed(widgetId) { + const widget = $('#' + widgetId); + expect(widget.hasClass('opened')).toBeFalsy(); +} + +/** + * Assert that a tracking event was sent + * @param {string} widgetId - Widget ID + * @param {string} eventType - Event type (show, confirm, cancel, close) + * @param {string} eventName - Optional action name + * @param {Object} additionalProps - Additional properties to check + */ +export function expectTrackingEvent( + widgetId, + eventType, + eventName = null, + additionalProps = {} +) { + const expected = { + 'pf-widget-id': widgetId, + 'pf-widget-event': eventType, + ...additionalProps, + }; + + if (eventName) { + expected['pf-widget-action'] = eventName; + } + + expect(window.jstag.send).toHaveBeenCalledWith( + jasmine.objectContaining(expected) + ); +} + +/** + * Assert widget content matches expected values + * @param {string} widgetId - Widget ID + * @param {Object} content - Object with headline and/or msg properties + */ +export function expectWidgetContent(widgetId, content) { + const widget = $('#' + widgetId); + + if (content.headline !== undefined) { + const headline = widget.find('.pf-widget-headline'); + expect(headline.html()).toBe(content.headline); + } + + if (content.msg !== undefined) { + const message = widget.find('.pf-widget-message'); + expect(message.html()).toBe(content.msg); + } +} + +/** + * Assert widget has expected theme class + * @param {string} widgetId - Widget ID + * @param {string} theme - Theme name (dark, light, custom) + */ +export function expectWidgetTheme(widgetId, theme) { + const widget = $('#' + widgetId); + expect(widget.hasClass('pf-theme-' + theme)).toBeTruthy(); +} + +/** + * Assert widget has expected layout class + * @param {string} widgetId - Widget ID + * @param {string} layout - Layout name (modal, slideout, bar, button, inline) + */ +export function expectWidgetLayout(widgetId, layout) { + const widget = $('#' + widgetId); + expect(widget.hasClass('pf-widget-' + layout)).toBeTruthy(); +} + +/** + * Assert form validation errors are displayed + * @param {string} widgetId - Widget ID + * @param {number} expectedErrorCount - Expected number of required fields with errors + */ +export function expectFormValidationErrors(widgetId, expectedErrorCount) { + const widget = $('#' + widgetId); + const required = widget.find('[data-required=true]'); + expect(required.length).toBe(expectedErrorCount); + + for (let i = 0; i < required.length; i++) { + const parent = required[i].parentNode; + expect(parent.className.indexOf('pf-form-required') !== -1).toBeTruthy(); + expect(parent.className.indexOf('invalid') !== -1).toBeTruthy(); + } +} + +/** + * Assert form has expected number of fields + * @param {string} widgetId - Widget ID + * @param {Object} fieldCounts - Object with counts for different field types + */ +export function expectFormFields(widgetId, fieldCounts) { + const widget = $('#' + widgetId); + const form = widget.find('form'); + + if (fieldCounts.inputs !== undefined) { + expect(form.find('input[type="text"]').length).toBe(fieldCounts.inputs); + } + + if (fieldCounts.emails !== undefined) { + expect(form.find('input[type="email"]').length).toBe(fieldCounts.emails); + } + + if (fieldCounts.radios !== undefined) { + expect(form.find('input[type="radio"]').length).toBeGreaterThanOrEqual( + fieldCounts.radios + ); + } + + if (fieldCounts.checkboxes !== undefined) { + expect(form.find('input[type="checkbox"]').length).toBeGreaterThanOrEqual( + fieldCounts.checkboxes + ); + } +} + +/** + * Wait for widget to be rendered in the DOM + * @param {string} widgetId - Widget ID + * @param {number} delay - Delay in milliseconds (default: 200) + * @returns {Promise} Promise that resolves with jQuery element + */ +export function waitForWidget(widgetId, delay = 200) { + return new Promise((resolve) => { + setTimeout(() => { + resolve($('#' + widgetId)); + }, delay); + }); +} + +/** + * Wait for a condition to be true + * @param {Function} condition - Function that returns true when condition is met + * @param {number} timeout - Maximum time to wait in milliseconds (default: 1000) + * @param {number} interval - Check interval in milliseconds (default: 50) + * @returns {Promise} Promise that resolves when condition is met + */ +export function waitFor(condition, timeout = 1000, interval = 50) { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + + const check = () => { + if (condition()) { + resolve(); + } else if (Date.now() - startTime > timeout) { + reject(new Error('Timeout waiting for condition')); + } else { + setTimeout(check, interval); + } + }; + + check(); + }); +} + +/** + * Initialize widgets and wait for them to render (for async/await tests) + * @param {Array|Object} widgets - Widget(s) to initialize + * @param {Object} config - Optional config object + * @param {number} delay - Delay to wait after initialization (default: 200) + * @returns {Promise} Promise that resolves after delay + */ +export function initializeAndWait(widgets, config = null, delay = 200) { + return new Promise((resolve) => { + if (config) { + pathfora.initializeWidgets(widgets, config); + } else { + pathfora.initializeWidgets(widgets); + } + + setTimeout(resolve, delay); + }); +} + +// ============================================================================ +// FORM INTERACTION HELPERS +// ============================================================================ + +/** + * Fill form fields with provided data + * @param {string} widgetId - Widget ID + * @param {Object} formData - Object with field names as keys and values + */ +export function fillForm(widgetId, formData) { + const widget = $('#' + widgetId); + const form = widget.find('form'); + + Object.keys(formData).forEach((name) => { + const field = form.find('[name="' + name + '"]'); + if (field.attr('type') === 'checkbox' || field.attr('type') === 'radio') { + // For radio/checkbox, check the one with matching value + form + .find('[name="' + name + '"][value="' + formData[name] + '"]') + .prop('checked', true); + } else { + field.val(formData[name]); + } + }); +} + +/** + * Fill and submit a form + * @param {string} widgetId - Widget ID + * @param {Object} formData - Object with field names as keys and values + */ +export function fillAndSubmitForm(widgetId, formData) { + fillForm(widgetId, formData); + + const widget = $('#' + widgetId); + widget.find('.pf-widget-ok').click(); +} + +/** + * Get widget jQuery element + * @param {string} widgetId - Widget ID + * @returns {Object} jQuery element + */ +export function getWidget(widgetId) { + return $('#' + widgetId); +} + +/** + * Click widget button by class + * @param {string} widgetId - Widget ID + * @param {string} buttonClass - Button class (e.g., 'pf-widget-ok', 'pf-widget-cancel', 'pf-widget-close') + */ +export function clickWidgetButton(widgetId, buttonClass) { + const widget = $('#' + widgetId); + widget.find('.' + buttonClass).click(); +} + +// ============================================================================ +// WIDGET INTERACTION SHORTCUTS +// ============================================================================ + +/** + * Click the confirm/OK button on a widget + * @param {string} widgetId - Widget ID + */ +export function confirmWidget(widgetId) { + clickWidgetButton(widgetId, 'pf-widget-ok'); +} + +/** + * Click the cancel button on a widget + * @param {string} widgetId - Widget ID + */ +export function cancelWidget(widgetId) { + clickWidgetButton(widgetId, 'pf-widget-cancel'); +} + +/** + * Click the close button on a widget + * @param {string} widgetId - Widget ID + */ +export function closeWidget(widgetId) { + clickWidgetButton(widgetId, 'pf-widget-close'); +}