diff --git a/app/javascript/.eslintrc.js b/app/javascript/.eslintrc.js index 46a7783c9..6eacb10ca 100644 --- a/app/javascript/.eslintrc.js +++ b/app/javascript/.eslintrc.js @@ -53,6 +53,14 @@ module.exports = { ], 'react/jsx-no-target-blank': [2, { enforceDynamicLinks: 'always' }], }, + overrides: [ + { + files: ['**/index.js'], // Or *.test.js + rules: { + 'import/export': 'off', + }, + }, + ], globals: { getCsrfToken: false, sendFetch: false, diff --git a/app/javascript/article-form/articleForm.jsx b/app/javascript/article-form/articleForm.jsx index 321c35b26..c8708f2d9 100644 --- a/app/javascript/article-form/articleForm.jsx +++ b/app/javascript/article-form/articleForm.jsx @@ -61,6 +61,25 @@ export default class ArticleForm extends Component { this.article = JSON.parse(article); organizations = organizations ? JSON.parse(organizations) : null; this.url = window.location.href; + + const previousContent = + JSON.parse( + localStorage.getItem(`editor-${version}-${window.location.href}`), + ) || {}; + const isLocalstorageNewer = + new Date(previousContent.updatedAt) > new Date(this.article.updated_at); + + const previousContentState = + previousContent && isLocalstorageNewer + ? { + title: previousContent.title || '', + tagList: previousContent.tagList || '', + mainImage: previousContent.mainImage || null, + bodyMarkdown: previousContent.bodyMarkdown || '', + edited: true, + } + : {}; + this.state = { id: this.article.id || null, // eslint-disable-line react/no-unused-state title: this.article.title || '', @@ -85,33 +104,21 @@ export default class ArticleForm extends Component { logoSvg, helpFor: null, helpPosition: null, + ...previousContentState, }; } componentDidMount() { - const { version, updatedAt } = this.state; - const previousContent = - JSON.parse( - localStorage.getItem(`editor-${version}-${window.location.href}`), - ) || {}; - const isLocalstorageNewer = - new Date(previousContent.updatedAt) > new Date(updatedAt); - - if (previousContent && isLocalstorageNewer) { - this.setState({ - title: previousContent.title || '', - tagList: previousContent.tagList || '', - mainImage: previousContent.mainImage || null, - bodyMarkdown: previousContent.bodyMarkdown || '', - edited: true, - }); - } - window.addEventListener('beforeunload', this.localStoreContent); } + componentWillUnmount() { + window.removeEventListener('beforeunload', this.localStoreContent); + } + componentDidUpdate() { const { previewResponse } = this.state; + if (previewResponse) { this.constructor.handleGistPreview(); this.constructor.handleRunkitPreview(); diff --git a/app/javascript/articles/Feed.jsx b/app/javascript/articles/Feed.jsx index 2edf20323..95546a98d 100644 --- a/app/javascript/articles/Feed.jsx +++ b/app/javascript/articles/Feed.jsx @@ -4,11 +4,16 @@ import PropTypes from 'prop-types'; /* global userData sendHapticMessage showModal buttonFormData renderNewSidebarCount */ export class Feed extends Component { - componentDidMount() { - const { timeFrame } = this.props; + constructor(props) { + super(props); + const { reading_list_ids = [] } = userData(); // eslint-disable-line camelcase - this.setState({ bookmarkedFeedItems: new Set(reading_list_ids) }); + this.state = { bookmarkedFeedItems: new Set(reading_list_ids) }; + } + + componentDidMount() { + const { timeFrame } = this.props; Feed.getFeedItems(timeFrame).then((feedItems) => { // Ensure first article is one with a main_image @@ -20,6 +25,7 @@ export class Feed extends Component { feedItems.splice(index, 1); const subStories = feedItems; const organizedFeedItems = [featuredStory, subStories].flat(); + this.setState({ feedItems: organizedFeedItems, podcastEpisodes: Feed.getPodcastEpisodes(), diff --git a/app/javascript/articles/components/SaveButton.jsx b/app/javascript/articles/components/SaveButton.jsx index d21d22473..5450fae88 100644 --- a/app/javascript/articles/components/SaveButton.jsx +++ b/app/javascript/articles/components/SaveButton.jsx @@ -3,9 +3,12 @@ import PropTypes from 'prop-types'; import { articlePropTypes } from '../../common-prop-types'; export class SaveButton extends Component { - componentDidMount() { - const { isBookmarked } = this.props; - this.setState({ buttonText: isBookmarked ? 'Saved' : 'Save' }); + constructor(props) { + super(props); + + const { isBookmarked } = props; + + this.state = { buttonText: isBookmarked ? 'Saved' : 'Save' }; } render() { diff --git a/app/javascript/chat/__tests__/util.test.js b/app/javascript/chat/__tests__/util.test.js index c3645fd33..d3cb76a28 100644 --- a/app/javascript/chat/__tests__/util.test.js +++ b/app/javascript/chat/__tests__/util.test.js @@ -30,7 +30,7 @@ describe('Chat utilities', () => { ); }); - test('should resolve if user and csrf token found.', () => { + test('should resolve if user and csrf token found.', async () => { const csrfToken = 'some-csrf-token'; const currentUser = { id: 41, @@ -49,7 +49,7 @@ describe('Chat utilities', () => { document.head.innerHTML = ``; document.body.setAttribute('data-user', JSON.stringify(currentUser)); - expect(getUserDataAndCsrfToken(document)).resolves.toEqual({ + expect(await getUserDataAndCsrfToken(document)).toEqual({ currentUser, csrfToken, }); diff --git a/app/javascript/chat/content.jsx b/app/javascript/chat/content.jsx index 4f84dfbfc..077dffeec 100644 --- a/app/javascript/chat/content.jsx +++ b/app/javascript/chat/content.jsx @@ -72,9 +72,10 @@ export default class Content extends Component { }; return ( + // TODO: A button (role="button") cannot contain other interactive elements, i.e. buttons. + // TODO: These should have key click events as well. // eslint-disable-next-line jsx-a11y/click-events-have-key-events