[ Salesforce, JS, Jest, Apex, HTML, CSS, Unit Tests ]


This is a project that was assigned to me when I tried to apply to a job. It’s a currency converter that can be used in both Salesforce Cloud and Experience Cloud web apps. It uses a public API to retrieve the rate, consists of LWC components, and an Apex backend. With this tool, users can easily convert currencies within Salesforce, saving them time and hassle.

The currency converter is made of several LWC components that are built with custom CSS styles. This not only gives the tool a professional look, but it also makes it easy for users to navigate and understand. The components are also designed to be responsive, meaning that they look great on any device. Behind the scenes, the currency converter is powered by an Apex backend and throughout the project I tried to keep everything consistent with SOLID principles. This ensures that all of the data is secure, testable and that the tool can handle large amounts of traffic. The backend retrieves currency rates and information from a public API, meaning that the data is always up-to-date and accurate. Here’s how the rates are calculated:

import Id from '@salesforce/user/Id';

const Utils = {
    gbpRates: [],
    calculateRates: function (baseCurrency) {
        if (!baseCurrency){
            console.error('calculateRates: base currency is empty');
            return;
        }
        let ratesForNewBaseCurrency = [];
        for (let i = 0; i < this.gbpRates.length; i++) {
            let newBaseRateInGbp = this.gbpRates.find(rate => rate.code === baseCurrency);
            let newRate = {};
            newRate.code = this.gbpRates[i].code;
            newRate.value = this.gbpRates[i].value / newBaseRateInGbp.value;
            newRate.order = LocalSettings.getCurrencyOrder(newRate.code);
            ratesForNewBaseCurrency.push(newRate);
        }

        // Sort by usage frequency
        ratesForNewBaseCurrency = ratesForNewBaseCurrency.sort((a, b) => (a.order > b.order) ? -1 : ((a.order < b.order) ? 1 : 0));
        
        return ratesForNewBaseCurrency;
    },
    ...
}
...

One of the key design principles that I followed when creating the currency converter is SOLID. This stands for Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. These principles help ensure that the code is easy to understand, maintain, and expand over time.

To ensure that everything was working as intended, I also wrote unit tests using Jest. These tests cover all of the key functionality of the currency converter, from retrieving currency rates to converting currencies. By writing these tests, I was able to catch and fix any issues early on, ensuring that the tool was ready for use.

This is how one of the test files look like:

import { createElement } from 'lwc';
import CurrencyConverter from 'c/CurrencyConverter';
const mockData = require('./mockData.json');
const flushPromises = () => new Promise(setImmediate);
import fetchMock from "jest-fetch-mock";


describe('c-currency-converter', () => {
    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('retrieve data test', async () => {
        const element = createElement('c-currency-converter', {
            is: CurrencyConverter
        });
        element.ratesPerPage = 2;

        fetchMock.enableMocks();
        fetch.mockResponseOnce(JSON.stringify({ rates: { CAD: 1.42 } }));

        document.body.appendChild(element);
        await flushPromises();

        const subheaderElement = element.shadowRoot.querySelector('[data-id=subheader]');
        const subheader = subheaderElement.textContent.split('on ')[1].replace(/\:\d+$/, '');
        const dateTimeNow = (new Date()).toLocaleString().replace(/\:\d+$/, '');

        // Check last update date time, as retrieveData is mocked, these should be the same
        expect(subheader).toEqual(dateTimeNow);
    });

    it('handlers test', async () => {
        const element = createElement('c-currency-converter', {
            is: CurrencyConverter
        });
        element.ratesPerPage = 2;

        fetchMock.enableMocks();
        fetch.mockResponseOnce(JSON.stringify({ rates: { CAD: 1.42, GBP: 1, USD: 2 } }));

        document.body.appendChild(element);
        await flushPromises();

        const currencyConverterCalc = element.shadowRoot.querySelector('c-currency-converter-calc');
        currencyConverterCalc.dispatchEvent(new CustomEvent("basechange", {detail: 'GBP'}));
        await flushPromises();

        currencyConverterCalc.dispatchEvent(new CustomEvent("quotechange", {detail: 'GBP'}));
        await flushPromises();

        const currencyConverterList = element.shadowRoot.querySelector('c-currency-converter-list');
        currencyConverterList.dispatchEvent(new CustomEvent("nextpage"));
        await flushPromises();

        currencyConverterList.dispatchEvent(new CustomEvent("previouspage"));
        await flushPromises();

    });
});

The first test case checks the “retrieve data” functionality, where the component fetches the latest currency exchange rates from an API and displays them on the UI. This test case mocks the API response and checks if the component is displaying the last update date and time correctly.

The second test case checks the event handlers of the component, where the user interacts with the UI to change the base and quote currencies and to navigate between pages of the currency rate list. This test case dispatches CustomEvents to simulate user interactions and checks if the component responds to these events correctly.

The test suite also contains an afterEach hook that resets the DOM after each test case.

Check out the complete code:
GitHub: https://github.com/movsar/kassignment

Leave a reply