From 2e3eb1da71fd189deccc62cf82cf86cf25a824a6 Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Tue, 16 Jul 2024 12:56:15 -0500 Subject: [PATCH] Added posts bulk action Admin tests (#20610) ref https://linear.app/tryghost/issue/ENG-1360 Not *all* functionality has been covered by these tests. There's a few missing pieces from our mirage build and use that likely doesn't need full coverage within the admin package. Regardless, this view has dramatically more coverage at this point. --- ghost/admin/mirage/config/posts.js | 27 ++ ghost/admin/tests/acceptance/content-test.js | 441 +++++++++++++++---- 2 files changed, 393 insertions(+), 75 deletions(-) diff --git a/ghost/admin/mirage/config/posts.js b/ghost/admin/mirage/config/posts.js index 3771dd191b..a12863bfe7 100644 --- a/ghost/admin/mirage/config/posts.js +++ b/ghost/admin/mirage/config/posts.js @@ -93,4 +93,31 @@ export default function mockPosts(server) { }); server.del('/posts/:id/'); + + server.del('/posts/', function ({posts}, {queryParams}) { + let ids = extractFilterParam('id', queryParams.filter); + + posts.find(ids).destroy(); + }); + + server.put('/posts/bulk/', function ({tags}, {requestBody}) { + const bulk = JSON.parse(requestBody).bulk; + const action = bulk.action; + // const ids = extractFilterParam('id', queryParams.filter); + + if (action === 'addTag') { + // create tag so we have an id from the server + const newTags = bulk.meta.tags; + + // check applied tags to see if any new ones should be created + newTags.forEach((tag) => { + if (!tag.id) { + tags.create(tag); + } + }); + // TODO: update the actual posts in the mock db + // const postsToUpdate = posts.find(ids); + // getting the posts is fine, but within this we CANNOT manipulate them (???) not even iterate with .forEach + } + }); } diff --git a/ghost/admin/tests/acceptance/content-test.js b/ghost/admin/tests/acceptance/content-test.js index da312358da..5fbbf3f743 100644 --- a/ghost/admin/tests/acceptance/content-test.js +++ b/ghost/admin/tests/acceptance/content-test.js @@ -1,11 +1,22 @@ +import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; import {beforeEach, describe, it} from 'mocha'; -import {blur, click, currentURL, fillIn, find, findAll, settled, visit} from '@ember/test-helpers'; +import {blur, click, currentURL, fillIn, find, findAll, triggerEvent, triggerKeyEvent, visit} from '@ember/test-helpers'; import {clickTrigger, selectChoose} from 'ember-power-select/test-support/helpers'; import {expect} from 'chai'; import {setupApplicationTest} from 'ember-mocha'; import {setupMirage} from 'ember-cli-mirage/test-support'; +/** + * + * @param {string} text + * @param {NodeList} buttons + * @returns Node + */ +const findButton = (text, buttons) => { + return Array.from(buttons).find(button => button.innerText.trim() === text); +}; + describe('Acceptance: Content', function () { let hooks = setupApplicationTest(); setupMirage(hooks); @@ -32,8 +43,9 @@ describe('Acceptance: Content', function () { publishedPost = this.server.create('post', {authors: [admin], status: 'published', title: 'Published Post'}); scheduledPost = this.server.create('post', {authors: [admin], status: 'scheduled', title: 'Scheduled Post'}); + // draftPost = this.server.create('post', {authors: [admin], status: 'draft', title: 'Draft Post', visibility: 'paid'}); draftPost = this.server.create('post', {authors: [admin], status: 'draft', title: 'Draft Post'}); - authorPost = this.server.create('post', {authors: [editor], status: 'published', title: 'Editor Published Post'}); + authorPost = this.server.create('post', {authors: [editor], status: 'published', title: 'Editor Published Post', visibiity: 'paid'}); // pages shouldn't appear in the list this.server.create('page', {authors: [admin], status: 'published', title: 'Published Page'}); @@ -41,97 +53,376 @@ describe('Acceptance: Content', function () { return await authenticateSession(); }); - it.skip('displays and filters posts', async function () { - await visit('/posts'); - // Not checking request here as it won't be the last request made - // Displays all posts + pages - expect(findAll('[data-test-post-id]').length, 'all posts count').to.equal(4); + describe('displays and filter posts', function () { + it('displays posts', async function () { + await visit('/posts'); - // show draft posts - await selectChoose('[data-test-type-select]', 'Draft posts'); + const posts = findAll('[data-test-post-id]'); + // displays all posts by default (all statuses) [no pages] + expect(posts.length, 'all posts count').to.equal(4); - // API request is correct - let [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"drafts" request status filter').to.have.string('status:draft'); - // Displays draft post - expect(findAll('[data-test-post-id]').length, 'drafts count').to.equal(1); - expect(find(`[data-test-post-id="${draftPost.id}"]`), 'draft post').to.exist; + // note: atm the mirage backend doesn't support ordering of the results set + }); - // show published posts - await selectChoose('[data-test-type-select]', 'Published posts'); + it('can filter by status', async function () { + await visit('/posts'); - // API request is correct - [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"published" request status filter').to.have.string('status:published'); - // Displays three published posts + pages - expect(findAll('[data-test-post-id]').length, 'published count').to.equal(2); - expect(find(`[data-test-post-id="${publishedPost.id}"]`), 'admin published post').to.exist; - expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author published post').to.exist; + // show draft posts + await selectChoose('[data-test-type-select]', 'Draft posts'); - // show scheduled posts - await selectChoose('[data-test-type-select]', 'Scheduled posts'); + // API request is correct + let [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, '"drafts" request status filter').to.have.string('status:draft'); + // Displays draft post + expect(findAll('[data-test-post-id]').length, 'drafts count').to.equal(1); + expect(find(`[data-test-post-id="${draftPost.id}"]`), 'draft post').to.exist; + + // show published posts + await selectChoose('[data-test-type-select]', 'Published posts'); + + // API request is correct + [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, '"published" request status filter').to.have.string('status:published'); + // Displays three published posts + pages + expect(findAll('[data-test-post-id]').length, 'published count').to.equal(2); + expect(find(`[data-test-post-id="${publishedPost.id}"]`), 'admin published post').to.exist; + expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author published post').to.exist; + + // show scheduled posts + await selectChoose('[data-test-type-select]', 'Scheduled posts'); + + // API request is correct + [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, '"scheduled" request status filter').to.have.string('status:scheduled'); + // Displays scheduled post + expect(findAll('[data-test-post-id]').length, 'scheduled count').to.equal(1); + expect(find(`[data-test-post-id="${scheduledPost.id}"]`), 'scheduled post').to.exist; + + // show all posts + await selectChoose('[data-test-type-select]', 'All posts'); + + // API request is correct + [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[draft,scheduled,published,sent]'); + }); - // API request is correct - [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"scheduled" request status filter').to.have.string('status:scheduled'); - // Displays scheduled post - expect(findAll('[data-test-post-id]').length, 'scheduled count').to.equal(1); - expect(find(`[data-test-post-id="${scheduledPost.id}"]`), 'scheduled post').to.exist; + it('can filter by author', async function () { + await visit('/posts'); - // show all posts - await selectChoose('[data-test-type-select]', 'All posts'); + // show all posts by editor + await selectChoose('[data-test-author-select]', editor.name); - // API request is correct - [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[draft,scheduled,published]'); + // API request is correct + let [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, '"editor" request status filter') + .to.have.string('status:[draft,scheduled,published,sent]'); + expect(lastRequest.queryParams.filter, '"editor" request filter param') + .to.have.string(`authors:${editor.slug}`); + }); - // show all posts by editor - await selectChoose('[data-test-author-select]', editor.name); + it('can filter by visibility', async function () { + await visit('/posts'); - // API request is correct - [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"editor" request status filter') - .to.have.string('status:[draft,scheduled,published]'); - expect(lastRequest.queryParams.filter, '"editor" request filter param') - .to.have.string(`authors:${editor.slug}`); + await selectChoose('[data-test-visibility-select]', 'Paid members-only'); - // Post status is only visible when members is enabled - expect(find('[data-test-visibility-select]'), 'access dropdown before members enabled').to.not.exist; - let featureService = this.owner.lookup('service:feature'); - featureService.set('members', true); - await settled(); - expect(find('[data-test-visibility-select]'), 'access dropdown after members enabled').to.exist; + let [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, '"visibility" request filter param') + .to.have.string('visibility:[paid,tiers]+status:[draft,scheduled,published,sent]'); + }); - await selectChoose('[data-test-visibility-select]', 'Paid members-only'); - [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"visibility" request filter param') - .to.have.string('visibility:[paid,tiers]+status:[draft,scheduled,published]'); + it('can filter by tag', async function () { + this.server.create('tag', {name: 'B - Second', slug: 'second'}); + this.server.create('tag', {name: 'Z - Last', slug: 'last'}); + this.server.create('tag', {name: 'A - First', slug: 'first'}); - // Displays editor post - // TODO: implement "filter" param support and fix mirage post->author association - // expect(find('[data-test-post-id]').length, 'editor post count').to.equal(1); - // expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author post').to.exist; + await visit('/posts'); + await clickTrigger('[data-test-tag-select]'); - // TODO: test tags dropdown + let options = findAll('.ember-power-select-option'); + + // check that dropdown sorts alphabetically + expect(options[0].textContent.trim()).to.equal('All tags'); + expect(options[1].textContent.trim()).to.equal('A - First'); + expect(options[2].textContent.trim()).to.equal('B - Second'); + expect(options[3].textContent.trim()).to.equal('Z - Last'); + + // select one + await selectChoose('[data-test-tag-select]', 'B - Second'); + // affirm request + let [lastRequest] = this.server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter, 'request filter').to.have.string('tag:second'); + }); }); - // TODO: skipped due to consistently random failures on Travis - // options[0] is undefined - // https://github.com/TryGhost/Ghost/issues/10308 - it.skip('sorts tags filter alphabetically', async function () { - this.server.create('tag', {name: 'B - Second', slug: 'second'}); - this.server.create('tag', {name: 'Z - Last', slug: 'last'}); - this.server.create('tag', {name: 'A - First', slug: 'first'}); + describe('context menu actions', function () { + describe('single post', function () { + // has a duplicate option + it.skip('can duplicate a post', async function () { + await visit('/posts'); - await visit('/posts'); - await clickTrigger('[data-test-tag-select]'); + // get the post + const post = find(`[data-test-post-id="${publishedPost.id}"]`); + expect(post, 'post').to.exist; - let options = findAll('.ember-power-select-option'); + await triggerEvent(post, 'contextmenu'); + // await this.pauseTest(); - expect(options[0].textContent.trim()).to.equal('All tags'); - expect(options[1].textContent.trim()).to.equal('A - First'); - expect(options[2].textContent.trim()).to.equal('B - Second'); - expect(options[3].textContent.trim()).to.equal('Z - Last'); + let contextMenu = find('.gh-posts-context-menu'); // this is a