Repository: ueberdosis/tiptap-php
Branch: main
Commit: 6ea321fa6650
Files: 150
Total size: 287.1 KB
Directory structure:
gitextract_xdo5lijc/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── SECURITY.md
│ └── workflows/
│ ├── php-cs-fixer.yml
│ ├── psalm.yml
│ └── run-tests.yml
├── .gitignore
├── .php_cs.dist.php
├── .vscode/
│ └── launch.json
├── LICENSE.md
├── README.md
├── composer.json
├── package.json
├── phpunit.xml.dist
├── psalm.xml.dist
├── src/
│ ├── Core/
│ │ ├── DOMParser.php
│ │ ├── DOMSerializer.php
│ │ ├── Extension.php
│ │ ├── JSONSerializer.php
│ │ ├── Mark.php
│ │ ├── Node.php
│ │ ├── Schema.php
│ │ └── TextSerializer.php
│ ├── Editor.php
│ ├── Extensions/
│ │ ├── Color.php
│ │ ├── FontFamily.php
│ │ ├── StarterKit.php
│ │ └── TextAlign.php
│ ├── Marks/
│ │ ├── Bold.php
│ │ ├── Code.php
│ │ ├── Highlight.php
│ │ ├── Italic.php
│ │ ├── Link.php
│ │ ├── Strike.php
│ │ ├── Subscript.php
│ │ ├── Superscript.php
│ │ ├── TextStyle.php
│ │ └── Underline.php
│ ├── Nodes/
│ │ ├── Blockquote.php
│ │ ├── BulletList.php
│ │ ├── CodeBlock.php
│ │ ├── CodeBlockHighlight.php
│ │ ├── CodeBlockShiki.php
│ │ ├── Details.php
│ │ ├── DetailsContent.php
│ │ ├── DetailsSummary.php
│ │ ├── Document.php
│ │ ├── HardBreak.php
│ │ ├── Heading.php
│ │ ├── HorizontalRule.php
│ │ ├── Image.php
│ │ ├── ListItem.php
│ │ ├── Mention.php
│ │ ├── OrderedList.php
│ │ ├── Paragraph.php
│ │ ├── Table.php
│ │ ├── TableCell.php
│ │ ├── TableHeader.php
│ │ ├── TableRow.php
│ │ ├── TaskItem.php
│ │ ├── TaskList.php
│ │ └── Text.php
│ └── Utils/
│ ├── HTML.php
│ ├── InlineStyle.php
│ └── Minify.php
└── tests/
├── DOMParser/
│ ├── EmojiTest.php
│ ├── EmptyNodesTest.php
│ ├── EmptyTextNodesTest.php
│ ├── Extensions/
│ │ ├── ColorTest.php
│ │ ├── FontFamilyTest.php
│ │ └── TextAlignTest.php
│ ├── KeepContentOfUnknownTagsTest.php
│ ├── Marks/
│ │ ├── BoldTest.php
│ │ ├── CodeTest.php
│ │ ├── CustomMarkTest.php
│ │ ├── HighlightTest.php
│ │ ├── ItalicTest.php
│ │ ├── LinkTest.php
│ │ ├── NestedMarksTest.php
│ │ ├── StrikeTest.php
│ │ ├── SubscriptTest.php
│ │ ├── SuperscriptTest.php
│ │ ├── TextStyleTest.php
│ │ └── UnderlineTest.php
│ ├── MarksInNodesTest.php
│ ├── MultipleMarksTest.php
│ ├── Nodes/
│ │ ├── BlockquoteTest.php
│ │ ├── BulletListTest.php
│ │ ├── CodeBlockTest.php
│ │ ├── DetailsTest.php
│ │ ├── HardBreakTest.php
│ │ ├── HeadingTest.php
│ │ ├── HighPriorityParagraph.php
│ │ ├── HorizontalRuleTest.php
│ │ ├── ImageTest.php
│ │ ├── MentionTest.php
│ │ ├── OrderedListTest.php
│ │ ├── ParagraphTest.php
│ │ └── TableTest.php
│ ├── ParseHTMLPriorityTest.php
│ ├── SpecialCharacterTest.php
│ ├── TaskListTest.php
│ └── WhitespaceTest.php
├── DOMSerializer/
│ ├── ExampleJsonTest.php
│ ├── Extensions/
│ │ ├── ColorTest.php
│ │ ├── FontFamilyTest.php
│ │ └── TextAlignTest.php
│ ├── InputTest.php
│ ├── Marks/
│ │ ├── BoldTest.php
│ │ ├── CodeTest.php
│ │ ├── HighlightTest.php
│ │ ├── ItalicTest.php
│ │ ├── LinkTest.php
│ │ ├── StrikeTest.php
│ │ ├── SubscriptTest.php
│ │ ├── SuperscriptTest.php
│ │ └── UnderlineTest.php
│ ├── MultipleMarksTest.php
│ ├── Nodes/
│ │ ├── BlockquoteTest.php
│ │ ├── BulletListTest.php
│ │ ├── CodeBlockHighlightTest.php
│ │ ├── CodeBlockShikiTest.php
│ │ ├── CodeBlockTest.php
│ │ ├── DetailsTest.php
│ │ ├── HardBreakNodeTest.php
│ │ ├── HeadingTest.php
│ │ ├── HorizontalRuleNodeTest.php
│ │ ├── ImageTest.php
│ │ ├── MentionTest.php
│ │ ├── OrderedListTest.php
│ │ ├── ParagraphTest.php
│ │ ├── TableTest.php
│ │ ├── TaskListTest.php
│ │ └── XSSTest.php
│ └── WrongFormatTest.php
├── Editor/
│ ├── DescendantsTest.php
│ ├── GetDocumentTest.php
│ ├── GetHTMLTest.php
│ ├── GetJSONTest.php
│ ├── GetTextTest.php
│ ├── SanitizeTest.php
│ └── SetContentTest.php
├── Pest.php
├── Schema/
│ ├── GetTopNodeTest.php
│ └── PriorityTest.php
└── Utils/
└── HTMLTest.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# Ignore all test and documentation with "export-ignore".
/.gitattributes export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.php_cs export-ignore
/.github export-ignore
/psalm.xml export-ignore
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing
Contributions are **welcome** and will be fully **credited**.
Please read and understand the contribution guide before creating an issue or pull request.
## Etiquette
This project is open source, and as such, the maintainers give their free time to build and maintain the source code
held within. They make the code freely available in the hope that it will be of use to other developers. It would be
extremely unfair for them to suffer abuse or anger for their hard work.
Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
world that developers are civilized and selfless people.
It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
## Viability
When requesting or submitting new features, first consider whether it might be useful to others. Open
source projects are used by many developers, who may have entirely different needs to your own. Think about
whether or not your feature is likely to be used by other users of the project.
## Procedure
Before filing an issue:
- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.
Before submitting a pull request:
- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
## Requirements
If the project maintainer has any additional requirements, you will find them listed here.
- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
**Happy coding**!
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
title: "[Bug]: "
description: Found a bug? Report it here to help us improve.
labels:
- "bug"
body:
- type: markdown
attributes:
value: "### Please provide details to help us diagnose the bug."
- type: input
id: php_version
attributes:
label: PHP Version
description: The version of PHP you are using.
placeholder: e.g. 8.0
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: Specify the version of Tiptap PHP you are using.
placeholder: 1.0.0
validations:
required: true
- type: textarea
id: problem
attributes:
label: Bug Description
description: Provide a clear and concise description of what the bug is.
placeholder: "The issue occurs when..."
validations:
required: true
- type: markdown
attributes:
value: |
### Additional Information
Please provide any additional information that may help us understand the issue.
- type: textarea
id: expectation
attributes:
label: Expected Behavior
description: Describe what you expected to happen.
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context (Optional)
description: "Add any other context about the problem here, such as screenshots or videos."
- type: checkboxes
attributes:
label: Dependency Updates
description: "Have you updated your dependencies? This can often resolve issues."
options:
- label: Yes, I've updated all my dependencies.
required: true
- type: markdown
attributes:
value: "Thank you for helping us improve our open-source projects by reporting this issue!"
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Request a feature
description: Share ideas for new features
title: "[Feature Request]: "
labels:
- "enhancement"
body:
- type: markdown
attributes:
value: |
## Feature Request
Thank you for considering contributing to Tiptap PHP! We welcome your ideas and suggestions for new features.
- type: textarea
id: feature_description
attributes:
label: "Feature Description"
description: "Please provide a clear and concise description of the feature you would like to see."
placeholder: "e.g. I would like to have a new command to..."
validations:
required: true
- type: textarea
id: additional_context
attributes:
label: "Additional Context"
description: "Please provide any additional context or information that may be helpful in understanding your request."
placeholder: "e.g. use case, related features, etc."
================================================
FILE: .github/SECURITY.md
================================================
# Security Policy
If you discover any security related issues, please email humans@tiptap.dev instead of using the issue tracker.
================================================
FILE: .github/workflows/php-cs-fixer.yml
================================================
name: Check & fix styling
on:
push:
branches:
- main
- develop
- next
- release/*
pull_request:
branches:
- main
- develop
- next
workflow_dispatch:
jobs:
php-cs-fixer:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga
with:
args: --config=.php_cs.dist.php --allow-risky=yes
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Fix styling
================================================
FILE: .github/workflows/psalm.yml
================================================
name: Psalm
on:
push:
branches:
- main
- develop
- next
- release/*
pull_request:
branches:
- main
- develop
- next
workflow_dispatch:
jobs:
psalm:
name: psalm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
coverage: none
- name: Cache composer dependencies
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
- name: Run composer install
run: composer install -n --prefer-dist
- name: Run psalm
run: ./vendor/bin/psalm --output-format=github
================================================
FILE: .github/workflows/run-tests.yml
================================================
name: Tests
on:
push:
branches:
- main
- develop
- next
- release/*
pull_request:
branches:
- main
- develop
- next
workflow_dispatch:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest]
php: [8.1, 8.2, 8.3]
stability: [prefer-lowest, prefer-stable]
node-version: [22]
name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
coverage: none
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.5.1
with:
node-version: ${{ matrix.node-version }}
- name: Load cached dependencies
uses: actions/cache@v4
id: cache
with:
path: |
**/node_modules
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
id: install-dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm install
- name: Setup problem matchers
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install dependencies
run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
- name: Execute tests
run: vendor/bin/pest
================================================
FILE: .gitignore
================================================
.idea
.php_cs
.php_cs.cache
.phpunit.result.cache
build
composer.lock
coverage
docs
phpunit.xml
psalm.xml
vendor
.php-cs-fixer.cache
node_modules
================================================
FILE: .php_cs.dist.php
================================================
in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php')
->notName('*.blade.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return (new PhpCsFixer\Config())
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'trailing_comma_in_multiline' => true,
'phpdoc_scalar' => true,
'unary_operator_spaces' => true,
'binary_operator_spaces' => true,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_var_without_name' => true,
'method_argument_space' => [
'on_multiline' => 'ensure_fully_multiline',
'keep_multiple_spaces_after_comma' => true,
],
'single_trait_insert_per_statement' => true,
])
->setFinder($finder);
================================================
FILE: .vscode/launch.json
================================================
{
"configurations": [
{
"type": "php",
"request": "launch",
"name": "Run Test",
"program": "${workspaceFolder}/vendor/bin/pest",
"args": [
"--filter",
"${input:testFilter}"
],
"runtimeArgs": [
"-dxdebug.mode=debug",
"-dxdebug.start_with_request=trigger"
],
"cwd": "${workspaceFolder}",
"port": 9003
}
],
"inputs": [
{
"type": "promptString",
"id": "testFilter",
"description": "Filter by test",
"default": ""
}
]
}
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
Copyright (c) überdosis Example Text Example Text Paragraph Paragraph Example Text Example Text Example TextExample Text
```
This doesn’t fully adhere to the ProseMirror schema. Some things are supported too, for example aren’t marks allowed in a `CodeBlock`.
If you need better schema support, create an issue with the feature you’re missing.
### Syntax highlighting for code blocks with [highlight.php](https://github.com/scrivo/highlight.php)
The default `CodeBlock` extension doesn’t add syntax highlighting to your code blocks. However, if you want to add syntax highlighting to your code blocks, there’s a special `CodeBlockHighlight` extension.
Swapping our the default one works like that:
```php
(new \Tiptap\Editor([
'extensions' => [
new \Tiptap\Extensions\StarterKit([
'codeBlock' => false,
]),
new \Tiptap\Nodes\CodeBlockHighlight(),
],
]))
->setContent('
')
->getHTML();
// Returns:
// <?php phpinfo()
```
This is still unstyled. You need to [load a CSS file](https://highlightjs.org/download/) to add colors to the output, for example like that:
```html
```
Boom, syntax highlighting! By the way, this is powered by the amazing [scrivo/highlight.php](https://github.com/scrivo/highlight.php).
### Syntax highlighting for code blocks with [Shiki](https://github.com/shikijs/shiki) (Requires Node.js)
There is an alternate syntax highlighter that utilizes [Shiki](https://github.com/shikijs/shiki). Shiki is a beautiful syntax highlighter powered by the same language engine that many code editors use. The major differences from the `CodeBlockHighlight` extensions are:
1. you must install the `shiki` npm package.
2. Shiki code highlighting works by injecting inline styles so pulling in a external css file is not required.
3. you can use most VS Code themes to highlight your code.
To use the Shiki extension, first install the npm package
```bash
npm install shiki
```
Then follow the example below:
```php
(new \Tiptap\Editor([
'extensions' => [
new \Tiptap\Extensions\StarterKit([
'codeBlock' => false,
]),
new \Tiptap\Nodes\CodeBlockShiki(),
],
]))
->setContent('<?php phpinfo()
')
->getHTML();
```
To configure the theme or default language for code blocks pass additonal configuration into the constructor as show below:
```php
(new \Tiptap\Editor([
'extensions' => [
new \Tiptap\Extensions\StarterKit([
'codeBlock' => false,
]),
new \Tiptap\Nodes\CodeBlockShiki([
'theme' => 'github-dark', // default: nord, see https://github.com/shikijs/shiki/blob/main/docs/themes.md
'defaultLanguage' => 'php', // default: html, see https://github.com/shikijs/shiki/blob/main/docs/languages.md
'guessLanguage' => true, // default: true, if the language isn’t passed, it tries to guess the language with highlight.php
]),
],
]))
->setContent('<?php phpinfo()
')
->getHTML();
```
Under the hood the Shiki extension utilizes [Shiki PHP by Spatie](https://github.com/spatie/shiki-php), so please see the documentation for additional details and considerations.
### Convert content to plain text
Content can also be transformed to plain text, for example to put it into a search index.
```php
(new \Tiptap\Editor)
->setContent('<?php phpinfo()Heading
Heading
";
} catch (DomainException $exception) {
$mergedAttributes = HTML::mergeAttributes(
$this->options['HTMLAttributes'],
$HTMLAttributes,
);
$renderedAttributes = HTML::renderAttributes($mergedAttributes);
$content = "";
$content .= $result->value;
$content .= "
";
}
return [
'content' => $content,
];
}
}
================================================
FILE: src/Nodes/CodeBlockShiki.php
================================================
'language-',
'HTMLAttributes' => [],
'defaultLanguage' => 'html',
'theme' => 'nord',
'guessLanguage' => true,
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
$code = $node->content[0]->text ?? '';
// Language is set
if ($node->attrs->language === null) {
$language = $node->attrs->language;
}
// Auto-detect the language
elseif ($this->options['guessLanguage']) {
try {
$highlighter = new Highlighter();
$result = $highlighter->highlightAuto($code);
$language = $result->language;
} catch (Exception $exception) {
//
}
}
// Use the default language
if (! isset($language)) {
$language = $this->options['defaultLanguage'];
}
try {
$content = Shiki::highlight($code, $language, 'nord');
} catch (DomainException $exception) {
$mergedAttributes = HTML::mergeAttributes(
$this->options['HTMLAttributes'],
$HTMLAttributes,
);
$renderedAttributes = HTML::renderAttributes($mergedAttributes);
$content = "";
$content .= htmlentities($code);
$content .= "
";
}
return [
'content' => $content,
];
}
}
================================================
FILE: src/Nodes/Details.php
================================================
false,
'openClassName' => 'is-open',
'HTMLAttributes' => [],
];
}
public function parseHTML()
{
return [
[
'tag' => 'details',
],
];
}
public function addAttributes()
{
if (! $this->options['persist']) {
return [];
}
return [
'open' => [
'default' => false,
'parseHTML' => fn ($DOMNode) => $DOMNode->hasAttribute('open'),
'renderHTML' => fn ($attributes) => $attributes->open ? ['open' => 'open'] : [],
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return [
'details',
HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
0,
];
}
}
================================================
FILE: src/Nodes/DetailsContent.php
================================================
[],
];
}
public function parseHTML(): array
{
return [
[
'tag' => 'div[data-type]',
'getAttrs' => fn ($value): bool => (bool) $value == 'detailsContent',
],
];
}
public function renderHTML($node, $HTMLAttributes = []): array
{
return [
'div',
HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes, ['data-type' => 'detailsContent']),
0,
];
}
}
================================================
FILE: src/Nodes/DetailsSummary.php
================================================
[],
];
}
public function parseHTML(): array
{
return [
[
'tag' => 'summary',
],
];
}
public function renderHTML($node, $HTMLAttributes = []): array
{
return [
'summary',
HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
0,
];
}
}
================================================
FILE: src/Nodes/Document.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'br',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['br', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes)];
}
}
================================================
FILE: src/Nodes/Heading.php
================================================
[1, 2, 3, 4, 5, 6],
'HTMLAttributes' => [],
];
}
public function parseHTML()
{
return array_map(function ($level) {
return [
'tag' => "h{$level}",
'attrs' => [
'level' => $level,
],
];
}, $this->options['levels']);
}
public function renderHTML($node, $HTMLAttributes = [])
{
$hasLevel = in_array($node->attrs->level, $this->options['levels']);
$level = $hasLevel ?
$node->attrs->level :
$this->options['levels'][0];
return [
"h{$level}",
HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
0,
];
}
}
================================================
FILE: src/Nodes/HorizontalRule.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'hr',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['hr', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes)];
}
}
================================================
FILE: src/Nodes/Image.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'img[src]',
],
];
}
public function addAttributes()
{
return [
'src' => [],
'alt' => [],
'title' => [],
'width' => [],
'height' => [],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['img', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
}
}
================================================
FILE: src/Nodes/ListItem.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'li',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['li', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
}
}
================================================
FILE: src/Nodes/Mention.php
================================================
[],
'renderLabel' => fn () => null,
];
}
public function parseHTML()
{
return [
[
'tag' => 'span[data-type="' . self::$name . '"]',
],
];
}
public function addAttributes()
{
return [
'id' => [
'parseHTML' => fn ($DOMNode) => $DOMNode->getAttribute('data-id') ?: null,
'renderHTML' => fn ($attributes) => ['data-id' => $attributes->id ?? null],
],
];
}
public function renderText($node)
{
return $this->options['renderLabel']($node);
}
public function renderHTML($node, $HTMLAttributes = [])
{
return [
'span',
HTML::mergeAttributes(
['data-type' => self::$name],
$this->options['HTMLAttributes'],
$HTMLAttributes,
),
0,
];
}
}
================================================
FILE: src/Nodes/OrderedList.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'ol',
],
];
}
public function addAttributes()
{
return [
'start' => [
'parseHTML' => fn ($DOMNode) => (int) $DOMNode->getAttribute('start') ?: null,
'renderHTML' => fn ($attributes) => ($attributes->start ?? null) ? ['start' => $attributes->start] : null,
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['ol', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
}
}
================================================
FILE: src/Nodes/Paragraph.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'p',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['p', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
}
}
================================================
FILE: src/Nodes/Table.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'table',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return [
'table',
HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
['tbody', 0],
];
}
}
================================================
FILE: src/Nodes/TableCell.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'td',
],
];
}
public function addAttributes()
{
return [
'rowspan' => [
'parseHTML' => fn ($DOMNode) => intval($DOMNode->getAttribute('rowspan')) ?: null,
],
'colspan' => [
'parseHTML' => fn ($DOMNode) => intval($DOMNode->getAttribute('colspan')) ?: null,
],
'colwidth' => [
'parseHTML' => function ($DOMNode) {
$colwidth = $DOMNode->getAttribute('data-colwidth');
if (! $colwidth) {
return null;
}
$widths = array_map(function ($w) {
return intval($w);
}, explode(',', $colwidth));
return $widths;
},
'renderHTML' => function ($attributes) {
if (! isset($attributes->colwidth)) {
return null;
}
return [
'data-colwidth' => join(',', $attributes->colwidth),
];
},
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return [
'td',
HTML::mergeAttributes(
$this->options['HTMLAttributes'],
$HTMLAttributes,
),
0,
];
}
}
================================================
FILE: src/Nodes/TableHeader.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'th',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return [
'th',
HTML::mergeAttributes(
$this->options['HTMLAttributes'],
$HTMLAttributes,
),
0,
];
}
}
================================================
FILE: src/Nodes/TableRow.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'tr',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['tr', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
}
}
================================================
FILE: src/Nodes/TaskItem.php
================================================
[],
];
}
public function addAttributes()
{
return [
'checked' => [
'default' => false,
'renderHTML' => fn ($attributes) => [
'data-checked' => $attributes->checked ?? null,
],
],
];
}
public function parseHTML()
{
return [
[
'tag' => 'li[data-type="' . self::$name . '"]',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return [
'li',
HTML::mergeAttributes(
$this->options['HTMLAttributes'],
$HTMLAttributes,
['data-type' => self::$name],
),
[
'label',
[
'input',
[
'type' => 'checkbox',
'checked' => $node->attrs->checked ?? null
? 'checked'
: null,
],
],
['span'],
],
[
'div',
0,
],
];
}
}
================================================
FILE: src/Nodes/TaskList.php
================================================
[],
];
}
public function parseHTML()
{
return [
[
'tag' => 'ul[data-type="' . self::$name . '"]',
],
];
}
public function renderHTML($node, $HTMLAttributes = [])
{
return ['ul', HTML::mergeAttributes(
$this->options['HTMLAttributes'],
$HTMLAttributes,
['data-type' => self::$name],
), 0];
}
}
================================================
FILE: src/Nodes/Text.php
================================================
'#text',
],
];
}
}
================================================
FILE: src/Utils/HTML.php
================================================
$value) {
// class="foo bar"
if ($key === 'class') {
$attributes['class'] = trim(($attributes['class'] ?? '') . ' ' . $value);
continue;
}
// style="color: red;"
if ($key === 'style') {
$style = rtrim($attributes['style'] ?? '', '; ') . '; ' . rtrim($value ?? '', ';') . '; ';
$attributes['style'] = ltrim(trim($style), '; ');
continue;
}
$attributes[$key] = $value;
}
}
return $attributes;
}
/**
* Render an associative array of attributes
* as a HTML string.
*/
public static function renderAttributes(array $attrs): string
{
// Make boolean values a string, so they can be rendered in HTML
$attrs = array_map(function ($attribute) {
if ($attribute === true) {
return 'true';
}
if ($attribute === false) {
return 'false';
}
return $attribute;
}, $attrs);
$attributes = [];
// class="custom"
foreach (array_filter($attrs) as $name => $value) {
$escapedValue = htmlentities($value);
$attributes[] = " {$name}=\"{$escapedValue}\"";
}
return join($attributes);
}
}
================================================
FILE: src/Utils/InlineStyle.php
================================================
*/
public static function get($DOMNode): array
{
$results = [];
if (! method_exists($DOMNode, 'getAttribute')) {
return [];
}
$style = $DOMNode->getAttribute('style');
preg_match_all(
"/([\w-]+)\s*:\s*([^;]+)\s*;?/",
$style,
$matches,
PREG_SET_ORDER
);
foreach ($matches as $match) {
$results[$match[1]] = $match[2];
}
return $results;
}
public static function hasAttribute($DOMNode, $value): bool
{
$styles = self::get($DOMNode);
if (is_string($value)) {
return in_array($value, array_keys($styles));
}
if (is_array($value)) {
return array_diff($value, $styles) == [];
}
throw new Exception('Can’t compare inline styles to ' . json_encode($value));
}
public static function getAttribute($DOMNode, $attribute): ?string
{
return self::get($DOMNode)[$attribute] ?? null;
}
}
================================================
FILE: src/Utils/Minify.php
================================================
_html = str_replace("\r\n", "\n", trim($html));
$hash = isset($_SERVER['REQUEST_TIME']) ? (string) $_SERVER['REQUEST_TIME'] : (string) time();
$this->_replacementHash = 'MINIFYHTML' . md5($hash);
// replace PREs with placeholders
$this->_html = preg_replace_callback('/\\s*";
$content .= htmlentities($code);
$content .= "]*?>[\\s\\S]*?<\\/pre>)\\s*/iu', [$this, '_removePreCB'], $this->_html);
// trim each line.
$this->_html = preg_replace('/^\\s+|\\s+$/mu', '', $this->_html);
// remove ws around block/undisplayed elements
$this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
. '|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'
. '|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'
. '|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'
. '|ul|video)\\b[^>]*>)/iu', '$1', $this->_html);
// fill placeholders
$this->_html = str_replace(
array_keys($this->_placeholders),
array_values($this->_placeholders),
$this->_html
);
return $this->_html;
}
protected function _removePreCB($m): string
{
return $this->_reservePlace("_replacementHash . count($this->_placeholders) . '%';
$this->_placeholders[$placeholder] = $content;
return $placeholder;
}
}
================================================
FILE: tests/DOMParser/EmojiTest.php
================================================
🔥
Multiple fonts
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextStyle(), new FontFamily(), ], ])) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'marks' => [ [ 'type' => 'textStyle', 'attrs' => [ 'fontFamily' => 'Helvetica Neue, Arial, \'Times New Roman\', "Open Sans", sans-serif', ], ], ], 'text' => 'Multiple fonts', ], ], ], ], ]); }); test('font family extension respects the types option', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextAlign([ 'types' => ['paragraph'], ]), ], ])) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'attrs' => [ 'textAlign' => 'left', ], 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); test('default text align is configureable', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextAlign([ 'types' => ['paragraph'], 'defaultAlignment' => 'center', ]), ], ])) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'attrs' => [ 'textAlign' => 'center', ], 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/KeepContentOfUnknownTagsTest.php ================================================ ExampleExample
Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'bold', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('b with font weight normal is ignored', function () { $html = 'Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); test('span with font weight bold is parsed', function () { $html = 'Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'bold', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('span with font weight 500 is parsed', function () { $html = 'Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'bold', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/CodeTest.php ================================================Example Text';
$result = (new Editor)
->setContent($html)
->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
'marks' => [
[
'type' => 'code',
],
],
],
],
],
],
]);
});
================================================
FILE: tests/DOMParser/Marks/CustomMarkTest.php
================================================
'span',
],
];
}
public function addAttributes()
{
return [
'foo' => [
'parseHTML' => fn ($DOMNode) => $DOMNode->getAttribute('data-foo') ?: null,
],
'fruit' => [],
];
}
}
test('b and strong get rendered correctly', function () {
$html = 'Example text
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new CustomMark, ], ])) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'custom', 'attrs' => [ 'foo' => 'bar', 'fruit' => 'banana', ], ], ], ], [ 'type' => 'text', 'text' => ' text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/HighlightTest.php ================================================ Example Text'; $result = (new Editor([ 'extensions' => [ new \Tiptap\Extensions\StarterKit, new \Tiptap\Marks\Highlight, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'highlight', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('color is ignored by default', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new \Tiptap\Extensions\StarterKit, new \Tiptap\Marks\Highlight, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'highlight', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('color is parsed from data attribute', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new \Tiptap\Extensions\StarterKit, new \Tiptap\Marks\Highlight([ 'multicolor' => true, ]), ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'highlight', 'attrs' => [ 'color' => 'red', ], ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('color is parsed from the background color inline style', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new \Tiptap\Extensions\StarterKit, new \Tiptap\Marks\Highlight([ 'multicolor' => true, ]), ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'highlight', 'attrs' => [ 'color' => '#ffcc00', ], ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/ItalicTest.php ================================================ Example Text'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'italic', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('em gets rendered correctly', function () { $html = 'Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'italic', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('i with font style normal is ignored', function () { $html = 'Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); test('span with font style italic is parsed', function () { $html = 'Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'italic', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/LinkTest.php ================================================ Example Link'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', ], ], ], ], ], ]); }); test('link_mark_has_support_for_rel', function () { $html = 'Example Link'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'rel' => 'noopener', ], ], ], ], ], ]); }); test('link_mark_has_support_for_class', function () { $html = 'Example Link'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'class' => 'tiptap', ], ], ], ], ], ]); }); test('link_mark_has_support_for_target', function () { $html = 'Example Link'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'target' => '_blank', ], ], ], ], ], ]); }); function getValidUrls() { return [ 'https://example.com', 'http://example.com', '/same-site/index.html', '../relative.html', 'mailto:info@example.com', 'ftp://info@example.com', ]; } function getInvalidUrls() { // Copied from https://github.com/ueberdosis/tiptap/blob/next/tests/cypress/integration/extensions/link.spec.ts return [ // A standard JavaScript protocol "javascript:alert(window.origin)", // The protocol is not case sensitive "jAvAsCrIpT:alert(window.origin)", // Characters \x01-\x20 are allowed before the protocol "\x00javascript:alert(window.origin)", "\x01javascript:alert(window.origin)", "\x02javascript:alert(window.origin)", "\x03javascript:alert(window.origin)", "\x04javascript:alert(window.origin)", "\x05javascript:alert(window.origin)", "\x06javascript:alert(window.origin)", "\x07javascript:alert(window.origin)", "\x08javascript:alert(window.origin)", "\x09javascript:alert(window.origin)", "\x0ajavascript:alert(window.origin)", "\x0bjavascript:alert(window.origin)", "\x0cjavascript:alert(window.origin)", "\x0djavascript:alert(window.origin)", "\x0ejavascript:alert(window.origin)", "\x0fjavascript:alert(window.origin)", "\x10javascript:alert(window.origin)", "\x11javascript:alert(window.origin)", "\x12javascript:alert(window.origin)", "\x13javascript:alert(window.origin)", "\x14javascript:alert(window.origin)", "\x15javascript:alert(window.origin)", "\x16javascript:alert(window.origin)", "\x17javascript:alert(window.origin)", "\x18javascript:alert(window.origin)", "\x19javascript:alert(window.origin)", "\x1ajavascript:alert(window.origin)", "\x1bjavascript:alert(window.origin)", "\x1cjavascript:alert(window.origin)", "\x1djavascript:alert(window.origin)", "\x1ejavascript:alert(window.origin)", "\x1fjavascript:alert(window.origin)", // Characters \x09,\x0a,\x0d are allowed inside the protocol "java\x09script:alert(window.origin)", "java\x0ascript:alert(window.origin)", "java\x0dscript:alert(window.origin)", // Characters \x09,\x0a,\x0d are allowed after protocol name before the colon "javascript\x09:alert(window.origin)", "javascript\x0a:alert(window.origin)", "javascript\x0d:alert(window.origin)", ]; } function getJsonContent($url) { return [ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Click me', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => $url, ], ], ], ], ], ], ], ]; } function getHtmlContent($url) { return ''; } test('link_mark_does_output_href_tag_for_valid_JSON_schemas', function () { foreach (getValidUrls() as $url) { $content = getJsonContent($url); $editor = (new Editor([ 'content' => $content, 'extensions' => [ new StarterKit, new Link, ], ])); $result = $editor->getHTML(); expect($result)->toContain($url); } }); test('link_mark_does_not_output_href_tag_for_valid_JSON_schemas', function () { foreach (getInvalidUrls() as $url) { $content = getJsonContent($url); $editor = (new Editor([ 'content' => $content, 'extensions' => [ new StarterKit, new Link, ], ])); $result = $editor->getHTML(); expect($result)->not->toContain($url); } }); test('link_mark_does_output_href_tag_for_valid_HTML_schemas', function () { foreach (getValidUrls() as $url) { $content = getHtmlContent($url); $editor = (new Editor([ 'content' => $content, 'extensions' => [ new StarterKit, new Link, ], ])); $result = $editor->getHTML(); expect($result)->toContain($url); } }); test('link_mark_does_not_output_href_tag_for_valid_HTML_schemas', function () { foreach (getInvalidUrls() as $url) { $content = getHtmlContent($url); $editor = (new Editor([ 'content' => $content, 'extensions' => [ new StarterKit, new Link, ], ])); $result = $editor->getJson(); expect($result)->not->toContain($url); } }); ================================================ FILE: tests/DOMParser/Marks/NestedMarksTest.php ================================================ only bold bold and italic only bold'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'only bold ', 'marks' => [ [ 'type' => 'bold', ], ], ], [ 'type' => 'text', 'text' => 'bold and italic', 'marks' => [ [ 'type' => 'bold', ], [ 'type' => 'italic', ], ], ], [ 'type' => 'text', 'text' => ' only bold', 'marks' => [ [ 'type' => 'bold', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/StrikeTest.php ================================================Example Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'strike', ], ], ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/SubscriptTest.php ================================================ Example Text'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Subscript, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'subscript', ], ], ], ], ], ], ]); }); test('inline style is parsed correctly', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Subscript, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'subscript', ], ], ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/SuperscriptTest.php ================================================ Example Text'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Superscript, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'superscript', ], ], ], ], ], ], ]); }); test('inline style is parsed correctly', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Superscript, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'superscript', ], ], ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/TextStyleTest.php ================================================ Example Text'; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextStyle, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'textStyle', ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]); }); test('span without inline style is ignored', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextStyle, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Marks/UnderlineTest.php ================================================ Example Text'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Underline, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'underline', ], ], ], ], ], ], ]); }); test('inline style is parsed correctly', function () { $html = 'Example Text
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Underline, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'underline', ], ], ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/MarksInNodesTest.php ================================================ Example Text."; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example ', ], [ 'type' => 'text', 'text' => 'Text', 'marks' => [ [ 'type' => 'bold', ], [ 'type' => 'italic', ], ], ], [ 'type' => 'text', 'text' => '.', ], ], ], ], ]); }); test('complex markup gets rendered correctly', function () { $html = 'Some text. Bold Text. Italic Text. Bold and italic Text. Here is a Link.
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'heading', 'attrs' => [ 'level' => '1', ], 'content' => [ [ 'type' => 'text', 'text' => 'Headline 1', ], ], ], [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Some text. ', ], [ 'type' => 'text', 'text' => 'Bold Text', 'marks' => [ [ 'type' => 'bold', ], ], ], [ 'type' => 'text', 'text' => '. ', ], [ 'type' => 'text', 'text' => 'Italic Text', 'marks' => [ [ 'type' => 'italic', ], ], ], [ 'type' => 'text', 'text' => '. ', ], [ 'type' => 'text', 'text' => 'Bold and italic Text', 'marks' => [ [ 'type' => 'bold', ], [ 'type' => 'italic', ], ], ], [ 'type' => 'text', 'text' => '. Here is a ', ], [ 'type' => 'text', 'text' => 'Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', ], ], ], ], [ 'type' => 'text', 'text' => '.', ], ], ], ], ]); }); test('multiple lists gets rendered correctly', function () { $html = 'ordered list item
ordered list item
ordered list item
unordered list item
unordered list item with link
unordered list item
Some Text.
'; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'heading', 'attrs' => [ 'level' => '2', ], 'content' => [ [ 'type' => 'text', 'text' => 'Headline 2', ], ], ], [ 'type' => 'orderedList', 'content' => [ [ 'type' => 'listItem', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'ordered list item', ], ], ], ], ], [ 'type' => 'listItem', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'ordered list item', ], ], ], ], ], [ 'type' => 'listItem', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'ordered list item', ], ], ], ], ], ], ], [ 'type' => 'bulletList', 'content' => [ [ 'type' => 'listItem', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'unordered list item', ], ], ], ], ], [ 'type' => 'listItem', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'unordered list item with ', ], [ 'type' => 'text', 'text' => 'link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', ], ], [ 'type' => 'bold', ], ], ], ], ], ], ], [ 'type' => 'listItem', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'unordered list item', ], ], ], ], ], ], ], [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Some Text.', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/MultipleMarksTest.php ================================================ Example Text'; $result = (new Editor)->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'bold', ], [ 'type' => 'italic', ], ], ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Nodes/BlockquoteTest.php ================================================Paragraph
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'blockquote', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Paragraph', ], ], ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Nodes/BulletListTest.php ================================================Example
Text
Example
Text Test
Tiptap
Example Text';
$result = (new Editor)
->setContent($html)
->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'codeBlock',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
],
]);
});
test('codeBlock with language gets rendered correctly', function () {
$html = 'body { display: none }';
$result = (new Editor)
->setContent($html)
->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'codeBlock',
'attrs' => [
'language' => 'css',
],
'content' => [
[
'type' => 'text',
'text' => 'body { display: none }',
],
],
],
],
]);
});
test('language class prefix is configureable', function () {
$html = 'body { display: none }';
$result =
(new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => [
'languageClassPrefix' => 'custom-language-prefix-',
],
]),
],
]))
->setContent($html)
->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'codeBlock',
'attrs' => [
'language' => 'css',
],
'content' => [
[
'type' => 'text',
'text' => 'body { display: none }',
],
],
],
],
]);
});
test('code block and inline code are rendered correctly', function () {
$html = 'Example Text
body { display: none }';
$result = (new Editor)
->setContent($html)
->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
'marks' => [
[
'type' => 'code',
],
],
],
],
],
[
'type' => 'codeBlock',
'content' => [
[
'type' => 'text',
'text' => 'body { display: none }',
],
],
],
],
]);
});
test('it handles code blocks without a code tag', function () {
$html = 'body { display: none }';
$result = (new Editor)->setContent($html)->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'codeBlock',
'content' => [
[
'type' => 'text',
'text' => 'body { display: none }',
],
],
],
],
]);
});
================================================
FILE: tests/DOMParser/Nodes/DetailsTest.php
================================================
Content
Content
Content
Content
Example
Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', ], ], ], [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Nodes/HeadingTest.php ================================================ Example Text'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'heading', 'attrs' => [ 'level' => 1, ], 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); test('h2 is rendered correctly', function () { $html = 'Rule
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Horizontal', ], ], ], [ 'type' => 'horizontalRule', ], [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Rule', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Nodes/ImageTest.php ================================================ '; $result = (new Editor([ 'extensions' => [ new StarterKit, new Image, ], ]))->setContent($html)->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'image', 'attrs' => [ 'alt' => 'The Finished Dish', 'src' => 'https://example.com/eggs.png', 'title' => 'Eggs in a dish', ], ], ], ]); }); test('image gets rendered correctly when title is missing', function () { $html = '
';
$result = (new Editor([
'extensions' => [
new StarterKit,
new Image,
],
]))->setContent($html)->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'image',
'attrs' => [
'alt' => 'The Finished Dish',
'src' => 'https://example.com/eggs.png',
],
],
],
]);
});
test('image gets rendered correctly when alt is missing', function () {
$html = '
';
$result = (new Editor([
'extensions' => [
new StarterKit,
new Image,
],
]))->setContent($html)->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'image',
'attrs' => [
'src' => 'https://example.com/eggs.png',
'title' => 'Eggs in a dish',
],
],
],
]);
});
================================================
FILE: tests/DOMParser/Nodes/MentionTest.php
================================================
Hey , was geht?';
$output = (new Editor([
'extensions' => [
new StarterKit,
new Mention,
],
]))->setContent($html)->getDocument();
expect($output)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Hey ',
],
[
'type' => 'mention',
'attrs' => [
'id' => 123,
],
],
[
'type' => 'text',
'text' => ', was geht?',
],
],
],
],
]);
});
================================================
FILE: tests/DOMParser/Nodes/OrderedListTest.php
================================================
Example
Text
Example
Text
Example
Text
'; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', ], ], ], [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Text', ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/Nodes/TableTest.php ================================================ ' . 'text in header cell
text in header cell with colspan 2
paragraph 1 in cell with rowspan 2
paragraph 2 in cell with rowspan 2
foo
bar
foo
bar
👩👩👦
"; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => "👩👩👦", ], ], ], ], ]); }); test('umlauts are transformed correctly()', function () { $html = "äöüÄÖÜß
"; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => "äöüÄÖÜß", ], ], ], ], ]); }); test('html entities are transformed correctly()', function () { $html = "<
"; $result = (new Editor) ->setContent($html) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => "<", ], ], ], ], ]); }); ================================================ FILE: tests/DOMParser/TaskListTest.php ================================================Example Text
Example Text
\n" . " Example Text\n" . "
\n" . "\n" .
"Line of Code\n" .
" Line of Code 2\n" .
"Line of Code";
$result = (new Editor)
->setContent($html)
->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
[
'type' => 'codeBlock',
'content' => [
[
'type' => 'text',
'text' => "Line of Code\n Line of Code 2\nLine of Code",
],
],
],
],
]);
});
================================================
FILE: tests/DOMSerializer/ExampleJsonTest.php
================================================
setContent($document)->getHTML();
expect($result)->toEqual('You are able to export your data as HTML or JSON. To pass HTML to the editor use the content slot. To pass JSON to the editor use the doc prop.
red text
'); }); ================================================ FILE: tests/DOMSerializer/Extensions/FontFamilyTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'marks' => [ [ 'type' => 'textStyle', 'attrs' => [ 'fontFamily' => 'Helvetica, Arial, sans-serif', ], ], ], 'text' => 'custom font text', ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit(), new TextStyle(), new FontFamily(), ], ])) ->setContent($json) ->getHTML(); expect($result)->toEqual('custom font text
'); }); ================================================ FILE: tests/DOMSerializer/Extensions/TextAlignTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'paragraph', 'attrs' => [ 'textAlign' => 'center', ], 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextAlign([ 'types' => ['paragraph'], ]), ], ])) ->setContent($document) ->getHTML(); expect($result)->toEqual('Example Text
'); }); test('default text align isn’t rendered', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'attrs' => [ 'textAlign' => 'left', ], 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextAlign([ 'types' => ['paragraph'], ]), ], ])) ->setContent($document) ->getHTML(); expect($result)->toEqual('Example Text
'); }); test('default text align is configureable', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'attrs' => [ 'textAlign' => 'center', ], 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new TextAlign([ 'types' => ['paragraph'], 'defaultAlignment' => 'center', ]), ], ])) ->setContent($document) ->getHTML(); expect($result)->toEqual('Example Text
'); }); ================================================ FILE: tests/DOMSerializer/InputTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('Example Text'); }); test('json gets rendered to html', function () { $document = json_encode([ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ]); $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('Example Text'); }); test('encoding is correct', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Äffchen', ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('Äffchen'); }); test('quotes are not escaped', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => '"Example Text"', ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('"Example Text"'); }); test('escaped attribute values', function () { $result = (new Editor([ 'extensions' => [ new StarterKit, new Image, ], ]))->setContent([ 'type' => 'doc', 'content' => [ [ 'type' => 'image', 'attrs' => [ 'src' => '">Example Text');
});
================================================
FILE: tests/DOMSerializer/Marks/HighlightTest.php
================================================
'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example',
'marks' => [
[
'type' => 'highlight',
'attrs' => [
'color' => 'red',
],
],
],
],
[
'type' => 'text',
'text' => ' Text',
],
],
],
],
];
$result = (new Editor([
'extensions' => [
new \Tiptap\Extensions\StarterKit,
new \Tiptap\Marks\Highlight,
],
]))->setContent($document)->getHTML();
expect($result)->toEqual('Example Text
'); }); test('mark allows specific colors when configured', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example', 'marks' => [ [ 'type' => 'highlight', 'attrs' => [ 'color' => 'red', ], ], ], ], [ 'type' => 'text', 'text' => ' Text', ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new \Tiptap\Extensions\StarterKit, new \Tiptap\Marks\Highlight([ 'multicolor' => true, ]), ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Text
'); }); ================================================ FILE: tests/DOMSerializer/Marks/ItalicTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'italic', ], ], ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('Example Text'); }); ================================================ FILE: tests/DOMSerializer/Marks/LinkTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', ], ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Link'); }); test('link mark has support for rel', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'rel' => 'noopener', ], ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Link'); }); test('link mark has support for class', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'rel' => 'noopener', 'class' => 'tiptap', ], ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Link'); }); test('link mark has support for target', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'target' => '_self', ], ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Link'); }); test('link with marks generates clean output', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://example.com', ], ], ], 'text' => 'Example ', ], [ 'type' => 'text', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://example.com', ], ], [ 'type' => 'bold', ], ], 'text' => 'Link', ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Link'); }); test('link with marks inside node generates clean output', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://example.com', ], ], ], 'text' => 'Example ', ], [ 'type' => 'text', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://example.com', ], ], [ 'type' => 'bold', ], ], 'text' => 'Link', ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual(''); }); test('link mark can disable rel', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'rel' => null, ], ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Link'); }); test('link mark can disable target', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Link', 'marks' => [ [ 'type' => 'link', 'attrs' => [ 'href' => 'https://tiptap.dev', 'target' => null, ], ], ], ], ], ]; $result = (new Editor([ 'extensions' => [ new StarterKit, new Link, ], ]))->setContent($document)->getHTML(); expect($result)->toEqual('Example Link'); }); ================================================ FILE: tests/DOMSerializer/Marks/StrikeTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', 'marks' => [ [ 'type' => 'strike', ], ], ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('Example Text
'); }); test('multiple marks get rendered correctly, with additional mark at the first node', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'marks' => [ [ 'type' => 'italic', ], [ 'type' => 'bold', ], ], 'text' => 'lorem ', ], [ 'type' => 'text', 'marks' => [ [ 'type' => 'bold', ], ], 'text' => 'ipsum', ], ], ]; $result = (new Editor)->setContent($document)->getHTML(); expect($result)->toEqual('lorem ipsum'); }); test('multiple marks get rendered correctly, with additional mark at the last node', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'text', 'marks' => [ [ 'type' => 'italic', ], ], 'text' => 'lorem ', ], [ 'type' => 'text', 'marks' => [ [ 'type' => 'italic', ], [ 'type' => 'bold', ], ], 'text' => 'ipsum', ], ], ]; $result = (new Editor)->setContent($document)->getHTML(); expect($result)->toEqual('lorem ipsum'); }); test('multiple marks get rendered correctly, when overlapping marks exist', function () { $document = [ "type" => "doc", "content" => [ [ "type" => "paragraph", "content" => [ [ "type" => "text", "marks" => [ [ "type" => "bold", ], ], "text" => "lorem ", ], [ "type" => "text", "marks" => [ [ "type" => "bold", ], [ "type" => "italic", ], ], "text" => "ipsum", ], [ "type" => "text", "marks" => [ [ "type" => "italic", ], ], "text" => " dolor", ], [ "type" => "text", "text" => " sit", ], ], ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('lorem ipsum dolor sit
'); }); test('multiple marks get rendered correctly, when overlapping passage with multiple marks exist', function () { $document = [ "type" => "doc", "content" => [ [ "type" => "paragraph", "content" => [ [ "type" => "text", "marks" => [ [ "type" => "bold", ], [ "type" => "strike", ], ], "text" => "lorem ", ], [ "type" => "text", "marks" => [ [ "type" => "italic", ], [ "type" => "bold", ], [ "type" => "strike", ], ], "text" => "ipsum", ], [ "type" => "text", "marks" => [ [ "type" => "strike", ], [ "type" => "italic", ], ], "text" => " dolor", ], ], ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('lorem ipsum dolor
Example Text
'); }); ================================================ FILE: tests/DOMSerializer/Nodes/BlockquoteTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'blockquote', 'content' => [ [ 'type' => 'text', 'text' => 'Example Quote', ], ], ], ], ]; $result = (new Editor)->setContent($document)->getHTML(); expect($result)->toEqual('Example Quote'); }); ================================================ FILE: tests/DOMSerializer/Nodes/BulletListTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'bulletList', 'content' => [ [ 'type' => 'listItem', 'content' => [ [ 'type' => 'text', 'text' => 'first list item', ], ], ], ], ], ], ]; $result = (new Editor) ->setContent($document) ->getHTML(); expect($result)->toEqual('
body { display: none }';
$result = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockHighlight(),
],
]))
->setContent($html)
->getHTML();
expect($result)->toEqual('body { display: none }');
});
test('codeBlockHighlight uses the specified language', function () {
$html = '<?php phpinfo()';
$result = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockHighlight(),
],
]))
->setContent($html)
->getHTML();
expect($result)->toEqual('<?php phpinfo()');
});
test('codeBlockHighlight uses the configured languageClassPrefix', function () {
$html = '<?php phpinfo()';
$result = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockHighlight([
'languageClassPrefix' => 'foo ',
]),
],
]))
->setContent($html)
->getHTML();
expect($result)->toEqual('<?php phpinfo()');
});
test('codeBlockHighlight falls back to just a pre and code tag', function () {
$html = '<?php phpinfo()';
$result = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockHighlight(),
],
]))
->setContent($html)
->getHTML();
expect($result)->toEqual('<?php phpinfo()');
});
================================================
FILE: tests/DOMSerializer/Nodes/CodeBlockShikiTest.php
================================================
[
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockShiki,
],
]));
expect($editor->configuration['extensions'][1])
->toBeInstanceOf(CodeBlockShiki::class);
});
test('default theme can be set for codeBlockShiki extension', function () {
$editor = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockShiki([
'theme' => 'mojave',
]),
],
]));
expect($editor->configuration['extensions'][1]->options['theme'])
->toEqual('mojave');
});
test('default language can be set for codeBlockShiki extension', function () {
$editor = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockShiki([
'defaultLanguage' => 'css',
]),
],
]));
expect($editor->configuration['extensions'][1]->options['defaultLanguage'])
->toEqual('css');
});
test('code block and inline code are rendered correctly', function () {
$html = 'Example Text
body { display: none }';
$result = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockShiki([
'defaultLanguage' => 'css',
]),
],
]))
->setContent($html)
->getDocument();
expect($result)->toEqual([
'type' => 'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
'marks' => [
[
'type' => 'code',
],
],
],
],
],
[
'type' => 'codeBlock',
'attrs' => [
'language' => 'css',
],
'content' => [
[
'type' => 'text',
'text' => 'body { display: none }',
],
],
],
],
]);
});
test('html result is properly rendered', function () {
$html = 'Example Text
body { display: none }';
$result = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => false,
]),
new CodeBlockShiki([
'defaultLanguage' => 'css',
]),
],
]))
->setContent($html)
->getHtml();
// expect($result)
// ->toEqual('Example Text
body { display: none }');
// Build a regex pattern that allows an optional whitespace (\s?) after the colon in each style attribute.
$expectedPattern = '/^Content Text Content Text Content Text some text some text some more text Hey , was geht? Hey @Philipp, was geht? Example Paragraph text in header cell text in header cell text in header cell with colspan 2 paragraph 1 in cell with rowspan 2 paragraph 2 in cell with rowspan 2 foo bar foo bar Example Text Example TextExample Text<\/code><\/p>'
. ''
. ''
. 'body<\/span>'
. '\s?<\/span>'
. '{<\/span>'
. '\s?<\/span>'
. 'display<\/span>'
. ':<\/span>'
. '\s?<\/span>'
. 'none<\/span>'
. '\s?<\/span>'
. '}<\/span>'
. '<\/span><\/code><\/pre>$/';
expect($result)->toMatch($expectedPattern);
});
================================================
FILE: tests/DOMSerializer/Nodes/CodeBlockTest.php
================================================
'doc',
'content' => [
[
'type' => 'codeBlock',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
],
];
$result = (new Editor)
->setContent($document)
->getHTML();
expect($result)->toEqual('
');
});
test('codeBlock language is rendered correctly', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'codeBlock',
'attrs' => [
'language' => 'css',
],
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
],
];
$result = (new Editor)
->setContent($document)
->getHTML();
expect($result)->toEqual('Example Text
');
});
test('codeBlock language prefix is configureable', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'codeBlock',
'attrs' => [
'language' => 'css',
],
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
],
];
$result = (new Editor([
'extensions' => [
new StarterKit([
'codeBlock' => [
'languageClassPrefix' => 'custom-language-prefix-',
],
]),
],
]))
->setContent($document)
->getHTML();
expect($result)->toEqual('Example Text
');
});
================================================
FILE: tests/DOMSerializer/Nodes/DetailsTest.php
================================================
'doc',
'content' => [
[
'type' => 'details',
'content' => [
[
'type' => 'detailsSummary',
'content' => [
[
'type' => 'text',
'text' => 'Summary Text',
],
],
],
[
'type' => 'detailsContent',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Content Text',
],
],
],
],
],
],
],
],
];
$result = (new Editor([
'extensions' => [
new StarterKit,
new Details,
new DetailsSummary,
new DetailsContent,
],
]))->setContent($document)->getHTML();
expect($result)->toEqual('Example TextSummary Text
Summary Text
Summary Text
some more textExample Headline
';
expect((new Editor)->setContent($document)->getHTML())->toEndWith($html);
});
test('forbidden heading levels are transformed to a heading with an allowed level', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'heading',
'attrs' => [
'level' => 7,
],
'content' => [
[
'type' => 'text',
'text' => 'Example Headline',
],
],
],
],
];
$html = 'Example Headline
';
expect((new Editor)->setContent($document)->getHTML())->toEqual($html);
});
test('depending on the configuration heading levels are allowed', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'heading',
'attrs' => [
'level' => 3,
],
'content' => [
[
'type' => 'text',
'text' => 'Example Headline',
],
],
],
],
];
$html = 'Example Headline
';
expect((new Editor([
'extensions' => [
new \Tiptap\Nodes\Heading(['levels' => [1, 2, 3]]),
],
]))->setContent($document)->getHTML())->toEqual($html);
});
test('depending on the configuration heading levels are transformed', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'heading',
'attrs' => [
'level' => 4,
],
'content' => [
[
'type' => 'text',
'text' => 'Example Headline',
],
],
],
],
];
$html = 'Example Headline
';
expect((new Editor([
'extensions' => [
new \Tiptap\Nodes\Heading(['levels' => [1, 2, 3]]),
],
]))->setContent($document)->getHTML())->toEqual($html);
});
test('configured HTMLAttributes are rendered to HTML', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'heading',
'attrs' => [
'level' => 1,
],
'content' => [
[
'type' => 'text',
'text' => 'Example Headline',
],
],
],
],
];
$html = 'Example Headline
';
expect((new Editor([
'extensions' => [
new \Tiptap\Nodes\Heading(['HTMLAttributes' => [
'class' => 'custom-heading-class',
]]),
],
]))->setContent($document)->getHTML())->toEqual($html);
});
test('custom attributes are rendered too', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'heading',
'attrs' => [
'level' => 1,
'color' => 'red',
],
'content' => [
[
'type' => 'text',
'text' => 'Example Headline',
],
],
],
],
];
$html = 'Example Headline
';
class CustomHeading extends \Tiptap\Nodes\Heading
{
public function addAttributes()
{
return [
'color' => [
'renderHTML' => function ($attributes) {
if (! isset($attributes->color)) {
return null;
}
return [
'style' => "color: {$attributes->color}",
];
},
],
];
}
}
$result = (new Editor([
'extensions' => [
new CustomHeading([
'HTMLAttributes' => [
'class' => 'custom-heading-class',
],
]),
],
]))->setContent($document)->getHTML();
expect($result)->toEqual($html);
});
test('inline styles are merged properly', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'heading',
'attrs' => [
'level' => 1,
'color' => 'red',
],
'content' => [
[
'type' => 'text',
'text' => 'Example Headline',
],
],
],
],
];
$html = 'Example Headline
';
class AnotherCustomHeading extends \Tiptap\Nodes\Heading
{
public function addAttributes()
{
return [
'color' => [
'renderHTML' => function ($attributes) {
if (! isset($attributes->color)) {
return null;
}
return [
'style' => "background-color: {$attributes->color}",
];
},
],
];
}
}
$result = (new Editor([
'extensions' => [
new AnotherCustomHeading([
'HTMLAttributes' => [
'style' => 'color: white; ',
],
]),
],
]))->setContent($document)->getHTML();
expect($result)->toEqual($html);
});
================================================
FILE: tests/DOMSerializer/Nodes/HorizontalRuleNodeTest.php
================================================
'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'some text',
],
],
],
[
'type' => 'horizontalRule',
],
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'some more text',
],
],
],
],
];
$result = (new Editor)
->setContent($document)
->getHTML();
expect($result)->toEqual('');
});
================================================
FILE: tests/DOMSerializer/Nodes/MentionTest.php
================================================
'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Hey ',
],
[
'type' => 'mention',
'attrs' => [
'id' => 123,
],
],
[
'type' => 'text',
'text' => ', was geht?',
],
],
],
],
];
$output = (new Editor([
'extensions' => [
new StarterKit,
new Mention,
],
]))->setContent($document)->getHTML();
expect($output)->toEqual('
');
});
test('function orderedList has offset', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'orderedList',
'attrs' => [
'start' => 3,
],
'content' => [
[
'type' => 'listItem',
'content' => [
[
'type' => 'text',
'text' => 'first list item',
],
],
],
],
],
],
];
$result = (new Editor)
->setContent($document)
->getHTML();
expect($result)->toEqual('
');
});
================================================
FILE: tests/DOMSerializer/Nodes/ParagraphTest.php
================================================
'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example Paragraph',
],
],
],
],
];
$result = (new Editor)
->setContent($document)
->getHTML();
expect($result)->toEqual('
');
});
test('table node gets rendered correctly', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'table',
'content' => [
[
'type' => 'tableRow',
'content' => [
[
'type' => 'tableHeader',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'text in header cell',
],
],
],
],
],
[
'type' => 'tableHeader',
'attrs' => [
'colspan' => 2,
'colwidth' => [
100,
0,
],
],
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'text in header cell with colspan 2',
],
],
],
],
],
],
],
[
'type' => 'tableRow',
'content' => [
[
'type' => 'tableCell',
'attrs' => [
'rowspan' => 2,
],
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'paragraph 1 in cell with rowspan 2',
],
],
],
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'paragraph 2 in cell with rowspan 2',
],
],
],
],
],
[
'type' => 'tableCell',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'foo',
],
],
],
],
],
[
'type' => 'tableCell',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'bar',
],
],
],
],
],
],
],
[
'type' => 'tableRow',
'content' => [
[
'type' => 'tableCell',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'foo',
],
],
],
],
],
[
'type' => 'tableCell',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'bar',
],
],
],
],
],
],
],
],
],
],
];
$result = (new Editor([
'extensions' => [
new Table,
new TableRow,
new TableCell,
new TableHeader,
new Paragraph,
new Text,
],
]))->setContent($document)->getHTML();
$html =
'' .
'
';
expect($result)->toEqual($html);
});
================================================
FILE: tests/DOMSerializer/Nodes/TaskListTest.php
================================================
'doc',
'content' => [
[
'type' => 'taskList',
'content' => [
[
'type' => 'taskItem',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
],
],
],
],
],
];
$result = (new Editor([
'extensions' => [
new StarterKit(),
new TaskList(),
new TaskItem(),
],
]))->setContent($document)->getHTML();
expect($result)->toEqual('' .
' ' .
' ' .
' ' .
'' .
' ' .
' ' .
' ' .
' ' .
'' .
' ' .
' ' .
' ' .
'
');
});
test('task item status is rendered correctly', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'taskList',
'content' => [
[
'type' => 'taskItem',
'attrs' => [
'checked' => true,
],
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
],
],
],
],
],
];
$result = (new Editor([
'extensions' => [
new StarterKit(),
new TaskList(),
new TaskItem(),
],
]))->setContent($document)->getHTML();
expect($result)->toEqual('
');
});
================================================
FILE: tests/DOMSerializer/Nodes/XSSTest.php
================================================
'doc',
'content' => [
[
'type' => 'text',
'text' => '',
],
],
];
$result = (new Editor)->setContent($document)->getHTML();
expect($result)->toEqual('<script>alert(1)</script>');
});
================================================
FILE: tests/DOMSerializer/WrongFormatTest.php
================================================
'doc',
'content' => 'test',
];
$result = (new Editor([
'extensions' => [
new StarterKit,
],
]))->setContent($document)->getHTML();
expect($result)->toBeEmpty();
});
test('node content is empty array gets rendered correctly 1', function () {
$document = [
'type' => 'doc',
'content' => [],
];
$result = (new Editor([
'extensions' => [
new StarterKit,
],
]))->setContent($document)->getHTML();
expect($result)->toBeEmpty();
});
test('node content is empty array gets rendered correctly 2', function () {
$document = [
'type' => 'doc',
'content' => [
[], [],
],
];
$result = (new Editor([
'extensions' => [
new StarterKit,
],
]))->setContent($document)->getHTML();
expect($result)->toBeEmpty();
});
test('node content contains empty array gets rendered correctly 3', function () {
$document = [
'type' => 'doc',
'content' => [
[],
'test',
[],
'',
[],
[
'type' => 'codeBlock',
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
[],
[],
[],
'',
],
];
$result = (new Editor([
'extensions' => [
new StarterKit,
],
]))->setContent($document)->getHTML();
expect($result)->toEqual('
');
});
test('node content contains empty array empty mark gets rendered correctly', function () {
$document = [
'type' => 'doc',
'content' => [
[],
'test',
[],
'',
[],
[
'type' => 'text',
'text' => 'Example Link',
'marks' => [
[],
'',
'test',
[
'type' => 'link',
'attrs' => [
'href' => 'https://tiptap.dev',
],
],
],
],
[],
[],
[],
'',
],
];
$result = (new Editor([
'extensions' => [
new StarterKit,
new Link,
],
]))->setContent($document)->getHTML();
expect($result)->toEqual('Example Link');
});
================================================
FILE: tests/Editor/DescendantsTest.php
================================================
'doc',
'content' => [
[
'type' => 'bulletList',
'content' => [
[
'type' => 'listItem',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example',
],
],
],
],
],
[
'type' => 'listItem',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Text ',
],
[
'type' => 'text',
'text' => 'Test',
'marks' => [
[
'type' => 'italic',
],
],
],
],
],
],
],
],
],
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'Example',
],
],
],
],
];
$editor = (new Editor)->setContent($document);
$result = [];
$editor->descendants(function ($node) use (&$result) {
$result[] = $node->type;
});
expect($result)->toEqual([
'doc',
'bulletList',
'listItem',
'paragraph',
'listItem',
'paragraph',
'paragraph',
]);
});
test('updating node attributes in descendants() works', function () {
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'heading',
'attrs' => [
'level' => 2,
],
'content' => [
[
'type' => 'text',
'text' => 'Example Text',
],
],
],
],
];
$editor = (new Editor)->setContent($document);
// Set the level for all headings to 1
$html = $editor->descendants(function (&$node) {
if ($node->type !== 'heading') {
return;
}
$node->attrs->level = 1;
})->getHTML();
expect($html)->toEqual('Example TextExample Text
');
});
================================================
FILE: tests/Editor/GetDocumentTest.php
================================================
Example Text
Example Text
'); }); ================================================ FILE: tests/Editor/GetJSONTest.php ================================================ Example"; $result = (new Editor) ->setContent($html) ->getJSON(); expect($result)->toEqual('{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Example"}]}]}'); }); ================================================ FILE: tests/Editor/GetTextTest.php ================================================ HeadingParagraph
"; $result = (new Editor) ->setContent($html) ->getText(); expect($result)->toEqual("Heading\n\nParagraph"); }); test('getText() only returns one blockSeparator between blocks', function () { $html = "Paragraph
ListItem
Paragraph
"; $result = (new Editor) ->setContent($html) ->getText([ 'blockSeparator' => "\n", ]); expect($result)->toEqual("Heading\nParagraph"); }); ================================================ FILE: tests/Editor/SanitizeTest.php ================================================ 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ]; $result = (new Editor) ->setContent($document) ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ]); }); test('unknown nodes are removed from the document with the sanitized method', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ]; $result = (new Editor)->sanitize($document); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ]); }); test('unknown HTML tags are removed', function () { $document = 'Example Text
'; $result = (new Editor)->setContent($document)->getHTML(); expect($result)->toEqual('Example Text
'); }); test('unknown HTML tags are removed with the sanitize method', function () { $document = 'Example Text
'; $result = (new Editor)->sanitize($document); expect($result)->toEqual('Example Text
'); }); test('unknown nodes are removed from the JSON', function () { $document = json_encode([ 'type' => 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ]); $result = (new Editor) ->setContent($document) ->getJSON(); expect($result)->toEqual(json_encode([ 'type' => 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ])); }); test('unknown nodes are removed from the json with the sanitized method', function () { $document = json_encode([ 'type' => 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ]); $result = (new Editor)->sanitize($document); expect($result)->toEqual(json_encode([ 'type' => 'doc', 'content' => [ [ 'type' => 'foo', 'content' => [ [ 'type' => 'foo', 'text' => 'Example Text', ], ], ], ], ])); }); ================================================ FILE: tests/Editor/SetContentTest.php ================================================ setContent('{ "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Example Text" } ] } ] }')->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); test('arrays are detected', function () { $document = [ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]; $result = (new Editor) ->setContent($document) ->getDocument(); expect($result)->toEqual($document); }); test('html is detected', function () { $result = (new Editor) ->setContent('Example Text
') ->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example ', ], [ 'type' => 'text', 'text' => 'Text', 'marks' => [ [ 'type' => 'bold', ], ], ], ], ], ], ]); }); test('content can be passed to the configuration', function () { $result = (new Editor([ 'content' => 'Example Text
', ]))->getDocument(); expect($result)->toEqual([ 'type' => 'doc', 'content' => [ [ 'type' => 'paragraph', 'content' => [ [ 'type' => 'text', 'text' => 'Example Text', ], ], ], ], ]); }); ================================================ FILE: tests/Pest.php ================================================ in('Feature'); /* |-------------------------------------------------------------------------- | Expectations |-------------------------------------------------------------------------- | | When you're writing tests, you often need to check that values meet certain conditions. The | "expect()" function gives you access to a set of "expectations" methods that you can use | to assert different things. Of course, you may extend the Expectation API at any time. | */ expect()->extend('toBeOne', function () { return $this->toBe(1); }); /* |-------------------------------------------------------------------------- | Functions |-------------------------------------------------------------------------- | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your | project that you don't want to repeat in every file. Here you can also expose helpers as | global functions to help you to reduce the number of lines of code in your test files. | */ function something() { // .. } ================================================ FILE: tests/Schema/GetTopNodeTest.php ================================================ topNode::$name)->toEqual('doc'); }); ================================================ FILE: tests/Schema/PriorityTest.php ================================================ defaultNode::$name)->toEqual('paragraph'); }); ================================================ FILE: tests/Utils/HTMLTest.php ================================================ 'a'], ['class' => 'b'], ]; $result = HTML::mergeAttributes(...$attributes); expect($result)->toEqual(['class' => 'a b']); });