-86
.detoxrc.js
-86
.detoxrc.js
···
1
-
/** @type {Detox.DetoxConfig} */
2
-
module.exports = {
3
-
testRunner: {
4
-
args: {
5
-
$0: 'jest',
6
-
config: '__e2e__/jest.config.js',
7
-
},
8
-
jest: {
9
-
setupTimeout: 120000,
10
-
},
11
-
},
12
-
apps: {
13
-
'ios.debug': {
14
-
type: 'ios.app',
15
-
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/bluesky.app',
16
-
build:
17
-
'xcodebuild -workspace ios/Bluesky.xcworkspace -scheme Bluesky -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
18
-
},
19
-
'ios.release': {
20
-
type: 'ios.app',
21
-
binaryPath:
22
-
'ios/build/Build/Products/Release-iphonesimulator/bluesky.app',
23
-
build:
24
-
'xcodebuild -workspace ios/Bluesky.xcworkspace -scheme Bluesky -configuration Release -sdk iphonesimulator -derivedDataPath ios/build',
25
-
},
26
-
'android.debug': {
27
-
type: 'android.apk',
28
-
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
29
-
build:
30
-
'cd android ; ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug ; cd -',
31
-
reversePorts: [8081],
32
-
},
33
-
'android.release': {
34
-
type: 'android.apk',
35
-
binaryPath: 'android/app/build/outputs/apk/release/app-release.apk',
36
-
build:
37
-
'cd android ; ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release ; cd -',
38
-
},
39
-
},
40
-
devices: {
41
-
simulator: {
42
-
type: 'ios.simulator',
43
-
device: {
44
-
type: 'iPhone 15 Pro',
45
-
},
46
-
},
47
-
attached: {
48
-
type: 'android.attached',
49
-
device: {
50
-
adbName: '.*',
51
-
},
52
-
},
53
-
emulator: {
54
-
type: 'android.emulator',
55
-
device: {
56
-
avdName: 'Pixel_3a_API_30_x86',
57
-
},
58
-
},
59
-
},
60
-
configurations: {
61
-
'ios.sim.debug': {
62
-
device: 'simulator',
63
-
app: 'ios.debug',
64
-
},
65
-
'ios.sim.release': {
66
-
device: 'simulator',
67
-
app: 'ios.release',
68
-
},
69
-
'android.att.debug': {
70
-
device: 'attached',
71
-
app: 'android.debug',
72
-
},
73
-
'android.att.release': {
74
-
device: 'attached',
75
-
app: 'android.release',
76
-
},
77
-
'android.emu.debug': {
78
-
device: 'emulator',
79
-
app: 'android.debug',
80
-
},
81
-
'android.emu.release': {
82
-
device: 'emulator',
83
-
app: 'android.release',
84
-
},
85
-
},
86
-
}
-1
.eslintrc.js
-1
.eslintrc.js
+30
__e2e__/flows/composer-self-label.yml
+30
__e2e__/flows/composer-self-label.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
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
+
- tapOn:
14
+
id: "composeFAB"
15
+
- inputText: "Post with an image"
16
+
- tapOn:
17
+
id: "openGalleryBtn"
18
+
- tapOn:
19
+
id: "labelsBtn"
20
+
- tapOn:
21
+
label: "Tap on porn"
22
+
point: 78%,67%
23
+
- tapOn:
24
+
label: "Tap on confirm"
25
+
point: 51%,92%
26
+
- tapOn:
27
+
id: "composerPublishBtn"
28
+
- tapOn:
29
+
id: "e2eRefreshHome"
30
+
- assertVisible: "Adult Content"
+87
__e2e__/flows/composer.yml
+87
__e2e__/flows/composer.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
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: "composeFAB"
13
+
- inputText: "Post text only"
14
+
- tapOn:
15
+
id: "composerPublishBtn"
16
+
- assertVisible:
17
+
id: "composeFAB"
18
+
- tapOn:
19
+
id: "composeFAB"
20
+
- inputText: "Post with an image"
21
+
- tapOn:
22
+
id: "openGalleryBtn"
23
+
- tapOn:
24
+
id: "composerPublishBtn"
25
+
- assertVisible:
26
+
id: "composeFAB"
27
+
- tapOn:
28
+
id: "composeFAB"
29
+
- inputText: "Post with a https://example.com link card"
30
+
- tapOn:
31
+
id: "composerPublishBtn"
32
+
- assertVisible:
33
+
id: "composeFAB"
34
+
- tapOn:
35
+
id: "e2eRefreshHome"
36
+
- tapOn:
37
+
id: "replyBtn"
38
+
- inputText: "Reply text only"
39
+
- tapOn:
40
+
id: "composerPublishBtn"
41
+
- assertVisible:
42
+
id: "composeFAB"
43
+
- tapOn:
44
+
id: "replyBtn"
45
+
- inputText: "Reply with an image"
46
+
- tapOn:
47
+
id: "openGalleryBtn"
48
+
- tapOn:
49
+
id: "composerPublishBtn"
50
+
- assertVisible:
51
+
id: "composeFAB"
52
+
- tapOn:
53
+
id: "replyBtn"
54
+
- inputText: "Reply with a https://example.com link card"
55
+
- tapOn:
56
+
id: "composerPublishBtn"
57
+
- assertVisible:
58
+
id: "composeFAB"
59
+
- tapOn:
60
+
id: "repostBtn"
61
+
- tapOn:
62
+
id: "quoteBtn"
63
+
- inputText: "QP text only"
64
+
- tapOn:
65
+
id: "composerPublishBtn"
66
+
- assertVisible:
67
+
id: "composeFAB"
68
+
- tapOn:
69
+
id: "repostBtn"
70
+
- tapOn:
71
+
id: "quoteBtn"
72
+
- inputText: "QP with an image"
73
+
- tapOn:
74
+
id: "openGalleryBtn"
75
+
- tapOn:
76
+
id: "composerPublishBtn"
77
+
- assertVisible:
78
+
id: "composeFAB"
79
+
- tapOn:
80
+
id: "repostBtn"
81
+
- tapOn:
82
+
id: "quoteBtn"
83
+
- inputText: "QP with a https://example.com link card"
84
+
- tapOn:
85
+
id: "composerPublishBtn"
86
+
- assertVisible:
87
+
id: "composeFAB"
+37
__e2e__/flows/create-account.yml
+37
__e2e__/flows/create-account.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
3
+
- runScript:
4
+
file: ../setupServer.js
5
+
env:
6
+
SERVER_PATH: ""
7
+
- runFlow:
8
+
file: ../setupApp.yml
9
+
- tapOn:
10
+
id: "e2eOpenLoggedOutView"
11
+
- tapOn:
12
+
id: "createAccountButton"
13
+
- tapOn:
14
+
id: "selectServiceButton"
15
+
- tapOn:
16
+
id: "customSelectBtn"
17
+
- tapOn:
18
+
id: "customServerTextInput"
19
+
- inputText: "http://localhost:3000"
20
+
- pressKey: Enter
21
+
- tapOn:
22
+
id: "doneBtn"
23
+
- tapOn:
24
+
id: "emailInput"
25
+
- inputText: "example@test.com"
26
+
- tapOn:
27
+
id: "passwordInput"
28
+
- inputText: "hunter2"
29
+
- pressKey: Enter
30
+
- tapOn:
31
+
id: "nextBtn"
32
+
- tapOn:
33
+
id: "handleInput"
34
+
- inputText: "e2e-test"
35
+
- tapOn:
36
+
id: "nextBtn"
37
+
+208
__e2e__/flows/curate-lists.yml
+208
__e2e__/flows/curate-lists.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
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"
14
+
id: "e2eGotoLists"
15
+
- tapOn:
16
+
id: "newUserListBtn"
17
+
- assertVisible:
18
+
id: "createOrEditListModal"
19
+
- tapOn:
20
+
id: "editNameInput"
21
+
- inputText: "Good Ppl"
22
+
- tapOn:
23
+
id: "editDescriptionInput"
24
+
- inputText: "They good"
25
+
- tapOn: "Save"
26
+
- tapOn: "Save"
27
+
- assertNotVisible:
28
+
id: "createOrEditListModal"
29
+
- tapOn: "About"
30
+
- assertVisible: "Good Ppl"
31
+
- assertVisible: "They good"
32
+
33
+
- tapOn:
34
+
label: "Edit display name and description via the edit curatelist modal"
35
+
point: "90%,9%"
36
+
- tapOn: "Edit list details"
37
+
- assertVisible:
38
+
id: "createOrEditListModal"
39
+
- tapOn:
40
+
id: "editNameInput"
41
+
- eraseText
42
+
- inputText: "Bad Ppl"
43
+
- hideKeyboard
44
+
- tapOn:
45
+
id: "editDescriptionInput"
46
+
- eraseText
47
+
- inputText: "They bad"
48
+
- tapOn: "Save"
49
+
- tapOn: "Save"
50
+
- assertNotVisible:
51
+
id: "createOrEditListModal"
52
+
- assertVisible: Bad Ppl
53
+
- assertVisible: They bad
54
+
55
+
- tapOn:
56
+
label: "Remove description via the edit curatelist modal"
57
+
point: "90%,9%"
58
+
- tapOn: "Edit list details"
59
+
- assertVisible:
60
+
id: "createOrEditListModal"
61
+
- tapOn:
62
+
id: "editDescriptionInput"
63
+
- eraseText
64
+
- tapOn: "Save"
65
+
- tapOn: "Save"
66
+
- assertNotVisible:
67
+
id: "createOrEditListModal"
68
+
- assertNotVisible:
69
+
id: "listDescription"
70
+
71
+
- tapOn:
72
+
label: "Delete the curatelist"
73
+
point: "90%,9%"
74
+
- tapOn: "Delete List"
75
+
- tapOn:
76
+
id: "confirmBtn"
77
+
- assertVisible:
78
+
id: "listsEmpty"
79
+
80
+
- tapOn:
81
+
label: "Create a new curatelist"
82
+
id: "e2eGotoLists"
83
+
- tapOn:
84
+
id: "newUserListBtn"
85
+
- assertVisible:
86
+
id: "createOrEditListModal"
87
+
- tapOn:
88
+
id: "editNameInput"
89
+
- inputText: "Good Ppl"
90
+
- tapOn:
91
+
id: "editDescriptionInput"
92
+
- inputText: "They good"
93
+
- tapOn: "Save"
94
+
- tapOn: "Save"
95
+
- assertNotVisible:
96
+
id: "createOrEditListModal"
97
+
- tapOn: "About"
98
+
- assertVisible: "Good Ppl"
99
+
- assertVisible: "They good"
100
+
- tapOn: "About"
101
+
102
+
- tapOn:
103
+
label: "Adds users on curatelists from the list"
104
+
id: "addUserBtn"
105
+
- assertVisible:
106
+
id: "listAddUserModal"
107
+
- tapOn:
108
+
id: "searchInput"
109
+
- inputText: "b"
110
+
- pressKey: Enter
111
+
- tapOn:
112
+
id: "user-bob.test-addBtn"
113
+
- tapOn:
114
+
id: "doneBtn"
115
+
- assertNotVisible:
116
+
id: "listAddUserModal"
117
+
- assertVisible:
118
+
id: "user-bob.test"
119
+
120
+
- tapOn: "Posts"
121
+
- assertVisible:
122
+
label: "Shows posts by the users in the list"
123
+
id: "feedItem-by-bob.test"
124
+
125
+
- tapOn:
126
+
label: "Pins the list"
127
+
id: "pinBtn"
128
+
- tapOn:
129
+
id: "e2eGotoHome"
130
+
- tapOn: "Good Ppl"
131
+
- assertVisible:
132
+
id: "feedItem-by-bob.test"
133
+
- tapOn:
134
+
id: "bottomBarFeedsBtn"
135
+
- tapOn:
136
+
id: "saved-feed-Good Ppl"
137
+
- assertVisible:
138
+
id: "feedItem-by-bob.test"
139
+
- tapOn:
140
+
id: "unpinBtn"
141
+
- tapOn:
142
+
id: "bottomBarHomeBtn"
143
+
- assertNotVisible:
144
+
id: "homeScreenFeedTabs-Good Ppl"
145
+
- tapOn:
146
+
id: "e2eGotoLists"
147
+
- tapOn:
148
+
id: "list-Good Ppl"
149
+
150
+
- tapOn: "About"
151
+
- assertVisible:
152
+
label: "Removes users on curatelists from the list"
153
+
id: "user-bob.test"
154
+
- tapOn:
155
+
point: "90%,43%"
156
+
- assertVisible:
157
+
id: "userAddRemoveListsModal"
158
+
- tapOn:
159
+
id: "user-bob.test-addBtn"
160
+
- tapOn:
161
+
id: "doneBtn"
162
+
- assertNotVisible:
163
+
id: "userAddRemoveListsModal"
164
+
165
+
- tapOn:
166
+
label: "Shows the curatelist on my profile"
167
+
id: "bottomBarProfileBtn"
168
+
- swipe:
169
+
from:
170
+
id: "profilePager-selector"
171
+
direction: LEFT
172
+
- tapOn:
173
+
id: "profilePager-selector-5"
174
+
- tapOn:
175
+
id: "list-Good Ppl"
176
+
177
+
- tapOn:
178
+
label: "Adds and removes users on curatelists from the profile"
179
+
id: "bottomBarSearchBtn"
180
+
- tapOn:
181
+
id: "searchTextInput"
182
+
- inputText: "bob"
183
+
- tapOn:
184
+
id: "searchAutoCompleteResult-bob.test"
185
+
- assertVisible:
186
+
id: "profileView"
187
+
- tapOn:
188
+
id: "profileHeaderDropdownBtn"
189
+
- tapOn: "Add to Lists"
190
+
- assertVisible:
191
+
id: "userAddRemoveListsModal"
192
+
- tapOn:
193
+
id: "user-bob.test-addBtn"
194
+
- tapOn:
195
+
id: "doneBtn"
196
+
- assertNotVisible:
197
+
id: "userAddRemoveListsModal"
198
+
- tapOn:
199
+
id: "profileHeaderDropdownBtn"
200
+
- tapOn: "Add to Lists"
201
+
- assertVisible:
202
+
id: "userAddRemoveListsModal"
203
+
- tapOn:
204
+
id: "user-bob.test-addBtn"
205
+
- tapOn:
206
+
id: "doneBtn"
207
+
- assertNotVisible:
208
+
id: "userAddRemoveListsModal"
+63
__e2e__/flows/home-screen.yml
+63
__e2e__/flows/home-screen.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
3
+
- runScript:
4
+
file: ../setupServer.js
5
+
env:
6
+
SERVER_PATH: ?users&follows&posts&feeds
7
+
- runFlow:
8
+
file: ../setupApp.yml
9
+
- tapOn:
10
+
id: "e2eSignInAlice"
11
+
12
+
- tapOn:
13
+
label: "Can go to feeds page using feeds button in tab bar"
14
+
text: "Feeds ✨"
15
+
- assertVisible: "Discover New Feeds"
16
+
17
+
- tapOn:
18
+
label: "Feeds button disappears after pinning a feed"
19
+
id: "bottomBarProfileBtn"
20
+
- swipe:
21
+
from:
22
+
id: "profilePager-selector"
23
+
direction: LEFT
24
+
- tapOn:
25
+
id: "profilePager-selector-4"
26
+
- tapOn:
27
+
id: "feed-alice-favs"
28
+
- tapOn: "Pin to Home"
29
+
- tapOn:
30
+
id: "bottomBarHomeBtn"
31
+
- assertNotVisible: "Feeds ✨"
32
+
33
+
- tapOn:
34
+
label: "Can like posts"
35
+
id: "likeBtn"
36
+
- assertVisible:
37
+
id: "likeCount"
38
+
text: "1"
39
+
- tapOn:
40
+
id: "likeBtn"
41
+
- assertNotVisible:
42
+
id: "likeCount"
43
+
44
+
- tapOn:
45
+
label: "Can repost posts"
46
+
id: "repostBtn"
47
+
- tapOn: "Repost"
48
+
- assertVisible:
49
+
id: "repostCount"
50
+
text: "1"
51
+
- tapOn:
52
+
id: "repostBtn"
53
+
- tapOn: "Undo repost"
54
+
- assertNotVisible:
55
+
id: "repostCount"
56
+
57
+
- tapOn:
58
+
label: "Can delete posts"
59
+
id: "postDropdownBtn"
60
+
childOf:
61
+
id: "feedItem-by-alice.test"
62
+
- tapOn: "Delete post"
63
+
- tapOn: "Delete"
+26
__e2e__/flows/login.yml
+26
__e2e__/flows/login.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
3
+
- runScript:
4
+
file: ../setupServer.js
5
+
env:
6
+
SERVER_PATH: "?users"
7
+
- runFlow:
8
+
file: ../setupApp.yml
9
+
- tapOn:
10
+
id: "e2eOpenLoggedOutView"
11
+
- tapOn: "Sign in"
12
+
- tapOn:
13
+
id: "selectServiceButton"
14
+
- tapOn: "Custom"
15
+
- tapOn:
16
+
id: "customServerTextInput"
17
+
- inputText: "http://localhost:3000"
18
+
- tapOn: "Done"
19
+
- tapOn:
20
+
id: "loginUsernameInput"
21
+
- inputText: "Alice"
22
+
- tapOn:
23
+
id: "loginPasswordInput"
24
+
- inputText: "hunter2"
25
+
- pressKey: Enter
26
+
- assertVisible: "Following"
+45
__e2e__/flows/mod-lists.yml
+45
__e2e__/flows/mod-lists.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
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:
14
+
id: "e2eGotoModeration"
15
+
- tapOn:
16
+
id: "moderationlistsBtn"
17
+
- tapOn: "New"
18
+
- tapOn:
19
+
id: "editNameInput"
20
+
- inputText: "Muted Users"
21
+
- tapOn:
22
+
id: "editDescriptionInput"
23
+
- inputText: "Shhh"
24
+
- tapOn: "Save"
25
+
- tapOn: "Save"
26
+
27
+
# view modlist
28
+
- assertVisible: "Muted Users"
29
+
- assertVisible: "Shhh"
30
+
31
+
# toggle mute subscription
32
+
- tapOn:
33
+
point: "70%,9%"
34
+
- tapOn: "Mute accounts"
35
+
- tapOn: "Mute list"
36
+
- tapOn: "Unmute"
37
+
38
+
# toggle block subscription
39
+
- tapOn:
40
+
point: "70%,9%"
41
+
- tapOn: "Block accounts"
42
+
- tapOn: "Block list"
43
+
- tapOn: "Unblock"
44
+
45
+
# the rest of the behaviors are tested in curate-lists.yml
+119
__e2e__/flows/profile-screen-edit.yml
+119
__e2e__/flows/profile-screen-edit.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
3
+
- runScript:
4
+
file: ../setupServer.js
5
+
env:
6
+
SERVER_PATH: "?users&posts&feeds"
7
+
- runFlow:
8
+
file: ../setupApp.yml
9
+
- tapOn:
10
+
id: "e2eSignInAlice"
11
+
12
+
13
+
# Navigate to my profile
14
+
- tapOn:
15
+
id: "bottomBarProfileBtn"
16
+
17
+
# Can see feeds
18
+
- swipe:
19
+
from:
20
+
id: "profilePager-selector"
21
+
direction: LEFT
22
+
- tapOn:
23
+
id: "profilePager-selector-4"
24
+
- assertVisible:
25
+
id: "feed-alice-favs"
26
+
- swipe:
27
+
from:
28
+
id: "profilePager-selector"
29
+
direction: RIGHT
30
+
- tapOn:
31
+
id: "profilePager-selector-0"
32
+
33
+
# Open and close edit profile modal
34
+
- tapOn:
35
+
id: "profileHeaderEditProfileButton"
36
+
- assertVisible:
37
+
id: "editProfileModal"
38
+
- tapOn:
39
+
id: "editProfileCancelBtn"
40
+
- assertNotVisible:
41
+
id: "editProfileModal"
42
+
43
+
# Edit display name and description via the edit profile modal
44
+
- tapOn:
45
+
id: "profileHeaderEditProfileButton"
46
+
- assertVisible:
47
+
id: "editProfileModal"
48
+
- tapOn:
49
+
id: "editProfileDisplayNameInput"
50
+
- eraseText
51
+
- inputText: "Alicia"
52
+
- tapOn:
53
+
id: "editProfileDescriptionInput"
54
+
- eraseText
55
+
- inputText: "One cool hacker"
56
+
- tapOn: "Description"
57
+
- tapOn:
58
+
id: "editProfileSaveBtn"
59
+
- assertNotVisible:
60
+
id: "editProfileModal"
61
+
- assertVisible: "Alicia"
62
+
- assertVisible: "One cool hacker"
63
+
64
+
# Remove display name and description via the edit profile modal
65
+
- tapOn:
66
+
id: "profileHeaderEditProfileButton"
67
+
- assertVisible:
68
+
id: "editProfileModal"
69
+
- tapOn:
70
+
id: "editProfileDisplayNameInput"
71
+
- eraseText
72
+
- tapOn:
73
+
id: "editProfileDescriptionInput"
74
+
- eraseText
75
+
- tapOn: "Description"
76
+
- tapOn:
77
+
id: "editProfileSaveBtn"
78
+
- assertNotVisible:
79
+
id: "editProfileModal"
80
+
- assertVisible: "alice.test"
81
+
- assertNotVisible: "One cool hacker"
82
+
83
+
# Set avi and banner via the edit profile modal
84
+
- assertVisible:
85
+
id: "userBannerFallback"
86
+
- tapOn:
87
+
id: "profileHeaderEditProfileButton"
88
+
- assertVisible:
89
+
id: "editProfileModal"
90
+
- tapOn:
91
+
id: "changeBannerBtn"
92
+
- tapOn: "Upload from Library"
93
+
- tapOn:
94
+
id: "changeAvatarBtn"
95
+
- tapOn: "Upload from Library"
96
+
- tapOn:
97
+
id: "editProfileSaveBtn"
98
+
- assertNotVisible:
99
+
id: "editProfileModal"
100
+
- assertVisible:
101
+
id: "userBannerImage"
102
+
103
+
# # Remove avi and banner via the edit profile modal
104
+
- tapOn:
105
+
id: "profileHeaderEditProfileButton"
106
+
- assertVisible:
107
+
id: "editProfileModal"
108
+
- tapOn:
109
+
id: "changeBannerBtn"
110
+
- tapOn: "Remove Banner"
111
+
- tapOn:
112
+
id: "changeAvatarBtn"
113
+
- tapOn: "Remove Avatar"
114
+
- tapOn:
115
+
id: "editProfileSaveBtn"
116
+
- assertNotVisible:
117
+
id: "editProfileModal"
118
+
- assertVisible:
119
+
id: "userBannerFallback"
+37
__e2e__/flows/profile-screen.yml
+37
__e2e__/flows/profile-screen.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
3
+
- runScript:
4
+
file: ../setupServer.js
5
+
env:
6
+
SERVER_PATH: "?users&posts&feeds"
7
+
- runFlow:
8
+
file: ../setupApp.yml
9
+
- tapOn:
10
+
id: "e2eSignInAlice"
11
+
12
+
# Navigate to another user profile
13
+
- tapOn:
14
+
id: "bottomBarSearchBtn"
15
+
- tapOn:
16
+
id: "searchTextInput"
17
+
- inputText: "b"
18
+
- tapOn:
19
+
id: "searchAutoCompleteResult-bob.test"
20
+
- assertVisible:
21
+
id: "profileView"
22
+
23
+
# Can follow/unfollow another user
24
+
- tapOn:
25
+
id: "followBtn"
26
+
- tapOn:
27
+
id: "unfollowBtn"
28
+
29
+
# Can mute/unmute another user
30
+
- tapOn:
31
+
id: "profileHeaderDropdownBtn"
32
+
- tapOn: "Mute Account"
33
+
- assertVisible: "Account Muted"
34
+
- tapOn:
35
+
id: "profileHeaderDropdownBtn"
36
+
- tapOn: "Unmute Account"
37
+
- assertNotVisible: "Account Muted"
+22
__e2e__/flows/search-screen.yml
+22
__e2e__/flows/search-screen.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
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
+
- tapOn:
16
+
id: "searchTextInput"
17
+
- inputText: "b"
18
+
- tapOn:
19
+
id: "searchAutoCompleteResult-bob.test"
20
+
- assertVisible:
21
+
id: "profileView"
22
+
+82
__e2e__/flows/thread-muting.yml
+82
__e2e__/flows/thread-muting.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
3
+
- runScript:
4
+
file: ../setupServer.js
5
+
env:
6
+
SERVER_PATH: "?users&follows"
7
+
- runFlow:
8
+
file: ../setupApp.yml
9
+
10
+
11
+
# Login, create a thread, and log out
12
+
- tapOn:
13
+
id: "e2eSignInAlice"
14
+
- tapOn:
15
+
id: "composeFAB"
16
+
- inputText: "Test thread"
17
+
- tapOn:
18
+
id: "composerPublishBtn"
19
+
20
+
# Login, reply to the thread, and log out
21
+
- tapOn:
22
+
id: "e2eSignInBob"
23
+
- tapOn:
24
+
id: "replyBtn"
25
+
- inputText: "Reply 1"
26
+
- tapOn:
27
+
id: "composerPublishBtn"
28
+
29
+
# Login, confirm notification exists, mute thread, and log out
30
+
- tapOn:
31
+
id: "e2eSignInAlice"
32
+
- tapOn:
33
+
id: "bottomBarNotificationsBtn"
34
+
- assertVisible:
35
+
id: "feedItem-by-bob.test"
36
+
- tapOn:
37
+
id: "feedItem-by-bob.test"
38
+
- tapOn:
39
+
id: "postDropdownBtn"
40
+
childOf:
41
+
id: "postThreadItem-by-bob.test"
42
+
- tapOn: "Mute thread"
43
+
44
+
# Login, reply to the thread twice, and log out
45
+
- tapOn:
46
+
id: "e2eSignInBob"
47
+
- tapOn:
48
+
id: "bottomBarProfileBtn"
49
+
- tapOn:
50
+
id: "profilePager-selector-1"
51
+
- tapOn:
52
+
id: "replyBtn"
53
+
- inputText: "Reply 2"
54
+
- tapOn:
55
+
id: "composerPublishBtn"
56
+
- tapOn:
57
+
id: "replyBtn"
58
+
- inputText: "Reply 3"
59
+
- tapOn:
60
+
id: "composerPublishBtn"
61
+
62
+
63
+
# Login, confirm notifications dont exist, unmute the thread, confirm notifications exist
64
+
- tapOn:
65
+
id: "e2eSignInAlice"
66
+
- tapOn:
67
+
id: "bottomBarNotificationsBtn"
68
+
- assertNotVisible:
69
+
id: "feedItem-by-bob.test"
70
+
- tapOn:
71
+
id: "bottomBarHomeBtn"
72
+
- tapOn:
73
+
id: "postDropdownBtn"
74
+
- tapOn: "Unmute thread"
75
+
- tapOn:
76
+
id: "bottomBarNotificationsBtn"
77
+
- swipe:
78
+
from:
79
+
id: "notifsFeed"
80
+
direction: DOWN
81
+
- assertVisible:
82
+
id: "feedItem-by-bob.test"
+84
__e2e__/flows/thread-screen.yml
+84
__e2e__/flows/thread-screen.yml
···
1
+
appId: xyz.blueskyweb.app
2
+
---
3
+
- runScript:
4
+
file: ../setupServer.js
5
+
env:
6
+
SERVER_PATH: "?users&follows&thread"
7
+
- runFlow:
8
+
file: ../setupApp.yml
9
+
- tapOn:
10
+
id: "e2eSignInAlice"
11
+
12
+
13
+
# Navigate to thread
14
+
- tapOn: "Thread root"
15
+
- assertVisible: "Thread reply"
16
+
17
+
# Can like the root post
18
+
- tapOn:
19
+
id: "likeBtn"
20
+
childOf:
21
+
id: "postThreadItem-by-bob.test"
22
+
- assertVisible:
23
+
id: "likeCount-expanded"
24
+
- tapOn:
25
+
id: "likeBtn"
26
+
childOf:
27
+
id: "postThreadItem-by-bob.test"
28
+
- assertNotVisible:
29
+
id: "likeCount-expanded"
30
+
31
+
# Can like a reply post
32
+
- tapOn:
33
+
id: "likeBtn"
34
+
childOf:
35
+
id: "postThreadItem-by-carla.test"
36
+
- assertVisible:
37
+
id: "likeCount"
38
+
childOf:
39
+
id: "postThreadItem-by-carla.test"
40
+
- tapOn:
41
+
id: "likeBtn"
42
+
childOf:
43
+
id: "postThreadItem-by-carla.test"
44
+
- assertNotVisible:
45
+
id: "likeCount"
46
+
childOf:
47
+
id: "postThreadItem-by-carla.test"
48
+
49
+
# Can repost the root post
50
+
- tapOn:
51
+
id: "repostBtn"
52
+
childOf:
53
+
id: "postThreadItem-by-bob.test"
54
+
- tapOn: "Repost"
55
+
- assertVisible:
56
+
id: "repostCount-expanded"
57
+
- tapOn:
58
+
id: "repostBtn"
59
+
childOf:
60
+
id: "postThreadItem-by-bob.test"
61
+
- tapOn: "Undo repost"
62
+
- assertNotVisible:
63
+
id: "repostCount-expanded"
64
+
65
+
66
+
# Can repost a reply post
67
+
- tapOn:
68
+
id: "repostBtn"
69
+
childOf:
70
+
id: "postThreadItem-by-carla.test"
71
+
- tapOn: "Repost"
72
+
- assertVisible:
73
+
id: "repostCount"
74
+
childOf:
75
+
id: "postThreadItem-by-carla.test"
76
+
- tapOn:
77
+
id: "repostBtn"
78
+
childOf:
79
+
id: "postThreadItem-by-carla.test"
80
+
- tapOn: "Undo repost"
81
+
- assertNotVisible:
82
+
id: "repostCount"
83
+
childOf:
84
+
id: "postThreadItem-by-carla.test"
-12
__e2e__/jest.config.js
-12
__e2e__/jest.config.js
···
1
-
/** @type {import('@jest/types').Config.InitialOptions} */
2
-
module.exports = {
3
-
rootDir: '..',
4
-
testMatch: ['<rootDir>/__e2e__/**/*.test.ts'],
5
-
testTimeout: 120000,
6
-
maxWorkers: 1,
7
-
globalSetup: 'detox/runners/jest/globalSetup',
8
-
globalTeardown: 'detox/runners/jest/globalTeardown',
9
-
reporters: ['detox/runners/jest/reporter'],
10
-
testEnvironment: 'detox/runners/jest/testEnvironment',
11
-
verbose: true,
12
-
}
+1
-1
__e2e__/maestro/scroll.yaml
__e2e__/perf-test.yml
+1
-1
__e2e__/maestro/scroll.yaml
__e2e__/perf-test.yml
+11
__e2e__/setupApp.yml
+11
__e2e__/setupApp.yml
+5
__e2e__/setupServer.js
+5
__e2e__/setupServer.js
-109
__e2e__/tests/composer.test.ts
-109
__e2e__/tests/composer.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {beforeAll, describe, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
6
-
import {createServer, loginAsAlice, openApp, sleep} from '../util'
7
-
8
-
describe('Composer', () => {
9
-
beforeAll(async () => {
10
-
await createServer('?users')
11
-
await openApp({
12
-
permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
13
-
})
14
-
})
15
-
16
-
it('Login', async () => {
17
-
await loginAsAlice()
18
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
19
-
})
20
-
21
-
it('Post text only', async () => {
22
-
await element(by.id('composeFAB')).tap()
23
-
await device.takeScreenshot('1- opened composer')
24
-
await element(by.id('composerTextInput')).typeText('Post text only')
25
-
await device.takeScreenshot('2- entered text')
26
-
await element(by.id('composerPublishBtn')).tap()
27
-
await device.takeScreenshot('3- opened general section')
28
-
await expect(element(by.id('composeFAB'))).toBeVisible()
29
-
})
30
-
31
-
it('Post with an image', async () => {
32
-
await element(by.id('composeFAB')).tap()
33
-
await element(by.id('composerTextInput')).typeText('Post with an image')
34
-
await element(by.id('openGalleryBtn')).tap()
35
-
await sleep(1e3)
36
-
await element(by.id('composerPublishBtn')).tap()
37
-
await expect(element(by.id('composeFAB'))).toBeVisible()
38
-
})
39
-
40
-
it('Post with a link card', async () => {
41
-
await element(by.id('composeFAB')).tap()
42
-
await element(by.id('composerTextInput')).typeText(
43
-
'Post with a https://example.com link card',
44
-
)
45
-
await element(by.id('composerPublishBtn')).tap()
46
-
await expect(element(by.id('composeFAB'))).toBeVisible()
47
-
})
48
-
49
-
it('Reply text only', async () => {
50
-
await element(by.id('e2eRefreshHome')).tap()
51
-
52
-
const post = by.id('feedItem-by-alice.test')
53
-
await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
54
-
await element(by.id('composerTextInput')).typeText('Reply text only')
55
-
await element(by.id('composerPublishBtn')).tap()
56
-
await expect(element(by.id('composeFAB'))).toBeVisible()
57
-
})
58
-
59
-
it('Reply with an image', async () => {
60
-
const post = by.id('feedItem-by-alice.test')
61
-
await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
62
-
await element(by.id('composerTextInput')).typeText('Reply with an image')
63
-
await element(by.id('openGalleryBtn')).tap()
64
-
await sleep(1e3)
65
-
await element(by.id('composerPublishBtn')).tap()
66
-
await expect(element(by.id('composeFAB'))).toBeVisible()
67
-
})
68
-
69
-
it('Reply with a link card', async () => {
70
-
const post = by.id('feedItem-by-alice.test')
71
-
await element(by.id('replyBtn').withAncestor(post)).atIndex(0).tap()
72
-
await element(by.id('composerTextInput')).typeText(
73
-
'Reply with a https://example.com link card',
74
-
)
75
-
await element(by.id('composerPublishBtn')).tap()
76
-
await expect(element(by.id('composeFAB'))).toBeVisible()
77
-
})
78
-
79
-
it('QP text only', async () => {
80
-
const post = by.id('feedItem-by-alice.test')
81
-
await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
82
-
await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
83
-
await element(by.id('composerTextInput')).typeText('QP text only')
84
-
await element(by.id('composerPublishBtn')).tap()
85
-
await expect(element(by.id('composeFAB'))).toBeVisible()
86
-
})
87
-
88
-
it('QP with an image', async () => {
89
-
const post = by.id('feedItem-by-alice.test')
90
-
await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
91
-
await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
92
-
await element(by.id('composerTextInput')).typeText('QP with an image')
93
-
await element(by.id('openGalleryBtn')).tap()
94
-
await sleep(1e3)
95
-
await element(by.id('composerPublishBtn')).tap()
96
-
await expect(element(by.id('composeFAB'))).toBeVisible()
97
-
})
98
-
99
-
it('QP with a link card', async () => {
100
-
const post = by.id('feedItem-by-alice.test')
101
-
await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
102
-
await element(by.id('quoteBtn').withAncestor(by.id('repostModal'))).tap()
103
-
await element(by.id('composerTextInput')).typeText(
104
-
'QP with a https://example.com link card',
105
-
)
106
-
await element(by.id('composerPublishBtn')).tap()
107
-
await expect(element(by.id('composeFAB'))).toBeVisible()
108
-
})
109
-
})
-39
__e2e__/tests/create-account.test.ts
-39
__e2e__/tests/create-account.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {describe, beforeAll, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
import {openApp, createServer} from '../util'
6
-
7
-
describe('Create account', () => {
8
-
let service: string
9
-
beforeAll(async () => {
10
-
service = await createServer('')
11
-
await openApp({permissions: {notifications: 'YES'}})
12
-
})
13
-
14
-
it('I can create a new account', async () => {
15
-
await element(by.id('e2eOpenLoggedOutView')).tap()
16
-
17
-
await element(by.id('createAccountButton')).tap()
18
-
await device.takeScreenshot('1- opened create account screen')
19
-
await element(by.id('selectServiceButton')).tap()
20
-
await device.takeScreenshot('2- selected other server')
21
-
await element(by.id('customSelectBtn')).tap()
22
-
await element(by.id('customServerTextInput')).typeText(service)
23
-
await element(by.id('customServerTextInput')).tapReturnKey()
24
-
await element(by.id('doneBtn')).tap()
25
-
await device.takeScreenshot('3- input test server URL')
26
-
await element(by.id('emailInput')).typeText('example@test.com')
27
-
await element(by.id('passwordInput')).typeText('hunter2')
28
-
await device.takeScreenshot('4- entered account details')
29
-
30
-
await element(by.id('nextBtn')).tap()
31
-
32
-
await element(by.id('handleInput')).typeText('e2e-test')
33
-
await device.takeScreenshot('5- entered handle')
34
-
35
-
await element(by.id('nextBtn')).tap()
36
-
37
-
await expect(element(by.id('onboardingInterests'))).toBeVisible()
38
-
})
39
-
})
-213
__e2e__/tests/curate-lists.test.ts
-213
__e2e__/tests/curate-lists.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {beforeAll, describe, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
6
-
import {createServer, loginAsAlice, loginAsBob, openApp, sleep} from '../util'
7
-
8
-
describe('Curate lists', () => {
9
-
beforeAll(async () => {
10
-
await createServer('?users&follows&posts')
11
-
await openApp({
12
-
permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
13
-
})
14
-
})
15
-
16
-
it('Login and create a curatelists', async () => {
17
-
await loginAsAlice()
18
-
await element(by.id('e2eGotoLists')).tap()
19
-
await element(by.id('newUserListBtn')).tap()
20
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
21
-
await element(by.id('editNameInput')).typeText('Good Ppl')
22
-
await element(by.id('editDescriptionInput')).typeText('They good')
23
-
await element(by.id('saveBtn')).tap()
24
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
25
-
await element(by.text('About')).tap()
26
-
await expect(element(by.id('headerTitle'))).toHaveText('Good Ppl')
27
-
await expect(element(by.id('listDescription'))).toHaveText('They good')
28
-
})
29
-
30
-
it('Edit display name and description via the edit curatelist modal', async () => {
31
-
await element(by.id('headerDropdownBtn')).tap()
32
-
await element(by.text('Edit list details')).tap()
33
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
34
-
await element(by.id('editNameInput')).clearText()
35
-
await element(by.id('editNameInput')).typeText('Bad Ppl')
36
-
await element(by.id('editDescriptionInput')).clearText()
37
-
await element(by.id('editDescriptionInput')).typeText('They bad')
38
-
await element(by.id('saveBtn')).tap()
39
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
40
-
await expect(element(by.id('headerTitle'))).toHaveText('Bad Ppl')
41
-
await expect(element(by.id('listDescription'))).toHaveText('They bad')
42
-
// have to wait for the toast to clear
43
-
await waitFor(element(by.id('headerDropdownBtn')))
44
-
.toBeVisible()
45
-
.withTimeout(5000)
46
-
})
47
-
48
-
it('Remove description via the edit curatelist modal', async () => {
49
-
await element(by.id('headerDropdownBtn')).tap()
50
-
await element(by.text('Edit list details')).tap()
51
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
52
-
await element(by.id('editDescriptionInput')).clearText()
53
-
await element(by.id('saveBtn')).tap()
54
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
55
-
await expect(element(by.id('listDescription'))).not.toBeVisible()
56
-
// have to wait for the toast to clear
57
-
await waitFor(element(by.id('headerDropdownBtn')))
58
-
.toBeVisible()
59
-
.withTimeout(5000)
60
-
})
61
-
62
-
it('Set avi via the edit curatelist modal', async () => {
63
-
await expect(element(by.id('userAvatarFallback'))).toExist()
64
-
await element(by.id('headerDropdownBtn')).tap()
65
-
await element(by.text('Edit list details')).tap()
66
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
67
-
await element(by.id('changeAvatarBtn')).tap()
68
-
await element(by.text('Upload from Library')).tap()
69
-
await sleep(3e3)
70
-
await element(by.id('saveBtn')).tap()
71
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
72
-
await expect(element(by.id('userAvatarImage'))).toExist()
73
-
// have to wait for the toast to clear
74
-
await waitFor(element(by.id('headerDropdownBtn')))
75
-
.toBeVisible()
76
-
.withTimeout(5000)
77
-
})
78
-
79
-
it('Remove avi via the edit curatelist modal', async () => {
80
-
await expect(element(by.id('userAvatarImage'))).toExist()
81
-
await element(by.id('headerDropdownBtn')).tap()
82
-
await element(by.text('Edit list details')).tap()
83
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
84
-
await element(by.id('changeAvatarBtn')).tap()
85
-
await element(by.text('Remove Avatar')).tap()
86
-
await element(by.id('saveBtn')).tap()
87
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
88
-
await expect(element(by.id('userAvatarFallback'))).toExist()
89
-
// have to wait for the toast to clear
90
-
await waitFor(element(by.id('headerDropdownBtn')))
91
-
.toBeVisible()
92
-
.withTimeout(5000)
93
-
})
94
-
95
-
it('Delete the curatelist', async () => {
96
-
await element(by.id('headerDropdownBtn')).tap()
97
-
await element(by.text('Delete List')).tap()
98
-
await element(by.id('confirmBtn')).tap()
99
-
await expect(element(by.id('listsEmpty'))).toBeVisible()
100
-
})
101
-
102
-
it('Create a new curatelist', async () => {
103
-
await element(by.id('e2eGotoLists')).tap()
104
-
await element(by.id('newUserListBtn')).tap()
105
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
106
-
await element(by.id('editNameInput')).typeText('Good Ppl')
107
-
await element(by.id('editDescriptionInput')).typeText('They good')
108
-
await element(by.id('saveBtn')).tap()
109
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
110
-
await element(by.text('About')).tap()
111
-
await expect(element(by.id('headerTitle'))).toHaveText('Good Ppl')
112
-
await expect(element(by.id('listDescription'))).toHaveText('They good')
113
-
})
114
-
115
-
it('Adds users on curatelists from the list', async () => {
116
-
await element(by.text('About')).tap()
117
-
await element(by.id('addUserBtn')).tap()
118
-
await expect(element(by.id('listAddUserModal'))).toBeVisible()
119
-
await element(by.id('searchInput')).typeText('b')
120
-
await waitFor(element(by.id('user-bob.test-addBtn')))
121
-
.toBeVisible()
122
-
.withTimeout(5000)
123
-
await element(by.id('user-bob.test-addBtn')).tap()
124
-
await element(by.id('doneBtn')).tap()
125
-
await expect(element(by.id('listAddUserModal'))).not.toBeVisible()
126
-
await expect(element(by.id('user-bob.test'))).toBeVisible()
127
-
})
128
-
129
-
it('Shows posts by the users in the list', async () => {
130
-
await element(by.text('Posts')).tap()
131
-
await expect(element(by.id('feedItem-by-bob.test'))).toBeVisible()
132
-
})
133
-
134
-
it('Pins the list', async () => {
135
-
await expect(element(by.id('pinBtn'))).toBeVisible()
136
-
await element(by.id('pinBtn')).tap()
137
-
await element(by.id('e2eGotoHome')).tap()
138
-
await element(by.id('homeScreenFeedTabs-Good Ppl')).tap()
139
-
await expect(element(by.id('feedItem-by-bob.test'))).toBeVisible()
140
-
141
-
await element(by.id('bottomBarFeedsBtn')).tap()
142
-
await element(by.id('saved-feed-Good Ppl')).tap()
143
-
await expect(element(by.id('feedItem-by-bob.test'))).toBeVisible()
144
-
145
-
await element(by.id('unpinBtn')).tap()
146
-
await element(by.id('bottomBarHomeBtn')).tap()
147
-
await expect(
148
-
element(by.id('homeScreenFeedTabs-Good Ppl')),
149
-
).not.toBeVisible()
150
-
151
-
await element(by.id('e2eGotoLists')).tap()
152
-
await element(by.id('list-Good Ppl')).tap()
153
-
})
154
-
155
-
it('Removes users on curatelists from the list', async () => {
156
-
await element(by.text('About')).tap()
157
-
await expect(element(by.id('user-bob.test'))).toBeVisible()
158
-
await element(by.id('user-bob.test-editBtn')).tap()
159
-
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
160
-
await element(by.id('user-bob.test-addBtn')).tap()
161
-
await element(by.id('doneBtn')).tap()
162
-
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
163
-
})
164
-
165
-
it('Shows the curatelist on my profile', async () => {
166
-
await element(by.id('bottomBarProfileBtn')).tap()
167
-
await element(by.id('profilePager-selector')).swipe('left')
168
-
await element(by.id('profilePager-selector-5')).tap()
169
-
await element(by.id('list-Good Ppl')).tap()
170
-
})
171
-
172
-
it('Adds and removes users on curatelists from the profile', async () => {
173
-
await element(by.id('bottomBarSearchBtn')).tap()
174
-
await element(by.id('searchTextInput')).typeText('bob')
175
-
await element(by.id('searchAutoCompleteResult-bob.test')).tap()
176
-
await expect(element(by.id('profileView'))).toBeVisible()
177
-
178
-
await element(by.id('profileHeaderDropdownBtn')).tap()
179
-
await element(by.text('Add to Lists')).tap()
180
-
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
181
-
await element(by.id('user-bob.test-addBtn')).tap()
182
-
await element(by.id('doneBtn')).tap()
183
-
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
184
-
185
-
await element(by.id('profileHeaderDropdownBtn')).tap()
186
-
await element(by.text('Add to Lists')).tap()
187
-
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
188
-
await element(by.id('user-bob.test-addBtn')).tap()
189
-
await element(by.id('doneBtn')).tap()
190
-
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
191
-
})
192
-
193
-
it('Can report a user list', async () => {
194
-
await element(by.id('e2eGotoSettings')).tap()
195
-
await element(by.id('signOutBtn')).tap()
196
-
await loginAsBob()
197
-
await element(by.id('bottomBarSearchBtn')).tap()
198
-
await element(by.id('searchTextInput')).typeText('alice')
199
-
await element(by.id('searchAutoCompleteResult-alice.test')).tap()
200
-
await element(by.id('profilePager-selector')).swipe('left')
201
-
await element(by.id('profilePager-selector-3')).tap()
202
-
await element(by.id('list-Good Ppl')).tap()
203
-
await element(by.id('headerDropdownBtn')).tap()
204
-
await element(by.text('Report List')).tap()
205
-
await expect(element(by.id('reportModal'))).toBeVisible()
206
-
await expect(element(by.text('Report List'))).toBeVisible()
207
-
await element(
208
-
by.id('reportReasonRadios-com.atproto.moderation.defs#reasonRude'),
209
-
).tap()
210
-
await element(by.id('sendReportBtn')).tap()
211
-
await expect(element(by.id('reportModal'))).not.toBeVisible()
212
-
})
213
-
})
-110
__e2e__/tests/home-screen.test.ts
-110
__e2e__/tests/home-screen.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {beforeAll, describe, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
6
-
import {createServer, loginAsAlice, openApp} from '../util'
7
-
8
-
describe('Home screen', () => {
9
-
beforeAll(async () => {
10
-
await createServer('?users&follows&posts&feeds')
11
-
await openApp({permissions: {notifications: 'YES'}})
12
-
})
13
-
14
-
it('Login', async () => {
15
-
await loginAsAlice()
16
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
17
-
})
18
-
19
-
it('Can go to feeds page using feeds button in tab bar', async () => {
20
-
await element(by.id('homeScreenFeedTabs-Feeds ✨')).tap()
21
-
await expect(element(by.text('Discover New Feeds'))).toBeVisible()
22
-
})
23
-
24
-
it('Feeds button disappears after pinning a feed', async () => {
25
-
await element(by.id('bottomBarProfileBtn')).tap()
26
-
await element(by.id('profilePager-selector')).swipe('left')
27
-
await element(by.id('profilePager-selector-4')).tap()
28
-
await element(by.id('feed-alice-favs')).tap()
29
-
await element(by.id('pinBtn')).tap()
30
-
await element(by.id('bottomBarHomeBtn')).tap()
31
-
await expect(
32
-
element(by.id('homeScreenFeedTabs-Feeds ✨')),
33
-
).not.toBeVisible()
34
-
})
35
-
36
-
it('Can like posts', async () => {
37
-
const carlaPosts = by.id('feedItem-by-carla.test')
38
-
await expect(
39
-
element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
40
-
).not.toExist()
41
-
await element(by.id('likeBtn').withAncestor(carlaPosts)).atIndex(0).tap()
42
-
await expect(
43
-
element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
44
-
).toHaveText('1')
45
-
await element(by.id('likeBtn').withAncestor(carlaPosts)).atIndex(0).tap()
46
-
await expect(
47
-
element(by.id('likeCount').withAncestor(carlaPosts)).atIndex(0),
48
-
).not.toExist()
49
-
})
50
-
51
-
it('Can repost posts', async () => {
52
-
const carlaPosts = by.id('feedItem-by-carla.test')
53
-
await expect(
54
-
element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
55
-
).not.toExist()
56
-
await element(by.id('repostBtn').withAncestor(carlaPosts)).atIndex(0).tap()
57
-
await expect(element(by.id('repostModal'))).toBeVisible()
58
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
59
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
60
-
await expect(
61
-
element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
62
-
).toHaveText('1')
63
-
await element(by.id('repostBtn').withAncestor(carlaPosts)).atIndex(0).tap()
64
-
await expect(element(by.id('repostModal'))).toBeVisible()
65
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
66
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
67
-
await expect(
68
-
element(by.id('repostCount').withAncestor(carlaPosts)).atIndex(0),
69
-
).not.toExist()
70
-
})
71
-
72
-
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
73
-
// it('Can report posts', async () => {
74
-
// const carlaPosts = by.id('feedItem-by-carla.test')
75
-
// await element(by.id('postDropdownBtn').withAncestor(carlaPosts))
76
-
// .atIndex(0)
77
-
// .tap()
78
-
// await element(by.text('Report post')).tap()
79
-
// await element(by.id('com.atproto.moderation.defs#reasonSpam')).tap()
80
-
// await element(by.id('sendReportBtn')).tap()
81
-
// })
82
-
83
-
it('Can swipe between feeds', async () => {
84
-
await element(by.id('homeScreen')).swipe('left', 'fast', 0.75)
85
-
await expect(element(by.id('customFeedPage'))).toBeVisible()
86
-
await element(by.id('homeScreen')).swipe('right', 'fast', 0.75)
87
-
await expect(element(by.id('followingFeedPage'))).toBeVisible()
88
-
})
89
-
90
-
it('Can tap between feeds', async () => {
91
-
await element(by.id('homeScreenFeedTabs-alice-favs')).tap()
92
-
await expect(element(by.id('customFeedPage'))).toBeVisible()
93
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
94
-
await expect(element(by.id('followingFeedPage'))).toBeVisible()
95
-
})
96
-
97
-
it('Can delete posts', async () => {
98
-
const alicePosts = by.id('feedItem-by-alice.test')
99
-
await expect(element(alicePosts.withDescendant(by.text('Post')))).toExist()
100
-
await element(by.id('postDropdownBtn').withAncestor(alicePosts))
101
-
.atIndex(0)
102
-
.tap()
103
-
await element(by.text('Delete post')).tap()
104
-
await expect(element(by.id('confirmModal'))).toBeVisible()
105
-
await element(by.id('confirmBtn')).tap()
106
-
await expect(
107
-
element(alicePosts.withDescendant(by.text('Post'))),
108
-
).not.toExist()
109
-
})
110
-
})
-47
__e2e__/tests/invite-codes.test.skip.ts
-47
__e2e__/tests/invite-codes.test.skip.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {beforeAll, describe, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
6
-
import {createServer, loginAsAlice, openApp} from '../util'
7
-
8
-
describe('invite-codes', () => {
9
-
let service: string
10
-
let inviteCode = ''
11
-
beforeAll(async () => {
12
-
service = await createServer('?users&invite')
13
-
await openApp({permissions: {notifications: 'YES'}})
14
-
})
15
-
16
-
it('I can fetch invite codes', async () => {
17
-
await loginAsAlice()
18
-
await element(by.id('e2eOpenInviteCodesModal')).tap()
19
-
await expect(element(by.id('inviteCodesModal'))).toBeVisible()
20
-
const attrs = await element(by.id('inviteCode-0-code')).getAttributes()
21
-
inviteCode = attrs.text
22
-
await element(by.id('closeBtn')).tap()
23
-
await element(by.id('e2eSignOut')).tap()
24
-
})
25
-
26
-
it('I can create a new account with the invite code', async () => {
27
-
await element(by.id('e2eOpenLoggedOutView')).tap()
28
-
await element(by.id('createAccountButton')).tap()
29
-
await device.takeScreenshot('1- opened create account screen')
30
-
await element(by.id('selectServiceButton')).tap()
31
-
await device.takeScreenshot('2- selected other server')
32
-
await element(by.id('customSelectBtn')).tap()
33
-
await element(by.id('customServerTextInput')).typeText(service)
34
-
await element(by.id('customServerTextInput')).tapReturnKey()
35
-
await element(by.id('doneBtn')).tap()
36
-
await device.takeScreenshot('3- input test server URL')
37
-
await element(by.id('inviteCodeInput')).typeText(inviteCode)
38
-
await element(by.id('emailInput')).typeText('example@test.com')
39
-
await element(by.id('passwordInput')).typeText('hunter2')
40
-
await device.takeScreenshot('4- entered account details')
41
-
await element(by.id('nextBtn')).tap()
42
-
await element(by.id('handleInput')).typeText('e2e-test')
43
-
await device.takeScreenshot('4- entered handle')
44
-
await element(by.id('nextBtn')).tap()
45
-
await expect(element(by.id('onboardingInterests'))).toBeVisible()
46
-
})
47
-
})
-23
__e2e__/tests/login.test.ts
-23
__e2e__/tests/login.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {describe, beforeAll, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
import {openApp, login, createServer} from '../util'
6
-
7
-
describe('Login', () => {
8
-
let service: string
9
-
beforeAll(async () => {
10
-
service = await createServer('?users')
11
-
await openApp({permissions: {notifications: 'YES'}})
12
-
})
13
-
14
-
it('As Alice, I can login', async () => {
15
-
await element(by.id('e2eOpenLoggedOutView')).tap()
16
-
17
-
await expect(element(by.id('signInButton'))).toBeVisible()
18
-
await login(service, 'alice', 'hunter2', {
19
-
takeScreenshots: true,
20
-
})
21
-
await device.takeScreenshot('5- opened home screen')
22
-
})
23
-
})
-163
__e2e__/tests/merge-feed.test.skip.ts
-163
__e2e__/tests/merge-feed.test.skip.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {describe, beforeAll, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
import {openApp, loginAsAlice, createServer} from '../util'
6
-
7
-
describe('Mergefeed', () => {
8
-
beforeAll(async () => {
9
-
await createServer('?mergefeed')
10
-
await openApp({permissions: {notifications: 'YES'}})
11
-
})
12
-
13
-
it('Login', async () => {
14
-
await element(by.id('e2eOpenLoggedOutView')).tap()
15
-
await loginAsAlice()
16
-
await element(by.id('e2eToggleMergefeed')).tap()
17
-
await element(by.id('bottomBarFeedsBtn')).tap()
18
-
await element(by.id('feed-alice-favs-toggleSave')).tap()
19
-
await element(by.id('e2eGotoHome')).tap()
20
-
})
21
-
22
-
it('Sees the expected mix of posts with default filters', async () => {
23
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
24
-
'down',
25
-
'slow',
26
-
1,
27
-
0.5,
28
-
0.5,
29
-
)
30
-
// followed users
31
-
await expect(
32
-
element(
33
-
by.id('postText').withAncestor(by.id('feedItem-by-carla.test')),
34
-
).atIndex(0),
35
-
).toHaveText('Post 9')
36
-
await expect(
37
-
element(
38
-
by.id('postText').withAncestor(by.id('feedItem-by-bob.test')),
39
-
).atIndex(0),
40
-
).toHaveText('Post 9')
41
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
42
-
'up',
43
-
'fast',
44
-
1,
45
-
0.5,
46
-
0.5,
47
-
)
48
-
// feed users
49
-
await expect(
50
-
element(
51
-
by.id('postText').withAncestor(by.id('feedItem-by-dan.test')),
52
-
).atIndex(0),
53
-
).toHaveText('Post 0')
54
-
})
55
-
56
-
it('Sees the expected mix of posts with replies disabled', async () => {
57
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
58
-
'down',
59
-
'fast',
60
-
1,
61
-
0.5,
62
-
0.5,
63
-
)
64
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
65
-
'down',
66
-
'fast',
67
-
1,
68
-
0.5,
69
-
0.5,
70
-
)
71
-
await element(by.id('viewHeaderHomeFeedPrefsBtn')).tap()
72
-
await element(by.id('toggleRepliesBtn')).tap()
73
-
await element(by.id('confirmBtn')).tap()
74
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
75
-
'down',
76
-
'slow',
77
-
1,
78
-
0.5,
79
-
0.5,
80
-
)
81
-
82
-
// followed users
83
-
await expect(
84
-
element(
85
-
by.id('postText').withAncestor(by.id('feedItem-by-carla.test')),
86
-
).atIndex(0),
87
-
).toHaveText('Post 9')
88
-
await expect(
89
-
element(
90
-
by.id('postText').withAncestor(by.id('feedItem-by-bob.test')),
91
-
).atIndex(0),
92
-
).toHaveText('Post 9')
93
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
94
-
'up',
95
-
'fast',
96
-
1,
97
-
0.5,
98
-
0.5,
99
-
)
100
-
101
-
// feed users
102
-
await expect(
103
-
element(
104
-
by.id('postText').withAncestor(by.id('feedItem-by-dan.test')),
105
-
).atIndex(0),
106
-
).toHaveText('Post 0')
107
-
})
108
-
109
-
it('Sees the expected mix of posts with no follows', async () => {
110
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
111
-
'down',
112
-
'fast',
113
-
1,
114
-
0.5,
115
-
0.5,
116
-
)
117
-
118
-
await element(by.id('bottomBarSearchBtn')).tap()
119
-
await element(by.id('searchTextInput')).typeText('bob')
120
-
await element(by.id('searchAutoCompleteResult-bob.test')).tap()
121
-
await expect(element(by.id('profileView'))).toBeVisible()
122
-
await element(by.id('unfollowBtn')).tap()
123
-
await element(by.id('profileHeaderBackBtn')).tap()
124
-
125
-
// have to wait for the toast to clear
126
-
await waitFor(element(by.id('searchTextInputClearBtn')))
127
-
.toBeVisible()
128
-
.withTimeout(5000)
129
-
await element(by.id('searchTextInputClearBtn')).tap()
130
-
await element(by.id('searchTextInput')).typeText('carla')
131
-
await element(by.id('searchAutoCompleteResult-carla.test')).tap()
132
-
await expect(element(by.id('profileView'))).toBeVisible()
133
-
await element(by.id('unfollowBtn')).tap()
134
-
await element(by.id('profileHeaderBackBtn')).tap()
135
-
136
-
await element(by.id('bottomBarHomeBtn')).tap()
137
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
138
-
'down',
139
-
'slow',
140
-
1,
141
-
0.5,
142
-
0.5,
143
-
)
144
-
await element(by.id('followingFeedPage-feed-flatlist')).swipe(
145
-
'down',
146
-
'slow',
147
-
1,
148
-
0.5,
149
-
0.5,
150
-
)
151
-
152
-
// followed users NOT present
153
-
await expect(element(by.id('feedItem-by-carla.test'))).not.toExist()
154
-
await expect(element(by.id('feedItem-by-bob.test'))).not.toExist()
155
-
156
-
// feed users
157
-
await expect(
158
-
element(
159
-
by.id('postText').withAncestor(by.id('feedItem-by-dan.test')),
160
-
).atIndex(0),
161
-
).toHaveText('Post 0')
162
-
})
163
-
})
-189
__e2e__/tests/mod-lists.test.ts
-189
__e2e__/tests/mod-lists.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {describe, beforeAll, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
import {openApp, loginAsAlice, loginAsBob, createServer} from '../util'
6
-
7
-
describe('Mod lists', () => {
8
-
beforeAll(async () => {
9
-
await createServer('?users&follows&labels')
10
-
await openApp({
11
-
permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
12
-
})
13
-
})
14
-
15
-
it('Login and view my modlists', async () => {
16
-
await loginAsAlice()
17
-
await element(by.id('e2eGotoModeration')).tap()
18
-
await element(by.id('moderationlistsBtn')).tap()
19
-
await expect(element(by.id('list-Muted Users'))).toBeVisible()
20
-
await element(by.id('list-Muted Users')).tap()
21
-
await expect(
22
-
element(by.id('user-muted-by-list-account.test')),
23
-
).toBeVisible()
24
-
})
25
-
26
-
it('Toggle mute subscription', async () => {
27
-
await element(by.id('unmuteBtn')).tap()
28
-
await element(by.id('subscribeBtn')).tap()
29
-
await element(by.text('Mute accounts')).tap()
30
-
await element(by.id('confirmBtn')).tap()
31
-
})
32
-
33
-
it('Edit display name and description via the edit modlist modal', async () => {
34
-
await element(by.id('headerDropdownBtn')).tap()
35
-
await element(by.text('Edit list details')).tap()
36
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
37
-
await element(by.id('editNameInput')).clearText()
38
-
await element(by.id('editNameInput')).typeText('Bad Ppl')
39
-
await element(by.id('editDescriptionInput')).clearText()
40
-
await element(by.id('editDescriptionInput')).typeText('They bad')
41
-
await element(by.id('saveBtn')).tap()
42
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
43
-
await expect(element(by.id('headerTitle'))).toHaveText('Bad Ppl')
44
-
await expect(element(by.id('listDescription'))).toHaveText('They bad')
45
-
// have to wait for the toast to clear
46
-
await waitFor(element(by.id('headerDropdownBtn')))
47
-
.toBeVisible()
48
-
.withTimeout(5000)
49
-
})
50
-
51
-
it('Remove description via the edit modlist modal', async () => {
52
-
await element(by.id('headerDropdownBtn')).tap()
53
-
await element(by.text('Edit list details')).tap()
54
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
55
-
await element(by.id('editDescriptionInput')).clearText()
56
-
await element(by.id('saveBtn')).tap()
57
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
58
-
await expect(element(by.id('listDescription'))).not.toBeVisible()
59
-
// have to wait for the toast to clear
60
-
await waitFor(element(by.id('headerDropdownBtn')))
61
-
.toBeVisible()
62
-
.withTimeout(5000)
63
-
})
64
-
65
-
// DISABLED e2e environment is real finicky about avatar uploads -prf
66
-
// it('Set avi via the edit modlist modal', async () => {
67
-
// await expect(element(by.id('userAvatarFallback'))).toExist()
68
-
// await element(by.id('headerDropdownBtn')).tap()
69
-
// await element(by.text('Edit list details')).tap()
70
-
// await expect(element(by.id('createOrEditListModal'))).toBeVisible()
71
-
// await element(by.id('changeAvatarBtn')).tap()
72
-
// await element(by.text('Library')).tap()
73
-
// await sleep(3e3)
74
-
// await element(by.id('saveBtn')).tap()
75
-
// await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
76
-
// await expect(element(by.id('userAvatarImage'))).toExist()
77
-
// // have to wait for the toast to clear
78
-
// await waitFor(element(by.id('headerDropdownBtn')))
79
-
// .toBeVisible()
80
-
// .withTimeout(5000)
81
-
// })
82
-
83
-
// it('Remove avi via the edit modlist modal', async () => {
84
-
// await expect(element(by.id('userAvatarImage'))).toExist()
85
-
// await element(by.id('headerDropdownBtn')).tap()
86
-
// await element(by.text('Edit list details')).tap()
87
-
// await expect(element(by.id('createOrEditListModal'))).toBeVisible()
88
-
// await element(by.id('changeAvatarBtn')).tap()
89
-
// await element(by.text('Remove')).tap()
90
-
// await element(by.id('saveBtn')).tap()
91
-
// await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
92
-
// await expect(element(by.id('userAvatarFallback'))).toExist()
93
-
// // have to wait for the toast to clear
94
-
// await waitFor(element(by.id('headerDropdownBtn')))
95
-
// .toBeVisible()
96
-
// .withTimeout(5000)
97
-
// })
98
-
99
-
it('Delete the modlist', async () => {
100
-
await element(by.id('headerDropdownBtn')).tap()
101
-
await element(by.text('Delete List')).tap()
102
-
await element(by.id('confirmBtn')).tap()
103
-
await expect(element(by.id('listsEmpty'))).toBeVisible()
104
-
})
105
-
106
-
it('Create a new modlist', async () => {
107
-
await element(by.id('newModListBtn')).tap()
108
-
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
109
-
await element(by.id('editNameInput')).typeText('Bad Ppl')
110
-
await element(by.id('editDescriptionInput')).typeText('They bad')
111
-
await element(by.id('saveBtn')).tap()
112
-
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
113
-
await expect(element(by.id('headerTitle'))).toHaveText('Bad Ppl')
114
-
await expect(element(by.id('listDescription'))).toHaveText('They bad')
115
-
})
116
-
117
-
it('Adds and removes users on modlists from the list', async () => {
118
-
await element(by.id('addUserBtn')).tap()
119
-
await expect(element(by.id('listAddUserModal'))).toBeVisible()
120
-
await waitFor(element(by.id('user-warn-posts.test-addBtn')))
121
-
.toBeVisible()
122
-
.withTimeout(5000)
123
-
await element(by.id('user-warn-posts.test-addBtn')).tap()
124
-
await element(by.id('doneBtn')).tap()
125
-
await expect(element(by.id('listAddUserModal'))).not.toBeVisible()
126
-
await element(by.id('listItems-flatlist')).swipe(
127
-
'down',
128
-
'slow',
129
-
1,
130
-
0.5,
131
-
0.5,
132
-
)
133
-
await expect(element(by.id('user-warn-posts.test'))).toBeVisible()
134
-
await element(by.id('user-warn-posts.test-editBtn')).tap()
135
-
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
136
-
await element(by.id('user-warn-posts.test-addBtn')).tap()
137
-
await element(by.id('doneBtn')).tap()
138
-
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
139
-
})
140
-
141
-
it('Shows the modlist on my profile', async () => {
142
-
await element(by.id('bottomBarProfileBtn')).tap()
143
-
await element(by.id('profilePager-selector')).swipe('left')
144
-
await element(by.id('profilePager-selector-5')).tap()
145
-
await element(by.id('list-Bad Ppl')).tap()
146
-
})
147
-
148
-
it('Adds and removes users on modlists from the profile', async () => {
149
-
await element(by.id('bottomBarSearchBtn')).tap()
150
-
await element(by.id('searchTextInput')).typeText('bob')
151
-
await element(by.id('searchAutoCompleteResult-bob.test')).tap()
152
-
await expect(element(by.id('profileView'))).toBeVisible()
153
-
154
-
await element(by.id('profileHeaderDropdownBtn')).tap()
155
-
await element(by.text('Add to Lists')).tap()
156
-
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
157
-
await element(by.id('user-bob.test-addBtn')).tap()
158
-
await element(by.id('doneBtn')).tap()
159
-
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
160
-
161
-
await element(by.id('profileHeaderDropdownBtn')).tap()
162
-
await element(by.text('Add to Lists')).tap()
163
-
await expect(element(by.id('userAddRemoveListsModal'))).toBeVisible()
164
-
await element(by.id('user-bob.test-addBtn')).tap()
165
-
await element(by.id('doneBtn')).tap()
166
-
await expect(element(by.id('userAddRemoveListsModal'))).not.toBeVisible()
167
-
})
168
-
169
-
it('Can report a mute list', async () => {
170
-
await element(by.id('e2eGotoSettings')).tap()
171
-
await element(by.id('signOutBtn')).tap()
172
-
await loginAsBob()
173
-
await element(by.id('bottomBarSearchBtn')).tap()
174
-
await element(by.id('searchTextInput')).typeText('alice')
175
-
await element(by.id('searchAutoCompleteResult-alice.test')).tap()
176
-
await element(by.id('profilePager-selector')).swipe('left')
177
-
await element(by.id('profilePager-selector-3')).tap()
178
-
await element(by.id('list-Bad Ppl')).tap()
179
-
await element(by.id('headerDropdownBtn')).tap()
180
-
await element(by.text('Report List')).tap()
181
-
await expect(element(by.id('reportModal'))).toBeVisible()
182
-
await expect(element(by.text('Report List'))).toBeVisible()
183
-
await element(
184
-
by.id('reportReasonRadios-com.atproto.moderation.defs#reasonRude'),
185
-
).tap()
186
-
await element(by.id('sendReportBtn')).tap()
187
-
await expect(element(by.id('reportModal'))).not.toBeVisible()
188
-
})
189
-
})
-196
__e2e__/tests/profile-screen.test.ts
-196
__e2e__/tests/profile-screen.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {beforeAll, describe, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
6
-
import {createServer, loginAsAlice, openApp, sleep} from '../util'
7
-
8
-
describe('Profile screen', () => {
9
-
beforeAll(async () => {
10
-
await createServer('?users&posts&feeds')
11
-
await openApp({
12
-
permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
13
-
})
14
-
})
15
-
16
-
it('Login and navigate to my profile', async () => {
17
-
await loginAsAlice()
18
-
await element(by.id('bottomBarProfileBtn')).tap()
19
-
})
20
-
21
-
it('Can see feeds', async () => {
22
-
await element(by.id('profilePager-selector')).swipe('left')
23
-
await element(by.id('profilePager-selector-4')).tap()
24
-
await expect(element(by.id('feed-alice-favs'))).toBeVisible()
25
-
await element(by.id('profilePager-selector')).swipe('right')
26
-
await element(by.id('profilePager-selector-0')).tap()
27
-
})
28
-
29
-
it('Open and close edit profile modal', async () => {
30
-
await element(by.id('profileHeaderEditProfileButton')).tap()
31
-
await expect(element(by.id('editProfileModal'))).toBeVisible()
32
-
await element(by.id('editProfileCancelBtn')).tap()
33
-
await expect(element(by.id('editProfileModal'))).not.toBeVisible()
34
-
})
35
-
36
-
it('Edit display name and description via the edit profile modal', async () => {
37
-
await element(by.id('profileHeaderEditProfileButton')).tap()
38
-
await expect(element(by.id('editProfileModal'))).toBeVisible()
39
-
await element(by.id('editProfileDisplayNameInput')).clearText()
40
-
await element(by.id('editProfileDisplayNameInput')).typeText('Alicia')
41
-
await element(by.id('editProfileDescriptionInput')).clearText()
42
-
await element(by.id('editProfileDescriptionInput')).typeText(
43
-
'One cool hacker',
44
-
)
45
-
await element(by.id('editProfileSaveBtn')).tap()
46
-
await expect(element(by.id('editProfileModal'))).not.toBeVisible()
47
-
await expect(element(by.id('profileHeaderDisplayName'))).toHaveText(
48
-
'Alicia',
49
-
)
50
-
await expect(element(by.id('profileHeaderDescription'))).toHaveText(
51
-
'One cool hacker',
52
-
)
53
-
})
54
-
55
-
it('Remove display name and description via the edit profile modal', async () => {
56
-
await element(by.id('profileHeaderEditProfileButton')).tap()
57
-
await expect(element(by.id('editProfileModal'))).toBeVisible()
58
-
await element(by.id('editProfileDisplayNameInput')).clearText()
59
-
await element(by.id('editProfileDescriptionInput')).clearText()
60
-
await element(by.id('editProfileSaveBtn')).tap()
61
-
await expect(element(by.id('editProfileModal'))).not.toBeVisible()
62
-
await expect(element(by.id('profileHeaderDisplayName'))).toHaveText(
63
-
'alice.test',
64
-
)
65
-
await expect(element(by.id('profileHeaderDescription'))).not.toExist()
66
-
})
67
-
68
-
it('Set avi and banner via the edit profile modal', async () => {
69
-
await expect(element(by.id('userBannerFallback'))).toExist()
70
-
await expect(element(by.id('userAvatarFallback'))).toExist()
71
-
await element(by.id('profileHeaderEditProfileButton')).tap()
72
-
await expect(element(by.id('editProfileModal'))).toBeVisible()
73
-
await element(by.id('changeBannerBtn')).tap()
74
-
await element(by.text('Upload from Library')).tap()
75
-
await sleep(3e3)
76
-
await element(by.id('changeAvatarBtn')).tap()
77
-
await element(by.text('Upload from Library')).tap()
78
-
await sleep(3e3)
79
-
await element(by.id('editProfileSaveBtn')).tap()
80
-
await expect(element(by.id('editProfileModal'))).not.toBeVisible()
81
-
await expect(element(by.id('userBannerImage'))).toExist()
82
-
await expect(element(by.id('userAvatarImage'))).toExist()
83
-
})
84
-
85
-
it('Remove avi and banner via the edit profile modal', async () => {
86
-
await expect(element(by.id('userBannerImage'))).toExist()
87
-
await expect(element(by.id('userAvatarImage'))).toExist()
88
-
await element(by.id('profileHeaderEditProfileButton')).tap()
89
-
await expect(element(by.id('editProfileModal'))).toBeVisible()
90
-
await element(by.id('changeBannerBtn')).tap()
91
-
await element(by.text('Remove Banner')).tap()
92
-
await element(by.id('changeAvatarBtn')).tap()
93
-
await element(by.text('Remove Avatar')).tap()
94
-
await element(by.id('editProfileSaveBtn')).tap()
95
-
await expect(element(by.id('editProfileModal'))).not.toBeVisible()
96
-
await expect(element(by.id('userBannerFallback'))).toExist()
97
-
await expect(element(by.id('userAvatarFallback'))).toExist()
98
-
})
99
-
100
-
it('Navigate to another user profile', async () => {
101
-
await element(by.id('bottomBarSearchBtn')).tap()
102
-
// have to wait for the toast to clear
103
-
await waitFor(element(by.id('searchTextInput')))
104
-
.toBeVisible()
105
-
.withTimeout(5000)
106
-
await element(by.id('searchTextInput')).typeText('bob')
107
-
await element(by.id('searchAutoCompleteResult-bob.test')).tap()
108
-
await expect(element(by.id('profileView'))).toBeVisible()
109
-
})
110
-
111
-
it('Can follow/unfollow another user', async () => {
112
-
await element(by.id('followBtn')).tap()
113
-
await expect(element(by.id('unfollowBtn'))).toBeVisible()
114
-
await element(by.id('unfollowBtn')).tap()
115
-
await expect(element(by.id('followBtn'))).toBeVisible()
116
-
})
117
-
118
-
it('Can mute/unmute another user', async () => {
119
-
await expect(element(by.id('profileHeaderAlert'))).not.toExist()
120
-
await element(by.id('profileHeaderDropdownBtn')).tap()
121
-
await element(by.text('Mute Account')).tap()
122
-
await expect(element(by.id('profileHeaderAlert'))).toBeVisible()
123
-
await element(by.id('profileHeaderDropdownBtn')).tap()
124
-
await element(by.text('Unmute Account')).tap()
125
-
await expect(element(by.id('profileHeaderAlert'))).not.toExist()
126
-
})
127
-
128
-
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
129
-
// it('Can report another user', async () => {
130
-
// await element(by.id('profileHeaderDropdownBtn')).tap()
131
-
// await element(by.text('Report Account')).tap()
132
-
// await expect(element(by.id('reportModal'))).toBeVisible()
133
-
// await element(
134
-
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
135
-
// ).tap()
136
-
// await element(by.id('sendReportBtn')).tap()
137
-
// await expect(element(by.id('reportModal'))).not.toBeVisible()
138
-
// })
139
-
140
-
it('Can like posts', async () => {
141
-
await element(by.id('postsFeed-flatlist')).swipe(
142
-
'down',
143
-
'slow',
144
-
1,
145
-
0.5,
146
-
0.5,
147
-
)
148
-
149
-
const posts = by.id('feedItem-by-bob.test')
150
-
await expect(
151
-
element(by.id('likeCount').withAncestor(posts)).atIndex(0),
152
-
).not.toExist()
153
-
await element(by.id('likeBtn').withAncestor(posts)).atIndex(0).tap()
154
-
await expect(
155
-
element(by.id('likeCount').withAncestor(posts)).atIndex(0),
156
-
).toHaveText('1')
157
-
await element(by.id('likeBtn').withAncestor(posts)).atIndex(0).tap()
158
-
await expect(
159
-
element(by.id('likeCount').withAncestor(posts)).atIndex(0),
160
-
).not.toExist()
161
-
})
162
-
163
-
it('Can repost posts', async () => {
164
-
const posts = by.id('feedItem-by-bob.test')
165
-
await expect(
166
-
element(by.id('repostCount').withAncestor(posts)).atIndex(0),
167
-
).not.toExist()
168
-
await element(by.id('repostBtn').withAncestor(posts)).atIndex(0).tap()
169
-
await expect(element(by.id('repostModal'))).toBeVisible()
170
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
171
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
172
-
await expect(
173
-
element(by.id('repostCount').withAncestor(posts)).atIndex(0),
174
-
).toHaveText('1')
175
-
await element(by.id('repostBtn').withAncestor(posts)).atIndex(0).tap()
176
-
await expect(element(by.id('repostModal'))).toBeVisible()
177
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
178
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
179
-
await expect(
180
-
element(by.id('repostCount').withAncestor(posts)).atIndex(0),
181
-
).not.toExist()
182
-
})
183
-
184
-
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
185
-
// it('Can report posts', async () => {
186
-
// const posts = by.id('feedItem-by-bob.test')
187
-
// await element(by.id('postDropdownBtn').withAncestor(posts)).atIndex(0).tap()
188
-
// await element(by.text('Report post')).tap()
189
-
// await expect(element(by.id('reportModal'))).toBeVisible()
190
-
// await element(
191
-
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
192
-
// ).tap()
193
-
// await element(by.id('sendReportBtn')).tap()
194
-
// await expect(element(by.id('reportModal'))).not.toBeVisible()
195
-
// })
196
-
})
-25
__e2e__/tests/search-screen.test.ts
-25
__e2e__/tests/search-screen.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {describe, beforeAll, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
import {openApp, loginAsAlice, createServer} from '../util'
6
-
7
-
describe('Search screen', () => {
8
-
beforeAll(async () => {
9
-
await createServer('?users')
10
-
await openApp({
11
-
permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
12
-
})
13
-
})
14
-
15
-
it('Login', async () => {
16
-
await loginAsAlice()
17
-
})
18
-
19
-
it('Navigate to another user profile via autocomplete', async () => {
20
-
await element(by.id('bottomBarSearchBtn')).tap()
21
-
await element(by.id('searchTextInput')).typeText('bob')
22
-
await element(by.id('searchAutoCompleteResult-bob.test')).tap()
23
-
await expect(element(by.id('profileView'))).toBeVisible()
24
-
})
25
-
})
-36
__e2e__/tests/self-labeling.test.ts
-36
__e2e__/tests/self-labeling.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {describe, beforeAll, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
import {openApp, loginAsAlice, createServer, sleep} from '../util'
6
-
7
-
describe('Self-labeling', () => {
8
-
beforeAll(async () => {
9
-
await createServer('?users')
10
-
await openApp({
11
-
permissions: {notifications: 'YES', medialibrary: 'YES', photos: 'YES'},
12
-
})
13
-
})
14
-
15
-
it('Login', async () => {
16
-
await loginAsAlice()
17
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
18
-
})
19
-
20
-
it('Post an image with the porn label', async () => {
21
-
await element(by.id('composeFAB')).tap()
22
-
await element(by.id('composerTextInput')).typeText('Post with an image')
23
-
await element(by.id('openGalleryBtn')).tap()
24
-
await sleep(3e3)
25
-
await element(by.id('labelsBtn')).tap()
26
-
await element(by.id('pornLabelBtn')).tap()
27
-
await element(by.id('confirmBtn')).tap()
28
-
await element(by.id('composerPublishBtn')).tap()
29
-
await expect(element(by.id('composeFAB'))).toBeVisible()
30
-
const posts = by.id('feedItem-by-alice.test')
31
-
await element(by.id('e2eRefreshHome')).tap()
32
-
await expect(
33
-
element(by.id('contentHider-embed').withAncestor(posts)).atIndex(0),
34
-
).toExist()
35
-
})
36
-
})
-33
__e2e__/tests/shell.test.skip.ts
-33
__e2e__/tests/shell.test.skip.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {openApp, loginAsAlice, createServer} from '../util'
4
-
5
-
describe('Shell', () => {
6
-
beforeAll(async () => {
7
-
await createServer('?users')
8
-
await openApp({permissions: {notifications: 'YES'}})
9
-
})
10
-
11
-
it('Login', async () => {
12
-
await loginAsAlice()
13
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
14
-
})
15
-
16
-
it('Can swipe the shelf open', async () => {
17
-
await element(by.id('homeScreen')).swipe('right', 'fast', 0.75)
18
-
await expect(element(by.id('drawer'))).toBeVisible()
19
-
await element(by.id('drawer')).swipe('left', 'fast', 0.75)
20
-
await expect(element(by.id('drawer'))).not.toBeVisible()
21
-
})
22
-
23
-
it('Can open the shelf by pressing the header avi', async () => {
24
-
await element(by.id('viewHeaderDrawerBtn')).tap()
25
-
await expect(element(by.id('drawer'))).toBeVisible()
26
-
})
27
-
28
-
it('Can navigate using the shelf', async () => {
29
-
await element(by.id('menuItemButton-Notifications')).tap()
30
-
await expect(element(by.id('drawer'))).not.toBeVisible()
31
-
await expect(element(by.id('notificationsScreen'))).toBeVisible()
32
-
})
33
-
})
-103
__e2e__/tests/thread-muting.test.ts
-103
__e2e__/tests/thread-muting.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {describe, beforeAll, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
import {openApp, loginAsAlice, loginAsBob, createServer} from '../util'
6
-
7
-
describe('Thread muting', () => {
8
-
beforeAll(async () => {
9
-
await createServer('?users&follows')
10
-
await openApp({permissions: {notifications: 'YES'}})
11
-
})
12
-
13
-
it('Login, create a thread, and log out', async () => {
14
-
await loginAsAlice()
15
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
16
-
await element(by.id('composeFAB')).tap()
17
-
await element(by.id('composerTextInput')).typeText('Test thread')
18
-
await element(by.id('composerPublishBtn')).tap()
19
-
await expect(element(by.id('composeFAB'))).toBeVisible()
20
-
})
21
-
22
-
it('Login, reply to the thread, and log out', async () => {
23
-
await loginAsBob()
24
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
25
-
const alicePosts = by.id('feedItem-by-alice.test')
26
-
await element(by.id('replyBtn').withAncestor(alicePosts)).atIndex(0).tap()
27
-
await element(by.id('composerTextInput')).typeText('Reply 1')
28
-
await element(by.id('composerPublishBtn')).tap()
29
-
await expect(element(by.id('composeFAB'))).toBeVisible()
30
-
})
31
-
32
-
it('Login, confirm notification exists, mute thread, and log out', async () => {
33
-
await loginAsAlice()
34
-
await element(by.id('bottomBarNotificationsBtn')).tap()
35
-
const bobNotifs = by.id('feedItem-by-bob.test')
36
-
await expect(
37
-
element(by.id('postText').withAncestor(bobNotifs)).atIndex(0),
38
-
).toHaveText('Reply 1')
39
-
await element(by.id('postDropdownBtn').withAncestor(bobNotifs))
40
-
.atIndex(0)
41
-
.tap()
42
-
await element(by.text('Mute thread')).tap()
43
-
// have to wait for the toast to clear
44
-
await waitFor(element(by.id('viewHeaderDrawerBtn')))
45
-
.toBeVisible()
46
-
.withTimeout(5000)
47
-
})
48
-
49
-
it('Login, reply to the thread twice, and log out', async () => {
50
-
await loginAsBob()
51
-
52
-
await element(by.id('bottomBarProfileBtn')).tap()
53
-
await element(by.id('profilePager-selector-1')).tap()
54
-
const bobPosts = by.id('feedItem-by-bob.test')
55
-
await element(by.id('replyBtn').withAncestor(bobPosts)).atIndex(0).tap()
56
-
await element(by.id('composerTextInput')).typeText('Reply 2')
57
-
await element(by.id('composerPublishBtn')).tap()
58
-
await expect(element(by.id('composeFAB'))).toBeVisible()
59
-
60
-
const alicePosts = by.id('feedItem-by-alice.test')
61
-
await element(by.id('replyBtn').withAncestor(alicePosts)).atIndex(0).tap()
62
-
await element(by.id('composerTextInput')).typeText('Reply 3')
63
-
await element(by.id('composerPublishBtn')).tap()
64
-
await expect(element(by.id('composeFAB'))).toBeVisible()
65
-
66
-
await element(by.id('bottomBarHomeBtn')).tap()
67
-
})
68
-
69
-
it('Login, confirm notifications dont exist, unmute the thread, confirm notifications exist', async () => {
70
-
await loginAsAlice()
71
-
72
-
await element(by.id('bottomBarNotificationsBtn')).tap()
73
-
const bobNotifs = by.id('feedItem-by-bob.test')
74
-
await expect(
75
-
element(by.id('postText').withAncestor(bobNotifs)).atIndex(0),
76
-
).not.toExist()
77
-
78
-
await element(by.id('bottomBarHomeBtn')).tap()
79
-
const alicePosts = by.id('feedItem-by-alice.test')
80
-
await element(by.id('postDropdownBtn').withAncestor(alicePosts))
81
-
.atIndex(0)
82
-
.tap()
83
-
await element(by.text('Unmute thread')).tap()
84
-
85
-
// TODO
86
-
// the swipe down to trigger PTR isnt working and I dont want to block on this
87
-
// -prf
88
-
// await element(by.id('bottomBarNotificationsBtn')).tap()
89
-
// await element(by.id('notifsFeed')).swipe('down', 'fast')
90
-
// await waitFor(element(by.id('postText').withAncestor(bobNotifs)))
91
-
// .toBeVisible()
92
-
// .withTimeout(5000)
93
-
// await expect(
94
-
// element(by.id('postText').withAncestor(bobNotifs)).atIndex(0),
95
-
// ).toHaveText('Reply 2')
96
-
// await expect(
97
-
// element(by.id('postText').withAncestor(bobNotifs)).atIndex(1),
98
-
// ).toHaveText('Reply 3')
99
-
// await expect(
100
-
// element(by.id('postText').withAncestor(bobNotifs)).atIndex(2),
101
-
// ).toHaveText('Reply 1')
102
-
})
103
-
})
-131
__e2e__/tests/thread-screen.test.ts
-131
__e2e__/tests/thread-screen.test.ts
···
1
-
/* eslint-env detox/detox */
2
-
3
-
import {beforeAll, describe, it} from '@jest/globals'
4
-
import {expect} from 'detox'
5
-
6
-
import {createServer, loginAsAlice, openApp} from '../util'
7
-
8
-
describe('Thread screen', () => {
9
-
beforeAll(async () => {
10
-
await createServer('?users&follows&thread')
11
-
await openApp({permissions: {notifications: 'YES'}})
12
-
})
13
-
14
-
it('Login & navigate to thread', async () => {
15
-
await loginAsAlice()
16
-
await element(by.id('homeScreenFeedTabs-Following')).tap()
17
-
await element(by.id('feedItem-by-bob.test')).atIndex(0).tap()
18
-
await expect(
19
-
element(
20
-
by
21
-
.id('postThreadItem-by-bob.test')
22
-
.withDescendant(by.text('Thread root')),
23
-
),
24
-
).toBeVisible()
25
-
await expect(
26
-
element(
27
-
by
28
-
.id('postThreadItem-by-carla.test')
29
-
.withDescendant(by.text('Thread reply')),
30
-
),
31
-
).toBeVisible()
32
-
})
33
-
34
-
it('Can like the root post', async () => {
35
-
const post = by.id('postThreadItem-by-bob.test')
36
-
await expect(
37
-
element(by.id('likeCount-expanded').withAncestor(post)).atIndex(0),
38
-
).not.toExist()
39
-
await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
40
-
await expect(
41
-
element(by.id('likeCount-expanded').withAncestor(post)).atIndex(0),
42
-
).toHaveText('1 like')
43
-
await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
44
-
await expect(
45
-
element(by.id('likeCount-expanded').withAncestor(post)).atIndex(0),
46
-
).not.toExist()
47
-
})
48
-
49
-
it('Can like a reply post', async () => {
50
-
const post = by.id('postThreadItem-by-carla.test')
51
-
await expect(
52
-
element(by.id('likeCount').withAncestor(post)).atIndex(0),
53
-
).not.toExist()
54
-
await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
55
-
await expect(
56
-
element(by.id('likeCount').withAncestor(post)).atIndex(0),
57
-
).toHaveText('1')
58
-
await element(by.id('likeBtn').withAncestor(post)).atIndex(0).tap()
59
-
await expect(
60
-
element(by.id('likeCount').withAncestor(post)).atIndex(0),
61
-
).not.toExist()
62
-
})
63
-
64
-
it('Can repost the root post', async () => {
65
-
const post = by.id('postThreadItem-by-bob.test')
66
-
await expect(
67
-
element(by.id('repostCount-expanded').withAncestor(post)).atIndex(0),
68
-
).not.toExist()
69
-
await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
70
-
await expect(element(by.id('repostModal'))).toBeVisible()
71
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
72
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
73
-
await expect(
74
-
element(by.id('repostCount-expanded').withAncestor(post)).atIndex(0),
75
-
).toHaveText('1 repost')
76
-
await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
77
-
await expect(element(by.id('repostModal'))).toBeVisible()
78
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
79
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
80
-
await expect(
81
-
element(by.id('repostCount-expanded').withAncestor(post)).atIndex(0),
82
-
).not.toExist()
83
-
})
84
-
85
-
it('Can repost a reply post', async () => {
86
-
const post = by.id('postThreadItem-by-carla.test')
87
-
await expect(
88
-
element(by.id('repostCount').withAncestor(post)).atIndex(0),
89
-
).not.toExist()
90
-
await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
91
-
await expect(element(by.id('repostModal'))).toBeVisible()
92
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
93
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
94
-
await expect(
95
-
element(by.id('repostCount').withAncestor(post)).atIndex(0),
96
-
).toHaveText('1')
97
-
await element(by.id('repostBtn').withAncestor(post)).atIndex(0).tap()
98
-
await expect(element(by.id('repostModal'))).toBeVisible()
99
-
await element(by.id('repostBtn').withAncestor(by.id('repostModal'))).tap()
100
-
await expect(element(by.id('repostModal'))).not.toBeVisible()
101
-
await expect(
102
-
element(by.id('repostCount').withAncestor(post)).atIndex(0),
103
-
).not.toExist()
104
-
})
105
-
106
-
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
107
-
// it('Can report the root post', async () => {
108
-
// const post = by.id('postThreadItem-by-bob.test')
109
-
// await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
110
-
// await element(by.text('Report post')).tap()
111
-
// await expect(element(by.id('reportModal'))).toBeVisible()
112
-
// await element(
113
-
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
114
-
// ).tap()
115
-
// await element(by.id('sendReportBtn')).tap()
116
-
// await expect(element(by.id('reportModal'))).not.toBeVisible()
117
-
// })
118
-
119
-
// TODO skipping because the test env PDS isnt setup correctly to handle the report -prf
120
-
// it('Can report a reply post', async () => {
121
-
// const post = by.id('postThreadItem-by-carla.test')
122
-
// await element(by.id('postDropdownBtn').withAncestor(post)).atIndex(0).tap()
123
-
// await element(by.text('Report post')).tap()
124
-
// await expect(element(by.id('reportModal'))).toBeVisible()
125
-
// await element(
126
-
// by.id('reportReasonRadios-com.atproto.moderation.defs#reasonSpam'),
127
-
// ).tap()
128
-
// await element(by.id('sendReportBtn')).tap()
129
-
// await expect(element(by.id('reportModal'))).not.toBeVisible()
130
-
// })
131
-
})
-141
__e2e__/util.ts
-141
__e2e__/util.ts
···
1
-
import {execSync} from 'child_process'
2
-
import {resolveConfig} from 'detox/internals'
3
-
import http from 'http'
4
-
5
-
const platform = device.getPlatform()
6
-
7
-
export async function openApp(opts: any) {
8
-
opts = opts || {}
9
-
const config = await resolveConfig()
10
-
11
-
if (device.getPlatform() === 'ios') {
12
-
// disable password autofill
13
-
execSync(
14
-
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/UserSettings.plist`,
15
-
)
16
-
execSync(
17
-
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/EffectiveUserSettings.plist`,
18
-
)
19
-
execSync(
20
-
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/PublicInfo/PublicEffectiveUserSettings.plist`,
21
-
)
22
-
}
23
-
if (config.configurationName.split('.').includes('debug')) {
24
-
return await openAppForDebugBuild(platform, opts)
25
-
} else {
26
-
return await device.launchApp({
27
-
...opts,
28
-
newInstance: true,
29
-
})
30
-
}
31
-
}
32
-
33
-
export async function isVisible(id: string) {
34
-
try {
35
-
await expect(element(by.id(id))).toBeVisible()
36
-
return true
37
-
} catch (e) {
38
-
return false
39
-
}
40
-
}
41
-
42
-
export async function login(
43
-
service: string,
44
-
username: string,
45
-
password: string,
46
-
{takeScreenshots} = {takeScreenshots: false},
47
-
) {
48
-
await element(by.id('signInButton')).tap()
49
-
if (takeScreenshots) {
50
-
await device.takeScreenshot('1- opened sign-in screen')
51
-
}
52
-
if (await isVisible('chooseAccountForm')) {
53
-
await element(by.id('chooseNewAccountBtn')).tap()
54
-
}
55
-
await element(by.id('selectServiceButton')).tap()
56
-
if (takeScreenshots) {
57
-
await device.takeScreenshot('2- opened service selector')
58
-
}
59
-
await element(by.id('customSelectBtn')).tap()
60
-
await element(by.id('customServerTextInput')).typeText(service)
61
-
await element(by.id('customServerTextInput')).tapReturnKey()
62
-
await element(by.id('doneBtn')).tap()
63
-
if (takeScreenshots) {
64
-
await device.takeScreenshot('3- input custom service')
65
-
}
66
-
await element(by.id('loginUsernameInput')).typeText(username)
67
-
await element(by.id('loginPasswordInput')).typeText(password)
68
-
if (takeScreenshots) {
69
-
await device.takeScreenshot('4- entered username and password')
70
-
}
71
-
await element(by.id('loginNextButton')).tap()
72
-
}
73
-
74
-
export async function loginAsAlice() {
75
-
await element(by.id('e2eSignInAlice')).tap()
76
-
}
77
-
78
-
export async function loginAsBob() {
79
-
await element(by.id('e2eSignInBob')).tap()
80
-
}
81
-
82
-
async function openAppForDebugBuild(platform: string, opts: any) {
83
-
const deepLinkUrl = // Local testing with packager
84
-
/*process.env.EXPO_USE_UPDATES
85
-
? // Testing latest published EAS update for the test_debug channel
86
-
getDeepLinkUrl(getLatestUpdateUrl())
87
-
: */ getDeepLinkUrl(getDevLauncherPackagerUrl(platform))
88
-
89
-
if (platform === 'ios') {
90
-
await device.launchApp({
91
-
...opts,
92
-
newInstance: true,
93
-
})
94
-
sleep(3000)
95
-
await device.openURL({
96
-
url: deepLinkUrl,
97
-
})
98
-
} else {
99
-
await device.launchApp({
100
-
...opts,
101
-
newInstance: true,
102
-
url: deepLinkUrl,
103
-
})
104
-
}
105
-
106
-
await sleep(3000)
107
-
}
108
-
109
-
export async function createServer(path = ''): Promise<string> {
110
-
return new Promise(function (resolve, reject) {
111
-
var req = http.request(
112
-
{
113
-
method: 'POST',
114
-
host: 'localhost',
115
-
port: 1986,
116
-
path: `/${path}`,
117
-
},
118
-
function (res) {
119
-
const body: Buffer[] = []
120
-
res.on('data', chunk => body.push(chunk))
121
-
res.on('end', function () {
122
-
try {
123
-
resolve(Buffer.concat(body).toString())
124
-
} catch (e) {
125
-
reject(e)
126
-
}
127
-
})
128
-
},
129
-
)
130
-
req.on('error', reject)
131
-
req.end()
132
-
})
133
-
}
134
-
135
-
const getDeepLinkUrl = (url: string) =>
136
-
`expo+bluesky://expo-development-client/?url=${encodeURIComponent(url)}`
137
-
138
-
const getDevLauncherPackagerUrl = (platform: string) =>
139
-
`http://localhost:8081/index.bundle?platform=${platform}&dev=true&minify=false&disableOnboarding=1`
140
-
141
-
export const sleep = (t: number) => new Promise(res => setTimeout(res, t))
+1
-8
docs/build.md
+1
-8
docs/build.md
···
16
16
- Add `eval "$(rbenv init - zsh)"` to your `~/.zshrc`
17
17
- From inside the project directory:
18
18
- `bundler install` (this will install Cocoapods)
19
-
- Setup your environment [for e2e testing using detox](https://wix.github.io/Detox/docs/introduction/getting-started):
20
-
- `yarn global add detox-cli`
21
-
- `brew tap wix/brew`
22
-
- `brew install applesimutils`
23
19
- After initial setup:
24
20
- Copy `google-services.json.example` to `google-services.json` or provide your own `google-services.json`. (A real firebase project is NOT required)
25
21
- `npx expo prebuild` -> you will also need to run this anytime `app.json` or native `package.json` deps change
···
120
116
121
117
### Running E2E Tests
122
118
123
-
- Make sure you've set your environment following the above
124
-
- Make sure Metro and the dev server are running
125
-
- Run `yarn e2e`
126
-
- Find the artifacts in the `artifact` folder
119
+
See [testing.md](./testing.md).
127
120
128
121
### Polyfills
129
122
+10
-4
docs/testing.md
+10
-4
docs/testing.md
···
3
3
Make sure you've copied `.env.example` to `.env.test` and provided any required
4
4
values.
5
5
6
-
### Using Maestro E2E tests
6
+
## Using Maestro
7
+
7
8
1. Install Maestro by following [these instructions](https://maestro.mobile.dev/getting-started/installing-maestro). This will help us run the E2E tests.
8
-
2. You can write Maestro tests in `__e2e__/maestro` directory by creating a new `.yaml` file or by modifying an existing one.
9
-
3. You can also use [Maestro Studio](https://maestro.mobile.dev/getting-started/maestro-studio) which automatically generates commands by recording your actions on the app. Therefore, you can create realistic tests without having to manually write any code. Use the `maestro studio` command to start recording your actions.
9
+
2. You can write Maestro tests in `/.maestro/flows/` directory by creating a new `.yml` file or by modifying an existing one.
10
+
3. You can also use [Maestro Studio](https://maestro.mobile.dev/getting-started/maestro-studio) which automatically generates commands by recording your actions on the app. Therefore, you can create realistic tests without having to manually write any code. Use the `maestro studio` command to start recording your actions.
10
11
12
+
### Running Maestro tests
11
13
12
-
### Using Flashlight for Performance Testing
14
+
- In one tab, run `yarn e2e:mock-server`
15
+
- In a second tab, run `yarn e2e:metro`
16
+
- In a third tab, run `yarn e2e:run`
17
+
18
+
## Using Flashlight for Performance Testing
13
19
1. Make sure Maestro is installed (optional: only for automated testing) by following the instructions above
14
20
2. Install Flashlight by following [these instructions](https://docs.flashlight.dev/)
15
21
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)
+1
-2
jest/test-pds.ts
+1
-2
jest/test-pds.ts
+3
-6
package.json
+3
-6
package.json
···
31
31
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src",
32
32
"typecheck": "tsc --project ./tsconfig.check.json",
33
33
"e2e:mock-server": "./jest/dev-infra/with-test-redis-and-db.sh ts-node --project tsconfig.e2e.json __e2e__/mock-server.ts",
34
-
"e2e:metro": "NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
35
-
"e2e:build": "NODE_ENV=test detox build -c ios.sim.debug",
36
-
"e2e:run": "NODE_ENV=test detox test --configuration ios.sim.debug --take-screenshots all",
34
+
"e2e:metro": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
35
+
"e2e:run": "maestro test __e2e__",
37
36
"perf:test": "NODE_ENV=test maestro test",
38
-
"perf:test:run": "NODE_ENV=test maestro test __e2e__/maestro/scroll.yaml",
37
+
"perf:test:run": "NODE_ENV=test maestro test __e2e__/perf-test.yml",
39
38
"perf:test:measure": "NODE_ENV=test flashlight test --bundleId xyz.blueskyweb.app --testCommand \"yarn perf:test\" --duration 150000 --resultsFilePath .perf/results.json",
40
39
"perf:test:results": "NODE_ENV=test flashlight report .perf/results.json",
41
40
"perf:measure": "NODE_ENV=test flashlight measure",
···
239
238
"babel-plugin-module-resolver": "^5.0.0",
240
239
"babel-plugin-react-native-web": "^0.18.12",
241
240
"babel-preset-expo": "^10.0.0",
242
-
"detox": "^20.14.8",
243
241
"eslint": "^8.19.0",
244
242
"eslint-plugin-bsky-internal": "link:./eslint",
245
-
"eslint-plugin-detox": "^1.0.0",
246
243
"eslint-plugin-ft-flow": "^2.0.3",
247
244
"eslint-plugin-lingui": "^0.2.0",
248
245
"eslint-plugin-react": "^7.33.2",
+7
-4
src/view/com/testing/TestCtrls.e2e.tsx
+7
-4
src/view/com/testing/TestCtrls.e2e.tsx
···
1
1
import React from 'react'
2
-
import {Pressable, View} from 'react-native'
3
-
import {navigate} from '../../../Navigation'
2
+
import {LogBox, Pressable, View} from 'react-native'
3
+
import {useQueryClient} from '@tanstack/react-query'
4
+
4
5
import {useModalControls} from '#/state/modals'
5
-
import {useQueryClient} from '@tanstack/react-query'
6
+
import {useSetFeedViewPreferencesMutation} from '#/state/queries/preferences'
6
7
import {useSessionApi} from '#/state/session'
7
-
import {useSetFeedViewPreferencesMutation} from '#/state/queries/preferences'
8
8
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
9
+
import {navigate} from '../../../Navigation'
10
+
11
+
LogBox.ignoreAllLogs()
9
12
10
13
/**
11
14
* This utility component is only included in the test simulator
+1
src/view/com/util/Toast.e2e.tsx
+1
src/view/com/util/Toast.e2e.tsx
···
1
+
export function show() {}
-48
yarn.lock
-48
yarn.lock
···
10719
10719
address "^1.0.1"
10720
10720
debug "^2.6.0"
10721
10721
10722
-
detox@^20.14.8:
10723
-
version "20.14.8"
10724
-
resolved "https://registry.yarnpkg.com/detox/-/detox-20.14.8.tgz#0a550cf677fc98a68d56d162e1c5caad317de9ca"
10725
-
integrity sha512-3E/0/7Cb7x+wcBsZpCxD8FykZUsFnfVT00d6PWH940boc0Mo1Kzabh+I151X/On4qZMqVdUzgwmap/z8g/kmaw==
10726
-
dependencies:
10727
-
ajv "^8.6.3"
10728
-
bunyan "^1.8.12"
10729
-
bunyan-debug-stream "^3.1.0"
10730
-
caf "^15.0.1"
10731
-
chalk "^4.0.0"
10732
-
child-process-promise "^2.2.0"
10733
-
execa "^5.1.1"
10734
-
find-up "^5.0.0"
10735
-
fs-extra "^11.0.0"
10736
-
funpermaproxy "^1.1.0"
10737
-
glob "^8.0.3"
10738
-
ini "^1.3.4"
10739
-
jest-environment-emit "^1.0.5"
10740
-
json-cycle "^1.3.0"
10741
-
lodash "^4.17.11"
10742
-
multi-sort-stream "^1.0.3"
10743
-
multipipe "^4.0.0"
10744
-
node-ipc "9.2.1"
10745
-
proper-lockfile "^3.0.2"
10746
-
resolve-from "^5.0.0"
10747
-
sanitize-filename "^1.6.1"
10748
-
semver "^7.0.0"
10749
-
serialize-error "^8.0.1"
10750
-
shell-quote "^1.7.2"
10751
-
signal-exit "^3.0.3"
10752
-
stream-json "^1.7.4"
10753
-
strip-ansi "^6.0.1"
10754
-
telnet-client "1.2.8"
10755
-
tempfile "^2.0.0"
10756
-
trace-event-lib "^1.3.1"
10757
-
which "^1.3.1"
10758
-
ws "^7.0.0"
10759
-
yargs "^17.0.0"
10760
-
yargs-parser "^21.0.0"
10761
-
yargs-unparser "^2.0.0"
10762
-
10763
10722
didyoumean@^1.2.2:
10764
10723
version "1.2.2"
10765
10724
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
···
11392
11351
"eslint-plugin-bsky-internal@link:./eslint":
11393
11352
version "0.0.0"
11394
11353
uid ""
11395
-
11396
-
eslint-plugin-detox@^1.0.0:
11397
-
version "1.0.0"
11398
-
resolved "https://registry.yarnpkg.com/eslint-plugin-detox/-/eslint-plugin-detox-1.0.0.tgz#2d9c0130e8ebc4ced56efb6eeaf0d0f5c163398d"
11399
-
integrity sha512-Dd+Cwyap5IO9DBKXOKrQTE1RQk9hvSSi+qsS1cMVPZY37mojz2PvriEOfGhKj5XN1G14lJ8TArf+6Y+Np2ZsoQ==
11400
-
dependencies:
11401
-
requireindex "~1.1.0"
11402
11354
11403
11355
eslint-plugin-eslint-comments@^3.2.0:
11404
11356
version "3.2.0"