Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
LEGACY_EVENTS,
mergeTableCells,
pasteFromClipboard,
resizeTableCell,
selectCellFromTableCoord,
selectCellsFromTableCords,
selectFromAdditionalStylesDropdown,
Expand Down Expand Up @@ -5948,6 +5949,185 @@ test.describe.parallel('Tables', () => {
);
});

test(`Can paste tables inside table cells (with hasNestedTables)`, async ({
page,
isPlainText,
isCollab,
}) => {
test.skip(isPlainText);
await initialize({hasNestedTables: true, isCollab, page});
await focusEditor(page);

// Create and copy a table
await insertTable(page, 2, 2);
await page.keyboard.type('test inner table');
await selectAll(page);
await withExclusiveClipboardAccess(async () => {
const clipboard = await copyToClipboard(page);
await page.keyboard.press('Backspace');
await moveToEditorBeginning(page);

// Create another table and try to paste the first table into a cell
await insertTable(page, 2, 2);
await click(page, '.PlaygroundEditorTheme__tableCell:first-child');
await pasteFromClipboard(page, clipboard);
});

// Verify that a nested table was pasted into the cell
await assertHTML(
page,
html`
<p><br /></p>
<table>
<colgroup>
<col style="width: 92px" />
<col style="width: 92px" />
</colgroup>
<tr>
<th>
<p><br /></p>
<table>
<colgroup>
<col style="width: 92px" />
<col style="width: 92px" />
</colgroup>
<tr>
<th>
<p>
<span data-lexical-text="true">test inner table</span>
</p>
</th>
<th>
<p><br /></p>
</th>
</tr>
<tr>
<th>
<p><br /></p>
</th>
<td>
<p><br /></p>
</td>
</tr>
</table>
<p><br /></p>
</th>
<th>
<p><br /></p>
</th>
</tr>
<tr>
<th>
<p><br /></p>
</th>
<td>
<p><br /></p>
</td>
</tr>
</table>
<p><br /></p>
`,
undefined,
{ignoreClasses: true, ignoreDir: true},
);
});

test(`Can paste and autofit tables inside table cells (with hasNestedTables, hasFitNestedTables)`, async ({
page,
isPlainText,
isCollab,
}) => {
test.skip(isPlainText);
await initialize({
hasFitNestedTables: true,
hasNestedTables: true,
isCollab,
page,
});
await focusEditor(page);

// Create and copy a table
await insertTable(page, 2, 2);

await page.keyboard.type('test inner table');

await selectAll(page);
await withExclusiveClipboardAccess(async () => {
const clipboard = await copyToClipboard(page);
await page.keyboard.press('Backspace');
await moveToEditorBeginning(page);

// Create another table and try to paste the first table into a cell
await insertTable(page, 2, 2);
// Resize outer table cell (92px default + 50px = 142px)
await resizeTableCell(page, 'tr:nth-child(2) > th:nth-child(1)', 50);
await click(
page,
'tr:nth-child(2) > th:nth-child(1) > .PlaygroundEditorTheme__paragraph',
);

await pasteFromClipboard(page, clipboard);
});

// Verify that a nested table was pasted into the cell
await assertHTML(
page,
html`
<p><br /></p>
<table>
<colgroup>
<col style="width: 142px" />
<col style="width: 92px" />
</colgroup>
<tr>
<th>
<p><br /></p>
<table>
<colgroup>
<col style="width: 62.5px" />
<col style="width: 62.5px" />
</colgroup>
<tr>
<th>
<p>
<span data-lexical-text="true">test inner table</span>
</p>
</th>
<th>
<p><br /></p>
</th>
</tr>
<tr>
<th>
<p><br /></p>
</th>
<td>
<p><br /></p>
</td>
</tr>
</table>
<p><br /></p>
</th>
<th>
<p><br /></p>
</th>
</tr>
<tr>
<th>
<p><br /></p>
</th>
<td>
<p><br /></p>
</td>
</tr>
</table>
<p><br /></p>
`,
undefined,
{ignoreClasses: true, ignoreDir: true},
);
});

