From 80e2bd66f54bca88a749d40784828d29bae8995f Mon Sep 17 00:00:00 2001 From: Jacob Scearcy Date: Mon, 6 May 2024 06:42:35 -0700 Subject: =?UTF-8?q?=F0=9F=94=80=20Use=20vitest=20for=20runtime/vdom=20test?= =?UTF-8?q?ing.=20(#124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🧪 move tests into test directory, bump birdie to ignore non-gleam files * implement feedback * add comments, update doc --- test/02-interactivity.test.js | 88 +++++++++++++++++++++++++ test/README.md | 29 ++++++++ test/utils.js | 29 ++++++++ test/vdom.ffi.bench.js | 27 ++++++++ test/vdom.ffi.test.js | 150 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 323 insertions(+) create mode 100644 test/02-interactivity.test.js create mode 100644 test/README.md create mode 100644 test/utils.js create mode 100644 test/vdom.ffi.bench.js create mode 100644 test/vdom.ffi.test.js (limited to 'test') diff --git a/test/02-interactivity.test.js b/test/02-interactivity.test.js new file mode 100644 index 0000000..3355e94 --- /dev/null +++ b/test/02-interactivity.test.js @@ -0,0 +1,88 @@ +import { beforeEach, describe, expect, test } from "vitest"; +import { setupDOM } from "./utils.js"; +// built via npm script "build:test:02" +import { main } from "@root/examples/02-interactivity/build/dev/javascript/app/app.mjs"; + +let appEl; +beforeEach(() => { + setupDOM(); + appEl = document.getElementById("app"); +}); + +describe("counter example", () => { + test("should render initially", () => { + main(); + + expect(document.toString()).toMatchSnapshot(); + }); + + test("should increment on button press", () => { + main(); + + const buttons = document.querySelectorAll("button.lustre-ui-button"); + const incrementButton = buttons[0]; + const count = document.querySelector("p"); + + expect(incrementButton).toBeTruthy(); + + incrementButton.click(); + + expect(count.innerText).toBe("1"); + + incrementButton.click(); + expect(count.innerText).toBe("2"); + + incrementButton.click(); + expect(count.innerText).toBe("3"); + + }); + + test("should decrement on button press", () => { + main(); + + const buttons = document.querySelectorAll("button.lustre-ui-button"); + const decrementButton = buttons[1]; + const count = document.querySelector("p"); + + expect(decrementButton).toBeTruthy(); + + decrementButton.click(); + expect(count.innerText).toBe("-1"); + + decrementButton.click(); + expect(count.innerText).toBe("-2"); + + decrementButton.click(); + expect(count.innerText).toBe("-3"); + }); + + test("should increment and decrement on button press", () => { + main(); + + const buttons = document.querySelectorAll("button.lustre-ui-button"); + const incrementButton = buttons[0]; + const decrementButton = buttons[1]; + const count = document.querySelector("p"); + + incrementButton.click(); + + expect(count.innerText).toBe("1"); + + incrementButton.click(); + expect(count.innerText).toBe("2"); + + incrementButton.click(); + expect(count.innerText).toBe("3"); + + expect(decrementButton).toBeTruthy(); + + decrementButton.click(); + expect(count.innerText).toBe("2"); + + decrementButton.click(); + expect(count.innerText).toBe("1"); + + decrementButton.click(); + expect(count.innerText).toBe("0"); + }); +}); diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..6577c5c --- /dev/null +++ b/test/README.md @@ -0,0 +1,29 @@ +# Client testing for Lustre runtime + +1. Build and test example projects +2. Build and test vdom + +Depends on: +- `linkedom` - headless DOM testing +- `npm-run-all` - run watch in parallel +- `vitest` - execute tests + + +### Commands + +Run from the `test` directory +Each command will run a `build` command to build project dependencies + +#### Benchmark + +- `npm run bench` + +#### Test + +- ##### Single + + - `npm run test` + +- ##### Watch + + - `npm run test:watch` diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..c3abaee --- /dev/null +++ b/test/utils.js @@ -0,0 +1,29 @@ +import { parseHTML } from 'linkedom'; +import { vi } from 'vitest'; + +// Parse the starting state of the basic starting template +export function setupDOM() { + const result = parseHTML(` + + + + + + + 🚧 {app_name} + + + + +
+ +`); + + global.HTMLElement = result.HTMLElement; + global.Node = result.Node; + global.document = result.document; + global.window = result.window; + global.window.requestAnimationFrame = vi.fn().mockImplementation((cb) => cb()); + + return result; +} \ No newline at end of file diff --git a/test/vdom.ffi.bench.js b/test/vdom.ffi.bench.js new file mode 100644 index 0000000..a8e139e --- /dev/null +++ b/test/vdom.ffi.bench.js @@ -0,0 +1,27 @@ +import { bench, describe } from "vitest"; +import { setupDOM } from "./utils"; +import { morph } from "../src/vdom.ffi.mjs"; +import { smoke_test } from "../test-apps/vdom-test-templates/build/dev/javascript/app/client_test.mjs"; + +// BENCH ------------------------------------------------------------------------ + +describe("vdom morph bench", () => { + let appEl; + let template; + bench( + "smoke test morph", + () => { + appEl = morph(appEl, template); + }, + { + setup: () => { + const result = setupDOM(); + + global.Node = result.Node; + global.document = result.document; + appEl = document.getElementById("app"); + template = smoke_test(); + } + } + ); +}); diff --git a/test/vdom.ffi.test.js b/test/vdom.ffi.test.js new file mode 100644 index 0000000..2614c71 --- /dev/null +++ b/test/vdom.ffi.test.js @@ -0,0 +1,150 @@ +import { beforeEach, describe, expect, test } from "vitest"; +import { setupDOM } from "./utils.js"; + +import { morph } from "@root/src/vdom.ffi.mjs"; + +// built via npm script "build:test:vdom" +import { + disabled_attr_test, + dynamic_content_test, + fragment_test, + keyed_test, + smoke_test, +} from "../test-apps/vdom-test-templates/build/dev/javascript/app/client_test.mjs"; + +let appEl; +beforeEach(() => { + setupDOM(); + appEl = document.getElementById("app"); +}); + +// TEST ------------------------------------------------------------------------ + +const singleMorphSnapshot = (name, template) => { + appEl = morph(appEl, template); + + const currentState = document.toString(); + + expect(currentState).toMatchSnapshot(name); +}; + +describe("vdom morph", () => { + test(`should render smoke test with vdom morph`, () => { + const template = smoke_test(); + + singleMorphSnapshot("smoke_test", template); + }); + + test(`should render using vdom morph with fragments`, () => { + const template = fragment_test(); + + singleMorphSnapshot("fragment_test", template); + }); + + test(`should render using vdom morph with keys`, () => { + const template = keyed_test(); + + singleMorphSnapshot("fragment_test", template); + }); + + test(`should be stable when vdom morph is called multiple times with no changes using fragment`, () => { + const template = fragment_test(); + appEl = morph(appEl, template); + + const initialState = document.toString(); + + const states = []; + for (let i = 0; i < 5; i++) { + appEl = morph(appEl, template); + states.push(document.toString()); + } + + states.forEach((state) => { + expect(state).toEqual(initialState); + }); + }); + + test(`should be stable when vdom morph is called multiple times with no changes using keys`, () => { + const template = keyed_test(); + appEl = morph(appEl, template); + + const initialState = document.toString(); + + const states = []; + for (let i = 0; i < 5; i++) { + appEl = morph(appEl, template); + states.push(document.toString()); + } + + states.forEach((state) => { + expect(state).toEqual(initialState); + }); + }); + + test(`should render updated templates`, () => { + const initialTemplate = dynamic_content_test(0, "initial_name"); + + appEl = morph(appEl, initialTemplate); + + const initialState = document.toString(); + + expect(initialState).toContain("0"); + expect(initialState).toContain("initial_name"); + + const updatedtemplate = dynamic_content_test(56, "updated_name"); + + appEl = morph(appEl, updatedtemplate); + + const updatedState = document.toString(); + + expect(updatedState).toContain("56"); + expect(updatedState).toContain("updated_name"); + }); +}); + +describe("vdom morph attribute", () => { + describe("disabled", () => { + test("should not be disabled when is_disabled is false", () => { + const template = disabled_attr_test(false); + + appEl = morph(appEl, template); + + const domResult = document.toString(); + + expect(domResult).toContain("input"); + expect(domResult).not.toContain("disabled"); + }); + + test("should be disabled when is_disabled is true", () => { + const template = disabled_attr_test(true); + + appEl = morph(appEl, template); + + const domResult = document.toString(); + + expect(domResult).toContain("input"); + expect(domResult).toContain("disabled"); + }); + + + // this fails today + test.skip("should be stable when disabled attribute does not change", () => { + const template = disabled_attr_test(true); + + appEl = morph(appEl, template); + + const initialState = document.toString(); + + const states = []; + for (let i = 0; i < 5; i++) { + appEl = morph(appEl, template); + states.push(document.toString()); + } + + states.forEach((state) => { + expect(state).toEqual(initialState); + }); + }); + }); +}); + -- cgit v1.2.3