An ATproto social media client -- with an independent Appview.

Improve e2e tests (#8927)

* get e2e image picker working

* verify create account actually reaches onboarding

* wait for image to actually be attached before posting

* wait until login finishes before moving on

* sign out before switch accounts then wait until logged in

* disable onboarding experiments in e2e

* add testId to handle availability checkmark

* fix too long username

* update thread muting test to reflect current behaviour

* hackfix for the british english translation

* unflake the onboarding tests

* fix curate list flow

* admit defeat on the most list one

authored by samuel.fm and committed by

GitHub d6dc52b6 541502c7

+200 -92
+9 -3
__e2e__/flows/composer-self-label.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: ?users 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 12 # Post an image with the porn label 13 - assertVisible: 14 - id: "composeFAB" 15 - tapOn: 16 id: "composeFAB" 17 - inputText: "Post with an image" 18 - tapOn: 19 - id: "openGalleryBtn" 20 - tapOn: "Content warnings" 21 - tapOn: "Porn" 22 - tapOn:
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: ?users 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 15 # Post an image with the porn label 16 - assertVisible: 17 + id: "composeFAB" 18 - tapOn: 19 id: "composeFAB" 20 - inputText: "Post with an image" 21 - tapOn: 22 + id: "openMediaBtn" 23 + - extendedWaitUntil: 24 + visible: 25 + id: "selectedPhotosView" 26 - tapOn: "Content warnings" 27 - tapOn: "Porn" 28 - tapOn:
+18 -5
__e2e__/flows/composer.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: ?users 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 - assertVisible: 12 - id: "composeFAB" 13 - tapOn: 14 id: "composeFAB" 15 - inputText: "Post text only" ··· 21 id: "composeFAB" 22 - inputText: "Post with an image" 23 - tapOn: 24 - id: "openGalleryBtn" 25 - tapOn: 26 id: "composerPublishBtn" 27 - assertVisible: ··· 46 id: "replyBtn" 47 - inputText: "Reply with an image" 48 - tapOn: 49 - id: "openGalleryBtn" 50 - tapOn: 51 id: "composerPublishBtn" 52 - assertVisible: ··· 73 id: "quoteBtn" 74 - inputText: "QP with an image" 75 - tapOn: 76 - id: "openGalleryBtn" 77 - tapOn: 78 id: "composerPublishBtn" 79 - assertVisible:
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: ?users 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 + 15 - assertVisible: 16 + id: "composeFAB" 17 - tapOn: 18 id: "composeFAB" 19 - inputText: "Post text only" ··· 25 id: "composeFAB" 26 - inputText: "Post with an image" 27 - tapOn: 28 + id: "openMediaBtn" 29 + - extendedWaitUntil: 30 + visible: 31 + id: "selectedPhotosView" 32 - tapOn: 33 id: "composerPublishBtn" 34 - assertVisible: ··· 53 id: "replyBtn" 54 - inputText: "Reply with an image" 55 - tapOn: 56 + id: "openMediaBtn" 57 + - extendedWaitUntil: 58 + visible: 59 + id: "selectedPhotosView" 60 - tapOn: 61 id: "composerPublishBtn" 62 - assertVisible: ··· 83 id: "quoteBtn" 84 - inputText: "QP with an image" 85 - tapOn: 86 + id: "openMediaBtn" 87 + - extendedWaitUntil: 88 + visible: 89 + id: "selectedPhotosView" 90 - tapOn: 91 id: "composerPublishBtn" 92 - assertVisible:
+4 -1
__e2e__/flows/create-account.yml
··· 32 text: "Not Now" 33 optional: true 34 - inputText: "e2e-test" 35 - tapOn: 36 id: "nextBtn" 37 -
··· 32 text: "Not Now" 33 optional: true 34 - inputText: "e2e-test" 35 + - extendedWaitUntil: 36 + visible: 37 + id: "handleAvailableCheck" 38 - tapOn: 39 id: "nextBtn" 40 + - assertVisible: "Give your profile a face"
+7 -5
__e2e__/flows/curate-lists.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users&follows&posts" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 12 - tapOn: 13 label: "Create a curate list" ··· 75 - tapOn: 76 id: "confirmBtn" 77 78 - - tapOn: 79 - label: "Create a new curatelist" 80 - id: "e2eGotoLists" 81 - tapOn: 82 id: "newUserListBtn" 83 - assertVisible: ··· 146 147 - tapOn: 148 id: "bottomBarSearchBtn" 149 - - tapOn: "Search for posts, users, or feeds" 150 - inputText: "bob" 151 - tapOn: 152 id: "searchAutoCompleteResult-bob.test"
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users&follows&posts" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 15 - tapOn: 16 label: "Create a curate list" ··· 78 - tapOn: 79 id: "confirmBtn" 80 81 + - assertVisible: 82 + id: "newUserListBtn" 83 - tapOn: 84 id: "newUserListBtn" 85 - assertVisible: ··· 148 149 - tapOn: 150 id: "bottomBarSearchBtn" 151 + - tapOn: "Search for posts, users[,]? or feeds" 152 - inputText: "bob" 153 - tapOn: 154 id: "searchAutoCompleteResult-bob.test"
+10 -10
__e2e__/flows/mod-lists.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users&follows&labels" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 12 # create a modlist 13 - tapOn: ··· 28 - assertVisible: "Muted Users" 29 - assertVisible: "Shhh" 30 31 - - tapOn: 32 - label: "Dropdown" 33 - point: "71%,9%" 34 - 35 - tapOn: "Mute accounts" 36 - tapOn: "Mute list" 37 - tapOn: "Unmute" 38 39 - - tapOn: 40 - label: "Dropdown" 41 - point: "71%,9%" 42 - 43 - tapOn: "Block accounts" 44 - tapOn: "Block list" 45 - tapOn: "Unblock" 46 47 - # the rest of the behaviors are tested in curate-lists.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users&follows&labels" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 15 # create a modlist 16 - tapOn: ··· 31 - assertVisible: "Muted Users" 32 - assertVisible: "Shhh" 33 34 + # DOES NOT WORK - THE BUTTON IS NOT ACCESSIBLE 35 + # IGNORING FOR NOW, FIX THE COMPONENTS IN THE NEXT RELEASE 36 + # BECAUSE THIS IS A LEGIT A11Y PROBLEM -sfn 37 + - tapOn: "Subscribe to this list" 38 - tapOn: "Mute accounts" 39 - tapOn: "Mute list" 40 - tapOn: "Unmute" 41 42 + - tapOn: "Subscribe to this list" 43 - tapOn: "Block accounts" 44 - tapOn: "Block list" 45 - tapOn: "Unblock" 46 47 + # the rest of the behaviors are tested in curate-lists.yml
+10 -6
__e2e__/flows/onboarding-avatar-creator.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 - tapOn: 12 id: "e2eStartOnboarding" 13 - tapOn: "Open avatar creator" ··· 21 - tapOn: "Select the atom emoji as your avatar" 22 - tapOn: "Done" 23 - waitForAnimationToEnd 24 - - tapOn: "Continue to next step" 25 - assertVisible: "What are your interests?" 26 - tapOn: 27 - label: "Tap on continue" 28 - point: "50%,92%" 29 - assertVisible: "You're ready to go!" 30 - tapOn: 31 - label: "Tap on Lets go" 32 - point: "50%,92%"
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 + 15 - tapOn: 16 id: "e2eStartOnboarding" 17 - tapOn: "Open avatar creator" ··· 25 - tapOn: "Select the atom emoji as your avatar" 26 - tapOn: "Done" 27 - waitForAnimationToEnd 28 + - tapOn: 29 + id: "onboardingContinue" 30 - assertVisible: "What are your interests?" 31 - tapOn: 32 + id: "onboardingContinue" 33 - assertVisible: "You're ready to go!" 34 - tapOn: 35 + id: "onboardingFinish" 36 + - assertVisible: "Following"
+10 -6
__e2e__/flows/onboarding.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 - tapOn: 12 id: "e2eStartOnboarding" 13 - tapOn: "Select an avatar" ··· 18 - waitForAnimationToEnd 19 - tapOn: "Done" 20 - waitForAnimationToEnd 21 - - tapOn: "Continue to next step" 22 - assertVisible: "What are your interests?" 23 - tapOn: 24 - label: "Tap on continue" 25 - point: "50%,92%" 26 - assertVisible: "You're ready to go!" 27 - tapOn: 28 - label: "Tap on Lets go" 29 - point: "50%,92%"
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 + 15 - tapOn: 16 id: "e2eStartOnboarding" 17 - tapOn: "Select an avatar" ··· 22 - waitForAnimationToEnd 23 - tapOn: "Done" 24 - waitForAnimationToEnd 25 + - tapOn: 26 + id: "onboardingContinue" 27 - assertVisible: "What are your interests?" 28 - tapOn: 29 + id: "onboardingContinue" 30 - assertVisible: "You're ready to go!" 31 - tapOn: 32 + id: "onboardingFinish" 33 + - assertVisible: "Following"
+4 -1
__e2e__/flows/post-report-flow.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users&follows&posts" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 12 - tapOn: 13 id: "postDropdownBtn"
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users&follows&posts" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 15 - tapOn: 16 id: "postDropdownBtn"
+4 -4
__e2e__/flows/profile-screen.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users&posts&feeds" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: ··· 12 # Navigate to another user profile 13 - extendedWaitUntil: 14 visible: 15 - id: "bottomBarSearchBtn" 16 - tapOn: 17 id: "bottomBarSearchBtn" 18 - - tapOn: "Search for posts, users, or feeds" 19 - inputText: "b" 20 - tapOn: 21 id: "searchAutoCompleteResult-bob.test" ··· 38 - tapOn: 39 id: "profileHeaderDropdownBtn" 40 - tapOn: "Unmute Account" 41 - - assertNotVisible: "Account Muted"
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users&posts&feeds" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: ··· 12 # Navigate to another user profile 13 - extendedWaitUntil: 14 visible: 15 + id: "bottomBarSearchBtn" 16 - tapOn: 17 id: "bottomBarSearchBtn" 18 + - tapOn: "Search for posts, users[,]? or feeds" 19 - inputText: "b" 20 - tapOn: 21 id: "searchAutoCompleteResult-bob.test" ··· 38 - tapOn: 39 id: "profileHeaderDropdownBtn" 40 - tapOn: "Unmute Account" 41 + - assertNotVisible: "Account Muted"
+6 -4
__e2e__/flows/search-screen.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 12 # Navigate to another user profile via autocomplete 13 - tapOn: 14 id: "bottomBarSearchBtn" 15 - - assertVisible: "Search for posts, users, or feeds" 16 - - tapOn: "Search for posts, users, or feeds" 17 - inputText: "b" 18 - tapOn: 19 id: "searchAutoCompleteResult-bob.test" 20 - assertVisible: 21 id: "profileView" 22 -
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users" 7 - runFlow: 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 12 # Navigate to another user profile via autocomplete 13 + - extendedWaitUntil: 14 + visible: 15 + id: "bottomBarSearchBtn" 16 - tapOn: 17 id: "bottomBarSearchBtn" 18 + - assertVisible: "Search for posts, users[,]? or feeds" 19 + - tapOn: "Search for posts, users[,]? or feeds" 20 - inputText: "b" 21 - tapOn: 22 id: "searchAutoCompleteResult-bob.test" 23 - assertVisible: 24 id: "profileView"
+5 -1
__e2e__/flows/shared-prefs.yml
··· 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 - assertVisible: 12 - id: "storybookBtn" 13 - tapOn: 14 id: "storybookBtn" 15 - tapOn:
··· 8 file: ../setupApp.yml 9 - tapOn: 10 id: "e2eSignInAlice" 11 + - extendedWaitUntil: 12 + visible: 13 + id: "viewHeaderHomeFeedPrefsBtn" 14 + 15 - assertVisible: 16 + id: "storybookBtn" 17 - tapOn: 18 id: "storybookBtn" 19 - tapOn:
+38 -9
__e2e__/flows/thread-muting.yml
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 - SERVER_PATH: "?users&follows" 7 - runFlow: 8 file: ../setupApp.yml 9 10 # Login, create a thread, and log out 11 - tapOn: 12 id: "e2eSignInAlice" 13 - assertVisible: 14 id: "composeFAB" 15 - tapOn: ··· 20 21 # Login, reply to the thread, and log out 22 - tapOn: 23 id: "e2eSignInBob" 24 - tapOn: 25 id: "replyBtn" 26 - inputText: "Reply 1" ··· 28 id: "composerPublishBtn" 29 30 # Login, confirm notification exists, mute thread, and log out 31 - tapOn: 32 id: "e2eSignInAlice" 33 - tapOn: 34 id: "bottomBarNotificationsBtn" 35 - assertVisible: ··· 39 - tapOn: 40 id: "postDropdownBtn" 41 childOf: 42 - id: "postThreadItem-by-bob.test" 43 - tapOn: "Mute thread" 44 45 # Login, reply to the thread twice, and log out 46 - tapOn: 47 id: "e2eSignInBob" 48 - tapOn: 49 id: "bottomBarProfileBtn" 50 - tapOn: ··· 60 - tapOn: 61 id: "composerPublishBtn" 62 63 - 64 - # Login, confirm notifications dont exist, unmute the thread, confirm notifications exist 65 - tapOn: 66 id: "e2eSignInAlice" 67 - tapOn: 68 id: "bottomBarNotificationsBtn" 69 - - assertNotVisible: 70 id: "feedItem-by-bob.test" 71 - tapOn: 72 - id: "bottomBarHomeBtn" 73 - tapOn: 74 id: "postDropdownBtn" 75 - tapOn: "Unmute thread" 76 - tapOn: 77 id: "bottomBarNotificationsBtn" 78 - swipe: 79 from: 80 - id: "notifsFeed" 81 direction: DOWN 82 - - assertVisible: 83 - id: "feedItem-by-bob.test"
··· 3 - runScript: 4 file: ../setupServer.js 5 env: 6 + SERVER_PATH: "?users&follows" 7 - runFlow: 8 file: ../setupApp.yml 9 10 # Login, create a thread, and log out 11 - tapOn: 12 id: "e2eSignInAlice" 13 + - extendedWaitUntil: 14 + visible: 15 + id: "viewHeaderHomeFeedPrefsBtn" 16 - assertVisible: 17 id: "composeFAB" 18 - tapOn: ··· 23 24 # Login, reply to the thread, and log out 25 - tapOn: 26 + id: "e2eSignOut" 27 + - tapOn: 28 id: "e2eSignInBob" 29 + - extendedWaitUntil: 30 + visible: 31 + id: "viewHeaderHomeFeedPrefsBtn" 32 - tapOn: 33 id: "replyBtn" 34 - inputText: "Reply 1" ··· 36 id: "composerPublishBtn" 37 38 # Login, confirm notification exists, mute thread, and log out 39 + - tapOn: 40 + id: "e2eSignOut" 41 - tapOn: 42 id: "e2eSignInAlice" 43 + - extendedWaitUntil: 44 + visible: 45 + id: "viewHeaderHomeFeedPrefsBtn" 46 - tapOn: 47 id: "bottomBarNotificationsBtn" 48 - assertVisible: ··· 52 - tapOn: 53 id: "postDropdownBtn" 54 childOf: 55 + id: "postThreadItem-by-bob.test" 56 - tapOn: "Mute thread" 57 58 # Login, reply to the thread twice, and log out 59 - tapOn: 60 + id: "e2eSignOut" 61 + - tapOn: 62 id: "e2eSignInBob" 63 + - extendedWaitUntil: 64 + visible: 65 + id: "viewHeaderHomeFeedPrefsBtn" 66 - tapOn: 67 id: "bottomBarProfileBtn" 68 - tapOn: ··· 78 - tapOn: 79 id: "composerPublishBtn" 80 81 + # Login, confirm notifications dont exist, unmute the thread, ~~confirm notifications exist~~ 82 + # Mute thread behaviour no longer change old notifications after muting/unmuting a thread -sfn 83 + - tapOn: 84 + id: "e2eSignOut" 85 - tapOn: 86 id: "e2eSignInAlice" 87 + - extendedWaitUntil: 88 + visible: 89 + id: "viewHeaderHomeFeedPrefsBtn" 90 - tapOn: 91 id: "bottomBarNotificationsBtn" 92 + - assertVisible: ".*Reply 1.*" 93 + - assertNotVisible: ".*Reply 2.*" 94 + - assertNotVisible: ".*Reply 3.*" 95 + - assertVisible: 96 id: "feedItem-by-bob.test" 97 - tapOn: 98 + id: "feedItem-by-bob.test" 99 - tapOn: 100 id: "postDropdownBtn" 101 + childOf: 102 + id: "postThreadItem-by-bob.test" 103 - tapOn: "Unmute thread" 104 - tapOn: 105 id: "bottomBarNotificationsBtn" 106 - swipe: 107 from: 108 + id: "notifsFeed" 109 direction: DOWN 110 + - assertVisible: ".*Reply 1.*" 111 + - assertNotVisible: ".*Reply 2.*" 112 + - assertNotVisible: ".*Reply 3.*"
+12 -12
__e2e__/mock-server.ts
··· 181 'warn-profile', 182 'warn-posts', 183 'muted-account', 184 - 'muted-by-list-account', 185 'blocking-account', 186 'blockedby-account', 187 - 'mutual-block-account', 188 ]) { 189 await server.mocker.createUser(user) 190 await server.mocker.follow('alice', user) ··· 411 await server.mocker.addToMuteList( 412 'alice', 413 list, 414 - server.mocker.users['muted-by-list-account'].did, 415 ) 416 - await server.mocker.createPost('muted-by-list-account', 'muted post') 417 await server.mocker.createQuotePost( 418 - 'muted-by-list-account', 419 'account quote post', 420 anchorPost, 421 ) 422 await server.mocker.createReply( 423 - 'muted-by-list-account', 424 'account reply', 425 anchorPost, 426 ) ··· 470 ) 471 472 await server.mocker.createPost( 473 - 'mutual-block-account', 474 'mutual-block post', 475 ) 476 await server.mocker.createQuotePost( 477 - 'mutual-block-account', 478 'mutual-block quote post', 479 anchorPost, 480 ) 481 await server.mocker.createReply( 482 - 'mutual-block-account', 483 'mutual-block reply', 484 anchorPost, 485 ) ··· 488 repo: server.mocker.users.alice.did, 489 }, 490 { 491 - subject: server.mocker.users['mutual-block-account'].did, 492 createdAt: new Date().toISOString(), 493 }, 494 ) 495 await server.mocker.users[ 496 - 'mutual-block-account' 497 ].agent.app.bsky.graph.block.create( 498 { 499 - repo: server.mocker.users['mutual-block-account'].did, 500 }, 501 { 502 subject: server.mocker.users.alice.did,
··· 181 'warn-profile', 182 'warn-posts', 183 'muted-account', 184 + 'muted-by-list-acc', 185 'blocking-account', 186 'blockedby-account', 187 + 'mutual-block-acc', 188 ]) { 189 await server.mocker.createUser(user) 190 await server.mocker.follow('alice', user) ··· 411 await server.mocker.addToMuteList( 412 'alice', 413 list, 414 + server.mocker.users['muted-by-list-acc'].did, 415 ) 416 + await server.mocker.createPost('muted-by-list-acc', 'muted post') 417 await server.mocker.createQuotePost( 418 + 'muted-by-list-acc', 419 'account quote post', 420 anchorPost, 421 ) 422 await server.mocker.createReply( 423 + 'muted-by-list-acc', 424 'account reply', 425 anchorPost, 426 ) ··· 470 ) 471 472 await server.mocker.createPost( 473 + 'mutual-block-acc', 474 'mutual-block post', 475 ) 476 await server.mocker.createQuotePost( 477 + 'mutual-block-acc', 478 'mutual-block quote post', 479 anchorPost, 480 ) 481 await server.mocker.createReply( 482 + 'mutual-block-acc', 483 'mutual-block reply', 484 anchorPost, 485 ) ··· 488 repo: server.mocker.users.alice.did, 489 }, 490 { 491 + subject: server.mocker.users['mutual-block-acc'].did, 492 createdAt: new Date().toISOString(), 493 }, 494 ) 495 await server.mocker.users[ 496 + 'mutual-block-acc' 497 ].agent.app.bsky.graph.block.create( 498 { 499 + repo: server.mocker.users['mutual-block-acc'].did, 500 }, 501 { 502 subject: server.mocker.users.alice.did,
+2 -2
__e2e__/setupServer.js
··· 1 - // eslint-disable-next-line 2 var res = http.post('http://localhost:1986/' + SERVER_PATH, { 3 headers: {'Content-Type': 'text/plain'}, 4 body: '', 5 }) 6 7 - // eslint-disable-next-line 8 output.result = json(res.body).appviewDid
··· 1 + // eslint-disable-next-line no-undef 2 var res = http.post('http://localhost:1986/' + SERVER_PATH, { 3 headers: {'Content-Type': 'text/plain'}, 4 body: '', 5 }) 6 7 + // eslint-disable-next-line no-undef 8 output.result = json(res.body).appviewDid
+17
src/lib/media/picker.e2e.tsx
··· 7 8 import {compressIfNeeded} from './manip' 9 import {type PickerImage} from './picker.shared' 10 11 async function getFile() { 12 const imagesDir = documentDirectory! ··· 36 37 export async function openPicker(): Promise<PickerImage[]> { 38 return [await getFile()] 39 } 40 41 export async function openCamera(): Promise<PickerImage> {
··· 7 8 import {compressIfNeeded} from './manip' 9 import {type PickerImage} from './picker.shared' 10 + import {ImagePickerResult} from 'expo-image-picker' 11 12 async function getFile() { 13 const imagesDir = documentDirectory! ··· 37 38 export async function openPicker(): Promise<PickerImage[]> { 39 return [await getFile()] 40 + } 41 + 42 + export async function openUnifiedPicker(): Promise<ImagePickerResult> { 43 + const file = await getFile() 44 + 45 + return { 46 + assets: [ 47 + { 48 + type: 'image', 49 + uri: file.path, 50 + mimeType: file.mime, 51 + ...file, 52 + }, 53 + ], 54 + canceled: false, 55 + } 56 } 57 58 export async function openCamera(): Promise<PickerImage> {
+22
src/lib/media/picker.shared.ts
··· 1 import { 2 type ImagePickerOptions, 3 launchImageLibraryAsync, 4 } from 'expo-image-picker' 5 import {t} from '@lingui/macro' 6 7 import {type ImageMeta} from '#/state/gallery' 8 import * as Toast from '#/view/com/util/Toast' 9 import {getDataUriSize} from './util' 10 11 export type PickerImage = ImageMeta & { ··· 36 size: getDataUriSize(image.uri), 37 })) 38 }
··· 1 import { 2 type ImagePickerOptions, 3 launchImageLibraryAsync, 4 + UIImagePickerPreferredAssetRepresentationMode, 5 } from 'expo-image-picker' 6 import {t} from '@lingui/macro' 7 8 + import {isIOS, isWeb} from '#/platform/detection' 9 import {type ImageMeta} from '#/state/gallery' 10 import * as Toast from '#/view/com/util/Toast' 11 + import {VIDEO_MAX_DURATION_MS} from '../constants' 12 import {getDataUriSize} from './util' 13 14 export type PickerImage = ImageMeta & { ··· 39 size: getDataUriSize(image.uri), 40 })) 41 } 42 + 43 + export async function openUnifiedPicker({ 44 + selectionCountRemaining, 45 + }: { 46 + selectionCountRemaining: number 47 + }) { 48 + return await launchImageLibraryAsync({ 49 + exif: false, 50 + mediaTypes: ['images', 'videos'], 51 + quality: 1, 52 + allowsMultipleSelection: true, 53 + legacy: true, 54 + base64: isWeb, 55 + selectionLimit: isIOS ? selectionCountRemaining : undefined, 56 + preferredAssetRepresentationMode: 57 + UIImagePickerPreferredAssetRepresentationMode.Current, 58 + videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000, 59 + }) 60 + }
+5 -1
src/lib/media/picker.tsx
··· 1 import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool' 2 import {type ImagePickerOptions, launchCameraAsync} from 'expo-image-picker' 3 4 - export {openPicker, type PickerImage as RNImage} from './picker.shared' 5 6 export async function openCamera(customOpts: ImagePickerOptions) { 7 const opts: ImagePickerOptions = {
··· 1 import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool' 2 import {type ImagePickerOptions, launchCameraAsync} from 'expo-image-picker' 3 4 + export { 5 + openPicker, 6 + openUnifiedPicker, 7 + type PickerImage as RNImage, 8 + } from './picker.shared' 9 10 export async function openCamera(customOpts: ImagePickerOptions) { 11 const opts: ImagePickerOptions = {
+1 -1
src/lib/media/picker.web.tsx
··· 3 import {type PickerImage} from './picker.shared' 4 import {type CameraOpts} from './types' 5 6 - export {openPicker} from './picker.shared' 7 8 export async function openCamera(_opts: CameraOpts): Promise<PickerImage> { 9 throw new Error('openCamera is not supported on web')
··· 3 import {type PickerImage} from './picker.shared' 4 import {type CameraOpts} from './types' 5 6 + export {openPicker, openUnifiedPicker} from './picker.shared' 7 8 export async function openCamera(_opts: CameraOpts): Promise<PickerImage> { 9 throw new Error('openCamera is not supported on web')
+1
src/screens/Onboarding/StepFinished.tsx
··· 580 581 <OnboardingControls.Portal> 582 <Button 583 disabled={saving} 584 key={state.activeStep} // remove focus state on nav 585 color="primary"
··· 580 581 <OnboardingControls.Portal> 582 <Button 583 + testID="onboardingFinish" 584 disabled={saving} 585 key={state.activeStep} // remove focus state on nav 586 color="primary"
+1
src/screens/Onboarding/StepInterests/index.tsx
··· 244 ) : ( 245 <Button 246 disabled={saving || !data} 247 variant="solid" 248 color="primary" 249 size="large"
··· 244 ) : ( 245 <Button 246 disabled={saving || !data} 247 + testID="onboardingContinue" 248 variant="solid" 249 color="primary" 250 size="large"
+2
src/screens/Onboarding/StepProfile/index.tsx
··· 268 <OnboardingControls.Portal> 269 <View style={[a.gap_md, gtMobile && a.flex_row_reverse]}> 270 <Button 271 variant="solid" 272 color="primary" 273 size="large" ··· 279 <ButtonIcon icon={ChevronRight} position="right" /> 280 </Button> 281 <Button 282 variant="ghost" 283 color="primary" 284 size="large"
··· 268 <OnboardingControls.Portal> 269 <View style={[a.gap_md, gtMobile && a.flex_row_reverse]}> 270 <Button 271 + testID="onboardingContinue" 272 variant="solid" 273 color="primary" 274 size="large" ··· 280 <ButtonIcon icon={ChevronRight} position="right" /> 281 </Button> 282 <Button 283 + testID="onboardingAvatarCreator" 284 variant="ghost" 285 color="primary" 286 size="large"
+4 -2
src/screens/Onboarding/index.tsx
··· 13 import {StepInterests} from '#/screens/Onboarding/StepInterests' 14 import {StepProfile} from '#/screens/Onboarding/StepProfile' 15 import {Portal} from '#/components/Portal' 16 import {StepSuggestedAccounts} from './StepSuggestedAccounts' 17 18 export function Onboarding() { 19 const {_} = useLingui() 20 const gate = useGate() 21 - const showValueProp = gate('onboarding_value_prop') 22 - const showSuggestedAccounts = gate('onboarding_suggested_accounts') 23 const [state, dispatch] = useReducer(reducer, { 24 ...initialState, 25 totalSteps: showSuggestedAccounts ? 4 : 3,
··· 13 import {StepInterests} from '#/screens/Onboarding/StepInterests' 14 import {StepProfile} from '#/screens/Onboarding/StepProfile' 15 import {Portal} from '#/components/Portal' 16 + import {ENV} from '#/env' 17 import {StepSuggestedAccounts} from './StepSuggestedAccounts' 18 19 export function Onboarding() { 20 const {_} = useLingui() 21 const gate = useGate() 22 + const showValueProp = ENV !== 'e2e' && gate('onboarding_value_prop') 23 + const showSuggestedAccounts = 24 + ENV !== 'e2e' && gate('onboarding_suggested_accounts') 25 const [state, dispatch] = useReducer(reducer, { 26 ...initialState, 27 totalSteps: showSuggestedAccounts ? 4 : 3,
+4 -1
src/screens/Signup/StepHandle/index.tsx
··· 168 </TextField.GhostText> 169 )} 170 {isHandleAvailable?.available && ( 171 - <CheckIcon style={[{color: t.palette.positive_600}, a.z_20]} /> 172 )} 173 </TextField.Root> 174 </View>
··· 168 </TextField.GhostText> 169 )} 170 {isHandleAvailable?.available && ( 171 + <CheckIcon 172 + testID="handleAvailableCheck" 173 + style={[{color: t.palette.positive_600}, a.z_20]} 174 + /> 175 )} 176 </TextField.Root> 177 </View>
+4 -18
src/view/com/composer/SelectMediaButton.tsx
··· 1 import {useCallback} from 'react' 2 import {Keyboard} from 'react-native' 3 - import { 4 - type ImagePickerAsset, 5 - launchImageLibraryAsync, 6 - UIImagePickerPreferredAssetRepresentationMode, 7 - } from 'expo-image-picker' 8 import {msg, plural} from '@lingui/macro' 9 import {useLingui} from '@lingui/react' 10 ··· 13 usePhotoLibraryPermission, 14 useVideoLibraryPermission, 15 } from '#/lib/hooks/usePermissions' 16 import {extractDataUriMime} from '#/lib/media/util' 17 - import {isIOS, isNative, isWeb} from '#/platform/detection' 18 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 19 import {atoms as a, useTheme} from '#/alf' 20 import {Button} from '#/components/Button' ··· 448 } 449 450 const {assets, canceled} = await sheetWrapper( 451 - launchImageLibraryAsync({ 452 - exif: false, 453 - mediaTypes: ['images', 'videos'], 454 - quality: 1, 455 - allowsMultipleSelection: true, 456 - legacy: true, 457 - base64: isWeb, 458 - selectionLimit: isIOS ? selectionCountRemaining : undefined, 459 - preferredAssetRepresentationMode: 460 - UIImagePickerPreferredAssetRepresentationMode.Current, 461 - videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000, 462 - }), 463 ) 464 465 if (canceled) return
··· 1 import {useCallback} from 'react' 2 import {Keyboard} from 'react-native' 3 + import {type ImagePickerAsset} from 'expo-image-picker' 4 import {msg, plural} from '@lingui/macro' 5 import {useLingui} from '@lingui/react' 6 ··· 9 usePhotoLibraryPermission, 10 useVideoLibraryPermission, 11 } from '#/lib/hooks/usePermissions' 12 + import {openUnifiedPicker} from '#/lib/media/picker' 13 import {extractDataUriMime} from '#/lib/media/util' 14 + import {isNative, isWeb} from '#/platform/detection' 15 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 16 import {atoms as a, useTheme} from '#/alf' 17 import {Button} from '#/components/Button' ··· 445 } 446 447 const {assets, canceled} = await sheetWrapper( 448 + openUnifiedPicker({selectionCountRemaining}), 449 ) 450 451 if (canceled) return