Screenshot tests (#130)

* #9 Add snapshot testing library

* #9 Create script to boot test simulators

* #9 Create the UI test plan

* #9 Create shared schemes for test targets

* #9 Disable split view for UI tests

* #9 Fix fastlane dependencies

* #9 Add snapshot testing to the application

* #9 assert screenshots

* #9 fix swipe gestures on iPad

* #9 Fix accessing items in session verification screen

* #9 Workaround for flaky unit test

* #9 Specify scheme for alpha build

* #9 Add reference screenshots

* Update python script path and check assets for png check

* Update script path

* Use static timezone for simulator time

* Fix build after SwiftFormat

* Add changelog

* Upload failed screenshots artifact

* Always upload artifacts

* Update boot simulator script

* Update simulator overridden time

* Install pytz before tests

* Get time from Ruby script

* Disable SwiftUI animation when running UI tests

* Update screenshots after animation setting

* Include reference images in the artifact

* Update matching precision

* Update image matching precision & revert artifact content

* Include Xcode result in the artifact

* Update test output directory

* Disable gradient on splash screen for tests

* Tap next button explicitly

* Wait a bit before checking alert

* Wait 1 second

* Run SwiftFormat on project

* Ignore temporary screenshots

* Fix most of the PR remarks

* Fix conflicts

* Bump Python version to 3

* Update reference screenshots for authentication screens

* Update SwiftFormat

* Fix flakey session verification test.

* Update scheme.

Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
ismailgulek 2022-08-11 15:02:47 +03:00 committed by GitHub
parent 207cbdebfd
commit 2cb6dc1cd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
127 changed files with 944 additions and 83 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
UITests/Sources/__Snapshots__/** filter=lfs diff=lfs merge=lfs -text

View File

@ -17,7 +17,14 @@ jobs:
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- name: Setup Xcode version
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: '13.4'
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
lfs: true
- uses: actions/cache@v3 - uses: actions/cache@v3
with: with:
@ -33,6 +40,15 @@ jobs:
- name: Run tests - name: Run tests
run: bundle exec fastlane tests run: bundle exec fastlane tests
- name: Archive artifacts
uses: actions/upload-artifact@v3
if: always()
with:
name: test-output
path: fastlane/test_output
retention-days: 7
if-no-files-found: ignore
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3

11
.gitignore vendored
View File

@ -38,3 +38,14 @@ Tools/Scripts/element-android
## macOS Files ## macOS Files
.DS_Store .DS_Store
._* ._*
## Temporary Screenshots
# ignore all
/UITests/Sources/__Snapshots__/Application/*
# but keep the references
!/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPad-9th-generation.*.png
!/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPad-9th-generation.*.png
!/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPad-9th-generation.*.png
!/UITests/Sources/__Snapshots__/Application/15-5-de-DE-iPhone-13-Pro-Max.*.png
!/UITests/Sources/__Snapshots__/Application/15-5-fr-FR-iPhone-13-Pro-Max.*.png
!/UITests/Sources/__Snapshots__/Application/15-5-en-GB-iPhone-13-Pro-Max.*.png

View File

@ -77,7 +77,7 @@ if hasChangedViews {
} }
// Check for pngs on resources // Check for pngs on resources
let hasPngs = !editedFiles.filter { $0.lowercased().hasSuffix(".png") }.isEmpty let hasPngs = !editedFiles.filter { $0.lowercased().contains(".xcassets") && $0.lowercased().hasSuffix(".png") }.isEmpty
if hasPngs { if hasPngs {
warn("You seem to have made changes to some images. Please consider using an SVG or PDF.") warn("You seem to have made changes to some resource images. Please consider using an SVG or PDF.")
} }

View File

@ -100,6 +100,7 @@
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; }; 4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */; };
490E606044B18985055FF690 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */; }; 490E606044B18985055FF690 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */; };
492274DA6691EE985C2FCCAA /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 21C83087604B154AA30E9A8F /* SnapshotTesting */; };
499A26EB06C97E48C27A2DB9 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; }; 499A26EB06C97E48C27A2DB9 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; };
49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D58333B377888012740101 /* LoginViewModel.swift */; }; 49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D58333B377888012740101 /* LoginViewModel.swift */; };
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; }; 49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; };
@ -265,6 +266,7 @@
D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; }; D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; };
D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; }; D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; };
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; }; D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4756C5A8C8649AD6C10C615 /* MockUserSession.swift */; };
D85D4FA590305180B4A41795 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3073CCD77D906B330BC1D6 /* Tests.swift */; };
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */; }; D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */; };
D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */; }; D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */; };
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC95CD75B688E946438165 /* Coordinator.swift */; }; DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC95CD75B688E946438165 /* Coordinator.swift */; };
@ -295,6 +297,7 @@
FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; }; FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */; };
FC6B7436C3A5B3D0565227D5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF05352F28D4E7336228E9F4 /* ActivityIndicatorView.swift */; }; FC6B7436C3A5B3D0565227D5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF05352F28D4E7336228E9F4 /* ActivityIndicatorView.swift */; };
FCB640C576292BEAF7FA3B2E /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F395A2E917115C7AAF7F34 /* SplashViewController.swift */; }; FCB640C576292BEAF7FA3B2E /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F395A2E917115C7AAF7F34 /* SplashViewController.swift */; };
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1593DD87F974F8509BB619 /* ElementAnimations.swift */; };
FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA154570F693D93513E584C1 /* RoomMessageFactory.swift */; }; FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA154570F693D93513E584C1 /* RoomMessageFactory.swift */; };
FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */; }; FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -536,6 +539,7 @@
8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; }; 8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; };
8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = "<group>"; };
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = "<group>"; };
9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = "<group>"; }; 9010EE0CC913D095887EF36E /* OIDCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCService.swift; sourceTree = "<group>"; };
90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = "<group>"; }; 90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = "<group>"; };
92B61C243325DC76D3086494 /* EventBriefFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactoryProtocol.swift; sourceTree = "<group>"; }; 92B61C243325DC76D3086494 /* EventBriefFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactoryProtocol.swift; sourceTree = "<group>"; };
@ -613,6 +617,7 @@
B83CB897B183BF3C33715F55 /* bn-IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-IN"; path = "bn-IN.lproj/Localizable.stringsdict"; sourceTree = "<group>"; }; B83CB897B183BF3C33715F55 /* bn-IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-IN"; path = "bn-IN.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
B8A56EA2A5AE726F445CB2E3 /* eo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = eo; path = eo.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; B8A56EA2A5AE726F445CB2E3 /* eo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = eo; path = eo.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; }; B902EA6CD3296B0E10EE432B /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; };
BB3073CCD77D906B330BC1D6 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
BC9B05D6B293A039EB963CA7 /* az */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = az; path = az.lproj/Localizable.strings; sourceTree = "<group>"; }; BC9B05D6B293A039EB963CA7 /* az */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = az; path = az.lproj/Localizable.strings; sourceTree = "<group>"; };
BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = "<group>"; }; BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = "<group>"; };
BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -684,6 +689,7 @@
EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = "<group>"; }; EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModel.swift; sourceTree = "<group>"; };
EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; }; EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactoryProtocol.swift; sourceTree = "<group>"; }; EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactoryProtocol.swift; sourceTree = "<group>"; };
EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = "<group>"; };
EF188681D6B6068CFAEAFC3F /* MXLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = "<group>"; }; EF188681D6B6068CFAEAFC3F /* MXLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = "<group>"; };
EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = "<group>"; }; EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFrameReader.swift; sourceTree = "<group>"; };
EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -720,6 +726,7 @@
A4E885358D7DD5A072A06824 /* SwiftState in Frameworks */, A4E885358D7DD5A072A06824 /* SwiftState in Frameworks */,
29EE1791E0AFA1ABB7F23D2F /* GZIP in Frameworks */, 29EE1791E0AFA1ABB7F23D2F /* GZIP in Frameworks */,
33CAC1226DFB8B5D8447D286 /* Sentry in Frameworks */, 33CAC1226DFB8B5D8447D286 /* Sentry in Frameworks */,
492274DA6691EE985C2FCCAA /* SnapshotTesting in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -756,6 +763,7 @@
052CC920F473C10B509F9FC1 /* SwiftUI */ = { 052CC920F473C10B509F9FC1 /* SwiftUI */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E2DA161C142B7AB8CC40F752 /* Animation */,
595B8797ED6A7489ABDCE384 /* ErrorHandling */, 595B8797ED6A7489ABDCE384 /* ErrorHandling */,
CE2FBFD64A89F5DBE4EB30DB /* Layout */, CE2FBFD64A89F5DBE4EB30DB /* Layout */,
10578D9852BA78D309A1CBDF /* ViewModel */, 10578D9852BA78D309A1CBDF /* ViewModel */,
@ -850,13 +858,6 @@
path = Resources; path = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3180C73BA7B8F5F7447C99B0 /* React */ = {
isa = PBXGroup;
children = (
);
path = React;
sourceTree = "<group>";
};
328DD5DA1281F758B72006C7 /* Views */ = { 328DD5DA1281F758B72006C7 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1143,6 +1144,7 @@
children = ( children = (
49D2C8E66E83EA578A7F318A /* Info.plist */, 49D2C8E66E83EA578A7F318A /* Info.plist */,
D4DA544B2520BFA65D6DB4BB /* target.yml */, D4DA544B2520BFA65D6DB4BB /* target.yml */,
8E088F2A1B9EC529D3221931 /* UITests.xctestplan */,
); );
path = SupportingFiles; path = SupportingFiles;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1454,6 +1456,7 @@
1027BB9A852F445B7623897F /* ElementSettings.swift */, 1027BB9A852F445B7623897F /* ElementSettings.swift */,
12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */, 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */,
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */, 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
44BBB96FAA2F0D53C507396B /* Extensions */, 44BBB96FAA2F0D53C507396B /* Extensions */,
8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */, 8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */,
06501F0E978B2D5C92771DC7 /* Logging */, 06501F0E978B2D5C92771DC7 /* Logging */,
@ -1497,13 +1500,20 @@
path = SessionVerification; path = SessionVerification;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E2DA161C142B7AB8CC40F752 /* Animation */ = {
isa = PBXGroup;
children = (
EF1593DD87F974F8509BB619 /* ElementAnimations.swift */,
);
path = Animation;
sourceTree = "<group>";
};
E59565F441830B19DBAE567C /* Screens */ = { E59565F441830B19DBAE567C /* Screens */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E74CD7681375AD2EAA34D66B /* Authentication */, E74CD7681375AD2EAA34D66B /* Authentication */,
4009BE2E791C16AC6EE39A7E /* BugReport */, 4009BE2E791C16AC6EE39A7E /* BugReport */,
B53CA9BECD3F97805E1432D0 /* HomeScreen */, B53CA9BECD3F97805E1432D0 /* HomeScreen */,
3180C73BA7B8F5F7447C99B0 /* React */,
679E9837ECA8D6776079D16E /* RoomScreen */, 679E9837ECA8D6776079D16E /* RoomScreen */,
D958761758AA1110476DE6A3 /* SessionVerification */, D958761758AA1110476DE6A3 /* SessionVerification */,
70B74A432C241E56A7ACE610 /* Settings */, 70B74A432C241E56A7ACE610 /* Settings */,
@ -1623,6 +1633,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = F1B67CF63C1231AEB14D70E6 /* Build configuration list for PBXNativeTarget "UITests" */; buildConfigurationList = F1B67CF63C1231AEB14D70E6 /* Build configuration list for PBXNativeTarget "UITests" */;
buildPhases = ( buildPhases = (
17364E8B37FC780E07DDEAF7 /* Override Simulator Status Bars */,
BAD5CD7BE53A7C832569B67A /* Sources */, BAD5CD7BE53A7C832569B67A /* Sources */,
86982BD498105258F3778110 /* Resources */, 86982BD498105258F3778110 /* Resources */,
CD30252A70288BD4BF476ED7 /* Frameworks */, CD30252A70288BD4BF476ED7 /* Frameworks */,
@ -1644,6 +1655,7 @@
3853B78FB8531B83936C5DA6 /* SwiftState */, 3853B78FB8531B83936C5DA6 /* SwiftState */,
1BCD21310B997A6837B854D6 /* GZIP */, 1BCD21310B997A6837B854D6 /* GZIP */,
67E7A6F388D3BF85767609D9 /* Sentry */, 67E7A6F388D3BF85767609D9 /* Sentry */,
21C83087604B154AA30E9A8F /* SnapshotTesting */,
); );
productName = UITests; productName = UITests;
productReference = F506C6ADB1E1DA6638078E11 /* UITests.xctest */; productReference = F506C6ADB1E1DA6638078E11 /* UITests.xctest */;
@ -1805,6 +1817,7 @@
D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */, D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */,
80B898A3AD2AC63F3ABFC218 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */, 80B898A3AD2AC63F3ABFC218 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */,
A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */, A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
F34594223DB1E7DCE48F92B8 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */, 6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */,
25B4484A6A20B9F1705DEEDA /* XCRemoteSwiftPackageReference "SwiftyBeaver" */, 25B4484A6A20B9F1705DEEDA /* XCRemoteSwiftPackageReference "SwiftyBeaver" */,
); );
@ -1864,6 +1877,24 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
17364E8B37FC780E07DDEAF7 /* Override Simulator Status Bars */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Override Simulator Status Bars";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "python3 $PROJECT_DIR/Tools/Scripts/bootTestSimulator.py --name 'iPhone 13 Pro Max' --version 'iOS.15.5'\npython3 $PROJECT_DIR/Tools/Scripts/bootTestSimulator.py --name 'iPad (9th generation)' --version 'iOS.15.5'\n";
};
98CA896D84BFD53B2554E891 /* ⚠️ SwiftLint */ = { 98CA896D84BFD53B2554E891 /* ⚠️ SwiftLint */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1991,6 +2022,7 @@
DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */, DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */,
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */, C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */,
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */, EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */,
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */,
06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */, 06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */,
9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */, 9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */,
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */, D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */,
@ -2126,6 +2158,7 @@
1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */, 1555A7643D85187D4851040C /* TemplateScreen.swift in Sources */,
75EA4ABBFAA810AFF289D6F4 /* TemplateViewModel.swift in Sources */, 75EA4ABBFAA810AFF289D6F4 /* TemplateViewModel.swift in Sources */,
5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */, 5F1FDE49DFD0C680386E48F9 /* TemplateViewModelProtocol.swift in Sources */,
D85D4FA590305180B4A41795 /* Tests.swift in Sources */,
D013E70C8E28E43497820444 /* TextRoomMessage.swift in Sources */, D013E70C8E28E43497820444 /* TextRoomMessage.swift in Sources */,
7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */, 7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */,
5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */, 5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */,
@ -2742,6 +2775,14 @@
minimumVersion = 7.2.0; minimumVersion = 7.2.0;
}; };
}; };
F34594223DB1E7DCE48F92B8 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.9.0;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -2765,6 +2806,11 @@
package = 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */; package = 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */;
productName = GZIP; productName = GZIP;
}; };
21C83087604B154AA30E9A8F /* SnapshotTesting */ = {
isa = XCSwiftPackageProductDependency;
package = F34594223DB1E7DCE48F92B8 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
productName = SnapshotTesting;
};
36B7FC232711031AA2B0D188 /* DTCoreText */ = { 36B7FC232711031AA2B0D188 /* DTCoreText */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */; package = C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */;

View File

@ -66,7 +66,7 @@
{ {
"identity" : "matrix-rust-components-swift", "identity" : "matrix-rust-components-swift",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift.git",
"state" : { "state" : {
"revision" : "1358c9c2a85cfb5fc1bfadce13565e02618725d4", "revision" : "1358c9c2a85cfb5fc1bfadce13565e02618725d4",
"version" : "1.0.13-alpha" "version" : "1.0.13-alpha"
@ -81,6 +81,15 @@
"version" : "7.18.1" "version" : "7.18.1"
} }
}, },
{
"identity" : "swift-snapshot-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing.git",
"state" : {
"revision" : "f8a9c997c3c1dab4e216a8ec9014e23144cbab37",
"version" : "1.9.0"
}
},
{ {
"identity" : "swiftstate", "identity" : "swiftstate",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -63,13 +63,6 @@
</Testables> </Testables>
<CommandLineArguments> <CommandLineArguments>
</CommandLineArguments> </CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "IS_RUNNING_UNIT_TESTS"
value = "1"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<CodeCoverageTargets> <CodeCoverageTargets>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E28CD62691FDBC63147D5E3"
BuildableName = "UITests.xctest"
BlueprintName = "UITests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
onlyGenerateCoverageForSpecifiedTargets = "NO"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
default = "YES"
reference = "container:UITests/SupportingFiles/UITests.xctestplan">
</TestPlanReference>
</TestPlans>
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E28CD62691FDBC63147D5E3"
BuildableName = "UITests.xctest"
BlueprintName = "UITests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</MacroExpansion>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E28CD62691FDBC63147D5E3"
BuildableName = "UITests.xctest"
BlueprintName = "UITests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</MacroExpansion>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E28CD62691FDBC63147D5E3"
BuildableName = "UITests.xctest"
BlueprintName = "UITests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32C23C8D224D46EFE62AFAD0"
BuildableName = "UnitTests.xctest"
BlueprintName = "UnitTests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO"
shouldUseLaunchSchemeArgsEnv = "YES"
systemAttachmentLifetime = "keepNever">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32C23C8D224D46EFE62AFAD0"
BuildableName = "UnitTests.xctest"
BlueprintName = "UnitTests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32C23C8D224D46EFE62AFAD0"
BuildableName = "UnitTests.xctest"
BlueprintName = "UnitTests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</MacroExpansion>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32C23C8D224D46EFE62AFAD0"
BuildableName = "UnitTests.xctest"
BlueprintName = "UnitTests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</MacroExpansion>
<CommandLineArguments>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "IS_RUNNING_UNIT_TESTS"
value = "1"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "32C23C8D224D46EFE62AFAD0"
BuildableName = "UnitTests.xctest"
BlueprintName = "UnitTests"
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -18,7 +18,7 @@ import UIKit
@main @main
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
private lazy var appCoordinator: Coordinator = isRunningUITests ? UITestsAppCoordinator() : AppCoordinator() private lazy var appCoordinator: Coordinator = Tests.isRunningUITests ? UITestsAppCoordinator() : AppCoordinator()
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
// use `en` as fallback language // use `en` as fallback language
@ -28,7 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
} }
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
if isRunningUnitTests { if Tests.isRunningUnitTests {
return true return true
} }
@ -36,20 +36,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true return true
} }
private var isRunningUnitTests: Bool {
#if DEBUG
ProcessInfo.processInfo.environment["IS_RUNNING_UNIT_TESTS"] == "1"
#else
false
#endif
}
private var isRunningUITests: Bool {
#if DEBUG
ProcessInfo.processInfo.environment["IS_RUNNING_UI_TESTS"] == "1"
#else
false
#endif
}
} }

