Skip to content

Commit

Permalink
Add PageLoader to run tests w/ JSDOM & the browser
Browse files Browse the repository at this point in the history
PageLoader, in test-helpers.js, enables running the same tests whether
running in Node with JSDOM or running in a browser. It also provides
PageLoader.closeAll() for easy afterEach() cleanup.

Its JsdomPageLoader contains the previous loadFromFile() and
importModules() logic from main.test.js. It's also redesigned to be much
more consistent, as extensively documented in the comments.

Now it's possible to run the tests using the Vitest browser runner via:

  pnpm test -- --browser.name=chrome

(Or "firefox"; "safari" seems a bit broken at the moment.)

---

However, it's not yet possible to run headless via:

  pnpm test -- --browser.name=chrome --browser.headless

This is because this change adds @vitest/browser v0.34.6, the latest non
beta version, which doesn't contain:

- vitest-dev/vitest#4364

That PR passes the options that enable headless mode through to the
BrowserProvider implementations, thus closing:

- vitest-dev/vitest#3930

However, the change is only present from v1.0.0-beta.3:

- vitest-dev/vitest@5cdeb55
- https://github.com/vitest-dev/vitest/releases/tag/v1.0.0-beta.3

I'll try updating to v1.0.0-beta.4 (the latest at this time). If that
works, I'll commit it.
  • Loading branch information
mbland committed Nov 15, 2023
1 parent 08f984d commit af2084b
Show file tree
Hide file tree
Showing 4 changed files with 1,860 additions and 99 deletions.
60 changes: 6 additions & 54 deletions strcalc/src/main/frontend/main.test.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,16 @@
/* eslint-env browser, node, jest, vitest */
'use strict'
import { describe, expect, test } from 'vitest'
import { JSDOM } from 'jsdom'

// Returns window and document objects from a JSDOM-parsed HTML file.
//
// It will execute <script type="module"> elements with a `src` attribute,
// but not those with inline code. See the comment for importModules().
//
// Based on hints from:
// - https://oliverjam.es/articles/frontend-testing-node-jsdom
let loadFromFile = async (filePath) => {
let dom = await JSDOM.fromFile(
filePath, { resources: 'usable', runScripts: 'dangerously' }
)
import { describe, afterEach, expect, test } from 'vitest'
import { PageLoader } from './test-helpers.js'

// Once importModules() goes away, wrap the return value in a Promise that
// resolves via dom.window.addEventListener('load', ...).
await importModules(dom)
return { window: dom.window, document: dom.window.document }
}

// Imports <script type="module"> elements parsed, but not executed, by JSDOM.
//
// Only works with scripts with a `src` attribute; it will not execute inline
// code.
//
// Remove this function once "jsdom/jsdom: <script type=module> support #2475"
// has been resolved:
// - https://github.com/jsdom/jsdom/issues/2475
//
// Note on timing of script execution
// ----------------------------------
// By the time the dynamic import() calls registered by importModules() begin
// executing, the window's 'DOMContentLoaded' and 'load' events will have
// already fired. Technically, the imported modules should execute similarly
// to <script defer> and execute before 'DOMContentLoaded'. As a result, we
// can't register handlers for these events in our module code. We can add these
// handlers in inline <script>s, but those can't reference module code and
// expect JSDOM tests to work at the moment.
//
// All that said, these should prove to be corner cases easily avoided by sound,
// modular app architecture.
let importModules = async (dom) => {
let modules = Array.from(
dom.window.document.querySelectorAll('script[type="module"]')
)
describe('String Calculator UI', () => {
let loader = new PageLoader('/strcalc')

// The JSDOM docs advise against setting global properties, but we don't
// have another option given the module may access window and/or document.
global.window = dom.window
global.document = dom.window.document
await Promise.all(modules.map(s => import(s.src)))
global.window = global.document = undefined
}
afterEach(() => loader.closeAll())

describe('String Calculator UI', () => {
describe('initial state', () => {
test('contains the "Hello, World!" placeholder', async () => {
let { document } = await loadFromFile('./index.html')
let { document } = await loader.load('index.html')

let e = document.querySelector('#app .placeholder')

Expand Down
8 changes: 5 additions & 3 deletions strcalc/src/main/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
"coverage": "vitest run --coverage"
},
"devDependencies": {
"@stylistic/eslint-plugin-js": "^1.0.1",
"@stylistic/eslint-plugin-js": "^1.2.0",
"@vitest/browser": "^0.34.6",
"@vitest/coverage-v8": "^0.34.6",
"@vitest/ui": "^0.34.6",
"eslint": "^8.53.0",
"eslint-plugin-vitest": "^0.3.9",
"jsdom": "^22.1.0",
"vite": "^4.4.5",
"vitest": "^0.34.6"
"vite": "^4.5.0",
"vitest": "^0.34.6",
"webdriverio": "^8.23.0"
}
}
Loading

0 comments on commit af2084b

Please sign in to comment.