Skip to content

Commit

Permalink
Bump to v1.0.4, add type files, fix type issues
Browse files Browse the repository at this point in the history
This adds Typescript to devDependencies and a `prepack` script to
generate `types/*.d.ts{,map}` files for distribution.

It also adds a jsonconfig.json files to ensure Visual Studio Code
performs more thorough type checking. Enables strict type checking, but
disables null and implicit "any" checks. Fixes several existing issues,
noted below.

Inspired by VS Code warnings from importing v1.0.3 into
mbland/rollup-plugin-handlebars-precompiler.

Based on guidance from:

- https://code.visualstudio.com/docs/nodejs/working-with-javascript
- https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html
- https://code.visualstudio.com/docs/languages/jsconfig
- https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html
- https://www.typescriptlang.org/docs/handbook/modules/introduction.html
- https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html

Minor issues fixed:

- The `@returns` type of `TestPageOpener.create()` was originally
  `TestPageOpener`, but is now `Promise<TestPageOpener>`.

- The `beforeAll()` handler originally returned
  `Promise<TestPageOpener>`, and now returns nothing.

- Added '.js' extension to all internal imports.

- Added @ts-nocheck to vite and vitest config files.

- Changed `@returns {Promise}` to `@returns {Promise<void>}`.

- Expanded the `resetGlobals()` event callback so the `delete`
  statements and `resolve()` call are on separate lines.

- Added `eslint-env browser` and `@type {HTMLScriptElement}` to
  test/event-ordering-demo/main.js.

- Added `@ts-expect-error` comment when overriding `globalThis.window`
  in JsdomPageOpener.

More substantial fixes:

- Assigned `const Event = globalThis.window.Event` and changed
  `window.Event` to just `Event` in JsdomPageOpener. Using the builtin
  Node.js Event was incompatible with the jsdom Window and Document
  implementations.

- Changed the jsdom and jsdom.JSDOM parameters of the JsdomPageOpener
  constructor to type "object".

  I had tried using:

  ```js
  /** @typedef {typeof import('jsdom')} jsdom */
  ```

  Which worked great in VS Code, but broke the jsdoc CLI. I found a
  couple jsdoc CLI plugins which seemed like they would strip this
  comment, but couldn't get them to work.

  My JSDoc comments could stand to use some work to get the
  `jsdoc`-generated site to look better and make more sense. They
  already work great in VS Code, though, and at least don't break the
  CLI.
  • Loading branch information
mbland committed Jan 8, 2024
1 parent 9e2b962 commit a63f274
Show file tree
Hide file tree
Showing 16 changed files with 69 additions and 25 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ node_modules/
out/
pnpm-debug.log
tmp/
types/
*.log
*.tgz
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ import TestPageOpener from 'test-page-opener'
describe('TestPageOpener', () => {
let opener

beforeAll(async () => opener = await TestPageOpener.create('/basedir/'))
beforeAll(async () => {opener = await TestPageOpener.create('/basedir/')})
afterEach(() => opener.closeAll())

test('loads page with module successfully', async () => {
const { document } = await opener.open('path/to/index.html')
const appElem = document.querySelector('#app')

expect(appElem).not.toBeNull()
expect(appElem.textContent).toContain('Hello, World!')
})
})
Expand Down
3 changes: 2 additions & 1 deletion ci/vitest.config.browser.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-nocheck
import { defineConfig, mergeConfig } from 'vitest/config'
import baseConfig from './vitest.config'
import baseConfig from './vitest.config.js'