View File

@ -0,0 +1,34 @@
//
// ElementAnimations.swift
// ElementX
//
// Created by Ismail on 8.07.2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
import SwiftUI
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public extension Animation {
/// Animation to be used to disable animations.
static let noAnimation: Animation = .linear(duration: 0)
/// `noAnimation` if running UI tests, otherwise `default` animation.
static var elementDefault: Animation {
Tests.isRunningUITests ? .noAnimation : .default
}
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
/// Returns the result of recomputing the view's body with the provided
/// animation.
/// - Parameters:
/// - animation: Animation
/// - body: operations to be animated
public func withElementAnimation<Result>(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result {
if Tests.isRunningUITests {
return try withAnimation(.noAnimation, body)
}
return try withAnimation(animation, body)
}

View File

@ -0,0 +1,29 @@
//
// Tests.swift
// ElementX
//
// Created by Ismail on 29.07.2022.
// Copyright © 2022 Element. All rights reserved.
//
import Foundation
public enum Tests {
/// Flag indicating whether the app is running the unit tests.
static var isRunningUnitTests: Bool {
#if DEBUG
ProcessInfo.processInfo.environment["IS_RUNNING_UNIT_TESTS"] == "1"
#else
false
#endif
}
/// Flag indicating whether the app is running the UI tests.
static var isRunningUITests: Bool {
#if DEBUG
ProcessInfo.processInfo.environment["IS_RUNNING_UI_TESTS"] == "1"
#else
false
#endif
}
}

View File

@ -51,7 +51,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
func displayError(_ type: ServerSelectionErrorType) { func displayError(_ type: ServerSelectionErrorType) {
switch type { switch type {
case .footerMessage(let message): case .footerMessage(let message):
withAnimation { withElementAnimation {
state.footerErrorMessage = message state.footerErrorMessage = message
} }
} }
@ -62,6 +62,6 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
/// Clear any errors shown in the text field footer. /// Clear any errors shown in the text field footer.
private func clearFooterError() { private func clearFooterError() {
guard state.footerErrorMessage != nil else { return } guard state.footerErrorMessage != nil else { return }
withAnimation { state.footerErrorMessage = nil } withElementAnimation { state.footerErrorMessage = nil }
} }
} }

View File

@ -64,7 +64,7 @@ struct HomeScreen: View {
Spacer() Spacer()
} }
.transition(.slide) .transition(.slide)
.animation(.default, value: context.viewState.showSessionVerificationBanner) .animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
.ignoresSafeArea(.all, edges: .bottom) .ignoresSafeArea(.all, edges: .bottom)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
@ -72,11 +72,11 @@ struct HomeScreen: View {
Button { context.send(viewAction: .tapUserAvatar) } label: { Button { context.send(viewAction: .tapUserAvatar) } label: {
HStack { HStack {
userAvatarImage userAvatarImage
.animation(.default, value: context.viewState.userAvatar) .animation(.elementDefault, value: context.viewState.userAvatar)
.transition(.opacity) .transition(.opacity)
userDisplayNameView userDisplayNameView
.animation(.default, value: context.viewState.userDisplayName) .animation(.elementDefault, value: context.viewState.userDisplayName)
.transition(.opacity) .transition(.opacity)
} }
} }
@ -147,7 +147,7 @@ struct RoomCell: View {
} }
} }
} }
.animation(.default, value: room) .animation(.elementDefault, value: room)
.frame(minHeight: 60.0) .frame(minHeight: 60.0)
.task { .task {
context.send(viewAction: .loadRoomData(roomIdentifier: room.id)) context.send(viewAction: .loadRoomData(roomIdentifier: room.id))

View File

@ -36,7 +36,7 @@ struct MessageComposer: View {
.padding(.bottom, 6.0) .padding(.bottom, 6.0)
.disabled(disabled) .disabled(disabled)
.opacity(disabled ? 0.5 : 1.0) .opacity(disabled ? 0.5 : 1.0)
.animation(.default, value: disabled) .animation(.elementDefault, value: disabled)
.keyboardShortcut(.return, modifiers: [.command]) .keyboardShortcut(.return, modifiers: [.command])
} }
} }

View File

@ -57,7 +57,7 @@ struct MessageComposerTextField: View {
.background(placeholderView, alignment: .topLeading) .background(placeholderView, alignment: .topLeading)
.clipShape(rect) .clipShape(rect)
.overlay(rect.stroke(borderColor, lineWidth: borderWidth)) .overlay(rect.stroke(borderColor, lineWidth: borderWidth))
.animation(.default, value: isEditing) .animation(.elementDefault, value: isEditing)
} }
@ViewBuilder @ViewBuilder

View File

@ -42,7 +42,7 @@ struct ImageRoomTimelineView: View {
} }
} }
.id(timelineItem.id) .id(timelineItem.id)
.animation(.default, value: timelineItem.image) .animation(.elementDefault, value: timelineItem.image)
.frame(maxHeight: 1000.0) .frame(maxHeight: 1000.0)
} else { } else {
TimelineStyler(timelineItem: timelineItem) { TimelineStyler(timelineItem: timelineItem) {

View File

@ -37,7 +37,7 @@ struct TimelineItemList: View {
Spacer() Spacer()
ProgressView() ProgressView()
.opacity(context.viewState.isBackPaginating ? 1.0 : 0.0) .opacity(context.viewState.isBackPaginating ? 1.0 : 0.0)
.animation(.default, value: context.viewState.isBackPaginating) .animation(.elementDefault, value: context.viewState.isBackPaginating)
Spacer() Spacer()
} }
.listRowBackground(Color.clear) .listRowBackground(Color.clear)

View File

@ -40,6 +40,6 @@ struct TimelineSenderAvatarView: View {
.stroke(Color.element.background, lineWidth: 2) .stroke(Color.element.background, lineWidth: 2)
) )
.animation(.default, value: timelineItem.senderAvatar) .animation(.elementDefault, value: timelineItem.senderAvatar)
} }
} }

View File

@ -47,7 +47,7 @@ struct TimelineView: View {
scollToBottomButtonVisible = !visible scollToBottomButtonVisible = !visible
}) })
.opacity(scollToBottomButtonVisible ? 1.0 : 0.0) .opacity(scollToBottomButtonVisible ? 1.0 : 0.0)
.animation(.default, value: scollToBottomButtonVisible) .animation(.elementDefault, value: scollToBottomButtonVisible)
} }
} }

View File

@ -50,6 +50,11 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
switch callback { switch callback {
case .receivedVerificationData(let emojis): case .receivedVerificationData(let emojis):
guard self.stateMachine.state == .requestingVerification else {
MXLog.warning("[SessionVerificationViewModel] callbacks: Ignoring receivedVerificationData due to invalid state.")
return
}
self.stateMachine.processEvent(.didReceiveChallenge(emojis: emojis)) self.stateMachine.processEvent(.didReceiveChallenge(emojis: emojis))
case .finished: case .finished:
self.stateMachine.processEvent(.didAcceptChallenge) self.stateMachine.processEvent(.didAcceptChallenge)

View File

@ -53,6 +53,9 @@ struct SplashScreenViewState: BindableState {
/// The background gradient for all 4 pages and the hidden page at the start of the carousel. /// The background gradient for all 4 pages and the hidden page at the start of the carousel.
var backgroundGradient: Gradient { var backgroundGradient: Gradient {
if Tests.isRunningUITests {
return Gradient(colors: [.white])
}
// Include the extra stop for the hidden page at the start of the carousel. // Include the extra stop for the hidden page at the start of the carousel.
// (The last color is the right-hand stop, but we need the left-hand stop, // (The last color is the right-hand stop, but we need the left-hand stop,
// so take the last but one color from the array). // so take the last but one color from the array).

View File

@ -123,11 +123,11 @@ struct SplashScreen: View {
if context.pageIndex == pageCount - 1 { if context.pageIndex == pageCount - 1 {
showHiddenPage() showHiddenPage()
withAnimation(.easeInOut(duration: 0.7)) { withElementAnimation(.easeInOut(duration: 0.7)) {
showNextPage() showNextPage()
} }
} else { } else {
withAnimation(.easeInOut(duration: 0.7)) { withElementAnimation(.easeInOut(duration: 0.7)) {
showNextPage() showNextPage()
} }
} }
@ -185,7 +185,7 @@ struct SplashScreen: View {
stopTimer() stopTimer()
// Animate the change over a few frames to smooth out any stuttering. // Animate the change over a few frames to smooth out any stuttering.
withAnimation(.linear(duration: 0.05)) { withElementAnimation(.linear(duration: 0.05)) {
dragOffset = isLeftToRight ? drag.translation.width : -drag.translation.width dragOffset = isLeftToRight ? drag.translation.width : -drag.translation.width
} }
} }
@ -195,11 +195,11 @@ struct SplashScreen: View {
private func handleDragGestureEnded(_ drag: DragGesture.Value, viewSize: CGSize) { private func handleDragGestureEnded(_ drag: DragGesture.Value, viewSize: CGSize) {
guard shouldSwipeForTranslation(drag.predictedEndTranslation.width) else { guard shouldSwipeForTranslation(drag.predictedEndTranslation.width) else {
// Reset the offset just in case. // Reset the offset just in case.
withAnimation { dragOffset = 0 } withElementAnimation { dragOffset = 0 }
return return
} }
withAnimation(.easeInOut(duration: 0.2)) { withElementAnimation(.easeInOut(duration: 0.2)) {
if drag.predictedEndTranslation.width < -viewSize.width / 2 { if drag.predictedEndTranslation.width < -viewSize.width / 2 {
showNextPage() showNextPage()
} else if drag.predictedEndTranslation.width > viewSize.width / 2 { } else if drag.predictedEndTranslation.width > viewSize.width / 2 {

View File

@ -89,7 +89,7 @@ class ScreenshotDetector {
} }
} }
private extension PHAsset { extension PHAsset {
static func fetchLastScreenshot() -> PHAsset? { static func fetchLastScreenshot() -> PHAsset? {
let options = PHFetchOptions() let options = PHFetchOptions()

View File

@ -31,5 +31,6 @@ struct UITestsRootView: View {
.listStyle(.plain) .listStyle(.plain)
} }
.navigationTitle("Screens") .navigationTitle("Screens")
.navigationViewStyle(.stack)
} }
} }

View File

@ -23,8 +23,6 @@ schemes:
gatherCoverageData: true gatherCoverageData: true
coverageTargets: coverageTargets:
- ElementX - ElementX
environmentVariables:
IS_RUNNING_UNIT_TESTS: "1"
targets: targets:
- UnitTests - UnitTests
- UITests - UITests

View File

@ -8,6 +8,7 @@ GEM
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.0) addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15) artifactory (3.0.15)
@ -246,10 +247,10 @@ GEM
rouge (~> 2.0.7) rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1) xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7) xcpretty (~> 0.2, >= 0.0.7)
zeitwerk (2.6.0)
PLATFORMS PLATFORMS
arm64-darwin-21 ruby
x86_64-linux
DEPENDENCIES DEPENDENCIES
fastlane fastlane
@ -259,4 +260,4 @@ DEPENDENCIES
slather slather
BUNDLED WITH BUNDLED WITH
2.3.7 2.1.4

View File

@ -15,3 +15,11 @@ Usage:
``` ```
./localizer.py ./localizer.py
``` ```
## Boot Test Simulator
Boots a desired simulator and makes status bar overrides on that.
Usage:
```
./bootTestSimulator.py 'iPhone 13 Pro Max'
```

View File

@ -43,7 +43,7 @@ struct TemplateScreen: View {
.padding(.horizontal) .padding(.horizontal)
.padding(.vertical) .padding(.vertical)
.readableFrame() .readableFrame()
.background(.regularMaterial) .background(Color.element.system)
} }
} }

View File

@ -26,6 +26,8 @@ class TemplateScreenUITests: XCTestCase {
XCTAssert(title.exists) XCTAssert(title.exists)
XCTAssertEqual(title.label, "Make this chat public?") XCTAssertEqual(title.label, "Make this chat public?")
app.assertScreenshot(.simpleRegular)
} }
func testUpgradeScreen() { func testUpgradeScreen() {
@ -36,5 +38,7 @@ class TemplateScreenUITests: XCTestCase {
XCTAssert(title.exists) XCTAssert(title.exists)
XCTAssertEqual(title.label, "Privacy warning") XCTAssertEqual(title.label, "Privacy warning")
app.assertScreenshot(.simpleUpgrade)
} }
} }

View File

@ -0,0 +1,52 @@
#!/usr/bin/python3
from encodings import utf_8
import os
import subprocess
import json
import argparse
from datetime import datetime
RUNTIME_PREFIX = 'com.apple.CoreSimulator.SimRuntime.'
def device_name(device):
return device['name']
def runtime_name(runtime):
return runtime.replace(RUNTIME_PREFIX, '').replace('-', '.')
parser = argparse.ArgumentParser()
parser.add_argument('--name', type=str, help='Simulator name (like \'iPhone 13 Pro Max\')', required=True)
parser.add_argument('--version', type=str, default='iOS.15.5', help='OS version (defaults to \'iOS.15.5\')', required=False)
args = vars(parser.parse_args())
simulator_name = args['name']
os_version = args['version'].replace('.', '-')
runtimes_map = subprocess.check_output("/usr/bin/xcrun simctl list --json devices available", shell=True)
runtime = RUNTIME_PREFIX + os_version
json_object = json.loads(runtimes_map)
if runtime in json_object['devices']:
devices = json_object['devices'][runtime]
device_found=False
for device in devices:
if device_name(device) == simulator_name:
UDID=device['udid']
print("Found device UDID: " + UDID)
dirname = os.path.dirname(os.path.abspath(__file__))
overridden_time = datetime(2007, 1, 9, 9, 41, 0).astimezone().isoformat()
print("Will override simulator with time: " + overridden_time)
os.system("/usr/bin/xcrun simctl boot '" + UDID + "' > /dev/null 2>&1")
os.system("/usr/bin/xcrun simctl status_bar '" + UDID + "' override --time '" + overridden_time + "' --dataNetwork 'wifi' --wifiMode 'active' --wifiBars 3 --cellularMode 'active' --cellularBars 4 --batteryState 'charged' --batteryLevel 100 > /dev/null 2>&1")
print("Simulator booted and status bar overriden")
device_found=True
break
if device_found == False:
print("Device could not be found. \n\nAvailable devices: " + ', '.join(map(device_name, devices)))
exit(1)
else:
print("Runtime could not be found. \n\nAvailable runtimes: " + ', '.join(map(runtime_name, json_object['devices'].keys())))
exit(1)

