More frontend lint fixes (#8931)

This commit is contained in:
Nick Taylor 2020-06-29 08:19:13 -04:00 committed by GitHub
parent 2995687d0c
commit 30bfe1adfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 195 additions and 175 deletions

View file

@ -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,

View file

@ -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();

View file

@ -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(),

View file

@ -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() {

View file

@ -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 = `<meta name="csrf-token" content="${csrfToken}" />`;
document.body.setAttribute('data-user', JSON.stringify(currentUser));
expect(getUserDataAndCsrfToken(document)).resolves.toEqual({
expect(await getUserDataAndCsrfToken(document)).toEqual({
currentUser,
csrfToken,
});

View file

@ -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
<div
role="presentation"
className="activechatchannel__activecontent activechatchannel__activecontent--sidecar"
id="chat_activecontent"
onClick={onTriggerContent}

View file

@ -1,3 +1,5 @@
/* global userData */
export default function hideBlockedContent() {
const contentUserElements = Array.from(
document.querySelectorAll('div[data-content-user-id]'),
@ -5,12 +7,12 @@ export default function hideBlockedContent() {
const user = userData(); //global var
const blockedUserIds = user ? user.blocked_user_ids : [];
const divsToHide = contentUserElements.filter(div => {
const divsToHide = contentUserElements.filter((div) => {
const { contentUserId } = div.dataset;
return blockedUserIds.includes(parseInt(contentUserId, 10));
});
divsToHide.forEach(div => {
divsToHide.forEach((div) => {
if (div.className.includes('single-article')) {
div.style.display = 'none';
} else if (div.className.includes('single-comment-node')) {

View file

@ -1,71 +1,5 @@
import { updateListings, getQueryParams, getLocation } from '../utils';
describe('updateListings', () => {
const firstListing = {
id: 20,
category: 'misc',
location: 'West Refugio',
bumped_at: '2019-06-11T17:01:25.143Z',
processed_html:
'\u003cp\u003eEius et ullam. Dolores et qui. Quis \u003cstrong\u003equi\u003c/strong\u003e omnis.\u003c/p\u003e\n',
slug: 'illo-iure-quos-htyashsayas-5hk7',
title: 'Mentor wanted',
tags: ['go', 'git'],
user_id: 1,
author: {
name: 'Mrs. Yoko Christiansen',
username: 'mrschristiansenyoko',
profile_image_90:
'/uploads/user/profile_image/7/4b1c980a-beb0-4a5f-b3f2-acc91adc503c.png',
},
};
const secondListing = {
id: 21,
category: 'misc',
location: 'West Refugio',
bumped_at: '2019-06-11T17:01:25.143Z',
processed_html:
'\u003cp\u003eEius et ullam. Dolores et qui. Quis \u003cstrong\u003equi\u003c/strong\u003e omnis.\u003c/p\u003e\n',
slug: 'illo-iure-quos-ereerr-5hk7',
title: 'Second tag.',
tags: ['functional', 'clojure'],
user_id: 1,
author: {
name: 'Mrs. Ashahir',
username: 'mrschristiansenyoko',
profile_image_90:
'/uploads/user/profile_image/7/4b1c980a-beb0-4a5f-b3f2-acc91adc503c.png',
},
};
const thirdListing = {
id: 22,
category: 'misc',
location: 'West Refugio',
processed_html:
'\u003cp\u003eEius et ullam. Dolores et qui. Quis \u003cstrong\u003equi\u003c/strong\u003e omnis.\u003c/p\u003e\n',
slug: 'illo-iure-fss-ssasas-5hk7',
title: 'Illo iure quos perspiciatis.',
tags: ['twitter', 'learning'],
user_id: 1,
author: {
name: 'Mrs. John Mack',
username: 'mrschristiansenyoko',
profile_image_90:
'/uploads/user/profile_image/7/4b1c980a-beb0-4a5f-b3f2-acc91adc503c.png',
},
};
const classifiedListings = [firstListing, secondListing, thirdListing];
test('Should update the listings', () => {
const result = updateListings(classifiedListings);
const expectedResult = [firstListing, secondListing];
expect(result).toEqual(expectedResult);
});
});
describe('updateListings', () => {
beforeEach(() => {
window.history.pushState(
@ -75,7 +9,72 @@ describe('updateListings', () => {
);
});
test('Should get the query params', () => {
it('Should update the listings', () => {
const firstListing = {
id: 20,
category: 'misc',
location: 'West Refugio',
bumped_at: '2019-06-11T17:01:25.143Z',
processed_html:
'\u003cp\u003eEius et ullam. Dolores et qui. Quis \u003cstrong\u003equi\u003c/strong\u003e omnis.\u003c/p\u003e\n',
slug: 'illo-iure-quos-htyashsayas-5hk7',
title: 'Mentor wanted',
tags: ['go', 'git'],
user_id: 1,
author: {
name: 'Mrs. Yoko Christiansen',
username: 'mrschristiansenyoko',
profile_image_90:
'/uploads/user/profile_image/7/4b1c980a-beb0-4a5f-b3f2-acc91adc503c.png',
},
};
const secondListing = {
id: 21,
category: 'misc',
location: 'West Refugio',
bumped_at: '2019-06-11T17:01:25.143Z',
processed_html:
'\u003cp\u003eEius et ullam. Dolores et qui. Quis \u003cstrong\u003equi\u003c/strong\u003e omnis.\u003c/p\u003e\n',
slug: 'illo-iure-quos-ereerr-5hk7',
title: 'Second tag.',
tags: ['functional', 'clojure'],
user_id: 1,
author: {
name: 'Mrs. Ashahir',
username: 'mrschristiansenyoko',
profile_image_90:
'/uploads/user/profile_image/7/4b1c980a-beb0-4a5f-b3f2-acc91adc503c.png',
},
};
const thirdListing = {
id: 22,
category: 'misc',
location: 'West Refugio',
processed_html:
'\u003cp\u003eEius et ullam. Dolores et qui. Quis \u003cstrong\u003equi\u003c/strong\u003e omnis.\u003c/p\u003e\n',
slug: 'illo-iure-fss-ssasas-5hk7',
title: 'Illo iure quos perspiciatis.',
tags: ['twitter', 'learning'],
user_id: 1,
author: {
name: 'Mrs. John Mack',
username: 'mrschristiansenyoko',
profile_image_90:
'/uploads/user/profile_image/7/4b1c980a-beb0-4a5f-b3f2-acc91adc503c.png',
},
};
const classifiedListings = [firstListing, secondListing, thirdListing];
const result = updateListings(classifiedListings);
const expectedResult = [firstListing, secondListing];
expect(result).toEqual(expectedResult);
});
it('Should get the query params', () => {
const result = getQueryParams();
const expectedResult = {
crcat: 'test',
@ -93,7 +92,7 @@ describe('getLocation', () => {
let category;
let query;
test('Should return a location with slug and category', () => {
it('Should return a location with slug and category', () => {
slug = 'slug';
tags = ['clojure', 'functional'];
category = 'clojure';
@ -105,7 +104,7 @@ describe('getLocation', () => {
expect(result).toEqual(expectedResult);
});
test('Should return a location with category, query and tags', () => {
it('Should return a location with category, query and tags', () => {
slug = undefined;
tags = ['clojure', 'functional'];
category = 'clojure';
@ -117,7 +116,7 @@ describe('getLocation', () => {
expect(result).toEqual(expectedResult);
});
test('Should return a location with category and query', () => {
it('Should return a location with category and query', () => {
slug = undefined;
tags = [];
category = 'clojure';
@ -129,7 +128,7 @@ describe('getLocation', () => {
expect(result).toEqual(expectedResult);
});
test('Should return a location with category and tags', () => {
it('Should return a location with category and tags', () => {
slug = undefined;
tags = ['clojure', 'functional'];
category = 'clojure';
@ -141,7 +140,7 @@ describe('getLocation', () => {
expect(result).toEqual(expectedResult);
});
test('Should return a location with category', () => {
it('Should return a location with category', () => {
slug = undefined;
tags = [];
category = 'clojure';
@ -153,7 +152,7 @@ describe('getLocation', () => {
expect(result).toEqual(expectedResult);
});
test('Should return just the URL base', () => {
it('Should return just the URL base', () => {
slug = undefined;
tags = [];
category = '';

View file

@ -29,6 +29,43 @@ const LETTERS_NUMBERS = /[a-z0-9]/i;
class Tags extends Component {
constructor(props) {
super(props);
const { listing } = props;
const listingState = listing
? {
additionalTags: {
jobs: [
'remote',
'remoteoptional',
'lgbtbenefits',
'greencard',
'senior',
'junior',
'intermediate',
'401k',
'fulltime',
'contract',
'temp',
],
forhire: [
'remote',
'remoteoptional',
'lgbtbenefits',
'greencard',
'senior',
'junior',
'intermediate',
'401k',
'fulltime',
'contract',
'temp',
],
forsale: ['laptop', 'desktopcomputer', 'new', 'used'],
events: ['conference', 'meetup'],
collabs: ['paid', 'temp'],
},
}
: null;
this.state = {
selectedIndex: -1,
@ -37,48 +74,10 @@ class Tags extends Component {
cursorIdx: 0,
prevLen: 0,
showingRulesForTag: null,
...listingState,
};
}
componentDidMount() {
const { listing } = this.props;
if (listing === true) {
this.setState({
additionalTags: {
jobs: [
'remote',
'remoteoptional',
'lgbtbenefits',
'greencard',
'senior',
'junior',
'intermediate',
'401k',
'fulltime',
'contract',
'temp',
],
forhire: [
'remote',
'remoteoptional',
'lgbtbenefits',
'greencard',
'senior',
'junior',
'intermediate',
'401k',
'fulltime',
'contract',
'temp',
],
forsale: ['laptop', 'desktopcomputer', 'new', 'used'],
events: ['conference', 'meetup'],
collabs: ['paid', 'temp'],
},
});
}
}
componentDidUpdate() {
// stop cursor jumping if the user goes back to edit previous tags
const { cursorIdx, prevLen } = this.state;

View file

@ -25,7 +25,7 @@ describe('Search utilities', () => {
describe('getInitialSearchTerm', () => {
describe(`When the querystring key 'q' has a value`, () => {
test(`should return the querystring key q's value`, () => {
it(`should return the querystring key q's value`, () => {
const expected = 'hello';
const querystring = `?q=${expected}`;
const actual = getInitialSearchTerm(querystring);
@ -34,7 +34,7 @@ describe('Search utilities', () => {
});
describe(`When the querystring key 'q' has a + character representing a space`, () => {
test(`should return the querystring key q's decoded value with + characters replaced by a space`, () => {
it(`should return the querystring key q's decoded value with + characters replaced by a space`, () => {
const expected = `my visual studio setup`;
const querystring = `?q=my+visual+studio+setup`;
const actual = getInitialSearchTerm(querystring);
@ -43,7 +43,7 @@ describe('Search utilities', () => {
});
describe(`When the querystring key 'q' has an encoded value with markup`, () => {
test(`should return the querystring key q's decoded value`, () => {
it(`should return the querystring key q's decoded value`, () => {
const expected = `<script>alert('XSS!');</script>`;
const querystring = `?q=<script>alert(%27XSS!%27);</script>`;
const actual = getInitialSearchTerm(querystring);
@ -52,7 +52,7 @@ describe('Search utilities', () => {
});
describe(`When the querystring key 'q' has no value`, () => {
test(`should return an empty string`, () => {
it(`should return an empty string`, () => {
const expected = '';
const querystring = `?q=`;
const actual = getInitialSearchTerm(querystring);
@ -61,7 +61,7 @@ describe('Search utilities', () => {
});
describe(`When the querystring key 'q' does not exist`, () => {
test(`should return an empty string`, () => {
it(`should return an empty string`, () => {
const expected = '';
const querystring = '?';
const actual = getInitialSearchTerm(querystring);
@ -83,7 +83,7 @@ describe('Search utilities', () => {
delete global.InstantClick;
});
test('should call InstantClick.preLoad', () => {
it('should call InstantClick.preLoad', () => {
const location = {
origin: 'http://localhost',
href: 'http://localhost',
@ -97,7 +97,7 @@ describe('Search utilities', () => {
});
describe('When a search term is passed in', () => {
test('should call InstantClick.preLoad with the search term value in the URL', () => {
it('should call InstantClick.preLoad with the search term value in the URL', () => {
const location = {
origin: 'http://localhost',
href: 'http://localhost',
@ -110,7 +110,7 @@ describe('Search utilities', () => {
expect(InstantClick.preload).toBeCalledWith(expected);
});
test('should call InstantClick.preLoad with the encoded value for the search term', () => {
it('should call InstantClick.preLoad with the encoded value for the search term', () => {
const location = {
origin: 'http://localhost',
href: 'http://localhost',
@ -124,7 +124,7 @@ describe('Search utilities', () => {
expect(InstantClick.preload).toBeCalledWith(expected);
});
test('should call InstantClick.preLoad if the search term is empty', () => {
it('should call InstantClick.preLoad if the search term is empty', () => {
const location = {
origin: 'http://localhost',
href: 'http://localhost',
@ -140,7 +140,7 @@ describe('Search utilities', () => {
describe('hasInstantClick', () => {
describe('When instant click exists', () => {
test('should return true', () => {
it('should return true', () => {
global.instantClick = {};
expect(hasInstantClick()).toEqual(true);
delete global.instantClick;
@ -148,7 +148,7 @@ describe('Search utilities', () => {
});
describe('When instant click does not exist', () => {
test('should return false', () => {
it('should return false', () => {
expect(hasInstantClick()).toEqual(false);
});
});
@ -167,19 +167,19 @@ describe('Search utilities', () => {
delete global.InstantClick;
});
test('should call InstantClick.display if search term is empty', () => {
it('should call InstantClick.display if search term is empty', () => {
displaySearchResults({ searchTerm: '', location: { href: '' } });
expect(InstantClick.display).toHaveBeenCalledTimes(1);
});
test('should call InstantClick.display once', () => {
it('should call InstantClick.display once', () => {
displaySearchResults({ searchTerm: 'hello', location: { href: '' } });
expect(InstantClick.display).toHaveBeenCalledTimes(1);
});
test('should call InstantClick.display with the correct search URL', () => {
it('should call InstantClick.display with the correct search URL', () => {
const searchTerm = 'hello';
const location = {
origin: 'http://localhost',
@ -192,7 +192,7 @@ describe('Search utilities', () => {
);
});
test('should call InstantClick.display with an encoded search term', () => {
it('should call InstantClick.display with an encoded search term', () => {
const searchTerm = '#hello%';
const sanitizedSearchTerm = '%23hello%25';
const location = {
@ -206,7 +206,7 @@ describe('Search utilities', () => {
);
});
test('should call InstantClick.display with filters, if present', () => {
it('should call InstantClick.display with filters, if present', () => {
const searchTerm = '#hello%';
const sanitizedSearchTerm = '%23hello%25';
const filterParameters = 'class_name:Article';
@ -222,7 +222,7 @@ describe('Search utilities', () => {
);
});
test('should not call InstantClick.display with filters, if filter querystring key is present, but has no value', () => {
it('should not call InstantClick.display with filters, if filter querystring key is present, but has no value', () => {
const searchTerm = '#hello%';
const sanitizedSearchTerm = '%23hello%25';
const location = {
@ -239,32 +239,27 @@ describe('Search utilities', () => {
});
describe('fetchSearch', () => {
let responsePromise;
let dataHash;
beforeEach(() => {
fetch.resetMocks();
fetch.once({});
dataHash = { name: 'jav' };
responsePromise = fetchSearch('tags', dataHash);
it('should return a Promise', () => {
expect(fetchSearch('tags', { name: 'jav' })).toBeInstanceOf(Promise);
});
test('should return a Promise', () => {
expect(responsePromise).toBeInstanceOf(Promise);
});
it('should return response formatted as JSON', async () => {
const expected = { results: [] };
test('should return response formatted as JSON', () => {
responsePromise.then((response) => {
expect(response).toBeInstanceOf(Object);
expect(response).toMatchObject({ results: expect.any(Array) });
});
fetch.mockResponse(JSON.stringify({ results: [] }));
const response = await fetchSearch('tags', { name: 'jav' });
expect(response).toBeInstanceOf(Object);
expect(response).toMatchObject(expected);
});
});
describe('createSearchUrl', () => {
test('should return a url string', () => {
it('should return a url string', () => {
const dataHash = { name: 'jav', tags: ['one', 'two'] };
const responseString = createSearchUrl(dataHash);
expect(responseString).toEqual('name=jav&tags%5B%5D=one&tags%5B%5D=two');
});
});