Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 174 additions & 25 deletions __tests__/pages/settings/account/index.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
jest.mock('@sentry/browser')

import React from 'react'
import { render, screen } from '@testing-library/react'
import {
render,
screen,
waitForElementToBeRemoved
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import AccountSettings from '../../../../pages/settings/account/index'
import { MockedProvider } from '@apollo/client/testing'
import GetApp from '../../../../graphql/queries/getApp.ts'
import USER_INFO from '../../../../graphql/queries/userInfo'
import UPDATE_USER_NAMES from '../../../../graphql/queries/updateUserNames'
import UPDATE_USER_PASSWORD from '../../../../graphql/queries/updateUserPassword'
import dummyLessonData from '../../../../__dummy__/lessonData'
import dummySessionData from '../../../../__dummy__/sessionData'
import dummyAlertData from '../../../../__dummy__/alertData'
import dummyStarsData from '../../../../__dummy__/starsData'
import { signIn, useSession } from 'next-auth/react'
import * as Sentry from '@sentry/browser'

// Imported to be able to use expect(...).toBeInTheDocument()
import '@testing-library/jest-dom'
import { UNLINK_DISCORD } from '../../../../graphql/queries/unlinkDiscord'

const UpdateUserNamesMock = {
request: {
Expand Down Expand Up @@ -112,14 +118,92 @@ const UpdateUserPasswordMockWithError = {
})
}

const unlinkDiscordMock = {
request: {
query: UNLINK_DISCORD
},
result: jest.fn(() => ({
data: {
unlinkDiscord: {
id: 1
}
}
}))
}

const unlinkDiscordMockWithError = {
request: {
query: UNLINK_DISCORD
},
newData: jest.fn(() => {
throw new Error('Error')
})
}

const session = {
...dummySessionData,
lessonStatus: [
{
id: 1,
userId: '1',
lessonId: 5,
passedAt: `'true'`,
starGiven: null,
starsReceived: [dummyStarsData[0]]
},
{
id: 2,
userId: '1',
lessonId: 2,
passedAt: Date.now().toString(),
starGiven: null,
starsReceived: [dummyStarsData[1]]
},
{
id: 3,
userId: '1',
lessonId: 1,
passedAt: Date.now().toString(),
starGiven: null,
starsReceived: [dummyStarsData[2], dummyStarsData[9]]
}
]
}

const UserInfoMock = {
request: {
query: USER_INFO,
variables: {
username: 'fakeusername'
}
},
result: {
data: {
lessons: dummyLessonData,
userInfo: session
}
}
}

const mocks = [
unlinkDiscordMock,
UserInfoMock,
UpdateUserNamesMock,
UpdateUserPasswordMock
]

const mocksWithDiscordData = [
unlinkDiscordMock,
{
request: { query: GetApp },
...UserInfoMock,
result: {
...UserInfoMock.result,
data: {
session: dummySessionData,
lessons: dummyLessonData,
alerts: dummyAlertData
...UserInfoMock.result.data,
userInfo: {
...session,
user: { ...session.user, discordUsername: 'floppityflob' }
}
}
}
},
Expand All @@ -128,33 +212,26 @@ const mocks = [
]

const mocksWithError = [
unlinkDiscordMockWithError,
{
request: { query: GetApp },
...UserInfoMock,
result: {
...UserInfoMock.result,
data: {
session: {
...dummySessionData,
user: { ...dummySessionData.user, name: null }
},
lessons: dummyLessonData,
alerts: dummyAlertData
...UserInfoMock.result.data,
userInfo: {
...session,
user: { ...session.user, name: null, discordUsername: 'floppityflob' }
}
}
}
},
UpdateUserNamesMockWithError
]

const updateUserPasswordMocksWithError = [
{
request: { query: GetApp },
result: {
data: {
session: dummySessionData,
lessons: dummyLessonData,
alerts: dummyAlertData
}
}
},
unlinkDiscordMock,
UserInfoMock,
UpdateUserPasswordMockWithError
]

Expand Down Expand Up @@ -327,7 +404,7 @@ describe('Account settings page', () => {
})
})

describe('Password settings', () => {
describe('Password setting', () => {
it('Should update inputs and submit', async () => {
expect.assertions(3)

Expand Down Expand Up @@ -457,4 +534,76 @@ describe('Account settings page', () => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})
})

describe('Linked account setting', () => {
it('Should start linking Discord flow', async () => {
expect.assertions(1)

render(
<MockedProvider mocks={mocks} addTypename={false}>
<AccountSettings />
</MockedProvider>
)

await waitForElementToBeRemoved(() => screen.queryByText('Loading...'))

const connectBtn = await screen.findByTestId('connect-to-discord')

await userEvent.click(connectBtn)

expect(signIn).toBeCalled()
})

it('Should start unlinking Discord flow', async () => {
expect.assertions(1)

render(
<MockedProvider mocks={mocksWithDiscordData} addTypename={false}>
<AccountSettings />
</MockedProvider>
)

await waitForElementToBeRemoved(() => screen.queryByText('Loading...'))

const unlinkButton = await screen.findByTestId('unlink-discord')

await userEvent.click(unlinkButton)

expect(unlinkDiscordMock.result).toBeCalled()
})

it('Should capture exception when unlinking Discord', async () => {
expect.assertions(1)

render(
<MockedProvider mocks={mocksWithError} addTypename={false}>
<AccountSettings />
</MockedProvider>
)

await waitForElementToBeRemoved(() => screen.queryByText('Loading...'))

const unlinkButton = await screen.findByTestId('unlink-discord')

await userEvent.click(unlinkButton)

expect(Sentry.captureException).toBeCalled()
})

it('Should not call userInfo query if session username is missing', async () => {
expect.assertions(1)

useSession.mockReturnValue({
status: 'unauthenticated'
})

render(
<MockedProvider mocks={mocks} addTypename={false}>
<AccountSettings />
</MockedProvider>
)

expect(await screen.findByText('Loading...')).toBeInTheDocument()
})
})
})
97 changes: 93 additions & 4 deletions pages/settings/account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { FormCard, Option } from '../../../components/FormCard'
import { formChange } from '../../../helpers/formChange'
import {
GetAppQuery,
useGetAppQuery,
useUnlinkDiscordMutation,
useUpdateUserNamesMutation,
useUpdateUserPasswordMutation
useUpdateUserPasswordMutation,
useUserInfoQuery
} from '../../../graphql/index'
import LoadingSpinner from '../../../components/LoadingSpinner'
import styles from '../../../scss/accountSettings.module.scss'
Expand All @@ -17,6 +18,11 @@ import {
} from '../../../helpers/formValidation'
import QueryInfo from '../../../components/QueryInfo'
import * as Sentry from '@sentry/browser'
import { Button } from '../../../components/theme/Button'
import Image from 'next/image'
import { signIn, useSession } from 'next-auth/react'
import { SessionContext } from '../../../@types/auth'
import { useRouter } from 'next/router'

const basicValues: (
username?: string,
Expand Down Expand Up @@ -180,9 +186,90 @@ const PasswordSettings = () => {
)
}

const LinkedAccountsSetting = ({
discordUsername
}: {
discordUsername: string | undefined
}) => {
const router = useRouter()
const [unlinkDiscordMutation, { data, loading, error }] =
useUnlinkDiscordMutation()

const unlinkDiscord = async () => {
try {
await unlinkDiscordMutation()
router.reload()
} catch (error) {
Sentry.captureException(error)
}
}

return (
<div>
<h3 className="mb-4">Linked accounts</h3>
<QueryInfo
loading={loading}
data={data}
error={error?.message || ''}
texts={{
data: 'Unlinked Discord successfully',
loading: 'Unlinking Discord...',
error: 'Oops, we could not Unlink Discord. Please try again'
}}
/>
<div className={styles.linkedAccounts__container}>
<div className={styles.linkedAccounts__container__item}>
<div className={styles.linkedAccounts__container__item__icon}>
<div>
<Image
src="/assets/discordClydeLogo.svg"
height={17}
width={22}
/>
</div>
<span className="fs-5">Discord</span>
</div>
{discordUsername ? (
<div className={styles.linkedAccounts__container__item__icon}>
<span>{discordUsername}</span>
<Button
data-testid="unlink-discord"
btnType="danger"
color="danger"
outline
onClick={unlinkDiscord}
>
Unlink Discord
</Button>
</div>
) : (
<Button
data-testid="connect-to-discord"
btnType="primary"
color="white"
onClick={() =>
signIn('discord', { callbackUrl: '/discord/success' })
}
>
Connect to discord
</Button>
)}
</div>
</div>
</div>
)
}

const AccountSettings = () => {
const { data, loading } = useGetAppQuery()
const { username, name } = data?.session.user || {}
const { data: session, status } = useSession() as SessionContext
const { data, loading: userInfoLoading } = useUserInfoQuery({
variables: {
username: session?.user.username || ''
}
})

const loading = userInfoLoading || status === 'loading'
const { username, name, discordUsername } = data?.userInfo?.user || {}

if (loading || !data) {
return <LoadingSpinner />
Expand All @@ -198,6 +285,8 @@ const AccountSettings = () => {
<BasicSettings username={username} name={name} />
<hr className="mt-4 mb-4" />
<PasswordSettings />
<hr className="mt-4 mb-4" />
<LinkedAccountsSetting discordUsername={discordUsername} />
</div>
</div>
</div>
Expand Down
Loading