Skip to content

Commit

Permalink
[lexical-playground] Feature: Add more keyboard shortcuts (#6754)
Browse files Browse the repository at this point in the history
Co-authored-by: Bob Ippolito <bob@redivi.com>
  • Loading branch information
bedre7 and etrepum authored Nov 6, 2024
1 parent 86eba22 commit b284bea
Show file tree
Hide file tree
Showing 11 changed files with 1,663 additions and 505 deletions.
372 changes: 372 additions & 0 deletions packages/lexical-playground/__tests__/e2e/KeyboardShortcuts.spec.mjs
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>
`,
);
});
});
Loading

0 comments on commit b284bea

Please sign in to comment.