diff --git a/.changeset/modernize-trigger-event.md b/.changeset/modernize-trigger-event.md new file mode 100644 index 00000000..594fb790 --- /dev/null +++ b/.changeset/modernize-trigger-event.md @@ -0,0 +1,23 @@ +--- +"@tko/utils": patch +"@tko/binding.core": patch +"@tko/provider.component": patch +--- + +Modernize synthetic event construction + +`triggerEvent` (exported from `@tko/utils`) now builds synthetic events using +`new MouseEvent`/`KeyboardEvent`/`Event` constructors instead of the +deprecated `document.createEvent('HTMLEvents')` + `initEvent(...)` path. This +restores native side-effects in modern DOM implementations (e.g. synthetic +clicks toggle checkbox `.checked` in happy-dom) without changing behavior in +real browsers. `relatedTarget` is still set to the target element for mouse +events to match the previous init-event argument list. + +`@tko/binding.core` event handler no longer assigns the legacy +`event.cancelBubble = true` before calling `event.stopPropagation()` — the +assignment is redundant on modern events and readonly on some implementations. + +`@tko/provider.component` now uses `Object.prototype.toString.call(node)` to +detect `HTMLUnknownElement` rather than `'' + node`, which is immune to +user-land `toString` overrides on custom elements. diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 62f1b1f7..25ae8a36 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -30,12 +30,17 @@ jobs: - name: Verify ESM extensions run: bun run verify:esm - - name: Run tests - run: bunx vitest run + - name: Run tests (browser matrix) + run: bunx vitest run --project browser env: HOME: /root VITEST_BROWSERS: chromium,firefox,webkit + - name: Run tests (cli-happy-dom) + run: bunx vitest run --project cli-happy-dom + env: + HOME: /root + - name: Upload build artifacts uses: actions/upload-artifact@v7 with: diff --git a/.github/workflows/test-headless.yml b/.github/workflows/test-headless.yml index 89fb725d..cce9cafa 100644 --- a/.github/workflows/test-headless.yml +++ b/.github/workflows/test-headless.yml @@ -34,7 +34,30 @@ jobs: run: bun run verify:esm - name: Run Tests - run: bunx vitest run + run: bunx vitest run --project browser env: HOME: /root VITEST_BROWSERS: ${{ matrix.browser }} + + cli-happy-dom: + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.59.1-noble + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install Bun + run: python3 tools/install-bun + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run Build + run: bun run build + + - name: Run cli-happy-dom tests + run: bunx vitest run --project cli-happy-dom + env: + HOME: /root diff --git a/builds/knockout/helpers/vitest-setup.js b/builds/knockout/helpers/vitest-setup.js index 09fc6c89..2b2557d4 100644 --- a/builds/knockout/helpers/vitest-setup.js +++ b/builds/knockout/helpers/vitest-setup.js @@ -1,11 +1,19 @@ import * as chai from 'chai' import sinon from 'sinon' +import { isHappyDom } from '../../../packages/utils/helpers/test-env.ts' // Set globals that builds/knockout specs and mocha-test-helpers.js expect globalThis.chai = chai globalThis.expect = chai.expect globalThis.sinon = sinon +// Test environment detector — used inside test bodies like: +// it('name', function (ctx) { +// if (isHappyDom()) return ctx.skip('happy-dom: reason') +// // ... +// }) +globalThis.isHappyDom = isHappyDom + // Load the knockout build (sets globalThis.ko) import '../dist/browser.min.js' diff --git a/builds/knockout/spec/components/defaultLoaderBehaviors.js b/builds/knockout/spec/components/defaultLoaderBehaviors.js index d5f896dd..25eb57ea 100644 --- a/builds/knockout/spec/components/defaultLoaderBehaviors.js +++ b/builds/knockout/spec/components/defaultLoaderBehaviors.js @@ -283,7 +283,8 @@ describe('Components: Default loader', function () { return testTemplateFromElement('', 'my-script-elem') }) - it('Can be configured as the ID of a ', 'my-textarea-elem') }) @@ -306,7 +307,8 @@ describe('Components: Default loader', function () { return testTemplateFromElement('', /* elementId */ null) }) - it('Can be configured as a ', /* elementId */ null) }) diff --git a/builds/knockout/spec/defaultBindings/attrBehaviors.js b/builds/knockout/spec/defaultBindings/attrBehaviors.js index 2c5a0263..61830d4b 100644 --- a/builds/knockout/spec/defaultBindings/attrBehaviors.js +++ b/builds/knockout/spec/defaultBindings/attrBehaviors.js @@ -9,7 +9,8 @@ describe('Binding: Attr', function () { expect(testNode.childNodes[0].getAttribute('second-attribute')).to.deep.equal('true') }) - it('Should be able to set namespaced attribute values', function () { + it('Should be able to set namespaced attribute values', function (ctx) { + if (isHappyDom()) return ctx.skip('happy-dom: Element.lookupNamespaceURI not implemented') var model = { myValue: 'first value' } testNode.innerHTML = [ '', diff --git a/builds/knockout/spec/defaultBindings/optionsBehaviors.js b/builds/knockout/spec/defaultBindings/optionsBehaviors.js index a19d5888..1262de4a 100644 --- a/builds/knockout/spec/defaultBindings/optionsBehaviors.js +++ b/builds/knockout/spec/defaultBindings/optionsBehaviors.js @@ -127,7 +127,8 @@ describe('Binding: Options', function () { expectHaveSelectedValues(testNode.childNodes[0], [4]) }) - it('Should select caption by default and retain selection when adding multiple items', function () { + it('Should select caption by default and retain selection when adding multiple items', function (ctx) { + if (isHappyDom()) return ctx.skip('happy-dom:
", @@ -135,7 +136,8 @@ describe('onError handler', function () { expect(windowOnErrorCount).to.equal(1) }) - it('passes through the error instance', async function () { + it('passes through the error instance', async function (ctx) { + if (isHappyDom()) return ctx.skip('happy-dom: setTimeout errors bypass window.onerror') var expectedInstance ko.tasks.schedule(function () { expectedInstance = new Error('Some error') diff --git a/bun.lock b/bun.lock index 2cc38fb8..0dbc5630 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,7 @@ "@vitest/browser-playwright": "^4.1.4", "chai": "^6.2.2", "esbuild": "^0.28.0", + "happy-dom": "^20.9.0", "jquery": "^4.0.0", "knip": "^6.4.1", "playwright": "^1.59.1", @@ -24,319 +25,292 @@ "name": "@tko/build.knockout", "version": "4.0.1", "dependencies": { - "@tko/binding.component": "^4.0.0", - "@tko/binding.core": "^4.0.0", - "@tko/binding.foreach": "^4.0.0", - "@tko/binding.if": "^4.0.0", - "@tko/binding.template": "^4.0.0", - "@tko/builder": "^4.0.0", - "@tko/filter.punches": "^4.0.0", - "@tko/provider.attr": "^4.0.0", - "@tko/provider.bindingstring": "^4.0.0", - "@tko/provider.component": "^4.0.0", - "@tko/provider.databind": "^4.0.0", - "@tko/provider.multi": "^4.0.0", - "@tko/provider.virtual": "^4.0.0", - "@tko/utils.component": "^4.0.0", - "@tko/utils.functionrewrite": "^4.0.0", - "tslib": "^2.2.0", + "@tko/binding.component": "^4.0.1", + "@tko/binding.core": "^4.0.1", + "@tko/binding.foreach": "^4.0.1", + "@tko/binding.if": "^4.0.1", + "@tko/binding.template": "^4.0.1", + "@tko/builder": "^4.0.1", + "@tko/filter.punches": "^4.0.1", + "@tko/provider.attr": "^4.0.1", + "@tko/provider.component": "^4.0.1", + "@tko/provider.databind": "^4.0.1", + "@tko/provider.multi": "^4.0.1", + "@tko/provider.virtual": "^4.0.1", + "@tko/utils": "^4.0.1", + "@tko/utils.component": "^4.0.1", + "@tko/utils.functionrewrite": "^4.0.1", }, }, "builds/reference": { "name": "@tko/build.reference", "version": "4.0.1", "dependencies": { - "@tko/binding.component": "^4.0.0", - "@tko/binding.core": "^4.0.0", - "@tko/binding.foreach": "^4.0.0", - "@tko/binding.if": "^4.0.0", - "@tko/binding.template": "^4.0.0", - "@tko/builder": "^4.0.0", - "@tko/filter.punches": "^4.0.0", - "@tko/provider.attr": "^4.0.0", - "@tko/provider.bindingstring": "^4.0.0", - "@tko/provider.component": "^4.0.0", - "@tko/provider.databind": "^4.0.0", - "@tko/provider.multi": "^4.0.0", - "@tko/provider.mustache": "^4.0.0", - "@tko/provider.native": "^4.0.0", - "@tko/provider.virtual": "^4.0.0", - "@tko/utils.component": "^4.0.0", - "@tko/utils.jsx": "^4.0.0", - "tslib": "^2.2.0", + "@tko/binding.component": "^4.0.1", + "@tko/binding.core": "^4.0.1", + "@tko/binding.foreach": "^4.0.1", + "@tko/binding.if": "^4.0.1", + "@tko/binding.template": "^4.0.1", + "@tko/builder": "^4.0.1", + "@tko/filter.punches": "^4.0.1", + "@tko/provider.attr": "^4.0.1", + "@tko/provider.component": "^4.0.1", + "@tko/provider.databind": "^4.0.1", + "@tko/provider.multi": "^4.0.1", + "@tko/provider.mustache": "^4.0.1", + "@tko/provider.native": "^4.0.1", + "@tko/provider.virtual": "^4.0.1", + "@tko/utils": "^4.0.1", + "@tko/utils.component": "^4.0.1", + "@tko/utils.jsx": "^4.0.1", }, }, "packages/bind": { "name": "@tko/bind", "version": "4.0.1", "dependencies": { - "@tko/computed": "^4.0.0", - "@tko/lifecycle": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/provider": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/builder": "^4.0.1", + "@tko/computed": "^4.0.1", + "@tko/lifecycle": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/provider": "^4.0.1", + "@tko/utils": "^4.0.1", }, "peerDependencies": { - "@tko/binding.foreach": "^4.0.0", + "@tko/binding.foreach": "^4.0.1", }, }, "packages/binding.component": { "name": "@tko/binding.component", "version": "4.0.1", "dependencies": { - "@tko/bind": "^4.0.0", - "@tko/lifecycle": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/provider": "^4.0.0", - "@tko/provider.native": "^4.0.0", - "@tko/utils": "^4.0.0", - "@tko/utils.component": "^4.0.0", - "@tko/utils.jsx": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/lifecycle": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/provider.native": "^4.0.1", + "@tko/utils": "^4.0.1", + "@tko/utils.component": "^4.0.1", + "@tko/utils.jsx": "^4.0.1", }, }, "packages/binding.core": { "name": "@tko/binding.core", "version": "4.0.1", "dependencies": { - "@tko/bind": "^4.0.0", - "@tko/computed": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/computed": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, }, "packages/binding.foreach": { "name": "@tko/binding.foreach", "version": "4.0.1", "dependencies": { - "@tko/bind": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/provider": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, }, "packages/binding.if": { "name": "@tko/binding.if", "version": "4.0.1", "dependencies": { - "@tko/bind": "^4.0.0", - "@tko/computed": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, }, "packages/binding.template": { "name": "@tko/binding.template", "version": "4.0.1", "dependencies": { - "@tko/bind": "^4.0.0", - "@tko/computed": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/computed": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, "peerDependencies": { - "@tko/binding.if": "^4.0.0", + "@tko/binding.if": "^4.0.1", }, }, "packages/builder": { "name": "@tko/builder", "version": "4.0.1", "dependencies": { - "@tko/bind": "^4.0.0", - "@tko/binding.template": "^4.0.0", - "@tko/computed": "^4.0.0", - "@tko/filter.punches": "^4.0.0", - "@tko/lifecycle": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/provider": "^4.0.0", - "@tko/utils": "^4.0.0", - "@tko/utils.parser": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/binding.template": "^4.0.1", + "@tko/computed": "^4.0.1", + "@tko/lifecycle": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/provider": "^4.0.1", + "@tko/utils": "^4.0.1", + "@tko/utils.parser": "^4.0.1", }, }, "packages/computed": { "name": "@tko/computed", "version": "4.0.1", "dependencies": { - "@tko/observable": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, }, "packages/filter.punches": { "name": "@tko/filter.punches", "version": "4.0.1", "dependencies": { - "@tko/observable": "^4.0.0", - "tslib": "^2.2.0", + "@tko/observable": "^4.0.1", }, }, "packages/lifecycle": { "name": "@tko/lifecycle", "version": "4.0.1", "dependencies": { - "@tko/computed": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/computed": "^4.0.1", + "@tko/utils": "^4.0.1", }, "peerDependencies": { - "@tko/observable": "^4.0.0", + "@tko/observable": "^4.0.1", }, }, "packages/observable": { "name": "@tko/observable", "version": "4.0.1", "dependencies": { - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/utils": "^4.0.1", }, }, "packages/provider": { "name": "@tko/provider", "version": "4.0.1", "dependencies": { - "@tko/observable": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, }, "packages/provider.attr": { "name": "@tko/provider.attr", "version": "4.0.1", "dependencies": { - "@tko/provider": "^4.0.0", - "tslib": "^2.2.0", + "@tko/provider": "^4.0.1", }, }, "packages/provider.bindingstring": { "name": "@tko/provider.bindingstring", "version": "4.0.1", "dependencies": { - "@tko/provider": "^4.0.0", - "@tko/utils.parser": "^4.0.0", - "tslib": "^2.2.0", + "@tko/provider": "^4.0.1", + "@tko/utils.parser": "^4.0.1", }, }, "packages/provider.component": { "name": "@tko/provider.component", "version": "4.0.1", "dependencies": { - "@tko/computed": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/provider": "^4.0.0", - "@tko/utils": "^4.0.0", - "@tko/utils.component": "^4.0.0", - "@tko/utils.parser": "^4.0.0", - "tslib": "^2.2.0", + "@tko/computed": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/provider": "^4.0.1", + "@tko/utils": "^4.0.1", + "@tko/utils.component": "^4.0.1", + "@tko/utils.parser": "^4.0.1", }, }, "packages/provider.databind": { "name": "@tko/provider.databind", "version": "4.0.1", "dependencies": { - "@tko/provider.bindingstring": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/provider.bindingstring": "^4.0.1", }, }, "packages/provider.multi": { "name": "@tko/provider.multi", "version": "4.0.1", "dependencies": { - "@tko/provider": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/provider": "^4.0.1", }, }, "packages/provider.mustache": { "name": "@tko/provider.mustache", "version": "4.0.1", "dependencies": { - "@tko/binding.template": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/provider": "^4.0.0", - "@tko/utils": "^4.0.0", - "@tko/utils.parser": "^4.0.0", - "tslib": "^2.2.0", + "@tko/observable": "^4.0.1", + "@tko/provider": "^4.0.1", + "@tko/utils.parser": "^4.0.1", }, }, "packages/provider.native": { "name": "@tko/provider.native", "version": "4.0.1", "dependencies": { - "@tko/observable": "^4.0.0", - "@tko/provider": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/provider": "^4.0.1", }, }, "packages/provider.virtual": { "name": "@tko/provider.virtual", "version": "4.0.1", "dependencies": { - "@tko/provider.bindingstring": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/provider.bindingstring": "^4.0.1", + "@tko/utils": "^4.0.1", }, }, "packages/utils": { "name": "@tko/utils", "version": "4.0.1", "dependencies": { - "tslib": "^2.2.0", + "@tko/builder": "^4.0.1", + "@tko/provider": "^4.0.1", }, }, "packages/utils.component": { "name": "@tko/utils.component", "version": "4.0.1", "dependencies": { - "@tko/lifecycle": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/lifecycle": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, "peerDependencies": { - "@tko/bind": "^4.0.0", - "@tko/binding.core": "^4.0.0", - "@tko/computed": "^4.0.0", - "@tko/provider.multi": "^4.0.0", - "@tko/provider.virtual": "^4.0.0", + "@tko/bind": "^4.0.1", + "@tko/binding.core": "^4.0.1", + "@tko/computed": "^4.0.1", + "@tko/provider.multi": "^4.0.1", + "@tko/provider.virtual": "^4.0.1", }, }, "packages/utils.functionrewrite": { "name": "@tko/utils.functionrewrite", "version": "4.0.1", - "dependencies": { - "tslib": "^2.2.0", - }, }, "packages/utils.jsx": { "name": "@tko/utils.jsx", "version": "4.0.1", "dependencies": { - "@tko/bind": "^4.0.0", - "@tko/computed": "^4.0.0", - "@tko/lifecycle": "^4.0.0", - "@tko/observable": "^4.0.0", - "@tko/provider.native": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/bind": "^4.0.1", + "@tko/computed": "^4.0.1", + "@tko/lifecycle": "^4.0.1", + "@tko/observable": "^4.0.1", + "@tko/provider.native": "^4.0.1", + "@tko/utils": "^4.0.1", }, }, "packages/utils.parser": { "name": "@tko/utils.parser", "version": "4.0.1", "dependencies": { - "@tko/observable": "^4.0.0", - "@tko/utils": "^4.0.0", - "tslib": "^2.2.0", + "@tko/observable": "^4.0.1", + "@tko/utils": "^4.0.1", }, "peerDependencies": { - "@tko/bind": "^4.0.0", - "@tko/binding.core": "^4.0.0", - "@tko/provider.databind": "^4.0.0", + "@tko/bind": "^4.0.1", + "@tko/binding.core": "^4.0.1", + "@tko/provider.databind": "^4.0.1", }, }, }, @@ -663,6 +637,10 @@ "@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="], + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@vitest/browser": ["@vitest/browser@4.1.4", "", { "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.4", "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "peerDependencies": { "vitest": "4.1.4" } }, "sha512-TrNaY/yVOwxtrxNsDUC/wQ56xSwplpytTeRAqF/197xV/ZddxxulBsxR6TrhVMyniJmp9in8d5u0AcDaNRY30w=="], "@vitest/browser-playwright": ["@vitest/browser-playwright@4.1.4", "", { "dependencies": { "@vitest/browser": "4.1.4", "@vitest/mocker": "4.1.4", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "playwright": "*", "vitest": "4.1.4" } }, "sha512-q3PchVhZINX23Pv+RERgAtDlp6wzVkID/smOPnZ5YGWpeWUe3jMNYppeVh15j4il3G7JIJty1d1Kicpm0HSMig=="], @@ -713,6 +691,8 @@ "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], "esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": "bin/esbuild" }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], @@ -751,6 +731,8 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="], + "human-id": ["human-id@4.1.3", "", { "bin": "dist/cli.js" }, "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], @@ -949,6 +931,8 @@ "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], diff --git a/package.json b/package.json index adf23237..d808b5a2 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@vitest/browser-playwright": "^4.1.4", "chai": "^6.2.2", "esbuild": "^0.28.0", + "happy-dom": "^20.9.0", "jquery": "^4.0.0", "knip": "^6.4.1", "playwright": "^1.59.1", diff --git a/packages/binding.component/spec/componentBindingBehaviors.ts b/packages/binding.component/spec/componentBindingBehaviors.ts index 8b39cccb..266e4d8b 100644 --- a/packages/binding.component/spec/componentBindingBehaviors.ts +++ b/packages/binding.component/spec/componentBindingBehaviors.ts @@ -35,6 +35,7 @@ import { restoreAfter, useMockForTasks } from '../../utils/helpers/mocha-test-helpers' +import { isHappyDom } from '../../utils/helpers/test-env' describe('Components: Component binding', function () { let testComponentName = 'test-component', @@ -1252,7 +1253,7 @@ describe('Components: Component binding', function () { expect((testNode.children[0] as HTMLInputElement).innerText.trim()).to.deep.equal(`beep`) }) - it('inserts multiple times into virtual element slot with the slot template', function () { + it('inserts multiple times into virtual element slot with the slot template', function (ctx: any) { testNode.innerHTML = ` @@ -1270,6 +1271,7 @@ describe('Components: Component binding', function () { } ViewModel.register('test-component') + if (isHappyDom()) return ctx.skip('happy-dom: innerText whitespace rendering differs from real browsers') applyBindings(outerViewModel, testNode) expect((testNode.children[0] as HTMLInputElement).innerText.trim()).to.deep.equal(`beep / beep`) }) @@ -1381,11 +1383,13 @@ describe('Components: Component binding', function () { ViewModel.register('test-component') applyBindings(outerViewModel, testNode) + // `innerText` varies slightly across DOM implementations around whitespace + // between block siblings; normalize before comparing text contents. const innerText = (testNode.children[0] as HTMLElement).innerText.replace(/\s+/g, ' ').trim() expect(innerText).to.deep.equal(`X beep Y Gamma Zeta Q`) }) - it('inserts all component template nodes in an unnamed (default) slot', function () { + it('inserts all component template nodes in an unnamed (default) slot', function (ctx: any) { testNode.innerHTML = ` B. @@ -1404,13 +1408,14 @@ describe('Components: Component binding', function () { } ViewModel.register('test-component') + if (isHappyDom()) return ctx.skip('happy-dom: innerText whitespace rendering differs from real browsers') applyBindings(outerViewModel, testNode) expect((testNode.children[0] as HTMLElement).innerText.trim()).to.deep.equal(`A. B. C.`) const em = testNode.children[0].children[0].children[0] expect(em.tagName).to.deep.equal('EM') }) - it('inserts multiple nodes from a