Skip to content

Commit

Permalink
Add E2E test for TableOfContentsPlugin (#2675)
Browse files Browse the repository at this point in the history
* Fixed getStyleObjectFromRawCSS function to work for unformatted css strings

* Testing that  handles unformatted css text

* Testing that $getStyleObjectFromRawCss handles unformatted css

* Added TableOfContents

* Renamed TableOfContetnsPlugin file name and added flow file

* Added TableOfContentsPlugin to config files and added styling

* Fixed types

* Added TableOfContentsList as a seperate module

* Fixed type of tag from string to HeadingTagType

* Table of contents updates as user scrolls

* Wrapped plugin in a feature

* Deleted package-lock.json

* Fixed conditioanl rendering syntax

* Removed extra parameter

* package-lock

* fix imports

* Update packages/lexical-playground/src/plugins/TableOfContentsPlugin.tsx

Co-authored-by: Gerard Rovira <zurfyx@users.noreply.github.com>

* Added sticky styling and handled text overflow

* Table of contents updates automtaically on scroll without observing all heading nodes

* Update table correctly  when headings are not visible but exist either up or down

* Fix failing E2E

* Changed isTableOfContentes to showTableOfContents in settings

* Added useEffect to fix memory leak

* Hoisted functions that don't use props

* Renamed isTableOfContets to showTableOfContents

* Changing selectedHeading by observing page top

* resolved lint error

* Refactored scroll up logic

* Added comments

* Added better css

* Changed place of toc div to fix failing test

* Fixed adjacent headings scrolling

* Fixed adjacent headings bug

* Renamed helper methods

* Fixed test

* Added dependency array to useEffect

* Added TableOfContents to dependency array

* Updated dependeny array in useEffect

* Created e2e test file for table of contents plugin

* Added scroll test

* E2E test: Adding heading to editor adds them to table-of-contents

* clean up

* Refactored getEditorElement

* Scrolling callback has better conditions

* Table of contents is now covering all edge cases and doesn't freeze webpage

* Solved page freezing

* Added one more assert statment to second test

* chore(lexical-playground): make directory clear (#2674)

* Conditionally utilize `startTransition` if it's present (#2676)

* Only utilize startTransition if it's available

* Add type annotation

* Run prettier

* fix(lexical-list): remove list breaks if selection in empty (#2672)

* fix(lexical-list): remove list breaks if selection in empty

* chore: add a comment

* chore: add test

* Separate `@lexical/code` into more atomic modules (#2673)

* separate code package into more atomic modules

* remove utils

* named exports

* Fixed typo (#2678)

* fix: path to icons (#2683)

* Fix VALID_TWITTER_URL to allow underscores. (#2690)

* fix(lexical-playground): LexicalTypeaheadMenuPlugin import (#2689)

Use the correct import path that available in NPM package

* Collapse and Expand DevTools Tree Nodes (#2679)

* fix(playground): fix rendering Exclidraw (#2694)

* Make includeHeaders a boolean (#2697)

Changed type for includeHeaders parameter from string to boolean to match the type of the parameter from the $createTableNodeWithDimensions function.

* Remove coverage reports (#2699)

* fix: check if options are empty (#2701)

* feat: Link node with target and rel (#2687)

* OnChangePlugin ignoreInitialChange -> ignoreHistoryMergeTagChange (#2706)

* OnChangePlugin ignoreInitialChange -> ignoreHistoryMergeTag

* .

* default to false because 0.4

Co-authored-by: Karam Qaoud <kqaoud@fb.com>
Co-authored-by: Gerard Rovira <zurfyx@users.noreply.github.com>
Co-authored-by: 子瞻 Luci <haru.lucinyan@gmail.com>
Co-authored-by: Jack Hanford <jackhanford@gmail.com>
Co-authored-by: John Flockton <thegreatercurve@users.noreply.github.com>
Co-authored-by: SalvadorLekan <66782276+SalvadorLekan@users.noreply.github.com>
Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>
Co-authored-by: hiraoka <62982380+y-hiraoka@users.noreply.github.com>
Co-authored-by: Elvin Dzhavadov <elvin.d@outlook.com>
Co-authored-by: Will <will.gutierrez@gmail.com>
Co-authored-by: Bryan <ImSingee@users.noreply.github.com>
Co-authored-by: alinamusuroi <44519061+alinamusuroi@users.noreply.github.com>
Co-authored-by: Andriy Chemerynskiy <andrzej.chem@gmail.com>
  • Loading branch information
14 people authored Jul 26, 2022
1 parent 9f65cbd commit b691cf0
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 37 deletions.
110 changes: 110 additions & 0 deletions packages/lexical-playground/__tests__/e2e/TableOfContents.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import {
assertHTML,
focusEditor,
getElement,
html,
initialize,
repeat,
selectFromFormatDropdown,
sleep,
test,
} from '../utils/index.mjs';

test.describe('Table of Contents', () => {
test.beforeEach(({isCollab, page}) => initialize({isCollab, page}));
test(`Adding headigns to editor adds them to table of contents`, async ({
page,
}) => {
await focusEditor(page);
await selectFromFormatDropdown(page, '.h1');
await page.keyboard.type('Hello');
await page.keyboard.type('\n');
await selectFromFormatDropdown(page, '.h1');
await page.keyboard.type('World!');
const tableOfContents = await getElement(page, 'ul.table-of-contents');
await assertHTML(
tableOfContents,
html`
<div class="heading" role="button" tabindex="0">
<div class="bar"></div>
<li>Hello</li>
</div>
<div class="heading" role="button" tabindex="0">
<div class="bar"></div>
<li>World!</li>
</div>
`,
);
});
test(`Scrolling through headigns in the editor makes them scroll inside the table of contents`, async ({
page,
}) => {
await focusEditor(page);
await selectFromFormatDropdown(page, '.h1');
await page.keyboard.type('Hello');

await repeat(20, () => {
page.keyboard.type('\n');
});
await selectFromFormatDropdown(page, '.h2');
await page.keyboard.type(' World');
await repeat(400, () => {
page.keyboard.type('\n');
});
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await sleep(50);
const tableOfContents = await getElement(page, 'ul.table-of-contents');
await assertHTML(
tableOfContents,
html`
<div class="heading" role="button" tabindex="0">
<div class="bar"></div>
<li>Hello</li>
</div>
<div class="selectedHeading" role="button" tabindex="0">
<div class="circle"></div>
<li class="heading2">World</li>
</div>
`,
);
await page.evaluate(() => window.scrollTo(0, 0));
await sleep(50);
await assertHTML(
tableOfContents,
html`
<div class="selectedHeading" role="button" tabindex="0">
<div class="circle"></div>
<li>Hello</li>
</div>
<div class="heading" role="button" tabindex="0">
<div class="bar"></div>
<li class="heading2">World</li>
</div>
`,
);

await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await sleep(50);
await assertHTML(
tableOfContents,
html`
<div class="heading" role="button" tabindex="0">
<div class="bar"></div>
<li>Hello</li>
</div>
<div class="selectedHeading" role="button" tabindex="0">
<div class="circle"></div>
<li class="heading2">World</li>
</div>
`,
);
});
});
16 changes: 13 additions & 3 deletions packages/lexical-playground/__tests__/utils/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export async function initialize({
isCharLimitUtf8,
isMaxLength,
showNestedEditorTreeView,
showTableOfContents,
}) {
const appSettings = {};
appSettings.isRichText = IS_RICH_TEXT;
Expand All @@ -48,6 +49,9 @@ export async function initialize({
if (showNestedEditorTreeView === undefined) {
appSettings.showNestedEditorTreeView = true;
}
if (showTableOfContents === undefined) {
appSettings.showTableOfContents = true;
}
appSettings.isAutocomplete = !!isAutocomplete;
appSettings.isCharLimit = !!isCharLimit;
appSettings.isCharLimitUtf8 = !!isCharLimitUtf8;
Expand Down Expand Up @@ -385,9 +389,10 @@ export async function getHTML(page, selector = 'div[contenteditable="true"]') {
return element.innerHTML();
}

export async function getEditorElement(page, parentSelector = '.editor-shell') {
const selector = `${parentSelector} div[contenteditable="true"]`;

export async function getElement(
page,
selector = 'div[contenteditable="true"]',
) {
if (IS_COLLAB) {
const leftFrame = await page.frame('left');
await leftFrame.waitForSelector(selector);
Expand All @@ -398,6 +403,11 @@ export async function getEditorElement(page, parentSelector = '.editor-shell') {
}
}

export async function getEditorElement(page, parentSelector = '.editor-shell') {
const selector = `${parentSelector} div[contenteditable="true"]`;
return getElement(selector);
}

export async function waitForSelector(page, selector, options) {
if (IS_COLLAB) {
const leftFrame = await page.frame('left');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
font-size: 15px;
}

.remove-ul-style {
.table-of-contents {
list-style: none;
padding: 0%;
margin-top: 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,60 +57,58 @@ function TableOfContentsList({

useEffect(() => {
function scrollCallback() {
if (
tableOfContents.length !== 0 &&
selectedIndex.current < tableOfContents.length - 1
) {
if (tableOfContents.length !== 0) {
let currentHeading = editor.getElementByKey(
tableOfContents[selectedIndex.current][0],
);
if (currentHeading !== null) {
if (isHeadingBelowTheTopOfThePage(currentHeading)) {
//On natural scroll, user is scrolling up
if (isHeadingAboveViewport(currentHeading)) {
//On natural scroll, user is scrolling down
while (
currentHeading !== null &&
isHeadingBelowTheTopOfThePage(currentHeading) &&
selectedIndex.current > 0
isHeadingAboveViewport(currentHeading) &&
selectedIndex.current < tableOfContents.length - 1
) {
const prevHeading = editor.getElementByKey(
tableOfContents[selectedIndex.current - 1][0],
const nextHeading = editor.getElementByKey(
tableOfContents[++selectedIndex.current][0],
);
if (
prevHeading !== null &&
(isHeadingAboveViewport(prevHeading) ||
isHeadingBelowTheTopOfThePage(prevHeading))
nextHeading !== null &&
isHeadingBelowTheTopOfThePage(nextHeading)
) {
selectedIndex.current--;
break;
} else {
const nextHeadingKey =
tableOfContents[selectedIndex.current][0];
setSelectedKey(nextHeadingKey);
currentHeading = nextHeading;
}
currentHeading = prevHeading;
}
const prevHeadingKey = tableOfContents[selectedIndex.current][0];
setSelectedKey(prevHeadingKey);
} else if (isHeadingAboveViewport(currentHeading)) {
//On natural scroll, user is scrolling down
} else if (isHeadingBelowTheTopOfThePage(currentHeading)) {
//On natural scroll, user is scrolling up
while (
currentHeading !== null &&
isHeadingAboveViewport(currentHeading) &&
selectedIndex.current < tableOfContents.length - 1
isHeadingBelowTheTopOfThePage(currentHeading) &&
selectedIndex.current > 0
) {
const nextHeading = editor.getElementByKey(
tableOfContents[selectedIndex.current + 1][0],
const prevHeading = editor.getElementByKey(
tableOfContents[--selectedIndex.current][0],
);
const prevHeadingKey = tableOfContents[selectedIndex.current][0];
setSelectedKey(prevHeadingKey);
if (
nextHeading !== null &&
(isHeadingAtTheTopOfThePage(nextHeading) ||
isHeadingAboveViewport(nextHeading))
prevHeading !== null &&
isHeadingBelowTheTopOfThePage(currentHeading) &&
(isHeadingAboveViewport(prevHeading) ||
isHeadingAtTheTopOfThePage(prevHeading))
) {
selectedIndex.current++;
break;
} else {
currentHeading = prevHeading;
}
currentHeading = nextHeading;
}
const nextHeadingKey = tableOfContents[selectedIndex.current][0];
setSelectedKey(nextHeadingKey);
}
}
} else {
selectedIndex.current = 0;
}
}
let timerId: ReturnType<typeof setTimeout>;
Expand All @@ -129,7 +127,7 @@ function TableOfContentsList({
}, [tableOfContents, editor]);

return (
<ul className="remove-ul-style">
<ul className="table-of-contents">
{tableOfContents.map(([key, text, tag], index) => (
<div
className={selectedKey === key ? 'selectedHeading' : 'heading'}
Expand Down

2 comments on commit b691cf0

@vercel
Copy link

@vercel vercel bot commented on b691cf0 Jul 26, 2022

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

lexical – ./packages/lexical-website-new

lexical-fbopensource.vercel.app
lexical.dev
lexical-git-main-fbopensource.vercel.app
lexicaljs.com
www.lexical.dev
lexicaljs.org

@vercel
Copy link

@vercel vercel bot commented on b691cf0 Jul 26, 2022

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

lexical-playground – ./packages/lexical-playground

lexical-playground-git-main-fbopensource.vercel.app
playground.lexical.dev
lexical-playground.vercel.app
lexical-playground-fbopensource.vercel.app

Please sign in to comment.