View File

@ -14,12 +14,14 @@
// limitations under the License. // limitations under the License.
// //
import SnapshotTesting
import XCTest import XCTest
struct Application { struct Application {
static func launch() -> XCUIApplication { static func launch() -> XCUIApplication {
let app = XCUIApplication() let app = XCUIApplication()
app.launchEnvironment = ["IS_RUNNING_UI_TESTS": "1"] app.launchEnvironment = ["IS_RUNNING_UI_TESTS": "1"]
Bundle.elementFallbackLanguage = "en"
app.launch() app.launch()
return app return app
} }
@ -36,4 +38,39 @@ extension XCUIApplication {
button.tap() button.tap()
} }
/// Assert screenshot for a screen with the given identifier. Does not fail if a screenshot is newly created.
/// - Parameter identifier: Identifier of the UI test screen
func assertScreenshot(_ identifier: UITestScreenIdentifier) {
let failure = verifySnapshot(matching: screenshot().image,
as: .image(precision: 0.98, scale: nil),
named: identifier.rawValue,
testName: testName)
if let failure = failure,
!failure.contains("No reference was found on disk."),
!failure.contains("to test against the newly-recorded snapshot") {
XCTFail(failure)
}
}
private var testName: String {
osVersion + "-" + languageCode + "-" + regionCode + "-" + deviceName
}
private var deviceName: String {
UIDevice.current.name
}
private var languageCode: String {
Locale.current.languageCode ?? ""
}
private var regionCode: String {
Locale.current.regionCode ?? ""
}
private var osVersion: String {
UIDevice.current.systemVersion.replacingOccurrences(of: ".", with: "-")
}
} }

View File

