From d9b14c607945d2f42c520ca9042406ec79121653 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 25 Jun 2022 14:45:00 -0500 Subject: [PATCH 01/41] StatusActionButton: show selected emoji --- .../components/status-action-button.tsx | 33 +++++++++++-------- app/soapbox/components/status_action_bar.tsx | 1 + 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/soapbox/components/status-action-button.tsx b/app/soapbox/components/status-action-button.tsx index 6bbd86fa8..43cfea942 100644 --- a/app/soapbox/components/status-action-button.tsx +++ b/app/soapbox/components/status-action-button.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; import React from 'react'; -import { Text, Icon } from 'soapbox/components/ui'; +import { Text, Icon, Emoji } from 'soapbox/components/ui'; import { shortNumberFormat } from 'soapbox/utils/numbers'; const COLORS = { @@ -15,7 +15,7 @@ interface IStatusActionCounter { count: number, } -/** Action button numerical counter, eg "5" likes */ +/** Action button numerical counter, eg "5" likes. */ const StatusActionCounter: React.FC = ({ count = 0 }): JSX.Element => { return ( @@ -31,10 +31,11 @@ interface IStatusActionButton extends React.ButtonHTMLAttributes): JSX.Element => { - const { icon, className, iconClassName, active, color, filled = false, count = 0, ...filteredProps } = props; +const StatusActionButton = React.forwardRef((props, ref): JSX.Element => { + const { icon, className, iconClassName, active, color, filled = false, count = 0, emoji, ...filteredProps } = props; return ( + ) : ( + + )} ) : ( Date: Mon, 4 Jul 2022 12:53:25 -0500 Subject: [PATCH 03/41] Upgrade axios to v1.0.0-alpha.1, remove custom toFormData implementation --- app/soapbox/actions/me.ts | 6 +++++- app/soapbox/features/edit_profile/index.tsx | 22 +------------------ package.json | 2 +- yarn.lock | 24 +++++++++++++-------- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/app/soapbox/actions/me.ts b/app/soapbox/actions/me.ts index 4cb4b2350..f6a79ab85 100644 --- a/app/soapbox/actions/me.ts +++ b/app/soapbox/actions/me.ts @@ -67,7 +67,11 @@ const patchMe = (params: Record) => dispatch(patchMeRequest()); return api(getState) - .patch('/api/v1/accounts/update_credentials', params) + .patch('/api/v1/accounts/update_credentials', params, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) .then(response => { persistAuthAccount(response.data, params); dispatch(patchMeSuccess(response.data)); diff --git a/app/soapbox/features/edit_profile/index.tsx b/app/soapbox/features/edit_profile/index.tsx index e32473505..1eaa74e62 100644 --- a/app/soapbox/features/edit_profile/index.tsx +++ b/app/soapbox/features/edit_profile/index.tsx @@ -25,25 +25,6 @@ const hidesNetwork = (account: Account): boolean => { return Boolean(hide_followers && hide_follows && hide_followers_count && hide_follows_count); }; -/** Converts JSON objects to FormData. */ -// https://stackoverflow.com/a/60286175/8811886 -// @ts-ignore -const toFormData = (f => f(f))(h => f => f(x => h(h)(f)(x)))(f => fd => pk => d => { - if (d instanceof Object) { - // eslint-disable-next-line consistent-return - Object.keys(d).forEach(k => { - const v = d[k]; - if (pk) k = `${pk}[${k}]`; - if (v instanceof Object && !(v instanceof Date) && !(v instanceof File)) { - return f(fd)(k)(v); - } else { - fd.append(k, v); - } - }); - } - return fd; -})(new FormData())(); - const messages = defineMessages({ heading: { id: 'column.edit_profile', defaultMessage: 'Edit profile' }, header: { id: 'edit_profile.header', defaultMessage: 'Edit Profile' }, @@ -205,9 +186,8 @@ const EditProfile: React.FC = () => { const handleSubmit: React.FormEventHandler = (event) => { const promises = []; - const formData = toFormData(data); - promises.push(dispatch(patchMe(formData))); + promises.push(dispatch(patchMe(data))); if (features.muteStrangers) { promises.push( diff --git a/package.json b/package.json index 47098b26b..8650f051c 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "@types/uuid": "^8.3.4", "array-includes": "^3.1.5", "autoprefixer": "^10.4.2", - "axios": "^0.27.2", + "axios": "^1.0.0-alpha.1", "axios-mock-adapter": "^1.21.1", "babel-loader": "^8.2.5", "babel-plugin-lodash": "^3.3.4", diff --git a/yarn.lock b/yarn.lock index 5db73d9ab..2fc8466ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3506,13 +3506,14 @@ axios-mock-adapter@^1.21.1: fast-deep-equal "^3.1.3" is-buffer "^2.0.5" -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== +axios@^1.0.0-alpha.1: + version "1.0.0-alpha.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.0.0-alpha.1.tgz#ce69c17ca7605d01787ca754dd906e6fccdf71ee" + integrity sha512-p+meG161943WT+K7sJYquHR46xxi/z0tk7vnSmEf/LrfEAyiP+0uTMMYk1OEo1IRF18oGRhnFxN1y8fLcXaTMw== dependencies: - follow-redirects "^1.14.9" + follow-redirects "^1.15.0" form-data "^4.0.0" + proxy-from-env "^1.1.0" axobject-query@^2.2.0: version "2.2.0" @@ -5827,10 +5828,10 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== -follow-redirects@^1.14.9: - version "1.14.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" - integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +follow-redirects@^1.15.0: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== foreach@^2.0.5: version "2.0.5" @@ -9501,6 +9502,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" From d168302e7201366515643f9f6c8007053fe310ff Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 4 Jul 2022 13:17:01 -0500 Subject: [PATCH 04/41] EditProfile: reenable birthdays with TextInput (for now) --- app/soapbox/features/edit_profile/index.tsx | 25 ++++++++++++++------- app/soapbox/utils/features.ts | 4 +--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/soapbox/features/edit_profile/index.tsx b/app/soapbox/features/edit_profile/index.tsx index 1eaa74e62..9d5c3b622 100644 --- a/app/soapbox/features/edit_profile/index.tsx +++ b/app/soapbox/features/edit_profile/index.tsx @@ -4,9 +4,19 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { updateNotificationSettings } from 'soapbox/actions/accounts'; import { patchMe } from 'soapbox/actions/me'; import snackbar from 'soapbox/actions/snackbar'; -import BirthdayInput from 'soapbox/components/birthday_input'; import List, { ListItem } from 'soapbox/components/list'; -import { Button, Column, Form, FormActions, FormGroup, Input, Textarea, HStack, Toggle, FileInput } from 'soapbox/components/ui'; +import { + Button, + Column, + FileInput, + Form, + FormActions, + FormGroup, + HStack, + Input, + Textarea, + Toggle, +} from 'soapbox/components/ui'; import Streamfield, { StreamfieldComponent } from 'soapbox/components/ui/streamfield/streamfield'; import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks'; import { normalizeAccount } from 'soapbox/normalizers'; @@ -222,10 +232,6 @@ const EditProfile: React.FC = () => { }; }; - const handleBirthdayChange = (date: string) => { - updateData('birthday', date); - }; - const handleHideNetworkChange: React.ChangeEventHandler = e => { const hide = e.target.checked; @@ -309,9 +315,12 @@ const EditProfile: React.FC = () => { } > - )} diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 6f4a77ddf..206eaf067 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -148,9 +148,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see POST /api/v1/accounts * @see PATCH /api/v1/accounts/update_credentials */ - // birthdays: v.software === PLEROMA && gte(v.version, '2.4.50'), - // FIXME: temporarily disabled until they can be deleted on the backend. - birthdays: false, + birthdays: v.software === PLEROMA && v.build === SOAPBOX && gte(v.version, '2.4.50'), /** Whether people who blocked you are visible through the API. */ blockersVisible: features.includes('blockers_visible'), From cef4b16a6966e01d98601d40027e319d0f022f09 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 4 Jul 2022 13:26:06 -0500 Subject: [PATCH 05/41] Account normalizer: use '' as default birthday --- app/soapbox/normalizers/__tests__/account.test.ts | 7 +++++++ app/soapbox/normalizers/account.ts | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/soapbox/normalizers/__tests__/account.test.ts b/app/soapbox/normalizers/__tests__/account.test.ts index b78017e06..761b2fac5 100644 --- a/app/soapbox/normalizers/__tests__/account.test.ts +++ b/app/soapbox/normalizers/__tests__/account.test.ts @@ -47,6 +47,13 @@ describe('normalizeAccount()', () => { expect(result.birthday).toEqual('1993-07-03'); }); + it('normalizes undefined birthday to empty string', () => { + const account = require('soapbox/__fixtures__/mastodon-account.json'); + const result = normalizeAccount(account); + + expect(result.birthday).toEqual(''); + }); + it('normalizes Pleroma legacy fields', () => { const account = require('soapbox/__fixtures__/pleroma-2.2.2-account.json'); const result = normalizeAccount(account); diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts index 784ba08aa..1a519b8a8 100644 --- a/app/soapbox/normalizers/account.ts +++ b/app/soapbox/normalizers/account.ts @@ -24,7 +24,7 @@ export const AccountRecord = ImmutableRecord({ acct: '', avatar: '', avatar_static: '', - birthday: undefined as string | undefined, + birthday: '', bot: false, created_at: new Date(), discoverable: false, @@ -261,6 +261,12 @@ const normalizeDiscoverable = (account: ImmutableMap) => { return account.set('discoverable', discoverable); }; +/** Normalize undefined/null birthday to empty string. */ +const fixBirthday = (account: ImmutableMap) => { + const birthday = account.get('birthday'); + return account.set('birthday', birthday || ''); +}; + export const normalizeAccount = (account: Record) => { return AccountRecord( ImmutableMap(fromJS(account)).withMutations(account => { @@ -280,6 +286,7 @@ export const normalizeAccount = (account: Record) => { addStaffFields(account); fixUsername(account); fixDisplayName(account); + fixBirthday(account); addInternalFields(account); }), ); From 02f0c15a8c34c9fadfe1496e7025b787d4326cdc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 4 Jul 2022 13:54:57 -0500 Subject: [PATCH 06/41] Modal: remove overflow-hidden --- app/soapbox/components/ui/modal/modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/ui/modal/modal.tsx b/app/soapbox/components/ui/modal/modal.tsx index 3987d6afe..10f0976ec 100644 --- a/app/soapbox/components/ui/modal/modal.tsx +++ b/app/soapbox/components/ui/modal/modal.tsx @@ -83,7 +83,7 @@ const Modal: React.FC = ({ }, [skipFocus, buttonRef]); return ( -
+
{title && ( From 46cb004c04ce60902175268850f71ee5090b341d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 4 Jul 2022 14:57:27 -0500 Subject: [PATCH 07/41] Tailwind: reduce xl breakpoint to 1280px --- tailwind.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailwind.config.js b/tailwind.config.js index 7ac10feee..45644a143 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -8,7 +8,7 @@ module.exports = { sm: '581px', md: '768px', lg: '976px', - xl: '1440px', + xl: '1280px', }, extend: { fontSize: { From 01b32238868e2b52aec78d99a1dd47f71c2b1938 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 4 Jul 2022 15:13:19 -0500 Subject: [PATCH 08/41] Account: don't grow more than needed --- app/soapbox/components/account.tsx | 2 +- app/soapbox/components/status.tsx | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index d6d076889..2d53e6da8 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -159,7 +159,7 @@ const Account = ({ return (
- + {children}} diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 3d964b3dc..200b2de10 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -474,18 +474,16 @@ class Status extends ImmutablePureComponent { {reblogElementMobile}
- - - +
From db56044f24f0fb9785b071185e3c1f9f756e9c88 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 4 Jul 2022 16:21:35 -0500 Subject: [PATCH 09/41] Revert "Remove react-router-scroll-4" This reverts commit 01d9f918ce2f09e3836c3028c5a267afef4cb878. --- app/soapbox/containers/soapbox.tsx | 27 ++++++++++++++++++--------- package.json | 1 + yarn.lock | 18 +++++++++++++++++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index e87d858d1..29cfd74fb 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -5,6 +5,8 @@ import React, { useState, useEffect } from 'react'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom'; +// @ts-ignore: it doesn't have types +import { ScrollContext } from 'react-router-scroll-4'; import { loadInstance } from 'soapbox/actions/instance'; import { fetchMe } from 'soapbox/actions/me'; @@ -115,6 +117,11 @@ const SoapboxMount = () => { }); }, []); + // @ts-ignore: I don't actually know what these should be, lol + const shouldUpdateScroll = (prevRouterProps, { location }) => { + return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey); + }; + /** Whether to display a loading indicator. */ const showLoading = [ me === null, @@ -223,17 +230,19 @@ const SoapboxMount = () => { {helmet} - <> - {renderBody()} + + <> + {renderBody()} - - {(Component) => } - + + {(Component) => } + - - {Component => } - - + + {Component => } + + + diff --git a/package.json b/package.json index 47098b26b..f42719a69 100644 --- a/package.json +++ b/package.json @@ -167,6 +167,7 @@ "react-popper": "^2.3.0", "react-redux": "^7.2.5", "react-router-dom": "^5.3.0", + "react-router-scroll-4": "^1.0.0-beta.2", "react-simple-pull-to-refresh": "^1.3.0", "react-sparklines": "^1.7.0", "react-sticky-box": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 5db73d9ab..d620b35e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6667,7 +6667,7 @@ intl@^1.2.5: resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde" integrity sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94= -invariant@^2.2.2: +invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -9811,6 +9811,14 @@ react-router-dom@^5.3.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-router-scroll-4@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/react-router-scroll-4/-/react-router-scroll-4-1.0.0-beta.2.tgz#d887063ec0f66124aaf450158dd158ff7d3dc279" + integrity sha512-K67Dnm75naSBs/WYc2CDNxqU+eE8iA3I0wSCArgGSHb0xR/7AUcgUEXtCxrQYVTogXvjVK60gmwYvOyRQ6fuBA== + dependencies: + scroll-behavior "^0.9.1" + warning "^3.0.0" + react-router@5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d" @@ -10427,6 +10435,14 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.0.0" +scroll-behavior@^0.9.1: + version "0.9.12" + resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.12.tgz#1c22d273ec4ce6cd4714a443fead50227da9424c" + integrity sha512-18sirtyq1P/VsBX6O/vgw20Np+ngduFXEMO4/NDFXabdOKBL2kjPVUpz1y0+jm99EWwFJafxf5/tCyMeXt9Xyg== + dependencies: + dom-helpers "^3.4.0" + invariant "^2.2.4" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" From 62ea445da587bac450787ecbce44fcbc9338ea76 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 4 Jul 2022 17:24:18 -0500 Subject: [PATCH 10/41] StatusActionButton: use a black/white counter if emoji is present --- app/soapbox/components/status-action-button.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/status-action-button.tsx b/app/soapbox/components/status-action-button.tsx index 43cfea942..0e837168d 100644 --- a/app/soapbox/components/status-action-button.tsx +++ b/app/soapbox/components/status-action-button.tsx @@ -47,8 +47,9 @@ const StatusActionButton = React.forwardRef Date: Tue, 5 Jul 2022 11:53:05 -0500 Subject: [PATCH 11/41] Datepicker: fix test failing due to date matching today --- .../datepicker/__tests__/datepicker.test.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx b/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx index 5fe6ca9d6..c4f94c89f 100644 --- a/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx +++ b/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx @@ -56,26 +56,41 @@ describe('', () => { it('calls the onChange function when the inputs change', async() => { const handler = jest.fn(); render(); + const today = new Date(); + + /** + * A date with a different day, month, and year than today + * so this test will always pass! + */ + const notToday = new Date( + today.getFullYear() - 1, // last year + (today.getMonth() + 2) % 11, // two months from now (mod 11 because it's 0-indexed) + (today.getDate() + 2) % 28, // 2 days from now (for timezone stuff) + ); + + const month = notToday.toLocaleString('en-us', { month: 'long' }); + const year = String(notToday.getFullYear()); + const day = String(notToday.getDate()); expect(handler.mock.calls.length).toEqual(1); await userEvent.selectOptions( screen.getByTestId('datepicker-month'), - screen.getByRole('option', { name: 'February' }), + screen.getByRole('option', { name: month }), ); expect(handler.mock.calls.length).toEqual(2); await userEvent.selectOptions( screen.getByTestId('datepicker-year'), - screen.getByRole('option', { name: '2020' }), + screen.getByRole('option', { name: year }), ); expect(handler.mock.calls.length).toEqual(3); await userEvent.selectOptions( screen.getByTestId('datepicker-day'), - screen.getByRole('option', { name: '5' }), + screen.getByRole('option', { name: day }), ); expect(handler.mock.calls.length).toEqual(4); From 9ce9a3f951d26f6d3f6315f068a4b477ed386d86 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Jul 2022 21:04:42 -0500 Subject: [PATCH 12/41] patchMe(): use multipart/form-data only optionally --- app/soapbox/actions/me.ts | 14 +++++++------- app/soapbox/features/edit_profile/index.tsx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/soapbox/actions/me.ts b/app/soapbox/actions/me.ts index f6a79ab85..074f5d4cc 100644 --- a/app/soapbox/actions/me.ts +++ b/app/soapbox/actions/me.ts @@ -6,7 +6,7 @@ import api from '../api'; import { loadCredentials } from './auth'; import { importFetchedAccount } from './importer'; -import type { AxiosError } from 'axios'; +import type { AxiosError, AxiosRequestHeaders } from 'axios'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -62,16 +62,16 @@ const persistAuthAccount = (account: APIEntity, params: Record) => } }; -const patchMe = (params: Record) => +const patchMe = (params: Record, isFormData = false) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(patchMeRequest()); + const headers: AxiosRequestHeaders = isFormData ? { + 'Content-Type': 'multipart/form-data', + } : {}; + return api(getState) - .patch('/api/v1/accounts/update_credentials', params, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }) + .patch('/api/v1/accounts/update_credentials', params, { headers }) .then(response => { persistAuthAccount(response.data, params); dispatch(patchMeSuccess(response.data)); diff --git a/app/soapbox/features/edit_profile/index.tsx b/app/soapbox/features/edit_profile/index.tsx index 9d5c3b622..a2fc13b92 100644 --- a/app/soapbox/features/edit_profile/index.tsx +++ b/app/soapbox/features/edit_profile/index.tsx @@ -197,7 +197,7 @@ const EditProfile: React.FC = () => { const handleSubmit: React.FormEventHandler = (event) => { const promises = []; - promises.push(dispatch(patchMe(data))); + promises.push(dispatch(patchMe(data, true))); if (features.muteStrangers) { promises.push( From 53629737b1d2a73ea522e4eaafdb8d345b7304fc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 10:13:34 -0500 Subject: [PATCH 13/41] CSS: 1440px --> 1280px --- app/styles/about.scss | 4 ++-- app/styles/footer.scss | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/styles/about.scss b/app/styles/about.scss index 5e2753feb..038e34ec8 100644 --- a/app/styles/about.scss +++ b/app/styles/about.scss @@ -52,7 +52,7 @@ $fluid-breakpoint: $maximum-width + 20px; .container { width: 100%; - max-width: 1440px; + max-width: 1280px; @media screen and (max-width: $no-gap-breakpoint) { padding: 0; @@ -82,7 +82,7 @@ $fluid-breakpoint: $maximum-width + 20px; .header-container { display: flex; - width: 1440px; + width: 1280px; align-items: stretch; justify-content: center; flex-wrap: nowrap; diff --git a/app/styles/footer.scss b/app/styles/footer.scss index e73994922..835d40061 100644 --- a/app/styles/footer.scss +++ b/app/styles/footer.scss @@ -15,7 +15,7 @@ .footer-container { display: flex; - width: 1440px; + width: 1280px; align-items: center; padding: 0 20px; flex-direction: column-reverse; From 2a3e5d67fcaa5abf1a14cb1ce5d3783219d362b5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 11:23:35 -0500 Subject: [PATCH 14/41] GitLab CI: try jest --runInBand --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7fb8999e2..90c856e40 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,7 +57,7 @@ lint-sass: jest: stage: test - script: yarn test:coverage + script: yarn test:coverage --runInBand only: changes: - "**/*.js" From e1807c9250073a96be0b8aac2abbc7ca80aadb25 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 11:26:44 -0500 Subject: [PATCH 15/41] tsconfig: don't exclude any files --- tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 8989bd57e..dba721d72 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,5 @@ "experimentalDecorators": true, "esModuleInterop": true, "typeRoots": [ "./types", "./node_modules/@types"] - }, - "exclude": ["node_modules", "types", "**/*.test.*", "**/__mocks__/*", "**/__tests__/*"] + } } From a509428f9714f9ece1574fe8158a8c386d874734 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 11:32:14 -0500 Subject: [PATCH 16/41] utils/timelines test: remove fromJS calls --- app/soapbox/utils/__tests__/timelines.test.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/soapbox/utils/__tests__/timelines.test.ts b/app/soapbox/utils/__tests__/timelines.test.ts index df48bdae9..9be55575d 100644 --- a/app/soapbox/utils/__tests__/timelines.test.ts +++ b/app/soapbox/utils/__tests__/timelines.test.ts @@ -7,67 +7,67 @@ import { shouldFilter } from '../timelines'; describe('shouldFilter', () => { it('returns false under normal circumstances', () => { const columnSettings = fromJS({}); - const status = normalizeStatus(fromJS({})); + const status = normalizeStatus({}); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('reblog: returns true when `shows.reblog == false`', () => { const columnSettings = fromJS({ shows: { reblog: false } }); - const status = normalizeStatus(fromJS({ reblog: {} })); + const status = normalizeStatus({ reblog: {} }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('reblog: returns false when `shows.reblog == true`', () => { const columnSettings = fromJS({ shows: { reblog: true } }); - const status = normalizeStatus(fromJS({ reblog: {} })); + const status = normalizeStatus({ reblog: {} }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('reply: returns true when `shows.reply == false`', () => { const columnSettings = fromJS({ shows: { reply: false } }); - const status = normalizeStatus(fromJS({ in_reply_to_id: '1234' })); + const status = normalizeStatus({ in_reply_to_id: '1234' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('reply: returns false when `shows.reply == true`', () => { const columnSettings = fromJS({ shows: { reply: true } }); - const status = normalizeStatus(fromJS({ in_reply_to_id: '1234' })); + const status = normalizeStatus({ in_reply_to_id: '1234' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('direct: returns true when `shows.direct == false`', () => { const columnSettings = fromJS({ shows: { direct: false } }); - const status = normalizeStatus(fromJS({ visibility: 'direct' })); + const status = normalizeStatus({ visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('direct: returns false when `shows.direct == true`', () => { const columnSettings = fromJS({ shows: { direct: true } }); - const status = normalizeStatus(fromJS({ visibility: 'direct' })); + const status = normalizeStatus({ visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('direct: returns false for a public post when `shows.direct == false`', () => { const columnSettings = fromJS({ shows: { direct: false } }); - const status = normalizeStatus(fromJS({ visibility: 'public' })); + const status = normalizeStatus({ visibility: 'public' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: false, reply: false, direct: false } }); - const status = normalizeStatus(fromJS({ reblog: null, in_reply_to_id: null, visibility: 'direct' })); + const status = normalizeStatus({ reblog: null, in_reply_to_id: null, visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: false, reply: true, direct: false } }); - const status = normalizeStatus(fromJS({ reblog: null, in_reply_to_id: '1234', visibility: 'public' })); + const status = normalizeStatus({ reblog: null, in_reply_to_id: '1234', visibility: 'public' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: true, reply: false, direct: true } }); - const status = normalizeStatus(fromJS({ reblog: {}, in_reply_to_id: '1234', visibility: 'direct' })); + const status = normalizeStatus({ reblog: {}, in_reply_to_id: '1234', visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); }); From cc4c8a0188098fa891cc666f9598aac2a12eddeb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 11:33:22 -0500 Subject: [PATCH 17/41] Fix utils/tailwind test --- app/soapbox/utils/__tests__/tailwind.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/utils/__tests__/tailwind.test.ts b/app/soapbox/utils/__tests__/tailwind.test.ts index 822408393..3c359ecc0 100644 --- a/app/soapbox/utils/__tests__/tailwind.test.ts +++ b/app/soapbox/utils/__tests__/tailwind.test.ts @@ -4,7 +4,7 @@ import { toTailwind, fromLegacyColors, expandPalette } from '../tailwind'; describe('toTailwind()', () => { it('handles empty Soapbox config', () => { - const soapboxConfig = ImmutableMap(); + const soapboxConfig = ImmutableMap(); const result = toTailwind(soapboxConfig); const expected = ImmutableMap({ colors: ImmutableMap() }); expect(result).toEqual(expected); From 8685b64f9d7a8649338cb03a006df279aeaf6328 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 11:36:01 -0500 Subject: [PATCH 18/41] utils/numbers: fix test --- app/soapbox/utils/__tests__/numbers.test.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/soapbox/utils/__tests__/numbers.test.tsx b/app/soapbox/utils/__tests__/numbers.test.tsx index 63c45b26b..a0edf41fe 100644 --- a/app/soapbox/utils/__tests__/numbers.test.tsx +++ b/app/soapbox/utils/__tests__/numbers.test.tsx @@ -10,28 +10,28 @@ test('isIntegerId()', () => { expect(isIntegerId('-1764036199')).toBe(true); expect(isIntegerId('106801667066418367')).toBe(true); expect(isIntegerId('9v5bmRalQvjOy0ECcC')).toBe(false); - expect(isIntegerId(null)).toBe(false); - expect(isIntegerId(undefined)).toBe(false); + expect(isIntegerId(null as any)).toBe(false); + expect(isIntegerId(undefined as any)).toBe(false); }); describe('shortNumberFormat', () => { test('handles non-numbers', () => { - render(
{shortNumberFormat('not-number')}
, null, null); + render(
{shortNumberFormat('not-number')}
, undefined, null); expect(screen.getByTestId('num')).toHaveTextContent('•'); }); test('formats numbers under 1,000', () => { - render(
{shortNumberFormat(555)}
, null, null); + render(
{shortNumberFormat(555)}
, undefined, null); expect(screen.getByTestId('num')).toHaveTextContent('555'); }); test('formats numbers under 1,000,000', () => { - render(
{shortNumberFormat(5555)}
, null, null); + render(
{shortNumberFormat(5555)}
, undefined, null); expect(screen.getByTestId('num')).toHaveTextContent('5.6K'); }); test('formats numbers over 1,000,000', () => { - render(
{shortNumberFormat(5555555)}
, null, null); + render(
{shortNumberFormat(5555555)}
, undefined, null); expect(screen.getByTestId('num')).toHaveTextContent('5.6M'); }); }); From 073dd4f37ae9d44c995f24e53af59d2779b1dcca Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 11:53:55 -0500 Subject: [PATCH 19/41] Fix types in reducer tests --- .../reducers/__tests__/accounts.test.ts | 2 +- app/soapbox/reducers/__tests__/alerts.test.ts | 2 +- .../reducers/__tests__/carousels.test.ts | 4 +- .../reducers/__tests__/compose.test.ts | 6 +-- .../reducers/__tests__/custom_emojis.test.ts | 2 +- .../reducers/__tests__/group_editor.test.ts | 2 +- .../reducers/__tests__/group_lists.test.ts | 2 +- .../__tests__/group_relationships.test.ts | 2 +- app/soapbox/reducers/__tests__/groups.test.ts | 2 +- app/soapbox/reducers/__tests__/index.test.ts | 2 +- app/soapbox/reducers/__tests__/meta.test.ts | 2 +- app/soapbox/reducers/__tests__/mutes.test.ts | 2 +- .../reducers/__tests__/onboarding.test.ts | 6 +-- app/soapbox/reducers/__tests__/rules.test.ts | 4 +- .../reducers/__tests__/settings.test.ts | 2 +- .../reducers/__tests__/statuses.test.ts | 43 ++++++++++++------- app/soapbox/reducers/__tests__/trends.test.ts | 2 +- 17 files changed, 49 insertions(+), 38 deletions(-) diff --git a/app/soapbox/reducers/__tests__/accounts.test.ts b/app/soapbox/reducers/__tests__/accounts.test.ts index 647bb676a..02baa6d92 100644 --- a/app/soapbox/reducers/__tests__/accounts.test.ts +++ b/app/soapbox/reducers/__tests__/accounts.test.ts @@ -23,7 +23,7 @@ describe('accounts reducer', () => { const action = { type: ACCOUNT_IMPORT, account }; const result = reducer(undefined, action).get('106801667066418367'); - expect(result.moved).toBe('107945464165013501'); + expect(result?.moved).toBe('107945464165013501'); }); }); }); diff --git a/app/soapbox/reducers/__tests__/alerts.test.ts b/app/soapbox/reducers/__tests__/alerts.test.ts index 306ab2fcd..441cf2d8d 100644 --- a/app/soapbox/reducers/__tests__/alerts.test.ts +++ b/app/soapbox/reducers/__tests__/alerts.test.ts @@ -11,7 +11,7 @@ import reducer from '../alerts'; describe('alerts reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableList()); + expect(reducer(undefined, {} as any)).toEqual(ImmutableList()); }); describe('ALERT_SHOW', () => { diff --git a/app/soapbox/reducers/__tests__/carousels.test.ts b/app/soapbox/reducers/__tests__/carousels.test.ts index 45e87c8ff..2745b078d 100644 --- a/app/soapbox/reducers/__tests__/carousels.test.ts +++ b/app/soapbox/reducers/__tests__/carousels.test.ts @@ -19,7 +19,7 @@ describe('carousels reducer', () => { describe('CAROUSEL_AVATAR_REQUEST', () => { it('sets "isLoading" to "true"', () => { - const initialState = { isLoading: false, avatars: [] }; + const initialState = { isLoading: false, avatars: [], error: false }; const action = { type: CAROUSEL_AVATAR_REQUEST }; expect(reducer(initialState, action).isLoading).toEqual(true); }); @@ -39,7 +39,7 @@ describe('carousels reducer', () => { describe('CAROUSEL_AVATAR_FAIL', () => { it('sets "isLoading" to "true"', () => { - const initialState = { isLoading: true, avatars: [] }; + const initialState = { isLoading: true, avatars: [], error: false }; const action = { type: CAROUSEL_AVATAR_FAIL }; const result = reducer(initialState, action); diff --git a/app/soapbox/reducers/__tests__/compose.test.ts b/app/soapbox/reducers/__tests__/compose.test.ts index a1d40fe7c..bb6906c6d 100644 --- a/app/soapbox/reducers/__tests__/compose.test.ts +++ b/app/soapbox/reducers/__tests__/compose.test.ts @@ -200,7 +200,7 @@ describe('compose reducer', () => { }); it('should handle COMPOSE_SENSITIVITY_CHANGE on Mark Sensitive click, don\'t toggle if spoiler active', () => { - const state = ReducerRecord({ spoiler: true, sensitive: true, idempotencyKey: null }); + const state = ReducerRecord({ spoiler: true, sensitive: true, idempotencyKey: '' }); const action = { type: actions.COMPOSE_SENSITIVITY_CHANGE, }; @@ -297,12 +297,12 @@ describe('compose reducer', () => { }); it('should handle COMPOSE_SUBMIT_SUCCESS', () => { - const state = ReducerRecord({ default_privacy: null, privacy: 'public' }); + const state = ReducerRecord({ default_privacy: 'public', privacy: 'private' }); const action = { type: actions.COMPOSE_SUBMIT_SUCCESS, }; expect(reducer(state, action).toJS()).toMatchObject({ - privacy: null, + privacy: 'public', }); }); diff --git a/app/soapbox/reducers/__tests__/custom_emojis.test.ts b/app/soapbox/reducers/__tests__/custom_emojis.test.ts index 43fec78ec..e2991d5fb 100644 --- a/app/soapbox/reducers/__tests__/custom_emojis.test.ts +++ b/app/soapbox/reducers/__tests__/custom_emojis.test.ts @@ -4,6 +4,6 @@ import reducer from '../custom_emojis'; describe('custom_emojis reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableList()); + expect(reducer(undefined, {} as any)).toEqual(ImmutableList()); }); }); diff --git a/app/soapbox/reducers/__tests__/group_editor.test.ts b/app/soapbox/reducers/__tests__/group_editor.test.ts index 73c9e1b6a..516b6df43 100644 --- a/app/soapbox/reducers/__tests__/group_editor.test.ts +++ b/app/soapbox/reducers/__tests__/group_editor.test.ts @@ -4,7 +4,7 @@ import reducer from '../group_editor'; describe('group_editor reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap({ + expect(reducer(undefined, {} as any)).toEqual(ImmutableMap({ groupId: null, isSubmitting: false, isChanged: false, diff --git a/app/soapbox/reducers/__tests__/group_lists.test.ts b/app/soapbox/reducers/__tests__/group_lists.test.ts index fa5c21eea..46527f682 100644 --- a/app/soapbox/reducers/__tests__/group_lists.test.ts +++ b/app/soapbox/reducers/__tests__/group_lists.test.ts @@ -4,7 +4,7 @@ import reducer from '../group_lists'; describe('group_lists reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap({ + expect(reducer(undefined, {} as any)).toEqual(ImmutableMap({ featured: ImmutableList(), member: ImmutableList(), admin: ImmutableList(), diff --git a/app/soapbox/reducers/__tests__/group_relationships.test.ts b/app/soapbox/reducers/__tests__/group_relationships.test.ts index 17cabf3c2..31e3e354f 100644 --- a/app/soapbox/reducers/__tests__/group_relationships.test.ts +++ b/app/soapbox/reducers/__tests__/group_relationships.test.ts @@ -4,6 +4,6 @@ import reducer from '../group_relationships'; describe('group_relationships reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap()); + expect(reducer(undefined, {} as any)).toEqual(ImmutableMap()); }); }); diff --git a/app/soapbox/reducers/__tests__/groups.test.ts b/app/soapbox/reducers/__tests__/groups.test.ts index 94a1a6ffe..05b88402f 100644 --- a/app/soapbox/reducers/__tests__/groups.test.ts +++ b/app/soapbox/reducers/__tests__/groups.test.ts @@ -4,6 +4,6 @@ import reducer from '../groups'; describe('groups reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap()); + expect(reducer(undefined, {} as any)).toEqual(ImmutableMap()); }); }); diff --git a/app/soapbox/reducers/__tests__/index.test.ts b/app/soapbox/reducers/__tests__/index.test.ts index 15572f337..e5674e91b 100644 --- a/app/soapbox/reducers/__tests__/index.test.ts +++ b/app/soapbox/reducers/__tests__/index.test.ts @@ -4,7 +4,7 @@ import reducer from '..'; describe('root reducer', () => { it('should return the initial state', () => { - const result = reducer(undefined, {}); + const result = reducer(undefined, {} as any); expect(ImmutableRecord.isRecord(result)).toBe(true); expect(result.accounts.get('')).toBe(undefined); expect(result.instance.version).toEqual('0.0.0'); diff --git a/app/soapbox/reducers/__tests__/meta.test.ts b/app/soapbox/reducers/__tests__/meta.test.ts index d318b4a30..272116391 100644 --- a/app/soapbox/reducers/__tests__/meta.test.ts +++ b/app/soapbox/reducers/__tests__/meta.test.ts @@ -6,7 +6,7 @@ import reducer from '../meta'; describe('meta reducer', () => { it('should return the initial state', () => { - const result = reducer(undefined, {}); + const result = reducer(undefined, {} as any); expect(ImmutableRecord.isRecord(result)).toBe(true); expect(result.instance_fetch_failed).toBe(false); expect(result.swUpdating).toBe(false); diff --git a/app/soapbox/reducers/__tests__/mutes.test.ts b/app/soapbox/reducers/__tests__/mutes.test.ts index 411db10c6..66a866958 100644 --- a/app/soapbox/reducers/__tests__/mutes.test.ts +++ b/app/soapbox/reducers/__tests__/mutes.test.ts @@ -9,7 +9,7 @@ import reducer from '../mutes'; describe('mutes reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {}).toJS()).toEqual({ + expect(reducer(undefined, {} as any).toJS()).toEqual({ new: { isSubmitting: false, accountId: null, diff --git a/app/soapbox/reducers/__tests__/onboarding.test.ts b/app/soapbox/reducers/__tests__/onboarding.test.ts index 95ecdf755..69b4b1b21 100644 --- a/app/soapbox/reducers/__tests__/onboarding.test.ts +++ b/app/soapbox/reducers/__tests__/onboarding.test.ts @@ -4,7 +4,7 @@ import reducer from '../onboarding'; describe('onboarding reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual({ + expect(reducer(undefined, {} as any)).toEqual({ needsOnboarding: false, }); }); @@ -12,7 +12,7 @@ describe('onboarding reducer', () => { describe('ONBOARDING_START', () => { it('sets "needsOnboarding" to "true"', () => { const initialState = { needsOnboarding: false }; - const action = { type: ONBOARDING_START }; + const action = { type: ONBOARDING_START } as any; expect(reducer(initialState, action).needsOnboarding).toEqual(true); }); }); @@ -20,7 +20,7 @@ describe('onboarding reducer', () => { describe('ONBOARDING_END', () => { it('sets "needsOnboarding" to "false"', () => { const initialState = { needsOnboarding: true }; - const action = { type: ONBOARDING_END }; + const action = { type: ONBOARDING_END } as any; expect(reducer(initialState, action).needsOnboarding).toEqual(false); }); }); diff --git a/app/soapbox/reducers/__tests__/rules.test.ts b/app/soapbox/reducers/__tests__/rules.test.ts index 151516a83..25f8575ed 100644 --- a/app/soapbox/reducers/__tests__/rules.test.ts +++ b/app/soapbox/reducers/__tests__/rules.test.ts @@ -15,14 +15,14 @@ describe('rules reducer', () => { describe('RULES_FETCH_REQUEST', () => { it('sets "needsOnboarding" to "true"', () => { - const action = { type: RULES_FETCH_REQUEST }; + const action = { type: RULES_FETCH_REQUEST } as any; expect(reducer(initialState, action).isLoading).toEqual(true); }); }); describe('ONBOARDING_END', () => { it('sets "needsOnboarding" to "false"', () => { - const action = { type: RULES_FETCH_SUCCESS, payload: [{ id: '123' }] }; + const action = { type: RULES_FETCH_SUCCESS, payload: [{ id: '123' }] } as any; const result = reducer(initialState, action); expect(result.isLoading).toEqual(false); expect(result.items[0].id).toEqual('123'); diff --git a/app/soapbox/reducers/__tests__/settings.test.ts b/app/soapbox/reducers/__tests__/settings.test.ts index b4574d55c..5b065fd4c 100644 --- a/app/soapbox/reducers/__tests__/settings.test.ts +++ b/app/soapbox/reducers/__tests__/settings.test.ts @@ -4,7 +4,7 @@ import reducer from '../settings'; describe('settings reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap({ + expect(reducer(undefined, {} as any)).toEqual(ImmutableMap({ saved: true, })); }); diff --git a/app/soapbox/reducers/__tests__/statuses.test.ts b/app/soapbox/reducers/__tests__/statuses.test.ts index fb245062a..06d82c871 100644 --- a/app/soapbox/reducers/__tests__/statuses.test.ts +++ b/app/soapbox/reducers/__tests__/statuses.test.ts @@ -1,7 +1,6 @@ import { Map as ImmutableMap, Record as ImmutableRecord, - fromJS, } from 'immutable'; import { STATUS_IMPORT } from 'soapbox/actions/importer'; @@ -11,12 +10,13 @@ import { STATUS_DELETE_REQUEST, STATUS_DELETE_FAIL, } from 'soapbox/actions/statuses'; +import { normalizeStatus } from 'soapbox/normalizers'; -import reducer from '../statuses'; +import reducer, { ReducerStatus } from '../statuses'; describe('statuses reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {})).toEqual(ImmutableMap()); + expect(reducer(undefined, {} as any)).toEqual(ImmutableMap()); }); describe('STATUS_IMPORT', () => { @@ -35,7 +35,7 @@ describe('statuses reducer', () => { const expected = ['NEETzsche', 'alex', 'Lumeinshin', 'sneeden']; const result = reducer(undefined, action) - .getIn(['AFChectaqZjmOVkXZ2', 'mentions']) + .get('AFChectaqZjmOVkXZ2')?.mentions .map(mention => mention.get('username')) .toJS(); @@ -84,19 +84,18 @@ describe('statuses reducer', () => { remote_url: null, }]; - expect(state.getIn(['017eeb0e-e5e7-98fe-6b2b-ad02349251fb', 'media_attachments']).toJS()).toMatchObject(expected); + expect(state.get('017eeb0e-e5e7-98fe-6b2b-ad02349251fb')?.media_attachments.toJS()).toMatchObject(expected); }); it('fixes Pleroma attachments', () => { const status = require('soapbox/__fixtures__/pleroma-status-with-attachments.json'); const action = { type: STATUS_IMPORT, status }; const state = reducer(undefined, action); - const result = state.get('AGNkA21auFR5lnEAHw').media_attachments; + const result = state.get('AGNkA21auFR5lnEAHw')?.media_attachments; - expect(result.size).toBe(4); - expect(result.get(0).text_url).toBe(undefined); - expect(result.get(1).meta).toEqual(ImmutableMap()); - expect(result.getIn([1, 'pleroma', 'mime_type'])).toBe('application/x-nes-rom'); + expect(result?.size).toBe(4); + expect(result?.get(1)?.meta).toEqual(ImmutableMap()); + expect(result?.getIn([1, 'pleroma', 'mime_type'])).toBe('application/x-nes-rom'); }); it('hides CWs', () => { @@ -160,7 +159,9 @@ Promoting free speech, even for people and ideas you dislike`; describe('STATUS_CREATE_REQUEST', () => { it('increments the replies_count of its parent', () => { - const state = fromJS({ '123': { replies_count: 4 } }); + const state = ImmutableMap({ + '123': normalizeStatus({ replies_count: 4 }) as ReducerStatus, + }); const action = { type: STATUS_CREATE_REQUEST, @@ -174,7 +175,9 @@ Promoting free speech, even for people and ideas you dislike`; describe('STATUS_CREATE_FAIL', () => { it('decrements the replies_count of its parent', () => { - const state = fromJS({ '123': { replies_count: 5 } }); + const state = ImmutableMap({ + '123': normalizeStatus({ replies_count: 5 }) as ReducerStatus, + }); const action = { type: STATUS_CREATE_FAIL, @@ -188,7 +191,9 @@ Promoting free speech, even for people and ideas you dislike`; describe('STATUS_DELETE_REQUEST', () => { it('decrements the replies_count of its parent', () => { - const state = fromJS({ '123': { replies_count: 4 } }); + const state = ImmutableMap({ + '123': normalizeStatus({ replies_count: 4 }) as ReducerStatus, + }); const action = { type: STATUS_DELETE_REQUEST, @@ -200,7 +205,9 @@ Promoting free speech, even for people and ideas you dislike`; }); it('gracefully does nothing if no parent', () => { - const state = fromJS({ '123': { replies_count: 4 } }); + const state = ImmutableMap({ + '123': normalizeStatus({ replies_count: 4 }) as ReducerStatus, + }); const action = { type: STATUS_DELETE_REQUEST, @@ -214,7 +221,9 @@ Promoting free speech, even for people and ideas you dislike`; describe('STATUS_DELETE_FAIL', () => { it('decrements the replies_count of its parent', () => { - const state = fromJS({ '123': { replies_count: 4 } }); + const state = ImmutableMap({ + '123': normalizeStatus({ replies_count: 4 }) as ReducerStatus, + }); const action = { type: STATUS_DELETE_FAIL, @@ -226,7 +235,9 @@ Promoting free speech, even for people and ideas you dislike`; }); it('gracefully does nothing if no parent', () => { - const state = fromJS({ '123': { replies_count: 4 } }); + const state = ImmutableMap({ + '123': normalizeStatus({ replies_count: 4 }) as ReducerStatus, + }); const action = { type: STATUS_DELETE_FAIL, diff --git a/app/soapbox/reducers/__tests__/trends.test.ts b/app/soapbox/reducers/__tests__/trends.test.ts index 644654038..25330d770 100644 --- a/app/soapbox/reducers/__tests__/trends.test.ts +++ b/app/soapbox/reducers/__tests__/trends.test.ts @@ -2,7 +2,7 @@ import reducer from '../trends'; describe('trends reducer', () => { it('should return the initial state', () => { - expect(reducer(undefined, {}).toJS()).toEqual({ + expect(reducer(undefined, {} as any).toJS()).toEqual({ items: [], isLoading: false, }); From c66f5c040ff0f4d1540789c970cf1422dc7392f7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 11:57:32 -0500 Subject: [PATCH 20/41] Fix utils tests types --- app/soapbox/utils/__tests__/accounts.test.ts | 4 +++- app/soapbox/utils/__tests__/status.test.ts | 11 +++++---- app/soapbox/utils/__tests__/timelines.test.ts | 24 ++++++++++--------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/soapbox/utils/__tests__/accounts.test.ts b/app/soapbox/utils/__tests__/accounts.test.ts index cafedce35..878d278dd 100644 --- a/app/soapbox/utils/__tests__/accounts.test.ts +++ b/app/soapbox/utils/__tests__/accounts.test.ts @@ -4,11 +4,13 @@ import { getDomain, } from '../accounts'; +import type { ReducerAccount } from 'soapbox/reducers/accounts'; + describe('getDomain', () => { const account = AccountRecord({ acct: 'alice', url: 'https://party.com/users/alice', - }); + }) as ReducerAccount; it('returns the domain', () => { expect(getDomain(account)).toEqual('party.com'); }); diff --git a/app/soapbox/utils/__tests__/status.test.ts b/app/soapbox/utils/__tests__/status.test.ts index 4556382de..2bc803ee5 100644 --- a/app/soapbox/utils/__tests__/status.test.ts +++ b/app/soapbox/utils/__tests__/status.test.ts @@ -1,4 +1,3 @@ -import { fromJS } from 'immutable'; import { normalizeStatus } from 'soapbox/normalizers/status'; @@ -7,9 +6,11 @@ import { defaultMediaVisibility, } from '../status'; +import type { ReducerStatus } from 'soapbox/reducers/statuses'; + describe('hasIntegerMediaIds()', () => { it('returns true for a Pleroma deleted status', () => { - const status = normalizeStatus(fromJS(require('soapbox/__fixtures__/pleroma-status-deleted.json'))); + const status = normalizeStatus(require('soapbox/__fixtures__/pleroma-status-deleted.json')) as ReducerStatus; expect(hasIntegerMediaIds(status)).toBe(true); }); }); @@ -20,17 +21,17 @@ describe('defaultMediaVisibility()', () => { }); it('hides sensitive media by default', () => { - const status = normalizeStatus({ sensitive: true }); + const status = normalizeStatus({ sensitive: true }) as ReducerStatus; expect(defaultMediaVisibility(status, 'default')).toBe(false); }); it('hides media when displayMedia is hide_all', () => { - const status = normalizeStatus({}); + const status = normalizeStatus({}) as ReducerStatus; expect(defaultMediaVisibility(status, 'hide_all')).toBe(false); }); it('shows sensitive media when displayMedia is show_all', () => { - const status = normalizeStatus({ sensitive: true }); + const status = normalizeStatus({ sensitive: true }) as ReducerStatus; expect(defaultMediaVisibility(status, 'show_all')).toBe(true); }); }); diff --git a/app/soapbox/utils/__tests__/timelines.test.ts b/app/soapbox/utils/__tests__/timelines.test.ts index 9be55575d..852a76ef6 100644 --- a/app/soapbox/utils/__tests__/timelines.test.ts +++ b/app/soapbox/utils/__tests__/timelines.test.ts @@ -4,70 +4,72 @@ import { normalizeStatus } from 'soapbox/normalizers/status'; import { shouldFilter } from '../timelines'; +import type { ReducerStatus } from 'soapbox/reducers/statuses'; + describe('shouldFilter', () => { it('returns false under normal circumstances', () => { const columnSettings = fromJS({}); - const status = normalizeStatus({}); + const status = normalizeStatus({}) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(false); }); it('reblog: returns true when `shows.reblog == false`', () => { const columnSettings = fromJS({ shows: { reblog: false } }); - const status = normalizeStatus({ reblog: {} }); + const status = normalizeStatus({ reblog: {} }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(true); }); it('reblog: returns false when `shows.reblog == true`', () => { const columnSettings = fromJS({ shows: { reblog: true } }); - const status = normalizeStatus({ reblog: {} }); + const status = normalizeStatus({ reblog: {} }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(false); }); it('reply: returns true when `shows.reply == false`', () => { const columnSettings = fromJS({ shows: { reply: false } }); - const status = normalizeStatus({ in_reply_to_id: '1234' }); + const status = normalizeStatus({ in_reply_to_id: '1234' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(true); }); it('reply: returns false when `shows.reply == true`', () => { const columnSettings = fromJS({ shows: { reply: true } }); - const status = normalizeStatus({ in_reply_to_id: '1234' }); + const status = normalizeStatus({ in_reply_to_id: '1234' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(false); }); it('direct: returns true when `shows.direct == false`', () => { const columnSettings = fromJS({ shows: { direct: false } }); - const status = normalizeStatus({ visibility: 'direct' }); + const status = normalizeStatus({ visibility: 'direct' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(true); }); it('direct: returns false when `shows.direct == true`', () => { const columnSettings = fromJS({ shows: { direct: true } }); - const status = normalizeStatus({ visibility: 'direct' }); + const status = normalizeStatus({ visibility: 'direct' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(false); }); it('direct: returns false for a public post when `shows.direct == false`', () => { const columnSettings = fromJS({ shows: { direct: false } }); - const status = normalizeStatus({ visibility: 'public' }); + const status = normalizeStatus({ visibility: 'public' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(false); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: false, reply: false, direct: false } }); - const status = normalizeStatus({ reblog: null, in_reply_to_id: null, visibility: 'direct' }); + const status = normalizeStatus({ reblog: null, in_reply_to_id: null, visibility: 'direct' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(true); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: false, reply: true, direct: false } }); - const status = normalizeStatus({ reblog: null, in_reply_to_id: '1234', visibility: 'public' }); + const status = normalizeStatus({ reblog: null, in_reply_to_id: '1234', visibility: 'public' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(false); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: true, reply: false, direct: true } }); - const status = normalizeStatus({ reblog: {}, in_reply_to_id: '1234', visibility: 'direct' }); + const status = normalizeStatus({ reblog: {}, in_reply_to_id: '1234', visibility: 'direct' }) as ReducerStatus; expect(shouldFilter(status, columnSettings)).toBe(true); }); }); From 9d85a9f8638d1abf518fa3724f3c5128aed5f268 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 12:08:51 -0500 Subject: [PATCH 21/41] Fix normalizer test types --- .../normalizers/__tests__/account.test.ts | 6 +-- .../normalizers/__tests__/poll.test.ts | 4 +- .../normalizers/__tests__/status.test.ts | 39 +++++++++++-------- .../soapbox/__tests__/soapbox_config-test.ts | 2 +- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/app/soapbox/normalizers/__tests__/account.test.ts b/app/soapbox/normalizers/__tests__/account.test.ts index 761b2fac5..b5f4f5a75 100644 --- a/app/soapbox/normalizers/__tests__/account.test.ts +++ b/app/soapbox/normalizers/__tests__/account.test.ts @@ -154,9 +154,9 @@ describe('normalizeAccount()', () => { const result = normalizeAccount(account); const field = result.fields.get(1); - expect(field.name_emojified).toBe('Soapbox :ablobcatrainbow:'); - expect(field.value_emojified).toBe('https://soapbox.pub :soapbox:'); - expect(field.value_plain).toBe('https://soapbox.pub :soapbox:'); + expect(field?.name_emojified).toBe('Soapbox :ablobcatrainbow:'); + expect(field?.value_emojified).toBe('https://soapbox.pub :soapbox:'); + expect(field?.value_plain).toBe('https://soapbox.pub :soapbox:'); }); it('adds default avatar and banner to GoToSocial account', () => { diff --git a/app/soapbox/normalizers/__tests__/poll.test.ts b/app/soapbox/normalizers/__tests__/poll.test.ts index 31691f484..8acf2ece4 100644 --- a/app/soapbox/normalizers/__tests__/poll.test.ts +++ b/app/soapbox/normalizers/__tests__/poll.test.ts @@ -38,11 +38,11 @@ describe('normalizePoll()', () => { const result = normalizePoll(poll); // Emojifies poll options - expect(result.options.get(1).title_emojified) + expect(result.options.get(1)?.title_emojified) .toEqual('Custom emoji :gleason_excited: '); // Parses emojis as Immutable.Record's expect(ImmutableRecord.isRecord(result.emojis.get(0))).toBe(true); - expect(result.emojis.get(1).shortcode).toEqual('soapbox'); + expect(result.emojis.get(1)?.shortcode).toEqual('soapbox'); }); }); diff --git a/app/soapbox/normalizers/__tests__/status.test.ts b/app/soapbox/normalizers/__tests__/status.test.ts index ac9358f51..43336d00f 100644 --- a/app/soapbox/normalizers/__tests__/status.test.ts +++ b/app/soapbox/normalizers/__tests__/status.test.ts @@ -2,6 +2,8 @@ import { Record as ImmutableRecord, fromJS } from 'immutable'; import { normalizeStatus } from '../status'; +import type { Poll, Card } from 'soapbox/types/entities'; + describe('normalizeStatus()', () => { it('adds base fields', () => { const status = {}; @@ -42,8 +44,8 @@ describe('normalizeStatus()', () => { const result = normalizeStatus(status).mentions; expect(result.size).toBe(1); - expect(result.get(0).toJS()).toMatchObject(expected); - expect(result.get(0).id).toEqual('106801667066418367'); + expect(result.get(0)?.toJS()).toMatchObject(expected); + expect(result.get(0)?.id).toEqual('106801667066418367'); expect(ImmutableRecord.isRecord(result.get(0))).toBe(true); }); @@ -101,8 +103,7 @@ describe('normalizeStatus()', () => { const result = normalizeStatus(status).media_attachments; expect(result.size).toBe(4); - expect(result.get(0).text_url).toBe(undefined); - expect(result.get(1).meta).toEqual(fromJS({})); + expect(result.get(1)?.meta).toEqual(fromJS({})); expect(result.getIn([1, 'pleroma', 'mime_type'])).toBe('application/x-nes-rom'); expect(ImmutableRecord.isRecord(result.get(3))).toBe(true); }); @@ -147,6 +148,7 @@ describe('normalizeStatus()', () => { it('normalizes poll and poll options', () => { const status = { poll: { options: [{ title: 'Apples' }] } }; const result = normalizeStatus(status); + const poll = result.poll as Poll; const expected = { options: [{ title: 'Apples', votes_count: 0 }], @@ -159,46 +161,49 @@ describe('normalizeStatus()', () => { voted: false, }; - expect(ImmutableRecord.isRecord(result.poll)).toBe(true); - expect(ImmutableRecord.isRecord(result.poll.options.get(0))).toBe(true); - expect(result.poll.toJS()).toMatchObject(expected); - expect(result.poll.expires_at instanceof Date).toBe(true); + expect(ImmutableRecord.isRecord(poll)).toBe(true); + expect(ImmutableRecord.isRecord(poll.options.get(0))).toBe(true); + expect(poll.toJS()).toMatchObject(expected); + expect(poll.expires_at instanceof Date).toBe(true); }); it('normalizes a Pleroma logged-out poll', () => { const status = require('soapbox/__fixtures__/pleroma-status-with-poll.json'); const result = normalizeStatus(status); + const poll = result.poll as Poll; // Adds logged-in fields - expect(result.poll.voted).toBe(false); - expect(result.poll.own_votes).toBe(null); + expect(poll.voted).toBe(false); + expect(poll.own_votes).toBe(null); }); it('normalizes poll with emojis', () => { const status = require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'); const result = normalizeStatus(status); + const poll = result.poll as Poll; // Emojifies poll options - expect(result.poll.options.get(1).title_emojified) + expect(poll.options.get(1)?.title_emojified) .toEqual('Custom emoji :gleason_excited: '); // Parses emojis as Immutable.Record's - expect(ImmutableRecord.isRecord(result.poll.emojis.get(0))).toBe(true); - expect(result.poll.emojis.get(1).shortcode).toEqual('soapbox'); + expect(ImmutableRecord.isRecord(poll.emojis.get(0))).toBe(true); + expect(poll.emojis.get(1)?.shortcode).toEqual('soapbox'); }); it('normalizes a card', () => { const status = require('soapbox/__fixtures__/status-with-card.json'); const result = normalizeStatus(status); + const card = result.card as Card; - expect(ImmutableRecord.isRecord(result.card)).toBe(true); - expect(result.card.type).toEqual('link'); - expect(result.card.provider_url).toEqual('https://soapbox.pub'); + expect(ImmutableRecord.isRecord(card)).toBe(true); + expect(card.type).toEqual('link'); + expect(card.provider_url).toEqual('https://soapbox.pub'); }); it('preserves Truth Social external_video_id', () => { const status = require('soapbox/__fixtures__/truthsocial-status-with-external-video.json'); const result = normalizeStatus(status); - expect(result.media_attachments.get(0).external_video_id).toBe('vwfnq9'); + expect(result.media_attachments.get(0)?.external_video_id).toBe('vwfnq9'); }); }); diff --git a/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.ts b/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.ts index 65c4291f8..e48198ecc 100644 --- a/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.ts +++ b/app/soapbox/normalizers/soapbox/__tests__/soapbox_config-test.ts @@ -32,6 +32,6 @@ describe('normalizeSoapboxConfig()', () => { const result = normalizeSoapboxConfig(require('soapbox/__fixtures__/spinster-soapbox.json')); expect(ImmutableRecord.isRecord(result.promoPanel)).toBe(true); expect(ImmutableRecord.isRecord(result.promoPanel.items.get(0))).toBe(true); - expect(result.promoPanel.items.get(2).icon).toBe('question-circle'); + expect(result.promoPanel.items.get(2)?.icon).toBe('question-circle'); }); }); From 882e4b2fda51f045b192f1f89eea0be1f4c33cfb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 12:10:21 -0500 Subject: [PATCH 22/41] Fix hooks tests types --- app/soapbox/hooks/__tests__/useDimensions.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/hooks/__tests__/useDimensions.test.ts b/app/soapbox/hooks/__tests__/useDimensions.test.ts index c437f1394..0adee42e4 100644 --- a/app/soapbox/hooks/__tests__/useDimensions.test.ts +++ b/app/soapbox/hooks/__tests__/useDimensions.test.ts @@ -6,7 +6,7 @@ let listener: ((rect: any) => void) | undefined = undefined; (window as any).ResizeObserver = class ResizeObserver { - constructor(ls) { + constructor(ls: any) { listener = ls; } @@ -63,7 +63,7 @@ describe('useDimensions()', () => { disconnect() { disconnect(); } - + }; const { result, unmount } = renderHook(() => useDimensions()); From 354395501e54bd67a3280202391dafe2ac9fc31d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 12:11:39 -0500 Subject: [PATCH 23/41] Fix verification tests types --- app/soapbox/features/verification/__tests__/index.test.tsx | 4 ++-- .../verification/steps/__tests__/age-verification.test.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/soapbox/features/verification/__tests__/index.test.tsx b/app/soapbox/features/verification/__tests__/index.test.tsx index 32182f515..e24aee442 100644 --- a/app/soapbox/features/verification/__tests__/index.test.tsx +++ b/app/soapbox/features/verification/__tests__/index.test.tsx @@ -14,7 +14,7 @@ const TestableComponent = () => ( ); -const renderComponent = (store) => render( +const renderComponent = (store: any) => render( , {}, store, @@ -22,7 +22,7 @@ const renderComponent = (store) => render( ); describe('', () => { - let store; + let store: any; beforeEach(() => { store = { diff --git a/app/soapbox/features/verification/steps/__tests__/age-verification.test.tsx b/app/soapbox/features/verification/steps/__tests__/age-verification.test.tsx index 7dd835313..f310a0741 100644 --- a/app/soapbox/features/verification/steps/__tests__/age-verification.test.tsx +++ b/app/soapbox/features/verification/steps/__tests__/age-verification.test.tsx @@ -8,7 +8,7 @@ import { fireEvent, render, screen } from 'soapbox/jest/test-helpers'; import AgeVerification from '../age-verification'; describe('', () => { - let store; + let store: any; beforeEach(() => { store = { From bdf00bb692d86f9b7a962c383f12bc6d303e7dea Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 12:16:14 -0500 Subject: [PATCH 24/41] Fix types in various component tests --- app/soapbox/features/ui/__tests__/index.test.tsx | 4 ++-- .../ui/components/__tests__/compose-button.test.tsx | 3 +-- .../ui/components/__tests__/cta-banner.test.tsx | 4 ++-- .../ui/components/__tests__/subscribe-button.test.tsx | 10 ++++++---- .../ui/components/__tests__/trends-panel.test.tsx | 8 ++++---- .../components/__tests__/who-to-follow-panel.test.tsx | 8 ++++---- .../report-modal/__tests__/report-modal.test.tsx | 2 +- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/app/soapbox/features/ui/__tests__/index.test.tsx b/app/soapbox/features/ui/__tests__/index.test.tsx index 5a3164996..c28c21589 100644 --- a/app/soapbox/features/ui/__tests__/index.test.tsx +++ b/app/soapbox/features/ui/__tests__/index.test.tsx @@ -15,12 +15,12 @@ const TestableComponent = () => ( Sign in {/* WrappedRount will redirect to /login for logged out users... which will resolve to the route above! */} - + null} /> ); describe('', () => { - let store; + let store: any; beforeEach(() => { store = { diff --git a/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx b/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx index 86b9b84bd..5dec81b27 100644 --- a/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx @@ -1,5 +1,4 @@ import { fireEvent, render, screen } from '@testing-library/react'; -import { Map as ImmutableMap } from 'immutable'; import React from 'react'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; @@ -11,7 +10,7 @@ import rootReducer from 'soapbox/reducers'; import ComposeButton from '../compose-button'; -const store = mockStore(rootReducer(ImmutableMap(), {})); +const store = mockStore(rootReducer(undefined, {} as any)); const renderComposeButton = () => { render( diff --git a/app/soapbox/features/ui/components/__tests__/cta-banner.test.tsx b/app/soapbox/features/ui/components/__tests__/cta-banner.test.tsx index cc4c118e6..dbda24b55 100644 --- a/app/soapbox/features/ui/components/__tests__/cta-banner.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/cta-banner.test.tsx @@ -14,7 +14,7 @@ describe('', () => { it('renders empty', () => { const store = { me: true }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('cta-banner')).toHaveLength(0); }); }); @@ -23,7 +23,7 @@ describe('', () => { it('renders empty', () => { const store = { soapbox: ImmutableMap({ singleUserMode: true }) }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('cta-banner')).toHaveLength(0); }); }); diff --git a/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx b/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx index 3dea16d97..d0ec92f96 100644 --- a/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx @@ -5,7 +5,9 @@ import { render, screen } from '../../../../jest/test-helpers'; import { normalizeAccount, normalizeRelationship } from '../../../../normalizers'; import SubscribeButton from '../subscription-button'; -let account = { +import type { ReducerAccount } from 'soapbox/reducers/accounts'; + +const justin = { id: '1', acct: 'justin-username', display_name: 'Justin L', @@ -13,13 +15,13 @@ let account = { }; describe('', () => { - let store; + let store: any; describe('with "accountNotifies" disabled', () => { it('renders nothing', () => { - account = normalizeAccount({ ...account, relationship: normalizeRelationship({ following: true }) }); + const account = normalizeAccount({ ...justin, relationship: normalizeRelationship({ following: true }) }) as ReducerAccount; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); }); }); diff --git a/app/soapbox/features/ui/components/__tests__/trends-panel.test.tsx b/app/soapbox/features/ui/components/__tests__/trends-panel.test.tsx index 03ae48553..487a84bfb 100644 --- a/app/soapbox/features/ui/components/__tests__/trends-panel.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/trends-panel.test.tsx @@ -23,7 +23,7 @@ describe('', () => { })(), }; - render(, null, store); + render(, undefined, store); expect(screen.getByTestId('hashtag')).toHaveTextContent(/hashtag 1/i); expect(screen.getByTestId('hashtag')).toHaveTextContent(/180 people talking/i); expect(screen.getByTestId('sparklines')).toBeInTheDocument(); @@ -46,7 +46,7 @@ describe('', () => { })(), }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('hashtag')).toHaveLength(2); }); @@ -67,7 +67,7 @@ describe('', () => { })(), }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('hashtag')).toHaveLength(1); }); @@ -79,7 +79,7 @@ describe('', () => { })(), }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('hashtag')).toHaveLength(0); }); }); diff --git a/app/soapbox/features/ui/components/__tests__/who-to-follow-panel.test.tsx b/app/soapbox/features/ui/components/__tests__/who-to-follow-panel.test.tsx index 56e4f8422..84a645514 100644 --- a/app/soapbox/features/ui/components/__tests__/who-to-follow-panel.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/who-to-follow-panel.test.tsx @@ -24,7 +24,7 @@ describe('', () => { }, }; - render(, null, store); + render(, undefined, store); expect(screen.getByTestId('account')).toHaveTextContent(/my name/i); }); @@ -58,7 +58,7 @@ describe('', () => { }, }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('account')).toHaveLength(2); }); @@ -92,7 +92,7 @@ describe('', () => { }, }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('account')).toHaveLength(1); }); @@ -117,7 +117,7 @@ describe('', () => { }, }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('account')).toHaveLength(0); }); }); diff --git a/app/soapbox/features/ui/components/modals/report-modal/__tests__/report-modal.test.tsx b/app/soapbox/features/ui/components/modals/report-modal/__tests__/report-modal.test.tsx index 1fe62d7f2..a2dee08b5 100644 --- a/app/soapbox/features/ui/components/modals/report-modal/__tests__/report-modal.test.tsx +++ b/app/soapbox/features/ui/components/modals/report-modal/__tests__/report-modal.test.tsx @@ -9,7 +9,7 @@ import { normalizeAccount, normalizeStatus } from '../../../../../../normalizers import ReportModal from '../report-modal'; describe('', () => { - let store; + let store: any; beforeEach(() => { const rules = require('soapbox/__fixtures__/rules.json'); From 590e85ac596d98c86eaff60066c0c7c2ccc262dc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 12:24:45 -0500 Subject: [PATCH 25/41] Fix notification test types --- app/soapbox/components/status.tsx | 2 -- .../__tests__/notification.test.tsx | 16 ++++++---- .../notifications/components/notification.tsx | 32 +++++++++---------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 200b2de10..90d7e13df 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -84,8 +84,6 @@ interface IStatus extends RouteComponentProps { onMoveDown: (statusId: string, featured?: boolean) => void, getScrollPosition?: () => ScrollPosition | undefined, updateScrollBottom?: (bottom: number) => void, - cacheMediaWidth: () => void, - cachedMediaWidth: number, group: ImmutableMap, displayMedia: string, allowedEmoji: ImmutableList, diff --git a/app/soapbox/features/notifications/components/__tests__/notification.test.tsx b/app/soapbox/features/notifications/components/__tests__/notification.test.tsx index 14c43f50c..eacca0f99 100644 --- a/app/soapbox/features/notifications/components/__tests__/notification.test.tsx +++ b/app/soapbox/features/notifications/components/__tests__/notification.test.tsx @@ -33,10 +33,12 @@ describe('', () => { describe('grouped notifications', () => { it('renders a grouped follow notification for more than 2', async() => { - const { notification, state } = normalize(require('soapbox/__fixtures__/notification-follow.json')); - const groupedNotification = { ...notification.toJS(), total_count: 5 }; + const { notification, state } = normalize({ + ...require('soapbox/__fixtures__/notification-follow.json'), + total_count: 5, + }); - render(, undefined, state); + render(, undefined, state); expect(screen.getByTestId('notification')).toBeInTheDocument(); expect(screen.getByTestId('account')).toContainHTML('neko@rdrama.cc'); @@ -44,10 +46,12 @@ describe('', () => { }); it('renders a grouped follow notification for 1', async() => { - const { notification, state } = normalize(require('soapbox/__fixtures__/notification-follow.json')); - const groupedNotification = { ...notification.toJS(), total_count: 2 }; + const { notification, state } = normalize({ + ...require('soapbox/__fixtures__/notification-follow.json'), + total_count: 2, + }); - render(, undefined, state); + render(, undefined, state); expect(screen.getByTestId('notification')).toBeInTheDocument(); expect(screen.getByTestId('account')).toContainHTML('neko@rdrama.cc'); diff --git a/app/soapbox/features/notifications/components/notification.tsx b/app/soapbox/features/notifications/components/notification.tsx index dda887919..4b67e5645 100644 --- a/app/soapbox/features/notifications/components/notification.tsx +++ b/app/soapbox/features/notifications/components/notification.tsx @@ -128,16 +128,14 @@ const buildMessage = ( interface INotificaton { hidden?: boolean, notification: NotificationEntity, - onMoveUp: (notificationId: string) => void, - onMoveDown: (notificationId: string) => void, - onMention: (account: Account) => void, - onFavourite: (status: Status) => void, - onReblog: (status: Status, e?: KeyboardEvent) => void, - onToggleHidden: (status: Status) => void, + onMoveUp?: (notificationId: string) => void, + onMoveDown?: (notificationId: string) => void, + onMention?: (account: Account) => void, + onFavourite?: (status: Status) => void, + onReblog?: (status: Status, e?: KeyboardEvent) => void, + onToggleHidden?: (status: Status) => void, getScrollPosition?: () => ScrollPosition | undefined, updateScrollBottom?: (bottom: number) => void, - cacheMediaWidth: () => void, - cachedMediaWidth: number, siteTitle?: string, } @@ -180,35 +178,39 @@ const Notification: React.FC = (props) => { const handleMention = (e?: KeyboardEvent) => { e?.preventDefault(); - if (account && typeof account === 'object') { + if (props.onMention && account && typeof account === 'object') { props.onMention(account); } }; const handleHotkeyFavourite = (e?: KeyboardEvent) => { - if (status && typeof status === 'object') { + if (props.onFavourite && status && typeof status === 'object') { props.onFavourite(status); } }; const handleHotkeyBoost = (e?: KeyboardEvent) => { - if (status && typeof status === 'object') { + if (props.onReblog && status && typeof status === 'object') { props.onReblog(status, e); } }; const handleHotkeyToggleHidden = (e?: KeyboardEvent) => { - if (status && typeof status === 'object') { + if (props.onToggleHidden && status && typeof status === 'object') { props.onToggleHidden(status); } }; const handleMoveUp = () => { - onMoveUp(notification.id); + if (onMoveUp) { + onMoveUp(notification.id); + } }; const handleMoveDown = () => { - onMoveDown(notification.id); + if (onMoveDown) { + onMoveDown(notification.id); + } }; const renderIcon = (): React.ReactNode => { @@ -268,8 +270,6 @@ const Notification: React.FC = (props) => { contextType='notifications' getScrollPosition={props.getScrollPosition} updateScrollBottom={props.updateScrollBottom} - cachedMediaWidth={props.cachedMediaWidth} - cacheMediaWidth={props.cacheMediaWidth} /> ) : null; default: From 0c7f1628b067c92457de5bffd01654b9f8c5a0f4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 12:31:11 -0500 Subject: [PATCH 26/41] Fix more types in tests --- .../components/__tests__/captcha.test.tsx | 6 +++-- .../components/__tests__/login_form.test.tsx | 4 +-- .../components/__tests__/login_page.test.tsx | 2 +- .../emoji/__tests__/emoji_index.test.ts | 27 +++++++++---------- .../__tests__/feed-carousel.test.tsx | 10 +++---- .../__tests__/landing_page.test.tsx | 8 +++--- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/app/soapbox/features/auth_login/components/__tests__/captcha.test.tsx b/app/soapbox/features/auth_login/components/__tests__/captcha.test.tsx index d48071b0c..bb09d1dba 100644 --- a/app/soapbox/features/auth_login/components/__tests__/captcha.test.tsx +++ b/app/soapbox/features/auth_login/components/__tests__/captcha.test.tsx @@ -6,7 +6,7 @@ import CaptchaField, { NativeCaptchaField } from '../captcha'; describe('', () => { it('renders null by default', () => { - render(); + render(); expect(screen.queryAllByRole('textbox')).toHaveLength(0); }); @@ -24,7 +24,9 @@ describe('', () => { render( {}} // eslint-disable-line react/jsx-no-bind + onChange={() => {}} + onClick={() => {}} + value='' />, ); diff --git a/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx b/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx index ac1c52930..b46acf31a 100644 --- a/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx +++ b/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx @@ -13,7 +13,7 @@ describe('', () => { }), }; - render(, null, store); + render(, undefined, store); expect(screen.getByRole('heading')).toHaveTextContent(/sign in/i); }); @@ -26,7 +26,7 @@ describe('', () => { }), }; - render(, null, store); + render(, undefined, store); expect(screen.getByRole('heading')).toHaveTextContent(/sign in/i); }); diff --git a/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx b/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx index 85daf3e4e..50f94b285 100644 --- a/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx +++ b/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx @@ -12,7 +12,7 @@ describe('', () => { }), }; - render(, null, store); + render(, undefined, store); expect(screen.getByRole('heading')).toHaveTextContent('Sign In'); }); diff --git a/app/soapbox/features/emoji/__tests__/emoji_index.test.ts b/app/soapbox/features/emoji/__tests__/emoji_index.test.ts index 99f46e3b3..78698981a 100644 --- a/app/soapbox/features/emoji/__tests__/emoji_index.test.ts +++ b/app/soapbox/features/emoji/__tests__/emoji_index.test.ts @@ -1,9 +1,10 @@ +// @ts-ignore import { emojiIndex } from 'emoji-mart'; import pick from 'lodash/pick'; import { search } from '../emoji_mart_search_light'; -const trimEmojis = emoji => pick(emoji, ['id', 'unified', 'native', 'custom']); +const trimEmojis = (emoji: any) => pick(emoji, ['id', 'unified', 'native', 'custom']); describe('emoji_index', () => { it('should give same result for emoji_index_light and emoji-mart', () => { @@ -46,7 +47,7 @@ describe('emoji_index', () => { }); it('can include/exclude categories', () => { - expect(search('flag', { include: ['people'] })).toEqual([]); + expect(search('flag', { include: ['people'] } as any)).toEqual([]); expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]); }); @@ -63,9 +64,8 @@ describe('emoji_index', () => { custom: true, }, ]; - search('', { custom }); + search('', { custom } as any); emojiIndex.search('', { custom }); - const expected = []; const lightExpected = [ { id: 'mastodon', @@ -73,7 +73,7 @@ describe('emoji_index', () => { }, ]; expect(search('masto').map(trimEmojis)).toEqual(lightExpected); - expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected); + expect(emojiIndex.search('masto').map(trimEmojis)).toEqual([]); }); it('(different behavior from emoji-mart) erases custom emoji if another is passed', () => { @@ -89,11 +89,10 @@ describe('emoji_index', () => { custom: true, }, ]; - search('', { custom }); + search('', { custom } as any); emojiIndex.search('', { custom }); - const expected = []; - expect(search('masto', { custom: [] }).map(trimEmojis)).toEqual(expected); - expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected); + expect(search('masto', { custom: [] } as any).map(trimEmojis)).toEqual([]); + expect(emojiIndex.search('masto').map(trimEmojis)).toEqual([]); }); it('handles custom emoji', () => { @@ -109,7 +108,7 @@ describe('emoji_index', () => { custom: true, }, ]; - search('', { custom }); + search('', { custom } as any); emojiIndex.search('', { custom }); const expected = [ { @@ -117,15 +116,15 @@ describe('emoji_index', () => { custom: true, }, ]; - expect(search('masto', { custom }).map(trimEmojis)).toEqual(expected); + expect(search('masto', { custom } as any).map(trimEmojis)).toEqual(expected); expect(emojiIndex.search('masto', { custom }).map(trimEmojis)).toEqual(expected); }); it('should filter only emojis we care about, exclude pineapple', () => { - const emojisToShowFilter = emoji => emoji.unified !== '1F34D'; - expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id)) + const emojisToShowFilter = (emoji: any) => emoji.unified !== '1F34D'; + expect(search('apple', { emojisToShowFilter } as any).map((obj: any) => obj.id)) .not.toContain('pineapple'); - expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id)) + expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj: any) => obj.id)) .not.toContain('pineapple'); }); diff --git a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx index 096682fa0..eb84e7ad0 100644 --- a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx +++ b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx @@ -18,7 +18,7 @@ jest.mock('../../../hooks/useDimensions', () => ({ }; describe('', () => { - let store; + let store: any; describe('with "feedUserFiltering" disabled', () => { beforeEach(() => { @@ -35,7 +35,7 @@ describe('', () => { }); it('should render nothing', () => { - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(0); }); @@ -56,7 +56,7 @@ describe('', () => { }); it('should render the Carousel', () => { - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(1); }); @@ -70,7 +70,7 @@ describe('', () => { }); it('renders the error message', () => { - render(, null, store); + render(, undefined, store); expect(screen.getByTestId('feed-carousel-error')).toBeInTheDocument(); }); @@ -110,7 +110,7 @@ describe('', () => { it('should render the correct prev/next buttons', async() => { const user = userEvent.setup(); - render(, null, store); + render(, undefined, store); await waitFor(() => { expect(screen.getByTestId('next-page')).toBeInTheDocument(); diff --git a/app/soapbox/features/landing_page/__tests__/landing_page.test.tsx b/app/soapbox/features/landing_page/__tests__/landing_page.test.tsx index 02b9a4fe0..a52dbd975 100644 --- a/app/soapbox/features/landing_page/__tests__/landing_page.test.tsx +++ b/app/soapbox/features/landing_page/__tests__/landing_page.test.tsx @@ -17,7 +17,7 @@ describe('', () => { }, }); - render(, null, state); + render(, undefined, state); expect(screen.queryByTestId('registrations-open')).toBeInTheDocument(); expect(screen.queryByTestId('registrations-closed')).not.toBeInTheDocument(); @@ -34,7 +34,7 @@ describe('', () => { }, }); - render(, null, state); + render(, undefined, state); expect(screen.queryByTestId('registrations-closed')).toBeInTheDocument(); expect(screen.queryByTestId('registrations-open')).not.toBeInTheDocument(); @@ -59,7 +59,7 @@ describe('', () => { }, }], rootReducer); - render(, null, state); + render(, undefined, state); expect(screen.queryByTestId('registrations-pepe')).toBeInTheDocument(); expect(screen.queryByTestId('registrations-open')).not.toBeInTheDocument(); @@ -81,7 +81,7 @@ describe('', () => { }, }], rootReducer); - render(, null, state); + render(, undefined, state); expect(screen.queryByTestId('registrations-closed')).toBeInTheDocument(); expect(screen.queryByTestId('registrations-pepe')).not.toBeInTheDocument(); From 2dd16311623b6bbe70abb68c424233c23861a6ca Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 13:12:35 -0500 Subject: [PATCH 27/41] More component test TS fixes --- app/soapbox/components/__tests__/account.test.tsx | 14 ++++++++------ app/soapbox/components/__tests__/avatar.test.tsx | 4 +++- .../components/__tests__/avatar_overlay.test.tsx | 13 ++++++++----- .../components/__tests__/display_name.test.tsx | 4 +++- .../components/__tests__/emoji_selector.test.tsx | 1 + .../components/__tests__/quoted-status.test.tsx | 8 +++++--- .../__tests__/scroll-top-button.test.tsx | 4 ++-- .../polls/__tests__/poll-footer.test.tsx | 2 +- .../ui/datepicker/__tests__/datepicker.test.tsx | 2 +- app/soapbox/jest/test-helpers.tsx | 4 +++- 10 files changed, 35 insertions(+), 21 deletions(-) diff --git a/app/soapbox/components/__tests__/account.test.tsx b/app/soapbox/components/__tests__/account.test.tsx index e7af7b8f0..7f1458349 100644 --- a/app/soapbox/components/__tests__/account.test.tsx +++ b/app/soapbox/components/__tests__/account.test.tsx @@ -5,6 +5,8 @@ import { render, screen } from '../../jest/test-helpers'; import { normalizeAccount } from '../../normalizers'; import Account from '../account'; +import type { ReducerAccount } from 'soapbox/reducers/accounts'; + describe('', () => { it('renders account name and username', () => { const account = normalizeAccount({ @@ -12,7 +14,7 @@ describe('', () => { acct: 'justin-username', display_name: 'Justin L', avatar: 'test.jpg', - }); + }) as ReducerAccount; const store = { accounts: ImmutableMap({ @@ -20,7 +22,7 @@ describe('', () => { }), }; - render(, null, store); + render(, undefined, store); expect(screen.getByTestId('account')).toHaveTextContent('Justin L'); expect(screen.getByTestId('account')).toHaveTextContent(/justin-username/i); }); @@ -33,7 +35,7 @@ describe('', () => { display_name: 'Justin L', avatar: 'test.jpg', verified: true, - }); + }) as ReducerAccount; const store = { accounts: ImmutableMap({ @@ -41,7 +43,7 @@ describe('', () => { }), }; - render(, null, store); + render(, undefined, store); expect(screen.getByTestId('verified-badge')).toBeInTheDocument(); }); @@ -52,7 +54,7 @@ describe('', () => { display_name: 'Justin L', avatar: 'test.jpg', verified: false, - }); + }) as ReducerAccount; const store = { accounts: ImmutableMap({ @@ -60,7 +62,7 @@ describe('', () => { }), }; - render(, null, store); + render(, undefined, store); expect(screen.queryAllByTestId('verified-badge')).toHaveLength(0); }); }); diff --git a/app/soapbox/components/__tests__/avatar.test.tsx b/app/soapbox/components/__tests__/avatar.test.tsx index 55abca520..56f592925 100644 --- a/app/soapbox/components/__tests__/avatar.test.tsx +++ b/app/soapbox/components/__tests__/avatar.test.tsx @@ -5,6 +5,8 @@ import { normalizeAccount } from 'soapbox/normalizers'; import { render, screen } from '../../jest/test-helpers'; import Avatar from '../avatar'; +import type { ReducerAccount } from 'soapbox/reducers/accounts'; + describe('', () => { const account = normalizeAccount({ username: 'alice', @@ -12,7 +14,7 @@ describe('', () => { display_name: 'Alice', avatar: '/animated/alice.gif', avatar_static: '/static/alice.jpg', - }); + }) as ReducerAccount; const size = 100; diff --git a/app/soapbox/components/__tests__/avatar_overlay.test.tsx b/app/soapbox/components/__tests__/avatar_overlay.test.tsx index b62d4eef8..105828556 100644 --- a/app/soapbox/components/__tests__/avatar_overlay.test.tsx +++ b/app/soapbox/components/__tests__/avatar_overlay.test.tsx @@ -1,25 +1,28 @@ -import { fromJS } from 'immutable'; import React from 'react'; +import { normalizeAccount } from 'soapbox/normalizers'; + import { render, screen } from '../../jest/test-helpers'; import AvatarOverlay from '../avatar_overlay'; +import type { ReducerAccount } from 'soapbox/reducers/accounts'; + describe(' { - const account = fromJS({ + const account = normalizeAccount({ username: 'alice', acct: 'alice', display_name: 'Alice', avatar: '/animated/alice.gif', avatar_static: '/static/alice.jpg', - }); + }) as ReducerAccount; - const friend = fromJS({ + const friend = normalizeAccount({ username: 'eve', acct: 'eve@blackhat.lair', display_name: 'Evelyn', avatar: '/animated/eve.gif', avatar_static: '/static/eve.jpg', - }); + }) as ReducerAccount; it('renders a overlay avatar', () => { render(); diff --git a/app/soapbox/components/__tests__/display_name.test.tsx b/app/soapbox/components/__tests__/display_name.test.tsx index 6d0a05ed5..4c1c1bd23 100644 --- a/app/soapbox/components/__tests__/display_name.test.tsx +++ b/app/soapbox/components/__tests__/display_name.test.tsx @@ -5,9 +5,11 @@ import { normalizeAccount } from 'soapbox/normalizers'; import { render, screen } from '../../jest/test-helpers'; import DisplayName from '../display-name'; +import type { ReducerAccount } from 'soapbox/reducers/accounts'; + describe('', () => { it('renders display name + account name', () => { - const account = normalizeAccount({ acct: 'bar@baz' }); + const account = normalizeAccount({ acct: 'bar@baz' }) as ReducerAccount; render(); expect(screen.getByTestId('display-name')).toHaveTextContent('bar@baz'); diff --git a/app/soapbox/components/__tests__/emoji_selector.test.tsx b/app/soapbox/components/__tests__/emoji_selector.test.tsx index 891e3e61c..c680d156e 100644 --- a/app/soapbox/components/__tests__/emoji_selector.test.tsx +++ b/app/soapbox/components/__tests__/emoji_selector.test.tsx @@ -6,6 +6,7 @@ import EmojiSelector from '../emoji_selector'; describe('', () => { it('renders correctly', () => { const children = ; + // @ts-ignore children.__proto__.addEventListener = () => {}; render(children); diff --git a/app/soapbox/components/__tests__/quoted-status.test.tsx b/app/soapbox/components/__tests__/quoted-status.test.tsx index 208a913b2..d57b59b30 100644 --- a/app/soapbox/components/__tests__/quoted-status.test.tsx +++ b/app/soapbox/components/__tests__/quoted-status.test.tsx @@ -4,6 +4,8 @@ import { render, screen, rootState } from '../../jest/test-helpers'; import { normalizeStatus, normalizeAccount } from '../../normalizers'; import QuotedStatus from '../quoted-status'; +import type { ReducerStatus } from 'soapbox/reducers/statuses'; + describe('', () => { it('renders content', () => { const account = normalizeAccount({ @@ -16,11 +18,11 @@ describe('', () => { account, content: 'hello world', contentHtml: 'hello world', - }); + }) as ReducerStatus; - const state = rootState.setIn(['accounts', '1', account]); + const state = rootState.setIn(['accounts', '1'], account); - render(, null, state); + render(, undefined, state); screen.getByText(/hello world/i); expect(screen.getByTestId('quoted-status')).toHaveTextContent(/hello world/i); }); diff --git a/app/soapbox/components/__tests__/scroll-top-button.test.tsx b/app/soapbox/components/__tests__/scroll-top-button.test.tsx index 89518b977..76e6b7c1c 100644 --- a/app/soapbox/components/__tests__/scroll-top-button.test.tsx +++ b/app/soapbox/components/__tests__/scroll-top-button.test.tsx @@ -28,7 +28,7 @@ describe('', () => { message={messages.queue} />, ); - expect(screen.getByText('Click to see 1 new post', { hidden: true })).toBeInTheDocument(); + expect(screen.getByText('Click to see 1 new post')).toBeInTheDocument(); render( ', () => { message={messages.queue} />, ); - expect(screen.getByText('Click to see 9999999 new posts', { hidden: true })).toBeInTheDocument(); + expect(screen.getByText('Click to see 9999999 new posts')).toBeInTheDocument(); }); }); diff --git a/app/soapbox/components/polls/__tests__/poll-footer.test.tsx b/app/soapbox/components/polls/__tests__/poll-footer.test.tsx index 7265f7e1c..9a403e339 100644 --- a/app/soapbox/components/polls/__tests__/poll-footer.test.tsx +++ b/app/soapbox/components/polls/__tests__/poll-footer.test.tsx @@ -36,7 +36,7 @@ describe('', () => { }); const user = userEvent.setup(); - const store = mockStore(rootReducer(undefined, {})); + const store = mockStore(rootReducer(undefined, {} as any)); render( diff --git a/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx b/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx index c4f94c89f..f2db8981c 100644 --- a/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx +++ b/app/soapbox/components/ui/datepicker/__tests__/datepicker.test.tsx @@ -30,7 +30,7 @@ describe('', () => { ); let daySelect: HTMLElement; - daySelect = document.querySelector('[data-testid="datepicker-day"]'); + daySelect = document.querySelector('[data-testid="datepicker-day"]') as HTMLElement; expect(queryAllByRole(daySelect, 'option')).toHaveLength(29); await userEvent.selectOptions( diff --git a/app/soapbox/jest/test-helpers.tsx b/app/soapbox/jest/test-helpers.tsx index 459049b52..abc13fffb 100644 --- a/app/soapbox/jest/test-helpers.tsx +++ b/app/soapbox/jest/test-helpers.tsx @@ -12,11 +12,13 @@ import '@testing-library/jest-dom'; import NotificationsContainer from '../features/ui/containers/notifications_container'; import { default as rootReducer } from '../reducers'; +import type { StateRecord } from 'soapbox/reducers'; + // Mock Redux // https://redux.js.org/recipes/writing-tests/ const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); -let rootState = rootReducer(undefined, {} as Action); +let rootState = rootReducer(undefined, {} as Action) as unknown as ReturnType; /** Apply actions to the state, one at a time. */ const applyActions = (state: any, actions: any, reducer: any) => { From 3f744554844f7ec0183474c35f9edc6abbbed0c0 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 6 Jul 2022 14:19:42 -0400 Subject: [PATCH 28/41] Improve UI of IconButton when disabled --- app/soapbox/components/ui/icon-button/icon-button.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/components/ui/icon-button/icon-button.tsx b/app/soapbox/components/ui/icon-button/icon-button.tsx index f74f2ca04..aa317b6ea 100644 --- a/app/soapbox/components/ui/icon-button/icon-button.tsx +++ b/app/soapbox/components/ui/icon-button/icon-button.tsx @@ -25,6 +25,7 @@ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef type='button' className={classNames('flex items-center space-x-2 p-1 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 dark:ring-offset-0 focus:ring-primary-500', { 'bg-white dark:bg-transparent': !transparent, + 'opacity-50': filteredProps.disabled, }, className)} {...filteredProps} data-testid='icon-button' From b2f2bcb8331f13f0f8acfe8825ddc8789d9ed11d Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 6 Jul 2022 14:19:51 -0400 Subject: [PATCH 29/41] Center tooltips and add arrow --- app/soapbox/components/ui/tooltip/tooltip.css | 4 ++ app/soapbox/components/ui/tooltip/tooltip.tsx | 49 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/soapbox/components/ui/tooltip/tooltip.css b/app/soapbox/components/ui/tooltip/tooltip.css index 6d439ddb3..9e4addd1f 100644 --- a/app/soapbox/components/ui/tooltip/tooltip.css +++ b/app/soapbox/components/ui/tooltip/tooltip.css @@ -6,3 +6,7 @@ @apply pointer-events-none absolute px-2.5 py-1.5 rounded shadow whitespace-nowrap text-xs font-medium bg-gray-800 text-white; z-index: 100; } + +[data-reach-tooltip-arrow] { + @apply absolute z-50 w-0 h-0 border-l-8 border-solid border-l-transparent border-r-8 border-r-transparent border-b-8 border-b-gray-800; +} diff --git a/app/soapbox/components/ui/tooltip/tooltip.tsx b/app/soapbox/components/ui/tooltip/tooltip.tsx index 4efa4749f..e04221200 100644 --- a/app/soapbox/components/ui/tooltip/tooltip.tsx +++ b/app/soapbox/components/ui/tooltip/tooltip.tsx @@ -1,4 +1,5 @@ -import { default as ReachTooltip } from '@reach/tooltip'; +import Portal from '@reach/portal'; +import { TooltipPopup, useTooltip } from '@reach/tooltip'; import React from 'react'; import './tooltip.css'; @@ -8,15 +9,55 @@ interface ITooltip { text: string, } +const centered = (triggerRect: any, tooltipRect: any) => { + const triggerCenter = triggerRect.left + triggerRect.width / 2; + const left = triggerCenter - tooltipRect.width / 2; + const maxLeft = window.innerWidth - tooltipRect.width - 2; + return { + left: Math.min(Math.max(2, left), maxLeft) + window.scrollX, + top: triggerRect.bottom + 8 + window.scrollY, + }; +}; + /** Hoverable tooltip element. */ const Tooltip: React.FC = ({ children, text, }) => { + // get the props from useTooltip + const [trigger, tooltip] = useTooltip(); + + // destructure off what we need to position the triangle + const { isVisible, triggerRect } = tooltip; + return ( - - {children} - + + {React.cloneElement(children as any, trigger)} + + {isVisible && ( + // The Triangle. We position it relative to the trigger, not the popup + // so that collisions don't have a triangle pointing off to nowhere. + // Using a Portal may seem a little extreme, but we can keep the + // positioning logic simpler here instead of needing to consider + // the popup's position relative to the trigger and collisions + +
+ + )} + + ); }; From 3e8ebb166147fe564b58ed9489b11f0c64290a65 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 6 Jul 2022 14:20:20 -0400 Subject: [PATCH 30/41] Improve UX after subscribing to account --- .../ui/components/subscription-button.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/soapbox/features/ui/components/subscription-button.tsx b/app/soapbox/features/ui/components/subscription-button.tsx index bbe01f3ba..22eb637d7 100644 --- a/app/soapbox/features/ui/components/subscription-button.tsx +++ b/app/soapbox/features/ui/components/subscription-button.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { @@ -16,6 +16,7 @@ const messages = defineMessages({ subscribe: { id: 'account.subscribe', defaultMessage: 'Subscribe to notifications from @{name}' }, unsubscribe: { id: 'account.unsubscribe', defaultMessage: 'Unsubscribe to notifications from @{name}' }, subscribeSuccess: { id: 'account.subscribe.success', defaultMessage: 'You have subscribed to this account.' }, + subscribeSuccessNotice: { id: 'account.subscribe.successNotice', defaultMessage: 'You have subscribed to this account, but your web notifications are disabled. Please enable them to receive notifications from @{name}.' }, unsubscribeSuccess: { id: 'account.unsubscribe.success', defaultMessage: 'You have unsubscribed from this account.' }, subscribeFailure: { id: 'account.subscribe.failure', defaultMessage: 'An error occurred trying to subscribed to this account.' }, unsubscribeFailure: { id: 'account.unsubscribe.failure', defaultMessage: 'An error occurred trying to unsubscribed to this account.' }, @@ -30,6 +31,14 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => { const features = useFeatures(); const intl = useIntl(); + const [hasWebNotificationsEnabled, setWebNotificationsEnabled] = useState(true); + + const checkWebNotifications = () => { + Notification.requestPermission() + .then((value) => setWebNotificationsEnabled(value === 'granted')) + .catch(() => null); + }; + const isFollowing = account.relationship?.following; const isRequested = account.relationship?.requested; const isSubscribed = features.accountNotifies ? @@ -39,8 +48,13 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => { intl.formatMessage(messages.unsubscribe, { name: account.get('username') }) : intl.formatMessage(messages.subscribe, { name: account.get('username') }); - const onSubscribeSuccess = () => - dispatch(snackbar.success(intl.formatMessage(messages.subscribeSuccess))); + const onSubscribeSuccess = () => { + if (hasWebNotificationsEnabled) { + dispatch(snackbar.success(intl.formatMessage(messages.subscribeSuccess))); + } else { + dispatch(snackbar.info(intl.formatMessage(messages.subscribeSuccessNotice, { name: account.get('username') }))); + } + }; const onSubscribeFailure = () => dispatch(snackbar.error(intl.formatMessage(messages.subscribeFailure))); @@ -83,6 +97,12 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => { } }; + useEffect(() => { + if (features.accountSubscriptions || features.accountNotifies) { + checkWebNotifications(); + } + }, []); + if (!features.accountSubscriptions && !features.accountNotifies) { return null; } @@ -93,7 +113,7 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => { src={isSubscribed ? require('@tabler/icons/icons/bell-ringing.svg') : require('@tabler/icons/icons/bell.svg')} onClick={handleToggle} title={title} - className='text-primary-700 bg-primary-100 dark:!bg-slate-700 dark:!text-white hover:bg-primary-200 p-2' + className='text-primary-700 bg-primary-100 dark:!bg-slate-700 dark:!text-white hover:bg-primary-200 disabled:hover:bg-primary-100 p-2' iconClassName='w-5 h-5' /> ); From 06d3f001703ca2f0520bd89637feca95b54f42af Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 13:56:55 -0500 Subject: [PATCH 31/41] Switch to @jedmao/redux-mock-store --- app/soapbox/jest/test-helpers.tsx | 10 +++++----- package.json | 2 +- yarn.lock | 12 +++++------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/soapbox/jest/test-helpers.tsx b/app/soapbox/jest/test-helpers.tsx index abc13fffb..12e0a2ebb 100644 --- a/app/soapbox/jest/test-helpers.tsx +++ b/app/soapbox/jest/test-helpers.tsx @@ -1,3 +1,4 @@ +import { configureMockStore } from '@jedmao/redux-mock-store'; import { render, RenderOptions } from '@testing-library/react'; import { merge } from 'immutable'; import React, { FC, ReactElement } from 'react'; @@ -5,20 +6,19 @@ import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import { Action, applyMiddleware, createStore } from 'redux'; -import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import '@testing-library/jest-dom'; import NotificationsContainer from '../features/ui/containers/notifications_container'; import { default as rootReducer } from '../reducers'; -import type { StateRecord } from 'soapbox/reducers'; +import type { AnyAction } from 'redux'; +import type { AppDispatch } from 'soapbox/store'; // Mock Redux // https://redux.js.org/recipes/writing-tests/ -const middlewares = [thunk]; -const mockStore = configureMockStore(middlewares); -let rootState = rootReducer(undefined, {} as Action) as unknown as ReturnType; +let rootState = rootReducer(undefined, {} as Action); +const mockStore = configureMockStore([thunk]); /** Apply actions to the state, one at a time. */ const applyActions = (state: any, actions: any, reducer: any) => { diff --git a/package.json b/package.json index 7568e6eba..cb9ab4481 100644 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ "wicg-inert": "^3.1.1" }, "devDependencies": { + "@jedmao/redux-mock-store": "^3.0.5", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.0.3", @@ -225,7 +226,6 @@ "lint-staged": ">=10", "raf": "^3.4.1", "react-intl-translations-manager": "^5.0.3", - "redux-mock-store": "^1.5.4", "stylelint": "^13.7.2", "stylelint-config-standard": "^22.0.0", "stylelint-scss": "^3.18.0", diff --git a/yarn.lock b/yarn.lock index 893a03639..8d2248152 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1603,6 +1603,11 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@jedmao/redux-mock-store@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@jedmao/redux-mock-store/-/redux-mock-store-3.0.5.tgz#015fa4fc96bfc02b61ca221d9ea0476b78c70c97" + integrity sha512-zNcVCd5/ekSMdQWk64CqTPM24D9Lo59st9KvS+fljGpQXV4SliB7Vo0NFQIgvQJWPYeeobdngnrGy0XbCaARNw== + "@jest/console@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" @@ -10028,13 +10033,6 @@ redux-immutable@^4.0.0: resolved "https://registry.yarnpkg.com/redux-immutable/-/redux-immutable-4.0.0.tgz#3a1a32df66366462b63691f0e1dc35e472bbc9f3" integrity sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM= -redux-mock-store@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.4.tgz#90d02495fd918ddbaa96b83aef626287c9ab5872" - integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA== - dependencies: - lodash.isplainobject "^4.0.6" - redux-thunk@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" From 8da7b8fe7a1dd349c295fdc0f30efe200376ceb1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 15:25:07 -0500 Subject: [PATCH 32/41] Fix rootState type in tests --- app/soapbox/jest/test-helpers.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/soapbox/jest/test-helpers.tsx b/app/soapbox/jest/test-helpers.tsx index 12e0a2ebb..d31d01bfd 100644 --- a/app/soapbox/jest/test-helpers.tsx +++ b/app/soapbox/jest/test-helpers.tsx @@ -1,6 +1,6 @@ import { configureMockStore } from '@jedmao/redux-mock-store'; import { render, RenderOptions } from '@testing-library/react'; -import { merge } from 'immutable'; +import { merge, Record as ImmutableRecord } from 'immutable'; import React, { FC, ReactElement } from 'react'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; @@ -17,7 +17,8 @@ import type { AppDispatch } from 'soapbox/store'; // Mock Redux // https://redux.js.org/recipes/writing-tests/ -let rootState = rootReducer(undefined, {} as Action); +const gg = rootReducer(undefined, {} as Action); +const rootState = gg as unknown as ImmutableRecord; const mockStore = configureMockStore([thunk]); /** Apply actions to the state, one at a time. */ @@ -29,12 +30,13 @@ const createTestStore = (initialState: any) => createStore(rootReducer, initialS const TestApp: FC = ({ children, storeProps, routerProps = {} }) => { let store: any; + let appState = rootState; if (storeProps) { - rootState = merge(rootState, storeProps); - store = createTestStore(rootState); + appState = merge(rootState, storeProps); + store = createTestStore(appState); } else { - store = createTestStore(rootState); + store = createTestStore(appState); } const props = { From e47e33e21c3bfbd0516170ef53f23cd0c2eead47 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 15:54:30 -0500 Subject: [PATCH 33/41] FIX ROOT STATE TYPE!!!!! --- app/soapbox/reducers/index.ts | 1 - types/redux-immutable/index.d.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/soapbox/reducers/index.ts b/app/soapbox/reducers/index.ts index 7f33dad1c..72b33f785 100644 --- a/app/soapbox/reducers/index.ts +++ b/app/soapbox/reducers/index.ts @@ -134,7 +134,6 @@ export const StateRecord = ImmutableRecord( }, {}), ); -// @ts-ignore: This type is fine but TS thinks it's wrong const appReducer = combineReducers(reducers, StateRecord); // Clear the state (mostly) when the user logs out diff --git a/types/redux-immutable/index.d.ts b/types/redux-immutable/index.d.ts index 80d00209a..fbaecd198 100644 --- a/types/redux-immutable/index.d.ts +++ b/types/redux-immutable/index.d.ts @@ -13,5 +13,5 @@ declare module 'redux-immutable' { export function combineReducers(reducers: ReducersMapObject, getDefaultState?: () => Collection.Keyed): Reducer; export function combineReducers(reducers: ReducersMapObject, getDefaultState?: () => Collection.Indexed): Reducer; export function combineReducers(reducers: ReducersMapObject, getDefaultState?: () => Collection.Indexed): Reducer; - export function combineReducers(reducers: ReducersMapObject, getDefaultState?: Record.Factory): Reducer; + export function combineReducers(reducers: ReducersMapObject, getDefaultState?: Record.Factory): Reducer>>; } From e9112506c6135a9c40dfd094f78692ad34c315fd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 17:02:44 -0500 Subject: [PATCH 34/41] FIX ACTION TESTS TYPES --- .../actions/__tests__/account-notes.test.ts | 26 +-- .../actions/__tests__/accounts.test.ts | 184 +++++++++--------- app/soapbox/actions/__tests__/alerts.test.ts | 17 +- app/soapbox/actions/__tests__/blocks.test.ts | 26 ++- .../actions/__tests__/carousels.test.ts | 4 +- app/soapbox/actions/__tests__/compose.test.ts | 43 ++-- .../actions/__tests__/onboarding.test.ts | 13 +- .../actions/__tests__/statuses.test.ts | 13 +- app/soapbox/jest/test-helpers.tsx | 7 +- app/soapbox/reducers/account_notes.ts | 6 +- app/soapbox/reducers/user_lists.ts | 6 +- 11 files changed, 170 insertions(+), 175 deletions(-) diff --git a/app/soapbox/actions/__tests__/account-notes.test.ts b/app/soapbox/actions/__tests__/account-notes.test.ts index 134b0abc7..61e0c20b0 100644 --- a/app/soapbox/actions/__tests__/account-notes.test.ts +++ b/app/soapbox/actions/__tests__/account-notes.test.ts @@ -1,18 +1,20 @@ import { Map as ImmutableMap } from 'immutable'; import { __stub } from 'soapbox/api'; -import { mockStore } from 'soapbox/jest/test-helpers'; -import rootReducer from 'soapbox/reducers'; +import { mockStore, rootState } from 'soapbox/jest/test-helpers'; +import { ReducerRecord, EditRecord } from 'soapbox/reducers/account_notes'; -import { normalizeAccount } from '../../normalizers'; +import { normalizeAccount, normalizeRelationship } from '../../normalizers'; import { changeAccountNoteComment, initAccountNoteModal, submitAccountNote } from '../account-notes'; +import type { Account } from 'soapbox/types/entities'; + describe('submitAccountNote()', () => { - let store; + let store: ReturnType; beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('account_notes', { edit: { account: 1, comment: 'hello' } }); + const state = rootState + .set('account_notes', ReducerRecord({ edit: EditRecord({ account: '1', comment: 'hello' }) })); store = mockStore(state); }); @@ -60,11 +62,11 @@ describe('submitAccountNote()', () => { }); describe('initAccountNoteModal()', () => { - let store; + let store: ReturnType; beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('relationships', ImmutableMap({ 1: { note: 'hello' } })); + const state = rootState + .set('relationships', ImmutableMap({ '1': normalizeRelationship({ note: 'hello' }) })); store = mockStore(state); }); @@ -75,7 +77,7 @@ describe('initAccountNoteModal()', () => { display_name: 'Justin L', avatar: 'test.jpg', verified: true, - }); + }) as Account; const expectedActions = [ { type: 'ACCOUNT_NOTE_INIT_MODAL', account, comment: 'hello' }, { type: 'MODAL_OPEN', modalType: 'ACCOUNT_NOTE' }, @@ -88,10 +90,10 @@ describe('initAccountNoteModal()', () => { }); describe('changeAccountNoteComment()', () => { - let store; + let store: ReturnType; beforeEach(() => { - const state = rootReducer(undefined, {}); + const state = rootState; store = mockStore(state); }); diff --git a/app/soapbox/actions/__tests__/accounts.test.ts b/app/soapbox/actions/__tests__/accounts.test.ts index dcfeabcf1..6ec79d68b 100644 --- a/app/soapbox/actions/__tests__/accounts.test.ts +++ b/app/soapbox/actions/__tests__/accounts.test.ts @@ -1,10 +1,10 @@ import { Map as ImmutableMap } from 'immutable'; import { __stub } from 'soapbox/api'; -import { mockStore } from 'soapbox/jest/test-helpers'; -import rootReducer from 'soapbox/reducers'; +import { mockStore, rootState, rootReducer } from 'soapbox/jest/test-helpers'; +import { ListRecord, ReducerRecord } from 'soapbox/reducers/user_lists'; -import { normalizeAccount } from '../../normalizers'; +import { normalizeAccount, normalizeInstance, normalizeRelationship } from '../../normalizers'; import { authorizeFollowRequest, blockAccount, @@ -28,7 +28,7 @@ import { unsubscribeAccount, } from '../accounts'; -let store; +let store: ReturnType; describe('createAccount()', () => { const params = { @@ -37,7 +37,7 @@ describe('createAccount()', () => { describe('with a successful API request', () => { beforeEach(() => { - const state = rootReducer(undefined, {}); + const state = rootState; store = mockStore(state); __stub((mock) => { @@ -74,10 +74,10 @@ describe('fetchAccount()', () => { avatar: 'test.jpg', }); - const state = rootReducer(undefined, {}) + const state = rootReducer(undefined, {} as any) .set('accounts', ImmutableMap({ [id]: account, - })); + }) as any); store = mockStore(state); @@ -98,7 +98,7 @@ describe('fetchAccount()', () => { const account = require('soapbox/__fixtures__/pleroma-account.json'); beforeEach(() => { - const state = rootReducer(undefined, {}); + const state = rootState; store = mockStore(state); __stub((mock) => { @@ -125,7 +125,7 @@ describe('fetchAccount()', () => { describe('with an unsuccessful API request', () => { beforeEach(() => { - const state = rootReducer(undefined, {}); + const state = rootState; store = mockStore(state); __stub((mock) => { @@ -155,7 +155,7 @@ describe('fetchAccount()', () => { describe('fetchAccountByUsername()', () => { const id = '123'; const username = 'tiger'; - let state, account; + let state, account: any; beforeEach(() => { account = normalizeAccount({ @@ -166,7 +166,7 @@ describe('fetchAccountByUsername()', () => { birthday: undefined, }); - state = rootReducer(undefined, {}) + state = rootState .set('accounts', ImmutableMap({ [id]: account, })); @@ -180,15 +180,15 @@ describe('fetchAccountByUsername()', () => { describe('when "accountByUsername" feature is enabled', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('instance', { + const state = rootState + .set('instance', normalizeInstance({ version: '2.7.2 (compatible; Pleroma 2.4.52-1337-g4779199e.gleasonator+soapbox)', pleroma: ImmutableMap({ metadata: ImmutableMap({ features: [], }), }), - }) + })) .set('me', '123'); store = mockStore(state); }); @@ -243,15 +243,15 @@ describe('fetchAccountByUsername()', () => { describe('when "accountLookup" feature is enabled', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('instance', { + const state = rootState + .set('instance', normalizeInstance({ version: '3.4.1 (compatible; TruthSocial 1.0.0)', pleroma: ImmutableMap({ metadata: ImmutableMap({ features: [], }), }), - }) + })) .set('me', '123'); store = mockStore(state); }); @@ -308,7 +308,7 @@ describe('fetchAccountByUsername()', () => { describe('when using the accountSearch function', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -373,12 +373,12 @@ describe('fetchAccountByUsername()', () => { describe('followAccount()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); it('should do nothing', async() => { - await store.dispatch(followAccount(1)); + await store.dispatch(followAccount('1')); const actions = store.getActions(); expect(actions).toEqual([]); @@ -386,10 +386,10 @@ describe('followAccount()', () => { }); describe('when logged in', () => { - const id = 1; + const id = '1'; beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -460,12 +460,12 @@ describe('followAccount()', () => { describe('unfollowAccount()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); it('should do nothing', async() => { - await store.dispatch(unfollowAccount(1)); + await store.dispatch(unfollowAccount('1')); const actions = store.getActions(); expect(actions).toEqual([]); @@ -473,10 +473,10 @@ describe('unfollowAccount()', () => { }); describe('when logged in', () => { - const id = 1; + const id = '1'; beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -489,7 +489,7 @@ describe('unfollowAccount()', () => { it('should dispatch the correct actions', async() => { const expectedActions = [ - { type: 'ACCOUNT_UNFOLLOW_REQUEST', id: 1, skipLoading: true }, + { type: 'ACCOUNT_UNFOLLOW_REQUEST', id: '1', skipLoading: true }, { type: 'ACCOUNT_UNFOLLOW_SUCCESS', relationship: { success: true }, @@ -534,11 +534,11 @@ describe('unfollowAccount()', () => { }); describe('blockAccount()', () => { - const id = 1; + const id = '1'; describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -552,7 +552,7 @@ describe('blockAccount()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -601,11 +601,11 @@ describe('blockAccount()', () => { }); describe('unblockAccount()', () => { - const id = 1; + const id = '1'; describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -619,7 +619,7 @@ describe('unblockAccount()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -667,11 +667,11 @@ describe('unblockAccount()', () => { }); describe('muteAccount()', () => { - const id = 1; + const id = '1'; describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -685,7 +685,7 @@ describe('muteAccount()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -734,11 +734,11 @@ describe('muteAccount()', () => { }); describe('unmuteAccount()', () => { - const id = 1; + const id = '1'; describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -752,7 +752,7 @@ describe('unmuteAccount()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -800,11 +800,11 @@ describe('unmuteAccount()', () => { }); describe('subscribeAccount()', () => { - const id = 1; + const id = '1'; describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -818,7 +818,7 @@ describe('subscribeAccount()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -866,11 +866,11 @@ describe('subscribeAccount()', () => { }); describe('unsubscribeAccount()', () => { - const id = 1; + const id = '1'; describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -884,7 +884,7 @@ describe('unsubscribeAccount()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -936,7 +936,7 @@ describe('removeFromFollowers()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -950,7 +950,7 @@ describe('removeFromFollowers()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -1002,7 +1002,7 @@ describe('fetchFollowers()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -1059,7 +1059,7 @@ describe('expandFollowers()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -1073,28 +1073,28 @@ describe('expandFollowers()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('user_lists', { + const state = rootState + .set('user_lists', ReducerRecord({ followers: ImmutableMap({ - [id]: { + [id]: ListRecord({ next: 'next_url', - }, + }), }), - }) + })) .set('me', '123'); store = mockStore(state); }); describe('when the url is null', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('user_lists', { + const state = rootState + .set('user_lists', ReducerRecord({ followers: ImmutableMap({ - [id]: { + [id]: ListRecord({ next: null, - }, + }), }), - }) + })) .set('me', '123'); store = mockStore(state); }); @@ -1160,7 +1160,7 @@ describe('fetchFollowing()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); @@ -1217,7 +1217,7 @@ describe('expandFollowing()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -1231,28 +1231,28 @@ describe('expandFollowing()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('user_lists', { + const state = rootState + .set('user_lists', ReducerRecord({ following: ImmutableMap({ - [id]: { + [id]: ListRecord({ next: 'next_url', - }, + }), }), - }) + })) .set('me', '123'); store = mockStore(state); }); describe('when the url is null', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('user_lists', { + const state = rootState + .set('user_lists', ReducerRecord({ following: ImmutableMap({ - [id]: { + [id]: ListRecord({ next: null, - }, + }), }), - }) + })) .set('me', '123'); store = mockStore(state); }); @@ -1318,7 +1318,7 @@ describe('fetchRelationships()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -1332,15 +1332,15 @@ describe('fetchRelationships()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) + const state = rootState .set('me', '123'); store = mockStore(state); }); describe('without newAccountIds', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('relationships', ImmutableMap({ [id]: {} })) + const state = rootState + .set('relationships', ImmutableMap({ [id]: normalizeRelationship({}) })) .set('me', '123'); store = mockStore(state); }); @@ -1355,7 +1355,7 @@ describe('fetchRelationships()', () => { describe('with a successful API request', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) + const state = rootState .set('relationships', ImmutableMap({})) .set('me', '123'); store = mockStore(state); @@ -1409,7 +1409,7 @@ describe('fetchRelationships()', () => { describe('fetchFollowRequests()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -1423,14 +1423,14 @@ describe('fetchFollowRequests()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) + const state = rootState .set('me', '123'); store = mockStore(state); }); describe('with a successful API request', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) + const state = rootState .set('relationships', ImmutableMap({})) .set('me', '123'); store = mockStore(state); @@ -1483,7 +1483,7 @@ describe('fetchFollowRequests()', () => { describe('expandFollowRequests()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -1497,24 +1497,24 @@ describe('expandFollowRequests()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('user_lists', { - follow_requests: { + const state = rootState + .set('user_lists', ReducerRecord({ + follow_requests: ListRecord({ next: 'next_url', - }, - }) + }), + })) .set('me', '123'); store = mockStore(state); }); describe('when the url is null', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) - .set('user_lists', { - follow_requests: { + const state = rootState + .set('user_lists', ReducerRecord({ + follow_requests: ListRecord({ next: null, - }, - }) + }), + })) .set('me', '123'); store = mockStore(state); }); @@ -1579,7 +1579,7 @@ describe('authorizeFollowRequest()', () => { describe('when logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -1593,7 +1593,7 @@ describe('authorizeFollowRequest()', () => { describe('when logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '123'); + const state = rootState.set('me', '123'); store = mockStore(state); }); diff --git a/app/soapbox/actions/__tests__/alerts.test.ts b/app/soapbox/actions/__tests__/alerts.test.ts index f2419893a..5f1f9f4d6 100644 --- a/app/soapbox/actions/__tests__/alerts.test.ts +++ b/app/soapbox/actions/__tests__/alerts.test.ts @@ -1,11 +1,10 @@ import { AxiosError } from 'axios'; -import { mockStore } from 'soapbox/jest/test-helpers'; -import rootReducer from 'soapbox/reducers'; +import { mockStore, rootState } from 'soapbox/jest/test-helpers'; import { dismissAlert, showAlert, showAlertForError } from '../alerts'; -const buildError = (message: string, status: number) => new AxiosError(message, String(status), null, null, { +const buildError = (message: string, status: number) => new AxiosError(message, String(status), undefined, null, { data: { error: message, }, @@ -15,10 +14,10 @@ const buildError = (message: string, status: number) => new AxiosError(mess config: {}, }); -let store; +let store: ReturnType; beforeEach(() => { - const state = rootReducer(undefined, {}); + const state = rootState; store = mockStore(state); }); @@ -28,7 +27,7 @@ describe('dismissAlert()', () => { const expectedActions = [ { type: 'ALERT_DISMISS', alert }, ]; - await store.dispatch(dismissAlert(alert)); + await store.dispatch(dismissAlert(alert as any)); const actions = store.getActions(); expect(actions).toEqual(expectedActions); @@ -70,11 +69,10 @@ describe('showAlert()', () => { it('dispatches the proper actions', async() => { const error = buildError('', 404); - const expectedActions = []; await store.dispatch(showAlertForError(error)); const actions = store.getActions(); - expect(actions).toEqual(expectedActions); + expect(actions).toEqual([]); }); }); @@ -82,11 +80,10 @@ describe('showAlert()', () => { it('dispatches the proper actions', async() => { const error = buildError('', 410); - const expectedActions = []; await store.dispatch(showAlertForError(error)); const actions = store.getActions(); - expect(actions).toEqual(expectedActions); + expect(actions).toEqual([]); }); }); diff --git a/app/soapbox/actions/__tests__/blocks.test.ts b/app/soapbox/actions/__tests__/blocks.test.ts index 2d4832007..8b4c040b3 100644 --- a/app/soapbox/actions/__tests__/blocks.test.ts +++ b/app/soapbox/actions/__tests__/blocks.test.ts @@ -1,8 +1,6 @@ -import { Record as ImmutableRecord } from 'immutable'; - import { __stub } from 'soapbox/api'; -import { mockStore } from 'soapbox/jest/test-helpers'; -import rootReducer from 'soapbox/reducers'; +import { mockStore, rootState } from 'soapbox/jest/test-helpers'; +import { ListRecord, ReducerRecord as UserListsRecord } from 'soapbox/reducers/user_lists'; import { expandBlocks, fetchBlocks } from '../blocks'; @@ -14,11 +12,11 @@ const account = { }; describe('fetchBlocks()', () => { - let store; + let store: ReturnType; describe('if logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -32,7 +30,7 @@ describe('fetchBlocks()', () => { describe('if logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '1234'); + const state = rootState.set('me', '1234'); store = mockStore(state); }); @@ -87,11 +85,11 @@ describe('fetchBlocks()', () => { }); describe('expandBlocks()', () => { - let store; + let store: ReturnType; describe('if logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -105,15 +103,15 @@ describe('expandBlocks()', () => { describe('if logged in', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', '1234'); + const state = rootState.set('me', '1234'); store = mockStore(state); }); describe('without a url', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) + const state = rootState .set('me', '1234') - .set('user_lists', ImmutableRecord({ blocks: { next: null } })()); + .set('user_lists', UserListsRecord({ blocks: ListRecord({ next: null }) })); store = mockStore(state); }); @@ -127,9 +125,9 @@ describe('expandBlocks()', () => { describe('with a url', () => { beforeEach(() => { - const state = rootReducer(undefined, {}) + const state = rootState .set('me', '1234') - .set('user_lists', ImmutableRecord({ blocks: { next: 'example' } })()); + .set('user_lists', UserListsRecord({ blocks: ListRecord({ next: 'example' }) })); store = mockStore(state); }); diff --git a/app/soapbox/actions/__tests__/carousels.test.ts b/app/soapbox/actions/__tests__/carousels.test.ts index 0953b8276..44e4ff0c0 100644 --- a/app/soapbox/actions/__tests__/carousels.test.ts +++ b/app/soapbox/actions/__tests__/carousels.test.ts @@ -4,14 +4,14 @@ import { mockStore, rootState } from 'soapbox/jest/test-helpers'; import { fetchCarouselAvatars } from '../carousels'; describe('fetchCarouselAvatars()', () => { - let store; + let store: ReturnType; beforeEach(() => { store = mockStore(rootState); }); describe('with a successful API request', () => { - let avatars; + let avatars: Record[]; beforeEach(() => { avatars = [ diff --git a/app/soapbox/actions/__tests__/compose.test.ts b/app/soapbox/actions/__tests__/compose.test.ts index 0dd6fc309..6a9ac6e43 100644 --- a/app/soapbox/actions/__tests__/compose.test.ts +++ b/app/soapbox/actions/__tests__/compose.test.ts @@ -1,28 +1,29 @@ -import { fromJS } from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; -import { mockStore } from 'soapbox/jest/test-helpers'; +import { mockStore, rootState } from 'soapbox/jest/test-helpers'; import { InstanceRecord } from 'soapbox/normalizers'; -import rootReducer from 'soapbox/reducers'; import { uploadCompose } from '../compose'; +import type { IntlShape } from 'react-intl'; + describe('uploadCompose()', () => { describe('with images', () => { - let files, store; + let files: FileList, store: ReturnType; beforeEach(() => { const instance = InstanceRecord({ - configuration: fromJS({ - statuses: { + configuration: ImmutableMap({ + statuses: ImmutableMap({ max_media_attachments: 4, - }, - media_attachments: { + }), + media_attachments: ImmutableMap({ image_size_limit: 10, - }, + }), }), }); - const state = rootReducer(undefined, {}) + const state = rootState .set('me', '1234') .set('instance', instance); @@ -32,13 +33,13 @@ describe('uploadCompose()', () => { name: 'Image', size: 15, type: 'image/png', - }]; + }] as unknown as FileList; }); it('creates an alert if exceeds max size', async() => { const mockIntl = { formatMessage: jest.fn().mockReturnValue('Image exceeds the current file size limit (10 Bytes)'), - }; + } as unknown as IntlShape; const expectedActions = [ { type: 'COMPOSE_UPLOAD_REQUEST', skipLoading: true }, @@ -60,21 +61,21 @@ describe('uploadCompose()', () => { }); describe('with videos', () => { - let files, store; + let files: FileList, store: ReturnType; beforeEach(() => { const instance = InstanceRecord({ - configuration: fromJS({ - statuses: { + configuration: ImmutableMap({ + statuses: ImmutableMap({ max_media_attachments: 4, - }, - media_attachments: { + }), + media_attachments: ImmutableMap({ video_size_limit: 10, - }, + }), }), }); - const state = rootReducer(undefined, {}) + const state = rootState .set('me', '1234') .set('instance', instance); @@ -84,13 +85,13 @@ describe('uploadCompose()', () => { name: 'Video', size: 15, type: 'video/mp4', - }]; + }] as unknown as FileList; }); it('creates an alert if exceeds max size', async() => { const mockIntl = { formatMessage: jest.fn().mockReturnValue('Video exceeds the current file size limit (10 Bytes)'), - }; + } as unknown as IntlShape; const expectedActions = [ { type: 'COMPOSE_UPLOAD_REQUEST', skipLoading: true }, diff --git a/app/soapbox/actions/__tests__/onboarding.test.ts b/app/soapbox/actions/__tests__/onboarding.test.ts index cdd268ed5..f786c7f90 100644 --- a/app/soapbox/actions/__tests__/onboarding.test.ts +++ b/app/soapbox/actions/__tests__/onboarding.test.ts @@ -1,5 +1,4 @@ -import { mockStore, mockWindowProperty } from 'soapbox/jest/test-helpers'; -import rootReducer from 'soapbox/reducers'; +import { mockStore, mockWindowProperty, rootState } from 'soapbox/jest/test-helpers'; import { checkOnboardingStatus, startOnboarding, endOnboarding } from '../onboarding'; @@ -17,7 +16,7 @@ describe('checkOnboarding()', () => { it('does nothing if localStorage item is not set', async() => { mockGetItem = jest.fn().mockReturnValue(null); - const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } }); + const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); await store.dispatch(checkOnboardingStatus()); @@ -30,7 +29,7 @@ describe('checkOnboarding()', () => { it('does nothing if localStorage item is invalid', async() => { mockGetItem = jest.fn().mockReturnValue('invalid'); - const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } }); + const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); await store.dispatch(checkOnboardingStatus()); @@ -43,7 +42,7 @@ describe('checkOnboarding()', () => { it('dispatches the correct action', async() => { mockGetItem = jest.fn().mockReturnValue('1'); - const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } }); + const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); await store.dispatch(checkOnboardingStatus()); @@ -66,7 +65,7 @@ describe('startOnboarding()', () => { }); it('dispatches the correct action', async() => { - const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } }); + const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); await store.dispatch(startOnboarding()); @@ -89,7 +88,7 @@ describe('endOnboarding()', () => { }); it('dispatches the correct action', async() => { - const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } }); + const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); await store.dispatch(endOnboarding()); diff --git a/app/soapbox/actions/__tests__/statuses.test.ts b/app/soapbox/actions/__tests__/statuses.test.ts index b206f17b9..18cbc173b 100644 --- a/app/soapbox/actions/__tests__/statuses.test.ts +++ b/app/soapbox/actions/__tests__/statuses.test.ts @@ -4,7 +4,6 @@ import { STATUSES_IMPORT } from 'soapbox/actions/importer'; import { __stub } from 'soapbox/api'; import { mockStore, rootState } from 'soapbox/jest/test-helpers'; import { normalizeStatus } from 'soapbox/normalizers/status'; -import rootReducer from 'soapbox/reducers'; import { deleteStatus, fetchContext } from '../statuses'; @@ -19,7 +18,7 @@ describe('fetchContext()', () => { const store = mockStore(rootState); - store.dispatch(fetchContext('017ed505-5926-392f-256a-f86d5075df70')).then(context => { + store.dispatch(fetchContext('017ed505-5926-392f-256a-f86d5075df70')).then(() => { const actions = store.getActions(); expect(actions[3].type).toEqual(STATUSES_IMPORT); @@ -31,11 +30,11 @@ describe('fetchContext()', () => { }); describe('deleteStatus()', () => { - let store; + let store: ReturnType; describe('if logged out', () => { beforeEach(() => { - const state = rootReducer(undefined, {}).set('me', null); + const state = rootState.set('me', null); store = mockStore(state); }); @@ -54,16 +53,16 @@ describe('deleteStatus()', () => { }); beforeEach(() => { - const state = rootReducer(undefined, {}) + const state = rootState .set('me', '1234') .set('statuses', fromJS({ [statusId]: cachedStatus, - })); + }) as any); store = mockStore(state); }); describe('with a successful API request', () => { - let status; + let status: any; beforeEach(() => { status = require('soapbox/__fixtures__/pleroma-status-deleted.json'); diff --git a/app/soapbox/jest/test-helpers.tsx b/app/soapbox/jest/test-helpers.tsx index d31d01bfd..657919b5a 100644 --- a/app/soapbox/jest/test-helpers.tsx +++ b/app/soapbox/jest/test-helpers.tsx @@ -1,6 +1,6 @@ import { configureMockStore } from '@jedmao/redux-mock-store'; import { render, RenderOptions } from '@testing-library/react'; -import { merge, Record as ImmutableRecord } from 'immutable'; +import { merge } from 'immutable'; import React, { FC, ReactElement } from 'react'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; @@ -17,8 +17,7 @@ import type { AppDispatch } from 'soapbox/store'; // Mock Redux // https://redux.js.org/recipes/writing-tests/ -const gg = rootReducer(undefined, {} as Action); -const rootState = gg as unknown as ImmutableRecord; +const rootState = rootReducer(undefined, {} as Action); const mockStore = configureMockStore([thunk]); /** Apply actions to the state, one at a time. */ @@ -29,7 +28,7 @@ const applyActions = (state: any, actions: any, reducer: any) => { const createTestStore = (initialState: any) => createStore(rootReducer, initialState, applyMiddleware(thunk)); const TestApp: FC = ({ children, storeProps, routerProps = {} }) => { - let store: any; + let store: ReturnType; let appState = rootState; if (storeProps) { diff --git a/app/soapbox/reducers/account_notes.ts b/app/soapbox/reducers/account_notes.ts index f5d753ee3..0f30b8d60 100644 --- a/app/soapbox/reducers/account_notes.ts +++ b/app/soapbox/reducers/account_notes.ts @@ -10,13 +10,13 @@ import { import type { AnyAction } from 'redux'; -const EditRecord = ImmutableRecord({ +export const EditRecord = ImmutableRecord({ isSubmitting: false, - account: null, + account: null as string | null, comment: '', }); -const ReducerRecord = ImmutableRecord({ +export const ReducerRecord = ImmutableRecord({ edit: EditRecord(), }); diff --git a/app/soapbox/reducers/user_lists.ts b/app/soapbox/reducers/user_lists.ts index 99483a854..fc86cceb2 100644 --- a/app/soapbox/reducers/user_lists.ts +++ b/app/soapbox/reducers/user_lists.ts @@ -54,7 +54,7 @@ import { import type { APIEntity } from 'soapbox/types/entities'; -const ListRecord = ImmutableRecord({ +export const ListRecord = ImmutableRecord({ next: null as string | null, items: ImmutableOrderedSet(), isLoading: false, @@ -72,7 +72,7 @@ const ReactionListRecord = ImmutableRecord({ isLoading: false, }); -const ReducerRecord = ImmutableRecord({ +export const ReducerRecord = ImmutableRecord({ followers: ImmutableMap(), following: ImmutableMap(), reblogged_by: ImmutableMap(), @@ -90,7 +90,7 @@ const ReducerRecord = ImmutableRecord({ }); type State = ReturnType; -type List = ReturnType; +export type List = ReturnType; type Reaction = ReturnType; type ReactionList = ReturnType; type Items = ImmutableOrderedSet; From afe1edddf9bf9dbc8e20009ad3bd12b29449b634 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Jul 2022 17:22:11 -0500 Subject: [PATCH 35/41] rootReducer(undefined, {} as any) --> rootState --- app/soapbox/actions/__tests__/accounts.test.ts | 4 ++-- app/soapbox/components/polls/__tests__/poll-footer.test.tsx | 4 ++-- .../features/ui/components/__tests__/compose-button.test.tsx | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/soapbox/actions/__tests__/accounts.test.ts b/app/soapbox/actions/__tests__/accounts.test.ts index 6ec79d68b..0793e36f7 100644 --- a/app/soapbox/actions/__tests__/accounts.test.ts +++ b/app/soapbox/actions/__tests__/accounts.test.ts @@ -1,7 +1,7 @@ import { Map as ImmutableMap } from 'immutable'; import { __stub } from 'soapbox/api'; -import { mockStore, rootState, rootReducer } from 'soapbox/jest/test-helpers'; +import { mockStore, rootState } from 'soapbox/jest/test-helpers'; import { ListRecord, ReducerRecord } from 'soapbox/reducers/user_lists'; import { normalizeAccount, normalizeInstance, normalizeRelationship } from '../../normalizers'; @@ -74,7 +74,7 @@ describe('fetchAccount()', () => { avatar: 'test.jpg', }); - const state = rootReducer(undefined, {} as any) + const state = rootState .set('accounts', ImmutableMap({ [id]: account, }) as any); diff --git a/app/soapbox/components/polls/__tests__/poll-footer.test.tsx b/app/soapbox/components/polls/__tests__/poll-footer.test.tsx index 9a403e339..29c841a0a 100644 --- a/app/soapbox/components/polls/__tests__/poll-footer.test.tsx +++ b/app/soapbox/components/polls/__tests__/poll-footer.test.tsx @@ -6,7 +6,7 @@ import { Provider } from 'react-redux'; import { __stub } from 'soapbox/api'; import { normalizePoll } from 'soapbox/normalizers/poll'; -import { mockStore, render, rootReducer, screen } from '../../../jest/test-helpers'; +import { mockStore, render, screen, rootState } from '../../../jest/test-helpers'; import PollFooter from '../poll-footer'; let poll = normalizePoll({ @@ -36,7 +36,7 @@ describe('', () => { }); const user = userEvent.setup(); - const store = mockStore(rootReducer(undefined, {} as any)); + const store = mockStore(rootState); render( diff --git a/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx b/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx index 5dec81b27..c98b7aa50 100644 --- a/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx @@ -5,12 +5,11 @@ import { Provider } from 'react-redux'; import '@testing-library/jest-dom'; import { MODAL_OPEN } from 'soapbox/actions/modals'; -import { mockStore } from 'soapbox/jest/test-helpers'; -import rootReducer from 'soapbox/reducers'; +import { mockStore, rootState } from 'soapbox/jest/test-helpers'; import ComposeButton from '../compose-button'; -const store = mockStore(rootReducer(undefined, {} as any)); +const store = mockStore(rootState); const renderComposeButton = () => { render( From 8bc8137231759844f373d59dfb1626cd81dfe809 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 8 Jul 2022 13:02:07 -0500 Subject: [PATCH 36/41] actions/auth: ignore the backend error message, always display "wrong username/password" --- app/soapbox/actions/auth.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/soapbox/actions/auth.ts b/app/soapbox/actions/auth.ts index 40532a582..e6ab20e9b 100644 --- a/app/soapbox/actions/auth.ts +++ b/app/soapbox/actions/auth.ts @@ -214,14 +214,6 @@ export const logIn = (username: string, password: string) => if ((error.response?.data as any).error === 'mfa_required') { // If MFA is required, throw the error and handle it in the component. throw error; - } else if ((error.response?.data as any).error === 'invalid_grant') { - // Mastodon returns this user-unfriendly error as a catch-all - // for everything from "bad request" to "wrong password". - // Assume our code is correct and it's a wrong password. - dispatch(snackbar.error(messages.invalidCredentials)); - } else if ((error.response?.data as any).error) { - // If the backend returns an error, display it. - dispatch(snackbar.error((error.response?.data as any).error)); } else { // Return "wrong password" message. dispatch(snackbar.error(messages.invalidCredentials)); From f37f4b9e6e4eec17a886b4b191ada77a31edddaf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 8 Jul 2022 13:50:38 -0500 Subject: [PATCH 37/41] Public layout header: fix weird error where the header can disappear --- app/soapbox/features/public_layout/components/header.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/public_layout/components/header.tsx b/app/soapbox/features/public_layout/components/header.tsx index bb952e406..6598bc397 100644 --- a/app/soapbox/features/public_layout/components/header.tsx +++ b/app/soapbox/features/public_layout/components/header.tsx @@ -8,7 +8,7 @@ import { fetchInstance } from 'soapbox/actions/instance'; import { openModal } from 'soapbox/actions/modals'; import SiteLogo from 'soapbox/components/site-logo'; import { Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui'; -import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppSelector, useFeatures, useSoapboxConfig, useOwnAccount } from 'soapbox/hooks'; import Sonar from './sonar'; @@ -27,6 +27,7 @@ const Header = () => { const dispatch = useDispatch(); const intl = useIntl(); + const account = useOwnAccount(); const soapboxConfig = useSoapboxConfig(); const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true; const { links } = soapboxConfig; @@ -67,7 +68,7 @@ const Header = () => { }); }; - if (shouldRedirect) return ; + if (account && shouldRedirect) return ; if (mfaToken) return ; return ( From 3fbc912dae764ba69827c968a6671e19fe3cd9a6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 8 Jul 2022 14:38:36 -0500 Subject: [PATCH 38/41] logIn: rethrow 403 for non-waitlisted account --- app/soapbox/actions/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/actions/auth.ts b/app/soapbox/actions/auth.ts index e6ab20e9b..5a5468e7f 100644 --- a/app/soapbox/actions/auth.ts +++ b/app/soapbox/actions/auth.ts @@ -181,7 +181,7 @@ export const verifyCredentials = (token: string, accountUrl?: string) => { } else { if (getState().me === null) dispatch(fetchMeFail(error)); dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error, skipAlert: true }); - return error; + throw error; } }); }; From da9eaf2b8279d8968a73e22c159a8bfcba6c6a0a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 8 Jul 2022 14:48:06 -0500 Subject: [PATCH 39/41] verifyCredentials: do alert on failure --- app/soapbox/actions/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/actions/auth.ts b/app/soapbox/actions/auth.ts index 5a5468e7f..e21974116 100644 --- a/app/soapbox/actions/auth.ts +++ b/app/soapbox/actions/auth.ts @@ -180,7 +180,7 @@ export const verifyCredentials = (token: string, accountUrl?: string) => { return account; } else { if (getState().me === null) dispatch(fetchMeFail(error)); - dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error, skipAlert: true }); + dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error }); throw error; } }); From 1b373fae9794f9a0f6e56803af955dfb5dd57060 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 8 Jul 2022 15:08:24 -0500 Subject: [PATCH 40/41] Fix registration test --- .../features/verification/__tests__/registration.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/verification/__tests__/registration.test.tsx b/app/soapbox/features/verification/__tests__/registration.test.tsx index f82c0dab4..fbde2fce3 100644 --- a/app/soapbox/features/verification/__tests__/registration.test.tsx +++ b/app/soapbox/features/verification/__tests__/registration.test.tsx @@ -18,7 +18,7 @@ describe('', () => { mock.onPost('/api/v1/pepe/accounts').reply(200, {}); mock.onPost('/api/v1/apps').reply(200, {}); mock.onPost('/oauth/token').reply(200, {}); - mock.onPost('/api/v1/accounts/verify_credentials').reply(200, {}); + mock.onGet('/api/v1/accounts/verify_credentials').reply(200, { id: '123' }); mock.onGet('/api/v1/instance').reply(200, {}); }); }); From 8be4bb74093c7e54fafa48df2ce7561b06dba45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 9 Jul 2022 00:15:26 +0200 Subject: [PATCH 41/41] Ensure attachment meta is not null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/normalizers/attachment.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/soapbox/normalizers/attachment.ts b/app/soapbox/normalizers/attachment.ts index 06fba73ad..23cce3686 100644 --- a/app/soapbox/normalizers/attachment.ts +++ b/app/soapbox/normalizers/attachment.ts @@ -46,8 +46,18 @@ const normalizeUrls = (attachment: ImmutableMap) => { return attachment.mergeWith(mergeDefined, base); }; +// Ensure meta is not null +const normalizeMeta = (attachment: ImmutableMap) => { + const meta = ImmutableMap().merge(attachment.get('meta')); + + return attachment.set('meta', meta); +}; + export const normalizeAttachment = (attachment: Record) => { return AttachmentRecord( - normalizeUrls(ImmutableMap(fromJS(attachment))), + ImmutableMap(fromJS(attachment)).withMutations((attachment: ImmutableMap) => { + normalizeUrls(attachment); + normalizeMeta(attachment); + }), ); };