diff --git a/apps/comments-ui/src/components/content/forms/Form.tsx b/apps/comments-ui/src/components/content/forms/Form.tsx index 1c5b157eb9..bc4ea3fddf 100644 --- a/apps/comments-ui/src/components/content/forms/Form.tsx +++ b/apps/comments-ui/src/components/content/forms/Form.tsx @@ -105,7 +105,8 @@ const FormEditor: React.FC = ({submit, progress, setProgress, c
+ `} + data-testid="form-editor"> { + test('Can comment on a post', async ({page}) => { + const mockedApi = new MockedApi({}); + mockedApi.setMember({}); + + mockedApi.addComment({ + html: '

This is comment 1

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

This is comment 1

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

This is a quote

This is a new line

This is a new paragraph

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

Click here to go to a new page

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

Click Some random text to go to a new page

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

Click here to go to a new page

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

This is line 1
This is line 2

This is a new paragraph

'); + }); + }); +}); + diff --git a/apps/comments-ui/test/e2e/options.test.ts b/apps/comments-ui/test/e2e/options.test.ts index 8cae151d48..3a1c6ef13f 100644 --- a/apps/comments-ui/test/e2e/options.test.ts +++ b/apps/comments-ui/test/e2e/options.test.ts @@ -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; diff --git a/apps/comments-ui/test/utils/MockedApi.ts b/apps/comments-ui/test/utils/MockedApi.ts index 66d5e4cb36..31a6bd99dd 100644 --- a/apps/comments-ui/test/utils/MockedApi.ts +++ b/apps/comments-ui/test/utils/MockedApi.ts @@ -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'); diff --git a/apps/comments-ui/test/utils/e2e.ts b/apps/comments-ui/test/utils/e2e.ts index 67e1ae84b5..0b45f91fdc 100644 --- a/apps/comments-ui/test/utils/e2e.ts +++ b/apps/comments-ui/test/utils/e2e.ts @@ -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 { + 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(`
${text}
`); + 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'; + } +}