docbrown/app/javascript/actionsPanel/__tests__/actionsPanel.test.js
Andy Zhao 885e042d31
[deploy] New Feature: Mod Actions Panel (#7777)
* db

* Add an iframe to host mod actions menu

This iframe is probably the fastest way to get a working mod actions
menu without rewriting existing code. We may eventually re-write this,
but based on Ben's recommendation we are going to go with an iframe
because it gets us a working feature quickly and it seems to be serving
us well in other parts of the application.

* Add the mod actions menu button to articles

When the user is "trusted" they will see a small badge on the lower
right-hand side of the screen. Clicking the icon reveals the mod actions
menu.

* Add header to actions panel (#7395)

* Update iframe border

* Add 'Moderate Post' header and button

* Use inline SVG over <img src='svg'>

* Use Crayons variables for units

* Use flexbox to center chevron

* Add accidentally deleted positioning

* Add chevron-right.svg

* Use a partial for actions panel and revert /mod page

* Load actions panel in iframe and not /mod

* Style UpVote, DownVote and Vomit buttons (#7421)

* Add a iframe to host mod actions menu

* Up and Downvote Icons

* Starting with vomit button

* Completed UI; interactions pending

* Incorporated Andy changes; making mod action-panel

* space reactions away from header

* UI tweaks and corrections 1

* Fixing styles to match crayons

* choose pure white color css variable

* Add thumbs-up emoji; address Lisa comments

* Trying to fix reactions interactions

* remove clearThumbReactions function

Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com>

* Add bottom layout for actions panel (#7501)

* WIP for Lisa

* Finalize mod actions bottom section

* Use showing instead of hidden for transition

* Add new optimized SVGs

* Fix weird height issues for actions panel

* Move hover styles below the cascade, yo

* Use margin-top for sticky footer over height: 100%

* Move initialize actions panel button to Webpack (#7506)

* Move actions panel JS code to webpack

* Refactor and use destructuring

Thanks @nickytonline!

Co-Authored-By: Nick Taylor <nick@iamdeveloper.com>

* Rename file

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>

* Configure Experience Level options for Mod Action Panel (#7507)

* Add a iframe to host mod actions menu

* Click interactions (no toggle of dropdowns yet)

Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com>

* Fix the iframe's path (#7509)

* Toggle the 'Set Experience Level' menu on button-click (#7529)

* Add a iframe to host mod actions menu

* Add set-exp dropdown functionality

* WIP: making exp level updates async

* Completes async implementation

* Fix controller for "actions_panel" and "/mod"

Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com>

* Add close button interaction (#7552)

* Rename actions panel pack file

* Add close button toggle to actions panel

* DB update

* Move more code to Webpack and add conditional tag adjustments section (#7652)

* Move JS code to Webpack

* Clean up a few style issues

* Add styling for tag adjustments

* Add active/inactive functionality

* Add conditional rendering for tag adjustments section

* Add missing variable oops (#7673)

* update with upstream

* Add missing subtract svg (#7695)

* Create 'Flag User' modal (#7626)

* Add a iframe to host mod actions menu

* Starting task

* Phase 1

* Fix "Close Modal" icon

* Implement open modal

* Working on all "Close Modal" actions

* Almost-complete implementation

* Remove Abuse Report snackbar

* Delete unneeded JS code

* Remove "How does this work?" links

* Link to Community Moderation page

* Addressing code review corrections

* Update api call to use "request" method

* Make else path clearer in api call

Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com>

* Mod Actions Panel: Prevent nonsensical reaction pairs (#7699)

* Still implementing

* Completed Implementation

* Address code review feednack; refactor "create" method

* Move scss variables to native-css (for ModPanel code only)

* Correct missing native css

* update to native-css again

* More sccs -> native-css fixes

* Adjust ModPanel Button position on mobile (#7744)

* Adjusted ModPanel position on mobile

* Better adjustment

* Fix phantom schema.rb changes

* Fix phantom schema.rb changes

* Finalize tag adjustments (#7740)

* Finalize tag adjustments and flow

* Fix logic for rendering adjust tags options

* Clean up alert response

* Rename user to trusted user

* Use new CSS variables

* Add/remove tag on article's tag section after adjusting

* Fix logic for admin tag input and some other niceties

* Final bug fixes mod panel (#7772)

* Use correct boolean statement

* Clear the value of the textarea not div

* Use the correct attribute

* Use actions panel for /mod page (#7775)

* Fix loading logic with tag adjust button conditional render

* Link to tag moderation for adjust tags section

* Use top over window.parent

Thanks @nickytonline!

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>

* Use Webpack alias to import

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>

* Use place-items shorthand over align-items + justify-content

Thanks @nickytonline!

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>

* Fix typo for utilities

* Add new line to end of file

* Disable linter for use for top function

* Use align-items and justify-content center

* Update SVGs to spec

* Remove unnecessary height and use spacing units for open button

* Remove accidentally added comment

* Error handling when selecting reactions; optimize "reactions.each" loop

* Add comment about clearing innerHTML

* Revert schema back to master

* Use each since there should only be two reactions

* Fix typo oops

* Add missing height oops

* Return if there are no reactions to be destroyed

* Center path with GUI tool shrug

* chose more appropriate name for JS pack; rewrote api calls using async-await

* Rename variables and methods and check categories before clearing

* Use addSnackbarItem instead of hardcoded version

* Place articleModerationTools pack behind user_signed_in

* Correct omission

* Implement dynamic imports for ModPanel tools

* Implemented async/await; added snackbar message for null response use-case

* Fix the "/article/mod" page; make sure all reactions, vomit-user and exp-level working as expected

* Update language to reflect new UI

* Close the modal after the "Confirm Action" button is clicked

* Change text for Spam/Abuse button

* Fix schema to use with master

* Update schema to master again

* Use crayons button for actions panel button

* UI tweak to play nicer with site themes

* Make actions panel work for dev.to/mod

* Add snackbar for reactions

* Update how does this work links

* Add "thumbsup" reactions to logging

* Add thumbsup to categories

* Add external link icon

* Changes to reflect "Privileged" and not "Negative" reactions; spec changes pending

* Updated instance variable

* Starting to update the specs

* Update "thumbsup" points from 10 to 5

* Still working on both requests

* Embracing an easier implemantation (thanks Andy!)

* Add tests for actions panel

* Add some basic tests for actions panel

* Change to flag to admins

* Indicate that 'Reason for Tag adjust' is required

Co-authored-by: Mac Siri <krairit.siri@gmail.com>

* Rename negative_reactions to privileged

* Update rating level to use correct values

* Use a different display value than backend value

* Don't show adjust tags for trusted users

* Add specs for moderation_routes for admin and super_admin

Co-authored-by: Arit Amana <msarit@gmail.com>
Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com>
Co-authored-by: Arit Amana <32520970+msarit@users.noreply.github.com>
Co-authored-by: Nick Taylor <nick@iamdeveloper.com>
Co-authored-by: Mac Siri <krairit.siri@gmail.com>
2020-06-08 13:24:18 -04:00

236 lines
8.7 KiB
JavaScript

/* eslint-disable no-restricted-globals */
import fetch from 'jest-fetch-mock';
import {
addCloseListener,
initializeHeight,
addReactionButtonListeners,
addAdjustTagListeners,
} from '../actionsPanel';
describe('addCloseListener()', () => {
test('toggles the mod actions panel and its button on click', () => {
document.body.innerHTML = `
<body>
<div class="mod-actions-menu showing">
<button class="close-actions-panel circle centered-icon" type="button" title="Close moderator actions panel">
</button>
</div>
<div id="mod-actions-menu-btn-area">
<div class="mod-actions-menu-btn crayons-btn crayons-btn--icon-rounded crayons-btn--s hidden">
</div>
</div>
</body>
`;
addCloseListener();
const closeButton = document.querySelector('.close-actions-panel');
closeButton.click();
// eslint-disable-next-line no-restricted-globals
const modPanel = top.document.querySelector('.mod-actions-menu');
const modPanelBtn = top.document.querySelector('.mod-actions-menu-btn');
expect(modPanel.classList).not.toContain('showing');
expect(modPanelBtn.classList).not.toContain('hidden');
});
});
describe('initializeHeight()', () => {
test('it sets the height of the proper elements', () => {
document.body.innerHTML = `
<body>
<div id="page-content">
<div id="page-content-inner">
</div>
</div>
</body>
`;
initializeHeight();
const { body } = document;
expect(document.documentElement.style.height).toEqual('100%');
expect(body.style.height).toEqual('100%');
expect(body.style.margin).toEqual('0px');
expect(body.style.marginTop).toEqual('0px');
expect(body.style.marginBottom).toEqual('0px');
expect(body.style.paddingTop).toEqual('0px');
expect(body.style.paddingTop).toEqual('0px');
});
});
describe('addReactionButtonListeners()', () => {
beforeEach(() => {
fetch.resetMocks();
document.body.innerHTML = `
<button class="reaction-button" data-reactable-id="1" data-reactable-type="Article" data-category="thumbsup">
</button>
<button class="reaction-button" data-reactable-id="1" data-reactable-type="Article" data-category="thumbsdown">
</button>
<button class="reaction-vomit-button" data-reactable-id="1" data-reactable-type="Article" data-category="vomit">
</button>
`;
const csrfToken = 'this-is-a-csrf-token';
window.fetch = fetch;
window.getCsrfToken = async () => csrfToken;
top.addSnackbarItem = jest.fn();
});
function sampleResponse(category, create = true) {
return JSON.stringify({
outcome: {
result: create ? 'create' : 'destroy',
category,
},
});
}
describe('when no reactions are already reacted on', () => {
test('it marks thumbs up reaction as reacted', async () => {
let category = 'thumbsup';
fetch.mockResponse(sampleResponse(category));
addReactionButtonListeners();
const thumbsupButton = document.querySelector(
`.reaction-button[data-category="${category}"]`,
);
thumbsupButton.click();
expect(thumbsupButton.classList).toContain('reacted');
category = 'thumbsdown';
const thumbsdownButton = document.querySelector(
`.reaction-button[data-category="${category}"]`,
);
thumbsdownButton.click();
expect(thumbsdownButton.classList).toContain('reacted');
category = 'vomit';
fetch.resetMocks();
fetch.mockResponse(sampleResponse(category));
const vomitButton = document.querySelector(
`.reaction-vomit-button[data-category="${category}"]`,
);
vomitButton.click();
expect(vomitButton.classList).toContain('reacted');
});
test('it unmarks the proper reaction(s) when positive/negative reactions are clicked', async () => {
let category = 'thumbsup';
fetch.mockResponse(sampleResponse(category));
addReactionButtonListeners();
const thumbsupButton = document.querySelector(
`.reaction-button[data-category="${category}"]`,
);
thumbsupButton.click();
category = 'thumbsdown';
fetch.resetMocks();
fetch.mockResponse(sampleResponse(category));
const thumbsdownButton = document.querySelector(
`.reaction-button[data-category="${category}"]`,
);
thumbsdownButton.click();
expect(thumbsupButton.classList).not.toContain('reacted');
fetch.resetMocks();
category = 'thumbsup';
fetch.mockResponse(sampleResponse(category, false));
thumbsupButton.click();
expect(thumbsdownButton.classList).not.toContain('reacted');
expect(thumbsupButton.classList).toContain('reacted');
category = 'vomit';
fetch.resetMocks();
fetch.mockResponse(sampleResponse(category));
const vomitButton = document.querySelector(
`.reaction-vomit-button[data-category="${category}"]`,
);
vomitButton.click();
expect(vomitButton.classList).toContain('reacted');
expect(thumbsupButton.classList).not.toContain('reacted');
});
});
});
describe('addAdjustTagListeners()', () => {
describe('when the user is tag moderator of #discuss', () => {
beforeEach(() => {
const tagName = 'discuss';
document.body.innerHTML = `
<a href="/t/${tagName}" class="tag">${tagName}</a>
<button class="adjustable-tag" data-adjustment-type="subtract" data-tag-name="${tagName}">
#${tagName}
</button>
<form id="adjustment-reason-container" class="adjustment-reason-container hidden">
<textarea class="crayons-textfield" placeholder="Reason for tag adjustment (required)" id="tag-adjustment-reason" required></textarea>
<button class="crayons-btn" id="tag-adjust-submit" type="submit">Submit</button>
</form>
`;
addAdjustTagListeners();
});
describe('when an article is tagged with #discuss', () => {
it('toggles the tag button and the form', () => {
const tagBtn = document.querySelector('.adjustable-tag');
tagBtn.click();
expect(tagBtn.classList).toContain('active');
expect(
document.getElementById('adjustment-reason-container').classList,
).not.toContain('hidden');
});
it('hides the form if the button is clicked again', () => {
const tagBtn = document.querySelector('.adjustable-tag');
tagBtn.click();
tagBtn.click();
expect(
document.getElementById('adjustment-reason-container').classList,
).toContain('hidden');
});
});
});
describe('when the user is an admin and the article has room for tags', () => {
describe('tag adjustment interactions', () => {
beforeEach(() => {
const tagName = 'discuss';
document.body.innerHTML = `
<div class="add-tag-container">
<input id="admin-add-tag" class="crayons-textfield" type="text" placeholder="Add a tag" data-article-id="1" data-adjustment-type="plus">
</div>
<a href="/t/${tagName}" class="tag">${tagName}</a>
<button class="adjustable-tag" data-adjustment-type="subtract" data-tag-name="${tagName}">
#${tagName}
</button>
<button class="adjustable-tag" data-adjustment-type="subtract" data-tag-name="ruby">
#ruby
</button>
<form id="adjustment-reason-container" class="adjustment-reason-container hidden">
<textarea class="crayons-textfield" placeholder="Reason for tag adjustment" id="tag-adjustment-reason" required></textarea>
<button class="crayons-btn" id="tag-adjust-submit" type="submit">Submit</button>
</form>
`;
addAdjustTagListeners();
});
it('shows the adjustment container when admin input is focused', () => {
document.getElementById('admin-add-tag').focus();
expect(
document.getElementById('adjustment-reason-container').classList,
).not.toContain('hidden');
});
it('triggers a confirmation if the admin add tag input was filled in', () => {
window.confirm = jest.fn();
document.getElementById('admin-add-tag').value = 'pizza';
document.querySelector('.adjustable-tag[data-tag-name="ruby"]').click();
expect(window.confirm).toHaveBeenCalled();
});
it('does not the hide reason container when going from one tag to another tag', () => {
document.querySelector('.adjustable-tag').click();
document.querySelector('.adjustable-tag[data-tag-name="ruby"]').click();
expect(
document.getElementById('adjustment-reason-container').classList,
).not.toContain('hidden');
});
});
});
});
/* eslint-enable no-restricted-globals */