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 sinon from 'sinon';
|
||||||
import {buildAnonymousMember, buildComment, buildDeletedMember} from '../../test/utils/fixtures';
|
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 () {
|
describe('formatNumber', function () {
|
||||||
it('adds commas to large numbers', function () {
|
it('adds commas to large numbers', function () {
|
||||||
expect(helpers.formatNumber(1234567)).toEqual('1,234,567');
|
expect(helpers.formatNumber(1234567)).toEqual('1,234,567');
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
import {Comment, Member, TranslationFunction} from '../AppContext';
|
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 {
|
export function formatNumber(number: number): string {
|
||||||
if (number !== 0 && !number) {
|
if (number !== 0 && !number) {
|
||||||
return '';
|
return '';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {MockedApi, initialize, waitEditorFocused} from '../utils/e2e';
|
import {MockedApi, initialize, waitEditorFocused} from '../utils/e2e';
|
||||||
|
import {buildReply} from '../utils/fixtures';
|
||||||
import {expect, test} from '@playwright/test';
|
import {expect, test} from '@playwright/test';
|
||||||
|
|
||||||
test.describe('Actions', async () => {
|
test.describe('Actions', async () => {
|
||||||
|
@ -72,6 +73,29 @@ test.describe('Actions', async () => {
|
||||||
await expect(likeButton2).toHaveText('52');
|
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}) => {
|
test('Can reply to a comment', async ({page}) => {
|
||||||
mockedApi.addComment({
|
mockedApi.addComment({
|
||||||
html: '<p>This is comment 1</p>'
|
html: '<p>This is comment 1</p>'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import {MOCKED_SITE_URL, MockedApi, initialize, mockAdminAuthFrame, mockAdminAuthFrame204} from '../utils/e2e';
|
import {MOCKED_SITE_URL, MockedApi, initialize, mockAdminAuthFrame, mockAdminAuthFrame204} from '../utils/e2e';
|
||||||
|
import {buildReply} from '../utils/fixtures';
|
||||||
import {expect, test} from '@playwright/test';
|
import {expect, test} from '@playwright/test';
|
||||||
|
|
||||||
const admin = MOCKED_SITE_URL + '/ghost/';
|
const admin = MOCKED_SITE_URL + '/ghost/';
|
||||||
|
@ -169,5 +170,32 @@ test.describe('Admin moderation', async () => {
|
||||||
await moreButtons.nth(1).getByText('Show comment').click();
|
await moreButtons.nth(1).getByText('Show comment').click();
|
||||||
await expect(secondComment).toContainText('This is comment 2');
|
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 nql from '@tryghost/nql';
|
||||||
import {buildComment, buildMember, buildReply, buildSettings} from './fixtures';
|
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.
|
// 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.
|
// 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
|
// Parse NQL filter
|
||||||
if (filter) {
|
if (filter) {
|
||||||
const parsed = nql(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) => {
|
filteredComments = filteredComments.filter((comment) => {
|
||||||
return parsed.queryJSON(comment);
|
return parsed.queryJSON(comment);
|
||||||
});
|
});
|
||||||
|
@ -182,14 +190,13 @@ export class MockedApi {
|
||||||
const comments = filteredComments.slice(startIndex, endIndex);
|
const comments = filteredComments.slice(startIndex, endIndex);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
comments: comments.map((comment) => {
|
comments: comments.map((comment) => {
|
||||||
return {
|
return {
|
||||||
...comment,
|
...comment,
|
||||||
replies: comment.replies.slice(0, 3),
|
replies: comment.replies ? comment.replies?.slice(0, 3) : [],
|
||||||
count: {
|
count: {
|
||||||
...comment.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 url = new URL(route.request().url());
|
||||||
const commentId = url.pathname.split('/').reverse()[2];
|
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) {
|
if (!comment) {
|
||||||
return await route.fulfill({
|
return await route.fulfill({
|
||||||
status: 404,
|
status: 404,
|
||||||
|
@ -441,14 +448,33 @@ export class MockedApi {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateComment(route) {
|
async getOrUpdateComment(route) {
|
||||||
await this.#delayResponse();
|
await this.#delayResponse();
|
||||||
const url = new URL(route.request().url());
|
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') {
|
if (route.request().method() === 'PUT') {
|
||||||
const commentId = url.pathname.split('/').reverse()[1];
|
const commentId = url.pathname.split('/').reverse()[1];
|
||||||
const payload = JSON.parse(route.request().postData());
|
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;
|
comment.status = payload.status;
|
||||||
|
|
||||||
|
@ -458,7 +484,8 @@ export class MockedApi {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
filter: `id:'${commentId}'`,
|
filter: `id:'${commentId}'`,
|
||||||
page: 1,
|
page: 1,
|
||||||
order: ''
|
order: '',
|
||||||
|
admin: true
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -479,6 +506,6 @@ export class MockedApi {
|
||||||
// Admin API -----------------------------------------------------------
|
// Admin API -----------------------------------------------------------
|
||||||
await page.route(`${path}/ghost/api/admin/users/me/`, this.adminRequestHandlers.getUser.bind(this));
|
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/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