mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Fixed comments-ui tests not properly mocking actions on replies
no issue - `MockedApi.browseComments` only worked on top-level comments, it couldn't find replies when passed `filter:'id:123'` such is used when liking and editing replies - fixed missing get handler for fetching single comment via Admin API (used after showing a hidden comment) - added `flattenComments` and `findCommentById` helper functions to facilitate easier finding of a comment or reply within the nested structure - added tests for liking and hiding replies
This commit is contained in:
parent
d32955b21e
commit
04f337e085
5 changed files with 124 additions and 8 deletions
|
@ -3,6 +3,34 @@ import moment, {DurationInputObject} from 'moment';
|
|||
import sinon from 'sinon';
|
||||
import {buildAnonymousMember, buildComment, buildDeletedMember} from '../../test/utils/fixtures';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('flattenComments', function () {
|
||||
it('flattens comments and replies', function () {
|
||||
const comments: any[] = [
|
||||
{id: '1', replies: [{id: '2'}]},
|
||||
{id: '3', replies: []}
|
||||
];
|
||||
expect(helpers.flattenComments(comments)).toEqual([
|
||||
{id: '1', replies: [{id: '2'}]},
|
||||
{id: '2'},
|
||||
{id: '3', replies: []}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findCommentById', function () {
|
||||
it('finds a top-level comment', function () {
|
||||
const comments: any[] = [{id: '1'}, {id: '2'}, {id: '3'}];
|
||||
expect(helpers.findCommentById(comments, '2')).toEqual({id: '2'});
|
||||
});
|
||||
|
||||
it('finds a reply', function () {
|
||||
const comments: any[] = [{id: '1', replies: [{id: '2'}]}, {id: '3'}];
|
||||
expect(helpers.findCommentById(comments, '2')).toEqual({id: '2'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatNumber', function () {
|
||||
it('adds commas to large numbers', function () {
|
||||
expect(helpers.formatNumber(1234567)).toEqual('1,234,567');
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import {Comment, Member, TranslationFunction} from '../AppContext';
|
||||
|
||||
export function flattenComments(comments: Comment[]): Comment[] {
|
||||
return comments.flatMap(comment => [comment, ...(comment.replies || [])]);
|
||||
}
|
||||
|
||||
export function findCommentById(comments: Comment[], id: string): Comment | undefined {
|
||||
return comments.find(comment => comment?.id === id)
|
||||
|| comments.flatMap(comment => comment.replies || []).find(reply => reply?.id === id);
|
||||
}
|
||||
|
||||
export function formatNumber(number: number): string {
|
||||
if (number !== 0 && !number) {
|
||||
return '';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {MockedApi, initialize, waitEditorFocused} from '../utils/e2e';
|
||||
import {buildReply} from '../utils/fixtures';
|
||||
import {expect, test} from '@playwright/test';
|
||||
|
||||
test.describe('Actions', async () => {
|
||||
|
@ -72,6 +73,29 @@ test.describe('Actions', async () => {
|
|||
await expect(likeButton2).toHaveText('52');
|
||||
});
|
||||
|
||||
test('Can like and unlike a reply', 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 reply = frame.getByTestId('comment-component').nth(1);
|
||||
const likeButton = reply.getByTestId('like-button');
|
||||
|
||||
await expect(likeButton).toHaveText('0');
|
||||
await likeButton.click();
|
||||
await expect(likeButton).toHaveText('1');
|
||||
await likeButton.click();
|
||||
await expect(likeButton).toHaveText('0');
|
||||
});
|
||||
|
||||
test('Can reply to a comment', async ({page}) => {
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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/';
|
||||
|
@ -169,5 +170,32 @@ test.describe('Admin moderation', async () => {
|
|||
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, {labs: true});
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import nql from '@tryghost/nql';
|
||||
import {buildComment, buildMember, buildReply, buildSettings} from './fixtures';
|
||||
import {findCommentById, flattenComments} from '../../src/utils/helpers';
|
||||
|
||||
// The test file doesn't run in the browser, so we can't use the DOM API.
|
||||
// We can use a simple regex to strip HTML tags from a string for test purposes.
|
||||
|
@ -171,6 +172,13 @@ export class MockedApi {
|
|||
// Parse NQL filter
|
||||
if (filter) {
|
||||
const parsed = nql(filter);
|
||||
|
||||
// When fetching a comment by ID, we need to include all replies so
|
||||
// the quick way to do that is to flatten the comment+replies array
|
||||
if (filter.includes('id:')) {
|
||||
filteredComments = flattenComments(filteredComments);
|
||||
}
|
||||
|
||||
filteredComments = filteredComments.filter((comment) => {
|
||||
return parsed.queryJSON(comment);
|
||||
});
|
||||
|
@ -182,14 +190,13 @@ export class MockedApi {
|
|||
const comments = filteredComments.slice(startIndex, endIndex);
|
||||
|
||||
return {
|
||||
|
||||
comments: comments.map((comment) => {
|
||||
return {
|
||||
...comment,
|
||||
replies: comment.replies.slice(0, 3),
|
||||
replies: comment.replies ? comment.replies?.slice(0, 3) : [],
|
||||
count: {
|
||||
...comment.count,
|
||||
replies: comment.replies.length
|
||||
replies: comment.replies ? comment.replies?.length : 0
|
||||
}
|
||||
};
|
||||
}),
|
||||
|
@ -341,7 +348,7 @@ export class MockedApi {
|
|||
const url = new URL(route.request().url());
|
||||
const commentId = url.pathname.split('/').reverse()[2];
|
||||
|
||||
const comment = this.comments.find(c => c.id === commentId);
|
||||
const comment = flattenComments(this.comments).find(c => c.id === commentId);
|
||||
if (!comment) {
|
||||
return await route.fulfill({
|
||||
status: 404,
|
||||
|
@ -441,14 +448,33 @@ export class MockedApi {
|
|||
});
|
||||
},
|
||||
|
||||
async updateComment(route) {
|
||||
async getOrUpdateComment(route) {
|
||||
await this.#delayResponse();
|
||||
const url = new URL(route.request().url());
|
||||
|
||||
if (route.request().method() === 'GET') {
|
||||
const commentId = url.pathname.split('/').reverse()[1];
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(this.browseComments({
|
||||
limit: 1,
|
||||
filter: `id:'${commentId}'`,
|
||||
page: 1,
|
||||
order: '',
|
||||
admin: true
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
if (route.request().method() === 'PUT') {
|
||||
const commentId = url.pathname.split('/').reverse()[1];
|
||||
const payload = JSON.parse(route.request().postData());
|
||||
const comment = this.comments.find(c => c.id === commentId);
|
||||
const comment = findCommentById(this.comments, commentId);
|
||||
|
||||
if (!comment) {
|
||||
await route.fulfill({status: 404});
|
||||
return;
|
||||
}
|
||||
|
||||
comment.status = payload.status;
|
||||
|
||||
|
@ -458,7 +484,8 @@ export class MockedApi {
|
|||
limit: 1,
|
||||
filter: `id:'${commentId}'`,
|
||||
page: 1,
|
||||
order: ''
|
||||
order: '',
|
||||
admin: true
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
@ -479,6 +506,6 @@ export class MockedApi {
|
|||
// Admin API -----------------------------------------------------------
|
||||
await page.route(`${path}/ghost/api/admin/users/me/`, this.adminRequestHandlers.getUser.bind(this));
|
||||
await page.route(`${path}/ghost/api/admin/comments/post/*/*`, this.adminRequestHandlers.browseComments.bind(this));
|
||||
await page.route(`${path}/ghost/api/admin/comments/*/`, this.adminRequestHandlers.updateComment.bind(this));
|
||||
await page.route(`${path}/ghost/api/admin/comments/*/`, this.adminRequestHandlers.getOrUpdateComment.bind(this));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue