diff --git a/apps/comments-ui/src/App.tsx b/apps/comments-ui/src/App.tsx index 4f61a34af8..ee9f97133c 100644 --- a/apps/comments-ui/src/App.tsx +++ b/apps/comments-ui/src/App.tsx @@ -126,11 +126,11 @@ const App: React.FC = ({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) { diff --git a/apps/comments-ui/src/components/content/context-menus/AdminContextMenu.tsx b/apps/comments-ui/src/components/content/context-menus/AdminContextMenu.tsx index 83c601f773..16403e0d65 100644 --- a/apps/comments-ui/src/components/content/context-menus/AdminContextMenu.tsx +++ b/apps/comments-ui/src/components/content/context-menus/AdminContextMenu.tsx @@ -23,11 +23,11 @@ const AdminContextMenu: React.FC = ({comment, close}) => {
{ isHidden ? - : - } diff --git a/apps/comments-ui/src/utils/api.ts b/apps/comments-ui/src/utils/api.ts index b08552d7c8..4e72e8219b 100644 --- a/apps/comments-ui/src/utils/api.ts +++ b/apps/comments-ui/src/utils/api.ts @@ -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 = { diff --git a/apps/comments-ui/test/e2e/admin-moderation.test.ts b/apps/comments-ui/test/e2e/admin-moderation.test.ts new file mode 100644 index 0000000000..3bddb4eaff --- /dev/null +++ b/apps/comments-ui/test/e2e/admin-moderation.test.ts @@ -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: '

This is comment 1

'}); + 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: '

This is comment 1

'}); + + 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: '

This is comment 1

'}); + + 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: `

This is comment 1

`}); + 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: `

This is comment 1

`}); + 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: '

This is comment 1

'}); + mockedApi.addComment({html: '

This is comment 2

'}); + + 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: '

This is comment 1

'}); + mockedApi.addComment({html: '

This is comment 2

', 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: '

This is comment 1

'}); + mockedApi.addComment({html: '

This is comment 2

', 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: `

This is comment ${i}

`})); + + 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'); + }); + }); +}); diff --git a/apps/comments-ui/test/e2e/auth-frame.test.ts b/apps/comments-ui/test/e2e/auth-frame.test.ts deleted file mode 100644 index 7344493b5c..0000000000 --- a/apps/comments-ui/test/e2e/auth-frame.test.ts +++ /dev/null @@ -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: '

This is comment 1

' - }); - - 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: '

This is comment 1

' - }); - mockedApi.addComment({ - html: '

This is comment 2

' - }); - mockedApi.addComment({ - html: '

This is comment 3

' - }); - mockedApi.addComment({ - html: '

This is comment 4

' - }); - mockedApi.addComment({ - html: '

This is comment 5

' - }); - - 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: '

This is comment 1

' - }); - mockedApi.addComment({ - html: '

This is comment 2

' - }); - mockedApi.addComment({ - html: '

This is comment 3

' - }); - mockedApi.addComment({ - html: '

This is comment 4

' - }); - mockedApi.addComment({ - html: '

This is comment 5

' - }); - - 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: '

This is comment 1

' - }); - mockedApi.addComment({ - html: '

This is comment 2

' - }); - mockedApi.addComment({ - html: '

This is comment 3

' - }); - mockedApi.addComment({ - html: '

This is comment 4

' - }); - mockedApi.addComment({ - html: '

This is comment 5

' - }); - - 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: '

This is comment 1

' - }); - mockedApi.addComment({ - html: '

This is comment 2

' - }); - mockedApi.addComment({ - html: '

This is comment 3

' - }); - mockedApi.addComment({ - html: '

This is comment 4

' - }); - mockedApi.addComment({ - html: '

This is comment 5

' - }); - - 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: '

This is comment 1

' - }); - mockedApi.addComment({ - html: '

This is comment 2

' - }); - mockedApi.addComment({ - html: '

This is comment 3

' - }); - mockedApi.addComment({ - html: '

This is comment 4

' - }); - mockedApi.addComment({ - html: '

This is comment 5

' - }); - - 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: '

This is comment 1

' - }); - mockedApi.addComment({ - html: '

This is comment 2

', - 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'); - }); -}); diff --git a/apps/comments-ui/test/utils/MockedApi.ts b/apps/comments-ui/test/utils/MockedApi.ts index 66efa82414..a008b67e39 100644 --- a/apps/comments-ui/test/utils/MockedApi.ts +++ b/apps/comments-ui/test/utils/MockedApi.ts @@ -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,7 +82,11 @@ export class MockedApi { } setMember(overrides) { - this.member = buildMember(overrides); + if (overrides === null) { + this.member = null; + } else { + this.member = buildMember(overrides); + } } logoutMember() { @@ -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)); } } diff --git a/apps/comments-ui/test/utils/e2e.ts b/apps/comments-ui/test/utils/e2e.ts index e522338133..6f9c6debd6 100644 --- a/apps/comments-ui/test/utils/e2e.ts +++ b/apps/comments-ui/test/utils/e2e.ts @@ -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: '

This is a comment

' - } - }); - } 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: `` + body: `` }); }); }