mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Refactored comments-ui admin moderation tests
no issue - expanded e2e test behaviour to route Admin requests through our MockedApi instance so we have the same test experience for normal and admin comments requests - extracted page route method bodies to enable request methods to be spied on - updated admin moderation tests to properly use admin requests
This commit is contained in:
parent
6c064b73e4
commit
cdea73b873
7 changed files with 349 additions and 439 deletions
|
@ -126,11 +126,11 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
|
|||
} catch (e) {
|
||||
// Loading of admin failed. Could be not signed in, or a different error (not important)
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`[Comments] Failed to fetch current admin user:`, e);
|
||||
console.warn(`[Comments] Failed to fetch admin endpoint:`, e);
|
||||
}
|
||||
|
||||
setState({
|
||||
adminApi: adminApi,
|
||||
adminApi,
|
||||
admin
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
@ -23,11 +23,11 @@ const AdminContextMenu: React.FC<Props> = ({comment, close}) => {
|
|||
<div className="flex w-full flex-col gap-0.5">
|
||||
{
|
||||
isHidden ?
|
||||
<button className="w-full rounded px-2.5 py-1.5 text-left text-[14px] transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-700" type="button" onClick={showComment}>
|
||||
<button className="w-full rounded px-2.5 py-1.5 text-left text-[14px] transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-700" data-testid="show-button" type="button" onClick={showComment}>
|
||||
<span className="hidden sm:inline">{t('Show comment')}</span><span className="sm:hidden">{t('Show')}</span>
|
||||
</button>
|
||||
:
|
||||
<button className="w-full rounded px-2.5 py-1.5 text-left text-[14px] text-red-600 transition-colors hover:bg-neutral-100 dark:text-red-500 dark:hover:bg-neutral-700" type="button" onClick={hideComment}>
|
||||
<button className="w-full rounded px-2.5 py-1.5 text-left text-[14px] text-red-600 transition-colors hover:bg-neutral-100 dark:text-red-500 dark:hover:bg-neutral-700" data-testid="hide-button" type="button" onClick={hideComment}>
|
||||
<span className="hidden sm:inline">{t('Hide comment')}</span><span className="sm:hidden">{t('Hide')}</span>
|
||||
</button>
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}: {site
|
|||
return fetch(url, options);
|
||||
}
|
||||
|
||||
// To fix pagination when we create new comments (or people post comments after you loaded the page, we need to only load comments creatd AFTER the page load)
|
||||
// To fix pagination when we create new comments (or people post comments
|
||||
// after you loaded the page), we need to only load comments created AFTER the page load
|
||||
let firstCommentCreatedAt: null | string = null;
|
||||
|
||||
const api = {
|
||||
|
|
173
apps/comments-ui/test/e2e/admin-moderation.test.ts
Normal file
173
apps/comments-ui/test/e2e/admin-moderation.test.ts
Normal file
|
@ -0,0 +1,173 @@
|
|||
import sinon from 'sinon';
|
||||
import {MOCKED_SITE_URL, MockedApi, initialize, mockAdminAuthFrame, mockAdminAuthFrame204} from '../utils/e2e';
|
||||
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'}, ...options};
|
||||
|
||||
if (options.isAdmin) {
|
||||
await mockAdminAuthFrame({page, admin});
|
||||
} else {
|
||||
await mockAdminAuthFrame204({page, admin});
|
||||
}
|
||||
|
||||
mockedApi.setMember(options.member);
|
||||
|
||||
if (options.labs) {
|
||||
mockedApi.setLabs({commentImprovements: true});
|
||||
}
|
||||
|
||||
return await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin,
|
||||
labs: {
|
||||
commentImprovements: options.labs
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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('can hide and show comments', async ({page}) => {
|
||||
mockedApi.addComment({html: '<p>This is comment 1</p>'});
|
||||
mockedApi.addComment({html: '<p>This is comment 2</p>'});
|
||||
|
||||
const {frame} = await initializeTest(page);
|
||||
|
||||
// Click the hide button for 2nd comment
|
||||
const moreButtons = frame.getByTestId('more-button');
|
||||
await moreButtons.nth(1).click();
|
||||
await moreButtons.nth(1).getByTestId('hide-button').click();
|
||||
|
||||
// comment becomes hidden
|
||||
const comments = frame.getByTestId('comment-component');
|
||||
const secondComment = comments.nth(1);
|
||||
await expect(secondComment).toContainText('This comment has been hidden.');
|
||||
await expect(secondComment).not.toContainText('This is comment 2');
|
||||
|
||||
// can show it again
|
||||
await moreButtons.nth(1).click();
|
||||
await moreButtons.nth(1).getByTestId('show-button').click();
|
||||
await expect(secondComment).toContainText('This is comment 2');
|
||||
});
|
||||
|
||||
test.describe('commentImprovements', function () {
|
||||
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, labs: true});
|
||||
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, {labs: true});
|
||||
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, {labs: true});
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,344 +0,0 @@
|
|||
import {MOCKED_SITE_URL, MockedApi, initialize, mockAdminAuthFrame, mockAdminAuthFrame204} from '../utils/e2e';
|
||||
import {expect, test} from '@playwright/test';
|
||||
|
||||
const admin = MOCKED_SITE_URL + '/ghost/';
|
||||
|
||||
test.describe('Auth Frame', async () => {
|
||||
test('skips rendering the auth frame with no comments', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin
|
||||
});
|
||||
|
||||
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
|
||||
await expect(iframeElement).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('renders the auth frame when there are comments', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
|
||||
await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin
|
||||
});
|
||||
|
||||
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
|
||||
await expect(iframeElement).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('has no admin options when not signed in to Ghost admin', async ({page}) => {
|
||||
await mockAdminAuthFrame204({page, admin});
|
||||
|
||||
const mockedApi = new MockedApi({});
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 2</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 3</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 4</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 5</p>'
|
||||
});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin
|
||||
});
|
||||
|
||||
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
|
||||
await expect(iframeElement).toHaveCount(1);
|
||||
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
await expect(comments).toHaveCount(5);
|
||||
|
||||
const moreButtons = await frame.getByTestId('more-button');
|
||||
await expect(moreButtons).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('has admin options when signed in to Ghost admin', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 2</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 3</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 4</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 5</p>'
|
||||
});
|
||||
|
||||
await mockAdminAuthFrame({
|
||||
admin,
|
||||
page
|
||||
});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin
|
||||
});
|
||||
|
||||
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
|
||||
await expect(iframeElement).toHaveCount(1);
|
||||
|
||||
// Check if more actions button is visible on each comment
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
await expect(comments).toHaveCount(5);
|
||||
|
||||
const moreButtons = await frame.getByTestId('more-button');
|
||||
await expect(moreButtons).toHaveCount(5);
|
||||
|
||||
// Click the 2nd button
|
||||
await moreButtons.nth(1).click();
|
||||
await moreButtons.nth(1).getByText('Hide comment').click();
|
||||
|
||||
// Check comment2 is replaced with a hidden message
|
||||
const secondComment = comments.nth(1);
|
||||
await expect(secondComment).toContainText('This comment has been hidden.');
|
||||
await expect(secondComment).not.toContainText('This is comment 2');
|
||||
|
||||
// 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('has admin options when signed in to Ghost admin and as a member', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.setMember({});
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 2</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 3</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 4</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 5</p>'
|
||||
});
|
||||
|
||||
await mockAdminAuthFrame({
|
||||
admin,
|
||||
page
|
||||
});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin
|
||||
});
|
||||
|
||||
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
|
||||
await expect(iframeElement).toHaveCount(1);
|
||||
|
||||
// Check if more actions button is visible on each comment
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
await expect(comments).toHaveCount(5);
|
||||
|
||||
const moreButtons = await frame.getByTestId('more-button');
|
||||
await expect(moreButtons).toHaveCount(5);
|
||||
|
||||
// Click the 2nd button
|
||||
await moreButtons.nth(1).click();
|
||||
await moreButtons.nth(1).getByText('Hide comment').click();
|
||||
|
||||
// Check comment2 is replaced with a hidden message
|
||||
const secondComment = comments.nth(1);
|
||||
await expect(secondComment).toContainText('This comment has been hidden.');
|
||||
await expect(secondComment).not.toContainText('This is comment 2');
|
||||
|
||||
// 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('Hidden comment is displayed for admins - needs flags enabled', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 2</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 3</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 4</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 5</p>'
|
||||
});
|
||||
|
||||
await mockAdminAuthFrame({
|
||||
admin,
|
||||
page
|
||||
});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin,
|
||||
labs: {
|
||||
commentImprovements: true
|
||||
}
|
||||
});
|
||||
|
||||
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
|
||||
await expect(iframeElement).toHaveCount(1);
|
||||
|
||||
// Check if more actions button is visible on each comment
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
await expect(comments).toHaveCount(5);
|
||||
|
||||
const moreButtons = await frame.getByTestId('more-button');
|
||||
await expect(moreButtons).toHaveCount(5);
|
||||
|
||||
// Click the 2nd button
|
||||
await moreButtons.nth(1).click();
|
||||
await moreButtons.nth(1).getByText('Hide comment').click();
|
||||
|
||||
const secondComment = comments.nth(1);
|
||||
// expect "hidden for members" message
|
||||
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('authFrameMain fires getUser (exposed function)', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 2</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 3</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 4</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 5</p>'
|
||||
});
|
||||
|
||||
const actions: string[] = [];
|
||||
|
||||
await page.exposeFunction('__testHelper', (action: string) => {
|
||||
actions.push(action);
|
||||
});
|
||||
|
||||
await mockAdminAuthFrame({
|
||||
admin,
|
||||
page
|
||||
});
|
||||
|
||||
await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin,
|
||||
labs: {
|
||||
commentImprovements: true
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger the message event
|
||||
await page.evaluate(() => {
|
||||
const event = new MessageEvent('message', {
|
||||
data: JSON.stringify({uid: 'test', action: 'getUser'}),
|
||||
origin: 'https://localhost:1234'
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// Validate that "getUser" was captured
|
||||
expect(actions).toContain('getUser');
|
||||
});
|
||||
|
||||
test('fires admin read when making a hidden comment visible', async ({page}) => {
|
||||
const mockedApi = new MockedApi({});
|
||||
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 1</p>'
|
||||
});
|
||||
mockedApi.addComment({
|
||||
html: '<p>This is comment 2</p>',
|
||||
status: 'hidden'
|
||||
});
|
||||
|
||||
const actions: string[] = [];
|
||||
|
||||
await page.exposeFunction('__testHelper', (action: string) => {
|
||||
actions.push(action);
|
||||
});
|
||||
|
||||
await mockAdminAuthFrame({
|
||||
admin,
|
||||
page
|
||||
});
|
||||
|
||||
const {frame} = await initialize({
|
||||
mockedApi,
|
||||
page,
|
||||
publication: 'Publisher Weekly',
|
||||
admin,
|
||||
labs: {
|
||||
commentImprovements: true
|
||||
}
|
||||
});
|
||||
|
||||
const iframeElement = await page.locator('iframe[data-frame="admin-auth"]');
|
||||
await expect(iframeElement).toHaveCount(1);
|
||||
|
||||
// Check if more actions button is visible on each comment
|
||||
const comments = await frame.getByTestId('comment-component');
|
||||
await expect(comments).toHaveCount(2);
|
||||
|
||||
const moreButtons = await frame.getByTestId('more-button');
|
||||
await expect(moreButtons).toHaveCount(2);
|
||||
|
||||
// Click the 2nd button
|
||||
await moreButtons.nth(1).click();
|
||||
await moreButtons.nth(1).getByText('Show comment').click();
|
||||
|
||||
await expect(actions).toContain('readComment');
|
||||
});
|
||||
});
|
|
@ -7,6 +7,8 @@ const htmlToPlaintext = (html) => {
|
|||
return html.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
||||
};
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export class MockedApi {
|
||||
comments: any[];
|
||||
postId: string;
|
||||
|
@ -15,19 +17,22 @@ export class MockedApi {
|
|||
members: any[];
|
||||
delay: number;
|
||||
|
||||
labs: any;
|
||||
|
||||
#lastCommentDate = new Date('2021-01-01T00:00:00.000Z');
|
||||
|
||||
#findReplyById(id: string) {
|
||||
return this.comments.flatMap(c => c.replies).find(r => r.id === id);
|
||||
}
|
||||
|
||||
constructor({postId = 'ABC', comments = [], member = undefined, settings = {}, members = []}: {postId?: string, comments?: any[], member?: any, settings?: any, members?: any[]}) {
|
||||
constructor({postId = 'ABC', comments = [], member = undefined, settings = {}, members = [], labs = {}}: {postId?: string, comments?: any[], member?: any, settings?: any, members?: any[], labs?: any}) {
|
||||
this.postId = postId;
|
||||
this.comments = comments;
|
||||
this.member = member;
|
||||
this.settings = settings;
|
||||
this.members = [];
|
||||
this.delay = 0;
|
||||
this.labs = labs;
|
||||
}
|
||||
|
||||
setDelay(delay: number) {
|
||||
|
@ -77,8 +82,12 @@ export class MockedApi {
|
|||
}
|
||||
|
||||
setMember(overrides) {
|
||||
if (overrides === null) {
|
||||
this.member = null;
|
||||
} else {
|
||||
this.member = buildMember(overrides);
|
||||
}
|
||||
}
|
||||
|
||||
logoutMember() {
|
||||
this.member = null;
|
||||
|
@ -88,13 +97,17 @@ export class MockedApi {
|
|||
this.settings = buildSettings(overrides);
|
||||
}
|
||||
|
||||
setLabs(overrides) {
|
||||
this.labs = overrides;
|
||||
}
|
||||
|
||||
commentsCounts() {
|
||||
return {
|
||||
[this.postId]: this.comments.length
|
||||
};
|
||||
}
|
||||
|
||||
browseComments({limit = 5, filter, page, order}: {limit?: number, filter?: string, page: number, order?: string}) {
|
||||
browseComments({limit = 5, filter, page, order, admin}: {limit?: number, filter?: string, page: number, order?: string, admin?: boolean}) {
|
||||
// Sort comments on created at + id
|
||||
const setOrder = order || 'default';
|
||||
|
||||
|
@ -145,10 +158,20 @@ export class MockedApi {
|
|||
|
||||
let filteredComments = this.comments;
|
||||
|
||||
if (this.labs.commentImprovements && !admin) {
|
||||
function filterPublishedComments(comments: any[] = []) {
|
||||
return comments
|
||||
.filter(comment => comment.status === 'published')
|
||||
.map(comment => ({...comment, replies: filterPublishedComments(comment.replies)}));
|
||||
}
|
||||
|
||||
filteredComments = filterPublishedComments(this.comments);
|
||||
}
|
||||
|
||||
// Parse NQL filter
|
||||
if (filter) {
|
||||
const parsed = nql(filter);
|
||||
filteredComments = this.comments.filter((comment) => {
|
||||
filteredComments = filteredComments.filter((comment) => {
|
||||
return parsed.queryJSON(comment);
|
||||
});
|
||||
}
|
||||
|
@ -232,8 +255,11 @@ export class MockedApi {
|
|||
});
|
||||
}
|
||||
|
||||
async listen({page, path}: {page: any, path: string}) {
|
||||
await page.route(`${path}/members/api/member/`, async (route) => {
|
||||
// Request handlers ------------------------------------------------------
|
||||
// (useful to spy on these methods in tests)
|
||||
|
||||
requestHandlers = {
|
||||
async getMember(route) {
|
||||
await this.#delayResponse();
|
||||
if (!this.member) {
|
||||
return await route.fulfill({
|
||||
|
@ -254,9 +280,9 @@ export class MockedApi {
|
|||
status: 200,
|
||||
body: JSON.stringify(this.member)
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
await page.route(`${path}/members/api/comments/*`, async (route) => {
|
||||
async addComment(route) {
|
||||
await this.#delayResponse();
|
||||
const payload = JSON.parse(route.request().postData());
|
||||
|
||||
|
@ -273,9 +299,9 @@ export class MockedApi {
|
|||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
await page.route(`${path}/members/api/comments/post/*/*`, async (route) => {
|
||||
async browseComments(route) {
|
||||
await this.#delayResponse();
|
||||
const url = new URL(route.request().url());
|
||||
|
||||
|
@ -292,10 +318,25 @@ export class MockedApi {
|
|||
order
|
||||
}))
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// LIKE a single comment
|
||||
await page.route(`${path}/members/api/comments/*/like/`, async (route) => {
|
||||
async getComment(route) {
|
||||
await this.#delayResponse();
|
||||
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,
|
||||
order: ''
|
||||
}))
|
||||
});
|
||||
},
|
||||
|
||||
async likeComment(route) {
|
||||
await this.#delayResponse();
|
||||
const url = new URL(route.request().url());
|
||||
const commentId = url.pathname.split('/').reverse()[2];
|
||||
|
@ -327,26 +368,9 @@ export class MockedApi {
|
|||
order: ''
|
||||
}))
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// GET a single comment
|
||||
await page.route(`${path}/members/api/comments/*/`, async (route) => {
|
||||
await this.#delayResponse();
|
||||
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,
|
||||
order: ''
|
||||
}))
|
||||
});
|
||||
});
|
||||
|
||||
await page.route(`${path}/members/api/comments/*/replies/*`, async (route) => {
|
||||
async getReplies(route) {
|
||||
await this.#delayResponse();
|
||||
const url = new URL(route.request().url());
|
||||
|
||||
|
@ -362,9 +386,9 @@ export class MockedApi {
|
|||
commentId
|
||||
}))
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
await page.route(`${path}/members/api/comments/counts/*`, async (route) => {
|
||||
async getCommentCounts(route) {
|
||||
await this.#delayResponse();
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
|
@ -372,16 +396,89 @@ export class MockedApi {
|
|||
this.commentsCounts()
|
||||
)
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// get settings from content api
|
||||
|
||||
await page.route(`${path}/settings/*`, async (route) => {
|
||||
async getSettings(route) {
|
||||
await this.#delayResponse();
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(this.settings)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
adminRequestHandlers = {
|
||||
async getUser(route) {
|
||||
await this.#delayResponse();
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
users: [{
|
||||
id: '1'
|
||||
}]
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
async browseComments(route) {
|
||||
await this.#delayResponse();
|
||||
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') ?? '';
|
||||
const order = url.searchParams.get('order') ?? '';
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(this.browseComments({
|
||||
page: p,
|
||||
limit,
|
||||
filter,
|
||||
order,
|
||||
admin: true
|
||||
}))
|
||||
});
|
||||
},
|
||||
|
||||
async updateComment(route) {
|
||||
await this.#delayResponse();
|
||||
const url = new URL(route.request().url());
|
||||
|
||||
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);
|
||||
|
||||
comment.status = payload.status;
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(this.browseComments({
|
||||
limit: 1,
|
||||
filter: `id:'${commentId}'`,
|
||||
page: 1,
|
||||
order: ''
|
||||
}))
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async listen({page, path}: {page: any, path: string}) {
|
||||
// Public API ----------------------------------------------------------
|
||||
await page.route(`${path}/members/api/member/`, this.requestHandlers.getMember.bind(this));
|
||||
await page.route(`${path}/members/api/comments/*`, this.requestHandlers.addComment.bind(this));
|
||||
await page.route(`${path}/members/api/comments/post/*/*`, this.requestHandlers.browseComments.bind(this));
|
||||
await page.route(`${path}/members/api/comments/*/`, this.requestHandlers.getComment.bind(this));
|
||||
await page.route(`${path}/members/api/comments/*/like/`, this.requestHandlers.likeComment.bind(this));
|
||||
await page.route(`${path}/members/api/comments/*/replies/*`, this.requestHandlers.getReplies.bind(this));
|
||||
await page.route(`${path}/members/api/comments/counts/*`, this.requestHandlers.getCommentCounts.bind(this));
|
||||
await page.route(`${path}/settings/*`, this.requestHandlers.getSettings.bind(this));
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,74 +21,57 @@ function escapeHtml(unsafe: string) {
|
|||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__testHelper?: (action: string) => void;
|
||||
}
|
||||
}
|
||||
|
||||
function authFrameMain() {
|
||||
window.addEventListener('message', function (event) {
|
||||
let d = null;
|
||||
const endpoints = {
|
||||
browseComments: ['GET', ['postId'], '/comments/post/$1/'],
|
||||
getReplies: ['GET', ['commentId'], '/comments/$1/replies/'],
|
||||
readComment: ['GET', ['commentId'], '/comments/$1/'],
|
||||
getUser: ['GET', [], '/users/me/'],
|
||||
hideComment: ['PUT', ['id'], '/comments/$1/', data => ({id: data.id, status: 'hidden'})],
|
||||
showComment: ['PUT', ['id'], '/comments/$1/', data => ({id: data.id, status: 'published'})]
|
||||
};
|
||||
|
||||
window.addEventListener('message', async function (event) {
|
||||
let data: any = null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
try {
|
||||
d = JSON.parse(event.data);
|
||||
data = JSON.parse(event.data) || {};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
if (!d) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const data: {uid: string, action: string} = d;
|
||||
|
||||
function respond(error, result) {
|
||||
event.source!.postMessage(JSON.stringify({
|
||||
uid: data.uid,
|
||||
error: error,
|
||||
result: result
|
||||
error: error?.message,
|
||||
result
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.action === 'getUser') {
|
||||
if (window.__testHelper) {
|
||||
window.__testHelper('getUser');
|
||||
}
|
||||
if (endpoints[data.action]) {
|
||||
try {
|
||||
respond(null, {
|
||||
users: [
|
||||
{
|
||||
id: 'someone'
|
||||
const [method, routeParams, route, bodyFn] = endpoints[data.action];
|
||||
const paramData = routeParams.map(param => data[param]);
|
||||
const path = route.replace(/\$(\d+)/g, (_, index) => paramData[index - 1]);
|
||||
const url = new URL(`/ghost/api/admin${path}`, MOCKED_SITE_URL);
|
||||
if (data.params) {
|
||||
url.search = new URLSearchParams(data.params).toString();
|
||||
}
|
||||
]
|
||||
});
|
||||
let body, headers;
|
||||
if (method === 'PUT' || method === 'POST') {
|
||||
body = JSON.stringify(bodyFn(data));
|
||||
headers = {'Content-Type': 'application/json'};
|
||||
}
|
||||
const res = await fetch(url, {method, body, headers});
|
||||
const json = await res.json();
|
||||
respond(null, json);
|
||||
} catch (err) {
|
||||
console.log('e2e Admin endpoint error:', err); // eslint-disable-line no-console
|
||||
respond(err, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.action === 'readComment') {
|
||||
if (window.__testHelper) {
|
||||
window.__testHelper('readComment');
|
||||
}
|
||||
try {
|
||||
respond(null, {
|
||||
comment: {
|
||||
id: 'comment-id',
|
||||
html: '<p>This is a comment</p>'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
respond(err, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Other actions: return empty object
|
||||
try {
|
||||
respond(null, {});
|
||||
} catch (err) {
|
||||
respond(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -97,7 +80,7 @@ export async function mockAdminAuthFrame({admin, page}) {
|
|||
await page.route(admin + 'auth-frame/', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: `<html><head><meta charset="UTF-8" /></head><body><script>${authFrameMain.toString()}; authFrameMain();</script></body></html>`
|
||||
body: `<html><head><meta charset="UTF-8" /></head><body><script>${authFrameMain.toString().replaceAll('MOCKED_SITE_URL', `'${MOCKED_SITE_URL}'`)}; authFrameMain();</script></body></html>`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue