about summary refs log tree commit diff
path: root/__e2e__/tests
diff options
context:
space:
mode:
Diffstat (limited to '__e2e__/tests')
-rw-r--r--__e2e__/tests/composer.test.ts108
-rw-r--r--__e2e__/tests/create-account.test.ts31
-rw-r--r--__e2e__/tests/home-screen.test.ts92
-rw-r--r--__e2e__/tests/login.test.ts19
-rw-r--r--__e2e__/tests/profile-screen.test.ts173
-rw-r--r--__e2e__/tests/search-screen.test.ts24
-rw-r--r--__e2e__/tests/shell.test.ts34
-rw-r--r--__e2e__/tests/thread-screen.test.ts123
8 files changed, 604 insertions, 0 deletions
diff --git a/__e2e__/tests/composer.test.ts b/__e2e__/tests/composer.test.ts
new file mode 100644
index 000000000..afc23cc13
--- /dev/null
+++ b/__e2e__/tests/composer.test.ts
@@ -0,0 +1,108 @@
+/* eslint-env detox/detox */
+
+import {openApp, login, createServer, sleep} from '../util'
+
+describe('Composer', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('?users')
+    await openApp({
+      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
+    })
+  })
+
+  it('Login', async () => {
+    await login(service, 'alice', 'hunter2')
+    await element(by.id('homeScreenFeedTabs-Following')).tap()
+  })
+
+  it('Post text only', async () => {
+    await element(by.id('composeFAB')).tap()
+    await device.takeScreenshot('1- opened composer')
+    await element(by.id('composerTextInput')).typeText('Post text only')
+    await device.takeScreenshot('2- entered text')
+    await element(by.id('composerPublishBtn')).tap()
+    await device.takeScreenshot('3- opened general section')
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('Post with an image', async () => {
+    await element(by.id('composeFAB')).tap()
+    await element(by.id('composerTextInput')).typeText('Post with an image')
+    await element(by.id('openGalleryBtn')).tap()
+    await sleep(1e3)
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('Post with a link card', async () => {
+    await element(by.id('composeFAB')).tap()
+    await element(by.id('composerTextInput')).typeText(
+      'Post with a https://example.com link card',
+    )
+    await element(by.id('addLinkCardBtn')).tap()
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('Reply text only', async () => {
+    const post = by.id('feedItem-by-alice.test')
+    await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('composerTextInput')).typeText('Reply text only')
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('Reply with an image', async () => {
+    const post = by.id('feedItem-by-alice.test')
+    await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('composerTextInput')).typeText('Reply with an image')
+    await element(by.id('openGalleryBtn')).tap()
+    await sleep(1e3)
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('Reply with a link card', async () => {
+    const post = by.id('feedItem-by-alice.test')
+    await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('composerTextInput')).typeText(
+      'Reply with a https://example.com link card',
+    )
+    await element(by.id('addLinkCardBtn')).tap()
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('QP text only', async () => {
+    const post = by.id('feedItem-by-alice.test')
+    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
+    await element(by.id('composerTextInput')).typeText('QP text only')
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('QP with an image', async () => {
+    const post = by.id('feedItem-by-alice.test')
+    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
+    await element(by.id('composerTextInput')).typeText('QP with an image')
+    await element(by.id('openGalleryBtn')).tap()
+    await sleep(1e3)
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+
+  it('QP with a link card', async () => {
+    const post = by.id('feedItem-by-alice.test')
+    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
+    await element(by.id('composerTextInput')).typeText(
+      'QP with a https://example.com link card',
+    )
+    await element(by.id('addLinkCardBtn')).tap()
+    await element(by.id('composerPublishBtn')).tap()
+    await expect(element(by.id('composeFAB'))).toBeVisible()
+  })
+})
diff --git a/__e2e__/tests/create-account.test.ts b/__e2e__/tests/create-account.test.ts
new file mode 100644
index 000000000..7b2e00fb5
--- /dev/null
+++ b/__e2e__/tests/create-account.test.ts
@@ -0,0 +1,31 @@
+/* eslint-env detox/detox */
+
+import {openApp, createServer} from '../util'
+
+describe('Create account', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('mock0')
+    await openApp({permissions: {notifications: 'YES'}})
+  })
+
+  it('I can create a new account', async () => {
+    await element(by.id('createAccountButton')).tap()
+    await device.takeScreenshot('1- opened create account screen')
+    await element(by.id('otherServerBtn')).tap()
+    await device.takeScreenshot('2- selected other server')
+    await element(by.id('customServerInput')).clearText()
+    await element(by.id('customServerInput')).typeText(service)
+    await device.takeScreenshot('3- input test server URL')
+    await element(by.id('nextBtn')).tap()
+    await element(by.id('emailInput')).typeText('example@test.com')
+    await element(by.id('passwordInput')).typeText('hunter2')
+    await element(by.id('is13Input')).tap()
+    await device.takeScreenshot('4- entered account details')
+    await element(by.id('nextBtn')).tap()
+    await element(by.id('handleInput')).typeText('e2e-test')
+    await device.takeScreenshot('4- entered handle')
+    await element(by.id('nextBtn')).tap()
+    await expect(element(by.id('homeScreen'))).toBeVisible()
+  })
+})
diff --git a/__e2e__/tests/home-screen.test.ts b/__e2e__/tests/home-screen.test.ts
new file mode 100644
index 000000000..1ec1774f3
--- /dev/null
+++ b/__e2e__/tests/home-screen.test.ts
@@ -0,0 +1,92 @@
+/* eslint-env detox/detox */
+
+import {openApp, login, createServer} from '../util'
+
+describe('Home screen', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('?users&follows&posts')
+    await openApp({permissions: {notifications: 'YES'}})
+  })
+
+  it('Login', async () => {
+    await login(service, 'alice', 'hunter2')
+    await element(by.id('homeScreenFeedTabs-Following')).tap()
+  })
+
+  it('Can like posts', async () => {
+    const carlaPosts = by.id('feedItem-by-carla.test')
+    await expect(
+      element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
+    ).toHaveText('0')
+    await element(by.id('likeBtn').withAncestor(carlaPosts)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
+    ).toHaveText('1')
+    await element(by.id('likeBtn').withAncestor(carlaPosts)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
+    ).toHaveText('0')
+  })
+
+  it('Can repost posts', async () => {
+    const carlaPosts = by.id('feedItem-by-carla.test')
+    await expect(
+      element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
+    ).toHaveText('0')
+    await element(by.id('repostBtn').withAncestor(carlaPosts)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
+    ).toHaveText('1')
+    await element(by.id('repostBtn').withAncestor(carlaPosts)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
+    ).toHaveText('0')
+  })
+
+  it('Can report posts', async () => {
+    const carlaPosts = by.id('feedItem-by-carla.test')
+    await element(by.id('postDropdownBtn').withAncestor(carlaPosts))
+      .atIndex(0)
+      .tap()
+    await element(by.id('postDropdownReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).toBeVisible()
+    await element(by.id('reportPostRadios-spam')).tap()
+    await element(by.id('sendReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).not.toBeVisible()
+  })
+
+  it('Can swipe between feeds', async () => {
+    await element(by.id('homeScreen')).swipe('left', 'fast', 0.75)
+    await expect(element(by.id('whatshotFeedPage'))).toBeVisible()
+    await element(by.id('homeScreen')).swipe('right', 'fast', 0.75)
+    await expect(element(by.id('followingFeedPage'))).toBeVisible()
+  })
+
+  it('Can tap between feeds', async () => {
+    await element(by.id("homeScreenFeedTabs-What's hot")).tap()
+    await expect(element(by.id('whatshotFeedPage'))).toBeVisible()
+    await element(by.id('homeScreenFeedTabs-Following')).tap()
+    await expect(element(by.id('followingFeedPage'))).toBeVisible()
+  })
+
+  it('Can delete posts', async () => {
+    const alicePosts = by.id('feedItem-by-alice.test')
+    await expect(element(alicePosts.withDescendant(by.text('Post')))).toExist()
+    await element(by.id('postDropdownBtn').withAncestor(alicePosts))
+      .atIndex(0)
+      .tap()
+    await element(by.id('postDropdownDeleteBtn')).tap()
+    await expect(element(by.id('confirmModal'))).toBeVisible()
+    await element(by.id('confirmBtn')).tap()
+    await expect(
+      element(alicePosts.withDescendant(by.text('Post'))),
+    ).not.toExist()
+  })
+})
diff --git a/__e2e__/tests/login.test.ts b/__e2e__/tests/login.test.ts
new file mode 100644
index 000000000..788016db6
--- /dev/null
+++ b/__e2e__/tests/login.test.ts
@@ -0,0 +1,19 @@
+/* eslint-env detox/detox */
+
+import {openApp, login, createServer} from '../util'
+
+describe('Login', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('?users')
+    await openApp({permissions: {notifications: 'YES'}})
+  })
+
+  it('As Alice, I can login', async () => {
+    await expect(element(by.id('signInButton'))).toBeVisible()
+    await login(service, 'alice', 'hunter2', {
+      takeScreenshots: true,
+    })
+    await device.takeScreenshot('5- opened home screen')
+  })
+})
diff --git a/__e2e__/tests/profile-screen.test.ts b/__e2e__/tests/profile-screen.test.ts
new file mode 100644
index 000000000..e1b6dcaf4
--- /dev/null
+++ b/__e2e__/tests/profile-screen.test.ts
@@ -0,0 +1,173 @@
+/* eslint-env detox/detox */
+
+import {openApp, login, createServer, sleep} from '../util'
+
+describe('Profile screen', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('?users&posts')
+    await openApp({
+      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
+    })
+  })
+
+  it('Login and navigate to my profile', async () => {
+    await expect(element(by.id('signInButton'))).toBeVisible()
+    await login(service, 'alice', 'hunter2')
+    await element(by.id('bottomBarProfileBtn')).tap()
+  })
+
+  it('Open and close edit profile modal', async () => {
+    await element(by.id('profileHeaderEditProfileButton')).tap()
+    await expect(element(by.id('editProfileModal'))).toBeVisible()
+    await element(by.id('editProfileCancelBtn')).tap()
+    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
+  })
+
+  it('Edit display name and description via the edit profile modal', async () => {
+    await element(by.id('profileHeaderEditProfileButton')).tap()
+    await expect(element(by.id('editProfileModal'))).toBeVisible()
+    await element(by.id('editProfileDisplayNameInput')).clearText()
+    await element(by.id('editProfileDisplayNameInput')).typeText('Alicia')
+    await element(by.id('editProfileDescriptionInput')).clearText()
+    await element(by.id('editProfileDescriptionInput')).typeText(
+      'One cool hacker',
+    )
+    await element(by.id('editProfileSaveBtn')).tap()
+    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
+    await expect(element(by.id('profileHeaderDisplayName'))).toHaveText(
+      'Alicia',
+    )
+    await expect(element(by.id('profileHeaderDescription'))).toHaveText(
+      'One cool hacker',
+    )
+  })
+
+  it('Remove display name and description via the edit profile modal', async () => {
+    await element(by.id('profileHeaderEditProfileButton')).tap()
+    await expect(element(by.id('editProfileModal'))).toBeVisible()
+    await element(by.id('editProfileDisplayNameInput')).clearText()
+    await element(by.id('editProfileDescriptionInput')).clearText()
+    await element(by.id('editProfileSaveBtn')).tap()
+    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
+    await expect(element(by.id('profileHeaderDisplayName'))).toHaveText(
+      'alice.test',
+    )
+    await expect(element(by.id('profileHeaderDescription'))).toHaveText('')
+  })
+
+  it('Set avi and banner via the edit profile modal', async () => {
+    await expect(element(by.id('userBannerFallback'))).toExist()
+    await expect(element(by.id('userAvatarFallback'))).toExist()
+    await element(by.id('profileHeaderEditProfileButton')).tap()
+    await expect(element(by.id('editProfileModal'))).toBeVisible()
+    await element(by.id('changeBannerBtn')).tap()
+    await element(by.id('changeBannerLibraryBtn')).tap()
+    await sleep(3e3)
+    await element(by.id('changeAvatarBtn')).tap()
+    await element(by.id('changeAvatarLibraryBtn')).tap()
+    await sleep(3e3)
+    await element(by.id('editProfileSaveBtn')).tap()
+    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
+    await expect(element(by.id('userBannerImage'))).toExist()
+    await expect(element(by.id('userAvatarImage'))).toExist()
+  })
+
+  it('Remove avi and banner via the edit profile modal', async () => {
+    await expect(element(by.id('userBannerImage'))).toExist()
+    await expect(element(by.id('userAvatarImage'))).toExist()
+    await element(by.id('profileHeaderEditProfileButton')).tap()
+    await expect(element(by.id('editProfileModal'))).toBeVisible()
+    await element(by.id('changeBannerBtn')).tap()
+    await element(by.id('changeBannerRemoveBtn')).tap()
+    await element(by.id('changeAvatarBtn')).tap()
+    await element(by.id('changeAvatarRemoveBtn')).tap()
+    await element(by.id('editProfileSaveBtn')).tap()
+    await expect(element(by.id('editProfileModal'))).not.toBeVisible()
+    await expect(element(by.id('userBannerFallback'))).toExist()
+    await expect(element(by.id('userAvatarFallback'))).toExist()
+  })
+
+  it('Navigate to another user profile', async () => {
+    await element(by.id('bottomBarSearchBtn')).tap()
+    // have to wait for the toast to clear
+    await waitFor(element(by.id('searchTextInput')))
+      .toBeVisible()
+      .withTimeout(2000)
+    await element(by.id('searchTextInput')).typeText('bob')
+    await element(by.id('searchAutoCompleteResult-bob.test')).tap()
+    await expect(element(by.id('profileView'))).toBeVisible()
+  })
+
+  it('Can follow/unfollow another user', async () => {
+    await element(by.id('followBtn')).tap()
+    await expect(element(by.id('unfollowBtn'))).toBeVisible()
+    await element(by.id('unfollowBtn')).tap()
+    await expect(element(by.id('followBtn'))).toBeVisible()
+  })
+
+  it('Can mute/unmute another user', async () => {
+    await expect(element(by.id('profileHeaderMutedNotice'))).not.toExist()
+    await element(by.id('profileHeaderDropdownBtn')).tap()
+    await element(by.id('profileHeaderDropdownMuteBtn')).tap()
+    await expect(element(by.id('profileHeaderMutedNotice'))).toBeVisible()
+    await element(by.id('profileHeaderDropdownBtn')).tap()
+    await element(by.id('profileHeaderDropdownMuteBtn')).tap()
+    await expect(element(by.id('profileHeaderMutedNotice'))).not.toExist()
+  })
+
+  it('Can report another user', async () => {
+    await element(by.id('profileHeaderDropdownBtn')).tap()
+    await element(by.id('profileHeaderDropdownReportBtn')).tap()
+    await expect(element(by.id('reportAccountModal'))).toBeVisible()
+    await element(by.id('reportAccountRadios-spam')).tap()
+    await element(by.id('sendReportBtn')).tap()
+    await expect(element(by.id('reportAccountModal'))).not.toBeVisible()
+  })
+
+  it('Can like posts', async () => {
+    const posts = by.id('feedItem-by-bob.test')
+    await expect(
+      element(by.id('likeCount').withAncestor(posts)).atIndex(0),
+    ).toHaveText('0')
+    await element(by.id('likeBtn').withAncestor(posts)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(posts)).atIndex(0),
+    ).toHaveText('1')
+    await element(by.id('likeBtn').withAncestor(posts)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(posts)).atIndex(0),
+    ).toHaveText('0')
+  })
+
+  it('Can repost posts', async () => {
+    const posts = by.id('feedItem-by-bob.test')
+    await expect(
+      element(by.id('repostCount').withAncestor(posts)).atIndex(0),
+    ).toHaveText('0')
+    await element(by.id('repostBtn').withAncestor(posts)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(posts)).atIndex(0),
+    ).toHaveText('1')
+    await element(by.id('repostBtn').withAncestor(posts)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(posts)).atIndex(0),
+    ).toHaveText('0')
+  })
+
+  it('Can report posts', async () => {
+    const posts = by.id('feedItem-by-bob.test')
+    await element(by.id('postDropdownBtn').withAncestor(posts)).atIndex(0).tap()
+    await element(by.id('postDropdownReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).toBeVisible()
+    await element(by.id('reportPostRadios-spam')).tap()
+    await element(by.id('sendReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).not.toBeVisible()
+  })
+})
diff --git a/__e2e__/tests/search-screen.test.ts b/__e2e__/tests/search-screen.test.ts
new file mode 100644
index 000000000..093d97c89
--- /dev/null
+++ b/__e2e__/tests/search-screen.test.ts
@@ -0,0 +1,24 @@
+/* eslint-env detox/detox */
+
+import {openApp, login, createServer} from '../util'
+
+describe('Search screen', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('?users')
+    await openApp({
+      permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
+    })
+  })
+
+  it('Login', async () => {
+    await login(service, 'alice', 'hunter2')
+  })
+
+  it('Navigate to another user profile via autocomplete', async () => {
+    await element(by.id('bottomBarSearchBtn')).tap()
+    await element(by.id('searchTextInput')).typeText('bob')
+    await element(by.id('searchAutoCompleteResult-bob.test')).tap()
+    await expect(element(by.id('profileView'))).toBeVisible()
+  })
+})
diff --git a/__e2e__/tests/shell.test.ts b/__e2e__/tests/shell.test.ts
new file mode 100644
index 000000000..5cfd4277f
--- /dev/null
+++ b/__e2e__/tests/shell.test.ts
@@ -0,0 +1,34 @@
+/* eslint-env detox/detox */
+
+import {openApp, login, createServer} from '../util'
+
+describe('Shell', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('?users')
+    await openApp({permissions: {notifications: 'YES'}})
+  })
+
+  it('Login', async () => {
+    await login(service, 'alice', 'hunter2')
+    await element(by.id('homeScreenFeedTabs-Following')).tap()
+  })
+
+  it('Can swipe the shelf open', async () => {
+    await element(by.id('homeScreen')).swipe('right', 'fast', 0.75)
+    await expect(element(by.id('drawer'))).toBeVisible()
+    await element(by.id('drawer')).swipe('left', 'fast', 0.75)
+    await expect(element(by.id('drawer'))).not.toBeVisible()
+  })
+
+  it('Can open the shelf by pressing the header avi', async () => {
+    await element(by.id('viewHeaderDrawerBtn')).tap()
+    await expect(element(by.id('drawer'))).toBeVisible()
+  })
+
+  it('Can navigate using the shelf', async () => {
+    await element(by.id('menuItemButton-Notifications')).tap()
+    await expect(element(by.id('drawer'))).not.toBeVisible()
+    await expect(element(by.id('notificationsScreen'))).toBeVisible()
+  })
+})
diff --git a/__e2e__/tests/thread-screen.test.ts b/__e2e__/tests/thread-screen.test.ts
new file mode 100644
index 000000000..f84c339ce
--- /dev/null
+++ b/__e2e__/tests/thread-screen.test.ts
@@ -0,0 +1,123 @@
+/* eslint-env detox/detox */
+
+import {openApp, login, createServer} from '../util'
+
+describe('Thread screen', () => {
+  let service: string
+  beforeAll(async () => {
+    service = await createServer('?users&follows&thread')
+    await openApp({permissions: {notifications: 'YES'}})
+  })
+
+  it('Login & navigate to thread', async () => {
+    await login(service, 'alice', 'hunter2')
+    await element(by.id('homeScreenFeedTabs-Following')).tap()
+    await element(by.id('feedItem-by-bob.test')).atIndex(0).tap()
+    await expect(
+      element(
+        by
+          .id('postThreadItem-by-bob.test')
+          .withDescendant(by.text('Thread root')),
+      ),
+    ).toBeVisible()
+    await expect(
+      element(
+        by
+          .id('postThreadItem-by-carla.test')
+          .withDescendant(by.text('Thread reply')),
+      ),
+    ).toBeVisible()
+  })
+
+  it('Can like the root post', async () => {
+    const post = by.id('postThreadItem-by-bob.test')
+    await expect(
+      element(by.id('likeCount').withAncestor(post)).atIndex(0),
+    ).not.toExist()
+    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('1 like')
+    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(post)).atIndex(0),
+    ).not.toExist()
+  })
+
+  it('Can like a reply post', async () => {
+    const post = by.id('postThreadItem-by-carla.test')
+    await expect(
+      element(by.id('likeCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('0')
+    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('1')
+    await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(
+      element(by.id('likeCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('0')
+  })
+
+  it('Can repost the root post', async () => {
+    const post = by.id('postThreadItem-by-bob.test')
+    await expect(
+      element(by.id('repostCount').withAncestor(post)).atIndex(0),
+    ).not.toExist()
+    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('1 repost')
+    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(post)).atIndex(0),
+    ).not.toExist()
+  })
+
+  it('Can repost a reply post', async () => {
+    const post = by.id('postThreadItem-by-carla.test')
+    await expect(
+      element(by.id('repostCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('0')
+    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('1')
+    await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
+    await expect(element(by.id('repostModal'))).toBeVisible()
+    await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
+    await expect(element(by.id('repostModal'))).not.toBeVisible()
+    await expect(
+      element(by.id('repostCount').withAncestor(post)).atIndex(0),
+    ).toHaveText('0')
+  })
+
+  it('Can report the root post', async () => {
+    const post = by.id('postThreadItem-by-bob.test')
+    await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('postDropdownReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).toBeVisible()
+    await element(by.id('reportPostRadios-spam')).tap()
+    await element(by.id('sendReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).not.toBeVisible()
+  })
+
+  it('Can report a reply post', async () => {
+    const post = by.id('postThreadItem-by-carla.test')
+    await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
+    await element(by.id('postDropdownReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).toBeVisible()
+    await element(by.id('reportPostRadios-spam')).tap()
+    await element(by.id('sendReportBtn')).tap()
+    await expect(element(by.id('reportPostModal'))).not.toBeVisible()
+  })
+})