Prefire basic implementation (#1743)

* prefire basic implementation

force the unit tests to run on iPhone 14

skipping plugin validation

archive artifacts

better workflow

removed the OS restriction

lfs

custom stencil

new test file

deleting

* git lfs

* lfs

* test

* customised the stencil to support our internal protocol

* for now we can remove the OS check from the stencil

* added a delay

* tests have been moved

* improvement

* recommit

* perceptual precision lowered

* updated snapshot testing and selected also iOS version

* added ios version control
This commit is contained in:
Mauro 2023-09-20 14:07:18 +02:00 committed by GitHub
parent faad37c803
commit 341b177e23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 509 additions and 16 deletions

1
.gitattributes vendored
View File

@ -1,2 +1,3 @@
UITests/Sources/__Snapshots__/** filter=lfs diff=lfs merge=lfs -text
UnitTests/Resources/** filter=lfs diff=lfs merge=lfs -text
UnitTests/Sources/__Snapshots__/** filter=lfs diff=lfs merge=lfs -text

View File

@ -1,3 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/pre-push'.\n"; exit 2; }
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs pre-push "$@"

View File

@ -42,6 +42,16 @@ jobs:
- name: Run tests
run: bundle exec fastlane unit_tests
- name: Archive artifacts
uses: actions/upload-artifact@v3
# We only care about artifcats if the tests fail
if: failure()
with:
name: test-output
path: fastlane/test_output
retention-days: 1
if-no-files-found: ignore
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:

5
.prefire.yml Normal file
View File

@ -0,0 +1,5 @@
test_configuration:
- test_file_path: UnitTests/Sources/PreviewTests.swift
- template_file_path: Tools/Prefire/PreviewTests.stencil
- simulator_device: "iPhone14"
- required_os: 16

View File

@ -48,6 +48,9 @@ nesting:
type_level:
warning: 5
type_name:
allowed_symbols: "_"
custom_rules:
print_deprecation:
regex: "\\b(print)\\b"

View File

@ -211,6 +211,7 @@
43F35A7E5703D64DB0519C59 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD469F7513574341181F7EAA /* ServerSelectionScreen.swift */; };
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; };
44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */; };
44F0E1B576C7599DF8022071 /* Prefire in Frameworks */ = {isa = PBXBuildFile; productRef = 2629CF48B33643CD5F69C612 /* Prefire */; };
4557192F5B15A8D9BB920232 /* AdvancedSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E492690C8B27A892C194CC4 /* AdvancedSettingsScreenCoordinator.swift */; };
46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; };
46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */; };
@ -299,6 +300,7 @@
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */; };
6298AB0906DDD3525CD78C6B /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 81DB3AB6CE996AB3954F4F03 /* KZFileWatchers */; };
63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */; };
642DF13C49ED4121C148230E /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E227F34BE43B08E098796E /* TestablePreview.swift */; };
6448F8D1D3CA4CD27BB4CADD /* RoomMemberProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F36C5D9B37E50915ECBD3EE /* RoomMemberProxy.swift */; };
644AA5001BCC58D7732EB772 /* MigrationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */; };
64C373ACCFA26D42BA45CFAD /* HomeScreenInvitesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */; };
@ -393,6 +395,7 @@
7F64FA937B95924B3A44EC12 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB8E75B9CB6C78BE8D09B1AF /* OnboardingScreen.swift */; };
7F7EA51A9A43125A8CB6AC90 /* NotificationSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D560DDA3B20C82766ACFAD /* NotificationSettingsScreenViewModel.swift */; };
7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */; };
7FF27DA70D833CFC5724EFC5 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 1CAB56FF1DE17B3E871A0BA2 /* SnapshotTesting */; };
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; };
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; };
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; };
@ -489,6 +492,7 @@
9BEA56957B3AF954E7321658 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44928D844E16EE48A311FCA /* ComposerToolbarViewModelProtocol.swift */; };
9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */; };
9C5A07E7C33F3F40287D7861 /* SettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */; };
9C7895941669EA7976A18D88 /* PreviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A01505B6371171413C3C4BD /* PreviewTests.swift */; };
9CCC77C31CB399661A034739 /* UserProperties+Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6C4BE591FE5C38CE9C7EF3 /* UserProperties+Element.swift */; };
9D2E03DB175A6AB14589076D /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 290FDEDA4D764B9F7EBE55A9 /* Algorithms */; };
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; };
@ -1093,6 +1097,7 @@
49E45C3DC740D3AB9A47FD32 /* SwipeToReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToReplyView.swift; sourceTree = "<group>"; };
49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
4A01505B6371171413C3C4BD /* PreviewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewTests.swift; sourceTree = "<group>"; };
4A4AD793D50748F8997E5B15 /* TimelineItemMacContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMacContextMenu.swift; sourceTree = "<group>"; };
4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = "<group>"; };
4ADC55DFF46083BC957E0019 /* CreatePollScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollScreenModels.swift; sourceTree = "<group>"; };
@ -1383,6 +1388,7 @@
B0A307A44F952CD73E63AE31 /* RoomEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomEventStringBuilder.swift; sourceTree = "<group>"; };
B16048D30F0438731C41F775 /* StateRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineItem.swift; sourceTree = "<group>"; };
B16CAF20C9AC874A210E2DCF /* SessionVerificationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
B1E227F34BE43B08E098796E /* TestablePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreview.swift; sourceTree = "<group>"; };
B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = "<group>"; };
B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItemContent.swift; sourceTree = "<group>"; };
B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenModels.swift; sourceTree = "<group>"; };
@ -1644,6 +1650,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
A7A4BAD642A61DCC41621311 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7FF27DA70D833CFC5724EFC5 /* SnapshotTesting in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
BF59B36A7B2DB184B62826F6 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -1704,6 +1718,7 @@
36AD4DD4C798E22584ED3200 /* Version in Frameworks */,
36CD6E11B37396E14F032CB6 /* Emojibase in Frameworks */,
A0D7E5BD0298A97DCBDCE40B /* WysiwygComposer in Frameworks */,
44F0E1B576C7599DF8022071 /* Prefire in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -2675,6 +2690,7 @@
514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */,
D53D6BB7E8E5EC031281872C /* OnboardingScreenViewModelTests.swift */,
6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */,
4A01505B6371171413C3C4BD /* PreviewTests.swift */,
086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */,
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */,
2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */,
@ -3495,6 +3511,7 @@
53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */,
DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */,
4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */,
B1E227F34BE43B08E098796E /* TestablePreview.swift */,
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */,
35FA991289149D31F4286747 /* UserPreference.swift */,
@ -3958,13 +3975,18 @@
buildPhases = (
11F93544B4FC60F78F47D89C /* Sources */,
9B3512762CF4A1D45A79C340 /* Resources */,
A7A4BAD642A61DCC41621311 /* Frameworks */,
);
buildRules = (
);
dependencies = (
0EEC1557A40FBA6DF49D83A2 /* PBXTargetDependency */,
9A791554EB868FA6F6B95324 /* PBXTargetDependency */,
);
name = UnitTests;
packageProductDependencies = (
1CAB56FF1DE17B3E871A0BA2 /* SnapshotTesting */,
);
productName = UnitTests;
productReference = AAC9344689121887B74877AF /* UnitTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
@ -4029,6 +4051,7 @@
A05AF81DDD14AD58CB0E1B9B /* Version */,
C05729B1684C331F5FFE9232 /* Emojibase */,
CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */,
2629CF48B33643CD5F69C612 /* Prefire */,
);
productName = ElementX;
productReference = 4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */;
@ -4157,6 +4180,7 @@
0CBF57301AA172C21F76CE86 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */,
80B898A3AD2AC63F3ABFC218 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */,
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */,
22E7BA2ED466B74739AB8567 /* XCRemoteSwiftPackageReference "Prefire" */,
A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
E9C4F3A12AA1F65C13A8C8EB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */,
@ -4440,6 +4464,7 @@
E3AC72E3E58F364EF15C1CC7 /* NotificationSettingsScreenViewModelTests.swift in Sources */,
0C26A1588B17DCDE5F490FE3 /* OnboardingScreenViewModelTests.swift in Sources */,
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
9C7895941669EA7976A18D88 /* PreviewTests.swift in Sources */,
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */,
@ -4952,6 +4977,7 @@
CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */,
275EDE8849A2AC1D9309ED7C /* TemplateScreenViewModel.swift in Sources */,
2C4C750D0039AFABDF24236C /* TemplateScreenViewModelProtocol.swift in Sources */,
642DF13C49ED4121C148230E /* TestablePreview.swift in Sources */,
D85D4FA590305180B4A41795 /* Tests.swift in Sources */,
8317E1314C00DCCC99D30DA8 /* TextBasedRoomTimelineItem.swift in Sources */,
A2A5AB2E8B3F5CA769E531FA /* TextBasedRoomTimelineViewProtocol.swift in Sources */,
@ -5130,6 +5156,10 @@
target = C0FAEB81CFD9776CD78CE489 /* ElementX */;
targetProxy = 6848AF4480814C5F810FB7EB /* PBXContainerItemProxy */;
};
9A791554EB868FA6F6B95324 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = B717B96C04D7B6A1212D9EDC /* PrefireTestsPlugin */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -5658,6 +5688,14 @@
minimumVersion = 5.13.0;
};
};
22E7BA2ED466B74739AB8567 /* XCRemoteSwiftPackageReference "Prefire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/BarredEwe/Prefire";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 1.4.1;
};
};
395DE6AE429B7ACC7C7FE31D /* XCRemoteSwiftPackageReference "KZFileWatchers" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/krzysztofzablocki/KZFileWatchers";
@ -5799,7 +5837,7 @@
repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 1.11.0;
minimumVersion = 1.13.0;
};
};
EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */ = {
@ -5851,6 +5889,11 @@
package = 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */;
productName = GZIP;
};
1CAB56FF1DE17B3E871A0BA2 /* SnapshotTesting */ = {
isa = XCSwiftPackageProductDependency;
package = E9C4F3A12AA1F65C13A8C8EB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
productName = SnapshotTesting;
};
21C83087604B154AA30E9A8F /* SnapshotTesting */ = {
isa = XCSwiftPackageProductDependency;
package = E9C4F3A12AA1F65C13A8C8EB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
@ -5861,6 +5904,11 @@
package = 80B898A3AD2AC63F3ABFC218 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */;
productName = MatrixRustSDK;
};
2629CF48B33643CD5F69C612 /* Prefire */ = {
isa = XCSwiftPackageProductDependency;
package = 22E7BA2ED466B74739AB8567 /* XCRemoteSwiftPackageReference "Prefire" */;
productName = Prefire;
};
290FDEDA4D764B9F7EBE55A9 /* Algorithms */ = {
isa = XCSwiftPackageProductDependency;
package = E025F19D013D9BA6C58B37F4 /* XCRemoteSwiftPackageReference "swift-algorithms" */;
@ -6029,6 +6077,11 @@
package = 80B898A3AD2AC63F3ABFC218 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */;
productName = MatrixRustSDK;
};
B717B96C04D7B6A1212D9EDC /* PrefireTestsPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = 22E7BA2ED466B74739AB8567 /* XCRemoteSwiftPackageReference "Prefire" */;
productName = "plugin:PrefireTestsPlugin";
};
BA93CD75CCE486660C9040BD /* Collections */ = {
isa = XCSwiftPackageProductDependency;
package = F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */;

View File

@ -151,6 +151,15 @@
"version" : "2.0.3"
}
},
{
"identity" : "prefire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/BarredEwe/Prefire",
"state" : {
"revision" : "abb8dfa44391b4f47edb4937a4ba124e76270a87",
"version" : "1.4.1"
}
},
{
"identity" : "sentry-cocoa",
"kind" : "remoteSourceControl",
@ -201,8 +210,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
"state" : {
"revision" : "dc46eeb3928a75390651fac6c1ef7f93ad59a73b",
"version" : "1.11.1"
"revision" : "696b86a6d151578bca7c1a2a3ed419a5f834d40f",
"version" : "1.13.0"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "74203046135342e4a4a627476dd6caf8b28fe11b",
"version" : "509.0.0"
}
},
{

View File

@ -0,0 +1,21 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
import Prefire
protocol TestablePreviewProvider: PreviewProvider, PrefireProvider { }

View File

@ -35,10 +35,10 @@ struct TimelineStyler<Content: View>: View {
}
}
struct TimelineItemStyler_Previews: PreviewProvider {
struct TimelineItemStyler_Previews: TestablePreviewProvider {
static let viewModel = RoomScreenViewModel.mock
static let base = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
static let base = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "Test"))
static let sentNonLast: TextRoomTimelineItem = {
var result = base
@ -54,7 +54,7 @@ struct TimelineItemStyler_Previews: PreviewProvider {
static let sendingLast: TextRoomTimelineItem = {
let id = viewModel.state.timelineViewState.timelineIDs.last ?? UUID().uuidString
var result = TextRoomTimelineItem(id: .init(timelineID: id), timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
var result = TextRoomTimelineItem(id: .init(timelineID: id), timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "Test"))
result.properties.deliveryStatus = .sending
return result
}()
@ -67,21 +67,21 @@ struct TimelineItemStyler_Previews: PreviewProvider {
static let sentLast: TextRoomTimelineItem = {
let id = viewModel.state.timelineViewState.timelineIDs.last ?? UUID().uuidString
let result = TextRoomTimelineItem(id: .init(timelineID: id), timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "Test"))
let result = TextRoomTimelineItem(id: .init(timelineID: id), timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "Test"))
return result
}()
static let ltrString = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "house!"))
static let ltrString = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "house!"))
static let rtlString = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת!"))
static let rtlString = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "באמת!"))
static let ltrStringThatContainsRtl = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת‏! -- house!"))
static let ltrStringThatContainsRtl = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "house! -- באמת‏! -- house!"))
static let rtlStringThatContainsLtr = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת‏! -- house! -- באמת!"))
static let rtlStringThatContainsLtr = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "באמת‏! -- house! -- באמת!"))
static let ltrStringThatFinishesInRtl = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "house! -- באמת!"))
static let ltrStringThatFinishesInRtl = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "house! -- באמת!"))
static let rtlStringThatFinishesInLtr = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .init(id: UUID().uuidString), content: .init(body: "באמת‏! -- house!"))
static let rtlStringThatFinishesInLtr = TextRoomTimelineItem(id: .random, timestamp: "Now", isOutgoing: true, isEditable: false, isThreaded: false, sender: .test, content: .init(body: "באמת‏! -- house!"))
static var testView: some View {
VStack {

View File

@ -17,6 +17,8 @@
import UIKit
struct TimelineItemSender: Identifiable, Hashable {
static let test = TimelineItemSender(id: "@test.matrix.org")
let id: String
let displayName: String?
let avatarURL: URL?

View File

@ -193,6 +193,7 @@ targets:
- package: Version
- package: Emojibase
- package: WysiwygComposer
- package: Prefire
sources:
- path: ../Sources

View File

@ -0,0 +1,181 @@
// swiftlint:disable all
// swiftformat:disable all
import XCTest
import SwiftUI
import Prefire
@testable import SnapshotTesting
#if canImport(AccessibilitySnapshot)
import AccessibilitySnapshot
#endif
{% if argument.mainTarget %}
@testable import {{ argument.mainTarget }}
{% endif %}
class PreviewTests: XCTestCase {
private let deviceConfig: ViewImageConfig = .iPhoneX
private let simulatorDevice = "{{ argument.simulatorDevice|default:"iPhone15,2" }}"
private let requiredOSVersion = {{ argument.simulatorOSVersion|default:"16" }}
{% if argument.file %}
private var file: StaticString { .init(stringLiteral: "{{ argument.file }}") }
{% endif %}
override func setUp() {
super.setUp()
checkEnvironments()
UIView.setAnimationsEnabled(false)
}
{% for type in types.types where (type.implements.PrefireProvider or type.based.PrefireProvider or type|annotated:"PrefireProvider") and type.name != "TestablePreviewProvider" %}
func test_{{ type.name|lowerFirstLetter|replace:"_Previews", "" }}() {
for preview in {{ type.name }}._allPreviews {
assertSnapshots(matching: preview)
}
}
{% endfor %}
// MARK: Private
private func assertSnapshots(matching preview: _Preview, testName: String = #function) {
let isScreen = preview.layout == .device
let device = preview.device?.snapshotDevice() ?? deviceConfig
var delay: TimeInterval = 1.0
var precision: Float = 0.99
let view = preview.content
.onPreferenceChange(DelayPreferenceKey.self) { delay = $0 }
.onPreferenceChange(PrecisionPreferenceKey.self) { precision = $0 }
let matchingView = isScreen ? AnyView(view) : AnyView(view
.frame(width: device.size?.width)
.fixedSize(horizontal: false, vertical: true)
)
assertSnapshot(
matching: matchingView,
as: .prefireImage(precision: { precision }, duration: { delay }, layout: isScreen ? .device(config: device) : .sizeThatFits),
named: preview.displayName{% if argument.file %},
file: file{% endif %},
testName: testName
)
#if canImport(AccessibilitySnapshot)
let vc = UIHostingController(rootView: matchingView)
vc.view.frame = UIScreen.main.bounds
assertSnapshot(
matching: vc,
as: .wait(for: delay, on: .accessibilityImage(showActivationPoints: .always)),
named: preview.displayName.map { $0 + ".accessibility" }{% if argument.file %},
file: file{% endif %},
testName: testName
)
#endif
}
/// Check environments to avoid problems with snapshots on different devices or OS.
private func checkEnvironments() {
let deviceModel = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]
let osVersion = ProcessInfo().operatingSystemVersion
guard deviceModel?.contains(simulatorDevice) ?? false else {
fatalError("Switch to using \(simulatorDevice) for these tests.")
}
guard osVersion.majorVersion == requiredOSVersion else {
fatalError("Switch to iOS \(requiredOSVersion) for these tests.")
}
}
}
// MARK: - SnapshotTesting + Extensions
private extension PreviewDevice {
func snapshotDevice() -> ViewImageConfig? {
switch rawValue {
case "iPhone 14", "iPhone 13", "iPhone 12", "iPhone 11", "iPhone 10":
return .iPhoneX
case "iPhone 6", "iPhone 6s", "iPhone 7", "iPhone 8":
return .iPhone8
case "iPhone 6 Plus", "iPhone 6s Plus", "iPhone 8 Plus":
return .iPhone8Plus
case "iPhone SE (1st generation)", "iPhone SE (2nd generation)":
return .iPhoneSe
default: return nil
}
}
}
private extension Snapshotting where Value: SwiftUI.View, Format == UIImage {
static func prefireImage(
drawHierarchyInKeyWindow: Bool = false,
precision: @escaping () -> Float,
duration: @escaping () -> TimeInterval,
layout: SwiftUISnapshotLayout = .sizeThatFits,
traits: UITraitCollection = .init()
) -> Snapshotting {
let config: ViewImageConfig
switch layout {
#if os(iOS) || os(tvOS)
case let .device(config: deviceConfig):
config = deviceConfig
#endif
case .sizeThatFits:
config = .init(safeArea: .zero, size: nil, traits: traits)
case let .fixed(width: width, height: height):
let size = CGSize(width: width, height: height)
config = .init(safeArea: .zero, size: size, traits: traits)
}
return SimplySnapshotting<UIImage>(pathExtension: "png", diffing: .prefireImage(precision: precision, scale: traits.displayScale))
.asyncPullback { view in
var config = config
let controller: UIViewController
if config.size != nil {
controller = UIHostingController(rootView: view)
} else {
let hostingController = UIHostingController(rootView: view)
let maxSize = CGSize.zero
config.size = hostingController.sizeThatFits(in: maxSize)
controller = hostingController
}
return Async<UIImage> { callback in
let strategy = snapshotView(
config: config,
drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
traits: traits,
view: controller.view,
viewController: controller
)
let duration = duration()
if duration != .zero {
let expectation = XCTestExpectation(description: "Wait")
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
expectation.fulfill()
}
_ = XCTWaiter.wait(for: [expectation], timeout: duration + 1)
}
strategy.run(callback)
}
}
}
}
private extension Diffing where Value == UIImage {
static func prefireImage(precision: @escaping () -> Float, scale: CGFloat?) -> Diffing {
lazy var originalDiffing = Diffing.image(precision: precision(), perceptualPrecision: 0.98, scale: scale)
return Diffing(
toData: { originalDiffing.toData($0) },
fromData: { originalDiffing.fromData($0) },
diff: { originalDiffing.diff($0, $1) }
)
}
}

View File

@ -0,0 +1,173 @@
// Generated using Sourcery 2.0.1 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable all
// swiftformat:disable all
import XCTest
import SwiftUI
import Prefire
@testable import SnapshotTesting
#if canImport(AccessibilitySnapshot)
import AccessibilitySnapshot
#endif
@testable import ElementX
class PreviewTests: XCTestCase {
private let deviceConfig: ViewImageConfig = .iPhoneX
private let simulatorDevice = "iPhone14"
private let requiredOSVersion = 16
override func setUp() {
super.setUp()
checkEnvironments()
UIView.setAnimationsEnabled(false)
}
func test_timelineItemStyler() {
for preview in TimelineItemStyler_Previews._allPreviews {
assertSnapshots(matching: preview)
}
}
// MARK: Private
private func assertSnapshots(matching preview: _Preview, testName: String = #function) {
let isScreen = preview.layout == .device
let device = preview.device?.snapshotDevice() ?? deviceConfig
var delay: TimeInterval = 1.0
var precision: Float = 0.99
let view = preview.content
.onPreferenceChange(DelayPreferenceKey.self) { delay = $0 }
.onPreferenceChange(PrecisionPreferenceKey.self) { precision = $0 }
let matchingView = isScreen ? AnyView(view) : AnyView(view
.frame(width: device.size?.width)
.fixedSize(horizontal: false, vertical: true)
)
assertSnapshot(
matching: matchingView,
as: .prefireImage(precision: { precision }, duration: { delay }, layout: isScreen ? .device(config: device) : .sizeThatFits),
named: preview.displayName,
testName: testName
)
#if canImport(AccessibilitySnapshot)
let vc = UIHostingController(rootView: matchingView)
vc.view.frame = UIScreen.main.bounds
assertSnapshot(
matching: vc,
as: .wait(for: delay, on: .accessibilityImage(showActivationPoints: .always)),
named: preview.displayName.map { $0 + ".accessibility" },
testName: testName
)
#endif
}
/// Check environments to avoid problems with snapshots on different devices or OS.
private func checkEnvironments() {
let deviceModel = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]
let osVersion = ProcessInfo().operatingSystemVersion
guard deviceModel?.contains(simulatorDevice) ?? false else {
fatalError("Switch to using \(simulatorDevice) for these tests.")
}
guard osVersion.majorVersion == requiredOSVersion else {
fatalError("Switch to iOS \(requiredOSVersion) for these tests.")
}
}
}
// MARK: - SnapshotTesting + Extensions
private extension PreviewDevice {
func snapshotDevice() -> ViewImageConfig? {
switch rawValue {
case "iPhone 14", "iPhone 13", "iPhone 12", "iPhone 11", "iPhone 10":
return .iPhoneX
case "iPhone 6", "iPhone 6s", "iPhone 7", "iPhone 8":
return .iPhone8
case "iPhone 6 Plus", "iPhone 6s Plus", "iPhone 8 Plus":
return .iPhone8Plus
case "iPhone SE (1st generation)", "iPhone SE (2nd generation)":
return .iPhoneSe
default: return nil
}
}
}
private extension Snapshotting where Value: SwiftUI.View, Format == UIImage {
static func prefireImage(
drawHierarchyInKeyWindow: Bool = false,
precision: @escaping () -> Float,
duration: @escaping () -> TimeInterval,
layout: SwiftUISnapshotLayout = .sizeThatFits,
traits: UITraitCollection = .init()
) -> Snapshotting {
let config: ViewImageConfig
switch layout {
#if os(iOS) || os(tvOS)
case let .device(config: deviceConfig):
config = deviceConfig
#endif
case .sizeThatFits:
config = .init(safeArea: .zero, size: nil, traits: traits)
case let .fixed(width: width, height: height):
let size = CGSize(width: width, height: height)
config = .init(safeArea: .zero, size: size, traits: traits)
}
return SimplySnapshotting<UIImage>(pathExtension: "png", diffing: .prefireImage(precision: precision, scale: traits.displayScale))
.asyncPullback { view in
var config = config
let controller: UIViewController
if config.size != nil {
controller = UIHostingController(rootView: view)
} else {
let hostingController = UIHostingController(rootView: view)
let maxSize = CGSize.zero
config.size = hostingController.sizeThatFits(in: maxSize)
controller = hostingController
}
return Async<UIImage> { callback in
let strategy = snapshotView(
config: config,
drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
traits: traits,
view: controller.view,
viewController: controller
)
let duration = duration()
if duration != .zero {
let expectation = XCTestExpectation(description: "Wait")
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
expectation.fulfill()
}
_ = XCTWaiter.wait(for: [expectation], timeout: duration + 1)
}
strategy.run(callback)
}
}
}
}
private extension Diffing where Value == UIImage {
static func prefireImage(precision: @escaping () -> Float, scale: CGFloat?) -> Diffing {
lazy var originalDiffing = Diffing.image(precision: precision(), perceptualPrecision: 0.98, scale: scale)
return Diffing(
toData: { originalDiffing.toData($0) },
fromData: { originalDiffing.fromData($0) },
diff: { originalDiffing.diff($0, $1) }
)
}
}

Binary file not shown.

Binary file not shown.

View File

@ -29,8 +29,13 @@ targets:
type: bundle.unit-test
platform: iOS
buildToolPlugins:
- plugin: PrefireTestsPlugin
package: Prefire
dependencies:
- target: ElementX
- package: SnapshotTesting
info:
path: ../SupportingFiles/Info.plist
@ -44,6 +49,8 @@ targets:
sources:
- path: ../Sources
excludes:
- "**/__Snapshots__/**"
- path: ../SupportingFiles
- path: ../../ElementX/Sources/Other/InfoPlistReader.swift
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit

View File

@ -77,8 +77,11 @@ end
lane :unit_tests do
run_tests(
scheme: "UnitTests",
device: 'iPhone 14 (16.4)',
ensure_devices_found: true,
result_bundle: true,
number_of_retries: 3,
xcargs: '-skipPackagePluginValidation',
)
slather(

View File

@ -94,12 +94,15 @@ packages:
PostHog:
url: https://github.com/PostHog/posthog-ios
minorVersion: 2.0.3
Prefire:
url: https://github.com/BarredEwe/Prefire
minorVersion: 1.4.1
Sentry:
url: https://github.com/getsentry/sentry-cocoa
minorVersion: 8.6.0
SnapshotTesting:
url: https://github.com/pointfreeco/swift-snapshot-testing
minorVersion: 1.11.0
minorVersion: 1.13.0
SwiftState:
url: https://github.com/ReactKit/SwiftState
minorVersion: 6.0.0