From 1e56c89f5984ce38d78b9a3f8d33e63e8fc5c72b Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 13 Jun 2022 11:34:59 -0400 Subject: [PATCH 01/10] Handle custom error messages from API --- .../verification/steps/sms-verification.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/app/soapbox/features/verification/steps/sms-verification.tsx b/app/soapbox/features/verification/steps/sms-verification.tsx index 6e26050dd..a4017f061 100644 --- a/app/soapbox/features/verification/steps/sms-verification.tsx +++ b/app/soapbox/features/verification/steps/sms-verification.tsx @@ -1,3 +1,4 @@ +import { AxiosError } from 'axios'; import React from 'react'; import { useIntl } from 'react-intl'; import OtpInput from 'react-otp-input'; @@ -61,15 +62,13 @@ const SmsVerification = () => { ), ); setStatus(Statuses.REQUESTED); - }).catch(() => { - dispatch( - snackbar.error( - intl.formatMessage({ - id: 'sms_verification.fail', - defaultMessage: 'Failed to send SMS message to your phone number.', - }), - ), - ); + }).catch((error: AxiosError) => { + const message = (error.response?.data as any)?.message || intl.formatMessage({ + id: 'sms_verification.fail', + defaultMessage: 'Failed to send SMS message to your phone number.', + }); + + dispatch(snackbar.error(message)); setStatus(Statuses.FAIL); }); }, [phone, isValid]); From b644b91a94e4a93b80fd931a80e9dbff9c864504 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 13 Jun 2022 12:15:07 -0500 Subject: [PATCH 02/10] GitLab CI: display pretty test coverage --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5831e8a8d..0e120f5ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,7 +56,11 @@ jest: - "jest.config.js" - "package.json" - "yarn.lock" - coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ + artifacts: + reports: + coverage_report: + coverage_format: cobertura + path: .coverage/cobertura-coverage.xml nginx-test: stage: test From 0c173d57579e119b0e1408579859f0ec6b7f557d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 13 Jun 2022 13:26:51 -0500 Subject: [PATCH 03/10] Produce a junit.xml during test:coverage --- .gitignore | 1 + .gitlab-ci.yml | 1 + jest.config.js | 1 + package.json | 3 ++- yarn.lock | 15 +++++++++++++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 138e96d05..92e9362d8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /deploy.sh /.vs/ yarn-error.log* +/junit.xml /static/ /static-test/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0e120f5ab..6248eb152 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,6 +58,7 @@ jest: - "yarn.lock" artifacts: reports: + junit: junit.xml coverage_report: coverage_format: cobertura path: .coverage/cobertura-coverage.xml diff --git a/jest.config.js b/jest.config.js index 8796e5595..924d6f766 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,6 +28,7 @@ module.exports = { ], 'coverageDirectory': '/.coverage/', 'coverageReporters': ['html', 'text', 'text-summary', 'cobertura'], + 'reporters': ['jest-junit'], 'moduleDirectories': [ '/node_modules', '/app', diff --git a/package.json b/package.json index dbced84ea..d5dc14f33 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "jsdoc": "npx jsdoc -c jsdoc.conf.js", "manage:translations": "node ./webpack/translationRunner.js", "test": "npx cross-env NODE_ENV=test npx jest", - "test:coverage": "npx jest --coverage", + "test:coverage": "${npm_execpath} run test --coverage", "test:all": "${npm_execpath} run test:coverage && ${npm_execpath} run lint", "lint": "${npm_execpath} run lint:js && ${npm_execpath} run lint:sass", "lint:js": "npx eslint --ext .js,.jsx,.ts,.tsx . --cache", @@ -219,6 +219,7 @@ "fake-indexeddb": "^3.1.7", "husky": "^7.0.2", "jest": "^27.5.1", + "jest-junit": "^13.2.0", "lint-staged": ">=10", "raf": "^3.4.1", "react-intl-translations-manager": "^5.0.3", diff --git a/yarn.lock b/yarn.lock index fbdd06355..99973c815 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6778,6 +6778,16 @@ jest-jasmine2@^27.5.1: pretty-format "^27.5.1" throat "^6.0.1" +jest-junit@^13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-13.2.0.tgz#66eeb86429aafac8c1745a70f44ace185aacb943" + integrity sha512-B0XNlotl1rdsvFZkFfoa19mc634+rrd8E4Sskb92Bb8MmSXeWV9XJGUyctunZS1W410uAxcyYuPUGVnbcOH8cg== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^6.0.1" + uuid "^8.3.2" + xml "^1.0.1" + jest-leak-detector@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" @@ -11312,6 +11322,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" From f7354c75bc3f9b707fa5db3663ee87256f6bde6b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 13 Jun 2022 13:57:10 -0500 Subject: [PATCH 04/10] GitLab CI: add back coverage regex --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6248eb152..13bfe4d48 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,7 @@ jest: - "jest.config.js" - "package.json" - "yarn.lock" + coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ artifacts: reports: junit: junit.xml From a69e7e74e26b6af2ac4769bfb23ba0bc91b27bb8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 13 Jun 2022 14:10:32 -0500 Subject: [PATCH 05/10] Rerun tests when .gitlab-ci.yml changes --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 13bfe4d48..f2f769441 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,7 @@ jest: - "jest.config.js" - "package.json" - "yarn.lock" + - ".gitlab-ci.yml" coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ artifacts: reports: From fe9ce637e86b6c5dc86d4848dd38b0f1e8d38bc6 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Jun 2022 13:52:08 -0400 Subject: [PATCH 06/10] Correctly autofocus on poll input --- .../features/compose/components/{ => polls}/poll-form.tsx | 4 ++-- .../features/compose/containers/poll_form_container.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/soapbox/features/compose/components/{ => polls}/poll-form.tsx (98%) diff --git a/app/soapbox/features/compose/components/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx similarity index 98% rename from app/soapbox/features/compose/components/poll-form.tsx rename to app/soapbox/features/compose/components/polls/poll-form.tsx index 06d178518..18e096e8e 100644 --- a/app/soapbox/features/compose/components/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -7,7 +7,7 @@ import AutosuggestInput from 'soapbox/components/autosuggest_input'; import { Button, Divider, HStack, Stack, Text, Toggle } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; -import DurationSelector from './polls/duration-selector'; +import DurationSelector from './duration-selector'; import type { AutoSuggestion } from 'soapbox/components/autosuggest_input'; @@ -94,7 +94,7 @@ const Option = (props: IOption) => { onSuggestionsClearRequested={onSuggestionsClearRequested} onSuggestionSelected={onSuggestionSelected} searchTokens={[':']} - autoFocus={index === 0} + autoFocus={index === 0 || index >= 2} /> diff --git a/app/soapbox/features/compose/containers/poll_form_container.js b/app/soapbox/features/compose/containers/poll_form_container.js index 04c3b61ee..c6bae9f84 100644 --- a/app/soapbox/features/compose/containers/poll_form_container.js +++ b/app/soapbox/features/compose/containers/poll_form_container.js @@ -10,7 +10,7 @@ import { fetchComposeSuggestions, selectComposeSuggestion, } from '../../../actions/compose'; -import PollForm from '../components/poll-form'; +import PollForm from '../components/polls/poll-form'; const mapStateToProps = state => ({ suggestions: state.getIn(['compose', 'suggestions']), From fb94cb8cd18a3680a31028ddc7fbad2b2f24f447 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Jun 2022 14:07:43 -0400 Subject: [PATCH 07/10] Get rid of PollFormContainer --- .../compose/components/compose_form.js | 4 +- .../compose/components/polls/poll-form.tsx | 63 +++++++------------ .../compose/containers/poll_form_container.js | 57 ----------------- 3 files changed, 24 insertions(+), 100 deletions(-) delete mode 100644 app/soapbox/features/compose/containers/poll_form_container.js diff --git a/app/soapbox/features/compose/components/compose_form.js b/app/soapbox/features/compose/components/compose_form.js index 31e80741c..15b14fadb 100644 --- a/app/soapbox/features/compose/components/compose_form.js +++ b/app/soapbox/features/compose/components/compose_form.js @@ -14,13 +14,13 @@ import Icon from 'soapbox/components/icon'; import { Button } from 'soapbox/components/ui'; import { isMobile } from 'soapbox/is_mobile'; +import PollForm from '../components/polls/poll-form'; import ReplyMentions from '../components/reply_mentions'; import UploadForm from '../components/upload_form'; import Warning from '../components/warning'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import MarkdownButtonContainer from '../containers/markdown_button_container'; import PollButtonContainer from '../containers/poll_button_container'; -import PollFormContainer from '../containers/poll_form_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import QuotedStatusContainer from '../containers/quoted_status_container'; import ReplyIndicatorContainer from '../containers/reply_indicator_container'; @@ -361,7 +361,7 @@ class ComposeForm extends ImmutablePureComponent { !condensed &&
- +
} diff --git a/app/soapbox/features/compose/components/polls/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx index 18e096e8e..04bee234b 100644 --- a/app/soapbox/features/compose/components/polls/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -3,9 +3,10 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import { addPollOption, changePollOption, changePollSettings, clearComposeSuggestions, fetchComposeSuggestions, removePoll, removePollOption, selectComposeSuggestion } from 'soapbox/actions/compose'; import AutosuggestInput from 'soapbox/components/autosuggest_input'; import { Button, Divider, HStack, Stack, Text, Toggle } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import DurationSelector from './duration-selector'; @@ -31,12 +32,8 @@ interface IOption { maxChars: number numOptions: number onChange(index: number, value: string): void - onClearSuggestions(): void - onFetchSuggestions(token: string): void onRemove(index: number): void onRemovePoll(): void - onSuggestionSelected(tokenStart: number, token: string, value: string, key: (string | number)[]): void - suggestions?: any // list title: string } @@ -46,16 +43,16 @@ const Option = (props: IOption) => { maxChars, numOptions, onChange, - onClearSuggestions, - onFetchSuggestions, onRemove, onRemovePoll, - suggestions, title, } = props; + const dispatch = useAppDispatch(); const intl = useIntl(); + const suggestions = useAppSelector((state) => state.compose.get('suggestions')); + const handleOptionTitleChange = (event: React.ChangeEvent) => onChange(index, event.target.value); const handleOptionRemove = () => { @@ -66,13 +63,13 @@ const Option = (props: IOption) => { } }; - const onSuggestionsClearRequested = () => onClearSuggestions(); + const onSuggestionsClearRequested = () => dispatch(clearComposeSuggestions()); - const onSuggestionsFetchRequested = (token: string) => onFetchSuggestions(token); + const onSuggestionsFetchRequested = (token: string) => dispatch(fetchComposeSuggestions(token)); const onSuggestionSelected = (tokenStart: number, token: string | null, value: AutoSuggestion) => { if (token && typeof value === 'string') { - props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', index]); + dispatch(selectComposeSuggestion(tokenStart, token, value, ['poll', 'options', index])); } }; @@ -107,42 +104,26 @@ const Option = (props: IOption) => { ); }; -interface IPollForm { - expiresIn?: number - isMultiple?: boolean - onAddOption(value: string): void - onChangeOption(): void - onChangeSettings(value: string | number | undefined, isMultiple?: boolean): void - onClearSuggestions(): void - onFetchSuggestions(token: string): void - onRemoveOption(): void - onRemovePoll(): void - onSuggestionSelected(tokenStart: number, token: string, value: string, key: (string | number)[]): void - options?: any - suggestions?: any // list -} - -const PollForm = (props: IPollForm) => { - const { - expiresIn, - isMultiple, - onAddOption, - onChangeOption, - onChangeSettings, - onRemoveOption, - options, - ...filteredProps - } = props; - +const PollForm = () => { + const dispatch = useAppDispatch(); const intl = useIntl(); const pollLimits = useAppSelector((state) => state.instance.getIn(['configuration', 'polls']) as any); + const options = useAppSelector((state) => state.compose.getIn(['poll', 'options'])); + const expiresIn = useAppSelector((state) => state.compose.getIn(['poll', 'expires_in'])); + const isMultiple = useAppSelector((state) => state.compose.getIn(['poll', 'multiple'])); + const maxOptions = pollLimits.get('max_options'); const maxOptionChars = pollLimits.get('max_characters_per_option'); - const handleAddOption = () => onAddOption(''); + const onRemoveOption = (index: number) => dispatch(removePollOption(index)); + const onChangeOption = (index: number, title: string) => dispatch(changePollOption(index, title)); + const handleAddOption = () => dispatch(addPollOption('')); + const onChangeSettings = (expiresIn: string | number | undefined, isMultiple?: boolean) => + dispatch(changePollSettings(expiresIn, isMultiple)); const handleSelectDuration = (value: number) => onChangeSettings(value, isMultiple); const handleToggleMultiple = () => onChangeSettings(expiresIn, !isMultiple); + const onRemovePoll = () => dispatch(removePoll()); if (!options) { return null; @@ -160,7 +141,7 @@ const PollForm = (props: IPollForm) => { onRemove={onRemoveOption} maxChars={maxOptionChars} numOptions={options.size} - {...filteredProps} + onRemovePoll={onRemovePoll} /> ))} @@ -211,7 +192,7 @@ const PollForm = (props: IPollForm) => { {/* Remove Poll */}
-
diff --git a/app/soapbox/features/compose/containers/poll_form_container.js b/app/soapbox/features/compose/containers/poll_form_container.js deleted file mode 100644 index c6bae9f84..000000000 --- a/app/soapbox/features/compose/containers/poll_form_container.js +++ /dev/null @@ -1,57 +0,0 @@ -import { connect } from 'react-redux'; - -import { - addPollOption, - removePollOption, - changePollOption, - changePollSettings, - removePoll, - clearComposeSuggestions, - fetchComposeSuggestions, - selectComposeSuggestion, -} from '../../../actions/compose'; -import PollForm from '../components/polls/poll-form'; - -const mapStateToProps = state => ({ - suggestions: state.getIn(['compose', 'suggestions']), - options: state.getIn(['compose', 'poll', 'options']), - expiresIn: state.getIn(['compose', 'poll', 'expires_in']), - isMultiple: state.getIn(['compose', 'poll', 'multiple']), -}); - -const mapDispatchToProps = dispatch => ({ - onAddOption(title) { - dispatch(addPollOption(title)); - }, - - onRemoveOption(index) { - dispatch(removePollOption(index)); - }, - - onChangeOption(index, title) { - dispatch(changePollOption(index, title)); - }, - - onChangeSettings(expiresIn, isMultiple) { - dispatch(changePollSettings(expiresIn, isMultiple)); - }, - - onClearSuggestions() { - dispatch(clearComposeSuggestions()); - }, - - onFetchSuggestions(token) { - dispatch(fetchComposeSuggestions(token)); - }, - - onSuggestionSelected(position, token, accountId, path) { - dispatch(selectComposeSuggestion(position, token, accountId, path)); - }, - - onRemovePoll() { - dispatch(removePoll()); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(PollForm); From e39c8d6713a68786a149bb8e65644e1ef9e8e5e8 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Jun 2022 14:17:35 -0400 Subject: [PATCH 08/10] Add danger-link button --- app/soapbox/components/ui/button/useButtonStyles.ts | 3 ++- app/soapbox/features/compose/components/polls/poll-form.tsx | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/soapbox/components/ui/button/useButtonStyles.ts b/app/soapbox/components/ui/button/useButtonStyles.ts index 4e24c0d43..6f26d8be1 100644 --- a/app/soapbox/components/ui/button/useButtonStyles.ts +++ b/app/soapbox/components/ui/button/useButtonStyles.ts @@ -1,6 +1,6 @@ import classNames from 'classnames'; -type ButtonThemes = 'primary' | 'secondary' | 'ghost' | 'accent' | 'danger' | 'transparent' | 'link' +type ButtonThemes = 'primary' | 'secondary' | 'ghost' | 'accent' | 'danger' | 'transparent' | 'link' | 'danger-link' type ButtonSizes = 'sm' | 'md' | 'lg' type IButtonStyles = { @@ -25,6 +25,7 @@ const useButtonStyles = ({ ghost: 'shadow-none border-gray-200 text-gray-700 bg-white dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2', accent: 'border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2', danger: 'border-transparent text-danger-700 bg-danger-100 hover:bg-danger-200 focus:ring-danger-500 focus:ring-2 focus:ring-offset-2', + 'danger-link': 'border-transparent text-accent-500 hover:bg-accent-500 hover:bg-opacity-10', transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80', link: 'border-transparent text-primary-600 dark:text-primary-400 hover:bg-primary-100 hover:text-primary-700 dark:hover:bg-slate-900/50', }; diff --git a/app/soapbox/features/compose/components/polls/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx index 04bee234b..56cbd0d46 100644 --- a/app/soapbox/features/compose/components/polls/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -1,5 +1,3 @@ -'use strict'; - import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; @@ -192,7 +190,7 @@ const PollForm = () => { {/* Remove Poll */}
-
From 97b282e4e466613f52e8cf7b371e6bae1f947c32 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 14 Jun 2022 14:19:46 -0400 Subject: [PATCH 09/10] Fix selecting emojis in Poll --- app/soapbox/features/compose/components/polls/poll-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/compose/components/polls/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx index 56cbd0d46..cfcefc822 100644 --- a/app/soapbox/features/compose/components/polls/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -66,7 +66,7 @@ const Option = (props: IOption) => { const onSuggestionsFetchRequested = (token: string) => dispatch(fetchComposeSuggestions(token)); const onSuggestionSelected = (tokenStart: number, token: string | null, value: AutoSuggestion) => { - if (token && typeof value === 'string') { + if (token && typeof token === 'string') { dispatch(selectComposeSuggestion(tokenStart, token, value, ['poll', 'options', index])); } }; From 36c72c9990e207920c64b4c38db47179ed3dd029 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jun 2022 16:28:06 -0500 Subject: [PATCH 10/10] Jest: add back default reporter, fix empty cli output --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 924d6f766..e86307f93 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,7 +28,7 @@ module.exports = { ], 'coverageDirectory': '/.coverage/', 'coverageReporters': ['html', 'text', 'text-summary', 'cobertura'], - 'reporters': ['jest-junit'], + 'reporters': ['default', 'jest-junit'], 'moduleDirectories': [ '/node_modules', '/app',