test(`Click and drag to create selection in Firefox #7245`, async ({
page,
isPlainText,
Expand Down
62 changes: 51 additions & 11 deletions packages/lexical-playground/__tests__/utils/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,31 @@ function wrapAndSlowDown(method, delay) {
};
}

export function wrapTableHtml(expected, {ignoreClasses = false} = {}) {
export function wrapTableHtml(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For whatever reason, "dir="auto" wasn't being applied on the nested table when it was pasted. Instead of debugging that, I just added ignoreDir. Then I tried to make it a bit more explicit about what it was doing (moving "dir" up to the div, and optionally adding the scrollable wrapper class).

expected,
{ignoreClasses = false, ignoreDir = false} = {},
) {
return html`
${expected
.replace(
/<table([^>]*)(dir="\w+")([^>]*)>/g,
`<div $2${
ignoreClasses
? ''
: ' class="PlaygroundEditorTheme__tableScrollableWrapper"'
}><table$1$3>`,
)
.replace(/<table(\s[^>]*)?>/g, (match, rawAttrs = '') => {
const attrs = [...rawAttrs.matchAll(/(\w+)=["']([^"']*)["']/g)].map(
(m) => [m[1], m[2]],
);
const dirAttr = attrs.find(([k]) => k === 'dir');
const divAttrs = [
dirAttr,
!ignoreClasses && [
'class',
'PlaygroundEditorTheme__tableScrollableWrapper',
],
]
.filter(Boolean)
.map(([k, v]) => `${k}="${v}"`);
const tableAttrs = attrs
.filter(([k]) => k !== 'dir')
.map(([k, v]) => `${k}="${v}"`);
return `<div ${divAttrs.join(' ')}><table ${tableAttrs.join(' ')}>`;
})
.replace(/<\/table>/g, '</table></div>')}
`;
}
Expand All @@ -82,6 +96,7 @@ export async function initialize({
isMaxLength,
hasLinkAttributes,
hasNestedTables,
hasFitNestedTables,
showNestedEditorTreeView,
tableCellMerge,
tableCellBackgroundColor,
Expand Down Expand Up @@ -117,6 +132,7 @@ export async function initialize({
appSettings.isMaxLength = !!isMaxLength;
appSettings.hasLinkAttributes = !!hasLinkAttributes;
appSettings.hasNestedTables = !!hasNestedTables;
appSettings.hasFitNestedTables = !!hasFitNestedTables;
if (tableCellMerge !== undefined) {
appSettings.tableCellMerge = tableCellMerge;
}
Expand Down Expand Up @@ -230,11 +246,13 @@ async function assertHTMLOnPageOrFrame(
expectedHtml,
ignoreClasses,
ignoreInlineStyles,
ignoreDir,
frameName,
actualHtmlModificationsCallback = (actualHtml) => actualHtml,
) {
const expected = await prettifyHTML(expectedHtml.replace(/\n/gm, ''), {
ignoreClasses,
ignoreDir,
ignoreInlineStyles,
});
return await expect(async () => {
Expand All @@ -246,6 +264,7 @@ async function assertHTMLOnPageOrFrame(
);
let actual = await prettifyHTML(actualHtml.replace(/\n/gm, ''), {
ignoreClasses,
ignoreDir,
ignoreInlineStyles,
});

Expand Down Expand Up @@ -283,7 +302,7 @@ export async function assertHTML(
page,
expectedHtml,
expectedHtmlFrameRight = expectedHtml,
{ignoreClasses = false, ignoreInlineStyles = false} = {},
{ignoreClasses = false, ignoreInlineStyles = false, ignoreDir = false} = {},
actualHtmlModificationsCallback,
) {
if (IS_COLLAB) {
Expand All @@ -293,6 +312,7 @@ export async function assertHTML(
expectedHtml,
ignoreClasses,
ignoreInlineStyles,
ignoreDir,
'left frame',
actualHtmlModificationsCallback,
),
Expand All @@ -301,6 +321,7 @@ export async function assertHTML(
expectedHtmlFrameRight,
ignoreClasses,
ignoreInlineStyles,
ignoreDir,
'right frame',
actualHtmlModificationsCallback,
),
Expand All @@ -311,6 +332,7 @@ export async function assertHTML(
expectedHtml,
ignoreClasses,
ignoreInlineStyles,
ignoreDir,
'page',
actualHtmlModificationsCallback,
);
Expand Down Expand Up @@ -851,7 +873,7 @@ export async function dragImage(

export async function prettifyHTML(
string,
{ignoreClasses, ignoreInlineStyles} = {},
{ignoreClasses, ignoreInlineStyles, ignoreDir} = {},
) {
let output = string;

Expand All @@ -863,6 +885,10 @@ export async function prettifyHTML(
output = output.replace(/\sstyle="([^"]*)"/g, '');
}

if (ignoreDir) {
output = output.replace(/\sdir="([^"]*)"/g, '');
}

output = output.replace(/\s__playwright_target__="[^"]+"/, '');

return await prettier.format(output, {
Expand Down Expand Up @@ -1031,6 +1057,20 @@ export async function insertTableColumnAfter(page) {
await click(page, '.item[data-test-id="table-insert-column-after"]');
}

export async function resizeTableCell(page, selector, width = 0, height = 0) {
await click(page, selector);
const resizerBoundingBox = await selectorBoundingBox(
page,
'.TableCellResizer__resizer:first-child',
);
const x = resizerBoundingBox.x + resizerBoundingBox.width / 2;
const y = resizerBoundingBox.y + resizerBoundingBox.height / 2;
await page.mouse.move(x, y);
await page.mouse.down();
await page.mouse.move(x + width, y + height);
await page.mouse.up();
}

export async function mergeTableCells(page) {
await clickTableCellActiveButton(page);
await click(page, '.item[data-test-id="table-merge-cells"]');
Expand Down
2 changes: 2 additions & 0 deletions packages/lexical-playground/src/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export default function Editor(): JSX.Element {
isCharLimit,
hasLinkAttributes,
hasNestedTables,
hasFitNestedTables,
isCharLimitUtf8,
isRichText,
showTreeView,
Expand Down Expand Up @@ -239,6 +240,7 @@ export default function Editor(): JSX.Element {
hasCellMerge={tableCellMerge}
hasCellBackgroundColor={tableCellBackgroundColor}
hasHorizontalScroll={tableHorizontalScroll}
hasFitNestedTables={hasFitNestedTables}
hasNestedTables={hasNestedTables}
/>
<TableCellResizer />
Expand Down
Loading