-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[lexical-playground] Feature: Add more keyboard shortcuts (#6754)
Co-authored-by: Bob Ippolito <bob@redivi.com>
- Loading branch information
Showing
11 changed files
with
1,663 additions
and
505 deletions.
There are no files selected for viewing
372 changes: 372 additions & 0 deletions
372
packages/lexical-playground/__tests__/e2e/KeyboardShortcuts.spec.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,372 @@ | ||
/** | ||
* 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 { | ||
applyCodeBlock, | ||
applyHeading, | ||
applyNormalFormat, | ||
applyQuoteBlock, | ||
centerAlign, | ||
clearFormatting, | ||
decreaseFontSize, | ||
increaseFontSize, | ||
indent, | ||
justifyAlign, | ||
leftAlign, | ||
outdent, | ||
rightAlign, | ||
selectCharacters, | ||
toggleBold, | ||
toggleBulletList, | ||
toggleChecklist, | ||
toggleInsertCodeBlock, | ||
toggleItalic, | ||
toggleNumberedList, | ||
toggleStrikethrough, | ||
toggleSubscript, | ||
toggleSuperscript, | ||
toggleUnderline, | ||
} from '../keyboardShortcuts/index.mjs'; | ||
import { | ||
assertHTML, | ||
assertSelection, | ||
evaluate, | ||
expect, | ||
focusEditor, | ||
html, | ||
initialize, | ||
test, | ||
textContent, | ||
} from '../utils/index.mjs'; | ||
|
||
const formatTestCases = [ | ||
{ | ||
applyShortcut: (page) => applyNormalFormat(page), | ||
canToggle: false, | ||
format: 'Normal', | ||
}, | ||
{ | ||
applyShortcut: (page) => applyHeading(page, 1), | ||
canToggle: false, | ||
format: 'Heading 1', | ||
}, | ||
{ | ||
applyShortcut: (page) => applyHeading(page, 2), | ||
canToggle: false, | ||
format: 'Heading 2', | ||
}, | ||
{ | ||
applyShortcut: (page) => applyHeading(page, 3), | ||
canToggle: false, | ||
format: 'Heading 3', | ||
}, | ||
{ | ||
applyShortcut: (page) => toggleBulletList(page), | ||
canToggle: true, | ||
format: 'Bulleted List', | ||
}, | ||
{ | ||
applyShortcut: (page) => toggleNumberedList(page), | ||
canToggle: true, | ||
format: 'Numbered List', | ||
}, | ||
{ | ||
applyShortcut: (page) => toggleChecklist(page), | ||
canToggle: true, | ||
format: 'Check List', | ||
}, | ||
{ | ||
applyShortcut: (page) => applyQuoteBlock(page), | ||
canToggle: false, | ||
format: 'Quote', | ||
}, | ||
{ | ||
applyShortcut: (page) => applyCodeBlock(page), | ||
canToggle: false, | ||
format: 'Code Block', | ||
}, | ||
]; | ||
|
||
const alignmentTestCases = [ | ||
{ | ||
alignment: 'Left Align', | ||
applyShortcut: (page) => leftAlign(page), | ||
}, | ||
{ | ||
alignment: 'Center Align', | ||
applyShortcut: (page) => centerAlign(page), | ||
}, | ||
{ | ||
alignment: 'Right Align', | ||
applyShortcut: (page) => rightAlign(page), | ||
}, | ||
{ | ||
alignment: 'Justify Align', | ||
applyShortcut: (page) => justifyAlign(page), | ||
}, | ||
]; | ||
|
||
const additionalStylesTestCases = [ | ||
{ | ||
applyShortcut: (page) => toggleStrikethrough(page), | ||
style: 'Strikethrough', | ||
}, | ||
{ | ||
applyShortcut: (page) => toggleSubscript(page), | ||
style: 'Subscript', | ||
}, | ||
{ | ||
applyShortcut: (page) => toggleSuperscript(page), | ||
style: 'Superscript', | ||
}, | ||
]; | ||
|
||
const DEFAULT_FORMAT = 'Normal'; | ||
|
||
const getSelectedFormat = async (page) => { | ||
return await textContent( | ||
page, | ||
'.toolbar-item.block-controls > .text.dropdown-button-text', | ||
); | ||
}; | ||
|
||
const isDropdownItemActive = async (page, dropdownItemIndex) => { | ||
return await evaluate( | ||
page, | ||
async (_dropdownItemIndex) => { | ||
await document | ||
.querySelector( | ||
'button[aria-label="Formatting options for additional text styles"]', | ||
) | ||
.click(); | ||
|
||
const isActive = await document | ||
.querySelector('.dropdown') | ||
.children[_dropdownItemIndex].classList.contains('active'); | ||
|
||
await document | ||
.querySelector( | ||
'button[aria-label="Formatting options for additional text styles"]', | ||
) | ||
.click(); | ||
|
||
return isActive; | ||
}, | ||
dropdownItemIndex, | ||
); | ||
}; | ||
|
||
test.describe('Keyboard shortcuts', () => { | ||
test.beforeEach(({isPlainText, isCollab, page}) => { | ||
test.skip(isPlainText); | ||
return initialize({isCollab, page}); | ||
}); | ||
|
||
formatTestCases.forEach(({format, applyShortcut, canToggle}) => { | ||
test(`Can use ${format} format with the shortcut`, async ({ | ||
page, | ||
isPlainText, | ||
}) => { | ||
await focusEditor(page); | ||
|
||
if (format === DEFAULT_FORMAT) { | ||
// Apply a different format first | ||
await applyHeading(page, 1); | ||
} | ||
|
||
await applyShortcut(page); | ||
|
||
expect(await getSelectedFormat(page)).toBe(format); | ||
|
||
if (canToggle) { | ||
await applyShortcut(page); | ||
|
||
// Should revert back to the default format | ||
expect(await getSelectedFormat(page)).toBe(DEFAULT_FORMAT); | ||
} | ||
}); | ||
}); | ||
|
||
alignmentTestCases.forEach(({alignment, applyShortcut}, index) => { | ||
test(`Can use ${alignment} with the shortcut`, async ({ | ||
page, | ||
isPlainText, | ||
}) => { | ||
await focusEditor(page); | ||
await applyShortcut(page); | ||
|
||
const selectedAlignment = await textContent( | ||
page, | ||
'.toolbar-item.spaced.alignment > .text.dropdown-button-text', | ||
); | ||
|
||
expect(selectedAlignment).toBe(alignment); | ||
}); | ||
}); | ||
|
||
additionalStylesTestCases.forEach( | ||
({applyShortcut, style}, dropdownItemIndex) => { | ||
test(`Can use ${style} with the shortcut`, async ({ | ||
page, | ||
isPlainText, | ||
}) => { | ||
await focusEditor(page); | ||
await applyShortcut(page); | ||
|
||
expect(await isDropdownItemActive(page, dropdownItemIndex)).toBe(true); | ||
|
||
// Toggle the style off and check if it's off | ||
await focusEditor(page); | ||
await applyShortcut(page); | ||
expect(await isDropdownItemActive(page, dropdownItemIndex)).toBe(false); | ||
}); | ||
}, | ||
); | ||
|
||
test('Can increase and decrease font size with the shortcuts', async ({ | ||
page, | ||
isPlainText, | ||
}) => { | ||
await focusEditor(page); | ||
await increaseFontSize(page); | ||
|
||
const getFontSize = async () => { | ||
return await evaluate(page, () => { | ||
return document.querySelector('.font-size-input').value; | ||
}); | ||
}; | ||
|
||
expect(await getFontSize()).toBe('17'); | ||
await decreaseFontSize(page); | ||
expect(await getFontSize()).toBe('15'); | ||
}); | ||
|
||
test('Can clear formatting with the shortcut', async ({ | ||
page, | ||
isPlainText, | ||
}) => { | ||
await focusEditor(page); | ||
// Apply some formatting first | ||
await page.keyboard.type('abc'); | ||
await selectCharacters(page, 'left', 3); | ||
|
||
await assertSelection(page, { | ||
anchorOffset: 3, | ||
anchorPath: [0, 0, 0], | ||
focusOffset: 0, | ||
focusPath: [0, 0, 0], | ||
}); | ||
|
||
await toggleBold(page); | ||
await toggleItalic(page); | ||
await toggleUnderline(page); | ||
await toggleStrikethrough(page); | ||
await toggleSubscript(page); | ||
|
||
await assertHTML( | ||
page, | ||
html` | ||
<p | ||
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr" | ||
dir="ltr"> | ||
<sub data-lexical-text="true"> | ||
<strong | ||
class="PlaygroundEditorTheme__textUnderlineStrikethrough PlaygroundEditorTheme__textBold PlaygroundEditorTheme__textItalic PlaygroundEditorTheme__textSubscript"> | ||
abc | ||
</strong> | ||
</sub> | ||
</p> | ||
`, | ||
); | ||
|
||
await clearFormatting(page); | ||
|
||
await assertHTML( | ||
page, | ||
html` | ||
<p | ||
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr" | ||
dir="ltr"> | ||
<span data-lexical-text="true">abc</span> | ||
</p> | ||
`, | ||
); | ||
}); | ||
|
||
test('Can toggle Insert Code Block with the shortcut', async ({ | ||
page, | ||
isPlainText, | ||
}) => { | ||
await focusEditor(page); | ||
|
||
const isCodeBlockActive = async () => { | ||
return await evaluate(page, () => { | ||
return document | ||
.querySelector(`button[aria-label="Insert code block"]`) | ||
.classList.contains('active'); | ||
}); | ||
}; | ||
|
||
// Toggle the code block on | ||
await toggleInsertCodeBlock(page); | ||
expect(await isCodeBlockActive()).toBe(true); | ||
|
||
// Toggle the code block off | ||
await toggleInsertCodeBlock(page); | ||
expect(await isCodeBlockActive()).toBe(false); | ||
}); | ||
|
||
test('Can indent and outdent with the shortcuts', async ({ | ||
page, | ||
isPlainText, | ||
}) => { | ||
await focusEditor(page); | ||
await page.keyboard.type('abc'); | ||
await indent(page, 3); | ||
|
||
await assertHTML( | ||
page, | ||
html` | ||
<p | ||
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr PlaygroundEditorTheme__indent" | ||
dir="ltr" | ||
style="padding-inline-start: calc(120px);"> | ||
<span data-lexical-text="true">abc</span> | ||
</p> | ||
`, | ||
); | ||
|
||
await outdent(page, 2); | ||
|
||
await assertHTML( | ||
page, | ||
html` | ||
<p | ||
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr PlaygroundEditorTheme__indent" | ||
dir="ltr" | ||
style="padding-inline-start: calc(40px);"> | ||
<span data-lexical-text="true">abc</span> | ||
</p> | ||
`, | ||
); | ||
|
||
await outdent(page, 1); | ||
|
||
await assertHTML( | ||
page, | ||
html` | ||
<p | ||
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr" | ||
dir="ltr" | ||
style=""> | ||
<span data-lexical-text="true">abc</span> | ||
</p> | ||
`, | ||
); | ||
}); | ||
}); |
Oops, something went wrong.