From 8460dd2d4967988bf125723c1fa859a8151e3940 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 10:49:17 -0500 Subject: [PATCH 01/19] Adding playwright. --- .github/workflows/playwright.yml | 27 +++++++++++ .gitignore | 4 ++ .vscode/extensions.json | 5 +++ package.json | 2 + playwright.config.ts | 77 ++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 .github/workflows/playwright.yml create mode 100644 .vscode/extensions.json create mode 100644 playwright.config.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000..5156520e --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index acfca97d..7e870ea4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ node_modules dist .*_cache* package-lock.json +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..a7c50832 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-playwright.playwright" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 689d80b8..6f5c1b52 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,10 @@ }, "license": "Apache-2.0", "devDependencies": { + "@playwright/test": "^1.40.1", "@rollup/plugin-terser": "0.4.0", "@rollup/plugin-typescript": "11.0.0", + "@types/node": "^20.10.5", "chai": "^4.3.7", "conditional-type-checks": "1.0.6", "husky": "8.0.3", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..301801ee --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); From 432fff79d9fd6ce9e807cef9bffadd9fc0f7fc29 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 10:57:55 -0500 Subject: [PATCH 02/19] Adding serve package for test fixtures. --- package.json | 1 + playwright.config.ts | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6f5c1b52..7f73ae5b 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "prettier": "2.8.3", "rimraf": "4.1.2", "rollup": "3.10.1", + "serve": "^14.2.1", "tslib": "2.4.1", "typescript": "4.9.4" } diff --git a/playwright.config.ts b/playwright.config.ts index 301801ee..670a9958 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -69,9 +69,9 @@ export default defineConfig({ ], /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, + webServer: { + command: 'npx serve -p 3000 tests/fixtures', + url: 'http://127.0.0.1:3000', + reuseExistingServer: true, + }, }); From bf5c39962bf9a7dc628ff4e9ac48e1b515e944f2 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 11:02:29 -0500 Subject: [PATCH 03/19] Removing karma. --- package.json | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 7f73ae5b..cabbe943 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,13 @@ "sideEffects": false, "scripts": { "build": "rollup -c", - "test:unit": "karma start", + "test:unit": "playwright test", "test:node": "mocha ./tests/node/main.mjs", "test:types": "tsc -p ./tests/tsconfig.json", "test:types:watch": "npm run test:types -- --watch", "test": "npm run fmt_test && npm run build && npm run test:types && npm run test:unit && npm run test:node", "fmt": "prettier --write './*.{mjs,js,ts,md,json,html}' './{src,docs,tests}/{,**/}*.{mjs,js,ts,md,json,html}'", - "fmt_test": "test $(prettier -l './*.{mjs,js,ts,md,json,html}' './{src,docs,tests}/{**/,}*.{mjs,js,ts,md,json,html}' | wc -l) -eq 0", - "watchtest": "CHROME_ONLY=1 karma start --no-single-run" + "fmt_test": "test $(prettier -l './*.{mjs,js,ts,md,json,html}' './{src,docs,tests}/{**/,}*.{mjs,js,ts,md,json,html}' | wc -l) -eq 0" }, "author": { "name": "Surma", @@ -34,14 +33,6 @@ "chai": "^4.3.7", "conditional-type-checks": "1.0.6", "husky": "8.0.3", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-detect-browsers": "2.3.3", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-safari-launcher": "1.0.0", - "karma-safaritechpreview-launcher": "2.0.2", "mocha": "10.2.0", "prettier": "2.8.3", "rimraf": "4.1.2", @@ -50,4 +41,4 @@ "tslib": "2.4.1", "typescript": "4.9.4" } -} +} \ No newline at end of file From 58423adeb6df17a5a15a10940a262e1f4f15059f Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 11:08:54 -0500 Subject: [PATCH 04/19] Removing karma config. --- karma.conf.js | 68 --------------------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 karma.conf.js diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index c7840f40..00000000 --- a/karma.conf.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2017 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = function (config) { - const configuration = { - basePath: "", - frameworks: ["mocha", "chai", "detectBrowsers"], - files: [ - { - pattern: "tests/fixtures/*", - included: false, - }, - { - pattern: "dist/**/*.@(mjs|js)", - included: false, - }, - { - pattern: "tests/*.test.js", - type: "module", - }, - ], - reporters: ["progress"], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - singleRun: true, - concurrency: Infinity, - detectBrowsers: { - enabled: true, - usePhantomJS: false, - preferHeadless: true, - postDetection: (availableBrowsers) => { - if (process.env.INSIDE_DOCKER) { - return ["DockerChrome"]; - } else if (process.env.CHROME_ONLY) { - return ["ChromeHeadless"]; - } else { - // Filtering SafariTechPreview because I am having - // local issues and I have no idea how to fix them. - // I know that’s not a good reason to disable tests, - // but Safari TP is relatively unimportant. - return availableBrowsers.filter( - (browser) => browser !== "SafariTechPreview" - ); - } - }, - }, - customLaunchers: { - DockerChrome: { - base: "ChromeHeadless", - flags: ["--no-sandbox"], - }, - }, - }; - - config.set(configuration); -}; From e32efa8d1f53c1c6cc54f874b0ada9d8679b6e7e Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 12:20:40 -0500 Subject: [PATCH 05/19] Adding custom assets server. --- package.json | 3 +-- playwright.config.ts | 2 +- test-server.mjs | 60 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 test-server.mjs diff --git a/package.json b/package.json index cabbe943..7ce8ca7c 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,7 @@ "prettier": "2.8.3", "rimraf": "4.1.2", "rollup": "3.10.1", - "serve": "^14.2.1", "tslib": "2.4.1", "typescript": "4.9.4" } -} \ No newline at end of file +} diff --git a/playwright.config.ts b/playwright.config.ts index 670a9958..87beea33 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -70,7 +70,7 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - command: 'npx serve -p 3000 tests/fixtures', + command: 'node test-server.mjs', url: 'http://127.0.0.1:3000', reuseExistingServer: true, }, diff --git a/test-server.mjs b/test-server.mjs new file mode 100644 index 00000000..194b0d51 --- /dev/null +++ b/test-server.mjs @@ -0,0 +1,60 @@ +import { createServer } from "node:http"; +import { extname } from "node:path"; +import { parse } from "node:url"; +import { readFile } from "node:fs/promises"; + +const testFixturesPath = "./tests/fixtures"; +const buildPath = "./dist/esm"; +const port = process.argv[2] || 3000; + +const mimeMap = new Map([ + [".html", "text/html"], + [".js", "text/javascript"], + [".mjs", "text/javascript"], + [".map", "application/json"], +]); + +createServer(async (req, res) => { + console.log(`${req.method} ${req.url}`); + const { pathname } = parse(req.url); + try { + const result = await readLocalFile(pathname); + if (!result) { + res.statusCode = 404; + res.end(`File ${pathname} not found!`); + return; + } + const ext = extname(pathname); + res.setHeader("Content-type", mimeMap.get(ext) ?? "text/plain"); + res.end(result); + } catch (err) { + console.error(err); + res.statusCode = 500; + res.end(`Error getting the file: ${err}.`); + return; + } +}).listen(parseInt(port)); + +console.log(`Server listening on port ${port}`); + +async function readLocalFile(pathname) { + const segments = pathname.split("/").filter(Boolean); + if (segments.length === 1) { + return await readLocalFileFromPath(segments[0], testFixturesPath); + } else if (segments.length === 2 && segments[0] === "dist") { + return await readLocalFileFromPath(segments[1], buildPath); + } else { + return null; + } +} + +async function readLocalFileFromPath(basename, rootpath) { + try { + return await readFile(`${rootpath}/${basename}`); + } catch (err) { + if (err.code === "ENOENT") { + return null; + } + throw err; + } +} From fd7fa9d212fdcbf53723982ad12019acdf64cc59 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 14:58:43 -0500 Subject: [PATCH 06/19] Updating worker test. --- playwright.config.ts | 12 ++++----- tests/fixtures/empty.html | 2 ++ tests/fixtures/worker.js | 2 +- tests/worker.comlink.test.js | 48 +++++++++++++++++++++++++----------- 4 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 tests/fixtures/empty.html diff --git a/playwright.config.ts b/playwright.config.ts index 87beea33..34500269 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -68,10 +68,10 @@ export default defineConfig({ // }, ], - /* Run your local dev server before starting the tests */ - webServer: { - command: 'node test-server.mjs', - url: 'http://127.0.0.1:3000', - reuseExistingServer: true, - }, + // /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'node test-server.mjs', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: true, + // }, }); diff --git a/tests/fixtures/empty.html b/tests/fixtures/empty.html new file mode 100644 index 00000000..8c7fe211 --- /dev/null +++ b/tests/fixtures/empty.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/fixtures/worker.js b/tests/fixtures/worker.js index 64f65de1..c9745215 100644 --- a/tests/fixtures/worker.js +++ b/tests/fixtures/worker.js @@ -11,6 +11,6 @@ * limitations under the License. */ -importScripts("/base/dist/umd/comlink.js"); +import * as Comlink from "/dist/comlink.js"; Comlink.expose((a, b) => a + b); diff --git a/tests/worker.comlink.test.js b/tests/worker.comlink.test.js index 3d87ef98..850c97f9 100644 --- a/tests/worker.comlink.test.js +++ b/tests/worker.comlink.test.js @@ -11,26 +11,46 @@ * limitations under the License. */ -import * as Comlink from "/base/dist/esm/comlink.mjs"; +import { test, expect } from "@playwright/test"; -describe("Comlink across workers", function () { - beforeEach(function () { - this.worker = new Worker("/base/tests/fixtures/worker.js"); +test.describe("Comlink across workers", () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/empty.html"); + await page.addScriptTag({ + content: ` +import * as Comlink from "./dist/comlink.mjs" +window.testData = { + Comlink, + worker: new Worker("./worker.js", { type: 'module' }), +};`, + type: "module", + }); }); - afterEach(function () { - this.worker.terminate(); + test.afterEach(async ({ page }) => { + await page.evaluate(async () => { + const { worker } = window.testData; + worker.terminate(); + }); }); - it("can communicate", async function () { - const proxy = Comlink.wrap(this.worker); - expect(await proxy(1, 3)).to.equal(4); + test("can communicate", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, worker } = window.testData; + const proxy = Comlink.wrap(worker); + return await proxy(1, 3); + }); + expect(result).toEqual(4); }); - it("can tunnels a new endpoint with createEndpoint", async function () { - const proxy = Comlink.wrap(this.worker); - const otherEp = await proxy[Comlink.createEndpoint](); - const otherProxy = Comlink.wrap(otherEp); - expect(await otherProxy(20, 1)).to.equal(21); + test("can tunnels a new endpoint with createEndpoint", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, worker } = window.testData; + const proxy = Comlink.wrap(worker); + const otherEp = await proxy[Comlink.createEndpoint](); + const otherProxy = Comlink.wrap(otherEp); + return await otherProxy(20, 1); + }); + expect(result).toEqual(21); }); }); From 1b669c1ebc2a8cfba50d5f9d04909fa84e77d1b4 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 15:19:31 -0500 Subject: [PATCH 07/19] Updating two-way iframe test. --- tests/fixtures/two-way-iframe.html | 2 +- tests/two-way-iframe.comlink.test.js | 67 +++++++++++++++++++--------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/tests/fixtures/two-way-iframe.html b/tests/fixtures/two-way-iframe.html index e7528624..e0a6decb 100644 --- a/tests/fixtures/two-way-iframe.html +++ b/tests/fixtures/two-way-iframe.html @@ -1,5 +1,5 @@ diff --git a/tests/iframe.comlink.test.js b/tests/iframe.comlink.test.js index 990131be..93c3e843 100644 --- a/tests/iframe.comlink.test.js +++ b/tests/iframe.comlink.test.js @@ -11,23 +11,47 @@ * limitations under the License. */ -import * as Comlink from "/base/dist/esm/comlink.mjs"; +import { test, expect } from "@playwright/test"; -describe("Comlink across iframes", function () { - beforeEach(function () { - this.ifr = document.createElement("iframe"); - this.ifr.sandbox.add("allow-scripts", "allow-same-origin"); - this.ifr.src = "/base/tests/fixtures/iframe.html"; - document.body.appendChild(this.ifr); - return new Promise((resolve) => (this.ifr.onload = resolve)); +test.describe("Comlink across iframes", () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/empty.html"); + await page.addScriptTag({ + content: ` +import * as Comlink from "./dist/comlink.mjs" +window.testData = { + Comlink, +};`, + type: "module", + }); + await page.evaluate(async () => { + const ifr = document.createElement("iframe"); + window.testData = { + ...window.testData, + ifr, + }; + ifr.sandbox.add("allow-scripts", "allow-same-origin"); + ifr.src = "/iframe.html"; + document.body.appendChild(ifr); + await new Promise((resolve) => (ifr.onload = resolve)); + }); }); - afterEach(function () { - this.ifr.remove(); + test.afterEach(async ({ page }) => { + await page.evaluate(async () => { + const { ifr } = window.testData; + ifr.remove(); + }); }); - it("can communicate", async function () { - const proxy = Comlink.wrap(Comlink.windowEndpoint(this.ifr.contentWindow)); - expect(await proxy(1, 3)).to.equal(4); + test("can communicate", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, ifr } = window.testData; + const proxy = Comlink.wrap( + Comlink.windowEndpoint(ifr.contentWindow) + ); + return await proxy(1, 3); + }); + expect(result).toEqual(4); }); }); From d01d18b79c4c976f9e2288d2759fe14014f5da65 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 15:49:07 -0500 Subject: [PATCH 09/19] Updating cross origin test. --- tests/cross-origin.comlink.test.js | 157 +++++++++++++++++------------ 1 file changed, 95 insertions(+), 62 deletions(-) diff --git a/tests/cross-origin.comlink.test.js b/tests/cross-origin.comlink.test.js index c72a9469..2b14b755 100644 --- a/tests/cross-origin.comlink.test.js +++ b/tests/cross-origin.comlink.test.js @@ -11,73 +11,106 @@ * limitations under the License. */ -import * as Comlink from "/base/dist/esm/comlink.mjs"; +import { test, expect } from "@playwright/test"; -describe("Comlink origin filtering", function () { - it("rejects messages from unknown origin", async function () { - // expose on our window so comlink is listening to window postmessage - const obj = { my: "value" }; - Comlink.expose(obj, self, [/^http:\/\/localhost(:[0-9]+)?\/?$/]); +test.describe("Comlink origin filtering", () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/empty.html"); + await page.addScriptTag({ + content: ` +import * as Comlink from "./dist/comlink.mjs" +window.testData = { + Comlink, +};`, + type: "module", + }); + }); + + test("rejects messages from unknown origin", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink } = window.testData; + + // expose on our window so comlink is listening to window postmessage + const obj = { my: "value" }; + Comlink.expose(obj, self, [/^http:\/\/localhost(:[0-9]+)?\/?$/]); - let handler; - // juggle async timings to get the attack started - const attackComplete = new Promise((resolve, reject) => { - handler = (ev) => { - if (ev.data === "ready" && ev.origin === "null") { - // tell the iframe it can start the attack - ifr.contentWindow.postMessage("start", "*"); - } else if (ev.data === "done") { - // confirm the attack failed, the prototype was not updated - expect(Object.prototype.foo).to.be.undefined; - expect(obj.my).to.equal("value"); - resolve(); - } - }; - window.addEventListener("message", handler); + let handler; + // juggle async timings to get the attack started + const attackComplete = new Promise((resolve, reject) => { + handler = (ev) => { + if (ev.data === "ready" && ev.origin === "null") { + // tell the iframe it can start the attack + ifr.contentWindow.postMessage("start", "*"); + } else if (ev.data === "done") { + // confirm the attack failed, the prototype was not updated + resolve({ + fooUndefined: Object.prototype.foo === undefined, + myValue: obj.my, + }); + } + }; + window.addEventListener("message", handler); + }); + // create a sandboxed iframe for the attack + const ifr = document.createElement("iframe"); + ifr.sandbox.add("allow-scripts"); + ifr.src = "/attack-iframe.html"; + document.body.appendChild(ifr); + // wait for the iframe to load + await new Promise((resolve) => (ifr.onload = resolve)); + // and wait for the attack to complete + const result = await attackComplete; + window.removeEventListener("message", handler); + ifr.remove(); + return result; + }); + expect(result).toEqual({ + fooUndefined: true, + myValue: "value", }); - // create a sandboxed iframe for the attack - const ifr = document.createElement("iframe"); - ifr.sandbox.add("allow-scripts"); - ifr.src = "/base/tests/fixtures/attack-iframe.html"; - document.body.appendChild(ifr); - // wait for the iframe to load - await new Promise((resolve) => (ifr.onload = resolve)); - // and wait for the attack to complete - await attackComplete; - window.removeEventListener("message", handler); - ifr.remove(); }); - it("accepts messages from matching origin", async function () { - // expose on our window so comlink is listening to window postmessage - const obj = { my: "value" }; - Comlink.expose(obj, self, [/^http:\/\/localhost(:[0-9]+)?\/?$/]); - let handler; - // juggle async timings to get the attack started - const attackComplete = new Promise((resolve, reject) => { - handler = (ev) => { - if (ev.data === "ready" && ev.origin === window.origin) { - // tell the iframe it can start the attack - ifr.contentWindow.postMessage("start", "*"); - } else if (ev.data === "done") { - // confirm the attack succeeded, the prototype was updated - expect(Object.prototype.foo).to.equal("x"); - expect(obj.my).to.equal("value"); - resolve(); - } - }; - window.addEventListener("message", handler); + test("accepts messages from matching origin", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink } = window.testData; + + // expose on our window so comlink is listening to window postmessage + const obj = { my: "value" }; + Comlink.expose(obj, self, [/^http:\/\/localhost(:[0-9]+)?\/?$/]); + + let handler; + // juggle async timings to get the attack started + const attackComplete = new Promise((resolve, reject) => { + handler = (ev) => { + if (ev.data === "ready" && ev.origin === window.origin) { + // tell the iframe it can start the attack + ifr.contentWindow.postMessage("start", "*"); + } else if (ev.data === "done") { + // confirm the attack succeeded, the prototype was updated + resolve({ + fooDefined: Object.prototype.foo === "x", + myValue: obj.my, + }); + } + }; + window.addEventListener("message", handler); + }); + // create a sandboxed iframe for the attack, but with same origin + const ifr = document.createElement("iframe"); + ifr.sandbox.add("allow-scripts", "allow-same-origin"); + ifr.src = "/attack-iframe.html"; + document.body.appendChild(ifr); + // wait for the iframe to load + await new Promise((resolve) => (ifr.onload = resolve)); + // and wait for the attack to complete + const result = await attackComplete; + window.removeEventListener("message", handler); + ifr.remove(); + return result; + }); + expect(result).toEqual({ + fooDefined: true, + myValue: "value", }); - // create a sandboxed iframe for the attack, but with same origin - const ifr = document.createElement("iframe"); - ifr.sandbox.add("allow-scripts", "allow-same-origin"); - ifr.src = "/base/tests/fixtures/attack-iframe.html"; - document.body.appendChild(ifr); - // wait for the iframe to load - await new Promise((resolve) => (ifr.onload = resolve)); - // and wait for the attack to complete - await attackComplete; - window.removeEventListener("message", handler); - ifr.remove(); }); }); From 6bc812350a83a83fc095ba4a48fbf5d5fc7bc637 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 16:30:34 -0500 Subject: [PATCH 10/19] Updating same window tests. --- tests/same_window.comlink.test.js | 272 ++++++++++++++++++------------ 1 file changed, 166 insertions(+), 106 deletions(-) diff --git a/tests/same_window.comlink.test.js b/tests/same_window.comlink.test.js index 9fea1292..39294666 100644 --- a/tests/same_window.comlink.test.js +++ b/tests/same_window.comlink.test.js @@ -11,128 +11,187 @@ * limitations under the License. */ -import * as Comlink from "/base/dist/esm/comlink.mjs"; - -class SampleClass { - constructor(counterInit = 1) { - this._counter = counterInit; - this._promise = Promise.resolve(4); - } - - static get SOME_NUMBER() { - return 4; - } - - static ADD(a, b) { - return a + b; - } - - get counter() { - return this._counter; - } - - set counter(value) { - this._counter = value; - } - - get promise() { - return this._promise; - } - - method() { - return 4; - } - - increaseCounter(delta = 1) { - this._counter += delta; - } - - promiseFunc() { - return new Promise((resolve) => setTimeout((_) => resolve(4), 100)); - } - - proxyFunc() { - return Comlink.proxy({ - counter: 0, - inc() { - this.counter++; - }, +import { test, expect } from "@playwright/test"; + +test.describe("Comlink in the same realm", () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/empty.html"); + await page.addScriptTag({ + content: ` +import * as Comlink from "./dist/comlink.mjs" +window.testData = { + Comlink, +};`, + type: "module", }); - } - throwsAnError() { - throw Error("OMG"); - } -} - -describe("Comlink in the same realm", function () { - beforeEach(function () { - const { port1, port2 } = new MessageChannel(); - port1.start(); - port2.start(); - this.port1 = port1; - this.port2 = port2; - }); + await page.waitForFunction(() => { + return window.testData !== undefined; + }); - it("can work with objects", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose({ value: 4 }, this.port2); - expect(await thing.value).to.equal(4); + await page.evaluate(async () => { + const { Comlink } = window.testData; + class SampleClass { + constructor(counterInit = 1) { + this._counter = counterInit; + this._promise = Promise.resolve(4); + } + + static get SOME_NUMBER() { + return 4; + } + + static ADD(a, b) { + return a + b; + } + + get counter() { + return this._counter; + } + + set counter(value) { + this._counter = value; + } + + get promise() { + return this._promise; + } + + method() { + return 4; + } + + increaseCounter(delta = 1) { + this._counter += delta; + } + + promiseFunc() { + return new Promise((resolve) => setTimeout((_) => resolve(4), 100)); + } + + proxyFunc() { + return Comlink.proxy({ + counter: 0, + inc() { + this.counter++; + }, + }); + } + + throwsAnError() { + throw Error("OMG"); + } + } + + const { port1, port2 } = new MessageChannel(); + port1.start(); + port2.start(); + + window.testData = { + Comlink, + SampleClass, + port1, + port2, + }; + }); }); - it("can work with functions on an object", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose({ f: (_) => 4 }, this.port2); - expect(await thing.f()).to.equal(4); + test("can work with objects", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose({ value: 4 }, port2); + return await thing.value; + }); + expect(result).toEqual(4); }); - it("can work with functions", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((_) => 4, this.port2); - expect(await thing()).to.equal(4); + test("can work with functions on an object", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose({ f: (_) => 4 }, port2); + return await thing.f(); + }); + expect(result).toEqual(4); }); - it("can work with objects that have undefined properties", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose({ x: undefined }, this.port2); - expect(await thing.x).to.be.undefined; + test("can work with functions", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((_) => 4, port2); + return await thing(); + }); + expect(result).toEqual(4); }); - it("can keep the stack and message of thrown errors", async function () { - let stack; - const thing = Comlink.wrap(this.port1); - Comlink.expose((_) => { - const error = Error("OMG"); - stack = error.stack; - throw error; - }, this.port2); - try { - await thing(); - throw "Should have thrown"; - } catch (err) { - expect(err).to.not.eq("Should have thrown"); - expect(err.message).to.equal("OMG"); - expect(err.stack).to.equal(stack); - } + test("can work with objects that have undefined properties", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose({ x: undefined }, port2); + return await thing.x; + }); + expect(result).toBeUndefined(); + }); + + test("can keep the stack and message of thrown errors", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + let stack; + const thing = Comlink.wrap(port1); + Comlink.expose((_) => { + const error = Error("OMG"); + stack = error.stack; + throw error; + }, port2); + try { + await thing(); + throw "Should have thrown"; + } catch (err) { + return { + throwExpected: err !== "Should have thrown", + errMessage: err.message, + stackEquals: err.stack === stack, + }; + } + }); + expect(result).toEqual({ + throwExpected: true, + errMessage: "OMG", + stackEquals: true, + }); }); - it("can forward an async function error", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose( - { - async throwError() { - throw new Error("Should have thrown"); + test("can forward an async function error", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose( + { + async throwError() { + throw new Error("Should have thrown"); + }, }, - }, - this.port2 - ); - try { - await thing.throwError(); - } catch (err) { - expect(err.message).to.equal("Should have thrown"); - } + port2 + ); + try { + await thing.throwError(); + } catch (err) { + return { + errMessage: err.message, + }; + } + }); + expect(result).toEqual({ + errMessage: "Should have thrown", + }); }); + /* it("can rethrow non-error objects", async function () { const thing = Comlink.wrap(this.port1); Comlink.expose((_) => { @@ -648,6 +707,7 @@ describe("Comlink in the same realm", function () { expect(err.message).to.equal("Unserializable return value"); } }); + */ }); function guardedIt(f) { From 5c59c4cc77463f6174a72b7b5914cf20af4ce339 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 17:02:45 -0500 Subject: [PATCH 11/19] Adding Playwright test fixture. --- tests/cross-origin.comlink.test.js | 14 +++----------- tests/helpers/testPage.js | 28 ++++++++++++++++++++++++++++ tests/helpers/testPageFixture.js | 12 ++++++++++++ tests/iframe.comlink.test.js | 14 +++----------- tests/same_window.comlink.test.js | 19 +++---------------- tests/two-way-iframe.comlink.test.js | 14 +++----------- tests/worker.comlink.test.js | 20 +++++++++----------- 7 files changed, 61 insertions(+), 60 deletions(-) create mode 100644 tests/helpers/testPage.js create mode 100644 tests/helpers/testPageFixture.js diff --git a/tests/cross-origin.comlink.test.js b/tests/cross-origin.comlink.test.js index 2b14b755..0b378be6 100644 --- a/tests/cross-origin.comlink.test.js +++ b/tests/cross-origin.comlink.test.js @@ -11,19 +11,11 @@ * limitations under the License. */ -import { test, expect } from "@playwright/test"; +import { test, expect } from "./helpers/testPageFixture.js"; test.describe("Comlink origin filtering", () => { - test.beforeEach(async ({ page }) => { - await page.goto("http://localhost:3000/empty.html"); - await page.addScriptTag({ - content: ` -import * as Comlink from "./dist/comlink.mjs" -window.testData = { - Comlink, -};`, - type: "module", - }); + test.beforeEach(async ({ testPage }) => { + await testPage.addComlinkImport(); }); test("rejects messages from unknown origin", async ({ page }) => { diff --git a/tests/helpers/testPage.js b/tests/helpers/testPage.js new file mode 100644 index 00000000..f179aab7 --- /dev/null +++ b/tests/helpers/testPage.js @@ -0,0 +1,28 @@ +export class TestPage { + /** + * @param {import('@playwright/test').Page} page + */ + constructor(page) { + this.page = page; + } + + async goto(name = "empty.html") { + const { page } = this; + await page.goto(`http://localhost:3000/${name}`); + } + + async addComlinkImport() { + const { page } = this; + await page.addScriptTag({ + content: ` +import * as Comlink from "./dist/comlink.mjs" +window.testData = { + Comlink, +};`, + type: "module", + }); + await page.waitForFunction(() => { + return window.testData !== undefined; + }); + } +} diff --git a/tests/helpers/testPageFixture.js b/tests/helpers/testPageFixture.js new file mode 100644 index 00000000..787b2d89 --- /dev/null +++ b/tests/helpers/testPageFixture.js @@ -0,0 +1,12 @@ +import { test as base, expect } from "@playwright/test"; +import { TestPage } from "./testPage.js"; + +const test = base.extend({ + testPage: async ({ page }, use) => { + const testPage = new TestPage(page); + await testPage.goto(); + await use(testPage); + }, +}); + +export { test, expect }; diff --git a/tests/iframe.comlink.test.js b/tests/iframe.comlink.test.js index 93c3e843..c3ade6be 100644 --- a/tests/iframe.comlink.test.js +++ b/tests/iframe.comlink.test.js @@ -11,19 +11,11 @@ * limitations under the License. */ -import { test, expect } from "@playwright/test"; +import { test, expect } from "./helpers/testPageFixture.js"; test.describe("Comlink across iframes", () => { - test.beforeEach(async ({ page }) => { - await page.goto("http://localhost:3000/empty.html"); - await page.addScriptTag({ - content: ` -import * as Comlink from "./dist/comlink.mjs" -window.testData = { - Comlink, -};`, - type: "module", - }); + test.beforeEach(async ({ testPage, page }) => { + await testPage.addComlinkImport(); await page.evaluate(async () => { const ifr = document.createElement("iframe"); window.testData = { diff --git a/tests/same_window.comlink.test.js b/tests/same_window.comlink.test.js index 39294666..2ae55e05 100644 --- a/tests/same_window.comlink.test.js +++ b/tests/same_window.comlink.test.js @@ -11,24 +11,11 @@ * limitations under the License. */ -import { test, expect } from "@playwright/test"; +import { test, expect } from "./helpers/testPageFixture.js"; test.describe("Comlink in the same realm", () => { - test.beforeEach(async ({ page }) => { - await page.goto("http://localhost:3000/empty.html"); - await page.addScriptTag({ - content: ` -import * as Comlink from "./dist/comlink.mjs" -window.testData = { - Comlink, -};`, - type: "module", - }); - - await page.waitForFunction(() => { - return window.testData !== undefined; - }); - + test.beforeEach(async ({ testPage, page }) => { + await testPage.addComlinkImport(); await page.evaluate(async () => { const { Comlink } = window.testData; class SampleClass { diff --git a/tests/two-way-iframe.comlink.test.js b/tests/two-way-iframe.comlink.test.js index cd6d0fa7..971a18a4 100644 --- a/tests/two-way-iframe.comlink.test.js +++ b/tests/two-way-iframe.comlink.test.js @@ -11,19 +11,11 @@ * limitations under the License. */ -import { test, expect } from "@playwright/test"; +import { test, expect } from "./helpers/testPageFixture.js"; test.describe("Comlink across iframes", () => { - test.beforeEach(async ({ page }) => { - await page.goto("http://localhost:3000/empty.html"); - await page.addScriptTag({ - content: ` -import * as Comlink from "./dist/comlink.mjs" -window.testData = { - Comlink, -};`, - type: "module", - }); + test.beforeEach(async ({ testPage, page }) => { + await testPage.addComlinkImport(); await page.evaluate(async () => { const ifr = document.createElement("iframe"); window.testData = { diff --git a/tests/worker.comlink.test.js b/tests/worker.comlink.test.js index 850c97f9..88ac3554 100644 --- a/tests/worker.comlink.test.js +++ b/tests/worker.comlink.test.js @@ -11,19 +11,17 @@ * limitations under the License. */ -import { test, expect } from "@playwright/test"; +import { test, expect } from "./helpers/testPageFixture.js"; test.describe("Comlink across workers", () => { - test.beforeEach(async ({ page }) => { - await page.goto("http://localhost:3000/empty.html"); - await page.addScriptTag({ - content: ` -import * as Comlink from "./dist/comlink.mjs" -window.testData = { - Comlink, - worker: new Worker("./worker.js", { type: 'module' }), -};`, - type: "module", + test.beforeEach(async ({ testPage, page }) => { + await testPage.addComlinkImport(); + await page.evaluate(() => { + const worker = new Worker("./worker.js", { type: "module" }); + window.testData = { + ...window.testData, + worker, + }; }); }); From 3747f77fb4b44ef02a8b03d82d05c3105ba551bd Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 17:11:06 -0500 Subject: [PATCH 12/19] Updating tests. --- tests/same_window.comlink.test.js | 35 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/tests/same_window.comlink.test.js b/tests/same_window.comlink.test.js index 2ae55e05..c5b03785 100644 --- a/tests/same_window.comlink.test.js +++ b/tests/same_window.comlink.test.js @@ -178,21 +178,30 @@ test.describe("Comlink in the same realm", () => { }); }); - /* - it("can rethrow non-error objects", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((_) => { - throw { test: true }; - }, this.port2); - try { - await thing(); - throw "Should have thrown"; - } catch (err) { - expect(err).to.not.equal("Should have thrown"); - expect(err.test).to.equal(true); - } + test("can rethrow non-error objects", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((_) => { + throw { test: true }; + }, port2); + try { + await thing(); + throw "Should have thrown"; + } catch (err) { + return { + throwExpected: err !== "Should have thrown", + errTest: err.test, + }; + } + }); + expect(result).toEqual({ + throwExpected: true, + errTest: true, + }); }); + /* it("can rethrow scalars", async function () { const thing = Comlink.wrap(this.port1); Comlink.expose((_) => { From c4d0f5eeca186a4a876231b09351f6fc1e3affce Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 17:14:49 -0500 Subject: [PATCH 13/19] Adding branch. --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5156520e..aa20c41a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,9 +1,9 @@ name: Playwright Tests on: push: - branches: [ main ] + branches: [ main, playwright-tests ] pull_request: - branches: [ main ] + branches: [ main, playwright-tests ] jobs: test: timeout-minutes: 60 From 9f88125efcccff07a38627accfaa1105a668f1fb Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 17:17:37 -0500 Subject: [PATCH 14/19] Adding package lock. --- .gitignore | 2 +- package-lock.json | 1488 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1489 insertions(+), 1 deletion(-) create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index 7e870ea4..0d8032e7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ node_modules *.bak dist .*_cache* -package-lock.json + /test-results/ /playwright-report/ /blob-report/ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..30162bca --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1488 @@ +{ + "name": "comlink", + "version": "4.4.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "comlink", + "version": "4.4.1", + "license": "Apache-2.0", + "devDependencies": { + "@playwright/test": "^1.40.1", + "@rollup/plugin-terser": "0.4.0", + "@rollup/plugin-typescript": "11.0.0", + "@types/node": "^20.10.5", + "chai": "^4.3.7", + "conditional-type-checks": "1.0.6", + "husky": "8.0.3", + "mocha": "10.2.0", + "prettier": "2.8.3", + "rimraf": "4.1.2", + "rollup": "3.10.1", + "tslib": "2.4.1", + "typescript": "4.9.4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@playwright/test": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "dev": true, + "dependencies": { + "playwright": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.0.tgz", + "integrity": "sha512-Ipcf3LPNerey1q9ZMjiaWHlNPEHNU/B5/uh9zXLltfEQ1lVSLLeZSgAtTPWGyw8Ip1guOeq+mDtdOlEj/wNxQw==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.0", + "smob": "^0.0.6", + "terser": "^5.15.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.x || ^3.x" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.0.0.tgz", + "integrity": "sha512-goPyCWBiimk1iJgSTgsehFD5OOFHiAknrRJjqFCudcW8JtWiBlK284Xnn4flqMqg6YAjVG/EE+3aVzrL5qNSzQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", + "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", + "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/conditional-type-checks": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/conditional-type-checks/-/conditional-type-checks-1.0.6.tgz", + "integrity": "sha512-3vyi+yNcmKq+xl1sTX7Ta+4pUvjusMYbC6FSbrS6YJV8TI51wiRn24u4bfdFVhDKKH5GtpKQzxW7bqXbPWllgQ==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/mocha/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "dev": true, + "dependencies": { + "playwright-core": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/prettier": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rimraf": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz", + "integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==", + "deprecated": "Please upgrade to 4.3.1 or higher to fix a potentially damaging issue regarding symbolic link following. See https://github.com/isaacs/rimraf/issues/259 for details.", + "dev": true, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.10.1.tgz", + "integrity": "sha512-3Er+yel3bZbZX1g2kjVM+FW+RUWDxbG87fcqFM5/9HbPCTpbVp6JOLn7jlxnNlbu7s/N/uDA4EV/91E2gWnxzw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/smob": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz", + "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz", + "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} From 236c53212b84d2fa603cc651cc738aa945414dc6 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 17:39:58 -0500 Subject: [PATCH 15/19] Updating build. --- .github/workflows/playwright.yml | 42 ++++++++++++++++++-------------- package.json | 1 + playwright.config.ts | 14 +++++------ tests/helpers/testPage.js | 2 +- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index aa20c41a..f8af52b8 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,27 +1,33 @@ name: Playwright Tests on: push: - branches: [ main, playwright-tests ] + branches: [main, playwright-tests] pull_request: - branches: [ main, playwright-tests ] + branches: [main, playwright-tests] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install dependencies - run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Run Playwright tests - run: npx playwright test - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Build Comlink + run: npm run build + - name: Run types test + run: npm run test:types + - name: Run Playwright (unit) tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + - name: Run Node tests + run: npm run test:node diff --git a/package.json b/package.json index 7ce8ca7c..fc5eeb09 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "sideEffects": false, "scripts": { "build": "rollup -c", + "test-server": "node ./test-server.mjs", "test:unit": "playwright test", "test:node": "mocha ./tests/node/main.mjs", "test:types": "tsc -p ./tests/tsconfig.json", diff --git a/playwright.config.ts b/playwright.config.ts index 34500269..e1790f48 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -24,7 +24,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -68,10 +68,10 @@ export default defineConfig({ // }, ], - // /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'node test-server.mjs', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: true, - // }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run test-server', + port: 3000, + reuseExistingServer: !process.env.CI, + }, }); diff --git a/tests/helpers/testPage.js b/tests/helpers/testPage.js index f179aab7..0a075ca2 100644 --- a/tests/helpers/testPage.js +++ b/tests/helpers/testPage.js @@ -8,7 +8,7 @@ export class TestPage { async goto(name = "empty.html") { const { page } = this; - await page.goto(`http://localhost:3000/${name}`); + await page.goto(`/${name}`); } async addComlinkImport() { From d18ba660d6e69b5d3512c3e94bf37b7bd1605553 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Mon, 18 Dec 2023 20:43:04 -0500 Subject: [PATCH 16/19] Updating tests. --- tests/same_window.comlink.test.js | 928 +++++++++++++++++++----------- 1 file changed, 577 insertions(+), 351 deletions(-) diff --git a/tests/same_window.comlink.test.js b/tests/same_window.comlink.test.js index c5b03785..5d8cfed2 100644 --- a/tests/same_window.comlink.test.js +++ b/tests/same_window.comlink.test.js @@ -201,181 +201,296 @@ test.describe("Comlink in the same realm", () => { }); }); - /* - it("can rethrow scalars", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((_) => { - throw "oops"; - }, this.port2); - try { - await thing(); - throw "Should have thrown"; - } catch (err) { - expect(err).to.not.equal("Should have thrown"); - expect(err).to.equal("oops"); - expect(typeof err).to.equal("string"); - } + test("can rethrow scalars", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((_) => { + throw "oops"; + }, port2); + try { + await thing(); + throw "Should have thrown"; + } catch (err) { + return { + throwExpected: err !== "Should have thrown", + errMessage: err, + errType: typeof err, + }; + } + }); + expect(result).toEqual({ + throwExpected: true, + errMessage: "oops", + errType: "string", + }); }); - it("can rethrow null", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((_) => { - throw null; - }, this.port2); - try { - await thing(); - throw "Should have thrown"; - } catch (err) { - expect(err).to.not.equal("Should have thrown"); - expect(err).to.equal(null); - expect(typeof err).to.equal("object"); - } + test("can rethrow null", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((_) => { + throw null; + }, port2); + try { + await thing(); + throw "Should have thrown"; + } catch (err) { + return { + throwExpected: err !== "Should have thrown", + errMessageIsNull: err === null, + errType: typeof err, + }; + } + }); + expect(result).toEqual({ + throwExpected: true, + errMessageIsNull: true, + errType: "object", + }); }); - it("can work with parameterized functions", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((a, b) => a + b, this.port2); - expect(await thing(1, 3)).to.equal(4); + test("can work with parameterized functions", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((a, b) => a + b, port2); + return await thing(1, 3); + }); + expect(result).toEqual(4); }); - it("can work with functions that return promises", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose( - (_) => new Promise((resolve) => setTimeout((_) => resolve(4), 100)), - this.port2 - ); - expect(await thing()).to.equal(4); + test("can work with functions that return promises", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose( + (_) => new Promise((resolve) => setTimeout((_) => resolve(4), 100)), + port2 + ); + return await thing(); + }); + expect(result).toEqual(4); }); - it("can work with classes", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance.method()).to.equal(4); + test("can work with classes", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + return await instance.method(); + }); + expect(result).toEqual(4); }); - it("can pass parameters to class constructor", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(23); - expect(await instance.counter).to.equal(23); + test("can pass parameters to class constructor", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(23); + return await instance.counter; + }); + expect(result).toEqual(23); }); - it("can access a class in an object", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose({ SampleClass }, this.port2); - const instance = await new thing.SampleClass(); - expect(await instance.method()).to.equal(4); + test("can access a class in an object", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose({ SampleClass }, port2); + const instance = await new thing.SampleClass(); + return await instance.method(); + }); + expect(result).toEqual(4); }); - it("can work with class instance properties", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance._counter).to.equal(1); + test("can work with class instance properties", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + return await instance._counter; + }); + expect(result).toEqual(1); }); - it("can set class instance properties", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance._counter).to.equal(1); - await (instance._counter = 4); - expect(await instance._counter).to.equal(4); + test("can set class instance properties", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + const v1 = await instance._counter; + await (instance._counter = 4); + const v2 = await instance._counter; + return { v1, v2 }; + }); + expect(result).toEqual({ + v1: 1, + v2: 4, + }); }); - it("can work with class instance methods", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance.counter).to.equal(1); - await instance.increaseCounter(); - expect(await instance.counter).to.equal(2); + test("can work with class instance methods", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + const v1 = await instance.counter; + await instance.increaseCounter(); + const v2 = await instance.counter; + return { v1, v2 }; + }); + expect(result).toEqual({ + v1: 1, + v2: 2, + }); }); - it("can handle throwing class instance methods", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - return instance - .throwsAnError() - .then((_) => Promise.reject()) - .catch((err) => {}); + test("can handle throwing class instance methods", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + return await instance + .throwsAnError() + .then((_) => Promise.reject()) + .catch((err) => {}); + }); + expect(result).toBeUndefined(); }); - it("can work with class instance methods multiple times", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance.counter).to.equal(1); - await instance.increaseCounter(); - await instance.increaseCounter(5); - expect(await instance.counter).to.equal(7); + test("can work with class instance methods multiple times", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + const v1 = await instance.counter; + await instance.increaseCounter(); + await instance.increaseCounter(5); + const v2 = await instance.counter; + return { v1, v2 }; + }); + expect(result).toEqual({ + v1: 1, + v2: 7, + }); }); - it("can work with class instance methods that return promises", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance.promiseFunc()).to.equal(4); + test("can work with class instance methods that return promises", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + return await instance.promiseFunc(); + }); + expect(result).toEqual(4); }); - it("can work with class instance properties that are promises", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance._promise).to.equal(4); + test("can work with class instance properties that are promises", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + return await instance._promise; + }); + expect(result).toEqual(4); }); - it("can work with class instance getters that are promises", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance.promise).to.equal(4); + test("can work with class instance getters that are promises", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + return await instance.promise; + }); + expect(result).toEqual(4); }); - it("can work with static class properties", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - expect(await thing.SOME_NUMBER).to.equal(4); + test("can work with static class properties", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + return await thing.SOME_NUMBER; + }); + expect(result).toEqual(4); }); - it("can work with static class methods", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - expect(await thing.ADD(1, 3)).to.equal(4); + test("can work with static class methods", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + return await thing.ADD(1, 3); + }); + expect(result).toEqual(4); }); - it("can work with bound class instance methods", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance.counter).to.equal(1); - const method = instance.increaseCounter.bind(instance); - await method(); - expect(await instance.counter).to.equal(2); + test("can work with bound class instance methods", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + const v1 = await instance.counter; + const method = instance.increaseCounter.bind(instance); + await method(); + const v2 = await instance.counter; + return { v1, v2 }; + }); + expect(result).toEqual({ v1: 1, v2: 2 }); }); - it("can work with class instance getters", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance.counter).to.equal(1); - await instance.increaseCounter(); - expect(await instance.counter).to.equal(2); + test("can work with class instance getters", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + const v1 = await instance.counter; + await instance.increaseCounter(); + const v2 = await instance.counter; + return { v1, v2 }; + }); + expect(result).toEqual({ v1: 1, v2: 2 }); }); - it("can work with class instance setters", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - expect(await instance._counter).to.equal(1); - await (instance.counter = 4); - expect(await instance._counter).to.equal(4); + test("can work with class instance setters", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + const v1 = await instance._counter; + await (instance.counter = 4); + const v2 = await instance._counter; + return { v1, v2 }; + }); + expect(result).toEqual({ v1: 1, v2: 4 }); }); + /* const hasBroadcastChannel = (_) => "BroadcastChannel" in self; - guardedIt(hasBroadcastChannel)( + guardedtest(hasBroadcastChannel)( "will work with BroadcastChannel", async function () { const b1 = new BroadcastChannel("comlink_bc_test"); @@ -389,17 +504,17 @@ test.describe("Comlink in the same realm", () => { // Buffer transfers seem to have regressed in Safari 11.1, it’s fixed in 11.2. const isNotSafari11_1 = (_) => !/11\.1(\.[0-9]+)? Safari/.test(navigator.userAgent); - guardedIt(isNotSafari11_1)("will transfer buffers", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((b) => b.byteLength, this.port2); + guardedtest(isNotSafari11_1)("will transfer buffers", async function () { + const thing = Comlink.wrap(port1); + Comlink.expose((b) => b.byteLength, port2); const buffer = new Uint8Array([1, 2, 3]).buffer; expect(await thing(Comlink.transfer(buffer, [buffer]))).to.equal(3); expect(buffer.byteLength).to.equal(0); }); - guardedIt(isNotSafari11_1)("will copy TypedArrays", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((b) => b, this.port2); + guardedtest(isNotSafari11_1)("will copy TypedArrays", async function () { + const thing = Comlink.wrap(port1); + Comlink.expose((b) => b, port2); const array = new Uint8Array([1, 2, 3]); const receive = await thing(array); expect(array).to.not.equal(receive); @@ -407,9 +522,9 @@ test.describe("Comlink in the same realm", () => { expect([...array]).to.deep.equal([...receive]); }); - guardedIt(isNotSafari11_1)("will copy nested TypedArrays", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((b) => b, this.port2); + guardedtest(isNotSafari11_1)("will copy nested TypedArrays", async function () { + const thing = Comlink.wrap(port1); + Comlink.expose((b) => b, port2); const array = new Uint8Array([1, 2, 3]); const receive = await thing({ v: 1, @@ -420,11 +535,11 @@ test.describe("Comlink in the same realm", () => { expect([...array]).to.deep.equal([...receive.array]); }); - guardedIt(isNotSafari11_1)( + guardedtest(isNotSafari11_1)( "will transfer deeply nested buffers", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((a) => a.b.c.d.byteLength, this.port2); + const thing = Comlink.wrap(port1); + Comlink.expose((a) => a.b.c.d.byteLength, port2); const buffer = new Uint8Array([1, 2, 3]).buffer; expect( await thing(Comlink.transfer({ b: { c: { d: buffer } } }, [buffer])) @@ -432,231 +547,327 @@ test.describe("Comlink in the same realm", () => { expect(buffer.byteLength).to.equal(0); } ); +*/ + + test("will transfer a message port", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1: firstPort1, port2: firstPort2 } = window.testData; + const thing = Comlink.wrap(firstPort1); + Comlink.expose((a) => a.postMessage("ohai"), firstPort2); + const { port1: secondPort1, port2: secondPort2 } = new MessageChannel(); + await thing(Comlink.transfer(secondPort2, [secondPort2])); + return new Promise((resolve) => { + secondPort1.onmessage = (event) => { + resolve({ + data: "ohai", + }); + }; + }); + }); + expect(result).toEqual({ + data: "ohai", + }); + }); + + test("will wrap marked return values", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose( + (_) => + Comlink.proxy({ + counter: 0, + inc() { + this.counter += 1; + }, + }), + port2 + ); + const obj = await thing(); + const v1 = await obj.counter; + await obj.inc(); + const v2 = await obj.counter; + return { v1, v2 }; + }); + expect(result).toEqual({ v1: 0, v2: 1 }); + }); - it("will transfer a message port", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose((a) => a.postMessage("ohai"), this.port2); - const { port1, port2 } = new MessageChannel(); - await thing(Comlink.transfer(port2, [port2])); - return new Promise((resolve) => { - port1.onmessage = (event) => { - expect(event.data).to.equal("ohai"); - resolve(); + test("will wrap marked return values from class instance methods", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + const obj = await instance.proxyFunc(); + const v1 = await obj.counter; + await obj.inc(); + const v2 = await obj.counter; + return { v1, v2 }; + }); + expect(result).toEqual({ v1: 0, v2: 1 }); + }); + + test("will wrap marked parameter values", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + const local = { + counter: 0, + inc() { + this.counter++; + }, }; + Comlink.expose(async function (f) { + await f.inc(); + }, port2); + const v1 = local.counter; + await thing(Comlink.proxy(local)); + const v2 = await local.counter; + return { v1, v2 }; }); + expect(result).toEqual({ v1: 0, v2: 1 }); }); - it("will wrap marked return values", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose( - (_) => - Comlink.proxy({ - counter: 0, - inc() { - this.counter += 1; + test("will wrap marked assignments", async ({ page }) => { + const result = await page.evaluate(async () => { + return new Promise((resolve) => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + const obj = { + onready: null, + call() { + this.onready(); }, - }), - this.port2 - ); - const obj = await thing(); - expect(await obj.counter).to.equal(0); - await obj.inc(); - expect(await obj.counter).to.equal(1); - }); - - it("will wrap marked return values from class instance methods", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - const obj = await instance.proxyFunc(); - expect(await obj.counter).to.equal(0); - await obj.inc(); - expect(await obj.counter).to.equal(1); - }); - - it("will wrap marked parameter values", async function () { - const thing = Comlink.wrap(this.port1); - const local = { - counter: 0, - inc() { - this.counter++; - }, - }; - Comlink.expose(async function (f) { - await f.inc(); - }, this.port2); - expect(local.counter).to.equal(0); - await thing(Comlink.proxy(local)); - expect(await local.counter).to.equal(1); - }); - - it("will wrap marked assignments", function (done) { - const thing = Comlink.wrap(this.port1); - const obj = { - onready: null, - call() { - this.onready(); - }, - }; - Comlink.expose(obj, this.port2); + }; + Comlink.expose(obj, port2); - thing.onready = Comlink.proxy(() => done()); - thing.call(); + thing.onready = Comlink.proxy(() => resolve({ done: true })); + thing.call(); + }); + }); + expect(result).toEqual({ done: true }); }); - it("will wrap marked parameter values, simple function", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(async function (f) { - await f(); - }, this.port2); - // Weird code because Mocha - await new Promise(async (resolve) => { - thing(Comlink.proxy((_) => resolve())); + test("will wrap marked parameter values, simple function", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(async function (f) { + await f(); + }, port2); + return new Promise(async (resolve) => { + thing(Comlink.proxy((_) => resolve({ done: true }))); + }); }); + expect(result).toEqual({ done: true }); }); - it("will wrap multiple marked parameter values, simple function", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(async function (f1, f2, f3) { - return (await f1()) + (await f2()) + (await f3()); - }, this.port2); - // Weird code because Mocha - expect( - await thing( + test("will wrap multiple marked parameter values, simple function", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(async function (f1, f2, f3) { + return (await f1()) + (await f2()) + (await f3()); + }, port2); + return await thing( Comlink.proxy((_) => 1), Comlink.proxy((_) => 2), Comlink.proxy((_) => 3) - ) - ).to.equal(6); + ); + }); + expect(result).toEqual(6); }); - it("will proxy deeply nested values", async function () { - const thing = Comlink.wrap(this.port1); - const obj = { - a: { - v: 4, - }, - b: Comlink.proxy({ - v: 5, - }), - }; - Comlink.expose(obj, this.port2); - - const a = await thing.a; - const b = await thing.b; - expect(await a.v).to.equal(4); - expect(await b.v).to.equal(5); - await (a.v = 8); - await (b.v = 9); - // Workaround for a weird scheduling inconsistency in Firefox. - // This test failed, but not when run in isolation, and only - // in Firefox. I think there might be problem with task ordering. - await new Promise((resolve) => setTimeout(resolve, 1)); - expect(await thing.a.v).to.equal(4); - expect(await thing.b.v).to.equal(9); + test("will proxy deeply nested values", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + const obj = { + a: { + v: 4, + }, + b: Comlink.proxy({ + v: 5, + }), + }; + Comlink.expose(obj, port2); + + const a = await thing.a; + const b = await thing.b; + const result = []; + result.push(await a.v); + result.push(await b.v); + await (a.v = 8); + await (b.v = 9); + // Workaround for a weird scheduling inconsistency in Firefox. + // This test failed, but not when run in isolation, and only + // in Firefox. I think there might be problem with task ordering. + await new Promise((resolve) => setTimeout(resolve, 1)); + result.push(await thing.a.v); + result.push(await thing.b.v); + return result; + }); + expect(result).toEqual([4, 5, 4, 9]); }); - it("will handle undefined parameters", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose({ f: (_) => 4 }, this.port2); - expect(await thing.f(undefined)).to.equal(4); + test("will handle undefined parameters", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose({ f: (_) => 4 }, port2); + return await thing.f(undefined); + }); + expect(result).toEqual(4); }); - it("can handle destructuring", async function () { - Comlink.expose( - { - a: 4, - get b() { - return 5; - }, - c() { - return 6; + test("can handle destructuring", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + Comlink.expose( + { + a: 4, + get b() { + return 5; + }, + c() { + return 6; + }, }, - }, - this.port2 - ); - const { a, b, c } = Comlink.wrap(this.port1); - expect(await a).to.equal(4); - expect(await b).to.equal(5); - expect(await c()).to.equal(6); - }); - - it("lets users define transfer handlers", function (done) { - Comlink.transferHandlers.set("event", { - canHandle(obj) { - return obj instanceof Event; - }, - serialize(obj) { - return [obj.data, []]; - }, - deserialize(data) { - return new MessageEvent("message", { data }); - }, + port2 + ); + const { a, b, c } = Comlink.wrap(port1); + return [await a, await b, await c()]; }); + expect(result).toEqual([4, 5, 6]); + }); - Comlink.expose((ev) => { - expect(ev).to.be.an.instanceOf(Event); - expect(ev.data).to.deep.equal({ a: 1 }); - done(); - }, this.port1); - const thing = Comlink.wrap(this.port2); + test("lets users define transfer handlers", async ({ page }) => { + const result = await page.evaluate(async () => { + return new Promise(function (resolve) { + const { + Comlink, + port1: firstPort1, + port2: firstPort2, + } = window.testData; + Comlink.transferHandlers.set("event", { + canHandle(obj) { + return obj instanceof Event; + }, + serialize(obj) { + return [obj.data, []]; + }, + deserialize(data) { + return new MessageEvent("message", { data }); + }, + }); + + Comlink.expose((ev) => { + // expect(ev).to.be.an.instanceOf(Event); + // expect(ev.data).to.deep.equal({ a: 1 }); + resolve({ + isInstanceOfEvent: ev instanceof Event, + data: ev.data, + }); + }, firstPort1); + const thing = Comlink.wrap(firstPort2); - const { port1, port2 } = new MessageChannel(); - port1.addEventListener("message", thing.bind(this)); - port1.start(); - port2.postMessage({ a: 1 }); + const { port1: secondPort1, port2: secondPort2 } = new MessageChannel(); + secondPort1.addEventListener("message", thing.bind(this)); + secondPort1.start(); + secondPort2.postMessage({ a: 1 }); + }); + }); + expect(result).toEqual({ + isInstanceOfEvent: true, + data: { a: 1 }, + }); }); - it("can tunnels a new endpoint with createEndpoint", async function () { - Comlink.expose( - { - a: 4, - c() { - return 5; + test("can tunnels a new endpoint with createEndpoint", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + Comlink.expose( + { + a: 4, + c() { + return 5; + }, }, - }, - this.port2 - ); - const proxy = Comlink.wrap(this.port1); - const otherEp = await proxy[Comlink.createEndpoint](); - const otherProxy = Comlink.wrap(otherEp); - expect(await otherProxy.a).to.equal(4); - expect(await proxy.a).to.equal(4); - expect(await otherProxy.c()).to.equal(5); - expect(await proxy.c()).to.equal(5); + port2 + ); + const proxy = Comlink.wrap(port1); + const otherEp = await proxy[Comlink.createEndpoint](); + const otherProxy = Comlink.wrap(otherEp); + return [ + await otherProxy.a, + await proxy.a, + await otherProxy.c(), + await proxy.c(), + ]; + }); + expect(result).toEqual([4, 4, 5, 5]); }); - it("released proxy should no longer be useable and throw an exception", async function () { - const thing = Comlink.wrap(this.port1); - Comlink.expose(SampleClass, this.port2); - const instance = await new thing(); - await instance[Comlink.releaseProxy](); - expect(() => instance.method()).to.throw(); + test("released proxy should no longer be useable and throw an exception", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { Comlink, SampleClass, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose(SampleClass, port2); + const instance = await new thing(); + await instance[Comlink.releaseProxy](); + try { + instance.method(); + } catch { + return { threwError: true }; + } + }); + expect(result).toEqual({ threwError: true }); }); - it("released proxy should invoke finalizer", async function () { - let finalized = false; - Comlink.expose( - { - a: "thing", - [Comlink.finalizer]: () => { - finalized = true; + test("released proxy should invoke finalizer", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + let finalized = false; + Comlink.expose( + { + a: "thing", + [Comlink.finalizer]: () => { + finalized = true; + }, }, - }, - this.port2 - ); - const instance = Comlink.wrap(this.port1); - expect(await instance.a).to.equal("thing"); - await instance[Comlink.releaseProxy](); - // wait a beat to let the events process - await new Promise((resolve) => setTimeout(resolve, 1)); - expect(finalized).to.be.true; + port2 + ); + const instance = Comlink.wrap(port1); + const value = await instance.a; + await instance[Comlink.releaseProxy](); + // wait a beat to let the events process + await new Promise((resolve) => setTimeout(resolve, 1)); + return { + value, + finalized, + }; + }); + expect(result).toEqual({ + value: "thing", + finalized: true, + }); }); + /* // commented out this test as it could be unreliable in various browsers as // it has to wait for GC to kick in which could happen at any timing // this does seem to work when testing locally - it.skip("released proxy via GC should invoke finalizer", async function () { + it.skip("released proxy via GC should invoke finalizer", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; let finalized = false; Comlink.expose( { @@ -665,7 +876,7 @@ test.describe("Comlink in the same realm", () => { finalized = true; }, }, - this.port2 + port2 ); let registry; @@ -678,34 +889,49 @@ test.describe("Comlink in the same realm", () => { heldValue(); }); - const instance = Comlink.wrap(this.port1); + const instance = Comlink.wrap(port1); registry.register(instance, resolve); expect(await instance.a).to.equal("thing"); }); // wait a beat to let the events process await new Promise((resolve) => setTimeout(resolve, 1)); expect(finalized).to.be.true; + }); + expect(result).toEqual({ + }); }); +*/ - it("can proxy with a given target", async function () { - const thing = Comlink.wrap(this.port1, { value: {} }); - Comlink.expose({ value: 4 }, this.port2); - expect(await thing.value).to.equal(4); + test("can proxy with a given target", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1, { value: {} }); + Comlink.expose({ value: 4 }, port2); + return await thing.value; + }); + expect(result).toEqual(4); }); - it("can handle unserializable types", async function () { - const thing = Comlink.wrap(this.port1, { value: {} }); - Comlink.expose({ value: () => "boom" }, this.port2); + test("can handle unserializable types", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1, { value: {} }); + Comlink.expose({ value: () => "boom" }, port2); - try { - await thing.value; - } catch (err) { - expect(err.message).to.equal("Unserializable return value"); - } + try { + await thing.value; + } catch (err) { + return { + errMessage: err.message, + }; + } + }); + expect(result).toEqual({ + errMessage: "Unserializable return value", + }); }); - */ }); -function guardedIt(f) { - return f() ? it : xit; -} +// function guardedtest(f) { +// return f() ? it : xit; +// } From 240d1aa4aaf2672e3f5e6251145ccfe0063dce12 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Tue, 26 Dec 2023 12:14:40 -0500 Subject: [PATCH 17/19] Updating guarded tests. --- tests/same_window.comlink.test.js | 137 +++++++++++++++++------------- 1 file changed, 80 insertions(+), 57 deletions(-) diff --git a/tests/same_window.comlink.test.js b/tests/same_window.comlink.test.js index 5d8cfed2..7da23329 100644 --- a/tests/same_window.comlink.test.js +++ b/tests/same_window.comlink.test.js @@ -488,66 +488,93 @@ test.describe("Comlink in the same realm", () => { expect(result).toEqual({ v1: 1, v2: 4 }); }); - /* - const hasBroadcastChannel = (_) => "BroadcastChannel" in self; - guardedtest(hasBroadcastChannel)( - "will work with BroadcastChannel", - async function () { + test("will work with BroadcastChannel", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink } = window.testData; const b1 = new BroadcastChannel("comlink_bc_test"); const b2 = new BroadcastChannel("comlink_bc_test"); const thing = Comlink.wrap(b1); Comlink.expose((b) => 40 + b, b2); - expect(await thing(2)).to.equal(42); - } - ); - - // Buffer transfers seem to have regressed in Safari 11.1, it’s fixed in 11.2. - const isNotSafari11_1 = (_) => - !/11\.1(\.[0-9]+)? Safari/.test(navigator.userAgent); - guardedtest(isNotSafari11_1)("will transfer buffers", async function () { - const thing = Comlink.wrap(port1); - Comlink.expose((b) => b.byteLength, port2); - const buffer = new Uint8Array([1, 2, 3]).buffer; - expect(await thing(Comlink.transfer(buffer, [buffer]))).to.equal(3); - expect(buffer.byteLength).to.equal(0); - }); - - guardedtest(isNotSafari11_1)("will copy TypedArrays", async function () { - const thing = Comlink.wrap(port1); - Comlink.expose((b) => b, port2); - const array = new Uint8Array([1, 2, 3]); - const receive = await thing(array); - expect(array).to.not.equal(receive); - expect(array.byteLength).to.equal(receive.byteLength); - expect([...array]).to.deep.equal([...receive]); - }); - - guardedtest(isNotSafari11_1)("will copy nested TypedArrays", async function () { - const thing = Comlink.wrap(port1); - Comlink.expose((b) => b, port2); - const array = new Uint8Array([1, 2, 3]); - const receive = await thing({ - v: 1, - array, - }); - expect(array).to.not.equal(receive.array); - expect(array.byteLength).to.equal(receive.array.byteLength); - expect([...array]).to.deep.equal([...receive.array]); - }); - - guardedtest(isNotSafari11_1)( - "will transfer deeply nested buffers", - async function () { + return await thing(2); + }); + expect(result).toEqual(42); + }); + + test("will transfer buffers", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((b) => b.byteLength, port2); + const buffer = new Uint8Array([1, 2, 3]).buffer; + const result = await thing(Comlink.transfer(buffer, [buffer])); + const byteLength = buffer.byteLength; + return { result, byteLength }; + }); + expect(result).toEqual({ result: 3, byteLength: 0 }); + }); + + test("will copy TypedArrays", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((b) => b, port2); + const array = new Uint8Array([1, 2, 3]); + const receive = await thing(array); + const sameArray = array === receive; + const sameByteLength = array.byteLength === receive.byteLength; + const array1 = [...array]; + const array2 = [...receive]; + return { sameArray, sameByteLength, array1, array2 }; + }); + expect(result).toEqual({ + sameArray: false, + sameByteLength: true, + array1: [1, 2, 3], + array2: [1, 2, 3], + }); + }); + + test("will copy nested TypedArrays", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; + const thing = Comlink.wrap(port1); + Comlink.expose((b) => b, port2); + const array = new Uint8Array([1, 2, 3]); + const receive = await thing({ + v: 1, + array, + }); + const sameArray = array === receive.array; + const sameByteLength = array.byteLength === receive.array.byteLength; + const array1 = [...array]; + const array2 = [...receive.array]; + return { sameArray, sameByteLength, array1, array2 }; + }); + expect(result).toEqual({ + sameArray: false, + sameByteLength: true, + array1: [1, 2, 3], + array2: [1, 2, 3], + }); + }); + + test("will transfer deeply nested buffers", async ({ page }) => { + const result = await page.evaluate(async () => { + const { Comlink, port1, port2 } = window.testData; const thing = Comlink.wrap(port1); Comlink.expose((a) => a.b.c.d.byteLength, port2); const buffer = new Uint8Array([1, 2, 3]).buffer; - expect( - await thing(Comlink.transfer({ b: { c: { d: buffer } } }, [buffer])) - ).to.equal(3); - expect(buffer.byteLength).to.equal(0); - } - ); -*/ + const result = await thing( + Comlink.transfer({ b: { c: { d: buffer } } }, [buffer]) + ); + const byteLength = buffer.byteLength; + return { result, byteLength }; + }); + expect(result).toEqual({ + result: 3, + byteLength: 0, + }); + }); test("will transfer a message port", async ({ page }) => { const result = await page.evaluate(async () => { @@ -931,7 +958,3 @@ test.describe("Comlink in the same realm", () => { }); }); }); - -// function guardedtest(f) { -// return f() ? it : xit; -// } From 66fe890460b922e094bfb917dbf1a0bce990858d Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Tue, 26 Dec 2023 12:35:33 -0500 Subject: [PATCH 18/19] Updating GC finalizer test. --- tests/same_window.comlink.test.js | 60 ++++++++++++++++--------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/tests/same_window.comlink.test.js b/tests/same_window.comlink.test.js index 7da23329..dd8834be 100644 --- a/tests/same_window.comlink.test.js +++ b/tests/same_window.comlink.test.js @@ -888,46 +888,48 @@ test.describe("Comlink in the same realm", () => { }); }); - /* // commented out this test as it could be unreliable in various browsers as // it has to wait for GC to kick in which could happen at any timing // this does seem to work when testing locally - it.skip("released proxy via GC should invoke finalizer", async ({ page }) => { + test.skip("released proxy via GC should invoke finalizer", async ({ + page, + }) => { + test.slow(); const result = await page.evaluate(async () => { const { Comlink, port1, port2 } = window.testData; - let finalized = false; - Comlink.expose( - { - a: "thing", - [Comlink.finalizer]: () => { - finalized = true; + let finalized = false; + Comlink.expose( + { + a: "thing", + [Comlink.finalizer]: () => { + finalized = true; + }, }, - }, - port2 - ); - - let registry; - - // set a long enough timeout to wait for a garbage collection - this.timeout(10000); - // promise will resolve when the proxy is garbage collected - await new Promise(async (resolve, reject) => { - registry = new FinalizationRegistry((heldValue) => { - heldValue(); - }); + port2 + ); - const instance = Comlink.wrap(port1); - registry.register(instance, resolve); - expect(await instance.a).to.equal("thing"); - }); - // wait a beat to let the events process - await new Promise((resolve) => setTimeout(resolve, 1)); - expect(finalized).to.be.true; + let registry; + let value; + + // promise will resolve when the proxy is garbage collected + await new Promise(async (resolve, reject) => { + registry = new FinalizationRegistry((heldValue) => { + heldValue(); + }); + + const instance = Comlink.wrap(port1); + registry.register(instance, resolve); + value = await instance.a; + }); + // wait a beat to let the events process + await new Promise((resolve) => setTimeout(resolve, 1)); + return { value, finalized }; }); expect(result).toEqual({ + value: "thing", + finalized: true, }); }); -*/ test("can proxy with a given target", async ({ page }) => { const result = await page.evaluate(async () => { From 6da6a76413fb991eb43114369b2ae0344661a469 Mon Sep 17 00:00:00 2001 From: Dave Baskin Date: Tue, 26 Dec 2023 12:42:23 -0500 Subject: [PATCH 19/19] Updating build branch. --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f8af52b8..f7ce68d6 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,9 +1,9 @@ name: Playwright Tests on: push: - branches: [main, playwright-tests] + branches: [main] pull_request: - branches: [main, playwright-tests] + branches: [main] jobs: test: timeout-minutes: 60