Ghost/apps/comments-ui/test/utils/MockedApi.ts
Kevin Ansfield dd3bc06761
🐛 Fixed comments block disappearing when performing certain actions (#19846)
closes https://linear.app/tryghost/issue/ENG-739

- `props.innerRef` in `<IFrame>` was inadvertently assumed to always exist, sometimes throwing an error on render when certain popups like the profile settings modal were opened resulting in the app crashing and the whole comments block disappearing
- added a guard to ensure the ref exists before trying to set it
- updated tests so the profile modal route is tested
2024-03-12 17:39:36 +00:00

286 lines
8.6 KiB
TypeScript

import nql from '@tryghost/nql';
import {buildComment, buildMember, buildReply} from './fixtures';
export class MockedApi {
comments: any[];
postId: string;
member: any;
#lastCommentDate = new Date('2021-01-01T00:00:00.000Z');
constructor({postId = 'ABC', comments = [], member = undefined}: {postId?: string, comments?: any[], member?: any}) {
this.postId = postId;
this.comments = comments;
this.member = member;
}
addComment(overrides: any = {}) {
if (!overrides.created_at) {
overrides.created_at = this.#lastCommentDate.toISOString();
this.#lastCommentDate = new Date(this.#lastCommentDate.getTime() + 1);
}
const fixture = buildComment({
...overrides,
post_id: this.postId
});
this.comments.push(fixture);
}
buildReply(overrides: any = {}) {
if (!overrides.created_at) {
overrides.created_at = this.#lastCommentDate.toISOString();
this.#lastCommentDate = new Date(this.#lastCommentDate.getTime() + 1);
}
return buildReply({
...overrides,
post_id: this.postId
});
}
addComments(count, overrides = {}) {
for (let i = 0; i < count; i += 1) {
this.addComment(overrides);
}
}
setMember(overrides) {
this.member = buildMember(overrides);
}
commentsCounts() {
return {
[this.postId]: this.comments.length
};
}
browseComments({limit = 5, filter, page}: {limit?: number, filter?: string, page: number}) {
// Sort comments on created at + id
this.comments.sort((a, b) => {
const aDate = new Date(a.created_at).getTime();
const bDate = new Date(b.created_at).getTime();
if (aDate === bDate) {
return a.id > b.id ? -1 : 1;
}
return aDate > bDate ? -1 : 1;
});
let filteredComments = this.comments;
// Parse NQL filter
if (filter) {
const parsed = nql(filter);
filteredComments = this.comments.filter((comment) => {
return parsed.queryJSON(comment);
});
}
// Splice based on page and limit
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const comments = filteredComments.slice(startIndex, endIndex);
return {
comments: comments.map((comment) => {
return {
...comment,
replies: comment.replies.slice(0, 3),
count: {
...comment.count,
replies: comment.replies.length
}
};
}),
meta: {
pagination: {
pages: Math.ceil(filteredComments.length / limit),
total: filteredComments.length,
page,
limit
}
}
};
}
browseReplies({commentId, filter, limit = 5}: {commentId: string, filter?: string, limit?: number}) {
const comment = this.comments.find(c => c.id === commentId);
if (!comment) {
return {
error: 'Comment ' + commentId + ' not found'
};
}
let replies: any[] = comment.replies;
// Sort replies on created at + id
replies.sort((a, b) => {
const aDate = new Date(a.created_at).getTime();
const bDate = new Date(b.created_at).getTime();
if (aDate === bDate) {
return a.id > b.id ? 1 : -1;
}
return aDate > bDate ? 1 : -1;
});
// Parse NQL filter
if (filter) {
const parsed = nql(filter);
replies = replies.filter((reply) => {
return parsed.queryJSON(reply);
});
}
const limitedReplies = replies.slice(0, limit);
return {
comments: limitedReplies,
meta: {
pagination: {
pages: Math.ceil(replies.length / limit),
total: replies.length,
page: 1,
limit
}
}
};
}
async listen({page, path}: {page: any, path: string}) {
await page.route(`${path}/members/api/member/`, async (route) => {
if (!this.member) {
return await route.fulfill({
status: 401,
body: 'Not authenticated'
});
}
if (route.request().method() === 'PUT') {
const payload = JSON.parse(route.request().postData());
this.member = {
...this.member,
...payload
};
}
await route.fulfill({
status: 200,
body: JSON.stringify(this.member)
});
});
await page.route(`${path}/members/api/comments/*`, async (route) => {
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]
]
})
});
});
await page.route(`${path}/members/api/comments/post/*/*`, async (route) => {
const url = new URL(route.request().url());
const p = parseInt(url.searchParams.get('page') ?? '1');
const limit = parseInt(url.searchParams.get('limit') ?? '5');
const filter = url.searchParams.get('filter') ?? '';
await route.fulfill({
status: 200,
body: JSON.stringify(this.browseComments({
page: p,
limit,
filter
}))
});
});
// LIKE a single comment
await page.route(`${path}/members/api/comments/*/like/`, async (route) => {
const url = new URL(route.request().url());
const commentId = url.pathname.split('/').reverse()[2];
const comment = this.comments.find(c => c.id === commentId);
if (!comment) {
return await route.fulfill({
status: 404,
body: 'Comment not found'
});
}
if (route.request().method() === 'POST') {
comment.count.likes += 1;
comment.liked = true;
}
if (route.request().method() === 'DELETE') {
comment.count.likes -= 1;
comment.liked = false;
}
await route.fulfill({
status: 200,
body: JSON.stringify(this.browseComments({
limit: 1,
filter: `id:'${commentId}'`,
page: 1
}))
});
});
// GET a single comment
await page.route(`${path}/members/api/comments/*/`, async (route) => {
const url = new URL(route.request().url());
const commentId = url.pathname.split('/').reverse()[1];
await route.fulfill({
status: 200,
body: JSON.stringify(this.browseComments({
limit: 1,
filter: `id:'${commentId}'`,
page: 1
}))
});
});
await page.route(`${path}/members/api/comments/*/replies/*`, async (route) => {
const url = new URL(route.request().url());
const limit = parseInt(url.searchParams.get('limit') ?? '5');
const commentId = url.pathname.split('/').reverse()[2];
const filter = url.searchParams.get('filter') ?? '';
await route.fulfill({
status: 200,
body: JSON.stringify(this.browseReplies({
limit,
filter,
commentId
}))
});
});
await page.route(`${path}/members/api/comments/counts/*`, async (route) => {
await route.fulfill({
status: 200,
body: JSON.stringify(
this.commentsCounts()
)
});
});
}
}