@ -33,6 +33,8 @@ class AuthenticationCoordinatorUITests: XCTestCase {
app.typeText("alice\n") app.typeText("alice\n")
app.secureTextFields["passwordTextField"].tap() app.secureTextFields["passwordTextField"].tap()
app.typeText("12345678") app.typeText("12345678")
app.assertScreenshot(.authenticationFlow)
// Login Screen: Tap next // Login Screen: Tap next
app.buttons["nextButton"].tap() app.buttons["nextButton"].tap()
@ -41,7 +43,7 @@ class AuthenticationCoordinatorUITests: XCTestCase {
XCTAssertFalse(app.alerts.element.exists, "No alert should be shown when logging in with valid credentials.") XCTAssertFalse(app.alerts.element.exists, "No alert should be shown when logging in with valid credentials.")
} }
func testLoginWithIncorrectPassword() { func testLoginWithIncorrectPassword() async throws {
// Given the authentication flow. // Given the authentication flow.
let app = Application.launch() let app = Application.launch()
app.goToScreenWithIdentifier(.authenticationFlow) app.goToScreenWithIdentifier(.authenticationFlow)
@ -51,11 +53,15 @@ class AuthenticationCoordinatorUITests: XCTestCase {
// Login Screen: Enter invalid credentials // Login Screen: Enter invalid credentials
app.textFields["usernameTextField"].tap() app.textFields["usernameTextField"].tap()
app.typeText("alice\n") app.typeText("alice")
app.typeText("87654321\n") app.secureTextFields["passwordTextField"].tap()
app.typeText("87654321")
// Login Screen: Tap next
app.buttons["nextButton"].tap()
// Then login should fail. // Then login should fail.
XCTAssertTrue(app.alerts.element.exists, "An error alert should be shown when attempting login with invalid credentials.") XCTAssertTrue(app.alerts.element.waitForExistence(timeout: 1), "An error alert should be shown when attempting login with invalid credentials.")
} }
func testSelectingOIDCServer() { func testSelectingOIDCServer() {

View File

@ -26,6 +26,8 @@ class BugReportUITests: XCTestCase {
XCTAssertFalse(app.images["screenshotImage"].exists) XCTAssertFalse(app.images["screenshotImage"].exists)
XCTAssertFalse(app.buttons["removeScreenshotButton"].exists) XCTAssertFalse(app.buttons["removeScreenshotButton"].exists)
app.assertScreenshot(.bugReport)
} }
func testToggleSendingLogs() { func testToggleSendingLogs() {
@ -62,6 +64,8 @@ class BugReportUITests: XCTestCase {
XCTAssert(app.images["screenshotImage"].exists) XCTAssert(app.images["screenshotImage"].exists)
XCTAssert(app.buttons["removeScreenshotButton"].exists) XCTAssert(app.buttons["removeScreenshotButton"].exists)
app.assertScreenshot(.bugReportWithScreenshot)
} }
func verifyInitialStateComponents(in app: XCUIApplication) { func verifyInitialStateComponents(in app: XCUIApplication) {

View File

@ -36,6 +36,8 @@ class LoginScreenUITests: XCTestCase {
validateOIDCButtonIsHidden(for: state) validateOIDCButtonIsHidden(for: state)
validateNextButtonIsDisabled(for: state) validateNextButtonIsDisabled(for: state)
validateUnsupportedServerTextIsHidden(for: state) validateUnsupportedServerTextIsHidden(for: state)
app.assertScreenshot(.login)
// When typing in a username and password. // When typing in a username and password.
app.textFields.element.tap() app.textFields.element.tap()

View File

@ -26,6 +26,8 @@ class RoomScreenUITests: XCTestCase {
XCTAssert(app.staticTexts["roomNameLabel"].exists) XCTAssert(app.staticTexts["roomNameLabel"].exists)
XCTAssert(app.staticTexts["roomAvatarPlaceholderImage"].exists) XCTAssert(app.staticTexts["roomAvatarPlaceholderImage"].exists)
XCTAssertFalse(app.images["encryptionBadgeIcon"].exists) XCTAssertFalse(app.images["encryptionBadgeIcon"].exists)
app.assertScreenshot(.roomPlainNoAvatar)
} }
func testEncryptedWithAvatar() { func testEncryptedWithAvatar() {
@ -35,5 +37,7 @@ class RoomScreenUITests: XCTestCase {
XCTAssert(app.staticTexts["roomNameLabel"].exists) XCTAssert(app.staticTexts["roomNameLabel"].exists)
XCTAssert(app.images["roomAvatarImage"].exists) XCTAssert(app.images["roomAvatarImage"].exists)
XCTAssert(app.images["encryptionBadgeIcon"].exists) XCTAssert(app.images["encryptionBadgeIcon"].exists)
app.assertScreenshot(.roomEncryptedWithAvatar)
} }
} }

View File

@ -41,6 +41,8 @@ class ServerSelectionUITests: XCTestCase {
let dismissButton = app.buttons["dismissButton"] let dismissButton = app.buttons["dismissButton"]
XCTAssertTrue(dismissButton.exists, "The dismiss button should be shown during modal presentation.") XCTAssertTrue(dismissButton.exists, "The dismiss button should be shown during modal presentation.")
app.assertScreenshot(.serverSelection)
} }
func testEmptyAddress() async { func testEmptyAddress() async {
@ -95,5 +97,7 @@ class ServerSelectionUITests: XCTestCase {
let confirmButton = app.buttons["confirmButton"] let confirmButton = app.buttons["confirmButton"]
XCTAssertEqual(confirmButton.label, ElementL10n.actionNext, "The confirm button should say Next when not in modal presentation.") XCTAssertEqual(confirmButton.label, ElementL10n.actionNext, "The confirm button should say Next when not in modal presentation.")
app.assertScreenshot(.serverSelectionNonModal)
} }
} }

View File

@ -22,11 +22,13 @@ class SessionVerificationUITests: XCTestCase {
let app = Application.launch() let app = Application.launch()
app.goToScreenWithIdentifier(.sessionVerification) app.goToScreenWithIdentifier(.sessionVerification)
XCTAssert(app.navigationBars["Verify this session"].exists) XCTAssert(app.navigationBars[ElementL10n.verificationVerifyDevice].exists)
XCTAssert(app.buttons["startButton"].exists) XCTAssert(app.buttons["startButton"].exists)
XCTAssert(app.buttons["dismissButton"].exists) XCTAssert(app.buttons["dismissButton"].exists)
XCTAssert(app.staticTexts["titleLabel"].exists) XCTAssert(app.staticTexts["titleLabel"].exists)
app.assertScreenshot(.sessionVerification)
app.buttons["startButton"].tap() app.buttons["startButton"].tap()
@ -52,7 +54,7 @@ class SessionVerificationUITests: XCTestCase {
let app = Application.launch() let app = Application.launch()
app.goToScreenWithIdentifier(.sessionVerification) app.goToScreenWithIdentifier(.sessionVerification)
XCTAssert(app.navigationBars["Verify this session"].exists) XCTAssert(app.navigationBars[ElementL10n.verificationVerifyDevice].exists)
XCTAssert(app.buttons["startButton"].exists) XCTAssert(app.buttons["startButton"].exists)
XCTAssert(app.buttons["dismissButton"].exists) XCTAssert(app.buttons["dismissButton"].exists)
@ -80,7 +82,7 @@ class SessionVerificationUITests: XCTestCase {
let app = Application.launch() let app = Application.launch()
app.goToScreenWithIdentifier(.sessionVerification) app.goToScreenWithIdentifier(.sessionVerification)
XCTAssert(app.navigationBars["Verify this session"].exists) XCTAssert(app.navigationBars[ElementL10n.verificationVerifyDevice].exists)
XCTAssert(app.buttons["startButton"].exists) XCTAssert(app.buttons["startButton"].exists)
XCTAssert(app.buttons["dismissButton"].exists) XCTAssert(app.buttons["dismissButton"].exists)
@ -88,12 +90,12 @@ class SessionVerificationUITests: XCTestCase {
app.buttons["startButton"].tap() app.buttons["startButton"].tap()
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists) XCTAssert(app.activityIndicators["requestingVerificationProgressView"].waitForExistence(timeout: 1))
XCTAssert(app.buttons["cancelButton"].exists) XCTAssert(app.buttons["cancelButton"].exists)
app.buttons["cancelButton"].tap() app.buttons["cancelButton"].tap()
XCTAssert(app.images["sessionVerificationFailedIcon"].exists) XCTAssert(app.images["sessionVerificationFailedIcon"].waitForExistence(timeout: 1))
XCTAssert(app.buttons["restartButton"].exists) XCTAssert(app.buttons["restartButton"].exists)
XCTAssert(app.buttons["dismissButton"].exists) XCTAssert(app.buttons["dismissButton"].exists)

View File

@ -35,5 +35,7 @@ class SettingsUITests: XCTestCase {
let logoutButton = app.buttons["logoutButton"] let logoutButton = app.buttons["logoutButton"]
XCTAssert(logoutButton.exists) XCTAssert(logoutButton.exists)
XCTAssertEqual(logoutButton.label, ElementL10n.logout) XCTAssertEqual(logoutButton.label, ElementL10n.logout)
app.assertScreenshot(.settings)
} }
} }

View File

@ -25,6 +25,8 @@ class SplashScreenUITests: XCTestCase {
let getStartedButton = app.buttons["getStartedButton"] let getStartedButton = app.buttons["getStartedButton"]
XCTAssertTrue(getStartedButton.exists, "The primary action button should be shown.") XCTAssertTrue(getStartedButton.exists, "The primary action button should be shown.")
XCTAssertEqual(getStartedButton.label, ElementL10n.loginSplashSubmit) XCTAssertEqual(getStartedButton.label, ElementL10n.loginSplashSubmit)
app.assertScreenshot(.splash)
} }
func testSwipingBetweenPages() { func testSwipingBetweenPages() {
@ -32,8 +34,8 @@ class SplashScreenUITests: XCTestCase {
app.goToScreenWithIdentifier(.splash) app.goToScreenWithIdentifier(.splash)
// Given the splash screen in its initial state. // Given the splash screen in its initial state.
let page1TitleText = app.staticTexts["Own your conversations."] let page1TitleText = app.staticTexts[ElementL10n.ftueAuthCarouselSecureTitle]
let page2TitleText = app.staticTexts["You're in control."] let page2TitleText = app.staticTexts[ElementL10n.ftueAuthCarouselControlTitle]
let hiddenPageTitleText = app.staticTexts["hiddenPage"].firstMatch let hiddenPageTitleText = app.staticTexts["hiddenPage"].firstMatch
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.") XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.")
@ -41,21 +43,21 @@ class SplashScreenUITests: XCTestCase {
XCTAssertFalse(hiddenPageTitleText.isHittable, "The hidden page of the carousel should be offscreen.") XCTAssertFalse(hiddenPageTitleText.isHittable, "The hidden page of the carousel should be offscreen.")
// When swiping to the next screen. // When swiping to the next screen.
page1TitleText.swipeLeft() page1TitleText.swipeLeft(velocity: .fast)
// Then the second screen should be shown. // Then the second screen should be shown.
XCTAssertFalse(page1TitleText.isHittable, "The title from the first page of the carousel should be offscreen.") XCTAssertFalse(page1TitleText.isHittable, "The title from the first page of the carousel should be offscreen.")
XCTAssertTrue(page2TitleText.isHittable, "The title from the second page of the carousel should be onscreen.") XCTAssertTrue(page2TitleText.isHittable, "The title from the second page of the carousel should be onscreen.")
// When swiping back to the previous screen. // When swiping back to the previous screen.
page2TitleText.swipeRight() page2TitleText.swipeRight(velocity: .fast)
// Then the first screen should be shown again. // Then the first screen should be shown again.
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.") XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.")
XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.") XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.")
// When swiping back to the previous screen. // When swiping back to the previous screen.
page1TitleText.swipeRight() page1TitleText.swipeRight(velocity: .fast)
// Then the screen shouldn't change and the hidden screen should be ignored. // Then the screen shouldn't change and the hidden screen should be ignored.
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be still be onscreen.") XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be still be onscreen.")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More