Ui-based-integration-testing can not get expected value from ElementID

32 views Asked by At

First and foremost. I am a total beginner. I have no prior knowledge or consensus so I have no I what I am doing, but I know that this is for an assignment. I have been stuck on this one part for hours at this point. For a little context. I am using JSDOM UI integrated testing for my assignment. For this example, we are using a roman numeral converter. More specifically an old roman numeral to modern roman numeral.

This is my romanNumerals.html file

<!DOCTYPE html>
<html lang="en">
    <head>
        <link rel="stylesheet" href="romanNumerals.css">
        <script src="romanNumerals.js" defer></script>
    </head>
    <body>
        <div class="results">
            <div class="result">
                <div id="old-roman-result"></div>
                "Old" Roman Numeral
            </div>
            <div class="result">
                <div id="modern-roman-result"></div>
                "Modern" Roman Numeral
            </div>
        </div>
        <form id="arabic-number-form">
            <label>
                Arabic number (1-3999)
                <input type="number" min="1" max="3999" id="arabic-number" name="arabic-number" />
            </label>
            <button>Convert to "modern" Roman</button>
        </form>
    </body>
</html>

This is my romanNumerals.js file

/**
 * This function initializes the JavaScript functionality after the HTML content is loaded.
 */
window.addEventListener('DOMContentLoaded', () => {
    const arabicNumberInput = document.getElementById("arabic-number");
    const form = document.getElementById("arabic-number-form");
    const oldRomanResult = document.getElementById("old-roman-result");
    const modernRomanResult = document.getElementById("modern-roman-result");

    /*
     * Clear Arabic number input field when the page first loads.
     */
    arabicNumberInput.value = "";

    /*
     * Add a listener to the Arabic number input field to listen for typing and
     * update the "old" Roman numeral result "live" as the user types.  If the
     * value input by the user can't be converted to an "old" Roman numeral,
     * don't display any value.
     */
    arabicNumberInput.addEventListener("input", function () {
        modernRomanResult.textContent = "";
        const arabicNumber = parseInt(arabicNumberInput.value);
        try {
            oldRomanResult.textContent = convertToOldRoman(arabicNumber);
        } catch (err) {
            oldRomanResult.textContent = "";
        }
    });

    /*
     * When the user actually submits the form, send a request to the API described
     * here to convert the number to a "modern" Roman numeral:
     *
     * https://helloacm.com/tools/romans/
     *
     * If an error occurs in the translation, an error message is displayed.
     */
    form.addEventListener("submit", async function (event) {
        event.preventDefault();
        clearErrorMessages();
        const arabicNumber = arabicNumberInput.value;
        try {
            const response = await fetch(
                `https://romans.justyy.workers.dev/api/romans/?n=${arabicNumber}`
            );
            const data = await response.json();
            if (data.error) {
                displayError(data.error);
            } else {
                modernRomanResult.textContent = data.result;
            }
        } catch (err) {
            displayError(err.message);
        }
    });

    /*
     * This function displays an error message to the user if the translation
     * request failed for any reason.
     */
    function displayError(errorMsg) {
        const errorDiv = document.createElement("div");
        errorDiv.classList.add("error");
        errorDiv.setAttribute("role", "alert");
        errorDiv.innerHTML = "<h3>❌ Error</h3>";

        /*
         * The error message may have come from the outside world, so we have to
         * treat it cautiously, making sure it's not executed as HTML code.
         */
        const errorMsgP = document.createElement("p");
        errorMsgP.textContent = errorMsg;
        errorDiv.appendChild(errorMsgP);

        form.appendChild(errorDiv);
    }

    /*
     * This function removes any error message currently being displayed to the
     * user.
     */
    function clearErrorMessages() {
        const errors = form.querySelectorAll(".error");
        errors.forEach(function (error) {
            error.remove();
        });
    }

    /*
     * This table represents the conversions between Roman digits and Arabic values.
     */
    const conversionTable = {
        "M": 1000,
        "C": 100,
        "L": 50,
        "X": 10,
        "V": 5,
        "I": 1
    };

    /**
     * This function converts an Arabic number into the corresponding "old" Roman
     * numeral.  Old Roman numerals are based only on addition.  For example, the
     * Arabic number 9 is "VIIII" in old Roman numerals (it's "IX" in modern Roman
     * numerals).
     *
     * @param {number} input A numeric value representing the Arabic number to
     *   convert to old Roman numerals.  Must be in the range 1-3999.  Any decimal
     *   part of the number is ignored.
     *
     * @throws {RangeError} Throws a RangeError if the input value is a number
     *   outside the range 1-3999.
     * @throws {Error} Throws an Error if the input is not a number.
     *
     * @returns {string} Returns a string containing the old Roman numeral
     *   conversion for the specified input value.
     */
    function convertToOldRoman(input) {
        if (input === undefined || typeof input !== "number") {
            throw Error("Expected a number parameter");
        }
        if (input < 1 || input > 3999) {
            throw RangeError("Input must be in range 1-3999");
        }

        /*
         * Cycle through Roman digits from greatest (M) to least (I).  For each
         * digit, subtract the corresponding Arabic value from the input number
         * as many times as possible, adding an instance of the current Roman
         * to the resultint translation for each subtraction.
         */
        const romanDigits = Object.keys(conversionTable);
        let currRomanIdx = 0;
        let result = "";
        while (input > 0 && currRomanIdx < romanDigits.length) {
            const currRomanDigit = romanDigits[currRomanIdx];
            const currArabicValue = conversionTable[currRomanDigit];
            while (input >= currArabicValue) {
                result += currRomanDigit;
                input -= currArabicValue;
            }
            currRomanIdx++;
        }
        return result;
    }

});

And this is my romanNumerals.test.js file

/**
 * @jest-environment jsdom
 */

// Polyfill TextEncoder
global.TextEncoder = require('util').TextEncoder;
global.TextDecoder = require('util').TextDecoder;

const fs = require("fs");
const { JSDOM } = require("jsdom");
const { fireEvent, waitFor } = require("@testing-library/dom");
const userEvent = require("@testing-library/user-event").default;
const axios = require("axios");
const MockAdapter = require("axios-mock-adapter");

// Load HTML content
const htmlContent = fs.readFileSync("./romanNumerals/romanNumerals.html", "utf8");
const { document } = new JSDOM(htmlContent).window;

// Mock the API response for conversion to "modern" Roman numerals
const mock = new MockAdapter(axios);
mock.onGet("https://romans.justyy.workers.dev/api/romans/?n=123").reply(200, { result: "CXXIII" });

function initDomFromFiles() {
    document.body.innerHTML = htmlContent;
    require("./romanNumerals.js");
}

test("should update 'modern' Roman numeral result on form submission", async () => {
    // Arrange
    initDomFromFiles();
    const arabicNumberInput = document.getElementById("arabic-number");
    const convertButton = document.querySelector("button");
    const modernRomanResult = document.getElementById("modern-roman-result");

    // Act:
    const user = userEvent.setup();
    await user.type(arabicNumberInput, "123");
    await user.click(convertButton);
    // Assert
    await waitFor(() => {
        expect(document.getElementById("modern-roman-result").textContent).toBe("CXXIII");
    });
});

I am at a complete and utter loss. I run npx jest. The test fails. because the actual value is nothing, but it should be CXXIII I tried using the API, I tried using an imported function from the js file. Nothing is working for me. I'd appreciate any help I can get.

1

There are 1 answers

2
Lajos Arpad On

You can solve this problem by separating the function that you want the submit to trigger from the actual submit. So it becomes a testable unit you can await for. The problem that you had is that you clicked on the button and expected the text to be changed, but it did not change - yet. A request is being sent to an API and once it arrived to the destination, which processed it, sent the response and your browser received it, the text is updated. But clicking on the button does not wait for all this.

/**
 * This function initializes the JavaScript functionality after the HTML content is loaded.
 */
