From 0f47c8c7453e96ab44675d6edd12b6606cdc1a69 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 14:11:57 +0200 Subject: [PATCH 01/17] refactor: improve typing of vue-outside-click --- .../components/Header/SearchBar/SearchBar.vue | 4 ++-- ...e-directive.js => click-outside-directive.ts} | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) rename packages/theme/utilities/directives/click-outside/{click-outside-directive.js => click-outside-directive.ts} (57%) diff --git a/packages/theme/components/Header/SearchBar/SearchBar.vue b/packages/theme/components/Header/SearchBar/SearchBar.vue index ab63e0ea8..07a2c0052 100644 --- a/packages/theme/components/Header/SearchBar/SearchBar.vue +++ b/packages/theme/components/Header/SearchBar/SearchBar.vue @@ -54,9 +54,9 @@ import { defineComponent, ref, watch, useRoute, } from '@nuxtjs/composition-api'; import debounce from 'lodash.debounce'; -import { clickOutside } from '~/utilities/directives/click-outside/click-outside-directive.js'; +import { clickOutside } from '~/utilities/directives/click-outside/click-outside-directive'; import SvgImage from '~/components/General/SvgImage.vue'; -import { useFacet } from '~/composables'; +import { useFacet } from '~/modules/catalog/category/composables/useFacet'; export default defineComponent({ name: 'SearchBar', diff --git a/packages/theme/utilities/directives/click-outside/click-outside-directive.js b/packages/theme/utilities/directives/click-outside/click-outside-directive.ts similarity index 57% rename from packages/theme/utilities/directives/click-outside/click-outside-directive.js rename to packages/theme/utilities/directives/click-outside/click-outside-directive.ts index 93a12f9ac..28c88ad28 100644 --- a/packages/theme/utilities/directives/click-outside/click-outside-directive.js +++ b/packages/theme/utilities/directives/click-outside/click-outside-directive.ts @@ -1,20 +1,24 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable no-param-reassign */ -export const clickOutside = { - bind(el, binding) { - binding.name = 'click-outside'; +import { DirectiveOptions } from 'vue'; + +type ElementWithClickOutsideListener = HTMLElement & { _outsideClickHandler: (e: MouseEvent) => void }; +export const clickOutside : DirectiveOptions = { + bind(el: ElementWithClickOutsideListener, binding) { const closeHandler = binding.value; - el._outsideClickHandler = function clickOutsideHandler(event) { - if (!el.contains(event.target)) { + + el._outsideClickHandler = (event: MouseEvent) => { + if (!el.contains(event.target as Node)) { event.stopPropagation(); closeHandler(event, el); } }; + document.addEventListener('mousedown', el._outsideClickHandler); document.addEventListener('touchstart', el._outsideClickHandler); }, - unbind(el) { + unbind(el: ElementWithClickOutsideListener) { document.removeEventListener('mousedown', el._outsideClickHandler); document.removeEventListener('touchstart', el._outsideClickHandler); el._outsideClickHandler = null; From f0c6a793e0e949dfb11755432e594b99130c5954 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 14:36:20 +0200 Subject: [PATCH 02/17] refactor: fix eslint errors in cypress entrypoint --- packages/theme/tests/e2e/cypress.json | 2 +- packages/theme/tests/e2e/plugins/index.js | 25 ----------------------- packages/theme/tests/e2e/plugins/index.ts | 11 ++++++++++ 3 files changed, 12 insertions(+), 26 deletions(-) delete mode 100755 packages/theme/tests/e2e/plugins/index.js create mode 100755 packages/theme/tests/e2e/plugins/index.ts diff --git a/packages/theme/tests/e2e/cypress.json b/packages/theme/tests/e2e/cypress.json index 3ef6351c7..a2c690465 100755 --- a/packages/theme/tests/e2e/cypress.json +++ b/packages/theme/tests/e2e/cypress.json @@ -2,7 +2,7 @@ "baseUrl": "http://localhost:3000", "fixturesFolder": "tests/e2e/fixtures", "integrationFolder": "tests/e2e/integration", - "pluginsFile": "tests/e2e/plugins/index.js", + "pluginsFile": "tests/e2e/plugins/index.ts", "supportFile": "tests/e2e/support/index.js", "viewportHeight": 1080, "viewportWidth": 1920, diff --git a/packages/theme/tests/e2e/plugins/index.js b/packages/theme/tests/e2e/plugins/index.js deleted file mode 100755 index 70b6427fe..000000000 --- a/packages/theme/tests/e2e/plugins/index.js +++ /dev/null @@ -1,25 +0,0 @@ -// eslint-disable-next-line spaced-comment -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -const tagify = require('cypress-tags'); - -/** - * @type {Cypress.PluginConfig} - */ -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - on('file:preprocessor', tagify(config)); -}; diff --git a/packages/theme/tests/e2e/plugins/index.ts b/packages/theme/tests/e2e/plugins/index.ts new file mode 100755 index 000000000..753679d49 --- /dev/null +++ b/packages/theme/tests/e2e/plugins/index.ts @@ -0,0 +1,11 @@ +/* eslint-disable unicorn/prefer-module */ +// using import does not work since cypress-tags is commonjs +const tagify = require('cypress-tags'); + +const pluginConfig : Cypress.PluginConfig = (on, config) => { + // tagify does not ship types + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + on('file:preprocessor', tagify(config)); +}; + +export default pluginConfig; From 18b7bf27ee4d22749c150226ac23ea7e2ba300dd Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 14:48:05 +0200 Subject: [PATCH 03/17] chore: split cypress classes into 4 separate files --- .../theme/tests/e2e/pages/checkout/Billing.ts | 15 ++++++ .../theme/tests/e2e/pages/checkout/Payment.ts | 15 ++++++ .../{checkout.ts => checkout/Shipping.ts} | 47 ++----------------- .../tests/e2e/pages/checkout/ThankYou.ts | 7 +++ .../theme/tests/e2e/pages/checkout/index.ts | 4 ++ 5 files changed, 44 insertions(+), 44 deletions(-) create mode 100644 packages/theme/tests/e2e/pages/checkout/Billing.ts create mode 100644 packages/theme/tests/e2e/pages/checkout/Payment.ts rename packages/theme/tests/e2e/pages/{checkout.ts => checkout/Shipping.ts} (65%) mode change 100755 => 100644 create mode 100644 packages/theme/tests/e2e/pages/checkout/ThankYou.ts create mode 100755 packages/theme/tests/e2e/pages/checkout/index.ts diff --git a/packages/theme/tests/e2e/pages/checkout/Billing.ts b/packages/theme/tests/e2e/pages/checkout/Billing.ts new file mode 100644 index 000000000..dec8b7ae5 --- /dev/null +++ b/packages/theme/tests/e2e/pages/checkout/Billing.ts @@ -0,0 +1,15 @@ +import { el } from '../utils/element'; + +export class Billing { + get continueToPaymentButton(): Cypress.Chainable { + return el('continue-to-payment'); + } + + get heading(): Cypress.Chainable { + return el('heading-billing'); + } + + get copyAddressLabel(): Cypress.Chainable { + return el('copy-address', 'label'); + } +} diff --git a/packages/theme/tests/e2e/pages/checkout/Payment.ts b/packages/theme/tests/e2e/pages/checkout/Payment.ts new file mode 100644 index 000000000..6b10347dd --- /dev/null +++ b/packages/theme/tests/e2e/pages/checkout/Payment.ts @@ -0,0 +1,15 @@ +import { el } from '../utils/element'; + +export class Payment { + get makeAnOrderButton(): Cypress.Chainable { + return el('make-an-order'); + } + + get paymentMethods(): Cypress.Chainable { + return el('payment-method'); + } + + get terms(): Cypress.Chainable { + return el('terms', 'label'); + } +} diff --git a/packages/theme/tests/e2e/pages/checkout.ts b/packages/theme/tests/e2e/pages/checkout/Shipping.ts old mode 100755 new mode 100644 similarity index 65% rename from packages/theme/tests/e2e/pages/checkout.ts rename to packages/theme/tests/e2e/pages/checkout/Shipping.ts index e01fd9412..fd3a22e67 --- a/packages/theme/tests/e2e/pages/checkout.ts +++ b/packages/theme/tests/e2e/pages/checkout/Shipping.ts @@ -1,7 +1,7 @@ -import { Customer } from '../types/customer'; -import { el } from './utils/element'; +import { Customer } from '../../types/customer'; +import { el } from '../utils/element'; -class Shipping { +export class Shipping { get firstName(): Cypress.Chainable { return el('firstName'); } @@ -66,44 +66,3 @@ class Shipping { this.phone.type(customer.address.shipping.phone); } } - -class Billing { - get continueToPaymentButton(): Cypress.Chainable { - return el('continue-to-payment'); - } - - get heading(): Cypress.Chainable { - return el('heading-billing'); - } - - get copyAddressLabel(): Cypress.Chainable { - return el('copy-address', 'label'); - } -} - -class Payment { - get makeAnOrderButton(): Cypress.Chainable { - return el('make-an-order'); - } - - get paymentMethods(): Cypress.Chainable { - return el('payment-method'); - } - - get terms(): Cypress.Chainable { - return el('terms', 'label'); - } -} - -class ThankYou { - get heading(): Cypress.Chainable { - return el('thank-you-banner', 'h2'); - } -} - -export { - Shipping, - Billing, - Payment, - ThankYou, -}; diff --git a/packages/theme/tests/e2e/pages/checkout/ThankYou.ts b/packages/theme/tests/e2e/pages/checkout/ThankYou.ts new file mode 100644 index 000000000..52931825b --- /dev/null +++ b/packages/theme/tests/e2e/pages/checkout/ThankYou.ts @@ -0,0 +1,7 @@ +import { el } from '../utils/element'; + +export class ThankYou { + get heading(): Cypress.Chainable { + return el('thank-you-banner', 'h2'); + } +} diff --git a/packages/theme/tests/e2e/pages/checkout/index.ts b/packages/theme/tests/e2e/pages/checkout/index.ts new file mode 100755 index 000000000..c23d6b76d --- /dev/null +++ b/packages/theme/tests/e2e/pages/checkout/index.ts @@ -0,0 +1,4 @@ +export { Billing } from './Billing'; +export { Shipping } from './Shipping'; +export { Payment } from './Payment'; +export { ThankYou } from './ThankYou'; From 4c266f138d52b9244209f90582152e3fc709808f Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 15:01:36 +0200 Subject: [PATCH 04/17] chore: disable eslint rules that don't work well in cypress --- .eslintrc.js | 12 +++++++++++- .../tests/e2e/integration/e2e-user-login.spec.ts | 9 +++++---- .../e2e/integration/e2e-user-registration.spec.ts | 7 ++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d0858feb9..092e5d103 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,6 +39,16 @@ module.exports = { } ], "no-plusplus": "off", - } + }, + overrides: [ + { + "files": ["packages/theme/tests/e2e/**/*"], + "rules": { + "jest/expect-expect": "off", + "promise/catch-or-return": "off", // conflicts with Cypress.Chainable + "promise/always-return": "off", + } + } + ] } diff --git a/packages/theme/tests/e2e/integration/e2e-user-login.spec.ts b/packages/theme/tests/e2e/integration/e2e-user-login.spec.ts index 3c8740991..2f939e079 100755 --- a/packages/theme/tests/e2e/integration/e2e-user-login.spec.ts +++ b/packages/theme/tests/e2e/integration/e2e-user-login.spec.ts @@ -1,6 +1,7 @@ import generator from '../utils/data-generator'; import page from '../pages/factory'; import requests from '../api/requests'; +import { Customer } from '../types/customer'; before(() => { cy.clearLocalStorage(); @@ -16,7 +17,7 @@ context(['regression'], 'User login', () => { const data = cy.fixtures.data['Should successfully login']; data.customer.email = generator.email; - requests.createCustomer(data.customer).then(() => { + requests.createCustomer(data.customer as Customer).then(() => { cy.clearCookies(); }); @@ -24,7 +25,7 @@ context(['regression'], 'User login', () => { page.home.header.openLoginModal(); page.components.loginModal.loginToAccountButton.click(); - page.components.loginModal.fillForm(data.customer); + page.components.loginModal.fillForm(data.customer as Customer); page.components.loginModal.loginBtn.click(); page.components.loginModal.container.should('not.exist'); page.home.header.account.click(); @@ -37,8 +38,8 @@ context(['regression'], 'User login', () => { page.home.visit(); page.home.header.openLoginModal(); page.components.loginModal.loginToAccountButton.click(); - page.components.loginModal.fillForm(data.customer); + page.components.loginModal.fillForm(data.customer as Customer); page.components.loginModal.loginBtn.click(); - page.components.loginModal.container.contains(data.errorMessage).should('be.visible'); + page.components.loginModal.container.contains(data.errorMessage as string).should('be.visible'); }); }); diff --git a/packages/theme/tests/e2e/integration/e2e-user-registration.spec.ts b/packages/theme/tests/e2e/integration/e2e-user-registration.spec.ts index 12977cd9b..3f90b4f22 100755 --- a/packages/theme/tests/e2e/integration/e2e-user-registration.spec.ts +++ b/packages/theme/tests/e2e/integration/e2e-user-registration.spec.ts @@ -1,6 +1,7 @@ import generator from '../utils/data-generator'; import page from '../pages/factory'; import requests from '../api/requests'; +import { Customer } from '../types/customer'; before(() => { cy.fixture('test-data/e2e-user-registration.json').then((fixture) => { @@ -16,7 +17,7 @@ context(['regression'], 'User registration', () => { data.customer.email = generator.email; page.home.visit(); page.home.header.openLoginModal(); - page.components.loginModal.fillForm(data.customer); + page.components.loginModal.fillForm(data.customer as Customer); page.components.loginModal.iWantToCreateAccountCheckbox.click(); page.components.loginModal.submitButton.click(); page.components.loginModal.container.should('not.exist'); @@ -28,13 +29,13 @@ context(['regression'], 'User registration', () => { const data = cy.fixtures.data['Existing user - should display an error']; data.customer.email = generator.email; - requests.createCustomer(data.customer).then(() => { + requests.createCustomer(data.customer as Customer).then(() => { cy.clearCookies(); }); page.home.visit(); page.home.header.openLoginModal(); - page.components.loginModal.fillForm(data.customer); + page.components.loginModal.fillForm(data.customer as Customer); page.components.loginModal.iWantToCreateAccountCheckbox.click(); page.components.loginModal.submitButton.click(); page.components.loginModal.container.contains(`${data.errorMessage} '"${data.customer.email}"'`).should('be.visible'); From 18ab9d116f6c3a0467be81431caff172e4707a78 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 15:21:15 +0200 Subject: [PATCH 05/17] refactor: safely spread nullish coalescing --- packages/theme/modules/wishlist/getters/wishlistGetters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/modules/wishlist/getters/wishlistGetters.ts b/packages/theme/modules/wishlist/getters/wishlistGetters.ts index 9c4b5b047..6bdd7d791 100644 --- a/packages/theme/modules/wishlist/getters/wishlistGetters.ts +++ b/packages/theme/modules/wishlist/getters/wishlistGetters.ts @@ -94,7 +94,7 @@ const getProducts = (wishlistData: Wishlist[] | Wishlist): { quantity: item.quantity, added_at: item.added_at, id: item.id, - }))]; + })) ?? []]; const mapper = (item) => ({ product: item.product, From ed340b4de0d73044d632612bfa9e14224d02e6ad Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 15:22:45 +0200 Subject: [PATCH 06/17] chore: fix import order --- .../Header/Navigation/__tests__/HeaderNavigation.spec.js | 2 +- .../Navigation/__tests__/HeaderNavigationItem.spec.js | 2 +- .../__tests__/HeaderNavigationSubcategories.spec.js | 2 +- .../composables/useContent/commands/loadContentCommand.ts | 1 - .../components/__tests__/CategoryEmptyResults.spec.ts | 2 +- .../catalog/category/composables/useFacet/index.ts | 8 ++++---- .../category/config/__tests__/filtersConfig.spec.ts | 4 ++-- .../product/composables/useUpsellProducts/index.ts | 3 +-- 8 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/theme/components/Header/Navigation/__tests__/HeaderNavigation.spec.js b/packages/theme/components/Header/Navigation/__tests__/HeaderNavigation.spec.js index 512083531..593c68723 100644 --- a/packages/theme/components/Header/Navigation/__tests__/HeaderNavigation.spec.js +++ b/packages/theme/components/Header/Navigation/__tests__/HeaderNavigation.spec.js @@ -1,10 +1,10 @@ import userEvent from '@testing-library/user-event'; -import HeaderNavigation from '../HeaderNavigation.vue'; import { render } from '~/test-utils'; import { useUiHelpers } from '~/composables'; import useUiHelpersMock from '~/test-utils/mocks/useUiHelpersMock'; import CategoryTreeDataMock from '~/test-utils/mocks/categoryTreeDataMock'; +import HeaderNavigation from '../HeaderNavigation.vue'; jest.mock('~/composables'); useUiHelpers.mockReturnValue(useUiHelpersMock()); diff --git a/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationItem.spec.js b/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationItem.spec.js index 3e21c9e0b..bc86592a6 100644 --- a/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationItem.spec.js +++ b/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationItem.spec.js @@ -1,5 +1,5 @@ -import HeaderNavigationItem from '../HeaderNavigationItem.vue'; import { render } from '~/test-utils'; +import HeaderNavigationItem from '../HeaderNavigationItem.vue'; describe('HeaderNavigationItem', () => { it('display proper label and link', () => { diff --git a/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationSubcategories.spec.js b/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationSubcategories.spec.js index 6d78ea1ec..8038238db 100644 --- a/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationSubcategories.spec.js +++ b/packages/theme/components/Header/Navigation/__tests__/HeaderNavigationSubcategories.spec.js @@ -1,9 +1,9 @@ import userEvent from '@testing-library/user-event'; -import HeaderNavigationSubcategories from '../HeaderNavigationSubcategories.vue'; import { render } from '~/test-utils'; import CategoryTreeDataMock from '~/test-utils/mocks/categoryTreeDataMock'; import { useUiHelpers } from '~/composables'; import useUiHelpersMock from '~/test-utils/mocks/useUiHelpersMock'; +import HeaderNavigationSubcategories from '../HeaderNavigationSubcategories.vue'; jest.mock('~/composables'); useUiHelpers.mockReturnValue(useUiHelpersMock()); diff --git a/packages/theme/composables/useContent/commands/loadContentCommand.ts b/packages/theme/composables/useContent/commands/loadContentCommand.ts index a3a29363e..789a8ae40 100644 --- a/packages/theme/composables/useContent/commands/loadContentCommand.ts +++ b/packages/theme/composables/useContent/commands/loadContentCommand.ts @@ -1,4 +1,3 @@ -import { Context } from '@nuxt/types'; import { Logger } from '~/helpers/logger'; import { VsfContext } from '~/composables/context'; diff --git a/packages/theme/modules/catalog/category/components/__tests__/CategoryEmptyResults.spec.ts b/packages/theme/modules/catalog/category/components/__tests__/CategoryEmptyResults.spec.ts index 34bf75d88..ebad116dc 100644 --- a/packages/theme/modules/catalog/category/components/__tests__/CategoryEmptyResults.spec.ts +++ b/packages/theme/modules/catalog/category/components/__tests__/CategoryEmptyResults.spec.ts @@ -1,5 +1,5 @@ -import CategoryEmptyResults from '../CategoryEmptyResults.vue'; import { render } from '~/test-utils'; +import CategoryEmptyResults from '../CategoryEmptyResults.vue'; describe('CategoryEmptyResults.vue', () => { it('Renders with a default value', () => { diff --git a/packages/theme/modules/catalog/category/composables/useFacet/index.ts b/packages/theme/modules/catalog/category/composables/useFacet/index.ts index 1a1130096..87eff824e 100644 --- a/packages/theme/modules/catalog/category/composables/useFacet/index.ts +++ b/packages/theme/modules/catalog/category/composables/useFacet/index.ts @@ -2,16 +2,16 @@ import { readonly, ref } from '@nuxtjs/composition-api'; import { Logger } from '~/helpers/logger'; import type { ComposableFunctionArgs } from '~/composables/types'; import type { GetProductSearchParams } from '~/modules/catalog/product/types'; -import type { - UseFacetInterface, UseFacetErrors, UseFacetSearchResult, FacetSearchParams, -} from './useFacet'; import useApi from '~/composables/useApi'; -import GetFacetDataQuery from './getFacetData.gql'; import { SortingOptions } from '~/modules/catalog/category/composables/useFacet/SortingOptions'; import { PerPageOptions } from '~/modules/catalog/category/composables/useFacet/PerPageOptions'; import { createProductAttributeFilterInput } from '~/modules/catalog/category/composables/useFacet/input/createProductAttributeFilterInput'; import { createProductAttributeSortInput } from '~/modules/catalog/category/composables/useFacet/input/createProductAttributeSortInput'; import { Products } from '~/modules/GraphQL/types'; +import GetFacetDataQuery from './getFacetData.gql'; +import type { + UseFacetInterface, UseFacetErrors, UseFacetSearchResult, FacetSearchParams, +} from './useFacet'; /** * The `useFacet()` composable allows searching for products using facets. diff --git a/packages/theme/modules/catalog/category/config/__tests__/filtersConfig.spec.ts b/packages/theme/modules/catalog/category/config/__tests__/filtersConfig.spec.ts index 26be7f050..bad7c4947 100644 --- a/packages/theme/modules/catalog/category/config/__tests__/filtersConfig.spec.ts +++ b/packages/theme/modules/catalog/category/config/__tests__/filtersConfig.spec.ts @@ -1,8 +1,8 @@ +import config, { FilterTypeEnum } from '~/modules/catalog/category/config/config'; +import RendererTypesEnum from '~/modules/catalog/category/components/filters/renderer/RendererTypesEnum'; import { getFilterConfig, getDisabledFilters, } from '../FiltersConfig'; -import config, { FilterTypeEnum } from '~/modules/catalog/category/config/config'; -import RendererTypesEnum from '~/modules/catalog/category/components/filters/renderer/RendererTypesEnum'; jest.mock('~/modules/catalog/category/config/config'); diff --git a/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts b/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts index da62d26b1..598090f70 100644 --- a/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts +++ b/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts @@ -1,13 +1,12 @@ import type { Ref } from '@nuxtjs/composition-api'; import { ref, readonly, useContext } from '@nuxtjs/composition-api'; +import { Logger } from '~/helpers/logger'; import type { UseUpsellProductsError, UseUpsellProductsInterface, UseUpsellProductsSearchParams, } from './useUpsellProducts'; -import { Logger } from '~/helpers/logger'; - /** * The `useUpsellProducts()` composable allows loading upsell products. * From d5b8944b41018f244ac2b05aa16fccc3b4ee94e7 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 15:31:47 +0200 Subject: [PATCH 07/17] refactor: handle nullish coalescing case --- packages/theme/getters/reviewGetters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/getters/reviewGetters.ts b/packages/theme/getters/reviewGetters.ts index 5937a8df7..0f9ced3d8 100644 --- a/packages/theme/getters/reviewGetters.ts +++ b/packages/theme/getters/reviewGetters.ts @@ -24,7 +24,7 @@ export const getReviewDate = (item: ProductReview): string => item.created_at; export const getTotalReviews = (review: ProductInterface): number => review?.review_count || 0; -export const getAverageRating = (review: ProductInterface): number => (review?.reviews?.items?.reduce((acc, curr) => Number.parseInt(`${acc}`, 10) + getReviewRating(curr), 0)) / (review?.review_count || 1) || 0; +export const getAverageRating = (review: ProductInterface): number => ((review?.reviews?.items?.reduce((acc, curr) => Number.parseInt(`${acc}`, 10) + getReviewRating(curr), 0)) ?? 0) / (review?.review_count || 1) || 0; export const getRatesCount = (_review: ProductReviews): AgnosticRateCount[] => []; From 5d940a2edab6231e37ed8c596e2d9564eceb7b31 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 15:33:37 +0200 Subject: [PATCH 08/17] chore: do not use module --- packages/theme/enums/cookieNameEnum.js | 2 +- packages/theme/enums/stockStatusEnum.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/theme/enums/cookieNameEnum.js b/packages/theme/enums/cookieNameEnum.js index 18ead2de4..e7274cde8 100644 --- a/packages/theme/enums/cookieNameEnum.js +++ b/packages/theme/enums/cookieNameEnum.js @@ -1,4 +1,4 @@ -module.exports = { +export default { currencyCookieName: 'vsf-currency', countryCookieName: 'vsf-country', localeCookieName: 'vsf-locale', diff --git a/packages/theme/enums/stockStatusEnum.js b/packages/theme/enums/stockStatusEnum.js index 2a42386a7..406a42755 100644 --- a/packages/theme/enums/stockStatusEnum.js +++ b/packages/theme/enums/stockStatusEnum.js @@ -1,4 +1,4 @@ -module.exports = { +export default { inStock: 'IN_STOCK', outOfStock: 'OUT_OF_STOCK', }; From ee14c7f7013f0485da6aa42d2da9b6d9a2c42a28 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 15:39:29 +0200 Subject: [PATCH 09/17] chore: remove useless async --- packages/theme/helpers/checkout/__tests__/steps.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/helpers/checkout/__tests__/steps.spec.js b/packages/theme/helpers/checkout/__tests__/steps.spec.js index 8171ae8da..30fc6e824 100644 --- a/packages/theme/helpers/checkout/__tests__/steps.spec.js +++ b/packages/theme/helpers/checkout/__tests__/steps.spec.js @@ -5,7 +5,7 @@ const CHECKOUT_DATA = { }; describe('steps :: steps helper for the checkout', () => { - beforeEach(async () => { + beforeEach(() => { localStorage.clear(); localStorage.setItem('vsf-checkout', JSON.stringify(CHECKOUT_DATA)); From f7baec2bce33d5849fd62fb40060db33036272a5 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 16:02:37 +0200 Subject: [PATCH 10/17] chore: explicitly add bodyParser dependencies --- packages/theme/package.json | 2 ++ packages/theme/serverMiddleware/body-parser.js | 8 +++++--- yarn.lock | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/theme/package.json b/packages/theme/package.json index 81f56ba93..71d08ac98 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -41,8 +41,10 @@ "@vue-storefront/nuxt": "~2.5.6", "@vue-storefront/redis-cache": "^1.0.1", "axios": "^0.26.1", + "body-parser": "1.19.2", "cookie-universal-nuxt": "^2.1.5", "deepdash": "^5.3.9", + "express": "4.17.3", "graphql-request": "^4.0.0", "is-https": "^4.0.0", "isomorphic-dompurify": "^0.18.0", diff --git a/packages/theme/serverMiddleware/body-parser.js b/packages/theme/serverMiddleware/body-parser.js index c9b6c46ea..b81616d2e 100644 --- a/packages/theme/serverMiddleware/body-parser.js +++ b/packages/theme/serverMiddleware/body-parser.js @@ -1,5 +1,7 @@ -const bodyParser = require('body-parser'); -const app = require('express')(); +import bodyParser from 'body-parser'; +import express from 'express'; + +const app = express(); app.use(bodyParser.json()); -module.exports = app; +export default app; diff --git a/yarn.lock b/yarn.lock index 76f087ef6..daad72c3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9290,7 +9290,7 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" -express@^4.17.1: +express@4.17.3, express@^4.17.1: version "4.17.3" resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== From d54e8c4d849d2540579f8cc0d9926dcf96402dbf Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 16:05:11 +0200 Subject: [PATCH 11/17] refactor: add integration properties types --- packages/theme/helpers/integrationPlugin/context.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/theme/helpers/integrationPlugin/context.ts b/packages/theme/helpers/integrationPlugin/context.ts index eaaa34a98..f9860feaa 100644 --- a/packages/theme/helpers/integrationPlugin/context.ts +++ b/packages/theme/helpers/integrationPlugin/context.ts @@ -1,8 +1,9 @@ /* eslint-disable no-param-reassign */ +type IntegrationProperties = Record; /** * It extends given integartion, defined by `tag` in the context. */ -export const createExtendIntegrationInCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties) => { +export const createExtendIntegrationInCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties: IntegrationProperties) => { const integrationKey = `$${tag}`; if (!nuxtCtx.$vsf || !nuxtCtx.$vsf[integrationKey]) { @@ -19,7 +20,7 @@ export const createExtendIntegrationInCtx = ({ tag, nuxtCtx, inject }) => (integ /** * It creates a function that adds an integration to the context under the given name, defined by `tag`. */ -export const createAddIntegrationToCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties) => { +export const createAddIntegrationToCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties: IntegrationProperties) => { const integrationKey = `$${tag}`; if (nuxtCtx.$vsf && !nuxtCtx.$vsf[integrationKey]) { From 93933c47608874bbf77eb4bfc1ff68a26376e8b3 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 16:06:17 +0200 Subject: [PATCH 12/17] chore: remove unused cacheControl.js --- packages/theme/helpers/cacheControl.js | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 packages/theme/helpers/cacheControl.js diff --git a/packages/theme/helpers/cacheControl.js b/packages/theme/helpers/cacheControl.js deleted file mode 100644 index ecfe60d7b..000000000 --- a/packages/theme/helpers/cacheControl.js +++ /dev/null @@ -1,11 +0,0 @@ -const cacheControl = (values) => ({ res }) => { - if (!process.server) return; - - const cacheControlValue = Object.entries(values) - .map(([key, value]) => `${key}=${value}`) - .join(','); - - res.setHeader('Cache-Control', cacheControlValue); -}; - -export default cacheControl; From 1e15326362ea307fd5523aba61124f5505a054d4 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 16:10:57 +0200 Subject: [PATCH 13/17] refactor: integrationPlugin types fix --- packages/theme/helpers/integrationPlugin/context.ts | 5 ++--- packages/theme/helpers/integrationPlugin/index.ts | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/theme/helpers/integrationPlugin/context.ts b/packages/theme/helpers/integrationPlugin/context.ts index f9860feaa..c94e9171a 100644 --- a/packages/theme/helpers/integrationPlugin/context.ts +++ b/packages/theme/helpers/integrationPlugin/context.ts @@ -1,9 +1,8 @@ /* eslint-disable no-param-reassign */ -type IntegrationProperties = Record; /** * It extends given integartion, defined by `tag` in the context. */ -export const createExtendIntegrationInCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties: IntegrationProperties) => { +export const createExtendIntegrationInCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties: Record) => { const integrationKey = `$${tag}`; if (!nuxtCtx.$vsf || !nuxtCtx.$vsf[integrationKey]) { @@ -20,7 +19,7 @@ export const createExtendIntegrationInCtx = ({ tag, nuxtCtx, inject }) => (integ /** * It creates a function that adds an integration to the context under the given name, defined by `tag`. */ -export const createAddIntegrationToCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties: IntegrationProperties) => { +export const createAddIntegrationToCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties: Record) => { const integrationKey = `$${tag}`; if (nuxtCtx.$vsf && !nuxtCtx.$vsf[integrationKey]) { diff --git a/packages/theme/helpers/integrationPlugin/index.ts b/packages/theme/helpers/integrationPlugin/index.ts index 735292d9f..fa66a6d14 100644 --- a/packages/theme/helpers/integrationPlugin/index.ts +++ b/packages/theme/helpers/integrationPlugin/index.ts @@ -2,7 +2,7 @@ import { Context as NuxtContext } from '@nuxt/types'; import { Inject } from '@nuxt/types/app'; import axios from 'axios'; import { createExtendIntegrationInCtx, createAddIntegrationToCtx } from './context'; -import { getIntegrationConfig, createProxiedApi } from './_proxyUtils'; +import { getIntegrationConfig, createProxiedApi, ApiClientMethod } from './_proxyUtils'; interface IntegrationContext { integration: { @@ -30,7 +30,7 @@ const setCookieValues = (cookieValues: Record, cookieString = '' }; export const integrationPlugin = (pluginFn: NuxtPluginWithIntegration) => (nuxtCtx: NuxtContext, inject: Inject) => { - const configure = (tag: string, configuration) => { + const configure = (tag: string, configuration: { api: Record }) => { const injectInContext = createAddIntegrationToCtx({ tag, nuxtCtx, inject }); const config = getIntegrationConfig(nuxtCtx, configuration); const { middlewareUrl, ssrMiddlewareUrl } = (nuxtCtx as any).$config; @@ -51,7 +51,7 @@ export const integrationPlugin = (pluginFn: NuxtPluginWithIntegration) => (nuxtC injectInContext({ api, client, config }); }; - const extend = (tag, integrationProperties) => { + const extend = (tag, integrationProperties: Record) => { createExtendIntegrationInCtx({ tag, nuxtCtx, inject })(integrationProperties); }; From 1e7748b5977a6c3f992bcbf669f5a4fabd0d9203 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 16:17:26 +0200 Subject: [PATCH 14/17] chore: fix unsafe arguments --- ...ocalStorage.spec.js => asyncLocalStorage.spec.ts} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename packages/theme/helpers/__tests__/{asyncLocalStorage.spec.js => asyncLocalStorage.spec.ts} (81%) diff --git a/packages/theme/helpers/__tests__/asyncLocalStorage.spec.js b/packages/theme/helpers/__tests__/asyncLocalStorage.spec.ts similarity index 81% rename from packages/theme/helpers/__tests__/asyncLocalStorage.spec.js rename to packages/theme/helpers/__tests__/asyncLocalStorage.spec.ts index 8fcc97144..5d6d9c3ca 100644 --- a/packages/theme/helpers/__tests__/asyncLocalStorage.spec.js +++ b/packages/theme/helpers/__tests__/asyncLocalStorage.spec.ts @@ -15,7 +15,7 @@ describe('asyncLocalStorage :: Promised Based Localstorage Management', () => { expect(localStorage.setItem).toHaveBeenLastCalledWith(VSF_KEY, VSF_VALUE); expect(localStorage.__STORE__[VSF_KEY]).toBe(VSF_VALUE); - expect(Object.keys(localStorage.__STORE__)).toHaveLength(1); + expect(Object.keys(localStorage.__STORE__ as Object)).toHaveLength(1); }); test('getItem :: should get value from localStorage by key', async () => { @@ -44,30 +44,30 @@ describe('asyncLocalStorage :: Promised Based Localstorage Management', () => { await setItem(KEY, VALUE); expect(localStorage.__STORE__[VSF_KEY]).toBe(VSF_VALUE); - expect(Object.keys(localStorage.__STORE__)).toHaveLength(1); + expect(Object.keys(localStorage.__STORE__ as Object)).toHaveLength(1); await mergeItem(KEY, MERGE_VALUE); expect(localStorage.__STORE__[VSF_KEY]).toBe(VSF_MERGE_VALUE); - expect(Object.keys(localStorage.__STORE__)).toHaveLength(1); + expect(Object.keys(localStorage.__STORE__ as Object)).toHaveLength(1); }); test('remove :: should remove a key and value from localStorage', async () => { const KEY = 'jest'; const VSF_KEY = `vsf-${KEY}`; - expect(Object.keys(localStorage.__STORE__)).toHaveLength(1); + expect(Object.keys(localStorage.__STORE__ as Object)).toHaveLength(1); await removeItem(KEY); expect(localStorage.removeItem).toHaveBeenCalledWith(VSF_KEY); - expect(Object.keys(localStorage.__STORE__)).toHaveLength(0); + expect(Object.keys(localStorage.__STORE__ as Object)).toHaveLength(0); }); test('clear :: should clear all values and keys from localStorage', async () => { await clear(); expect(localStorage.clear).toHaveBeenCalledWith(); - expect(Object.keys(localStorage.__STORE__)).toHaveLength(0); + expect(Object.keys(localStorage.__STORE__ as Object)).toHaveLength(0); }); }); From b52998272ed35a3a0ba318872a559096e19a4e29 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 16:23:35 +0200 Subject: [PATCH 15/17] chore: disable prefer-module for pm2 --- packages/theme/ecosystem.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/theme/ecosystem.config.js b/packages/theme/ecosystem.config.js index e8900a614..33f4b4e75 100644 --- a/packages/theme/ecosystem.config.js +++ b/packages/theme/ecosystem.config.js @@ -1,3 +1,5 @@ +// the PM2 ecosystem.config.js needs to use module.exports +// eslint-disable-next-line unicorn/prefer-module module.exports = { apps: [ { From 171156d4a2405e69424f274e33a285f154b979c0 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Mon, 16 May 2022 16:23:57 +0200 Subject: [PATCH 16/17] refactor: add specific type for htmlDecoder --- packages/composables/src/helpers/htmlDecoder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/composables/src/helpers/htmlDecoder.ts b/packages/composables/src/helpers/htmlDecoder.ts index 2e23e5247..7f6ee5622 100644 --- a/packages/composables/src/helpers/htmlDecoder.ts +++ b/packages/composables/src/helpers/htmlDecoder.ts @@ -1,7 +1,7 @@ /** * @deprecated since version 1.0.0 */ -export function htmlDecode(input) { +export function htmlDecode(input: string) { const formatName = () => { try { const domParser = new DOMParser(); From b27b0578c450e4cb4febb3f55be576bc0b2ea692 Mon Sep 17 00:00:00 2001 From: Artur Tagisow Date: Tue, 17 May 2022 07:39:02 +0200 Subject: [PATCH 17/17] refactor: improve asyncLocalStorage typing --- packages/theme/helpers/asyncLocalStorage.ts | 46 ++++++++++++++------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/theme/helpers/asyncLocalStorage.ts b/packages/theme/helpers/asyncLocalStorage.ts index 87faac386..f25760755 100644 --- a/packages/theme/helpers/asyncLocalStorage.ts +++ b/packages/theme/helpers/asyncLocalStorage.ts @@ -1,14 +1,22 @@ -const getVsfKey = (key) => `vsf-${key}`; +const getVsfKey = (key: string) => `vsf-${key}`; -const mergeLocalStorageItem = (key: string, value: string) => { +type Callback = (error: Error | null, value?: TValue) => void; + +const mergeLocalStorageItem = (key: string, value: Record) : void => { const oldValue = window.localStorage.getItem(key); const oldObject = JSON.parse(oldValue); const newObject = value; - const nextValue = JSON.stringify({ ...JSON.parse(JSON.stringify(oldObject)), ...JSON.parse(JSON.stringify(newObject)) }); + const nextValue = JSON.stringify({ + ...JSON.parse(JSON.stringify(oldObject)), + ...JSON.parse(JSON.stringify(newObject)), + }); window.localStorage.setItem(key, nextValue); }; -const createPromise = (getValue, callback): Promise => new Promise((resolve, reject) => { +const createPromise = ( + getValue: () => TValue, + callback: Callback, +) : Promise => new Promise((resolve, reject) => { try { const value = getValue(); if (callback) { @@ -17,26 +25,36 @@ const createPromise = (getValue, callback): Promise => new Promise((resolve resolve(value); } catch (err) { if (callback) { - callback(err); + callback(err as Error); } reject(err); } }); -// eslint-disable-next-line max-len -export const getItem = (key: string, callback?: Function): Promise => createPromise(() => JSON.parse(window.localStorage.getItem(getVsfKey(key))), callback); +export const getItem = ( + key: string, + callback?: Callback, +): Promise => createPromise(() => JSON.parse(window.localStorage.getItem(getVsfKey(key))), callback); -// eslint-disable-next-line max-len -export const setItem = (key: string, value: string, callback?: Function): Promise => createPromise(() => (window.localStorage.setItem(getVsfKey(key), JSON.stringify(value))), callback); +export const setItem = ( + key: string, + value: T, + callback?: Callback, +): Promise => createPromise(() => (window.localStorage.setItem(getVsfKey(key), JSON.stringify(value))), callback); -// eslint-disable-next-line max-len -export const removeItem = (key: string, callback?: Function): Promise => createPromise(() => { +export const removeItem = ( + key: string, + callback?: Callback, +): Promise => createPromise(() => { window.localStorage.removeItem(getVsfKey(key)); }, callback); -// eslint-disable-next-line max-len -export const mergeItem = (key: string, value: string, callback?: Function): Promise => createPromise(() => mergeLocalStorageItem(getVsfKey(key), value), callback); +export const mergeItem = ( + key: string, + value: Record, + callback?: Callback, +): Promise => createPromise(() => mergeLocalStorageItem(getVsfKey(key), value), callback); -export const clear = (callback?: Function): Promise => createPromise(() => { +export const clear = (callback?: Callback): Promise => createPromise(() => { window.localStorage.clear(); }, callback);