diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-01-24 09:06:27 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-24 09:06:27 -0600 |
commit | 9027882fb401df2a9df6a89facb2bdb94b8b731b (patch) | |
tree | dc60ca1a2cc1be0838229f06b588f56871f2b91e /__tests__ | |
parent | 439305b57e0c20799d87baf92c067ec8e262ea13 (diff) | |
download | voidsky-9027882fb401df2a9df6a89facb2bdb94b8b731b.tar.zst |
Account switcher (#85)
* Update the account-create and signin views to use the design system. Also: - Add borderDark to the theme - Start to an account selector in the signin flow * Dark mode fixes in signin ui * Track multiple active accounts and provide account-switching UI * Add test tooling for an in-memory pds * Add complete integration tests for login and the account switcher
Diffstat (limited to '__tests__')
-rw-r--r-- | __tests__/accounts.test.tsx | 241 | ||||
-rw-r--r-- | __tests__/view/com/login/Signin.test.tsx | 126 | ||||
-rw-r--r-- | __tests__/view/shell/mobile/Menu.test.tsx | 6 |
3 files changed, 244 insertions, 129 deletions
diff --git a/__tests__/accounts.test.tsx b/__tests__/accounts.test.tsx new file mode 100644 index 000000000..f3ecb6af4 --- /dev/null +++ b/__tests__/accounts.test.tsx @@ -0,0 +1,241 @@ +import React from 'react' +import {MobileShell} from '../src/view/shell/mobile' +import {cleanup, fireEvent, render, waitFor} from '../jest/test-utils' +import {createServer, TestPDS} from '../jest/test-pds' +import {RootStoreModel, setupState} from '../src/state' + +const WAIT_OPTS = {timeout: 5e3} + +describe('Account flows', () => { + let pds: TestPDS | undefined + let rootStore: RootStoreModel | undefined + beforeAll(async () => { + jest.useFakeTimers() + pds = await createServer() + rootStore = await setupState(pds.pdsUrl) + }) + + afterAll(async () => { + jest.clearAllMocks() + cleanup() + await pds?.close() + }) + + it('renders initial screen', () => { + const {getByTestId} = render(<MobileShell />, rootStore) + const signUpScreen = getByTestId('signinOrCreateAccount') + + expect(signUpScreen).toBeTruthy() + }) + + it('completes signin to the server', async () => { + const {getByTestId} = render(<MobileShell />, rootStore) + + // move to signin view + fireEvent.press(getByTestId('signInButton')) + expect(getByTestId('signIn')).toBeTruthy() + expect(getByTestId('loginForm')).toBeTruthy() + + // input the target server + expect(getByTestId('loginSelectServiceButton')).toBeTruthy() + fireEvent.press(getByTestId('loginSelectServiceButton')) + expect(getByTestId('serverInputModal')).toBeTruthy() + fireEvent.changeText( + getByTestId('customServerTextInput'), + pds?.pdsUrl || '', + ) + fireEvent.press(getByTestId('customServerSelectBtn')) + await waitFor(() => { + expect(getByTestId('loginUsernameInput')).toBeTruthy() + }, WAIT_OPTS) + + // enter username & pass + fireEvent.changeText(getByTestId('loginUsernameInput'), 'alice') + fireEvent.changeText(getByTestId('loginPasswordInput'), 'hunter2') + await waitFor(() => { + expect(getByTestId('loginNextButton')).toBeTruthy() + }, WAIT_OPTS) + fireEvent.press(getByTestId('loginNextButton')) + + // signed in + await waitFor(() => { + expect(getByTestId('homeFeed')).toBeTruthy() + expect(rootStore?.me?.displayName).toBe('Alice') + expect(rootStore?.me?.handle).toBe('alice.test') + expect(rootStore?.session.accounts.length).toBe(1) + }, WAIT_OPTS) + expect(rootStore?.me?.displayName).toBe('Alice') + expect(rootStore?.me?.handle).toBe('alice.test') + expect(rootStore?.session.accounts.length).toBe(1) + }) + + it('opens the login screen when "add account" is pressed', async () => { + const {getByTestId, getAllByTestId} = render(<MobileShell />, rootStore) + await waitFor(() => expect(getByTestId('homeFeed')).toBeTruthy(), WAIT_OPTS) + + // open side menu + fireEvent.press(getAllByTestId('viewHeaderBackOrMenuBtn')[0]) + await waitFor(() => expect(getByTestId('menuView')).toBeTruthy(), WAIT_OPTS) + + // nav to settings + fireEvent.press(getByTestId('menuItemButton-Settings')) + await waitFor( + () => expect(getByTestId('settingsScreen')).toBeTruthy(), + WAIT_OPTS, + ) + + // press '+ new account' in switcher + fireEvent.press(getByTestId('switchToNewAccountBtn')) + await waitFor( + () => expect(getByTestId('signinOrCreateAccount')).toBeTruthy(), + WAIT_OPTS, + ) + }) + + it('shows the "choose account" form when a previous session has been created', async () => { + const {getByTestId} = render(<MobileShell />, rootStore) + + // move to signin view + fireEvent.press(getByTestId('signInButton')) + expect(getByTestId('signIn')).toBeTruthy() + expect(getByTestId('chooseAccountForm')).toBeTruthy() + }) + + it('logs directly into the account due to still possessing session tokens', async () => { + const {getByTestId} = render(<MobileShell />, rootStore) + + // move to signin view + fireEvent.press(getByTestId('signInButton')) + expect(getByTestId('signIn')).toBeTruthy() + expect(getByTestId('chooseAccountForm')).toBeTruthy() + + // select the previous account + fireEvent.press(getByTestId('chooseAccountBtn-alice.test')) + + // signs in immediately + await waitFor(() => { + expect(getByTestId('homeFeed')).toBeTruthy() + expect(rootStore?.me?.displayName).toBe('Alice') + expect(rootStore?.me?.handle).toBe('alice.test') + expect(rootStore?.session.accounts.length).toBe(1) + }, WAIT_OPTS) + expect(rootStore?.me?.displayName).toBe('Alice') + expect(rootStore?.me?.handle).toBe('alice.test') + expect(rootStore?.session.accounts.length).toBe(1) + }) + + it('logs into a second account via the switcher', async () => { + const {getByTestId, getAllByTestId} = render(<MobileShell />, rootStore) + await waitFor(() => expect(getByTestId('homeFeed')).toBeTruthy(), WAIT_OPTS) + + // open side menu + fireEvent.press(getAllByTestId('viewHeaderBackOrMenuBtn')[0]) + await waitFor(() => expect(getByTestId('menuView')).toBeTruthy(), WAIT_OPTS) + + // nav to settings + fireEvent.press(getByTestId('menuItemButton-Settings')) + await waitFor( + () => expect(getByTestId('settingsScreen')).toBeTruthy(), + WAIT_OPTS, + ) + + // press '+ new account' in switcher + fireEvent.press(getByTestId('switchToNewAccountBtn')) + await waitFor( + () => expect(getByTestId('signinOrCreateAccount')).toBeTruthy(), + WAIT_OPTS, + ) + + // move to signin view + fireEvent.press(getByTestId('signInButton')) + expect(getByTestId('signIn')).toBeTruthy() + expect(getByTestId('chooseAccountForm')).toBeTruthy() + + // select a new account + fireEvent.press(getByTestId('chooseNewAccountBtn')) + expect(getByTestId('loginForm')).toBeTruthy() + + // input the target server + expect(getByTestId('loginSelectServiceButton')).toBeTruthy() + fireEvent.press(getByTestId('loginSelectServiceButton')) + expect(getByTestId('serverInputModal')).toBeTruthy() + fireEvent.changeText( + getByTestId('customServerTextInput'), + pds?.pdsUrl || '', + ) + fireEvent.press(getByTestId('customServerSelectBtn')) + await waitFor( + () => expect(getByTestId('loginUsernameInput')).toBeTruthy(), + WAIT_OPTS, + ) + + // enter username & pass + fireEvent.changeText(getByTestId('loginUsernameInput'), 'bob') + fireEvent.changeText(getByTestId('loginPasswordInput'), 'hunter2') + await waitFor( + () => expect(getByTestId('loginNextButton')).toBeTruthy(), + WAIT_OPTS, + ) + fireEvent.press(getByTestId('loginNextButton')) + + // signed in + await waitFor(() => { + expect(getByTestId('settingsScreen')).toBeTruthy() // we go back to settings in this situation + expect(rootStore?.me?.displayName).toBe('Bob') + expect(rootStore?.me?.handle).toBe('bob.test') + expect(rootStore?.session.accounts.length).toBe(2) + }, WAIT_OPTS) + expect(rootStore?.me?.displayName).toBe('Bob') + expect(rootStore?.me?.handle).toBe('bob.test') + expect(rootStore?.session.accounts.length).toBe(2) + }) + + it('can instantly switch between accounts', async () => { + const {getByTestId} = render(<MobileShell />, rootStore) + await waitFor( + () => expect(getByTestId('settingsScreen')).toBeTruthy(), + WAIT_OPTS, + ) + + // select the alice account + fireEvent.press(getByTestId('switchToAccountBtn-alice.test')) + + // swapped account + await waitFor(() => { + expect(rootStore?.me?.displayName).toBe('Alice') + expect(rootStore?.me?.handle).toBe('alice.test') + expect(rootStore?.session.accounts.length).toBe(2) + }, WAIT_OPTS) + expect(rootStore?.me?.displayName).toBe('Alice') + expect(rootStore?.me?.handle).toBe('alice.test') + expect(rootStore?.session.accounts.length).toBe(2) + }) + + it('will prompt for a password if you sign out', async () => { + const {getByTestId} = render(<MobileShell />, rootStore) + await waitFor( + () => expect(getByTestId('settingsScreen')).toBeTruthy(), + WAIT_OPTS, + ) + + // press the sign out button + fireEvent.press(getByTestId('signOutBtn')) + + // in the logged out state + await waitFor( + () => expect(getByTestId('signinOrCreateAccount')).toBeTruthy(), + WAIT_OPTS, + ) + + // move to signin view + fireEvent.press(getByTestId('signInButton')) + expect(getByTestId('signIn')).toBeTruthy() + expect(getByTestId('chooseAccountForm')).toBeTruthy() + + // select an existing account + fireEvent.press(getByTestId('chooseAccountBtn-alice.test')) + + // goes to login screen instead of straight back to settings + expect(getByTestId('loginForm')).toBeTruthy() + }) +}) diff --git a/__tests__/view/com/login/Signin.test.tsx b/__tests__/view/com/login/Signin.test.tsx deleted file mode 100644 index e5b6bdbc6..000000000 --- a/__tests__/view/com/login/Signin.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react' -import {Signin} from '../../../../src/view/com/login/Signin' -import {cleanup, fireEvent, render} from '../../../../jest/test-utils' -import {SessionServiceClient, sessionClient as AtpApi} from '@atproto/api' -import { - mockedSessionStore, - mockedShellStore, -} from '../../../../__mocks__/state-mock' -import {Keyboard} from 'react-native' - -describe('Signin', () => { - const requestPasswordResetMock = jest.fn() - const resetPasswordMock = jest.fn() - jest.spyOn(AtpApi, 'service').mockReturnValue({ - com: { - atproto: { - account: { - requestPasswordReset: requestPasswordResetMock, - resetPassword: resetPasswordMock, - }, - }, - }, - } as unknown as SessionServiceClient) - const mockedProps = { - onPressBack: jest.fn(), - } - afterAll(() => { - jest.clearAllMocks() - cleanup() - }) - - it('renders logs in form', async () => { - const {findByTestId} = render(<Signin {...mockedProps} />) - - const loginFormView = await findByTestId('loginFormView') - expect(loginFormView).toBeTruthy() - - const loginUsernameInput = await findByTestId('loginUsernameInput') - expect(loginUsernameInput).toBeTruthy() - - fireEvent.changeText(loginUsernameInput, 'testusername') - - const loginPasswordInput = await findByTestId('loginPasswordInput') - expect(loginPasswordInput).toBeTruthy() - - fireEvent.changeText(loginPasswordInput, 'test pass') - - const loginNextButton = await findByTestId('loginNextButton') - expect(loginNextButton).toBeTruthy() - - fireEvent.press(loginNextButton) - - expect(mockedSessionStore.login).toHaveBeenCalled() - }) - - it('renders selects service from login form', async () => { - const keyboardSpy = jest.spyOn(Keyboard, 'dismiss') - const {findByTestId} = render(<Signin {...mockedProps} />) - - const loginSelectServiceButton = await findByTestId( - 'loginSelectServiceButton', - ) - expect(loginSelectServiceButton).toBeTruthy() - - fireEvent.press(loginSelectServiceButton) - - expect(mockedShellStore.openModal).toHaveBeenCalled() - expect(keyboardSpy).toHaveBeenCalled() - }) - - it('renders new password form', async () => { - const {findByTestId} = render(<Signin {...mockedProps} />) - - const forgotPasswordButton = await findByTestId('forgotPasswordButton') - expect(forgotPasswordButton).toBeTruthy() - - fireEvent.press(forgotPasswordButton) - const forgotPasswordView = await findByTestId('forgotPasswordView') - expect(forgotPasswordView).toBeTruthy() - - const forgotPasswordEmail = await findByTestId('forgotPasswordEmail') - expect(forgotPasswordEmail).toBeTruthy() - fireEvent.changeText(forgotPasswordEmail, 'test@email.com') - - const newPasswordButton = await findByTestId('newPasswordButton') - expect(newPasswordButton).toBeTruthy() - fireEvent.press(newPasswordButton) - - expect(requestPasswordResetMock).toHaveBeenCalled() - - const newPasswordView = await findByTestId('newPasswordView') - expect(newPasswordView).toBeTruthy() - - const newPasswordInput = await findByTestId('newPasswordInput') - expect(newPasswordInput).toBeTruthy() - const resetCodeInput = await findByTestId('resetCodeInput') - expect(resetCodeInput).toBeTruthy() - - fireEvent.changeText(newPasswordInput, 'test pass') - fireEvent.changeText(resetCodeInput, 'test reset code') - - const setNewPasswordButton = await findByTestId('setNewPasswordButton') - expect(setNewPasswordButton).toBeTruthy() - - fireEvent.press(setNewPasswordButton) - - expect(resetPasswordMock).toHaveBeenCalled() - }) - - it('renders forgot password form', async () => { - const {findByTestId} = render(<Signin {...mockedProps} />) - - const forgotPasswordButton = await findByTestId('forgotPasswordButton') - expect(forgotPasswordButton).toBeTruthy() - - fireEvent.press(forgotPasswordButton) - const forgotPasswordSelectServiceButton = await findByTestId( - 'forgotPasswordSelectServiceButton', - ) - expect(forgotPasswordSelectServiceButton).toBeTruthy() - - fireEvent.press(forgotPasswordSelectServiceButton) - - expect(mockedShellStore.openModal).toHaveBeenCalled() - }) -}) diff --git a/__tests__/view/shell/mobile/Menu.test.tsx b/__tests__/view/shell/mobile/Menu.test.tsx index 0bffaff7f..313259041 100644 --- a/__tests__/view/shell/mobile/Menu.test.tsx +++ b/__tests__/view/shell/mobile/Menu.test.tsx @@ -46,10 +46,10 @@ describe('Menu', () => { }) it("presses notifications menu item' button", () => { - const {getAllByTestId} = render(<Menu {...mockedProps} />) + const {getByTestId} = render(<Menu {...mockedProps} />) - const menuItemButton = getAllByTestId('menuItemButton') - fireEvent.press(menuItemButton[1]) + const menuItemButton = getByTestId('menuItemButton-Notifications') + fireEvent.press(menuItemButton) expect(onCloseMock).toHaveBeenCalled() expect(mockedNavigationStore.switchTo).toHaveBeenCalledWith(1, true) |