diff --git a/priv/static/js/components-test.js b/priv/static/js/components-test.js new file mode 100644 index 0000000..5438c14 --- /dev/null +++ b/priv/static/js/components-test.js @@ -0,0 +1,41 @@ +import { test, assert } from "./test-utils.js"; +import "./components.js"; + +test("editable-area renders", async (container) => { + container.innerHTML = `Test 123`; + + const textarea = container.querySelector("textarea"); + const display = container.querySelector("p"); + + await assert(() => textarea.value === "Test 123"); + await assert(() => display.classList.contains("display")); + await assert(() => display.innerHTML === "Test 123
"); +}); + +test("editable-area updates on input", async (container) => { + container.innerHTML = ``; + + const textarea = container.querySelector("textarea"); + const display = container.querySelector("p"); + + let eventCalled = false; + container.addEventListener("contentChange", () => { + eventCalled = true; + }); + + textarea.value = "Some new content\nwith a newline"; + textarea.dispatchEvent( + new Event("input", { bubbles: true, cancelable: true }), + ); + + await assert(() => display.innerHTML === `Some new content
with a newline
`); + await assert(() => eventCalled === true); +}); + +test("editable-area respects readonly attribute", async (container) => { + container.innerHTML = `Some text`; + + const textarea = container.querySelector("textarea"); + + await assert(() => textarea.disabled); +}); diff --git a/priv/static/js/jenot-tests.js b/priv/static/js/jenot-tests.js index cdc6cb3..95bdf65 100644 --- a/priv/static/js/jenot-tests.js +++ b/priv/static/js/jenot-tests.js @@ -1,123 +1,11 @@ -import "./components.js"; +import { runTests } from "./test-utils.js"; + +// tests to run +import "./components-test.js"; const URL_PARAMS = new URLSearchParams(window.location.search); - const CONCRETE_TEST = URL_PARAMS.get("t"); -let counter = 1; - -const body = document.querySelector("body"); -const log = document.querySelector("#test-log"); - -class AssertError extends Error { - constructor(message, options) { - super(message, options); - } -} - -const setup = (idx) => { - const container = document.createElement("div", { id: `test-${idx}` }); - body.appendChild(container); - return container; -}; - -async function assert(assertion, timeout) { - timeout = timeout || 100; - const interval = Math.max(timeout / 10, 10); - const start = performance.now(); - let now = performance.now(); - - while (true) { - let result; - let error; - - try { - result = assertion(); - } catch (error) { - result = false; - error = error; - } - - if (result === true) break; - - if (now - start >= timeout) { - const assertionStr = assertion.toString(); - const opts = error ? { cause: error } : {}; - throw new AssertError(`Assertion failed: ${assertionStr}`, opts); - } - - await new Promise((r) => setTimeout(r, interval)); - now = performance.now(); - } -} - -function test(label, testFun) { - if (CONCRETE_TEST && CONCRETE_TEST != counter) { - counter++; - return; - } - const container = setup(counter); - const logRow = document.createElement("li"); - logRow.textContent = `[RUNNING] ${label}`; - log.appendChild(logRow); - - try { - testFun(container); - const message = `[OK] ${label}`; - console.info(message); - logRow.textContent = message; - logRow.classList.add("success"); - } catch (error) { - const message = `[ERROR] ${label}`; - console.error(message, error); - logRow.textContent = message; - logRow.classList.add("failure"); - const errorOutput = document.createElement("pre"); - errorOutput.textContent = `${error.name}: ${error.message}`; - if (error.stack) { - errorOutput.textContent += `\n${error.stack}`; - } - logRow.appendChild(errorOutput); - } - container.remove(); - counter++; -} - -test("editable-area renders", (container) => { - container.innerHTML = `Test 123`; - - const textarea = container.querySelector("textarea"); - const display = container.querySelector("p"); - - assert(() => textarea.value === "Test 123"); - assert(() => display.classList.contains("display")); - assert(() => display.innerHTML === "Test 123
"); +runTests({ + only: CONCRETE_TEST ? [parseInt(CONCRETE_TEST)] : null, }); - -test("editable-area updates on input", (container) => { - container.innerHTML = ``; - - const textarea = container.querySelector("textarea"); - const display = container.querySelector("p"); - - let eventCalled = false; - container.addEventListener("contentChange", () => { - eventCalled = true; - }); - - textarea.value = "Some new content\nwith a newline"; - textarea.dispatchEvent( - new Event("input", { bubbles: true, cancelable: true }), - ); - - assert(() => display.innerHTML === `Some new content
with a newline
`); - assert(() => eventCalled === true); -}); - -test("editable-area respects readonly attribute", (container) => { - container.innerHTML = `Some text`; - - const textarea = container.querySelector("textarea"); - - assert(() => textarea.disabled) -}) diff --git a/priv/static/js/test-utils.js b/priv/static/js/test-utils.js new file mode 100644 index 0000000..9faec0f --- /dev/null +++ b/priv/static/js/test-utils.js @@ -0,0 +1,101 @@ +let counter = 1; +let tests = []; + +const body = document.querySelector("body"); +const log = document.querySelector("#test-log"); + +class AssertError extends Error { + constructor(message, options) { + super(message, options); + } +} + +const setup = (idx) => { + const container = document.createElement("div", { id: `test-${idx}` }); + body.appendChild(container); + return container; +}; + +export async function assert(assertion, timeout) { + timeout = timeout || 100; + const interval = Math.max(timeout / 10, 10); + const start = performance.now(); + let now = performance.now(); + + while (true) { + let result; + let error; + + try { + result = assertion(); + } catch (error) { + result = false; + error = error; + } + + if (result === true) break; + + if (now - start >= timeout) { + const assertionStr = assertion.toString(); + const opts = error ? { cause: error } : {}; + throw new AssertError(`Assertion failed: ${assertionStr}`, opts); + } + + await new Promise((r) => setTimeout(r, interval)); + now = performance.now(); + } +} + +export function runTests(globalOptions) { + tests.forEach(async ({ label, testFun, idx, options }) => { + const logRow = document.createElement("li"); + + if (globalOptions.only && globalOptions.only.indexOf(idx) < 0) { + return; + } + + if (options.skip) { + const message = `[SKIPPED] ${label}`; + logRow.textContent = message; + log.appendChild(logRow); + console.info(message); + } + + const container = setup(idx); + logRow.textContent = `[RUNNING] ${label}`; + log.appendChild(logRow); + + try { + await testFun(container); + const message = `[OK] ${label}`; + console.info(message); + logRow.textContent = message; + logRow.classList.add("success"); + } catch (error) { + const message = `[ERROR] ${label}`; + console.error(message, error); + logRow.textContent = message; + logRow.classList.add("failure"); + const errorOutput = document.createElement("pre"); + errorOutput.textContent = `${error.name}: ${error.message}`; + if (error.stack) { + errorOutput.textContent += `\n${error.stack}`; + } + logRow.appendChild(errorOutput); + } + container.remove(); + }); +} + +export function test(label, testFun, options) { + options = options || {}; + + tests.push({ + label: label, + testFun: testFun, + idx: counter, + options: options, + }); + + counter++; +}