export default mergeConfig(baseConfig, defineConfig({
test: {
Expand Down
3 changes: 2 additions & 1 deletion ci/vitest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-nocheck
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from '../vite.config'
import viteConfig from '../vite.config.js'

export default mergeConfig(viteConfig, defineConfig({
test: {
Expand Down
11 changes: 7 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ import { OpenedPage } from './lib/types.js'
* describe('TestPageOpener', () => {
* let opener
*
* beforeAll(async () => opener = await TestPageOpener.create('/basedir/'))
* beforeAll(async () => {opener = await TestPageOpener.create('/basedir/')})
* afterEach(() => opener.closeAll())
*
* test('loads page with module successfully', async () => {
* const { document } = await opener.open('path/to/index.html')
* const appElem = document.querySelector('#app')
*
* expect(appElem).not.toBeNull()
* expect(appElem.textContent).toContain('Hello, World!')
* })
* })
Expand All @@ -38,6 +39,7 @@ export default class TestPageOpener {

#basePath
#impl
/** @type {OpenedPage[]} */
#opened

/**
Expand Down Expand Up @@ -69,12 +71,13 @@ export default class TestPageOpener {
*
* ```js
* let opener
* beforeAll(async () => opener = await TestPageOpener.create('/basedir/'))
* beforeAll(async () => {opener = await TestPageOpener.create('/basedir/')})
* ```
* @param {string} basePath - base path of the application under test; must
* start with '/' and end with '/'
* @returns {TestPageOpener} - a new TestPageOpener initialized to open pages
* in the current test environment, either via Jsdom or the browser
* @returns {Promise<TestPageOpener>} - a new TestPageOpener initialized to
* open pages in the current test environment, either via jsdom or the
* browser
*/
static async create(basePath) {
const impl = globalThis.window ?
Expand Down
10 changes: 10 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"checkJs": true,
"module": "nodenext",
"strict": true,
"strictNullChecks": false,
"noImplicitAny": false
},
"exclude": ["node_modules"]
}
4 changes: 3 additions & 1 deletion jsdoc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"plugins": [ "plugins/markdown" ],
"plugins": [
"plugins/markdown"
],
"recurseDepth": 10,
"source": {
"includePattern": ".+\\.js$",
Expand Down
3 changes: 3 additions & 0 deletions lib/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export default class BrowserPageOpener {
#window
#coverageKey

/**
* @param {Window} window - the global (browser) window object
*/
constructor(window) {
const covKey = getCoverageKey(window)

Expand Down
24 changes: 15 additions & 9 deletions lib/jsdom.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default class JsdomPageOpener {
/**
* Creates a JsdomPageOpener from a dynamically imported jsdom module
* @param {object} jsdom - dynamically imported jsdom module
* @param {jsdom.JSDOM} jsdom.JSDOM - the JSDOM class
* @param {object} jsdom.JSDOM - JSDOM class from the jsdom module
*/
constructor({ JSDOM }) {
this.#JSDOM = JSDOM
Expand Down Expand Up @@ -91,7 +91,7 @@ export default class JsdomPageOpener {
* Dynamically imports ECMAScript modules.
* @param {Window} window - the jsdom window object
* @param {Document} document - the jsdom window.document object
* @returns {Promise} - resolves after importing all ECMAScript modules
* @returns {Promise<void>} - resolves after importing all ECMAScript modules
* @throws if importing any ECMAScript modules fails
*/
#importModules(window, document) {
Expand Down Expand Up @@ -125,8 +125,11 @@ export default class JsdomPageOpener {
// register closures over window and document, or specific document
// elements. That would ensure they remain defined even after we remove
// window and document from globalThis.
//
// @ts-expect-error
globalThis.window = window
globalThis.document = document
const Event = globalThis.window.Event

try { await importModules(document) }
catch (err) { reject(err) }
Expand All @@ -137,20 +140,22 @@ export default class JsdomPageOpener {
// DOMContentLoaded event listeners and have them fire here.
//
// We eventually fire the 'load' event again too for the same reason.
document.dispatchEvent(new window.Event(
document.dispatchEvent(new Event(
'DOMContentLoaded', {bubbles: true, cancelable: false}
))

// Register a 'load' listener that deletes the global window and
// document variables. Because it's registered after any
// DOMContentLoaded listeners have fired, it should execute after any
// other 'load' listeners registered by any module code.
const resetGlobals = () => resolve(
delete globalThis.document, delete globalThis.window
)
const resetGlobals = () => {
delete globalThis.document
delete globalThis.window
resolve()
}
window.addEventListener('load', resetGlobals, {once: true})
window.dispatchEvent(
new window.Event('load', {bubbles: false, cancelable: false})
new Event('load', {bubbles: false, cancelable: false})
)
}
window.addEventListener('load', importModulesOnEvent, {once: true})
Expand All @@ -163,10 +168,11 @@ export default class JsdomPageOpener {
*
* Only works with the `src` attribute; it will not execute inline code.
* @param {Document} doc - the jsdom window.document object
* @returns {Promise} - resolves after importing all ECMAScript modules in doc
* @throws if importing any ECMAScript modules fails
* @returns {Promise<void[]>} - resolves after importing all modules in doc
* @throws if any module import fails
*/
function importModules(doc) {
/** @type {HTMLScriptElement[]} */
const modules = Array.from(doc.querySelectorAll('script[type="module"]'))
return Promise.all(modules.filter(m => m.src).map(async m => {
try { await import(m.src) }
Expand Down
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
{
"name": "test-page-opener",
"version": "1.0.3",
"version": "1.0.4",
"description": "Enables an application's tests to open its own page URLs both in the browser and in Node.js using jsdom",
"main": "index.js",
"types": "types/index.d.ts",
"scripts": {
"lint": "eslint --color --max-warnings 0 .",
"test": "vitest",
"test:ci": "eslint --color --max-warnings 0 . && vitest run -c ci/vitest.config.js && vitest run -c ci/vitest.config.browser.js",
"jsdoc": "jsdoc-cli-wrapper -c jsdoc.json ."
"jsdoc": "jsdoc-cli-wrapper -c jsdoc.json .",
"prepack": "npx -p typescript tsc ./index.js --allowJs --declaration --declarationMap --emitDeclarationOnly --outDir types"
},
"files": [ "index.js", "lib/*" ],
"files": [
"index.js",
"lib/*",
"types/*"
],
"keywords": [
"testing",
"jsdom",
Expand All @@ -35,6 +41,7 @@
"eslint-plugin-vitest": "^0.3.20",
"jsdoc-cli-wrapper": "^1.0.4",
"jsdom": "^23.1.0",
"typescript": "^5.3.3",
"vite": "^5.0.11",
"vitest": "^1.1.3",
"webdriverio": "^8.27.0"
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion test/browser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { DEFAULT_COVERAGE_KEY, getCoverageKey } from '../lib/browser'
import { DEFAULT_COVERAGE_KEY, getCoverageKey } from '../lib/browser.js'
import { describe, expect, test } from 'vitest'

describe('getCoverageKey', () => {
Expand Down
6 changes: 4 additions & 2 deletions test/event-ordering-demo/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
/* eslint-env browser */
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
Expand Down Expand Up @@ -46,8 +47,9 @@ const { window } = await JSDOM.fromFile(
pagePath, {resources: 'usable', runScripts: 'dangerously'}
)
const document = window.document
const modulePath = document.querySelector('script[type="module"]').src
const importPromise = import(modulePath)
/** @type {HTMLScriptElement} */
const moduleElem = document.querySelector('script[type="module"]')
const importPromise = import(moduleElem.src)

print(`document.readyState === ${document.readyState}`)
document.addEventListener('DOMContentLoaded', () => print('DOMContentLoaded'))
Expand Down
2 changes: 1 addition & 1 deletion test/jsdom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import JsdomPageOpener from '../lib/jsdom'
import JsdomPageOpener from '../lib/jsdom.js'
import { beforeAll, describe, expect, test } from 'vitest'

describe.skipIf(globalThis.window !== undefined)('JsdomPageOpener', () => {
Expand Down
4 changes: 3 additions & 1 deletion test/main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ import TestPageOpener from '../index.js'
describe('TestPageOpener', () => {
let opener

beforeAll(async () => opener = await TestPageOpener.create('/basedir/'))
beforeAll(async () => {opener = await TestPageOpener.create('/basedir/')})
afterEach(() => opener.closeAll())

test('loads page with module successfully', async () => {
const { document } = await opener.open('test-modules/index.html')
const appElem = document.querySelector('#app')
const linkElem = document.querySelector('#app p a')

expect(appElem).not.toBeNull()
expect(appElem.textContent).toContain('Hello, World!')
expect(linkElem).not.toBeNull()
expect(linkElem.href).toContain('%22Hello,_World!%22')
})

Expand Down
1 change: 1 addition & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-nocheck
import { defineConfig } from 'vite'
import { configDefaults } from 'vitest/config'
import fs from 'node:fs'
Expand Down

0 comments on commit a63f274

Please sign in to comment.