about summary refs log tree commit diff
path: root/__tests__
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-01-24 09:06:27 -0600
committerGitHub <noreply@github.com>2023-01-24 09:06:27 -0600
commit9027882fb401df2a9df6a89facb2bdb94b8b731b (patch)
treedc60ca1a2cc1be0838229f06b588f56871f2b91e /__tests__
parent439305b57e0c20799d87baf92c067ec8e262ea13 (diff)
downloadvoidsky-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.tsx241
-rw-r--r--__tests__/view/com/login/Signin.test.tsx126
-rw-r--r--__tests__/view/shell/mobile/Menu.test.tsx6
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)