diff --git a/app/javascript/internal/__mocks__/mutationObserver.js b/app/javascript/internal/__mocks__/mutationObserver.js new file mode 100644 index 000000000..bd9e91cba --- /dev/null +++ b/app/javascript/internal/__mocks__/mutationObserver.js @@ -0,0 +1,23 @@ +// Sourced from https://github.com/stimulusjs/stimulus/issues/34 +// and https://shime.sh/testing-stimulus + +// Setup MutationObserver shim since jsdom doesn't +// support it out of the box. + +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const shim = readFileSync( + resolve( + 'node_modules', + 'mutationobserver-shim', + 'dist', + 'mutationobserver.min.js', + ), + { encoding: 'utf-8' }, +); + +const script = window.document.createElement('script'); +script.textContent = shim; + +window.document.body.appendChild(script); diff --git a/app/javascript/internal/__tests__/controllers/article_controller.test.js b/app/javascript/internal/__tests__/controllers/article_controller.test.js new file mode 100644 index 000000000..e7011c9be --- /dev/null +++ b/app/javascript/internal/__tests__/controllers/article_controller.test.js @@ -0,0 +1,61 @@ +import { Application } from 'stimulus'; +import ArticleController from '../../controllers/article_controller'; +import '../../__mocks__/mutationObserver'; + +describe('ArticleController', () => { + beforeEach(() => { + document.body.innerHTML = `
+ + + + +
`; + + const application = Application.start(); + application.register('article', ArticleController); + }); + + // Unix timestamp, one hour ago + const initialValue = Math.round((Date.now() - 3600000) / 1000); + + describe('#increaseFeaturedNumber', () => { + it('increases the featured number input', () => { + const button = document.querySelectorAll('button')[0]; + const input = document.querySelector( + "[data-target='article.featuredNumber']", + ); + + input.value = initialValue; + button.click(); + + expect(parseInt(input.value, 10)).toBeGreaterThan(initialValue); + }); + }); + + describe('#decreaseFeaturedNumber', () => { + it('increases the featured number input', () => { + const button = document.querySelectorAll('button')[1]; + const input = document.querySelector( + "[data-target='article.featuredNumber']", + ); + + input.value = initialValue; + button.click(); + + expect(parseInt(input.value, 10)).toBeLessThan(initialValue); + }); + }); + + describe('#highlightElement', () => { + it('adds a class to the controller element', () => { + const button = document.querySelectorAll('button')[2]; + const element = document.querySelector("[data-controller='article']"); + + button.click(); + + expect( + element.classList.contains('highlighted-bg', 'highlighted-border'), + ).toBe(true); + }); + }); +}); diff --git a/app/javascript/internal/__tests__/controllers/buffer_controller.test.js b/app/javascript/internal/__tests__/controllers/buffer_controller.test.js new file mode 100644 index 000000000..0fef95860 --- /dev/null +++ b/app/javascript/internal/__tests__/controllers/buffer_controller.test.js @@ -0,0 +1,52 @@ +import { Application } from 'stimulus'; +import BufferController from '../../controllers/buffer_controller'; +import '../../__mocks__/mutationObserver'; + +describe('BufferController', () => { + beforeEach(() => { + document.body.innerHTML = `
+

