Sample
@tiptap/extension-color 3.11.0
Verified sample for npm @tiptap/extension-color 3.11.0. The contract ran on node 22 · linux debian/x64 · docker and passed: assert importing…
sha256:4c36ca712e34d2463334f8004c396be54bff31293334ba55c68feb22a69f58e5
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 1
- Signing keys that built it
- 1
Declared environment
linux 24 · ubuntu · glibc 2.39 x64 npm
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| node 22 · linux debian/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1node:22@sha256:8a34c4ab3ea2… |
2026-09-11 |
Case
HOW- Goal
- verify pkg:npm/%40tiptap/extension-color@3.11.0
- Packages
- Created
- 2026-09-11T13:54:57Z
Contract
- assert importing @tiptap/extension-color provides Color extension matching default export with name 'color' and type 'extension'
- assert default Color options configure target types as ['textStyle']
- assert Color.configure customizes target types to include arbitrary node or mark types
- assert addGlobalAttributes registers color attribute on configured types with null default
- assert parseHTML extracts color from style attribute and strips quotes
- assert parseHTML chooses the last color declaration when style attribute contains multiple declarations
- assert parseHTML falls back to element style color property when style attribute is not present
- assert renderHTML produces style string with color when color attribute is present and empty object when null or missing
- assert setColor command invokes chain setMark on textStyle with the specified color
- assert unsetColor command invokes chain setMark on textStyle with null color and removes empty text style
- assert getSchema integrates Color attribute into Prosemirror textStyle mark schema with default null
- assert getSchema applies color attribute to configured node types when types option is customized
Files
- NOTES.md
- PROMPT.md
- csx.json
- package-lock.json
- package.json
- spec.json
- test/contract.mjs
Source
# Notes: TipTap Color Extension Verification
## Search Result
`search_known_solution` returned `NO_SAFE_MATCH` for `pkg:npm/%40tiptap/extension-color@3.11.0`. No verified public sample existed in the network for this extension. The network observation records showed 75 observations with an overall pass rate of 0.25, with `TS2307` ("Cannot find module or its corresponding type declarations") being the most common recorded failure across build environments.
## Naive Model Assumption
A developer or coding model typically assumes:
1. `@tiptap/extension-color` is an independent extension that creates a standalone ProseMirror mark named `color` (e.g. attempting `editor.commands.setMark('color')` or expecting `schema.marks.color`).
2. `@tiptap/extension-color` can be installed and resolved alone without `@tiptap/extension-text-style` or peer dependencies.
3. The extension defines its own marks or nodes rather than extending `textStyle` with global attributes.
4. Setting text color directly modifies a mark attribute named `color` on arbitrary nodes without explicit `types` configuration.
## Exact Behavior & Failure Mode
1. **Module Re-export & Peer Dependencies**: In TipTap 3, `@tiptap/extension-color` acts as a facade re-exporting `Color` and `ColorOptions` directly from `@tiptap/extension-text-style`. If `@tiptap/extension-text-style` (and transitively `@tiptap/core` and `@tiptap/pm`) is omitted, module resolution fails immediately with `TS2307` / `ERR_MODULE_NOT_FOUND`.
2. **Extension Identity**: `Color` is an `Extension` (not a `Mark` or `Node`), with `Color.name === 'color'` and `Color.type === 'extension'`. Both named and default exports are identical.
3. **Global Attributes on textStyle**: By default, `Color` injects a global attribute `color` onto target mark `textStyle` (default `types: ['textStyle']`) with a default value of `null`. It does not define an independent mark type.
4. **Custom Configuration**: `Color.configure({ types: [...] })` enables extending other mark or node types (e.g. `heading`, `paragraph`) so they also receive the `color` attribute in schema generation.
5. **HTML Parsing**: `parseHTML` reads the raw inline `style` attribute of the element to preserve the author's original color format (such as hex `#rrggbb`) instead of computed rgb values. It handles multiple inline declarations by selecting the last declaration, and falls back to `element.style.color`.
6. **HTML Rendering**: `renderHTML` returns `{ style: 'color: <value>' }` when the color attribute is set, and an empty object `{}` when unset or null.
7. **Commands**: `addCommands` registers `setColor(color)` and `unsetColor()`. `setColor` chains `setMark('textStyle', { color })`. `unsetColor` chains `setMark('textStyle', { color: null }).removeEmptyTextStyle()`.
8. **ProseMirror Schema Integration**: When integrated into TipTap schema via `getSchema`, `textStyle` mark incorporates the `color` attribute with default `null`.
Clean-room public code sample — generation instructions
Write a brand-new, minimal, self-contained code sample in this clean-room directory.
Do not copy, paraphrase, or reference any existing project source. Work only from this spec.
A csx.json manifest scaffold already exists. Do not recreate it from memory. Preserve its case.goal, packages and symbols; fill its empty case.contract with exact assertions and correct its environment, commands and verifierAdapter for the files you generate.
Goal: verify pkg:npm/%40tiptap/extension-color@3.11.0
Kind: HOW
Use EXACTLY these public packages and versions:
- pkg:npm/%40tiptap/extension-color@3.11.0
Rules:
- One focused purpose; the smallest project that proves the goal.
- Include a contract test (test/contract.*) that runs OFFLINE and exits 0 exactly when the goal behavior works.
- Pin every dependency with a lockfile so resolution is reproducible.
- No secrets, credentials, or tokens. No real URLs (only example.com or localhost). No absolute paths.
- No personal names, emails, company names, or project identifiers of any kind.
- No binaries and no generated output (node_modules, dist, target, venv, .git, .env).
- Keep it under 200 files and 256KB packed.
{"case":{"caseId":"case:sha256:fc3fc5b9d669e859e0cece665ca0e6251e3646df2cc534e93493eace91489929","contract":["assert importing @tiptap/extension-color provides Color extension matching default export with name 'color' and type 'extension'","assert default Color options configure target types as ['textStyle']","assert Color.configure customizes target types to include arbitrary node or mark types","assert addGlobalAttributes registers color attribute on configured types with null default","assert parseHTML extracts color from style attribute and strips quotes","assert parseHTML chooses the last color declaration when style attribute contains multiple declarations","assert parseHTML falls back to element style color property when style attribute is not present","assert renderHTML produces style string with color when color attribute is present and empty object when null or missing","assert setColor command invokes chain setMark on textStyle with the specified color","assert unsetColor command invokes chain setMark on textStyle with null color and removes empty text style","assert getSchema integrates Color attribute into Prosemirror textStyle mark schema with default null","assert getSchema applies color attribute to configured node types when types option is customized"],"goal":"verify pkg:npm/%40tiptap/extension-color@3.11.0","kind":"HOW","packages":["pkg:npm/%40tiptap/extension-color@3.11.0"],"schemaVersion":1},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","distro":"ubuntu","ecosystem":"npm","libc":"glibc","libcVersion":"2.39","os":"linux","osVersionBucket":"24","packageManager":"npm","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/%40tiptap/extension-color@3.11.0"],"schemaVersion":1,"subject":"pkg:npm/%40tiptap/extension-color@3.11.0","verifierAdapter":"node-typescript@1"}
{
"name": "tiptap-extension-color-sample",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tiptap-extension-color-sample",
"version": "1.0.0",
"license": "MIT-0",
"dependencies": {
"@tiptap/core": "3.11.0",
"@tiptap/extension-color": "3.11.0",
"@tiptap/extension-text-style": "3.11.0",
"@tiptap/pm": "3.11.0"
}
},
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@tiptap/core": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.11.0.tgz",
"integrity": "sha512-kmS7ZVpHm1EMnW1Wmft9H5ZLM7E0G0NGBx+aGEHGDcNxZBXD2ZUa76CuWjIhOGpwsPbELp684ZdpF2JWoNi4Dg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^3.11.0"
}
},
"node_modules/@tiptap/extension-color": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.11.0.tgz",
"integrity": "sha512-4H+3nyheow0tBt+q/Pkkm/pR/F6vUAdKZaq1HtWFi7LcFzCYxdB3Si625EparKO3kMvGvULjdd+/zt68NS7Acw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-text-style": "^3.11.0"
}
},
"node_modules/@tiptap/extension-text-style": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.11.0.tgz",
"integrity": "sha512-q8RM4gzmdnUHosL65SIJzTTmL29bm+3hNPdloOuJyLd4sTYs2q+cues5mH5/n85HqX3+TvKrfrTVb1Yj62E1NA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.11.0"
}
},
"node_modules/@tiptap/pm": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.11.0.tgz",
"integrity": "sha512-plCQDLCZIOc92cizB8NNhBRN0szvYR3cx9i5IXo6v9Xsgcun8KHNcJkesc2AyeqdIs0BtOJZaqQ9adHThz8UDw==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.24.1",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.5.0",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.38.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.2.0.tgz",
"integrity": "sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"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==",
"license": "Python-2.0"
},
"node_modules/crelt": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz",
"integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==",
"license": "MIT"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/linkify-it": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz",
"integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/markdown-it": {
"version": "14.3.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.1.tgz",
"integrity": "sha512-4Ej49aYTDFIQ+uBkfX8GBvJGccoARxxPep+7aWTs55ozbjQJpW9M26Fe53vnGgvLeVzva/amzjQQaQu9w0vMhA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.5.0",
"linkify-it": "^5.0.2",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/mdurl": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz",
"integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==",
"license": "MIT"
},
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/prosemirror-changeset": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.2.tgz",
"integrity": "sha512-ViYrjMSg3YFiXwIhKaluu+/mi3Yrxt6AR8ri14ulTaGcZtXO1CThl7A2gv79qx5fQnOw8woKwyBU2u+9PVCm3w==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz",
"integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz",
"integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.7",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.7.tgz",
"integrity": "sha512-NV57Oxe3fXR+bNGXS5XW4sKt/XGIyY3n0eHVUoJORcismeF4FFMpLBnl0YESy2BvJQHloexX+9P+zT3VqFj/CQ==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.2.tgz",
"integrity": "sha512-6VgUJTYod0nMBlCaYJGhXGLu7Gt4AvcwcOq0YfJCY/6Uh+3S7UsWhpy6rJFCBFOmonq1hD8KyWOtZhkppd4YPg==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.25.11",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz",
"integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.3",
"prosemirror-model": "^1.25.4",
"prosemirror-state": "^1.4.4",
"prosemirror-transform": "^1.10.5",
"prosemirror-view": "^1.41.4"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.1.tgz",
"integrity": "sha512-t4F5615FycnCqsX7ShTUs8+jfnwcf46kuFRvSl/3qFx2QTZ4DjgowesLHQXcFP7G//TjesqZG3WtgL7vdyVJyA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.42.3",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.3.tgz",
"integrity": "sha512-oTN7EtH+CpwxU9NrwEYWd0UZ4JUx7l048l5A2Xppm4p/60isZYLnth9QVQmC3VRIvdrIWCxwZSd+Uz791G31/w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.25.8",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
}
}
}
{
"name": "tiptap-extension-color-sample",
"version": "1.0.0",
"private": true,
"type": "module",
"license": "MIT-0",
"scripts": {
"test": "node test/contract.mjs"
},
"dependencies": {
"@tiptap/core": "3.11.0",
"@tiptap/extension-color": "3.11.0",
"@tiptap/extension-text-style": "3.11.0",
"@tiptap/pm": "3.11.0"
}
}
{
"schemaVersion": 1,
"goal": "verify pkg:npm/%40tiptap/extension-color@3.11.0",
"kind": "HOW",
"packages": [
"pkg:npm/%40tiptap/extension-color@3.11.0"
]
}
import { strict as assert } from 'node:assert';
import { Color, default as DefaultColor } from '@tiptap/extension-color';
import { Extension, Node, getSchema, getAttributesFromExtensions } from '@tiptap/core';
import { TextStyle } from '@tiptap/extension-text-style';
async function runContract() {
// ---------------------------------------------------------------------------
// Contract Assertion 1:
// importing @tiptap/extension-color provides Color extension matching default
// export with name 'color' and type 'extension'
// ---------------------------------------------------------------------------
assert.strictEqual(Color, DefaultColor, 'named and default export must be identical');
assert.strictEqual(Color.name, 'color', 'extension name must be color');
assert.strictEqual(Color.type, 'extension', 'extension type must be extension');
// ---------------------------------------------------------------------------
// Contract Assertion 2:
// default Color options configure target types as ['textStyle']
// ---------------------------------------------------------------------------
const defaultExt = Color.configure();
assert.deepStrictEqual(
defaultExt.options.types,
['textStyle'],
'default Color options must target textStyle'
);
// ---------------------------------------------------------------------------
// Contract Assertion 3:
// Color.configure customizes target types to include arbitrary node or mark types
// ---------------------------------------------------------------------------
const customExt = Color.configure({
types: ['textStyle', 'heading', 'paragraph'],
});
assert.deepStrictEqual(
customExt.options.types,
['textStyle', 'heading', 'paragraph'],
'configured types must reflect custom option array'
);
// ---------------------------------------------------------------------------
// Contract Assertion 4:
// addGlobalAttributes registers color attribute on configured types with null default
// ---------------------------------------------------------------------------
const globalAttrs = defaultExt.config.addGlobalAttributes.call(defaultExt);
assert.strictEqual(Array.isArray(globalAttrs), true, 'addGlobalAttributes must return an array');
assert.strictEqual(globalAttrs.length, 1, 'must contain exactly one global attribute spec');
assert.deepStrictEqual(
globalAttrs[0].types,
['textStyle'],
'global attribute types must match configured types'
);
assert.strictEqual(
globalAttrs[0].attributes.color.default,
null,
'default color attribute must be null'
);
// ---------------------------------------------------------------------------
// Contract Assertion 5:
// parseHTML extracts color from style attribute and strips quotes
// ---------------------------------------------------------------------------
const parseFn = globalAttrs[0].attributes.color.parseHTML;
const mockElementQuoted = {
getAttribute: (name) => (name === 'style' ? 'font-size: 14px; color: "rgb(255, 0, 0)"; font-weight: bold' : null),
style: { color: 'rgb(255, 0, 0)' },
};
assert.strictEqual(
parseFn(mockElementQuoted),
'rgb(255, 0, 0)',
'parseHTML must extract color and strip surrounding quotes'
);
// ---------------------------------------------------------------------------
// Contract Assertion 6:
// parseHTML chooses the last color declaration when style attribute contains multiple declarations
// ---------------------------------------------------------------------------
const mockElementMultiple = {
getAttribute: (name) => (name === 'style' ? 'color: #0000ff; font-weight: bold; color: #ff00ff' : null),
style: { color: '#ff00ff' },
};
assert.strictEqual(
parseFn(mockElementMultiple),
'#ff00ff',
'parseHTML must pick the last color declaration when multiple are present'
);
// ---------------------------------------------------------------------------
// Contract Assertion 7:
// parseHTML falls back to element style color property when style attribute is not present
// ---------------------------------------------------------------------------
const mockElementFallback = {
getAttribute: () => null,
style: { color: '#00ff00' },
};
assert.strictEqual(
parseFn(mockElementFallback),
'#00ff00',
'parseHTML must fall back to element.style.color when style attribute is absent'
);
const mockElementNone = {
getAttribute: () => null,
style: {},
};
assert.strictEqual(
parseFn(mockElementNone),
undefined,
'parseHTML must return undefined when no color style exists'
);
// ---------------------------------------------------------------------------
// Contract Assertion 8:
// renderHTML produces style string with color when color attribute is present and empty object when null or missing
// ---------------------------------------------------------------------------
const renderFn = globalAttrs[0].attributes.color.renderHTML;
assert.deepStrictEqual(
renderFn({ color: '#ff8800' }),
{ style: 'color: #ff8800' },
'renderHTML must return style with color property when color attribute is set'
);
assert.deepStrictEqual(
renderFn({ color: null }),
{},
'renderHTML must return empty object when color attribute is null'
);
assert.deepStrictEqual(
renderFn({}),
{},
'renderHTML must return empty object when color attribute is missing'
);
// ---------------------------------------------------------------------------
// Contract Assertion 9:
// setColor command invokes chain setMark on textStyle with the specified color
// ---------------------------------------------------------------------------
const cmds = defaultExt.config.addCommands.call(defaultExt);
let capturedSetMark = null;
let setMarkRunCalled = false;
const mockSetChain = () => ({
setMark: (type, attrs) => {
capturedSetMark = { type, attrs };
return {
run: () => {
setMarkRunCalled = true;
return true;
},
};
},
});
const setResult = cmds.setColor('#123456')({ chain: mockSetChain });
assert.strictEqual(setResult, true, 'setColor must return the result of chain().run()');
assert.strictEqual(setMarkRunCalled, true, 'setColor must execute run()');
assert.deepStrictEqual(
capturedSetMark,
{ type: 'textStyle', attrs: { color: '#123456' } },
'setColor must invoke setMark on textStyle with color attribute'
);
// ---------------------------------------------------------------------------
// Contract Assertion 10:
// unsetColor command invokes chain setMark on textStyle with null color and removes empty text style
// ---------------------------------------------------------------------------
let capturedUnsetMark = null;
let removeEmptyCalled = false;
let unsetRunCalled = false;
const mockUnsetChain = () => ({
setMark: (type, attrs) => {
capturedUnsetMark = { type, attrs };
return {
removeEmptyTextStyle: () => {
removeEmptyCalled = true;
return {
run: () => {
unsetRunCalled = true;
return true;
},
};
},
};
},
});
const unsetResult = cmds.unsetColor()({ chain: mockUnsetChain });
assert.strictEqual(unsetResult, true, 'unsetColor must return the result of chain().run()');
assert.strictEqual(removeEmptyCalled, true, 'unsetColor must call removeEmptyTextStyle()');
assert.strictEqual(unsetRunCalled, true, 'unsetColor must execute run()');
assert.deepStrictEqual(
capturedUnsetMark,
{ type: 'textStyle', attrs: { color: null } },
'unsetColor must invoke setMark on textStyle with color: null'
);
// ---------------------------------------------------------------------------
// Contract Assertion 11:
// getSchema integrates Color attribute into Prosemirror textStyle mark schema with default null
// ---------------------------------------------------------------------------
const Doc = Node.create({ name: 'doc', topNode: true, content: 'block+' });
const Paragraph = Node.create({ name: 'paragraph', group: 'block', content: 'inline*' });
const Text = Node.create({ name: 'text', group: 'inline' });
const defaultSchema = getSchema([Doc, Paragraph, Text, TextStyle, defaultExt]);
assert.ok(defaultSchema.marks.textStyle, 'schema must register textStyle mark');
assert.ok(defaultSchema.marks.textStyle.spec.attrs.color, 'textStyle mark must have color attribute');
assert.strictEqual(
defaultSchema.marks.textStyle.spec.attrs.color.default,
null,
'schema textStyle color attribute default must be null'
);
const testMark = defaultSchema.marks.textStyle.create({ color: '#abcdef' });
assert.strictEqual(testMark.attrs.color, '#abcdef', 'created textStyle mark must carry color attribute');
// ---------------------------------------------------------------------------
// Contract Assertion 12:
// getSchema applies color attribute to configured node types when types option is customized
// ---------------------------------------------------------------------------
const customSchema = getSchema([
Doc,
Paragraph,
Text,
TextStyle,
Color.configure({ types: ['paragraph'] }),
]);
assert.ok(customSchema.nodes.paragraph.spec.attrs.color, 'paragraph node must receive color attribute');
assert.strictEqual(
customSchema.nodes.paragraph.spec.attrs.color.default,
null,
'paragraph color attribute default must be null'
);
console.log('All 12 contract assertions passed successfully.');
}
runContract().catch((err) => {
console.error('Contract failed:', err);
process.exit(1);
});
Origin Seeder
anonymous