CodeSampleX

サンプル

happy-dom 20.11.2: Enforce strict whitespace token separation for CSS attribute selector [attr~="val"] and prevent hyphenated substring matching in happy-dom

検証済みサンプル — npm happy-dom 20.11.2: Enforce strict whitespace token separation for CSS attribute selector [attr~="val"] and prevent hyphenated substring…

sha256:143a1a028249dbbfdb1ae521d43e57375365a008d96dcb4792fc0427df76731e

このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。 合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。 MIT-0

実行証拠

宣言された環境と署名済みの実行を分けてあります。このサンプルが何をどこで実行したかをそのまま確認できます。

証拠の基準
署名済みコントラクト合格
検証レシート
3
ビルドした署名鍵
3
宣言された環境 node linux x64 node node npm

検証実行環境

環境 コントラクト ステージ 実行日
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-17
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-07

ケース

HOW
ゴール
Enforce strict whitespace token separation for CSS attribute selector [attr~="val"] and prevent hyphenated substring matching in happy-dom
パッケージ
シンボル
  • happy-dom.Window
  • happy-dom.Element.matches
  • happy-dom.Element.closest
環境
node
作成日
2026-08-17T01:51:52Z

コントラクト

  1. child.matches('[data-controller~="modal"]') returns false against data-controller="modal-auto-close" rather than true, because [attr~="val"] matches only whitespace-separated tokens per CSS Selectors Level 4
  2. child.closest('[data-controller~="modal"]') ignores the hyphenated child element and resolves to the matching ancestor element
  3. btn1.matches('[class~="btn"]') returns false on class="btn-primary"
  4. btn2.matches('[class~="btn"]') returns true on whitespace-delimited class="btn btn-primary"

ファイル

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs

ソースアーティファクトをダウンロード (tar.gz)

ソース

NOTES.md
# Happy DOM CSS Attribute Selector (~=) Token Matching

Prior search query returned a compatible sample `sha256:89403ecd9c6d0cedbcb3196736c424205b2a84bbc8c91b01064e26f661d16b1e` demonstrating that inline script execution in happy-dom depends on explicit JavaScript evaluation settings and is disabled by default.

This sample instead targets a CSS selector matching fix in happy-dom v20.10.3 (issue #2194).

Prior to happy-dom v20.10.3 (such as v20.10.2), the `[attr~="value"]` selector compiled to a regular expression `[- ]value|value[- ]|^value$` which treated hyphens as word delimiters and performed unanchored substring matches against kebab-case attribute values. In v20.10.3 and later, this was corrected to require strict whitespace token boundaries `(^|\s)value(\s|$)` matching CSS Selectors Level 4 specifications.

On versions prior to v20.10.3, the contract fails loudly with an `AssertionError` (`Expected false, got true`) because `child.matches('[data-controller~="modal"]')` erroneously matches `modal-auto-close`.
csx.json
{"case":{"believed":"The CSS attribute selector [attr~=\"val\"] treats hyphens as word boundaries and matches substrings in hyphenated attribute values.","caseId":"case:sha256:110044a465124ce685d179bd745a5bde8e62728d530063426c2cd5ca69f8c76f","contract":["child.matches('[data-controller~=\"modal\"]') returns false against data-controller=\"modal-auto-close\" rather than true, because [attr~=\"val\"] matches only whitespace-separated tokens per CSS Selectors Level 4","child.closest('[data-controller~=\"modal\"]') ignores the hyphenated child element and resolves to the matching ancestor element","btn1.matches('[class~=\"btn\"]') returns false on class=\"btn-primary\"","btn2.matches('[class~=\"btn\"]') returns true on whitespace-delimited class=\"btn btn-primary\""],"goal":"Enforce strict whitespace token separation for CSS attribute selector [attr~=\"val\"] and prevent hyphenated substring matching in happy-dom","kind":"HOW","packages":["pkg:npm/happy-dom@20.11.2"],"schemaVersion":1,"symbols":["happy-dom.Window","happy-dom.Element.matches","happy-dom.Element.closest"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/happy-dom@20.11.2"],"schemaVersion":1,"symbols":["happy-dom.Window","happy-dom.Element.matches","happy-dom.Element.closest"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-happy-dom-bugfix",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-happy-dom-bugfix",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "happy-dom": "^20.11.2"
      }
    },
    "node_modules/@types/node": {
      "version": "26.2.0",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
      "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
      "license": "MIT",
      "dependencies": {
        "undici-types": "~8.3.0"
      }
    },
    "node_modules/@types/whatwg-mimetype": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
      "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
      "license": "MIT"
    },
    "node_modules/@types/ws": {
      "version": "8.18.1",
      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
      "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/buffer-image-size": {
      "version": "0.6.4",
      "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz",
      "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==",
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      },
      "engines": {
        "node": ">=4.0"
      }
    },
    "node_modules/entities": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
      "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=0.12"
      },
      "funding": {
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/happy-dom": {
      "version": "20.11.2",
      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.11.2.tgz",
      "integrity": "sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw==",
      "license": "MIT",
      "dependencies": {
        "@types/node": ">=20.0.0",
        "@types/whatwg-mimetype": "^3.0.2",
        "@types/ws": "^8.18.1",
        "buffer-image-size": "^0.6.4",
        "entities": "^7.0.1",
        "whatwg-mimetype": "^3.0.0",
        "ws": "^8.21.0"
      },
      "engines": {
        "node": ">=20.0.0"
      }
    },
    "node_modules/undici-types": {
      "version": "8.3.0",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
      "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
      "license": "MIT"
    },
    "node_modules/whatwg-mimetype": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
      "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/ws": {
      "version": "8.21.3",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
      "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    }
  }
}
package.json
{
  "name": "happy-dom-selector-contract",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "happy-dom": "20.11.2"
  }
}
test/contract.mjs
import assert from 'node:assert/strict';
import { Window } from 'happy-dom';

const window = new Window();
const document = window.document;

document.body.innerHTML = `
  <div id="parent" data-controller="modal">
    <div id="child" data-controller="modal-auto-close"></div>
  </div>
  <button id="btn1" class="btn-primary"></button>
  <button id="btn2" class="btn btn-primary"></button>
  <span id="target" data-name="item-sub-item"></span>
`;

const child = document.getElementById('child');
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
const target = document.getElementById('target');

// In happy-dom <= 20.10.2, [attr~="val"] treated hyphens as word boundaries and matched unanchored substrings.
// In happy-dom >= 20.10.3, [attr~="val"] strictly matches whitespace-separated words per CSS Selectors spec.
assert.strictEqual(
  child.matches('[data-controller~="modal"]'),
  false,
  '[data-controller~="modal"] must not match hyphenated value "modal-auto-close"'
);

assert.strictEqual(
  child.matches('[data-controller~="odal"]'),
  false,
  '[data-controller~="odal"] must not match substring of "modal-auto-close"'
);

assert.strictEqual(
  child.closest('[data-controller~="modal"]')?.id,
  'parent',
  'child.closest must traverse to parent rather than matching child itself'
);

assert.strictEqual(
  btn1.matches('[class~="btn"]'),
  false,
  '[class~="btn"] must not match class="btn-primary"'
);

assert.strictEqual(
  btn2.matches('[class~="btn"]'),
  true,
  '[class~="btn"] matches class="btn btn-primary" via whitespace separation'
);

assert.strictEqual(
  target.matches('[data-name~="sub"]'),
  false,
  '[data-name~="sub"] must not match hyphenated substring in "item-sub-item"'
);

オリジンシーダー

csx-seed