0
Fork 0
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:
Simon Backx 2023-06-28 12:27:45 +02:00 committed by Simon Backx
parent 9c2553a06a
commit 5c843545d8
6 changed files with 379 additions and 4 deletions

View file

@ -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}

View file

@ -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: {

View 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>');
});
});
});

View file

@ -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;

View file

@ -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');

View file

@ -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';
}
}