Skip to content

Initial setup for JavaScript

Vladimir Turov edited this page Sep 21, 2020 · 10 revisions

Languages

At the moment, we have a separate repository hs-test-web to test JavaScript web applications. Also, with the additional server located on a separate repository hs-test-web-server, you can test web applications based on React library. Visit this page to learn more about testing React applications.

JavaScript

To build a project, we use node. Use node version 12 and above to be sure you won't run into trouble, though versions 11 and below haven't been tested and may work properly. A typical project with several stages with JavaScript tests looks like this:

Webstorm_project_folder
    .idea
        --Idea internal files--
    node_modules
        --dependencies--
    package.json
    Project_name
    |   stage1
    |   |   src
    |   |   |   --user files--
    |   |   test
    |   |       --test files--
    |   stage2
    |   |   src
    |   |   |   --user files--
    |   |   test
    |   |       --test files--
    |   ...
    |   stageN
    |   |   src
    |   |   |   --user files--
    |   |   test
    |   |       --test files--

As you can see, the project is divided into stages. Each stage does not overlap with other stages in any way. The src package is intended for the user to write his code to the project. The test folder is intended for writing tests to the project stage.

So, the initial setup for writing tests for a web project is as follows (let's say you're writing tests for the Tic-Tac-Toe project):

Idea_project
    package.json
    Tic-Tac-Toe
    |   stage1
    |   |   src
    |   |   |   index.html
    |   |   test
    |   |       test.js

The file index.html can contain some initial code for the student. He'll see it the first time he opens the project. Usually, this file looks like this for a student when opening a project for the first time:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello, World!</title>
</head>
<body>

</body>
</html>

But you will need to implement the project stage in this file. Since you will need to check whether your tests work or not.

Below is an initial example of the test.js file:

const puppeteer = require('puppeteer');
const path = require('path');
// '..' since we're in the test/ subdirectory; learner is supposed to have src/index.html
const pagePath = 'file://' + path.resolve(__dirname, '../src/index.html');

const hs = require('hs-test-web');

const sleep = (ms) => new Promise(res => setTimeout(res, ms));

async function stageTest() {
    const browser = await puppeteer.launch({
        headless: false,
        defaultViewport: null,
        args:['--start-maximized', '--disable-infobar'],
        ignoreDefaultArgs: ['--enable-automation']
    });

    const page = await browser.newPage();
    await page.goto(pagePath);

    page.on('console', msg => console.log(msg.text()));

    await sleep(1000);

    let result = await hs.testPage(page,
        // Test #1
        () => {
            // test #1
        },

        // Test #2
        () => {
            // test #2
        },

        // Test #3
        () => {
            // test #3
        },

        // ...
    );

    await browser.close();
    return result;
}


jest.setTimeout(30000);
test("Test stage", async () => {
        let result = await stageTest();
        if (result['type'] === 'wrong') {
            fail(result['message']);
        }
    }
);

As you can see, there is a lot of additional code on top of writing tests. This is a subject for simplification in the future. You need to write tests in separate lambdas inside hs.testPage method.

The test code is executed inside the browser context, not in the node context, so you cannot use any node functions here despite the fact that IDE shows that you can. Local variables are not shared between tests, but global variables are shared. Every test should return either hs.correct() or hs.wrong(message) and message will be shown to the user after the end of the test. Here's an example of 3 tests of the first stage in the Virtual Piano project. Notice the global variable this.innerBodyElements that is visible across all the tests.

// Test #1
() => {
    let bodyNodes = Array.from(document.body.childNodes);
    this.innerBodyElements = bodyNodes.filter(
        e => e.nodeType === Node.ELEMENT_NODE);

    let len = this.innerBodyElements.length;

    return len === 7 ?
        hs.correct() :
        hs.wrong(`There should be 7 elements in the body of the HTML document, found: ${len}`)
},

// Test #2
() => {
    let failNum = 0;
    let failElem = '';
    let i = 0;
    for (let elem of this.innerBodyElements) {
        i++;
        elem = elem.nodeName.toLowerCase();
        if (elem !== 'kbd') {
            failNum = i;
            failElem = elem;
            break;
        }
    }

    return failNum === 0 ?
        hs.correct() :
        hs.wrong(`Element #${failNum} is not <kbd> element, it's <${failElem}>`);
},

// Test #3
() => {
    let failNum = 0;
    let textInside = '';
    let i = 0;
    for (let elem of this.innerBodyElements) {
        i++;
        elem = elem.innerHTML;
        if (elem.length !== 1) {
            failNum = i;
            textInside = elem;
            break;
        }
    }

    if (failNum === 0) {
        return hs.correct();
    }

    if (textInside.length === 0) {
        return hs.wrong(`Element #${failNum} is empty, ` +
            `but should contain a single letter.`);
    }

    return hs.wrong(`Element #${failNum} contains ${textInside.length} symbols, ` +
        `but should contain a single letter. The text inside element is:\n"${textInside}"`);
},

Below is package.json file that you can use that contains minimal dependencies needed for any project.

{
  "devDependencies": {
    "jest": "^23.6.0",
    "@types/jest": "^23.3.12",
    "hs-test-web": "https://github.com/hyperskill/hs-test-web/archive/v1.tar.gz",
    "puppeteer": "^2.0.0"
  },
  "scripts": {
    "test": "jest"
  }
}

Creating project in EduTools

You are not limited to using the EduTools plugin when creating tests for a project (you can just create a regular Web Storm project), but with the EduTools plugin, it may be more convenient.

To create a project with the EduTools plugin, you need to download install it from the market. Open Settings -> Plugins -> Marketplace -> EduTools and install the plugin.

To create a project, you need to click File -> Learn and Teach -> Create New Hyperskill Course

Set the title and click Create.

This is how your project should look like for now. Please, don't touch .yaml files - they are internal for the EduTools plugin.

Next, you need to delete lesson1 and package-lock.json. You should end up with a single file package.json. Replace the contents of this file with what was given above.

Then, you should create a so-called Framework Lesson and give it a name like a project name. For this, you need to create a new Lesson and choose Framework lesson in the dialog.

Then, you should create a new Task inside this Framework Lesson and give it the name stage1. The task type should be Edu.

Then remove task.js and hstest/test.java, create folders test, src create files src/index.html, and test/test.js and replace the contents of these files with what was provided above. Notice, that you need to name folder and file appropriately your project theme, don't stick to Tic-Tac-Toe. This is just an example. You should end up with the following configuration:

To create tests for the second stage, click on Framework Lesson named Tic-Tac-Toe and add a new Task. Name it stage2. Your result should look like the following:

You created an initial setup and ready to write tests!