From fdccf1df0405f06d772232e9e8493dbcd4066903 Mon Sep 17 00:00:00 2001 From: Levente Santha Date: Wed, 25 Aug 2021 13:07:04 +0300 Subject: [PATCH] CIF-2272 - Cannot close category filter accordion once it is open * added unit test --- .../clientlibs/js/productcollection.js | 4 +- .../productCollectionV2Test.js | 456 ++++++++++++++++++ 2 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 ui.apps/test/components/commerce/productcollection/productCollectionV2Test.js diff --git a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v2/productcollection/clientlibs/js/productcollection.js b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v2/productcollection/clientlibs/js/productcollection.js index 6ae9616256..27a8f752a3 100644 --- a/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v2/productcollection/clientlibs/js/productcollection.js +++ b/ui.apps/src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v2/productcollection/clientlibs/js/productcollection.js @@ -19,7 +19,7 @@ class ProductCollection { constructor(config) { this._element = config.element; - let sortKeySelect = document.querySelector(ProductCollection.selectors.sortKey); + let sortKeySelect = this._element.querySelector(ProductCollection.selectors.sortKey); if (sortKeySelect) { sortKeySelect.addEventListener('change', () => this._applySortKey(sortKeySelect)); } @@ -29,7 +29,7 @@ class ProductCollection { loadMoreButton.addEventListener('click', () => this._loadMore(loadMoreButton)); } - let filters = document.querySelector(ProductCollection.selectors.filtersBody); + let filters = this._element.querySelector(ProductCollection.selectors.filtersBody); if (filters) { let selectedFilter = null; filters.addEventListener('click', e => { diff --git a/ui.apps/test/components/commerce/productcollection/productCollectionV2Test.js b/ui.apps/test/components/commerce/productcollection/productCollectionV2Test.js new file mode 100644 index 0000000000..fba3902322 --- /dev/null +++ b/ui.apps/test/components/commerce/productcollection/productCollectionV2Test.js @@ -0,0 +1,456 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2021 Adobe + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +'use strict'; + +import ProductCollection from '../../../../src/main/content/jcr_root/apps/core/cif/components/commerce/productcollection/v2/productcollection/clientlibs/js/productcollection.js'; +import PriceFormatter from '../../../../src/main/content/jcr_root/apps/core/cif/clientlibs/common/js/PriceFormatter.js'; + +describe('Productcollection', () => { + let listRoot; + let windowCIF; + + const clientPrices = { + 'sku-a': { + __typename: 'SimpleProduct', + minimum_price: { + regular_price: { + value: 156.89, + currency: 'USD' + }, + final_price: { + value: 156.89, + currency: 'USD' + }, + discount: { + amount_off: 0, + percent_off: 0 + } + } + }, + 'sku-b': { + __typename: 'ConfigurableProduct', + minimum_price: { + regular_price: { + value: 123.45, + currency: 'USD' + }, + final_price: { + value: 123.45, + currency: 'USD' + }, + discount: { + amount_off: 0, + percent_off: 0 + } + }, + maximum_price: { + regular_price: { + value: 150.45, + currency: 'USD' + }, + final_price: { + value: 150.45, + currency: 'USD' + }, + discount: { + amount_off: 0, + percent_off: 0 + } + } + }, + 'sku-c': { + __typename: 'SimpleProduct', + minimum_price: { + regular_price: { + value: 20, + currency: 'USD' + }, + final_price: { + value: 10, + currency: 'USD' + }, + discount: { + amount_off: 10, + percent_off: 50 + } + } + }, + 'sku-d': { + __typename: 'GroupedProduct', + minimum_price: { + regular_price: { + value: 20, + currency: 'USD' + }, + final_price: { + value: 20, + currency: 'USD' + }, + discount: { + amount_off: 0, + percent_off: 0 + } + } + } + }; + + const convertedPrices = { + 'sku-a': { + isStartPrice: false, + currency: 'USD', + regularPrice: 156.89, + finalPrice: 156.89, + discountAmount: 0, + discountPercent: 0, + discounted: false, + range: false + }, + 'sku-b': { + isStartPrice: false, + currency: 'USD', + regularPrice: 123.45, + finalPrice: 123.45, + discountAmount: 0, + discountPercent: 0, + regularPriceMax: 150.45, + finalPriceMax: 150.45, + discountAmountMax: 0, + discountPercentMax: 0, + discounted: false, + range: true + }, + 'sku-c': { + isStartPrice: false, + currency: 'USD', + regularPrice: 20, + finalPrice: 10, + discountAmount: 10, + discountPercent: 50, + discounted: true, + range: false + }, + 'sku-d': { + isStartPrice: true, + currency: 'USD', + regularPrice: 20, + finalPrice: 20, + discountAmount: 0, + discountPercent: 0, + discounted: false, + range: false + } + }; + + before(() => { + // Create empty context + windowCIF = window.CIF; + window.CIF = {}; + window.CIF.PriceFormatter = PriceFormatter; + }); + + after(() => { + // Restore original context + window.CIF = windowCIF; + }); + + beforeEach(() => { + listRoot = document.createElement('div'); + listRoot.dataset.locale = 'en-US'; // enforce the locale for prices + listRoot.insertAdjacentHTML( + 'afterbegin', + ` +
+
+
+
Filter by
+
+ +
+
+
+
+ 123 +
+
+
+
+ 456 +
+
+
+
+ 789 +
+
+
+
+ 101112 +
+
+
+
` + ); + + window.CIF.CommerceGraphqlApi = { + getProductPrices: sinon.stub().resolves(clientPrices) + }; + }); + + it('initializes a product list component', () => { + let list = new ProductCollection({ element: listRoot }); + + assert.deepEqual(list._state.skus, ['sku-a', 'sku-b', 'sku-c', 'sku-d']); + }); + + it('retrieves prices via GraphQL', () => { + listRoot.dataset.loadClientPrice = true; + let list = new ProductCollection({ element: listRoot }); + assert.isTrue(list._state.loadPrices); + + return list._fetchPrices().then(() => { + assert.isTrue(window.CIF.CommerceGraphqlApi.getProductPrices.called); + assert.deepEqual(list._state.prices, convertedPrices); + + // Verify price updates + assert.equal(listRoot.querySelector('[data-sku=sku-a] .price').innerText, '$156.89'); + assert.equal(listRoot.querySelector('[data-sku=sku-b] .price').innerText, 'From $123.45 To $150.45'); + assert.include(listRoot.querySelector('[data-sku=sku-c] .price').innerText, '$20.00'); + assert.include(listRoot.querySelector('[data-sku=sku-c] .price').innerText, '$10.00'); + assert.equal(listRoot.querySelector('[data-sku=sku-d] .price').innerText, 'Starting at $20.00'); + }); + }); + + it('displays a null price', () => { + listRoot = document.createElement('div'); + listRoot.dataset.locale = 'en-US'; // enforce the locale for prices + listRoot.insertAdjacentHTML( + 'afterbegin', + ` + ` + ); + + const priceRange = { + 'sku-a': { + minimum_price: { + regular_price: { + value: null, + currency: 'USD' + }, + final_price: { + value: null, + currency: 'USD' + } + } + }, + 'sku-b': { + minimum_price: { + regular_price: { + value: null, + currency: 'USD' + }, + final_price: { + value: null, + currency: 'USD' + } + }, + maximum_price: { + regular_price: { + value: null, + currency: 'USD' + }, + final_price: { + value: null, + currency: 'USD' + } + } + } + }; + window.CIF.CommerceGraphqlApi.getProductPrices.resetBehavior(); + window.CIF.CommerceGraphqlApi.getProductPrices.resolves(priceRange); + + listRoot.dataset.loadClientPrice = true; + let list = new ProductCollection({ element: listRoot }); + assert.isTrue(list._state.loadPrices); + + return list._fetchPrices().then(() => { + assert.isTrue(window.CIF.CommerceGraphqlApi.getProductPrices.called); + + // Verify price updates + assert.equal(listRoot.querySelector('[data-sku=sku-a] .price'), null); + assert.equal(listRoot.querySelector('[data-sku=sku-b] .price'), null); + assert.equal(listRoot.querySelector('[data-sku=sku-c] .price'), null); + assert.equal(listRoot.querySelector('[data-sku=sku-c] .price'), null); + }); + }); + + it('skips retrieving of prices if CommerceGraphqlApi is not available', () => { + delete window.CIF.CommerceGraphqlApi; + + listRoot.dataset.loadClientPrice = true; + let list = new ProductCollection({ element: listRoot }); + assert.isTrue(list._state.loadPrices); + + list._fetchPrices(); + assert.isEmpty(list._state.prices); + }); + + it('skips retrieving of prices via GraphQL when data attribute is not set', () => { + let list = new ProductCollection({ element: listRoot }); + assert.isFalse(list._state.loadPrices); + }); + + it('lazy loads products', () => { + listRoot.insertAdjacentHTML( + 'beforeend', + ` +
` + ); + listRoot.dataset.loadClientPrice = true; + + let response = ` +
+
+ 123 +
+
+ `; + + let mockResponse = new window.Response(response, { + status: 200, + headers: { + 'Content-type': 'text/html' + } + }); + + let list = new ProductCollection({ element: listRoot }); + + // Check the skus before we load more products + assert.deepEqual(list._state.skus, ['sku-a', 'sku-b', 'sku-c', 'sku-d']); + + list._fetchMoreProducts = sinon.stub().resolves(mockResponse); + let loadMoreButton = listRoot.querySelector('.productcollection__loadmore-button'); + + return list._loadMore(loadMoreButton).then(() => { + assert.isTrue(list._fetchMoreProducts.called); + + // Verify that the product has been added to the HTML + assert.equal(listRoot.querySelectorAll('.productcollection__item').length, 5); + + // Verify that the first load more button was replaced with the new button + assert.equal( + listRoot.querySelector('.productcollection__loadmore-button').dataset.loadMore, + 'http://more.products2' + ); + + // Check the skus after we load more products and check that the price loading function was called twice + assert.deepEqual(list._state.skus, ['sku-e']); + assert.isTrue(window.CIF.CommerceGraphqlApi.getProductPrices.calledTwice); + }); + }); + + it('lazy loads products with HTTP error', () => { + listRoot.insertAdjacentHTML( + 'beforeend', + ` +
` + ); + + let mockResponse = new window.Response('Internal server error', { + status: 500, + headers: { + 'Content-type': 'text/html' + } + }); + + let list = new ProductCollection({ element: listRoot }); + list._fetchMoreProducts = sinon.stub().resolves(mockResponse); + let loadMoreButton = listRoot.querySelector('.productcollection__loadmore-button'); + + return list._loadMore(loadMoreButton).catch(error => { + assert.isTrue(list._fetchMoreProducts.called); + assert.equal(error.message, 'Internal server error'); + + // Verify that the product has NOT been added to the HTML + assert.equal(listRoot.querySelectorAll('.productcollection__item').length, 4); + + // Verify that the first load more button is still there + assert.equal( + listRoot.querySelector('.productcollection__loadmore-button').dataset.loadMore, + 'http://more.products' + ); + }); + }); + + it('selects and deselects filter on mouse click', () => { + let list = new ProductCollection({ element: listRoot }); + + let priceFilter = listRoot.querySelector('.productcollection__filter-toggler[id="price"]'); + assert.isNotNull(priceFilter); + assert.isFalse(priceFilter.checked); + + priceFilter.click(); + assert.isTrue(priceFilter.checked); + + priceFilter.click(); + assert.isFalse(priceFilter.checked); + }); +});