Skip to content

Commit

Permalink
unit tests for table import normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
etrepum committed Dec 6, 2024
1 parent 7fd2ed4 commit 21872b7
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 13 deletions.
14 changes: 1 addition & 13 deletions packages/lexical-react/src/shared/useCharacterLimit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
OverflowNode,
} from '@lexical/overflow';
import {$rootTextContent} from '@lexical/text';
import {$dfs, mergeRegister} from '@lexical/utils';
import {$dfs, $unwrapNode, mergeRegister} from '@lexical/utils';
import {
$getSelection,
$isElementNode,
Expand Down Expand Up @@ -254,18 +254,6 @@ function $wrapNode(node: LexicalNode): OverflowNode {
return overflowNode;
}

function $unwrapNode(node: OverflowNode): LexicalNode | null {
const children = node.getChildren();
const childrenLength = children.length;

for (let i = 0; i < childrenLength; i++) {
node.insertBefore(children[i]);
}

node.remove();
return childrenLength > 0 ? children[childrenLength - 1] : null;
}

export function $mergePrevious(overflowNode: OverflowNode): void {
const previousNode = overflowNode.getPreviousSibling();

Expand Down
312 changes: 312 additions & 0 deletions packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,318 @@ describe('LexicalTableNode tests', () => {
);
});

test('Copy table with caption/tbody/thead/tfoot from an external source', async () => {
const {editor} = testEnv;

const dataTransfer = new DataTransferMock();
dataTransfer.setData(
'text/html',
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead
html`
<meta charset="utf-8" />
<table
style="box-sizing: border-box; border-collapse: collapse; border: 2px solid rgb(140, 140, 140); font-family: sans-serif; font-size: 0.8rem; letter-spacing: 1px; color: rgb(21, 20, 26); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
<caption
style="box-sizing: border-box; caption-side: bottom; padding: 10px;">
Council budget (in £) 2018
</caption>
<thead
style="box-sizing: border-box; background-color: rgb(44, 94, 119); color: rgb(255, 255, 255);">
<tr style="box-sizing: border-box;">
<th
scope="col"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px;">
Items
</th>
<th
scope="col"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px;">
Expenditure
</th>
</tr>
</thead>
<tbody
style="box-sizing: border-box; background-color: rgb(228, 240, 245);">
<tr style="box-sizing: border-box;">
<th
scope="row"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px;">
Donuts
</th>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center;">
3,000
</td>
</tr>
<tr style="box-sizing: border-box;">
<th
scope="row"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px;">
Stationery
</th>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center;">
18,000
</td>
</tr>
</tbody>
<tfoot
style="box-sizing: border-box; background-color: rgb(44, 94, 119); color: rgb(255, 255, 255);">
<tr style="box-sizing: border-box;">
<th
scope="row"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px;">
Totals
</th>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center;">
21,000
</td>
</tr>
</tfoot>
</table>
`,
);
await editor.update(() => {
const selection = $getSelection();
invariant(
$isRangeSelection(selection),
'isRangeSelection(selection)',
);
$insertDataTransferForRichText(dataTransfer, selection, editor);
});
// Here we are testing the createDOM, not the exportDOM, so the tbody is not there
expectTableHtmlToBeEqual(
testEnv.innerHTML,
html`
<table class="test-table-class">
<colgroup>
<col />
<col />
</colgroup>
<tr style="text-align: start">
<th>
<p dir="ltr">
<span data-lexical-text="true">Items</span>
</p>
</th>
<th>
<p dir="ltr">
<span data-lexical-text="true">Expenditure</span>
</p>
</th>
</tr>
<tr style="text-align: start">
<th>
<p dir="ltr">
<span data-lexical-text="true">Donuts</span>
</p>
</th>
<td>
<p style="text-align: center;">
<span data-lexical-text="true">3,000</span>
</p>
</td>
</tr>
<tr style="text-align: start">
<th>
<p dir="ltr">
<span data-lexical-text="true">Stationery</span>
</p>
</th>
<td>
<p style="text-align: center;">
<span data-lexical-text="true">18,000</span>
</p>
</td>
</tr>
<tr style="text-align: start">
<th>
<p dir="ltr">
<span data-lexical-text="true">Totals</span>
</p>
</th>
<td>
<p style="text-align: center;">
<span data-lexical-text="true">21,000</span>
</p>
</td>
</tr>
</table>
`,
);
});

test('Copy table with caption from an external source', async () => {
const {editor} = testEnv;

const dataTransfer = new DataTransferMock();
dataTransfer.setData(
'text/html',
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption
html`
<meta charset="utf-8" />
<table
style="box-sizing: border-box; border-collapse: collapse; border: 2px solid rgb(140, 140, 140); font-family: sans-serif; font-size: 0.8rem; letter-spacing: 1px; color: rgb(21, 20, 26); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
<caption
style="box-sizing: border-box; caption-side: bottom; padding: 10px; font-weight: bold;">
He-Man and Skeletor facts
</caption>
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center; background-color: rgb(240, 240, 240);"></td>
<th
class="heman"
scope="col"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; background-color: rgb(230, 230, 230); font: 1.4rem molot; text-shadow: rgb(255, 255, 255) 1px 1px 1px, rgb(0, 0, 0) 2px 2px 1px;">
He-Man
</th>
<th
class="skeletor"
scope="col"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; background-color: rgb(230, 230, 230); font: 1.7rem rapscallion; letter-spacing: 3px; text-shadow: rgb(255, 255, 255) 1px 1px 0px, rgb(0, 0, 0) 0px 0px 9px;">
Skeletor
</th>
</tr>
<tr style="box-sizing: border-box;">
<th
scope="row"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; background-color: rgb(230, 230, 230);">
Role
</th>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center; background-color: rgb(250, 250, 250);">
Hero
</td>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center; background-color: rgb(250, 250, 250);">
Villain
</td>
</tr>
<tr style="box-sizing: border-box;">
<th
scope="row"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; background-color: rgb(230, 230, 230);">
Weapon
</th>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center; background-color: rgb(240, 240, 240);">
Power Sword
</td>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center; background-color: rgb(240, 240, 240);">
Havoc Staff
</td>
</tr>
<tr style="box-sizing: border-box;">
<th
scope="row"
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; background-color: rgb(230, 230, 230);">
Dark secret
</th>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center; background-color: rgb(250, 250, 250);">
Expert florist
</td>
<td
style="box-sizing: border-box; border: 1px solid rgb(160, 160, 160); padding: 8px 10px; text-align: center; background-color: rgb(250, 250, 250);">
Cries at romcoms
</td>
</tr>
</tbody>
</table>
`,
);
await editor.update(() => {
const selection = $getSelection();
invariant(
$isRangeSelection(selection),
'isRangeSelection(selection)',
);
$insertDataTransferForRichText(dataTransfer, selection, editor);
});
// Here we are testing the createDOM, not the exportDOM, so the tbody is not there
expectTableHtmlToBeEqual(
testEnv.innerHTML,
html`
<table class="test-table-class">
<colgroup>
<col />
<col />
<col />
</colgroup>
<tr style="text-align: start">
<td style="background-color: rgb(240, 240, 240)">
<p style="text-align: center"><br /></p>
</td>
<th style="background-color: rgb(230, 230, 230)">
<p dir="ltr">
<span data-lexical-text="true">He-Man</span>
</p>
</th>
<th style="background-color: rgb(230, 230, 230)">
<p dir="ltr">
<span data-lexical-text="true">Skeletor</span>
</p>
</th>
</tr>
<tr style="text-align: start">
<th style="background-color: rgb(230, 230, 230)">
<p dir="ltr">
<span data-lexical-text="true">Role</span>
</p>
</th>
<td style="background-color: rgb(250, 250, 250)">
<p dir="ltr" style="text-align: center">
<span data-lexical-text="true">Hero</span>
</p>
</td>
<td style="background-color: rgb(250, 250, 250)">
<p dir="ltr" style="text-align: center">
<span data-lexical-text="true">Villain</span>
</p>
</td>
</tr>
<tr style="text-align: start">
<th style="background-color: rgb(230, 230, 230)">
<p dir="ltr">
<span data-lexical-text="true">Weapon</span>
</p>
</th>
<td style="background-color: rgb(240, 240, 240)">
<p dir="ltr" style="text-align: center">
<span data-lexical-text="true">Power Sword</span>
</p>
</td>
<td style="background-color: rgb(240, 240, 240)">
<p dir="ltr" style="text-align: center">
<span data-lexical-text="true">Havoc Staff</span>
</p>
</td>
</tr>
<tr style="text-align: start">
<th style="background-color: rgb(230, 230, 230)">
<p dir="ltr">
<span data-lexical-text="true">Dark secret</span>
</p>
</th>
<td style="background-color: rgb(250, 250, 250)">
<p dir="ltr" style="text-align: center">
<span data-lexical-text="true">Expert florist</span>
</p>
</td>
<td style="background-color: rgb(250, 250, 250)">
<p dir="ltr" style="text-align: center">
<span data-lexical-text="true">Cries at romcoms</span>
</p>
</td>
</tr>
</table>
`,
);
});

test('Copy table from an external source like gdoc with formatting', async () => {
const {editor} = testEnv;

Expand Down
6 changes: 6 additions & 0 deletions packages/lexical-utils/flow/LexicalUtils.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,9 @@ declare export function $unwrapAndFilterDescendants(
root: ElementNode,
$predicate: (node: LexicalNode) => boolean,
): boolean;

declare export function $firstToLastIterator(node: ElementNode): Iterable<LexicalNode>;

declare export function $lastToFirstIterator(node: ElementNode): Iterable<LexicalNode>;

declare export function $unwrapNode(node: ElementNode): void;
12 changes: 12 additions & 0 deletions packages/lexical-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,3 +833,15 @@ function $childIterator(
},
};
}

/**
* Insert all children before this node, and then remove it.
*
* @param node The ElementNode to unwrap and remove
*/
export function $unwrapNode(node: ElementNode): void {
for (const child of $firstToLastIterator(node)) {
node.insertBefore(child);
}
node.remove();
}

0 comments on commit 21872b7

Please sign in to comment.