+ + + +
`; + + const application = Application.start(); + application.register('buffer', BufferController); + }); + + describe('#tagBufferUpdateConfirmed', () => { + it('adds a badge to the header', () => { + const button = document.querySelectorAll('button')[0]; + const header = document.querySelector('h2'); + + button.click(); + + expect(header.firstChild.textContent).toMatch(/Confirm/); + }); + }); + + describe('#tagBufferUpdateDismissed', () => { + it('adds a badge to the header', () => { + const button = document.querySelectorAll('button')[1]; + const header = document.querySelector('h2'); + + button.click(); + + expect(header.firstChild.textContent).toMatch(/Dismiss/); + }); + }); + + describe('#highlightElement', () => { + it('adds a class to the controller element', () => { + const button = document.querySelectorAll('button')[2]; + const element = document.querySelector("[data-controller='buffer']"); + + button.click(); + + expect( + element.classList.contains('highlighted-bg', 'highlighted-border'), + ).toBe(true); + }); + }); +}); diff --git a/app/javascript/internal/controllers/article_controller.js b/app/javascript/internal/controllers/article_controller.js new file mode 100644 index 000000000..9d515db86 --- /dev/null +++ b/app/javascript/internal/controllers/article_controller.js @@ -0,0 +1,32 @@ +import { Controller } from 'stimulus'; + +export default class ArticleController extends Controller { + static targets = ['featuredNumber']; + + increaseFeaturedNumber() { + // Increases the article's chances of being seen + const seconds = new Date().getTime() / 1000; + this.featuredNumberTarget.value = Math.round(seconds); + } + + decreaseFeaturedNumber() { + // Decreases the article's chances of being seen + const seconds = new Date().getTime() / 1080; + this.featuredNumberTarget.value = Math.round(seconds); + } + + highlightElement() { + this.element.classList.add('highlighted-bg', 'highlighted-border'); + setTimeout(() => { + this.element.classList.remove('highlighted-bg'); + }, 350); + } + + get articleId() { + return parseInt(this.data.get('id'), 10); + } + + set articleId(value) { + this.data.set('id', value); + } +} diff --git a/app/javascript/internal/controllers/buffer_controller.js b/app/javascript/internal/controllers/buffer_controller.js new file mode 100644 index 000000000..47b7c9323 --- /dev/null +++ b/app/javascript/internal/controllers/buffer_controller.js @@ -0,0 +1,30 @@ +import { Controller } from 'stimulus'; + +export default class BufferController extends Controller { + static targets = ['header']; + + tagBufferUpdateConfirmed() { + this.headerTarget.innerHTML += + 'Confirm'; + } + + tagBufferUpdateDismissed() { + this.headerTarget.innerHTML += + 'Dismiss'; + } + + highlightElement() { + this.element.classList.add('highlighted-bg', 'highlighted-border'); + setTimeout(() => { + this.element.classList.remove('highlighted-bg'); + }, 350); + } + + get bufferUpdateId() { + return parseInt(this.data.get('id'), 10); + } + + set bufferUpdateId(value) { + this.data.set('id', value); + } +} diff --git a/app/javascript/packs/internal.js b/app/javascript/packs/internal.js new file mode 100644 index 000000000..e149f445e --- /dev/null +++ b/app/javascript/packs/internal.js @@ -0,0 +1,6 @@ +import { Application } from 'stimulus'; +import { definitionsFromContext } from 'stimulus/webpack-helpers'; + +const application = Application.start(); +const context = require.context('internal/controllers', true, /.js$/); +application.load(definitionsFromContext(context)); diff --git a/app/views/internal/articles/_article_script.html.erb b/app/views/internal/articles/_image_upload_script.html.erb similarity index 55% rename from app/views/internal/articles/_article_script.html.erb rename to app/views/internal/articles/_image_upload_script.html.erb index 76ea1f3bc..09e215312 100644 --- a/app/views/internal/articles/_article_script.html.erb +++ b/app/views/internal/articles/_image_upload_script.html.erb @@ -1,50 +1,5 @@ diff --git a/package.json b/package.json index b94c36f10..8c1bd0640 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "jest-fetch-mock": "^3.0.1", "jsdom": "^15.2.1", "lint-staged": "^10.0.0", + "mutationobserver-shim": "^0.3.3", "preact-render-spy": "1.3.0", "preact-render-to-json": "^3.6.6", "prettier": "^1.19.1", @@ -105,6 +106,7 @@ "preact-textarea-autosize": "^4.0.7", "prop-types": "^15.7.2", "pusher-js": "^5.0.3", + "stimulus": "^1.1.1", "twilio-video": "^2.0.0", "web-share-wrapper": "^0.2.1" }, diff --git a/yarn.lock b/yarn.lock index ecabd2193..835a6a456 100644 --- a/yarn.lock +++ b/yarn.lock @@ -290,6 +290,30 @@ dependencies: any-observable "^0.3.0" +"@stimulus/core@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@stimulus/core/-/core-1.1.1.tgz#42b0cfe5b73ca492f41de64b77a03980bae92c82" + integrity sha512-PVJv7IpuQx0MVPCBblXc6O2zbCmU8dlxXNH4bC9KK6LsvGaE+PCXXrXQfXUwAsse1/CmRu/iQG7Ov58himjiGg== + dependencies: + "@stimulus/mutation-observers" "^1.1.1" + +"@stimulus/multimap@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@stimulus/multimap/-/multimap-1.1.1.tgz#b95e3fd607345ab36e5d5b55486ee1a12d56b331" + integrity sha512-26R1fI3a8uUj0WlMmta4qcfIQGlagegdP4PTz6lz852q/dXlG6r+uPS/bx+H8GtfyS+OOXVr3SkZ0Zg0iRqRfQ== + +"@stimulus/mutation-observers@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@stimulus/mutation-observers/-/mutation-observers-1.1.1.tgz#0f6c6f081308427fed2a26360dda0c173b79cfc0" + integrity sha512-/zCnnw1KJlWO2mrx0yxYaRFZWMGnDMdOgSnI4hxDLxdWVuL2HMROU8FpHWVBLjKY3T9A+lGkcrmPGDHF3pfS9w== + dependencies: + "@stimulus/multimap" "^1.1.1" + +"@stimulus/webpack-helpers@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@stimulus/webpack-helpers/-/webpack-helpers-1.1.1.tgz#eff60cd4e58b921d1a2764dc5215f5141510f2c2" + integrity sha512-XOkqSw53N9072FLHvpLM25PIwy+ndkSSbnTtjKuyzsv8K5yfkFB2rv68jU1pzqYa9FZLcvZWP4yazC0V38dx9A== + "@storybook/addon-actions@3.4.12": version "3.4.12" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.4.12.tgz#ff6cbaf563c3cb5d648d6a35f66cfa50ced49bf4" @@ -8297,6 +8321,11 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +mutationobserver-shim@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#65869630bc89d7bf8c9cd9cb82188cd955aacd2b" + integrity sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ== + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -12071,6 +12100,14 @@ stickyfill@^1.1.1: resolved "https://registry.yarnpkg.com/stickyfill/-/stickyfill-1.1.1.tgz#39413fee9d025c74a7e59ceecb23784cc0f17f02" integrity sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI= +stimulus@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stimulus/-/stimulus-1.1.1.tgz#53c2fded6849e7b85eed3ed8dd76e33abd74bec5" + integrity sha512-R0mBqKp48YnRDZOxZ8hiOH4Ilph3Yj78CIFTBkCwyHs4iGCpe7xlEdQ7cjIxb+7qVCSxFKgxO+mAQbsNgt/5XQ== + dependencies: + "@stimulus/core" "^1.1.1" + "@stimulus/webpack-helpers" "^1.1.1" + store2@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/store2/-/store2-2.7.1.tgz#22070b7dc04748a792fc6912a58ab99d3a21d788"