mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 03:14:03 +03:00
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.
This commit is contained in:
parent
bb18e6571e
commit
2e3eb1da71
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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,11 +53,19 @@ describe('Acceptance: Content', function () {
|
||||
return await authenticateSession();
|
||||
});
|
||||
|
||||
it.skip('displays and filters posts', async function () {
|
||||
describe('displays and filter posts', function () {
|
||||
it('displays posts', async function () {
|
||||
await visit('/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);
|
||||
|
||||
// note: atm the mirage backend doesn't support ordering of the results set
|
||||
});
|
||||
|
||||
it('can filter by status', 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);
|
||||
|
||||
// show draft posts
|
||||
await selectChoose('[data-test-type-select]', 'Draft posts');
|
||||
@ -83,42 +103,34 @@ describe('Acceptance: Content', function () {
|
||||
|
||||
// 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]');
|
||||
expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[draft,scheduled,published,sent]');
|
||||
});
|
||||
|
||||
it('can filter by author', async function () {
|
||||
await visit('/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);
|
||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.filter, '"editor" request status filter')
|
||||
.to.have.string('status:[draft,scheduled,published]');
|
||||
.to.have.string('status:[draft,scheduled,published,sent]');
|
||||
expect(lastRequest.queryParams.filter, '"editor" request filter param')
|
||||
.to.have.string(`authors:${editor.slug}`);
|
||||
|
||||
// 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;
|
||||
|
||||
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]');
|
||||
|
||||
// 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;
|
||||
|
||||
// TODO: test tags dropdown
|
||||
});
|
||||
|
||||
// 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 () {
|
||||
it('can filter by visibility', async function () {
|
||||
await visit('/posts');
|
||||
|
||||
await selectChoose('[data-test-visibility-select]', 'Paid members-only');
|
||||
|
||||
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]');
|
||||
});
|
||||
|
||||
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'});
|
||||
@ -128,10 +140,289 @@ describe('Acceptance: Content', function () {
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('context menu actions', function () {
|
||||
describe('single post', function () {
|
||||
// has a duplicate option
|
||||
it.skip('can duplicate a post', async function () {
|
||||
await visit('/posts');
|
||||
|
||||
// get the post
|
||||
const post = find(`[data-test-post-id="${publishedPost.id}"]`);
|
||||
expect(post, 'post').to.exist;
|
||||
|
||||
await triggerEvent(post, 'contextmenu');
|
||||
// await this.pauseTest();
|
||||
|
||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||
|
||||
let buttons = contextMenu.querySelectorAll('button');
|
||||
|
||||
// should have three options for a published post
|
||||
expect(contextMenu, 'context menu').to.exist;
|
||||
expect(buttons.length, 'context menu buttons').to.equal(5);
|
||||
expect(buttons[0].innerText.trim(), 'context menu button 1').to.contain('Unpublish');
|
||||
expect(buttons[1].innerText.trim(), 'context menu button 2').to.contain('Feature'); // or Unfeature
|
||||
expect(buttons[2].innerText.trim(), 'context menu button 3').to.contain('Add a tag');
|
||||
expect(buttons[3].innerText.trim(), 'context menu button 4').to.contain('Duplicate');
|
||||
expect(buttons[4].innerText.trim(), 'context menu button 5').to.contain('Delete');
|
||||
|
||||
// duplicate the post
|
||||
await click(buttons[3]);
|
||||
|
||||
// API request is correct
|
||||
// POST /ghost/api/admin/posts/{id}/copy/?formats=mobiledoc,lexical
|
||||
|
||||
// TODO: probably missing endpoint in mirage...
|
||||
|
||||
// let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||
// console.log(`lastRequest`, lastRequest);
|
||||
// expect(lastRequest.url, 'request url').to.match(new RegExp(`/posts/${publishedPost.id}/copy/`));
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple posts', function () {
|
||||
it('can feature and unfeature posts', async function () {
|
||||
await visit('/posts');
|
||||
|
||||
// get all posts
|
||||
const posts = findAll('[data-test-post-id]');
|
||||
expect(posts.length, 'all posts count').to.equal(4);
|
||||
|
||||
const postThreeContainer = posts[2].parentElement; // draft post
|
||||
const postFourContainer = posts[3].parentElement; // published post
|
||||
|
||||
await click(postThreeContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
await click(postFourContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
|
||||
expect(postFourContainer.getAttribute('data-selected'), 'postFour selected').to.exist;
|
||||
expect(postThreeContainer.getAttribute('data-selected'), 'postThree selected').to.exist;
|
||||
|
||||
// NOTE: right clicks don't seem to work in these tests
|
||||
// contextmenu is the event triggered - https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
|
||||
await triggerEvent(postFourContainer, 'contextmenu');
|
||||
|
||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||
expect(contextMenu, 'context menu').to.exist;
|
||||
|
||||
// feature the post
|
||||
let buttons = contextMenu.querySelectorAll('button');
|
||||
let featureButton = findButton('Feature', buttons);
|
||||
expect(featureButton, 'feature button').to.exist;
|
||||
await click(featureButton);
|
||||
|
||||
// API request is correct - note, we don't mock the actual model updates
|
||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.filter, 'feature request id').to.equal(`id:['3','4']`);
|
||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'feature request action').to.equal('feature');
|
||||
|
||||
// ensure ui shows these are now featured
|
||||
expect(postThreeContainer.querySelector('.gh-featured-post'), 'postFour featured').to.exist;
|
||||
expect(postFourContainer.querySelector('.gh-featured-post'), 'postFour featured').to.exist;
|
||||
|
||||
// unfeature the posts
|
||||
await triggerEvent(postFourContainer, 'contextmenu');
|
||||
|
||||
contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||
expect(contextMenu, 'context menu').to.exist;
|
||||
|
||||
// unfeature the posts
|
||||
buttons = contextMenu.querySelectorAll('button');
|
||||
featureButton = findButton('Unfeature', buttons);
|
||||
expect(featureButton, 'unfeature button').to.exist;
|
||||
await click(featureButton);
|
||||
|
||||
// API request is correct - note, we don't mock the actual model updates
|
||||
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.filter, 'unfeature request id').to.equal(`id:['3','4']`);
|
||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'unfeature request action').to.equal('unfeature');
|
||||
|
||||
// ensure ui shows these are now unfeatured
|
||||
expect(postThreeContainer.querySelector('.gh-featured-post'), 'postFour featured').to.not.exist;
|
||||
expect(postFourContainer.querySelector('.gh-featured-post'), 'postFour featured').to.not.exist;
|
||||
});
|
||||
|
||||
it('can add a tag to multiple posts', async function () {
|
||||
await visit('/posts');
|
||||
|
||||
// get all posts
|
||||
const posts = findAll('[data-test-post-id]');
|
||||
expect(posts.length, 'all posts count').to.equal(4);
|
||||
|
||||
const postThreeContainer = posts[2].parentElement; // draft post
|
||||
const postFourContainer = posts[3].parentElement; // published post
|
||||
|
||||
await click(postThreeContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
await click(postFourContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
|
||||
expect(postFourContainer.getAttribute('data-selected'), 'postFour selected').to.exist;
|
||||
expect(postThreeContainer.getAttribute('data-selected'), 'postThree selected').to.exist;
|
||||
|
||||
// NOTE: right clicks don't seem to work in these tests
|
||||
// contextmenu is the event triggered - https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
|
||||
await triggerEvent(postFourContainer, 'contextmenu');
|
||||
|
||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||
expect(contextMenu, 'context menu').to.exist;
|
||||
|
||||
// add a tag to the posts
|
||||
let buttons = contextMenu.querySelectorAll('button');
|
||||
let addTagButton = findButton('Add a tag', buttons);
|
||||
expect(addTagButton, 'add tag button').to.exist;
|
||||
await click(addTagButton);
|
||||
|
||||
const addTagsModal = find('[data-test-modal="add-tags"]');
|
||||
expect(addTagsModal, 'tag settings modal').to.exist;
|
||||
|
||||
const input = addTagsModal.querySelector('input');
|
||||
expect(input, 'tag input').to.exist;
|
||||
await fillIn(input, 'test-tag');
|
||||
await triggerKeyEvent(input, 'keydown', 13);
|
||||
await click('[data-test-button="confirm"]');
|
||||
|
||||
// API request is correct - note, we don't mock the actual model updates
|
||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-2);
|
||||
expect(lastRequest.queryParams.filter, 'add tag request id').to.equal(`id:['3','4']`);
|
||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'add tag request action').to.equal('addTag');
|
||||
});
|
||||
|
||||
// NOTE: we do not seem to be loading the settings properly into the membersutil service, such that the members
|
||||
// service doesn't think members are enabled
|
||||
it.skip('can change access to multiple posts', async function () {
|
||||
await visit('/posts');
|
||||
|
||||
// get all posts
|
||||
const posts = findAll('[data-test-post-id]');
|
||||
expect(posts.length, 'all posts count').to.equal(4);
|
||||
|
||||
const postThreeContainer = posts[2].parentElement; // draft post
|
||||
const postFourContainer = posts[3].parentElement; // published post
|
||||
|
||||
await click(postThreeContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
await click(postFourContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
|
||||
expect(postFourContainer.getAttribute('data-selected'), 'postFour selected').to.exist;
|
||||
expect(postThreeContainer.getAttribute('data-selected'), 'postThree selected').to.exist;
|
||||
|
||||
// NOTE: right clicks don't seem to work in these tests
|
||||
// contextmenu is the event triggered - https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
|
||||
await triggerEvent(postFourContainer, 'contextmenu');
|
||||
|
||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||
expect(contextMenu, 'context menu').to.exist;
|
||||
|
||||
// TODO: the change access button is not showing; need to debug the UI to see what field it expects
|
||||
// change access to the posts
|
||||
let buttons = contextMenu.querySelectorAll('button');
|
||||
let changeAccessButton = findButton('Change access', buttons);
|
||||
|
||||
expect(changeAccessButton, 'change access button').to.exist;
|
||||
await click(changeAccessButton);
|
||||
|
||||
const changeAccessModal = find('[data-test-modal="edit-posts-access"]');
|
||||
expect(changeAccessModal, 'change access modal').to.exist;
|
||||
});
|
||||
|
||||
it('can unpublish posts', async function () {
|
||||
await visit('/posts');
|
||||
|
||||
// get all posts
|
||||
const posts = findAll('[data-test-post-id]');
|
||||
expect(posts.length, 'all posts count').to.equal(4);
|
||||
|
||||
const postThreeContainer = posts[2].parentElement; // draft post
|
||||
const postFourContainer = posts[3].parentElement; // published post
|
||||
|
||||
await click(postThreeContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
await click(postFourContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
|
||||
expect(postFourContainer.getAttribute('data-selected'), 'postFour selected').to.exist;
|
||||
expect(postThreeContainer.getAttribute('data-selected'), 'postThree selected').to.exist;
|
||||
|
||||
// NOTE: right clicks don't seem to work in these tests
|
||||
// contextmenu is the event triggered - https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
|
||||
await triggerEvent(postFourContainer, 'contextmenu');
|
||||
|
||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||
expect(contextMenu, 'context menu').to.exist;
|
||||
|
||||
// unpublish the posts
|
||||
let buttons = contextMenu.querySelectorAll('button');
|
||||
let unpublishButton = findButton('Unpublish', buttons);
|
||||
expect(unpublishButton, 'unpublish button').to.exist;
|
||||
await click(unpublishButton);
|
||||
|
||||
// handle modal
|
||||
const modal = find('[data-test-modal="unpublish-posts"]');
|
||||
expect(modal, 'unpublish modal').to.exist;
|
||||
await click('[data-test-button="confirm"]');
|
||||
|
||||
// API request is correct - note, we don't mock the actual model updates
|
||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.filter, 'unpublish request id').to.equal(`id:['3','4']`);
|
||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'unpublish request action').to.equal('unpublish');
|
||||
|
||||
// ensure ui shows these are now unpublished
|
||||
expect(postThreeContainer.querySelector('.gh-content-entry-status').textContent, 'postThree status').to.contain('Draft');
|
||||
expect(postFourContainer.querySelector('.gh-content-entry-status').textContent, 'postThree status').to.contain('Draft');
|
||||
});
|
||||
|
||||
it('can delete posts', async function () {
|
||||
await visit('/posts');
|
||||
|
||||
// get all posts
|
||||
const posts = findAll('[data-test-post-id]');
|
||||
expect(posts.length, 'all posts count').to.equal(4);
|
||||
|
||||
const postThreeContainer = posts[2].parentElement; // draft post
|
||||
const postFourContainer = posts[3].parentElement; // published post
|
||||
|
||||
await click(postThreeContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
await click(postFourContainer, {metaKey: ctrlOrCmd === 'command', ctrlKey: ctrlOrCmd === 'ctrl'});
|
||||
|
||||
expect(postFourContainer.getAttribute('data-selected'), 'postFour selected').to.exist;
|
||||
expect(postThreeContainer.getAttribute('data-selected'), 'postThree selected').to.exist;
|
||||
|
||||
// NOTE: right clicks don't seem to work in these tests
|
||||
// contextmenu is the event triggered - https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
|
||||
await triggerEvent(postFourContainer, 'contextmenu');
|
||||
|
||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||
expect(contextMenu, 'context menu').to.exist;
|
||||
|
||||
// delete the posts
|
||||
let buttons = contextMenu.querySelectorAll('button');
|
||||
let deleteButton = findButton('Delete', buttons);
|
||||
expect(deleteButton, 'delete button').to.exist;
|
||||
await click(deleteButton);
|
||||
|
||||
// handle modal
|
||||
const modal = find('[data-test-modal="delete-posts"]');
|
||||
expect(modal, 'delete modal').to.exist;
|
||||
await click('[data-test-button="confirm"]');
|
||||
|
||||
// API request is correct - note, we don't mock the actual model updates
|
||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.filter, 'delete request id').to.equal(`id:['3','4']`);
|
||||
expect(lastRequest.method, 'delete request method').to.equal('DELETE');
|
||||
|
||||
// ensure ui shows these are now deleted
|
||||
expect(findAll('[data-test-post-id]').length, 'all posts count').to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and edit custom views', async function () {
|
||||
|
Loading…
Reference in New Issue
Block a user