window.addEventListener('DOMContentLoaded', () => {
    const arabicNumberInput = document.getElementById("arabic-number");
    const form = document.getElementById("arabic-number-form");
    const oldRomanResult = document.getElementById("old-roman-result");
    const modernRomanResult = document.getElementById("modern-roman-result");

    /*
     * Clear Arabic number input field when the page first loads.
     */
    arabicNumberInput.value = "";

    /*
     * Add a listener to the Arabic number input field to listen for typing and
     * update the "old" Roman numeral result "live" as the user types.  If the
     * value input by the user can't be converted to an "old" Roman numeral,
     * don't display any value.
     */
    arabicNumberInput.addEventListener("input", function () {
        modernRomanResult.textContent = "";
        const arabicNumber = parseInt(arabicNumberInput.value);
        try {
            oldRomanResult.textContent = convertToOldRoman(arabicNumber);
        } catch (err) {
            oldRomanResult.textContent = "";
        }
    });

    /*
     * When the user actually submits the form, send a request to the API described
     * here to convert the number to a "modern" Roman numeral:
     *
     * https://helloacm.com/tools/romans/
     *
     * If an error occurs in the translation, an error message is displayed.
     */
    async function mysubmit() {
        clearErrorMessages();
        const arabicNumber = arabicNumberInput.value;
        try {
            const response = await fetch(
                `https://romans.justyy.workers.dev/api/romans/?n=${arabicNumber}`
            );
            const data = await response.json();
            if (data.error) {
                displayError(data.error);
            } else {
                modernRomanResult.textContent = data.result;
                console.log(modernRomanResult.textContent);
            }
        } catch (err) {
            displayError(err.message);
        }
    }
    form.addEventListener("submit", async function (event) {
        event.preventDefault();
        mysubmit();
    });

    /*
     * This function displays an error message to the user if the translation
     * request failed for any reason.
     */
    function displayError(errorMsg) {
        const errorDiv = document.createElement("div");
        errorDiv.classList.add("error");
        errorDiv.setAttribute("role", "alert");
        errorDiv.innerHTML = "<h3>❌ Error</h3>";

        /*
         * The error message may have come from the outside world, so we have to
         * treat it cautiously, making sure it's not executed as HTML code.
         */
        const errorMsgP = document.createElement("p");
        errorMsgP.textContent = errorMsg;
        errorDiv.appendChild(errorMsgP);

        form.appendChild(errorDiv);
    }

    /*
     * This function removes any error message currently being displayed to the
     * user.
     */
    function clearErrorMessages() {
        const errors = form.querySelectorAll(".error");
        errors.forEach(function (error) {
            error.remove();
        });
    }

    /*
     * This table represents the conversions between Roman digits and Arabic values.
     */
    const conversionTable = {
        "M": 1000,
        "C": 100,
        "L": 50,
        "X": 10,
        "V": 5,
        "I": 1
    };

    /**
     * This function converts an Arabic number into the corresponding "old" Roman
     * numeral.  Old Roman numerals are based only on addition.  For example, the
     * Arabic number 9 is "VIIII" in old Roman numerals (it's "IX" in modern Roman
     * numerals).
     *
     * @param {number} input A numeric value representing the Arabic number to
     *   convert to old Roman numerals.  Must be in the range 1-3999.  Any decimal
     *   part of the number is ignored.
     *
     * @throws {RangeError} Throws a RangeError if the input value is a number
     *   outside the range 1-3999.
     * @throws {Error} Throws an Error if the input is not a number.
     *
     * @returns {string} Returns a string containing the old Roman numeral
     *   conversion for the specified input value.
     */
    function convertToOldRoman(input) {
        if (input === undefined || typeof input !== "number") {
            throw Error("Expected a number parameter");
        }
        if (input < 1 || input > 3999) {
            throw RangeError("Input must be in range 1-3999");
        }

        /*
         * Cycle through Roman digits from greatest (M) to least (I).  For each
         * digit, subtract the corresponding Arabic value from the input number
         * as many times as possible, adding an instance of the current Roman
         * to the resultint translation for each subtraction.
         */
        const romanDigits = Object.keys(conversionTable);
        let currRomanIdx = 0;
        let result = "";
        while (input > 0 && currRomanIdx < romanDigits.length) {
            const currRomanDigit = romanDigits[currRomanIdx];
            const currArabicValue = conversionTable[currRomanDigit];
            while (input >= currArabicValue) {
                result += currRomanDigit;
                input -= currArabicValue;
            }
            currRomanIdx++;
        }
        return result;
    }

    async function foo() {
        const arabicNumberInput = document.getElementById("arabic-number");
        const convertButton = document.querySelector("button");
        const modernRomanResult = document.getElementById("modern-roman-result");

        // Act:
        arabicNumberInput.value = "123";
        //convertButton.click();
        await mysubmit();
        // Assert
            console.log(modernRomanResult.textContent);//.toBe("CXXIII");
    };
    foo();

});
<!DOCTYPE html>
<html lang="en">
    <head>
        <link rel="stylesheet" href="romanNumerals.css">
        <script src="romanNumerals.js" defer></script>
    </head>
    <body>
        <div class="results">
            <div class="result">
                <div id="old-roman-result"></div>
                "Old" Roman Numeral
            </div>
            <div class="result">
                <div id="modern-roman-result"></div>
                "Modern" Roman Numeral
            </div>
        </div>
        <form id="arabic-number-form">
            <label>
                Arabic number (1-3999)
                <input type="number" min="1" max="3999" id="arabic-number" name="arabic-number" />
            </label>
            <button>Convert to "modern" Roman</button>
        </form>
    </body>
</html>