* Add logged out e2e ctrl, fix login test

* Fix log handling via env vars in expo

* Fix create account test

* Upgrade dev-env

* Fix home screen tests

* Fix composer tests

* Fix curate-lists tests, split in two

* Fix invite codes test

* Fix curate-lists tests

* Give up on mergefeed test

* Fix mod lists

* Fix app view url

* Fix profile tests

* Fix profile test with hack

* Keep using globals

* Fix two more

* Fix thread view

* Better skip for merge feed

* Revert debug code
zio/stable
Eric Bailey 2023-12-05 14:50:56 -06:00 committed by GitHub
parent ed5a97d0fa
commit 5f553c29df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1600 additions and 234 deletions

View File

@ -1,3 +1,6 @@
# Copy this to `.env` and `.env.test` files
SENTRY_AUTH_TOKEN=
EXPO_PUBLIC_ENV=development
EXPO_PUBLIC_LOG_LEVEL=debug
EXPO_PUBLIC_LOG_DEBUG=

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer, sleep} from '../util'
describe('Composer', () => {
@ -45,6 +47,8 @@ describe('Composer', () => {
})
it('Reply text only', async () => {
await element(by.id('e2eRefreshHome')).tap()
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')

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, createServer} from '../util'
describe('Create account', () => {
@ -10,6 +12,8 @@ describe('Create account', () => {
})
it('I can create a new account', async () => {
await element(by.id('e2eOpenLoggedOutView')).tap()
await element(by.id('createAccountButton')).tap()
await device.takeScreenshot('1- opened create account screen')
await element(by.id('otherServerBtn')).tap()
@ -17,14 +21,20 @@ describe('Create account', () => {
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 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('welcomeOnboarding'))).toBeVisible()
await element(by.id('continueBtn')).tap()
await expect(element(by.id('recommendedFeedsOnboarding'))).toBeVisible()

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, loginAsBob, createServer, sleep} from '../util'
describe('Curate lists', () => {
@ -11,7 +13,6 @@ describe('Curate lists', () => {
})
it('Login and create a curatelists', async () => {
await expect(element(by.id('signInButton'))).toBeVisible()
await loginAsAlice()
await element(by.id('e2eGotoLists')).tap()
await element(by.id('newUserListBtn')).tap()
@ -27,7 +28,7 @@ describe('Curate lists', () => {
it('Edit display name and description via the edit curatelist modal', async () => {
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('editNameInput')).clearText()
await element(by.id('editNameInput')).typeText('Bad Ppl')
@ -45,7 +46,7 @@ describe('Curate lists', () => {
it('Remove description via the edit curatelist modal', async () => {
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('editDescriptionInput')).clearText()
await element(by.id('saveBtn')).tap()
@ -60,7 +61,7 @@ describe('Curate lists', () => {
it('Set avi via the edit curatelist modal', async () => {
await expect(element(by.id('userAvatarFallback'))).toExist()
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('changeAvatarBtn')).tap()
await element(by.text('Library')).tap()
@ -77,7 +78,7 @@ describe('Curate lists', () => {
it('Remove avi via the edit curatelist modal', async () => {
await expect(element(by.id('userAvatarImage'))).toExist()
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('changeAvatarBtn')).tap()
await element(by.text('Remove')).tap()
@ -98,6 +99,7 @@ describe('Curate lists', () => {
})
it('Create a new curatelist', async () => {
await element(by.id('e2eGotoLists')).tap()
await element(by.id('newUserListBtn')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('editNameInput')).typeText('Good Ppl')
@ -128,6 +130,7 @@ describe('Curate lists', () => {
})
it('Pins the list', async () => {
await expect(element(by.id('pinBtn'))).toBeVisible()
await element(by.id('pinBtn')).tap()
await element(by.id('e2eGotoHome')).tap()
await element(by.id('homeScreenFeedTabs-Good Ppl')).tap()
@ -152,15 +155,15 @@ describe('Curate lists', () => {
await expect(element(by.id('user-bob.test'))).toBeVisible()
await element(by.id('user-bob.test-editBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
await element(by.id('toggleBtn-Good Ppl')).tap()
await element(by.id('saveBtn')).tap()
await element(by.id('user-bob.test-addBtn')).tap()
await element(by.id('doneBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
})
it('Shows the curatelist on my profile', async () => {
await element(by.id('bottomBarProfileBtn')).tap()
await element(by.id('selector')).swipe('left')
await element(by.id('selector-4')).tap()
await element(by.id('profilePager-selector')).swipe('left')
await element(by.id('profilePager-selector-5')).tap()
await element(by.id('list-Good Ppl')).tap()
})
@ -173,15 +176,15 @@ describe('Curate lists', () => {
await element(by.id('profileHeaderDropdownBtn')).tap()
await element(by.text('Add to Lists')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
await element(by.id('toggleBtn-Good Ppl')).tap()
await element(by.id('saveBtn')).tap()
await element(by.id('user-bob.test-addBtn')).tap()
await element(by.id('doneBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
await element(by.id('profileHeaderDropdownBtn')).tap()
await element(by.text('Add to Lists')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
await element(by.id('toggleBtn-Good Ppl')).tap()
await element(by.id('saveBtn')).tap()
await element(by.id('user-bob.test-addBtn')).tap()
await element(by.id('doneBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
})
@ -192,8 +195,8 @@ describe('Curate lists', () => {
await element(by.id('bottomBarSearchBtn')).tap()
await element(by.id('searchTextInput')).typeText('alice')
await element(by.id('searchAutoCompleteResult-alice.test')).tap()
await element(by.id('selector')).swipe('left')
await element(by.id('selector-3')).tap()
await element(by.id('profilePager-selector')).swipe('left')
await element(by.id('profilePager-selector-3')).tap()
await element(by.id('list-Good Ppl')).tap()
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Report List')).tap()

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer} from '../util'
describe('Home screen', () => {

View File

@ -5,6 +5,8 @@
* with the side drawer.
*/
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer} from '../util'
describe('invite-codes', () => {
@ -16,7 +18,6 @@ describe('invite-codes', () => {
})
it('I can fetch invite codes', async () => {
await expect(element(by.id('signInButton'))).toBeVisible()
await loginAsAlice()
await element(by.id('e2eOpenInviteCodesModal')).tap()
await expect(element(by.id('inviteCodesModal'))).toBeVisible()
@ -27,6 +28,7 @@ describe('invite-codes', () => {
})
it('I can create a new account with the invite code', async () => {
await element(by.id('e2eOpenLoggedOutView')).tap()
await element(by.id('createAccountButton')).tap()
await device.takeScreenshot('1- opened create account screen')
await element(by.id('otherServerBtn')).tap()
@ -51,19 +53,4 @@ describe('invite-codes', () => {
await element(by.id('continueBtn')).tap()
await expect(element(by.id('homeScreen'))).toBeVisible()
})
it('I get a notification for the new user', async () => {
await element(by.id('e2eSignOut')).tap()
await loginAsAlice()
await waitFor(element(by.id('homeScreen')))
.toBeVisible()
.withTimeout(5000)
await element(by.id('bottomBarNotificationsBtn')).tap()
await expect(element(by.id('invitedUser'))).toBeVisible()
})
it('I can dismiss the new user notification', async () => {
await element(by.id('dismissBtn')).tap()
await expect(element(by.id('invitedUser'))).not.toBeVisible()
})
})

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, login, createServer} from '../util'
describe('Login', () => {
@ -10,6 +12,8 @@ describe('Login', () => {
})
it('As Alice, I can login', async () => {
await element(by.id('e2eOpenLoggedOutView')).tap()
await expect(element(by.id('signInButton'))).toBeVisible()
await login(service, 'alice', 'hunter2', {
takeScreenshots: true,

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer} from '../util'
describe('Mergefeed', () => {
@ -9,8 +11,12 @@ describe('Mergefeed', () => {
})
it('Login', async () => {
await element(by.id('e2eOpenLoggedOutView')).tap()
await loginAsAlice()
await element(by.id('e2eToggleMergefeed')).tap()
await element(by.id('bottomBarFeedsBtn')).tap()
await element(by.id('feed-alice-favs-toggleSave')).tap()
await element(by.id('e2eGotoHome')).tap()
})
it('Sees the expected mix of posts with default filters', async () => {

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, loginAsBob, createServer, sleep} from '../util'
describe('Mod lists', () => {
@ -11,7 +13,6 @@ describe('Mod lists', () => {
})
it('Login and view my modlists', async () => {
await expect(element(by.id('signInButton'))).toBeVisible()
await loginAsAlice()
await element(by.id('e2eGotoModeration')).tap()
await element(by.id('moderationlistsBtn')).tap()
@ -31,7 +32,7 @@ describe('Mod lists', () => {
it('Edit display name and description via the edit modlist modal', async () => {
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('editNameInput')).clearText()
await element(by.id('editNameInput')).typeText('Bad Ppl')
@ -49,7 +50,7 @@ describe('Mod lists', () => {
it('Remove description via the edit modlist modal', async () => {
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('editDescriptionInput')).clearText()
await element(by.id('saveBtn')).tap()
@ -64,7 +65,7 @@ describe('Mod lists', () => {
it('Set avi via the edit modlist modal', async () => {
await expect(element(by.id('userAvatarFallback'))).toExist()
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('changeAvatarBtn')).tap()
await element(by.text('Library')).tap()
@ -81,7 +82,7 @@ describe('Mod lists', () => {
it('Remove avi via the edit modlist modal', async () => {
await expect(element(by.id('userAvatarImage'))).toExist()
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Edit List Details')).tap()
await element(by.text('Edit list details')).tap()
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
await element(by.id('changeAvatarBtn')).tap()
await element(by.text('Remove')).tap()
@ -131,15 +132,15 @@ describe('Mod lists', () => {
await expect(element(by.id('user-warn-posts.test'))).toBeVisible()
await element(by.id('user-warn-posts.test-editBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
await element(by.id('toggleBtn-Bad Ppl')).tap()
await element(by.id('saveBtn')).tap()
await element(by.id('user-warn-posts.test-addBtn')).tap()
await element(by.id('doneBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
})
it('Shows the modlist on my profile', async () => {
await element(by.id('bottomBarProfileBtn')).tap()
await element(by.id('selector')).swipe('left')
await element(by.id('selector-4')).tap()
await element(by.id('profilePager-selector')).swipe('left')
await element(by.id('profilePager-selector-5')).tap()
await element(by.id('list-Bad Ppl')).tap()
})
@ -152,15 +153,15 @@ describe('Mod lists', () => {
await element(by.id('profileHeaderDropdownBtn')).tap()
await element(by.text('Add to Lists')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
await element(by.id('toggleBtn-Bad Ppl')).tap()
await element(by.id('saveBtn')).tap()
await element(by.id('user-bob.test-addBtn')).tap()
await element(by.id('doneBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
await element(by.id('profileHeaderDropdownBtn')).tap()
await element(by.text('Add to Lists')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
await element(by.id('toggleBtn-Bad Ppl')).tap()
await element(by.id('saveBtn')).tap()
await element(by.id('user-bob.test-addBtn')).tap()
await element(by.id('doneBtn')).tap()
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
})
@ -171,8 +172,8 @@ describe('Mod lists', () => {
await element(by.id('bottomBarSearchBtn')).tap()
await element(by.id('searchTextInput')).typeText('alice')
await element(by.id('searchAutoCompleteResult-alice.test')).tap()
await element(by.id('selector')).swipe('left')
await element(by.id('selector-3')).tap()
await element(by.id('profilePager-selector')).swipe('left')
await element(by.id('profilePager-selector-3')).tap()
await element(by.id('list-Bad Ppl')).tap()
await element(by.id('headerDropdownBtn')).tap()
await element(by.text('Report List')).tap()

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer, sleep} from '../util'
describe('Profile screen', () => {
@ -11,17 +13,16 @@ describe('Profile screen', () => {
})
it('Login and navigate to my profile', async () => {
await expect(element(by.id('signInButton'))).toBeVisible()
await loginAsAlice()
await element(by.id('bottomBarProfileBtn')).tap()
})
it('Can see feeds', async () => {
await element(by.id('selector')).swipe('left')
await element(by.id('selector-4')).tap()
await element(by.id('profilePager-selector')).swipe('left')
await element(by.id('profilePager-selector-4')).tap()
await expect(element(by.id('feed-alice-favs'))).toBeVisible()
await element(by.id('selector')).swipe('right')
await element(by.id('selector-0')).tap()
await element(by.id('profilePager-selector')).swipe('right')
await element(by.id('profilePager-selector-0')).tap()
})
it('Open and close edit profile modal', async () => {
@ -135,6 +136,14 @@ describe('Profile screen', () => {
})
it('Can like posts', async () => {
await element(by.id('postsFeed-flatlist')).swipe(
'down',
'slow',
1,
0.5,
0.5,
)
const posts = by.id('feedItem-by-bob.test')
await expect(
element(by.id('likeCount').withAncestor(posts)).atIndex(0),

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer} from '../util'
describe('Search screen', () => {

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer, sleep} from '../util'
describe('Self-labeling', () => {

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, loginAsBob, createServer} from '../util'
describe('Thread muting', () => {
@ -48,7 +50,7 @@ describe('Thread muting', () => {
await loginAsBob()
await element(by.id('bottomBarProfileBtn')).tap()
await element(by.id('selector-1')).tap()
await element(by.id('profilePager-selector-1')).tap()
const bobPosts = by.id('feedItem-by-bob.test')
await element(by.id('replyBtn').withAncestor(bobPosts)).atIndex(0).tap()
await element(by.id('composerTextInput')).typeText('Reply 2')

View File

@ -1,5 +1,7 @@
/* eslint-env detox/detox */
import {describe, beforeAll, it} from '@jest/globals'
import {expect} from 'detox'
import {openApp, loginAsAlice, createServer} from '../util'
describe('Thread screen', () => {
@ -31,15 +33,15 @@ describe('Thread screen', () => {
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),
element(by.id('likeCount-expanded').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),
element(by.id('likeCount-expanded').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),
element(by.id('likeCount-expanded').withAncestor(post)).atIndex(0),
).not.toExist()
})
@ -61,21 +63,21 @@ describe('Thread screen', () => {
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),
element(by.id('repostCount-expanded').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),
element(by.id('repostCount-expanded').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),
element(by.id('repostCount-expanded').withAncestor(post)).atIndex(0),
).not.toExist()
})

View File

@ -1,5 +1,8 @@
# Testing instructions
Make sure you've copied `.env.example` to `.env.test` and provided any required
values.
### Using Maestro E2E tests
1. Install Maestro by following [these instructions](https://maestro.mobile.dev/getting-started/installing-maestro). This will help us run the E2E tests.
2. You can write Maestro tests in `__e2e__/maestro` directory by creating a new `.yaml` file or by modifying an existing one.
@ -11,4 +14,4 @@
2. Install Flashlight by following [these instructions](https://docs.flashlight.dev/)
3. The simplest way to get started is by running `yarn perf:measure` which will run a live preview of the performance test results. You can [see a demo here](https://github.com/bamlab/flashlight/assets/4534323/4038a342-f145-4c3b-8cde-17949bf52612)
4. The `yarn perf:test:measure` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml` and give the results in `.perf/results.json` which can be viewed by running `yarn:perf:results`
5. You can also run your own tests by running `yarn perf:test <path_to_test>` where `<path_to_test>` is the path to your test file. For example, `yarn perf:test __e2e__/maestro/scroll.yaml` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml`.
5. You can also run your own tests by running `yarn perf:test <path_to_test>` where `<path_to_test>` is the path to your test file. For example, `yarn perf:test __e2e__/maestro/scroll.yaml` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml`.

View File

@ -1,15 +1,21 @@
import 'react-native-gesture-handler' // must be first
import {LogBox} from 'react-native'
LogBox.ignoreLogs(['Require cycle:']) // suppress require-cycle warnings, it's fine
import '#/platform/polyfills'
import {IS_TEST} from '#/env'
import {registerRootComponent} from 'expo'
import {doPolyfill} from '#/lib/api/api-polyfill'
doPolyfill()
import App from '#/App'
doPolyfill()
if (IS_TEST) {
LogBox.ignoreAllLogs() // suppress all logs in tests
} else {
LogBox.ignoreLogs(['Require cycle:']) // suppress require-cycle warnings, it's fine
}
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately

View File

@ -59,17 +59,21 @@ export async function createServer(
): Promise<TestPDS> {
const port = await getPort()
const port2 = await getPort(port + 1)
const port3 = await getPort(port2 + 1)
const pdsUrl = `http://localhost:${port}`
const id = ids.next()
const testNet = await TestNetwork.create({
pds: {
port,
publicUrl: pdsUrl,
inviteRequired,
hostname: 'localhost',
dbPostgresSchema: `pds_${id}`,
inviteRequired,
},
bsky: {
dbPostgresSchema: `bsky_${id}`,
port: port3,
publicUrl: 'http://localhost:2584',
},
plc: {port: port2},
})

View File

@ -21,9 +21,9 @@
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
"typecheck": "tsc --project ./tsconfig.check.json",
"e2e:mock-server": "./jest/dev-infra/with-test-redis-and-db.sh ts-node __e2e__/mock-server.ts",
"e2e:metro": "RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
"e2e:build": "detox build -c ios.sim.debug",
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all",
"e2e:metro": "NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
"e2e:build": "NODE_ENV=test detox build -c ios.sim.debug",
"e2e:run": "NODE_ENV=test detox test --configuration ios.sim.debug --take-screenshots all",
"perf:test": "NODE_ENV=test maestro test",
"perf:test:run": "NODE_ENV=test maestro test __e2e__/maestro/scroll.yaml",
"perf:test:measure": "NODE_ENV=test flashlight test --bundleId xyz.blueskyweb.app --testCommand 'yarn perf:test' --duration 150000 --resultsFilePath .perf/results.json",
@ -168,7 +168,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
"@atproto/dev-env": "^0.2.5",
"@atproto/dev-env": "^0.2.16",
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",

View File

@ -1,4 +1,4 @@
export const IS_TEST = process.env.NODE_ENV === 'test'
export const IS_TEST = process.env.EXPO_PUBLIC_ENV === 'test'
export const IS_DEV = __DEV__
export const IS_PROD = !IS_DEV
export const LOG_DEBUG = process.env.EXPO_PUBLIC_LOG_DEBUG || ''

View File

@ -61,6 +61,7 @@ export interface CreateOrEditListModal {
export interface UserAddRemoveListsModal {
name: 'user-add-remove-lists'
subject: string
handle: string
displayName: string
onAdd?: (listUri: string) => void
onRemove?: (listUri: string) => void

View File

@ -170,6 +170,7 @@ export function FeedSourceCardLoaded({
{showSaveBtn && feed.type === 'feed' && (
<View>
<Pressable
testID={`feed-${feed.displayName}-toggleSave`}
disabled={isSavePending || isPinPending || isRemovePending}
accessibilityRole="button"
accessibilityLabel={

View File

@ -132,6 +132,7 @@ export function ListMembers({
name: 'user-add-remove-lists',
subject: profile.did,
displayName: profile.displayName || profile.handle,
handle: profile.handle,
})
},
[openModal],

View File

@ -28,11 +28,13 @@ export const snapPoints = ['fullscreen']
export function Component({
subject,
handle,
displayName,
onAdd,
onRemove,
}: {
subject: string
handle: string
displayName: string
onAdd?: (listUri: string) => void
onRemove?: (listUri: string) => void
@ -60,6 +62,7 @@ export function Component({
list={list}
memberships={memberships}
subject={subject}
handle={handle}
onAdd={onAdd}
onRemove={onRemove}
/>
@ -87,6 +90,7 @@ function ListItem({
list,
memberships,
subject,
handle,
onAdd,
onRemove,
}: {
@ -94,6 +98,7 @@ function ListItem({
list: GraphDefs.ListView
memberships: ListMembersip[] | undefined
subject: string
handle: string
onAdd?: (listUri: string) => void
onRemove?: (listUri: string) => void
}) {
@ -182,7 +187,7 @@ function ListItem({
<ActivityIndicator />
) : (
<Button
testID={`user-${subject}-addBtn`}
testID={`user-${handle}-addBtn`}
type="default"
label={membership === false ? _(msg`Add`) : _(msg`Remove`)}
onPress={onToggleMembership}

View File

@ -375,7 +375,10 @@ let PostThreadItemLoaded = ({
style={styles.expandedInfoItem}
href={repostsHref}
title={repostsTitle}>
<Text testID="repostCount" type="lg" style={pal.textLight}>
<Text
testID="repostCount-expanded"
type="lg"
style={pal.textLight}>
<Text type="xl-bold" style={pal.text}>
{formatCount(post.repostCount)}
</Text>{' '}
@ -390,7 +393,10 @@ let PostThreadItemLoaded = ({
style={styles.expandedInfoItem}
href={likesHref}
title={likesTitle}>
<Text testID="likeCount" type="lg" style={pal.textLight}>
<Text
testID="likeCount-expanded"
type="lg"
style={pal.textLight}>
<Text type="xl-bold" style={pal.text}>
{formatCount(post.likeCount)}
</Text>{' '}

View File

@ -217,6 +217,7 @@ let ProfileHeaderLoaded = ({
openModal({
name: 'user-add-remove-lists',
subject: profile.did,
handle: profile.handle,
displayName: profile.displayName || profile.handle,
onAdd: invalidateProfileQuery,
onRemove: invalidateProfileQuery,

View File

@ -5,6 +5,7 @@ import {useModalControls} from '#/state/modals'
import {useQueryClient} from '@tanstack/react-query'
import {useSessionApi} from '#/state/session'
import {useSetFeedViewPreferencesMutation} from '#/state/queries/preferences'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
/**
* This utility component is only included in the test simulator
@ -19,6 +20,7 @@ export function TestCtrls() {
const {logout, login} = useSessionApi()
const {openModal} = useModalControls()
const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
const {setShowLoggedOut} = useLoggedOutViewControls()
const onPressSignInAlice = async () => {
await login({
service: 'http://localhost:3000',
@ -95,6 +97,12 @@ export function TestCtrls() {
accessibilityRole="button"
style={BTN}
/>
<Pressable
testID="e2eOpenLoggedOutView"
onPress={() => setShowLoggedOut(true)}
accessibilityRole="button"
style={BTN}
/>
</View>
)
}

View File

@ -7,6 +7,7 @@ import {colors} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {IS_TEST} from '#/env'
const TIMEOUT = 4e3
@ -14,6 +15,7 @@ export function show(
message: string,
_icon: FontAwesomeProps['icon'] = 'check',
) {
if (IS_TEST) return
const item = new RootSiblings(<Toast message={message} />)
setTimeout(() => {
item.destroy()

View File

@ -42,6 +42,7 @@ export function SearchResultCard({
return (
<Link
testID={`searchAutoCompleteResult-${profile.handle}`}
href={makeProfileLink(profile)}
title={profile.handle}
asAnchor

1614
yarn.lock

File diff suppressed because it is too large Load Diff