mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Added comments-ui editor E2E tests
refs https://github.com/TryGhost/Team/issues/3504
This commit is contained in:
parent
9c2553a06a
commit
5c843545d8
6 changed files with 379 additions and 4 deletions
|
@ -105,7 +105,8 @@ const FormEditor: React.FC<FormEditorProps> = ({submit, progress, setProgress, c
|
|||
<div className={`relative w-full pl-[52px] transition-[padding] delay-100 duration-150 ${reduced && 'pl-0'} ${isOpen && 'pl-[1px] pt-[64px] sm:pl-[52px]'}`}>
|
||||
<div
|
||||
className={`shadow-form hover:shadow-formxl w-full rounded-md border border-none border-slate-50 bg-[rgba(255,255,255,0.9)] px-3 py-4 font-sans text-[16.5px] leading-normal transition-all delay-100 duration-150 focus:outline-0 dark:border-none dark:bg-[rgba(255,255,255,0.08)] dark:text-neutral-300 dark:shadow-transparent ${isOpen ? 'min-h-[144px] cursor-text pb-[68px] pt-2' : 'min-h-[48px] cursor-pointer overflow-hidden hover:border-slate-300'}
|
||||
`}>
|
||||
`}
|
||||
data-testid="form-editor">
|
||||
<EditorContent
|
||||
editor={editor} onMouseDown={stopIfFocused}
|
||||
onTouchStart={stopIfFocused}
|
||||
|
|
|
@ -29,7 +29,8 @@ export function getEditorConfig({placeholder, autofocus = false, content = ''}:
|
|||
autofocus,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: `gh-comment-content focus:outline-0`
|
||||
class: `gh-comment-content focus:outline-0`,
|
||||
'data-testid': 'editor'
|
||||
}
|
||||
},
|
||||
parseOptions: {
|
||||
|
|
306
apps/comments-ui/test/e2e/editor.test.ts
Normal file
306
apps/comments-ui/test/e2e/editor.test.ts
Normal file
|
@ -0,0 +1,306 @@
|
|||
import {Locator, expect, test} from '@playwright/test';
|
||||
import {MockedApi, getHeight, getModifierKey, initialize, selectText, setClipboard} from '../utils/e2e';
|
||||
|
||||
test.describe('Editor', async () => {
|
||||
test('Can comment on a post', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
count: true,
|
||||
title: 'Title'
|
||||
});
|
||||
|
||||
await expect(frame.getByTestId('comment-component')).toHaveCount(1);
|
||||
await expect(frame.getByTestId('count')).toHaveText('1 comment');
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
const editorHeight = await getHeight(editor);
|
||||
|
||||
await editor.click({force: true});
|
||||
|
||||
// Wait for animation to finish
|
||||
await page.waitForTimeout(200);
|
||||
const newEditorHeight = await getHeight(editor);
|
||||
|
||||
expect(newEditorHeight).toBeGreaterThan(editorHeight);
|
||||
|
||||
// Type in the editor
|
||||
await editor.type('Newly added comment');
|
||||
|
||||
// Post the comment
|
||||
const button = await frame.getByTestId('submit-form-button');
|
||||
await button.click();
|
||||
|
||||
// Check editor is empty
|
||||
await expect(editor).toHaveText('');
|
||||
|
||||
// Check the comment is added to the view
|
||||
await expect(frame.getByTestId('comment-component')).toHaveCount(2);
|
||||
await expect(frame.getByTestId('count')).toHaveText('2 comments');
|
||||
|
||||
await expect(frame.getByText('This is comment 1')).toBeVisible();
|
||||
await expect(frame.getByText('Newly added comment')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Can use C to focus editor', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly'
|
||||
});
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
const editorHeight = await getHeight(editor);
|
||||
|
||||
await page.keyboard.press('c');
|
||||
|
||||
// Wait for animation to finish
|
||||
await page.waitForTimeout(200);
|
||||
const newEditorHeight = await getHeight(editor);
|
||||
|
||||
expect(newEditorHeight).toBeGreaterThan(editorHeight);
|
||||
});
|
||||
|
||||
test('Can use CMD+ENTER to submmit', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
count: true,
|
||||
title: 'Title'
|
||||
});
|
||||
|
||||
await expect(frame.getByTestId('comment-component')).toHaveCount(1);
|
||||
await expect(frame.getByTestId('count')).toHaveText('1 comment');
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
const editorHeight = await getHeight(editor);
|
||||
|
||||
await editor.click({force: true});
|
||||
|
||||
// Wait for animation to finish
|
||||
await page.waitForTimeout(200);
|
||||
const newEditorHeight = await getHeight(editor);
|
||||
|
||||
expect(newEditorHeight).toBeGreaterThan(editorHeight);
|
||||
|
||||
// Type in the editor
|
||||
await editor.type('Newly added comment');
|
||||
|
||||
// Post the comment
|
||||
await page.keyboard.press(`${getModifierKey()}+Enter`);
|
||||
|
||||
// Check editor is empty
|
||||
await expect(editor).toHaveText('');
|
||||
|
||||
// Check the comment is added to the view
|
||||
await expect(frame.getByTestId('comment-component')).toHaveCount(2);
|
||||
await expect(frame.getByTestId('count')).toHaveText('2 comments');
|
||||
|
||||
await expect(frame.getByText('This is comment 1')).toBeVisible();
|
||||
await expect(frame.getByText('Newly added comment')).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe('Markdown', () => {
|
||||
test('Can use > to type a quote', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly'
|
||||
});
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
|
||||
await editor.click({force: true});
|
||||
|
||||
// Type in the editor
|
||||
await editor.type('> This is a quote');
|
||||
await page.keyboard.press('Enter');
|
||||
await editor.type('This is a new line');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Enter');
|
||||
await editor.type('This is a new paragraph');
|
||||
|
||||
// Post the comment
|
||||
await page.keyboard.press(`${getModifierKey()}+Enter`);
|
||||
|
||||
await expect(editor).toHaveText('');
|
||||
|
||||
// Check comment
|
||||
expect(mockedApi.comments).toHaveLength(1);
|
||||
expect(mockedApi.comments[0].html).toBe('<blockquote><p>This is a quote</p><p>This is a new line</p></blockquote><p>This is a new paragraph<br></p>');
|
||||
});
|
||||
|
||||
test('Can paste an URL to create a link', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
await setClipboard(page, 'https://www.google.com');
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly'
|
||||
});
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
|
||||
await editor.click({force: true});
|
||||
|
||||
// Check focused
|
||||
const editorEditable = frame.getByTestId('editor');
|
||||
await expect(editorEditable).toBeFocused();
|
||||
|
||||
// Type in the editor
|
||||
await editor.type('Click here to go to a new page');
|
||||
|
||||
// Select 'here'
|
||||
await selectText(editorEditable, /here/);
|
||||
await page.keyboard.press(`${getModifierKey()}+KeyV`);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Click the button
|
||||
const button = await frame.getByTestId('submit-form-button');
|
||||
await button.click();
|
||||
|
||||
await expect(editor).toHaveText('');
|
||||
|
||||
// Check comment
|
||||
expect(mockedApi.comments).toHaveLength(1);
|
||||
expect(mockedApi.comments[0].html).toBe('<p>Click <a target="_blank" rel="noopener noreferrer nofollow" href="https://www.google.com">here</a> to go to a new page</p>');
|
||||
});
|
||||
|
||||
test('Can paste text without creating a url', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
await setClipboard(page, 'Some random text');
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly'
|
||||
});
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
|
||||
await editor.click({force: true});
|
||||
|
||||
// Check focused
|
||||
const editorEditable = frame.getByTestId('editor');
|
||||
await expect(editorEditable).toBeFocused();
|
||||
|
||||
// Type in the editor
|
||||
await editor.type('Click here to go to a new page');
|
||||
|
||||
// Select 'here'
|
||||
await selectText(editorEditable, /here/);
|
||||
await page.keyboard.press(`${getModifierKey()}+KeyV`);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Click the button
|
||||
const button = await frame.getByTestId('submit-form-button');
|
||||
await button.click();
|
||||
|
||||
await expect(editor).toHaveText('');
|
||||
|
||||
// Check comment
|
||||
expect(mockedApi.comments).toHaveLength(1);
|
||||
expect(mockedApi.comments[0].html).toBe('<p>Click Some random text to go to a new page</p>');
|
||||
});
|
||||
|
||||
test('Cannot bold text', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
await setClipboard(page, 'Some random text');
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly'
|
||||
});
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
|
||||
await editor.click({force: true});
|
||||
|
||||
// Check focused
|
||||
const editorEditable = frame.getByTestId('editor');
|
||||
await expect(editorEditable).toBeFocused();
|
||||
|
||||
// Type in the editor
|
||||
await editor.type('Click here to go to a new page');
|
||||
|
||||
// Select 'here'
|
||||
await selectText(editorEditable, /here/);
|
||||
await page.keyboard.press(`${getModifierKey()}+B`);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Click the button
|
||||
const button = await frame.getByTestId('submit-form-button');
|
||||
await button.click();
|
||||
|
||||
await expect(editor).toHaveText('');
|
||||
|
||||
// Check comment
|
||||
expect(mockedApi.comments).toHaveLength(1);
|
||||
expect(mockedApi.comments[0].html).toBe('<p>Click here to go to a new page</p>');
|
||||
});
|
||||
|
||||
test('Can do a soft line break', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly'
|
||||
});
|
||||
|
||||
const editor = frame.getByTestId('form-editor');
|
||||
|
||||
await editor.click({force: true});
|
||||
|
||||
// Check focused
|
||||
const editorEditable = frame.getByTestId('editor');
|
||||
await expect(editorEditable).toBeFocused();
|
||||
|
||||
// Type in the editor
|
||||
await editor.type('This is line 1');
|
||||
await page.keyboard.press('Shift+Enter');
|
||||
await editor.type('This is line 2');
|
||||
await page.keyboard.press('Enter');
|
||||
await editor.type('This is a new paragraph');
|
||||
|
||||
// Click the button
|
||||
const button = await frame.getByTestId('submit-form-button');
|
||||
await button.click();
|
||||
|
||||
await expect(editor).toHaveText('');
|
||||
|
||||
// Check comment
|
||||
expect(mockedApi.comments).toHaveLength(1);
|
||||
expect(mockedApi.comments[0].html).toBe('<p>This is line 1<br>This is line 2</p><p>This is a new paragraph</p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -13,7 +13,7 @@ function rgbToHsl(r: number, g: number, b: number) {
|
|||
h = s = 0; // achromatic
|
||||
} else {
|
||||
var d = max - min;
|
||||
s = Math.round(l > 0.5 ? d / (2 - max - min) : d / (max + min) * 100) / 100;
|
||||
s = Math.round(l > 0.5 ? d / (2 - max - min) : d / (max + min) * 10) / 10;
|
||||
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
|
|
|
@ -166,6 +166,24 @@ export class MockedApi {
|
|||
});
|
||||
|
||||
await page.route(`${path}/members/api/comments/*`, async (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
const payload = JSON.parse(route.request().postData());
|
||||
|
||||
this.#lastCommentDate = new Date();
|
||||
this.addComment({
|
||||
...payload.comments[0],
|
||||
member: this.member
|
||||
});
|
||||
return await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
comments: [
|
||||
this.comments[this.comments.length - 1]
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const url = new URL(route.request().url());
|
||||
|
||||
const p = parseInt(url.searchParams.get('page') ?? '1');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {E2E_PORT} from '../../playwright.config';
|
||||
import {Locator, Page} from '@playwright/test';
|
||||
import {MockedApi} from './MockedApi';
|
||||
import {Page} from '@playwright/test';
|
||||
|
||||
export const MOCKED_SITE_URL = 'https://localhost:1234';
|
||||
export {MockedApi};
|
||||
|
@ -70,3 +70,52 @@ export async function initialize({mockedApi, page, bodyStyle, ...options}: {
|
|||
frame: page.frameLocator('iframe')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Select text range by RegExp.
|
||||
*/
|
||||
export async function selectText(locator: Locator, pattern: string | RegExp): Promise<void> {
|
||||
await locator.evaluate(
|
||||
(element, {pattern: p}) => {
|
||||
let textNode = element.childNodes[0];
|
||||
|
||||
while (textNode.nodeType !== Node.TEXT_NODE && textNode.childNodes.length) {
|
||||
textNode = textNode.childNodes[0];
|
||||
}
|
||||
const match = textNode.textContent?.match(new RegExp(p));
|
||||
if (match) {
|
||||
const range = document.createRange();
|
||||
range.setStart(textNode, match.index!);
|
||||
range.setEnd(textNode, match.index! + match[0].length);
|
||||
const selection = document.getSelection();
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
},
|
||||
{pattern}
|
||||
);
|
||||
}
|
||||
|
||||
export async function getHeight(locator: Locator) {
|
||||
return await locator.evaluate((node) => {
|
||||
return node.clientHeight;
|
||||
});
|
||||
}
|
||||
|
||||
export async function setClipboard(page, text) {
|
||||
const modifier = getModifierKey();
|
||||
await page.setContent(`<div contenteditable>${text}</div>`);
|
||||
await page.focus('div');
|
||||
await page.keyboard.press(`${modifier}+KeyA`);
|
||||
await page.keyboard.press(`${modifier}+KeyC`);
|
||||
}
|
||||
|
||||
export function getModifierKey() {
|
||||
const os = require('os');
|
||||
let platform = os.platform();
|
||||
if (platform === 'darwin') {
|
||||
return 'Meta';
|
||||
} else {
|
||||
return 'Control';
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue