Add more unit and integration tests (#773)

* Add unit tests for pyloader and pytitle

* Add more unit tests for pyrepl, pybox and pyinputbox

* Add more tests for pyscript and pyconfig

* White space

* Fix d3 tests and improve more examples test

* Update matplotlib test

* Add numpy to dependencies

* Address Madhur comments

* Update test name
This commit is contained in:
Fábio Rosado
2022-09-26 23:17:32 +01:00
committed by GitHub
parent d033ab04da
commit b674515d06
12 changed files with 482 additions and 53 deletions

View File

@@ -0,0 +1,25 @@
import { jest } from "@jest/globals"
import { PyBox } from "../../src/components/pybox"
customElements.define('py-box', PyBox)
describe('PyBox', () => {
let instance: PyBox;
beforeEach(() => {
instance = new PyBox();
})
it('PyBox instantiates correctly', async () => {
expect(instance).toBeInstanceOf(PyBox)
})
it("test connectedCallback creates pybox div", async () => {
expect(instance.innerHTML).toBe("")
instance.connectedCallback()
expect(instance.innerHTML).toBe('<div class=\"py-box\"></div>')
})
})

View File

@@ -14,7 +14,7 @@ describe('PyButton', () => {
expect(instance).toBeInstanceOf(PyButton);
});
it('confirm that runAfterRuntimeInitialized is called', async () => {
it('confirm that runAfterRuntimeInitialized is called', async () => {
const mockedRunAfterRuntimeInitialized = jest
.spyOn(instance, 'runAfterRuntimeInitialized')
.mockImplementation(jest.fn());

View File

@@ -1,13 +1,17 @@
import { jest } from '@jest/globals';
import type { AppConfig, RuntimeConfig } from '../../src/runtime';
import { PyConfig } from '../../src/components/pyconfig';
// inspired by trump typos
const covfefeConfig = {
"name": "covfefe",
"runtimes": [{
"src": "/demo/covfefe.js",
"name": "covfefe",
"lang": "covfefe"
}],
"wonerful": "discgrace"
name: 'covfefe',
runtimes: [
{
src: '/demo/covfefe.js',
name: 'covfefe',
lang: 'covfefe',
},
],
wonerful: 'discgrace',
};
const covfefeConfigToml = `
@@ -21,20 +25,18 @@ name = "covfefe"
lang = "covfefe"
`;
import {jest} from '@jest/globals';
customElements.define('py-config', PyConfig);
describe('PyConfig', () => {
let instance: PyConfig;
const xhrMockClass = () => ({
open : jest.fn(),
send : jest.fn(),
responseText : JSON.stringify(covfefeConfig)
open: jest.fn(),
send: jest.fn(),
responseText: JSON.stringify(covfefeConfig),
});
// @ts-ignore
window.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
window.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass);
beforeEach(() => {
instance = new PyConfig();
@@ -47,57 +49,57 @@ describe('PyConfig', () => {
it('should load runtime from config and set as script src', () => {
instance.values = covfefeConfig;
instance.loadRuntimes();
expect(document.scripts[0].src).toBe("http://localhost/demo/covfefe.js");
expect(document.scripts[0].src).toBe('http://localhost/demo/covfefe.js');
});
it('should load the default config', ()=> {
it('should load the default config', () => {
instance.connectedCallback();
expect(instance.values.name).toBe("pyscript");
expect(instance.values.author_email).toBe("foo@bar.com");
expect(instance.values.name).toBe('pyscript');
expect(instance.values.author_email).toBe('foo@bar.com');
expect(instance.values.pyscript?.time).not.toBeNull();
// @ts-ignore
expect(instance.values.runtimes[0].lang).toBe("python");
expect(instance.values.runtimes[0].lang).toBe('python');
});
it('should load the JSON config from inline', ()=> {
instance.setAttribute("type", "json");
it('should load the JSON config from inline', () => {
instance.setAttribute('type', 'json');
instance.innerHTML = JSON.stringify(covfefeConfig);
instance.connectedCallback();
// @ts-ignore
expect(instance.values.runtimes[0].lang).toBe("covfefe");
expect(instance.values.runtimes[0].lang).toBe('covfefe');
expect(instance.values.pyscript?.time).not.toBeNull();
// version wasn't present in `inline config` but is still set due to merging with default
expect(instance.values.version).toBe("0.1");
expect(instance.values.version).toBe('0.1');
});
it('should load the JSON config from src attribute', ()=> {
instance.setAttribute("type", "json");
instance.setAttribute("src", "/covfefe.json");
it('should load the JSON config from src attribute', () => {
instance.setAttribute('type', 'json');
instance.setAttribute('src', '/covfefe.json');
instance.connectedCallback();
// @ts-ignore
expect(instance.values.runtimes[0].lang).toBe("covfefe");
expect(instance.values.runtimes[0].lang).toBe('covfefe');
expect(instance.values.pyscript?.time).not.toBeNull();
// wonerful is an extra key supplied by the user and is unaffected by merging process
expect(instance.values.wonerful).toBe("discgrace");
expect(instance.values.wonerful).toBe('discgrace');
// version wasn't present in `config from src` but is still set due to merging with default
expect(instance.values.version).toBe("0.1");
expect(instance.values.version).toBe('0.1');
});
it('should load the JSON config from both inline and src', ()=> {
instance.setAttribute("type", "json");
instance.innerHTML = JSON.stringify({"version": "0.2a", "wonerful": "highjacked"});
instance.setAttribute("src", "/covfefe.json");
it('should load the JSON config from both inline and src', () => {
instance.setAttribute('type', 'json');
instance.innerHTML = JSON.stringify({ version: '0.2a', wonerful: 'highjacked' });
instance.setAttribute('src', '/covfefe.json');
instance.connectedCallback();
// @ts-ignore
expect(instance.values.runtimes[0].lang).toBe("covfefe");
expect(instance.values.runtimes[0].lang).toBe('covfefe');
expect(instance.values.pyscript?.time).not.toBeNull();
// config from src had an extra key "wonerful" with value "discgrace"
// inline config had the same extra key "wonerful" with value "highjacked"
// the merge process works for extra keys that clash as well
// so the final value is "highjacked" since inline takes precedence over src
expect(instance.values.wonerful).toBe("highjacked");
expect(instance.values.wonerful).toBe('highjacked');
// version wasn't present in `config from src` but is still set due to merging with default and inline
expect(instance.values.version).toBe("0.2a");
expect(instance.values.version).toBe('0.2a');
});
it('should be able to load an inline TOML config', () => {
@@ -105,11 +107,11 @@ describe('PyConfig', () => {
instance.innerHTML = covfefeConfigToml;
instance.connectedCallback();
// @ts-ignore
expect(instance.values.runtimes[0].lang).toBe("covfefe");
expect(instance.values.runtimes[0].lang).toBe('covfefe');
expect(instance.values.pyscript?.time).not.toBeNull();
// version wasn't present in `inline config` but is still set due to merging with default
expect(instance.values.version).toBe("0.1");
expect(instance.values.wonerful).toBe("highjacked");
expect(instance.values.version).toBe('0.1');
expect(instance.values.wonerful).toBe('highjacked');
});
it.failing('should NOT be able to load an inline config in JSON format with type as TOML', () => {
@@ -118,21 +120,56 @@ describe('PyConfig', () => {
});
it.failing('should NOT be able to load an inline config in TOML format with type as JSON', () => {
instance.setAttribute("type", "json");
instance.setAttribute('type', 'json');
instance.innerHTML = covfefeConfigToml;
instance.connectedCallback();
});
it.failing('should NOT be able to load an inline TOML config with a JSON config from src with type as toml', () => {
instance.innerHTML = covfefeConfigToml;
instance.setAttribute("src", "/covfefe.json");
instance.setAttribute('src', '/covfefe.json');
instance.connectedCallback();
});
it.failing('should NOT be able to load an inline TOML config with a JSON config from src with type as json', () => {
instance.setAttribute("type", "json");
instance.setAttribute('type', 'json');
instance.innerHTML = covfefeConfigToml;
instance.setAttribute("src", "/covfefe.json");
instance.setAttribute('src', '/covfefe.json');
instance.connectedCallback();
});
it('connectedCallback should call loadRuntimes', async () => {
const mockedMethod = jest.fn();
instance.loadRuntimes = mockedMethod;
instance.connectedCallback();
expect(mockedMethod).toHaveBeenCalled();
});
it('confirm connectedCallback happy path', async () => {
const mockedMethod = jest.fn();
instance.loadRuntimes = mockedMethod;
instance.innerHTML = 'test';
instance.connectedCallback();
expect(instance.code).toBe('test');
expect(instance.values['0']).toBe('test');
});
it('log should add new message to the page', async () => {
// details are undefined, so let's create a div for it
instance.details = document.createElement('div');
instance.log('this is a log');
// @ts-ignore: typescript complains about accessing innerText
expect(instance.details.childNodes[0].innerText).toBe('this is a log');
});
it('confirm that calling close would call this.remove', async () => {
instance.remove = jest.fn();
instance.close();
expect(instance.remove).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,66 @@
import { jest } from "@jest/globals"
import { PyInputBox } from "../../src/components/pyinputbox"
customElements.define('py-inputbox', PyInputBox)
describe("PyInputBox", () => {
let instance: PyInputBox;
beforeEach(() => {
instance = new PyInputBox()
instance.runAfterRuntimeInitialized = jest.fn();
})
it("PyInputBox instantiates correctly", async () => {
expect(instance).toBeInstanceOf(PyInputBox)
})
it('connectedCallback gets or sets a new id', async () => {
expect(instance.id).toBe('');
instance.connectedCallback();
const instanceId = instance.id;
// id should be similar to py-4850c8c3-d70d-d9e0-03c1-3cfeb0bcec0d-container
expect(instanceId).toMatch(/py-(\w+-){1,5}container/);
// calling checkId directly should return the same id
instance.checkId();
expect(instance.id).toEqual(instanceId);
});
it('confirm that runAfterRuntimeInitialized is called', async () => {
const mockedRunAfterRuntimeInitialized = jest
.spyOn(instance, 'runAfterRuntimeInitialized')
.mockImplementation(jest.fn());
instance.connectedCallback();
expect(mockedRunAfterRuntimeInitialized).toHaveBeenCalled();
})
it('onCallback sets mount_name based on id', async () => {
expect(instance.id).toBe('');
expect(instance.mount_name).toBe(undefined);
instance.connectedCallback();
const instanceId = instance.id;
expect(instanceId).toMatch(/py-(\w+-){1,5}container/);
expect(instance.mount_name).toBe(instanceId.replace('-container', '').split('-').join('_'));
});
it('onCallback updates on_keypress code and function name ', async () => {
expect(instance.code).toBe(undefined);
expect(instance.innerHTML).toBe('');
instance.innerHTML = '\ndef on_keypress(e):\n';
instance.connectedCallback();
expect(instance.code).toMatch(/def\son_keypress_py_(\w+)\(e\)/);
expect(instance.innerHTML).toContain('<input type="text" class="py-input"');
});
})

View File

@@ -0,0 +1,52 @@
import { jest } from '@jest/globals';
import { PyLoader } from "../../src/components/pyloader"
import { getLogger } from "../../src/logger"
customElements.define('py-loader', PyLoader);
describe('PyLoader', () => {
let instance: PyLoader;
const logger = getLogger("py-loader")
beforeEach(() => {
instance = new PyLoader();
logger.info = jest.fn()
})
it('PyLoader instantiates correctly', async () => {
expect (instance).toBeInstanceOf(PyLoader);
})
it('connectedCallback adds splash screen', async () => {
// innerHTML should be empty
expect(instance.innerHTML).toBe("")
instance.connectedCallback();
// This is just checking that we have some ids or class names
expect(instance.innerHTML).toContain('pyscript_loading_splash')
expect(instance.innerHTML).toContain("spinner")
expect(instance.mount_name).toBe("")
})
it('confirm calling log will log to console and page', () => {
const element = document.createElement('div')
element.setAttribute("id", "pyscript-operation-details")
instance.details = element
instance.log("Hello, world!")
const printedLog = element.getElementsByTagName('p')
expect(logger.info).toHaveBeenCalledWith("Hello, world!")
expect(printedLog[0].innerText).toBe("Hello, world!")
})
it('confirm that calling close removes element', async () => {
instance.remove = jest.fn()
instance.close()
expect(logger.info).toHaveBeenCalledWith("Closing")
expect(instance.remove).toHaveBeenCalled()
})
})

View File

@@ -1,4 +1,5 @@
import 'jest';
import { PyRepl } from '../../src/components/pyrepl';
customElements.define('py-repl', PyRepl);
@@ -13,4 +14,52 @@ describe('PyRepl', () => {
expect(instance).toBeInstanceOf(PyRepl);
});
it('confirm that codemirror editor is available', async () => {
// We are assuming that if editorNode has the 'editor-box' class
// then the div was created properly.
expect(instance.editorNode.getAttribute('class')).toBe("editor-box")
})
it("connectedCallback gets or sets new id", async () => {
expect(instance.id).toBe("")
instance.connectedCallback()
const instanceId = instance.id;
// id should be similar to py-4850c8c3-d70d-d9e0-03c1-3cfeb0bcec0d
expect(instanceId).toMatch(/py-(\w+-){1,4}\w+/);
// calling checkId directly should return the same id
instance.checkId();
expect(instance.id).toEqual(instanceId);
})
it('confirm that calling connectedCallback renders the expected elements', async () => {
expect(instance.innerHTML).toBe("")
instance.innerHTML = "<p>Hello</p>"
instance.connectedCallback()
expect(instance.code).toBe("<p>Hello</p>")
expect(instance.editorNode.id).toBe("code-editor")
// Just check that the button was created
expect(instance.btnRun.getAttribute("class")).toBe("absolute repl-play-button")
const editorNode = instance.editorNode.innerHTML
expect(editorNode).toContain("Python Script Run Button")
// Confirm that our innerHTML is set as well
expect(editorNode).toContain("Hello")
})
it("confirm that addToOutput updates output element", async () => {
expect(instance.outputElement).toBe(undefined)
// This is just to avoid throwing the test since outputElement is undefined
instance.outputElement = document.createElement("div")
instance.addToOutput("Hello, World!")
expect(instance.outputElement.innerHTML).toBe("<div>Hello, World!</div>")
expect(instance.outputElement.hidden).toBe(false)
})
});

View File

@@ -0,0 +1,76 @@
import { jest } from "@jest/globals"
import { PyScript } from "../../src/components/pyscript"
customElements.define('py-script', PyScript)
describe('PyScript', () => {
let instance: PyScript;
beforeEach(() => {
instance = new PyScript();
})
it('PyScript instantiates correctly', async () => {
expect(instance).toBeInstanceOf(PyScript)
})
it('connectedCallback gets or sets a new id', async () => {
expect(instance.id).toBe('');
instance.connectedCallback();
const instanceId = instance.id;
// id should be similar to py-4850c8c3-d70d-d9e0-03c1-3cfeb0bcec0d-container
expect(instanceId).toMatch(/py-(\w+-){1,5}/);
// calling checkId directly should return the same id
instance.checkId();
expect(instance.id).toEqual(instanceId);
});
it('connectedCallback creates output div', async () => {
instance.connectedCallback();
expect(instance.innerHTML).toContain('<div class="output">')
})
it('confirm that outputElement has std-out id element', async () => {
expect(instance.outputElement).toBe(undefined);
instance.setAttribute('id', 'std-out')
instance.connectedCallback();
expect(instance.outputElement.getAttribute('id')).toBe("std-out")
})
it('confirm that std-err id element sets errorElement', async () => {
expect(instance.outputElement).toBe(undefined);
instance.setAttribute('id', 'std-err')
instance.connectedCallback();
// We should have an errorElement
expect(instance.errorElement.getAttribute('id')).toBe("std-err")
})
it('test output attribute path', async () => {
expect(instance.outputElement).toBe(undefined);
expect(instance.errorElement).toBe(undefined)
const createdOutput = document.createElement("output")
instance.setAttribute('output', 'output')
instance.connectedCallback();
expect(instance.innerHTML).toBe('<div class="output"></div>')
})
it('getSourceFromElement returns decoded html', async () => {
instance.innerHTML = "<p>Hello</p>"
instance.connectedCallback();
const source = instance.getSourceFromElement();
expect(source).toBe("<p>Hello</p>")
})
})

View File

@@ -0,0 +1,40 @@
import { jest } from "@jest/globals";
import { PyTitle } from "../../src/components/pytitle"
customElements.define("py-title", PyTitle);
describe("PyTitle", () => {
let instance: PyTitle;
beforeEach(() => {
instance = new PyTitle();
})
it("PyTitle instantiates correctly", async () => {
expect(instance).toBeInstanceOf(PyTitle);
})
it("test connectedCallback defaults", async () => {
instance.connectedCallback();
expect(instance.label).toBe("")
expect(instance.mount_name).toBe("")
expect(instance.innerHTML).toBe(`<div class=\"py-title\" id=\"\"><h1></h1></div>`)
})
it("label renders correctly on the page and updates id", async () => {
instance.innerHTML = "Hello, world!"
// We need this to test mount_name works properly since connectedCallback
// doesn't automatically call checkId (should it?)
instance.checkId();
instance.connectedCallback();
expect(instance.label).toBe("Hello, world!")
// mount_name should be similar to: py_be025f4c_2150_7f2a_1a85_af92970c2a0e
expect(instance.mount_name).toMatch(/py_(\w+_){1,5}/);
expect(instance.innerHTML).toContain("<h1>Hello, world!</h1>")
})
})