diff --git a/force-app/main/default/lwc/orderBuilder/__tests__/data/getOrderItems.json b/force-app/main/default/lwc/orderBuilder/__tests__/data/getOrderItems.json new file mode 100644 index 00000000..fcb54485 --- /dev/null +++ b/force-app/main/default/lwc/orderBuilder/__tests__/data/getOrderItems.json @@ -0,0 +1,50 @@ +[ + { + "Id": "a003B000004fG1VQAU", + "Qty_S__c": 1, + "Qty_M__c": 1, + "Qty_L__c": 1, + "Price__c": 4200, + "Product__c": "a033B00000381hNQAQ", + "Product__r": { + "Name": "DYNAMO X1", + "MSRP__c": 7000, + "Picture_URL__c": "https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/dynamox1.jpg", + "Id": "a033B00000381hNQAQ", + "$serId$": 343 + }, + "$serId$": 342 + }, + { + "Id": "a003B000004fG1aQAE", + "Qty_S__c": 1, + "Qty_M__c": 1, + "Qty_L__c": 1, + "Price__c": 4320, + "Product__c": "a033B00000381hOQAQ", + "Product__r": { + "Name": "DYNAMO X2", + "MSRP__c": 7200, + "Picture_URL__c": "https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/dynamox2.jpg", + "Id": "a033B00000381hOQAQ", + "$serId$": 345 + }, + "$serId$": 344 + }, + { + "Id": "a003B000004fG1fQAE", + "Qty_S__c": 1, + "Qty_M__c": 1, + "Qty_L__c": 1, + "Price__c": 4440, + "Product__c": "a033B00000381hPQAQ", + "Product__r": { + "Name": "DYNAMO X3", + "MSRP__c": 7400, + "Picture_URL__c": "https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/dynamox3.jpg", + "Id": "a033B00000381hPQAQ", + "$serId$": 347 + }, + "$serId$": 346 + } +] diff --git a/force-app/main/default/lwc/orderBuilder/__tests__/data/getOrderItemsEmpty.json b/force-app/main/default/lwc/orderBuilder/__tests__/data/getOrderItemsEmpty.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/force-app/main/default/lwc/orderBuilder/__tests__/data/getOrderItemsEmpty.json @@ -0,0 +1 @@ +[] diff --git a/force-app/main/default/lwc/orderBuilder/__tests__/orderBuilder.test.js b/force-app/main/default/lwc/orderBuilder/__tests__/orderBuilder.test.js new file mode 100644 index 00000000..d53a6499 --- /dev/null +++ b/force-app/main/default/lwc/orderBuilder/__tests__/orderBuilder.test.js @@ -0,0 +1,237 @@ +import { createElement } from 'lwc'; +import OrderBuilder from 'c/orderBuilder'; +import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; +import getOrderItems from '@salesforce/apex/OrderController.getOrderItems'; + +// Mock realistic data for the getOrderItems adapter +const mockGetOrderItems = require('./data/getOrderItems.json'); +const mockGetOrderItemsEmpty = require('./data/getOrderItemsEmpty.json'); + +// Mock realistic data for the public properties +const mockRecordId = '0031700000pHcf8AAC'; + +//Expected Wire Input +const WIRE_INPUT = { + orderId: '0031700000pHcf8AAC' +}; + +// Register as Apex wire adapter. Some tests verify that provisioned values trigger desired behavior. +const getOrderItemsAdapter = registerApexTestWireAdapter(getOrderItems); + +describe('c-order-builder', () => { + afterEach(() => { + // The jsdom instance is shared across test cases in a single file so reset the DOM + while (document.body.firstChild) { + document.body.removeChild(document.body.firstChild); + } + }); + + it('displays the correct number of tiles and their details', () => { + // Set values for validating component changes + const expectedItems = 9; + const expectedSum = 38880; + + const element = createElement('c-order-builder', { + is: OrderBuilder + }); + element.recordId = mockRecordId; + document.body.appendChild(element); + + // Emit Data from the Apex wire adapter.s + getOrderItemsAdapter.emit(mockGetOrderItems); + + // Return a promise to wait for any asynchronous DOM updates. Jest + // will automatically wait for the Promise chain to complete before + // ending the test and fail the test if the promise rejects. + return Promise.resolve().then(() => { + // Check the wire parameters are correct + expect(getOrderItemsAdapter.getLastConfig()).toEqual(WIRE_INPUT); + // Select elements for validation + const orderItemTileEl = element.shadowRoot.querySelectorAll( + 'c-order-item-tile' + ); + expect(orderItemTileEl.length).toBe(mockGetOrderItems.length); + // Get the order items to verify they have been set correctly + const { orderItem } = orderItemTileEl[0]; + expect(orderItem).toEqual( + expect.objectContaining(mockGetOrderItems[0]) + ); + // Get the formatted number to verify it has been calculated + const formattedNumberEl = element.shadowRoot.querySelector( + 'lightning-formatted-number' + ); + expect(formattedNumberEl.value).toBe(expectedSum); + // Get the order total to verify it has been calculated + const orderTotalDivEl = element.shadowRoot.querySelector( + 'div.right' + ); + expect(orderTotalDivEl.textContent).toBe( + `Total Items: ${expectedItems}` + ); + }); + }); + + it('updates the component when an order has been updated', () => { + // Set values for validating component changes + const mockRecordUpdate = { Id: 'a003B000004fG1VQAU', Qty_S__c: 3 }; + const expectedItems = 11; + const expectedSum = 47280; + + const element = createElement('c-order-builder', { + is: OrderBuilder + }); + element.recordId = mockRecordId; + document.body.appendChild(element); + + // Emit Data from the Apex wire adapter.s + getOrderItemsAdapter.emit(mockGetOrderItems); + + // Return a promise to wait for any asynchronous DOM updates. Jest + // will automatically wait for the Promise chain to complete before + // ending the test and fail the test if the promise rejects. + return Promise.resolve() + .then(() => { + // Check the wire parameters are correct + expect(getOrderItemsAdapter.getLastConfig()).toEqual( + WIRE_INPUT + ); + // Select elements for validation + const orderItemTileEl = element.shadowRoot.querySelectorAll( + 'c-order-item-tile' + ); + orderItemTileEl[0].dispatchEvent( + new CustomEvent('orderitemchange', { + detail: mockRecordUpdate, + bubbles: true + }) + ); + }) + .then(() => { + const orderItemTileEl = element.shadowRoot.querySelectorAll( + 'c-order-item-tile' + ); + // Get the first order item and check that the quantity has ben updated + const orderItem = orderItemTileEl[0].orderItem.Qty_S__c; + expect(orderItem).toEqual(mockRecordUpdate.Qty_S__c); + // Get the formatted number to verify it has been updated + const formattedNumberEl = element.shadowRoot.querySelector( + 'lightning-formatted-number' + ); + expect(formattedNumberEl.value).toBe(expectedSum); + // Get the order total to verify it has been updated + const orderTotalDivEl = element.shadowRoot.querySelector( + 'div.right' + ); + expect(orderTotalDivEl.textContent).toBe( + `Total Items: ${expectedItems}` + ); + }); + }); + it('updates the component when an order has been deleted', () => { + // Set values for validating component changes + const mockRecordToDeleteId = 'a003B000004fG1VQAU'; + const expectedItems = 6; + const expectedSum = 26280; + + const element = createElement('c-order-builder', { + is: OrderBuilder + }); + element.recordId = mockRecordId; + document.body.appendChild(element); + + // Emit Data from the Apex wire adapter. + getOrderItemsAdapter.emit(mockGetOrderItems); + + // Return a promise to wait for any asynchronous DOM updates. Jest + // will automatically wait for the Promise chain to complete before + // ending the test and fail the test if the promise rejects. + return Promise.resolve() + .then(() => { + // Check the wire parameters are correct + expect(getOrderItemsAdapter.getLastConfig()).toEqual( + WIRE_INPUT + ); + // Select elements for validation + const orderItemTileEl = element.shadowRoot.querySelectorAll( + 'c-order-item-tile' + ); + orderItemTileEl[0].dispatchEvent( + new CustomEvent('orderitemdelete', { + detail: { id: mockRecordToDeleteId } + }) + ); + }) + .then(() => { + const orderItemTileEl = element.shadowRoot.querySelectorAll( + 'c-order-item-tile' + ); + // Get the first order item and check that the quantity has ben updated + expect(orderItemTileEl.length).toBe( + mockGetOrderItems.length - 1 + ); + // Get the formatted number to verify it has been updated + const formattedNumberEl = element.shadowRoot.querySelector( + 'lightning-formatted-number' + ); + expect(formattedNumberEl.value).toBe(expectedSum); + // Get the order total to verify it has been updated + const orderTotalDivEl = element.shadowRoot.querySelector( + 'div.right' + ); + expect(orderTotalDivEl.textContent).toBe( + `Total Items: ${expectedItems}` + ); + }); + }); + + it('displays a panel when no data is returned', () => { + // Set values for validating component changes + const expectedMessage = 'Drag products here to add items to the order'; + + const element = createElement('c-order-builder', { + is: OrderBuilder + }); + element.recordId = mockRecordId; + document.body.appendChild(element); + + // Emit Data from the Apex wire adapter. + getOrderItemsAdapter.emit(mockGetOrderItemsEmpty); + + // Return a promise to wait for any asynchronous DOM updates. Jest + // will automatically wait for the Promise chain to complete before + // ending the test and fail the test if the promise rejects. + return Promise.resolve().then(() => { + // verify that the placeholder is showing the correct data + const placeholderEl = element.shadowRoot.querySelector( + 'c-placeholder' + ); + expect(placeholderEl.message).toBe(expectedMessage); + }); + }); + + it('displays a error when an error is returned', () => { + // Set values for validating component changes + const mockError = { message: 'mockError' }; + + const element = createElement('c-order-builder', { + is: OrderBuilder + }); + element.recordId = mockRecordId; + document.body.appendChild(element); + + // Emit Data from the Apex wire adapter. + getOrderItemsAdapter.error(mockError); + + // Return a promise to wait for any asynchronous DOM updates. Jest + // will automatically wait for the Promise chain to complete before + // ending the test and fail the test if the promise rejects. + return Promise.resolve().then(() => { + // Verify that the error panel is showing the correct data + const errorPanelEl = element.shadowRoot.querySelector( + 'c-error-panel' + ); + expect(errorPanelEl).not.toBeNull(); + expect(errorPanelEl.errors.body).toStrictEqual(mockError); + }); + }); +}); diff --git a/force-app/main/default/lwc/orderBuilder/orderBuilder.js b/force-app/main/default/lwc/orderBuilder/orderBuilder.js index f556fcf8..b4a59014 100644 --- a/force-app/main/default/lwc/orderBuilder/orderBuilder.js +++ b/force-app/main/default/lwc/orderBuilder/orderBuilder.js @@ -106,7 +106,6 @@ export default class OrderBuilder extends LightningElement { /** Handles drag-and-dropping a new product to create a new Order_Item__c. */ handleDrop(event) { event.preventDefault(); - // Product__c from LDS const product = JSON.parse(event.dataTransfer.getData('product')); diff --git a/force-app/test/jest-mocks/apex.js b/force-app/test/jest-mocks/apex.js new file mode 100644 index 00000000..ed4a6397 --- /dev/null +++ b/force-app/test/jest-mocks/apex.js @@ -0,0 +1,4 @@ +// Mocking how getSObjectValue retrieves the field value. +export const getSObjectValue = (object, field) => { + return object[field.fieldApiName]; +}; diff --git a/jest.config.js b/jest.config.js index 489e5a10..046afbde 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,7 @@ const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config'); module.exports = { ...jestConfig, moduleNameMapper: { + '^@salesforce/apex$': '/force-app/test/jest-mocks/apex', '^lightning/navigation$': '/force-app/test/jest-mocks/lightning/navigation', '^lightning/messageService$':