0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00
ghost/apps/comments-ui/test/e2e/admin-moderation.test.ts
Kevin Ansfield 3dd33968f5 Improved various aspects of comments app
ref https://linear.app/ghost/issue/PLG-300/

Full details available soon on https://ghost.org/changelog/

- removed `commentImprovements` labs flag conditionals
2024-12-12 19:03:23 +00:00

316 lines
13 KiB
TypeScript

import sinon from 'sinon';
import {MOCKED_SITE_URL, MockedApi, initialize, mockAdminAuthFrame, mockAdminAuthFrame204} from '../utils/e2e';
import {buildReply} from '../utils/fixtures';
import {expect, test} from '@playwright/test';
const admin = MOCKED_SITE_URL + '/ghost/';
test.describe('Admin moderation', async () => {
let mockedApi: MockedApi;
test.beforeEach(async ({}) => {
mockedApi = new MockedApi({});
});
type InitializeTestOptions = {
isAdmin?: boolean;
labs?: boolean;
member?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};
async function initializeTest(page, options: InitializeTestOptions = {}) {
options = {isAdmin: true, labs: false, member: {id: '1', uuid: '12345'}, ...options};
if (options.isAdmin) {
await mockAdminAuthFrame({page, admin});
} else {
await mockAdminAuthFrame204({page, admin});
}
mockedApi.setMember(options.member);
if (options.labs) {
// enable specific labs flags here
}
return await initialize({
mockedApi,
page,
publication: 'Publisher Weekly',
title: 'Member discussion',
count: true,
admin,
labs: {
// enable specific labs flags here
}
});
}
test('skips rendering the auth frame with no comments', async ({page}) => {
await initializeTest(page);
const iframeElement = page.locator('iframe[data-frame="admin-auth"]');
await expect(iframeElement).toHaveCount(0);
});
test('renders the auth frame when there are comments', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
await initializeTest(page);
const iframeElement = page.locator('iframe[data-frame="admin-auth"]');
await expect(iframeElement).toHaveCount(1);
});
test('has no admin options when not signed in to Ghost admin or as member', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
const {frame} = await initializeTest(page, {isAdmin: false, member: null});
await expect(frame.getByTestId('more-button')).toHaveCount(0);
});
test('has no admin options when not signed in to Ghost admin but signed in as member', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
const {frame} = await initializeTest(page, {isAdmin: false, member: {id: '2'}});
// more button shows because it has a report button
await expect(frame.getByTestId('more-button')).toHaveCount(1);
await frame.getByTestId('more-button').nth(0).click();
await expect(frame.getByTestId('hide-button')).not.toBeVisible();
});
test('has admin options when signed in to Ghost admin but not signed in as member', async ({page}) => {
mockedApi.addComment({html: `<p>This is comment 1</p>`});
const {frame} = await initializeTest(page, {member: null});
const moreButtons = frame.getByTestId('more-button');
await expect(moreButtons).toHaveCount(1);
// Admin buttons should be visible
await moreButtons.nth(0).click();
await expect(frame.getByTestId('hide-button')).toBeVisible();
});
test('has admin options when signed in to Ghost admin and as a member', async ({page}) => {
mockedApi.addComment({html: `<p>This is comment 1</p>`});
const {frame} = await initializeTest(page);
const moreButtons = frame.getByTestId('more-button');
await expect(moreButtons).toHaveCount(1);
// Admin buttons should be visible
await moreButtons.nth(0).click();
await expect(frame.getByTestId('hide-button')).toBeVisible();
});
test('member uuid are passed to admin browse api params', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(1);
expect(adminBrowseSpy.called).toBe(true);
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when loading more comments', async ({page}) => {
// create 25 comments
for (let i = 0; i < 25; i++) {
mockedApi.addComment({html: `<p>This is comment ${i}</p>`});
}
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
await frame.getByTestId('pagination-component').click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when changing order', async ({page}) => {
mockedApi.addComment({
html: '<p>This is the oldest</p>',
created_at: new Date('2024-02-01T00:00:00Z')
});
mockedApi.addComment({
html: '<p>This is comment 2</p>',
created_at: new Date('2024-03-02T00:00:00Z')
});
mockedApi.addComment({
html: '<p>This is the newest comment</p>',
created_at: new Date('2024-04-03T00:00:00Z')
});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
const sortingForm = await frame.getByTestId('comments-sorting-form');
await sortingForm.click();
const sortingDropdown = await frame.getByTestId(
'comments-sorting-form-dropdown'
);
const optionSelect = await sortingDropdown.getByText('Newest');
mockedApi.setDelay(100);
await optionSelect.click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when loading more replies', async ({page}) => {
mockedApi.addComment({
html: '<p>This is comment 1</p>',
replies: [
buildReply({html: '<p>This is reply 1</p>'}),
buildReply({html: '<p>This is reply 2</p>'}),
buildReply({html: '<p>This is reply 3</p>'}),
buildReply({html: '<p>This is reply 4</p>'}),
buildReply({html: '<p>This is reply 5</p>'}),
buildReply({html: '<p>This is reply 6</p>'})
]
});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getReplies');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
const comment = comments.nth(0);
await comment.getByTestId('reply-pagination-button').click();
const lastCall = adminBrowseSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('member uuid gets set when reading a comment (after unhiding)', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminReadSpy = sinon.spy(mockedApi.adminRequestHandlers, 'getOrUpdateComment');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(2);
await expect(comments.nth(1)).toContainText('Hidden for members');
const moreButtons = comments.nth(1).getByTestId('more-button');
await moreButtons.click();
await moreButtons.getByTestId('show-button').click();
await expect(comments.nth(1)).not.toContainText('Hidden for members');
const lastCall = adminReadSpy.lastCall.args[0];
const url = new URL(lastCall.request().url());
expect(url.searchParams.get('impersonate_member_uuid')).toBe('12345');
});
test('hidden comments are not displayed for non-admins', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page, {isAdmin: false});
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(1);
expect(adminBrowseSpy.called).toBe(false);
});
test('hidden comments are displayed for admins', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>'});
mockedApi.addComment({html: '<p>This is comment 2</p>', status: 'hidden'});
const adminBrowseSpy = sinon.spy(mockedApi.adminRequestHandlers, 'browseComments');
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
await expect(comments).toHaveCount(2);
await expect(comments.nth(1)).toContainText('Hidden for members');
expect(adminBrowseSpy.called).toBe(true);
});
test('can hide and show comments', async ({page}) => {
[1,2].forEach(i => mockedApi.addComment({html: `<p>This is comment ${i}</p>`}));
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
// Hide the 2nd comment
const moreButtons = frame.getByTestId('more-button');
await moreButtons.nth(1).click();
await moreButtons.nth(1).getByText('Hide comment').click();
const secondComment = comments.nth(1);
await expect(secondComment).toContainText('Hidden for members');
// Check can show it again
await moreButtons.nth(1).click();
await moreButtons.nth(1).getByText('Show comment').click();
await expect(secondComment).toContainText('This is comment 2');
});
test('can hide and show replies', async ({page}) => {
mockedApi.addComment({
id: '1',
html: '<p>This is comment 1</p>',
replies: [
buildReply({id: '2', html: '<p>This is reply 1</p>'}),
buildReply({id: '3', html: '<p>This is reply 2</p>'})
]
});
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
const replyToHide = comments.nth(1);
// Hide the 1st reply
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('hide-button').click();
await expect(replyToHide).toContainText('Hidden for members');
// Show it again
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('show-button').click();
await expect(replyToHide).not.toContainText('Hidden for members');
});
test('updates in-reply-to snippets when hiding', async ({page}) => {
mockedApi.addComment({
id: '1',
html: '<p>This is comment 1</p>',
replies: [
buildReply({id: '2', html: '<p>This is reply 1</p>'}),
buildReply({id: '3', html: '<p>This is reply 2</p>', in_reply_to_id: '2', in_reply_to_snippet: 'This is reply 1'}),
buildReply({id: '4', html: '<p>This is reply 3</p>'})
]
});
const {frame} = await initializeTest(page);
const comments = await frame.getByTestId('comment-component');
const replyToHide = comments.nth(1);
const inReplyToComment = comments.nth(2);
// Hide the 1st reply
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('hide-button').click();
await expect(inReplyToComment).toContainText('[removed]');
await expect(inReplyToComment).not.toContainText('This is reply 1');
// Show it again
await replyToHide.getByTestId('more-button').click();
await replyToHide.getByTestId('show-button').click();
await expect(inReplyToComment).not.toContainText('[removed]');
await expect(inReplyToComment).toContainText('This is reply 1');
});
test('has correct comments count', async ({page}) => {
mockedApi.addComment({html: '<p>This is comment 1</p>', replies: [buildReply()]});
mockedApi.addComment({html: '<p>This is comment 2</p>'});
const {frame} = await initializeTest(page);
await expect(frame.getByTestId('count')).toContainText('3 comments');
});
});