From b54d0be1d434ff92d19e3d0ee02bee68fbeba206 Mon Sep 17 00:00:00 2001 From: Edie Lemoine Date: Tue, 10 Oct 2023 14:10:46 +0200 Subject: [PATCH] test(checkout-core): add tests --- .../src/__tests__/getMockCheckoutContext.ts | 40 +++++++++ .../core/src/__tests__/getMockFormData.ts | 29 +++++++ .../src/__tests__/getMockPdkCheckoutConfig.ts | 46 ++++++++++ libs/checkout/core/src/__tests__/index.ts | 5 ++ .../__tests__/mockDeliveryOptionsElement.ts | 23 +++++ .../core/src/__tests__/mockPdkCheckout.ts | 21 +++++ libs/checkout/core/src/__tests__/spies.ts | 30 +++++++ libs/checkout/core/src/constants.ts | 1 + libs/checkout/core/src/index.ts | 3 + .../core/src/listeners/updateCheckoutForm.ts | 5 +- .../core/src/utils/getFrontendContext.ts | 5 +- .../core/src/utils/global/doRequest.spec.ts | 18 ++++ .../core/src/utils/global/fieldsEqual.spec.ts | 83 +++++++++++++++++++ .../core/src/utils/global/fieldsEqual.ts | 3 +- .../core/src/utils/global/getAddressField.ts | 3 +- .../core/src/utils/global/getElement.spec.ts | 28 +++++++ .../core/src/utils/global/getFieldValue.ts | 2 +- .../src/utils/global/triggerEvent.spec.ts | 29 +++++++ .../core/src/utils/global/triggerEvent.ts | 2 - .../core/src/utils/hasAddressType.spec.ts | 22 +++++ 20 files changed, 388 insertions(+), 10 deletions(-) create mode 100644 libs/checkout/core/src/__tests__/getMockCheckoutContext.ts create mode 100644 libs/checkout/core/src/__tests__/getMockFormData.ts create mode 100644 libs/checkout/core/src/__tests__/getMockPdkCheckoutConfig.ts create mode 100644 libs/checkout/core/src/__tests__/index.ts create mode 100644 libs/checkout/core/src/__tests__/mockDeliveryOptionsElement.ts create mode 100644 libs/checkout/core/src/__tests__/mockPdkCheckout.ts create mode 100644 libs/checkout/core/src/__tests__/spies.ts create mode 100644 libs/checkout/core/src/constants.ts create mode 100644 libs/checkout/core/src/utils/global/doRequest.spec.ts create mode 100644 libs/checkout/core/src/utils/global/fieldsEqual.spec.ts create mode 100644 libs/checkout/core/src/utils/global/getElement.spec.ts create mode 100644 libs/checkout/core/src/utils/global/triggerEvent.spec.ts create mode 100644 libs/checkout/core/src/utils/hasAddressType.spec.ts diff --git a/libs/checkout/core/src/__tests__/getMockCheckoutContext.ts b/libs/checkout/core/src/__tests__/getMockCheckoutContext.ts new file mode 100644 index 000000000..d1009e683 --- /dev/null +++ b/libs/checkout/core/src/__tests__/getMockCheckoutContext.ts @@ -0,0 +1,40 @@ +import {vi} from 'vitest'; +import {FrontendEndpoint} from '@myparcel-pdk/checkout-common'; +import {type CheckoutAppContext} from '../types'; + +export const getMockCheckoutContext = vi.fn( + (context?: Partial): CheckoutAppContext['checkout'] => { + return { + config: { + ...context?.config, + }, + settings: { + hasDeliveryOptions: true, + actions: { + baseUrl: '', + endpoints: { + [FrontendEndpoint.FetchCheckoutContext]: { + body: '', + headers: {}, + method: 'GET', + parameters: {}, + path: '', + property: 'context', + ...context?.settings?.actions?.endpoints?.[FrontendEndpoint.FetchCheckoutContext], + }, + }, + ...context?.settings?.actions, + }, + allowedShippingMethods: [], + carriersWithTaxFields: [], + countriesWithSeparateAddressFields: [], + hiddenInputName: '', + ...context?.settings, + }, + strings: { + ...context?.strings, + }, + ...context, + }; + }, +); diff --git a/libs/checkout/core/src/__tests__/getMockFormData.ts b/libs/checkout/core/src/__tests__/getMockFormData.ts new file mode 100644 index 000000000..ee7b69155 --- /dev/null +++ b/libs/checkout/core/src/__tests__/getMockFormData.ts @@ -0,0 +1,29 @@ +import {AddressField, AddressType, PdkField} from '@myparcel-pdk/checkout-common'; +import {type RecursivePartial} from '@myparcel/ts-utils'; +import {type PdkCheckoutConfigInput} from '../types'; + +export const getMockFormData = ( + formData?: RecursivePartial, +): PdkCheckoutConfigInput['formData'] => { + return { + [PdkField.AddressType]: 'address-type', + [PdkField.ShippingMethod]: 'shipping-method', + [AddressType.Billing]: { + [AddressField.Address1]: 'b-address1', + [AddressField.Address2]: 'b-address2', + [AddressField.City]: 'b-city', + [AddressField.Country]: 'b-country', + [AddressField.PostalCode]: 'b-postal-code', + ...formData?.[AddressType.Billing], + }, + [AddressType.Shipping]: { + [AddressField.Address1]: 's-address1', + [AddressField.Address2]: 's-address2', + [AddressField.City]: 's-city', + [AddressField.Country]: 's-country', + [AddressField.PostalCode]: 's-postal-code', + ...formData?.[AddressType.Shipping], + }, + ...formData, + } as PdkCheckoutConfigInput['formData']; +}; diff --git a/libs/checkout/core/src/__tests__/getMockPdkCheckoutConfig.ts b/libs/checkout/core/src/__tests__/getMockPdkCheckoutConfig.ts new file mode 100644 index 000000000..e21262755 --- /dev/null +++ b/libs/checkout/core/src/__tests__/getMockPdkCheckoutConfig.ts @@ -0,0 +1,46 @@ +import {vi} from 'vitest'; +import {AddressField, AddressType, PdkField} from '@myparcel-pdk/checkout-common'; +import {type PdkCheckoutConfigInput} from '../types'; +import {doRequestSpy, getFormDataSpy, getFormSpy, hasAddressTypeSpy, initializeSpy, toggleFieldSpy} from './spies'; +import {getMockFormData} from './getMockFormData'; + +export const getMockPdkCheckoutConfig = vi.fn( + (config?: Partial): PdkCheckoutConfigInput => ({ + doRequest: doRequestSpy, + getForm: getFormSpy, + getFormData: getFormDataSpy, + hasAddressType: hasAddressTypeSpy, + initialize: initializeSpy, + toggleField: toggleFieldSpy, + + selectors: { + deliveryOptions: '#delivery-options', + deliveryOptionsWrapper: '#delivery-options-wrapper', + ...config?.selectors, + }, + + fields: { + [PdkField.AddressType]: 'address-type', + [PdkField.ShippingMethod]: 'shipping-method', + [AddressType.Billing]: { + [AddressField.Address1]: 'b-address1', + [AddressField.Address2]: 'b-address2', + [AddressField.City]: 'b-city', + [AddressField.Country]: 'b-country', + [AddressField.PostalCode]: 'b-postal-code', + ...config?.fields?.[AddressType.Billing], + }, + [AddressType.Shipping]: { + [AddressField.Address1]: 's-address1', + [AddressField.Address2]: 's-address2', + [AddressField.City]: 's-city', + [AddressField.Country]: 's-country', + [AddressField.PostalCode]: 's-postal-code', + ...config?.fields?.[AddressType.Shipping], + }, + ...config?.fields, + }, + + formData: getMockFormData(config?.formData), + }), +); diff --git a/libs/checkout/core/src/__tests__/index.ts b/libs/checkout/core/src/__tests__/index.ts new file mode 100644 index 000000000..3a55183b8 --- /dev/null +++ b/libs/checkout/core/src/__tests__/index.ts @@ -0,0 +1,5 @@ +export * from './getMockFormData'; +export * from './getMockPdkCheckoutConfig'; +export * from './mockDeliveryOptionsElement'; +export * from './mockPdkCheckout'; +export * from './spies'; diff --git a/libs/checkout/core/src/__tests__/mockDeliveryOptionsElement.ts b/libs/checkout/core/src/__tests__/mockDeliveryOptionsElement.ts new file mode 100644 index 000000000..d32f06254 --- /dev/null +++ b/libs/checkout/core/src/__tests__/mockDeliveryOptionsElement.ts @@ -0,0 +1,23 @@ +import {type CheckoutAppContext} from '../types'; +import {ATTRIBUTE_CONTEXT} from '../constants'; +import {getMockCheckoutContext} from './getMockCheckoutContext'; + +export const mockDeliveryOptionsElement = (): void => { + const form = document.createElement('form'); + + const wrapper = document.createElement('div'); + const element = document.createElement('div'); + + wrapper.id = 'delivery-options-wrapper'; + element.id = 'delivery-options'; + + wrapper.setAttribute( + ATTRIBUTE_CONTEXT, + JSON.stringify({checkout: getMockCheckoutContext()} satisfies CheckoutAppContext), + ); + + wrapper.appendChild(element); + form.appendChild(wrapper); + + document.body.appendChild(form); +}; diff --git a/libs/checkout/core/src/__tests__/mockPdkCheckout.ts b/libs/checkout/core/src/__tests__/mockPdkCheckout.ts new file mode 100644 index 000000000..4d84ade36 --- /dev/null +++ b/libs/checkout/core/src/__tests__/mockPdkCheckout.ts @@ -0,0 +1,21 @@ +import {usePdkCheckout} from '../usePdkCheckout'; +import {type PdkCheckoutConfigInput} from '../types'; +import {createPdkCheckout} from '../init'; +import {mockDeliveryOptionsElement} from './mockDeliveryOptionsElement'; +import {getMockPdkCheckoutConfig} from './getMockPdkCheckoutConfig'; + +export const mockPdkCheckout = (config?: Partial, includeElements = true): Promise => { + return new Promise((resolve, reject) => { + if (includeElements) { + mockDeliveryOptionsElement(); + } + + createPdkCheckout(getMockPdkCheckoutConfig(config)); + + usePdkCheckout().onInitialize(() => resolve()); + + setTimeout(() => { + reject(new Error('Timeout: mockPdkCheckout')); + }, 1000); + }); +}; diff --git a/libs/checkout/core/src/__tests__/spies.ts b/libs/checkout/core/src/__tests__/spies.ts new file mode 100644 index 000000000..99546cc1f --- /dev/null +++ b/libs/checkout/core/src/__tests__/spies.ts @@ -0,0 +1,30 @@ +import {vi} from 'vitest'; +import {AddressType} from '@myparcel-pdk/checkout-common'; + +export const doRequestSpy = vi.fn(); + +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +export const getFormSpy = vi.fn(() => document.querySelector('form')!); + +export const getFormDataSpy = vi.fn( + (): Record => ({ + 'address-type': AddressType.Billing, + 'b-address1': 'Straatnaam 12e', + 'b-address2': '', + 'b-city': 'Amsterdam', + 'b-country': 'NL', + 'b-postal-code': '1234AB', + 's-address1': 'Straatnaam 12e', + 's-address2': '', + 's-city': 'Amsterdam', + 's-country': 'NL', + 's-postal-code': '1234AB', + 'shipping-method': 'shipping-method', + }), +); + +export const hasAddressTypeSpy = vi.fn(() => true); + +export const initializeSpy = vi.fn(() => Promise.resolve()); + +export const toggleFieldSpy = vi.fn(); diff --git a/libs/checkout/core/src/constants.ts b/libs/checkout/core/src/constants.ts new file mode 100644 index 000000000..18f4c5c6e --- /dev/null +++ b/libs/checkout/core/src/constants.ts @@ -0,0 +1 @@ +export const ATTRIBUTE_CONTEXT = 'data-context'; diff --git a/libs/checkout/core/src/index.ts b/libs/checkout/core/src/index.ts index 5d2bb5572..fc9e531dd 100644 --- a/libs/checkout/core/src/index.ts +++ b/libs/checkout/core/src/index.ts @@ -1,6 +1,9 @@ import './globals'; +export * from './__tests__'; + export * from './config'; +export * from './constants'; export * from './data'; export * from './events'; export * from './init'; diff --git a/libs/checkout/core/src/listeners/updateCheckoutForm.ts b/libs/checkout/core/src/listeners/updateCheckoutForm.ts index 64829d71d..5a69e67a3 100644 --- a/libs/checkout/core/src/listeners/updateCheckoutForm.ts +++ b/libs/checkout/core/src/listeners/updateCheckoutForm.ts @@ -1,6 +1,7 @@ import {type AddressField, AddressType, type PdkField} from '@myparcel-pdk/checkout-common'; -import {useCheckoutStore} from '../utils'; -import {getAddressType, type PdkCheckoutForm, useConfig} from '../index'; +import {getAddressType, useCheckoutStore} from '../utils'; +import {type PdkCheckoutForm} from '../types'; +import {useConfig} from '../config'; function getEntry(data: Record, value: undefined | string): string { return (data[value as string] as string | undefined) ?? ''; diff --git a/libs/checkout/core/src/utils/getFrontendContext.ts b/libs/checkout/core/src/utils/getFrontendContext.ts index 6434cbc46..25393d6b2 100644 --- a/libs/checkout/core/src/utils/getFrontendContext.ts +++ b/libs/checkout/core/src/utils/getFrontendContext.ts @@ -1,13 +1,12 @@ import {type CheckoutAppContext} from '../types'; +import {ATTRIBUTE_CONTEXT} from '../constants'; import {useConfig} from '../config'; import {getElement} from './global'; -const ATTRIBUTE_CONTEXT = 'data-context'; - export const getFrontendContext = (): CheckoutAppContext['checkout'] => { const config = useConfig(); - const wrapper = getElement(config.selectors.deliveryOptionsWrapper); + const wrapper = getElement(config.selectors.deliveryOptionsWrapper, false); const context = wrapper?.getAttribute(ATTRIBUTE_CONTEXT); if (!wrapper || !context) { diff --git a/libs/checkout/core/src/utils/global/doRequest.spec.ts b/libs/checkout/core/src/utils/global/doRequest.spec.ts new file mode 100644 index 000000000..a37a4e3e5 --- /dev/null +++ b/libs/checkout/core/src/utils/global/doRequest.spec.ts @@ -0,0 +1,18 @@ +import {beforeEach, describe, expect, it} from 'vitest'; +import {FrontendEndpoint} from '@myparcel-pdk/checkout-common'; +import {doRequestSpy, mockPdkCheckout} from '../../__tests__'; +import {doRequest} from './doRequest'; + +describe('doRequest', () => { + beforeEach(async () => { + await mockPdkCheckout(); + }); + + it('should call doRequest', async () => { + expect.assertions(1); + + await doRequest(FrontendEndpoint.FetchCheckoutContext, {shippingMethod: 'shipping-method'}); + + expect(doRequestSpy).toHaveBeenCalled(); + }); +}); diff --git a/libs/checkout/core/src/utils/global/fieldsEqual.spec.ts b/libs/checkout/core/src/utils/global/fieldsEqual.spec.ts new file mode 100644 index 000000000..25279285f --- /dev/null +++ b/libs/checkout/core/src/utils/global/fieldsEqual.spec.ts @@ -0,0 +1,83 @@ +import {beforeEach, describe, expect, it} from 'vitest'; +import {AddressField, AddressType, PdkField} from '@myparcel-pdk/checkout-common'; +import {getMockFormData, mockPdkCheckout} from '../../__tests__'; +import {fieldsEqual} from './fieldsEqual'; + +describe('fieldsEqual', () => { + beforeEach(async () => { + await mockPdkCheckout(); + }); + + it('should return true if the fields are equal', () => { + const result = fieldsEqual( + getMockFormData({[AddressType.Billing]: {[AddressField.Address1]: 'appelboom'}}), + getMockFormData({[AddressType.Billing]: {[AddressField.Address1]: 'appelboom'}}), + AddressField.Address1, + ); + + expect(result).toBe(true); + }); + + it('should return false if the fields are not equal', () => { + const result = fieldsEqual( + getMockFormData({[AddressType.Billing]: {[AddressField.Address1]: 'appelboom'}}), + getMockFormData({[AddressType.Billing]: {[AddressField.Address1]: 'perenboom'}}), + AddressField.Address1, + ); + + expect(result).toBe(false); + }); + + it('should return true if the fields are equal for a specific address type', () => { + const result = fieldsEqual( + getMockFormData({ + [AddressType.Billing]: { + [AddressField.Address1]: 'ruurd', + }, + [AddressType.Shipping]: { + [AddressField.Address1]: 'appelboom', + }, + }), + getMockFormData({ + [AddressType.Billing]: { + [AddressField.Address1]: 'jan-piet', + }, + [AddressType.Shipping]: { + [AddressField.Address1]: 'appelboom', + }, + }), + AddressField.Address1, + AddressType.Shipping, + ); + + expect(result).toBe(true); + }); + + it('can check if pdk fields match', () => { + const result = fieldsEqual( + getMockFormData({ + [PdkField.ShippingMethod]: 'ruurd', + }), + getMockFormData({ + [PdkField.ShippingMethod]: 'ruurd', + }), + PdkField.ShippingMethod, + ); + + expect(result).toBe(true); + }); + + it('can check if pdk fields do not match', () => { + const result = fieldsEqual( + getMockFormData({ + [PdkField.ShippingMethod]: 'ruurd', + }), + getMockFormData({ + [PdkField.ShippingMethod]: 'barend', + }), + PdkField.ShippingMethod, + ); + + expect(result).toBe(false); + }); +}); diff --git a/libs/checkout/core/src/utils/global/fieldsEqual.ts b/libs/checkout/core/src/utils/global/fieldsEqual.ts index d689ac31f..4edf1f4f7 100644 --- a/libs/checkout/core/src/utils/global/fieldsEqual.ts +++ b/libs/checkout/core/src/utils/global/fieldsEqual.ts @@ -1,6 +1,7 @@ import {type AddressField, type AddressFields, type AddressType, PdkField} from '@myparcel-pdk/checkout-common'; import {isEnumValue} from '@myparcel/ts-utils'; -import {getAddressType, type PdkCheckoutForm} from '../../index'; +import {type PdkCheckoutForm} from '../../types'; +import {getAddressType} from './getAddressType'; type FieldsEqual = { ( diff --git a/libs/checkout/core/src/utils/global/getAddressField.ts b/libs/checkout/core/src/utils/global/getAddressField.ts index 41486847a..06961cf70 100644 --- a/libs/checkout/core/src/utils/global/getAddressField.ts +++ b/libs/checkout/core/src/utils/global/getAddressField.ts @@ -1,6 +1,7 @@ import {type AddressField, type AddressType} from '@myparcel-pdk/checkout-common'; +import {useUtil, Util} from '../useUtil'; import {useCheckoutStore} from '../useCheckoutStore'; -import {useConfig, useUtil, Util} from '../../index'; +import {useConfig} from '../../config'; /** * Get field by name. Will return element with selector: "#_". diff --git a/libs/checkout/core/src/utils/global/getElement.spec.ts b/libs/checkout/core/src/utils/global/getElement.spec.ts new file mode 100644 index 000000000..438a62d09 --- /dev/null +++ b/libs/checkout/core/src/utils/global/getElement.spec.ts @@ -0,0 +1,28 @@ +import {afterEach, describe, expect, it, vi} from 'vitest'; +import {getElement} from './getElement'; + +describe('getElement', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('should return null if the element is not found', () => { + expect(getElement('#foo', false)).toBeNull(); + }); + + it('should return the element if it is found', () => { + document.body.innerHTML = ` +
+ `; + + expect(getElement('#foo')).toEqual(document.querySelector('#foo')); + }); + + it('should warn if the element is not found', () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + getElement('#foo', true); + + expect(consoleWarnSpy).toHaveBeenCalledWith('Element not found: "#foo"'); + }); +}); diff --git a/libs/checkout/core/src/utils/global/getFieldValue.ts b/libs/checkout/core/src/utils/global/getFieldValue.ts index 4f774d0b7..394ebfa4b 100644 --- a/libs/checkout/core/src/utils/global/getFieldValue.ts +++ b/libs/checkout/core/src/utils/global/getFieldValue.ts @@ -1,7 +1,7 @@ import {type AddressField, type AddressType, PdkField} from '@myparcel-pdk/checkout-common'; import {isEnumValue} from '@myparcel/ts-utils'; import {useCheckoutStore} from '../useCheckoutStore'; -import {type PdkCheckoutForm} from '../../index'; +import {type PdkCheckoutForm} from '../../types'; type GetFieldValue = { (field: AddressField | string, addressType?: AddressType, fields?: PdkCheckoutForm): undefined | string; diff --git a/libs/checkout/core/src/utils/global/triggerEvent.spec.ts b/libs/checkout/core/src/utils/global/triggerEvent.spec.ts new file mode 100644 index 000000000..19e3c01f0 --- /dev/null +++ b/libs/checkout/core/src/utils/global/triggerEvent.spec.ts @@ -0,0 +1,29 @@ +import {afterEach, describe, expect, it, vi} from 'vitest'; +import {triggerEvent} from './triggerEvent'; + +describe('triggerEvent', () => { + const documentDispatchEventSpy = vi.spyOn(document, 'dispatchEvent'); + + afterEach(() => { + documentDispatchEventSpy.mockClear(); + }); + + it('dispatches an event on the document', () => { + triggerEvent('foo'); + expect(documentDispatchEventSpy).toHaveBeenCalled(); + }); + + it('dispatches an event on the document with a detail', () => { + triggerEvent('foo', {bar: 'baz'}); + expect(documentDispatchEventSpy).toHaveBeenCalledWith(expect.any(CustomEvent)); + }); + + it('dispatches an event on a given element', () => { + const element = document.createElement('div'); + + element.dispatchEvent = vi.fn(); + + triggerEvent('foo', null, element); + expect(element.dispatchEvent).toHaveBeenCalled(); + }); +}); diff --git a/libs/checkout/core/src/utils/global/triggerEvent.ts b/libs/checkout/core/src/utils/global/triggerEvent.ts index 284a19552..43411ad06 100644 --- a/libs/checkout/core/src/utils/global/triggerEvent.ts +++ b/libs/checkout/core/src/utils/global/triggerEvent.ts @@ -14,8 +14,6 @@ export const triggerEvent = ( eventSource = element; } - console.warn('triggerEvent', eventName, detail, eventSource); - if (detail) { eventSource?.dispatchEvent(new CustomEvent(eventName, {detail})); return; diff --git a/libs/checkout/core/src/utils/hasAddressType.spec.ts b/libs/checkout/core/src/utils/hasAddressType.spec.ts new file mode 100644 index 000000000..719f36b13 --- /dev/null +++ b/libs/checkout/core/src/utils/hasAddressType.spec.ts @@ -0,0 +1,22 @@ +import {beforeEach, describe, expect, it} from 'vitest'; +import {AddressType} from '@myparcel-pdk/checkout-common'; +import {hasAddressTypeSpy, mockPdkCheckout} from '../__tests__'; +import {hasAddressType} from './hasAddressType'; + +describe('hasAddressType', () => { + beforeEach(async () => { + await mockPdkCheckout(); + }); + + it('should return true if the address type is enabled', () => { + hasAddressTypeSpy.mockReturnValueOnce(true); + + expect(hasAddressType(AddressType.Shipping)).toBe(true); + }); + + it('should return false if the address type is disabled', () => { + hasAddressTypeSpy.mockReturnValueOnce(false); + + expect(hasAddressType(AddressType.Billing)).toBe(false); + }); +});