diff --git a/Gruntfile.js b/Gruntfile.js index 6af2f86771..eb4470f6be 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,5 +8,6 @@ module.exports = function(grunt) { } }); - grunt.registerTask('default', ['eslint', 'karma', 'scsslint', 'svgstore']) + grunt.loadNpmTasks('grunt-run'); + grunt.registerTask('default', ['eslint', 'jest', 'scsslint', 'svgstore']) }; diff --git a/assets/js/test-unit/theme/common/collapsible-group.spec.js b/assets/js/test-unit/theme/common/collapsible-group.spec.js index 7d3db8102f..c8073d4238 100644 --- a/assets/js/test-unit/theme/common/collapsible-group.spec.js +++ b/assets/js/test-unit/theme/common/collapsible-group.spec.js @@ -25,14 +25,17 @@ describe('CollapsibleGroup', () => { let childCollapsible; beforeEach(() => { - collapsible = jasmine.createSpyObj('collapsible', ['close', 'hasCollapsible']); + collapsible = { + close: jest.fn(), + hasCollapsible: jest.fn() + }; childCollapsible = {}; collapsibleGroup.openCollapsible = collapsible; }); it('should close the currently open collapsible if it does not contain the newly open collapsible', () => { - collapsible.hasCollapsible.and.returnValue(false); + collapsible.hasCollapsible.mockImplementation(() => false); collapsibleGroup.$component.trigger(CollapsibleEvents.open, [childCollapsible]); expect(collapsible.close).toHaveBeenCalled(); @@ -40,7 +43,7 @@ describe('CollapsibleGroup', () => { }); it('should not close the currently open collapsible if it contains the newly open collapsible', () => { - collapsible.hasCollapsible.and.returnValue(true); + collapsible.hasCollapsible.mockImplementation(() => true); collapsibleGroup.$component.trigger(CollapsibleEvents.open, [childCollapsible]); expect(collapsible.close).not.toHaveBeenCalled(); @@ -54,21 +57,24 @@ describe('CollapsibleGroup', () => { let childCollapsible; beforeEach(() => { - collapsible = jasmine.createSpyObj('collapsible', ['hasCollapsible']); + collapsible = { + close: jest.fn(), + hasCollapsible: jest.fn() + }; childCollapsible = {}; collapsibleGroup.openCollapsible = collapsible; }); it('should unset `openCollapsible` if it does not contain the newly open collapsible', () => { - collapsible.hasCollapsible.and.returnValue(false); + collapsible.hasCollapsible.mockImplementation(() => false); collapsibleGroup.$component.trigger(CollapsibleEvents.close, [childCollapsible]); expect(collapsibleGroup.openCollapsible).toEqual(null); }); it('should not unset `openCollapsible` if it contains the newly open collapsible', () => { - collapsible.hasCollapsible.and.returnValue(true); + collapsible.hasCollapsible.mockImplementation(() => true); collapsibleGroup.$component.trigger(CollapsibleEvents.close, [childCollapsible]); expect(collapsibleGroup.openCollapsible).not.toEqual(null); diff --git a/assets/js/test-unit/theme/common/collapsible.spec.js b/assets/js/test-unit/theme/common/collapsible.spec.js index 29787ef97d..9d5bb6ecd3 100644 --- a/assets/js/test-unit/theme/common/collapsible.spec.js +++ b/assets/js/test-unit/theme/common/collapsible.spec.js @@ -32,9 +32,9 @@ describe('Collapsible', () => { describe('when clicking on a toggle', () => { beforeEach(() => { - spyOn(collapsible, 'open'); - spyOn(collapsible, 'close'); - spyOn(collapsible, 'toggle').and.callThrough(); + jest.spyOn(collapsible, 'open').mockImplementation(() => {}); + jest.spyOn(collapsible, 'close').mockImplementation(() => {}); + jest.spyOn(collapsible, 'toggle'); }); it('should open if it is closed', () => { diff --git a/assets/js/test-unit/theme/common/faceted-search.spec.js b/assets/js/test-unit/theme/common/faceted-search.spec.js index 8ea67185b3..ca79ddeb64 100644 --- a/assets/js/test-unit/theme/common/faceted-search.spec.js +++ b/assets/js/test-unit/theme/common/faceted-search.spec.js @@ -12,7 +12,7 @@ describe('FacetedSearch', () => { let $element; beforeEach(() => { - onSearchSuccess = jasmine.createSpy('onSearchSuccess'); + onSearchSuccess = jest.fn(); requestOptions = { config: { @@ -73,9 +73,9 @@ describe('FacetedSearch', () => { beforeEach(() => { content = { html: '
Results
' }; - spyOn(facetedSearch, 'restoreCollapsedFacets'); - spyOn(facetedSearch, 'restoreCollapsedFacetItems'); - spyOn(Validators, 'setMinMaxPriceValidation'); + jest.spyOn(facetedSearch, 'restoreCollapsedFacets').mockImplementation(() => {}); + jest.spyOn(facetedSearch, 'restoreCollapsedFacetItems').mockImplementation(() => {}); + jest.spyOn(Validators, 'setMinMaxPriceValidation').mockImplementation(() => {}); }); it('should update view with content by firing registered callback', () => { @@ -108,9 +108,9 @@ describe('FacetedSearch', () => { const url = '/current/path?facet=1'; beforeEach(() => { - spyOn(api, 'getPage'); - spyOn(facetedSearch, 'refreshView'); - spyOn(urlUtils, 'getUrl').and.returnValue(url); + jest.spyOn(api, 'getPage').mockImplementation(() => {}); + jest.spyOn(facetedSearch, 'refreshView').mockImplementation(() => {}); + jest.spyOn(urlUtils, 'getUrl').mockImplementation(() => url); content = {}; }); @@ -118,11 +118,11 @@ describe('FacetedSearch', () => { it('should fetch content from remote server', function() { facetedSearch.updateView(); - expect(api.getPage).toHaveBeenCalledWith(url, requestOptions, jasmine.any(Function)); + expect(api.getPage).toHaveBeenCalledWith(url, requestOptions, expect.any(Function)); }); it('should refresh view', function() { - api.getPage.and.callFake(function(url, options, callback) { + jest.spyOn(api, 'getPage').mockImplementation(function(url, options, callback) { callback(null, content); }); @@ -153,8 +153,8 @@ describe('FacetedSearch', () => { let $navList; beforeEach(() => { - spyOn(facetedSearch, 'getMoreFacetResults'); - spyOn(facetedSearch, 'collapseFacetItems'); + jest.spyOn(facetedSearch, 'getMoreFacetResults').mockImplementation(() => {}); + jest.spyOn(facetedSearch, 'collapseFacetItems').mockImplementation(() => {}); $navList = $('#facet-brands'); }); @@ -180,11 +180,11 @@ describe('FacetedSearch', () => { beforeEach(() => { href = document.location.href; - spyOn(facetedSearch, 'updateView'); + jest.spyOn(facetedSearch, 'updateView').mockImplementation(() => {}); }); afterEach(() => { - urlUtils.goToUrl(href); + urlUtils.goToUrl('/'); }); it('should update view', () => { @@ -202,21 +202,21 @@ describe('FacetedSearch', () => { eventName = 'facetedSearch-range-submitted'; event = { currentTarget: '#facet-range-form', - preventDefault: jasmine.createSpy('preventDefault'), + preventDefault: jest.fn(), }; - spyOn(urlUtils, 'goToUrl'); - spyOn(facetedSearch.priceRangeValidator, 'areAll').and.returnValue(true); + jest.spyOn(urlUtils, 'goToUrl').mockImplementation(() => {}); + jest.spyOn(facetedSearch.priceRangeValidator, 'areAll').mockImplementation(() => true); }); it('should set `min_price` and `max_price` query param to corresponding form values if form is valid', () => { hooks.emit(eventName, event); - expect(urlUtils.goToUrl).toHaveBeenCalledWith('/context.html?min_price=0&max_price=100'); + expect(urlUtils.goToUrl).toHaveBeenCalledWith('/?min_price=0&max_price=100'); }); it('should not set `min_price` and `max_price` query param to corresponding form values if form is invalid', () => { - facetedSearch.priceRangeValidator.areAll.and.returnValue(false); + jest.spyOn(facetedSearch.priceRangeValidator, 'areAll').mockImplementation(() => false); hooks.emit(eventName, event); expect(urlUtils.goToUrl).not.toHaveBeenCalled(); @@ -237,17 +237,17 @@ describe('FacetedSearch', () => { eventName = 'facetedSearch-range-submitted'; event = { currentTarget: '#facet-range-form-with-other-facets', - preventDefault: jasmine.createSpy('preventDefault'), + preventDefault: jest.fn(), }; - spyOn(urlUtils, 'goToUrl'); - spyOn(facetedSearch.priceRangeValidator, 'areAll').and.returnValue(true); + jest.spyOn(urlUtils, 'goToUrl').mockImplementation(() => {}); + jest.spyOn(facetedSearch.priceRangeValidator, 'areAll').mockImplementation(() => true); }); it('send `min_price` and `max_price` query params if form is valid', () => { hooks.emit(eventName, event); - expect(urlUtils.goToUrl).toHaveBeenCalledWith('/context.html?brand[]=item1&brand[]=item2&min_price=0&max_price=50'); + expect(urlUtils.goToUrl).toHaveBeenCalledWith('/?brand[]=item1&brand[]=item2&min_price=0&max_price=50'); }); }); @@ -259,16 +259,16 @@ describe('FacetedSearch', () => { eventName = 'sortBy-submitted'; event = { currentTarget: '#facet-sort', - preventDefault: jasmine.createSpy('preventDefault'), + preventDefault: jest.fn(), }; - spyOn(urlUtils, 'goToUrl'); + jest.spyOn(urlUtils, 'goToUrl').mockImplementation(() => {}); }); it('should set `sort` query param to the value of selected option', () => { hooks.emit(eventName, event); - expect(urlUtils.goToUrl).toHaveBeenCalledWith('/context.html?sort=featured'); + expect(urlUtils.goToUrl).toHaveBeenCalledWith('/?sort=featured'); }); it('should prevent default event', function() { @@ -286,10 +286,10 @@ describe('FacetedSearch', () => { eventName = 'facetedSearch-facet-clicked'; event = { currentTarget: '[href="?brand=item1"]', - preventDefault: jasmine.createSpy('preventDefault'), + preventDefault: jest.fn(), }; - spyOn(urlUtils, 'goToUrl'); + jest.spyOn(urlUtils, 'goToUrl').mockImplementation(() => {}); }); it('should change the URL of window to the URL of facet item', () => { diff --git a/assets/js/test-unit/theme/common/form-utils.spec.js b/assets/js/test-unit/theme/common/form-utils.spec.js index c888fa73ed..e27fbad411 100644 --- a/assets/js/test-unit/theme/common/form-utils.spec.js +++ b/assets/js/test-unit/theme/common/form-utils.spec.js @@ -4,11 +4,11 @@ describe('Validators', () => { let validator; beforeEach(() => { - validator = jasmine.createSpyObj('validator', [ - 'add', - 'configure', - 'setMessageOptions', - ]); + validator = { + add: jest.fn(), + configure: jest.fn(), + setMessageOptions: jest.fn() + }; }); describe('setMinMaxPriceValidation', () => { diff --git a/assets/js/test-unit/theme/common/state-country.spec.js b/assets/js/test-unit/theme/common/state-country.spec.js new file mode 100644 index 0000000000..4617840dea --- /dev/null +++ b/assets/js/test-unit/theme/common/state-country.spec.js @@ -0,0 +1,116 @@ +import $ from 'jquery'; +import '../../../theme/global/jquery-migrate'; +//import { api } from '@bigcommerce/stencil-utils'; +jest.mock('@bigcommerce/stencil-utils'); +import utils from '@bigcommerce/stencil-utils'; +const api = utils.api; +import modalFactory, { alertModal } from '../../../theme/global/modal'; +import foundation from '../../../theme/global/foundation'; +import stateCountry from '../../../theme/common/state-country'; + +//console.log(utils); +//jest.mock('api.country'); + +describe('StateCountry', () => { + let $countryElement, $stateElement; + + beforeEach(() => { + $countryElement = $(` + + `); + $stateElement = $(` + + `); + $countryElement.appendTo(document.body); + $stateElement.appendTo(document.body); + }); + + afterEach(() => { + $countryElement.remove(); + $stateElement.remove(); + }); + + describe('on error', () => { + let $modalElement, modal; + + beforeEach(() => { + $modalElement = $(` + + `) + $modalElement.appendTo(document.body); + modal = alertModal(); + + api.country.getByName.mockImplementation((countryName, callback) => { + callback(new Error(countryName + 'missing'), null); + }); + jest.spyOn(modal, 'open'); + }); + + afterEach(() => { + $modalElement.remove(); + $('body').removeClass(); + }); + + it('should show modal', (done) => { + stateCountry($stateElement, {state_error: 'Missing'}, {}, (err) => { + expect(modal.open).toHaveBeenCalled(); + done(); + }); + + $countryElement.val('1').trigger('change'); + }); + }); + + describe('on change', () => { + beforeEach(() => { + api.country.getByName.mockImplementation((countryName, callback) => { + let states = []; + switch(countryName) { + case '1': break; + case '3': + states = [ + {id: '1', name: 'Kepler'}, + {id: '2', name: 'Grimaldi'}, + {id: '3', name: 'Byrgius'} + ]; + break; + } + + callback(null, {data: {states}}); + }); + }); + + it('should update states', (done) => { + stateCountry($stateElement, {}, {}, (err) => { + $stateElement.remove(); + $stateElement = $('[data-field-type="State"]'); + expect($stateElement.find('option').length).toEqual(4); + + const names = $stateElement.find('option').map(function() { + return $(this).text(); + }).get(); + expect(names).toEqual(['undefined', 'Kepler', 'Grimaldi', 'Byrgius']); + + done(); + }); + + $countryElement.val('3').trigger('change'); + }); + + it('should erase existing', (done) => { + $countryElement.val('3').trigger('change'); + $countryElement.val('1').trigger('change'); + done(); + }); + }); +}); \ No newline at end of file diff --git a/assets/js/test-unit/theme/global/modal.spec.js b/assets/js/test-unit/theme/global/modal.spec.js index c60fa409c5..459e2ede9b 100644 --- a/assets/js/test-unit/theme/global/modal.spec.js +++ b/assets/js/test-unit/theme/global/modal.spec.js @@ -1,6 +1,6 @@ -import '../../../theme/global/jquery-migrate'; import modalFactory, { ModalEvents } from '../../../theme/global/modal'; import $ from 'jquery'; +import '../../../theme/global/jquery-migrate'; function attachHtml(html) { const $element = $(html); @@ -47,8 +47,6 @@ describe('Modal', () => { let $modalBody; beforeEach(() => { - $('body').height(500); - $modalBody = $(`