mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Timeline UI Tests (#380)
* Add signalling between UI tests and the app. * Fix navigationStack on roomLayout screens and update screenshots Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
This commit is contained in:
@@ -173,6 +173,7 @@
|
||||
5B8B51CEC4717AF487794685 /* NotificationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B490675B8E31423AF116BDA /* NotificationServiceProxy.swift */; };
|
||||
5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; };
|
||||
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; };
|
||||
5D1C6D4E3583700DF7A64834 /* UITestSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D619626288AAB513B7620BB6 /* UITestSignalling.swift */; };
|
||||
5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; };
|
||||
5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */; };
|
||||
5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; };
|
||||
@@ -459,6 +460,7 @@
|
||||
F6E860FF7B18B81DF43B30B8 /* EncryptedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3FA7C8D4EF2B1873C180ED7 /* EncryptedRoomTimelineItem.swift */; };
|
||||
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; };
|
||||
F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; };
|
||||
F98333D53D85B0E029FDCA12 /* UITestSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D619626288AAB513B7620BB6 /* UITestSignalling.swift */; };
|
||||
F9981191DC408AED537C1749 /* MediaProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12C9E0B61A77C7F0EE7918C /* MediaProxy.swift */; };
|
||||
F99FB21EFC6D99D247FE7CBE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DE8DC9B3FBA402117DC4C49F /* Kingfisher */; };
|
||||
F9F6D2883BBEBB9A3789A137 /* OnboardingViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A941F289F6AB876BA3361A /* OnboardingViewModelTests.swift */; };
|
||||
@@ -708,7 +710,6 @@
|
||||
541542F5AC323709D8563458 /* AnalyticsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPrompt.swift; sourceTree = "<group>"; };
|
||||
542D4F49FABA056DEEEB3400 /* RustTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustTracing.swift; sourceTree = "<group>"; };
|
||||
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
|
||||
551DAED7F623AA5366E79927 /* repository */ = {isa = PBXFileReference; lastKnownFileType = folder; name = repository; path = .; sourceTree = SOURCE_ROOT; };
|
||||
55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
55EA4B03F92F31EAA83B3F7B /* FilePreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewModels.swift; sourceTree = "<group>"; };
|
||||
@@ -953,10 +954,12 @@
|
||||
D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||
D263254AFE5B7993FFBBF324 /* NSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NSE.entitlements; sourceTree = "<group>"; };
|
||||
D2D783758EAE6A88C93564EB /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = "<group>"; };
|
||||
D31DC8105C6233E5FFD9B84C /* element-x-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "element-x-ios"; path = .; sourceTree = SOURCE_ROOT; };
|
||||
D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
D3D455BC2423D911A62ACFB2 /* NSELogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSELogger.swift; sourceTree = "<group>"; };
|
||||
D4DA544B2520BFA65D6DB4BB /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineDeliveryStatusView.swift; sourceTree = "<group>"; };
|
||||
D619626288AAB513B7620BB6 /* UITestSignalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestSignalling.swift; sourceTree = "<group>"; };
|
||||
D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
D67CBAFA48ED0B6FCE74F88F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -1254,6 +1257,7 @@
|
||||
children = (
|
||||
46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */,
|
||||
CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */,
|
||||
D619626288AAB513B7620BB6 /* UITestSignalling.swift */,
|
||||
D751BB69BB7C38FD247517B4 /* UITestsRootCoordinator.swift */,
|
||||
);
|
||||
path = UITests;
|
||||
@@ -1937,7 +1941,7 @@
|
||||
9413F680ECDFB2B0DDB0DEF2 /* Packages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
551DAED7F623AA5366E79927 /* repository */,
|
||||
D31DC8105C6233E5FFD9B84C /* element-x-ios */,
|
||||
);
|
||||
name = Packages;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
@@ -3164,6 +3168,7 @@
|
||||
706F79A39BDB32F592B8C2C7 /* UIKitBackgroundTask.swift in Sources */,
|
||||
3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */,
|
||||
D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */,
|
||||
5D1C6D4E3583700DF7A64834 /* UITestSignalling.swift in Sources */,
|
||||
E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */,
|
||||
086C2FA7750378EB2BFD0BEE /* UITestsRootCoordinator.swift in Sources */,
|
||||
071A017E415AD378F2961B11 /* URL.swift in Sources */,
|
||||
@@ -3221,6 +3226,7 @@
|
||||
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */,
|
||||
0ED951768EC443A8728DE1D7 /* TimelineStyle.swift in Sources */,
|
||||
9A47B7EFE3793760EEF68FFE /* UITestScreenIdentifier.swift in Sources */,
|
||||
F98333D53D85B0E029FDCA12 /* UITestSignalling.swift in Sources */,
|
||||
35C57543D245E82CBFE15DF0 /* URL.swift in Sources */,
|
||||
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */,
|
||||
588411C8FD72B2A2DFE5F7DE /* XCUIElement.swift in Sources */,
|
||||
|
||||
@@ -79,4 +79,6 @@ struct RoomScreenViewStateBindings {
|
||||
enum RoomScreenErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
/// A specific error message shown in a toast.
|
||||
case toast(String)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ typealias RoomScreenViewModelType = StateStoreViewModel<RoomScreenViewState, Roo
|
||||
class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol {
|
||||
private enum Constants {
|
||||
static let backPaginationPageSize: UInt = 50
|
||||
static let toastErrorID = "RoomScreenToastError"
|
||||
}
|
||||
|
||||
private let timelineController: RoomTimelineControllerProtocol
|
||||
@@ -123,7 +124,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
private func paginateBackwards() async {
|
||||
switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) {
|
||||
case .failure:
|
||||
displayError(.alert(ElementL10n.roomTimelineBackpaginationFailure))
|
||||
displayError(.toast(ElementL10n.roomTimelineBackpaginationFailure))
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -179,6 +180,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
state.bindings.alertInfo = AlertInfo(id: type,
|
||||
title: ElementL10n.dialogTitleError,
|
||||
message: message)
|
||||
case .toast(let message):
|
||||
ServiceLocator.shared.userNotificationController.submitNotification(UserNotification(id: Constants.toastErrorID,
|
||||
type: .toast,
|
||||
title: message,
|
||||
iconName: "xmark"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
styledContentWithReactions
|
||||
}
|
||||
.padding(.horizontal, 16.0)
|
||||
.padding(timelineItem.isOutgoing ? .leading : .trailing, 40) // Extra padding to differentiate alignment.
|
||||
}
|
||||
|
||||
if timelineItem.isOutgoing {
|
||||
|
||||
@@ -64,7 +64,7 @@ enum RoomTimelineItemFixtures {
|
||||
senderId: "",
|
||||
senderDisplayName: "Helena"),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "And John's speech was amazing!And John's speech was amazing!And John's speech was amazing!",
|
||||
text: "And John's speech was amazing!",
|
||||
timestamp: "5 PM",
|
||||
inGroupState: .beginning,
|
||||
isOutgoing: true,
|
||||
|
||||
+60
-33
@@ -20,13 +20,8 @@ import Foundation
|
||||
class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
/// An array of timeline item arrays that will be inserted in order for each back pagination request.
|
||||
var backPaginationResponses: [[RoomTimelineItemProtocol]] = []
|
||||
/// The time delay added to each back pagination request.
|
||||
var backPaginationDelay: Duration = .milliseconds(500)
|
||||
|
||||
/// An array of timeline items that will be appended in order when ``simulateIncomingItems()`` is called.
|
||||
var incomingItems: [RoomTimelineItemProtocol] = []
|
||||
/// The time delay between each incoming item.
|
||||
var incomingDelay: Duration = .milliseconds(750)
|
||||
|
||||
let roomId = "MockRoomIdentifier"
|
||||
|
||||
@@ -34,38 +29,17 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
var timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default
|
||||
|
||||
func simulateIncomingItems() {
|
||||
guard !incomingItems.isEmpty else { return }
|
||||
|
||||
let incomingItem = incomingItems.removeFirst()
|
||||
|
||||
Task {
|
||||
try await Task.sleep(for: incomingDelay)
|
||||
timelineItems.append(incomingItem)
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
|
||||
if !self.incomingItems.isEmpty {
|
||||
simulateIncomingItems()
|
||||
}
|
||||
private var client: UITestsSignalling.Client?
|
||||
|
||||
init(listenForSignals: Bool = false) {
|
||||
if listenForSignals {
|
||||
client = .init()
|
||||
Task { try await startListening() }
|
||||
}
|
||||
}
|
||||
|
||||
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||
callbacks.send(.startedBackPaginating)
|
||||
|
||||
guard !backPaginationResponses.isEmpty else {
|
||||
callbacks.send(.finishedBackPaginating)
|
||||
return .failure(.generic)
|
||||
}
|
||||
|
||||
let newItems = backPaginationResponses.removeFirst()
|
||||
|
||||
try? await Task.sleep(for: backPaginationDelay)
|
||||
timelineItems.insert(contentsOf: newItems, at: 0)
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
callbacks.send(.finishedBackPaginating)
|
||||
|
||||
return .success(())
|
||||
.failure(.generic)
|
||||
}
|
||||
|
||||
func processItemAppearance(_ itemId: String) async { }
|
||||
@@ -89,4 +63,57 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
func retryDecryption(forSessionId sessionId: String) async { }
|
||||
|
||||
// MARK: - UI Test signalling
|
||||
|
||||
/// Allows the simulation of server responses by listening for signals from UI tests.
|
||||
private func startListening() async throws {
|
||||
try await client?.connect()
|
||||
|
||||
Task {
|
||||
while let client {
|
||||
do {
|
||||
try await handleSignal(client.receive())
|
||||
} catch {
|
||||
client.disconnect()
|
||||
self.client = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a UI test signal as necessary.
|
||||
private func handleSignal(_ signal: UITestsSignal) async throws {
|
||||
switch signal {
|
||||
case .paginate:
|
||||
try await simulateBackPagination()
|
||||
case .incomingMessage:
|
||||
try await simulateIncomingItem()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends the next incoming item to the `timelineItems` array.
|
||||
private func simulateIncomingItem() async throws {
|
||||
guard !incomingItems.isEmpty else { return }
|
||||
|
||||
let incomingItem = incomingItems.removeFirst()
|
||||
timelineItems.append(incomingItem)
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
|
||||
try? await client?.send(.success)
|
||||
}
|
||||
|
||||
/// Prepends the next chunk of items to the `timelineItems` array.
|
||||
private func simulateBackPagination() async throws {
|
||||
guard !backPaginationResponses.isEmpty else { return }
|
||||
|
||||
let newItems = backPaginationResponses.removeFirst()
|
||||
timelineItems.insert(contentsOf: newItems, at: 0)
|
||||
callbacks.send(.updatedTimelineItems)
|
||||
callbacks.send(.finishedBackPaginating)
|
||||
|
||||
try? await client?.send(.success)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ enum UITestScreenIdentifier: String {
|
||||
case roomSmallTimeline
|
||||
case roomSmallTimelineIncomingAndSmallPagination
|
||||
case roomSmallTimelineLargePagination
|
||||
case roomLayoutTop
|
||||
case roomLayoutMiddle
|
||||
case roomLayoutBottom
|
||||
case sessionVerification
|
||||
case userSessionScreen
|
||||
case roomDetailsScreen
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// Copyright 2022 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 Foundation
|
||||
import Network
|
||||
|
||||
enum UITestsSignal: String {
|
||||
/// An internal signal used to bring up the connection.
|
||||
case connect
|
||||
/// Ask the app to back paginate.
|
||||
case paginate
|
||||
/// Ask the app to simulate an incoming message.
|
||||
case incomingMessage
|
||||
/// The operation has completed successfully.
|
||||
case success
|
||||
}
|
||||
|
||||
enum UITestsSignalError: Error {
|
||||
/// An unknown error occurred.
|
||||
case unknown
|
||||
/// Signalling could not be used as is hasn't been enabled.
|
||||
case disabled
|
||||
/// The connection was cancelled.
|
||||
case cancelled
|
||||
/// The connection hasn't been established.
|
||||
case notConnected
|
||||
/// Attempted to receive multiple signals at once.
|
||||
case awaitingAnotherSignal
|
||||
/// A network error occurred.
|
||||
case nwError(NWError)
|
||||
/// An unexpected signal was received. This error isn't used internally.
|
||||
case unexpected
|
||||
}
|
||||
|
||||
enum UITestsSignalling {
|
||||
/// A UDP server that can be used for signalling between the UI tests jig and the app.
|
||||
/// The server should be instantiated on the UI tests side.
|
||||
class Server: UITestsSignallingProtocol {
|
||||
private let listener: NWListener
|
||||
|
||||
var connection: NWConnection
|
||||
var nextMessageContinuation: CheckedContinuation<UITestsSignal, Error>?
|
||||
|
||||
/// Creates a new signalling server.
|
||||
init() throws {
|
||||
connection = NWConnection(host: "127.0.0.1", port: 0, using: .udp)
|
||||
listener = try NWListener(using: .udp, on: 1234)
|
||||
}
|
||||
|
||||
/// Listens for a client and attempts to negotiate a connection when one is discovered.
|
||||
func connect() async throws {
|
||||
guard listener.state == .setup else { return }
|
||||
|
||||
return try await withCheckedThrowingContinuation { [weak self] continuation in
|
||||
listener.newConnectionHandler = { connection in
|
||||
connection.start(queue: .main)
|
||||
connection.stateUpdateHandler = { state in
|
||||
switch state {
|
||||
case .ready:
|
||||
self?.listener.cancel()
|
||||
self?.receiveNextMessage()
|
||||
continuation.resume()
|
||||
case .failed(let error):
|
||||
self?.listener.cancel()
|
||||
continuation.resume(with: .failure(error))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
self?.connection = connection
|
||||
}
|
||||
listener.start(queue: .main)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the connection (or the listener if a connection hasn't been established).
|
||||
func disconnect() {
|
||||
listener.cancel()
|
||||
connection.cancel()
|
||||
if let nextMessageContinuation {
|
||||
nextMessageContinuation.resume(throwing: UITestsSignalError.cancelled)
|
||||
self.nextMessageContinuation = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A UDP client that can be used for signalling between the app and the UI tests jig.
|
||||
/// The client should be instantiated on the app side.
|
||||
class Client: UITestsSignallingProtocol {
|
||||
let connection: NWConnection
|
||||
var nextMessageContinuation: CheckedContinuation<UITestsSignal, Error>?
|
||||
|
||||
/// Creates a new signalling client.
|
||||
init() {
|
||||
connection = NWConnection(host: "127.0.0.1", port: 1234, using: .udp)
|
||||
}
|
||||
|
||||
/// Attempts to connect to a server.
|
||||
func connect() async throws {
|
||||
guard connection.state == .setup else { return }
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
connection.start(queue: .main)
|
||||
connection.stateUpdateHandler = { state in
|
||||
switch state {
|
||||
case .ready:
|
||||
self.receiveNextMessage()
|
||||
continuation.resume()
|
||||
Task { try await self.send(.connect) }
|
||||
case .failed(let error):
|
||||
continuation.resume(with: .failure(error))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the connection.
|
||||
func disconnect() {
|
||||
connection.cancel()
|
||||
if let nextMessageContinuation {
|
||||
nextMessageContinuation.resume(throwing: UITestsSignalError.cancelled)
|
||||
self.nextMessageContinuation = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A shared implementation for sending/receiving signals between a client and a server.
|
||||
protocol UITestsSignallingProtocol: AnyObject {
|
||||
var connection: NWConnection { get }
|
||||
var nextMessageContinuation: CheckedContinuation<UITestsSignal, Error>? { get set }
|
||||
}
|
||||
|
||||
extension UITestsSignallingProtocol {
|
||||
/// Sends a message to the connected client/server.
|
||||
func send(_ signal: UITestsSignal) async throws {
|
||||
guard connection.state == .ready else { throw UITestsSignalError.notConnected }
|
||||
let data = signal.rawValue.data(using: .utf8)
|
||||
connection.send(content: data, completion: .idempotent)
|
||||
}
|
||||
|
||||
/// Returns the next message received by the client/server.
|
||||
func receive() async throws -> UITestsSignal {
|
||||
guard connection.state == .ready else { throw UITestsSignalError.notConnected }
|
||||
guard nextMessageContinuation == nil else { throw UITestsSignalError.awaitingAnotherSignal }
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
self.nextMessageContinuation = continuation
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the next message received by the client/server
|
||||
fileprivate func receiveNextMessage() {
|
||||
connection.receiveMessage { [weak self] completeContent, _, isComplete, error in
|
||||
guard let self else { return }
|
||||
guard isComplete else { fatalError("Partial messages not supported") }
|
||||
|
||||
guard let completeContent,
|
||||
let message = String(data: completeContent, encoding: .utf8),
|
||||
let signal = UITestsSignal(rawValue: message)
|
||||
else {
|
||||
let error: UITestsSignalError = error.map { .nwError($0) } ?? .unknown
|
||||
self.nextMessageContinuation?.resume(with: .failure(error))
|
||||
self.nextMessageContinuation = nil
|
||||
return
|
||||
}
|
||||
|
||||
if signal != .connect {
|
||||
self.nextMessageContinuation?.resume(returning: signal)
|
||||
self.nextMessageContinuation = nil
|
||||
}
|
||||
|
||||
self.receiveNextMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,11 +178,10 @@ class MockScreen: Identifiable {
|
||||
case .roomSmallTimelineIncomingAndSmallPagination:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let timelineController = MockRoomTimelineController(listenForSignals: true)
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.singleMessageChunk]
|
||||
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
|
||||
timelineController.simulateIncomingItems()
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Small timeline", avatarURL: "mock_url"),
|
||||
timelineController: timelineController,
|
||||
@@ -195,7 +194,7 @@ class MockScreen: Identifiable {
|
||||
case .roomSmallTimelineLargePagination:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let timelineController = MockRoomTimelineController(listenForSignals: true)
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
@@ -205,6 +204,52 @@ class MockScreen: Identifiable {
|
||||
emojiProvider: EmojiProvider())
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomLayoutTop:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController(listenForSignals: true)
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: "mock_url"),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomLayoutMiddle:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController(listenForSignals: true)
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
|
||||
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: "mock_url"),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomLayoutBottom:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController(listenForSignals: true)
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
|
||||
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: "mock_url"),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .sessionVerification:
|
||||
|
||||
@@ -41,13 +41,21 @@ extension XCUIApplication {
|
||||
|
||||
/// 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, step: Int? = nil) {
|
||||
/// - Parameter step: An optional integer that can be used to take multiple snapshots per test identifier.
|
||||
/// - Parameter insets: Optional insets with which to crop the image by.
|
||||
func assertScreenshot(_ identifier: UITestScreenIdentifier, step: Int? = nil, insets: UIEdgeInsets? = nil) {
|
||||
var snapshotName = identifier.rawValue
|
||||
if let step {
|
||||
snapshotName += "-\(step)"
|
||||
}
|
||||
|
||||
var snapshot = XCUIScreen.main.screenshot().image
|
||||
|
||||
if let insets {
|
||||
snapshot = snapshot.inset(by: insets)
|
||||
}
|
||||
|
||||
let failure = verifySnapshot(matching: XCUIScreen.main.screenshot().image,
|
||||
let failure = verifySnapshot(matching: snapshot,
|
||||
as: .image(precision: 0.99, perceptualPrecision: 0.98, scale: nil),
|
||||
named: snapshotName,
|
||||
testName: testName)
|
||||
@@ -79,3 +87,15 @@ extension XCUIApplication {
|
||||
UIDevice.current.systemVersion.replacingOccurrences(of: ".", with: "-")
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIImage {
|
||||
/// Adjusts the image by cropping it with the given edge insets.
|
||||
func inset(by insets: UIEdgeInsets) -> UIImage {
|
||||
let insetRect = CGRect(origin: .zero, size: size).inset(by: insets)
|
||||
let renderer = UIGraphicsImageRenderer(size: insetRect.size)
|
||||
|
||||
return renderer.image { _ in
|
||||
draw(at: CGPoint(x: -insets.left, y: -insets.top))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,19 +47,127 @@ class RoomScreenUITests: XCTestCase {
|
||||
app.assertScreenshot(.roomSmallTimeline)
|
||||
}
|
||||
|
||||
func testSmallTimelineWithIncomingAndPagination() {
|
||||
func testSmallTimelineWithIncomingAndPagination() async throws {
|
||||
let server = try UITestsSignalling.Server()
|
||||
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.roomSmallTimelineIncomingAndSmallPagination)
|
||||
|
||||
// Wait for both the incoming message and the pagination chunk.
|
||||
XCTAssert(app.staticTexts["Bob"].waitForExistence(timeout: 2))
|
||||
XCTAssert(app.staticTexts["Helena"].waitForExistence(timeout: 2))
|
||||
try await server.connect()
|
||||
defer { server.disconnect() }
|
||||
|
||||
// When a back pagination occurs and an incoming message arrives.
|
||||
try await performOperation(.incomingMessage, using: server)
|
||||
try await performOperation(.paginate, using: server)
|
||||
|
||||
// The messages should still be bottom aligned after the new items are added.
|
||||
// Then the 4 visible messages should stay aligned to the bottom.
|
||||
app.assertScreenshot(.roomSmallTimelineIncomingAndSmallPagination)
|
||||
}
|
||||
|
||||
func testSmallTimelineWithLargePagination() {
|
||||
// To be implemented
|
||||
func testSmallTimelineWithLargePagination() async throws {
|
||||
let server = try UITestsSignalling.Server()
|
||||
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.roomSmallTimelineLargePagination)
|
||||
|
||||
try await server.connect()
|
||||
defer { server.disconnect() }
|
||||
|
||||
// When a large back pagination occurs.
|
||||
try await performOperation(.paginate, using: server)
|
||||
|
||||
// The bottom of the timeline should remain visible with more items added above.
|
||||
app.assertScreenshot(.roomSmallTimelineLargePagination)
|
||||
}
|
||||
|
||||
func testTimelineLayoutInMiddle() async throws {
|
||||
let server = try UITestsSignalling.Server()
|
||||
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.roomLayoutMiddle)
|
||||
|
||||
try await server.connect()
|
||||
defer { server.disconnect() }
|
||||
|
||||
// Given a timeline that is neither at the top nor the bottom.
|
||||
app.tables.element.swipeDown()
|
||||
try await Task.sleep(for: .milliseconds(500)) // Allow the table to settle
|
||||
app.assertScreenshot(.roomLayoutMiddle, step: 0) // Assert initial state for comparison.
|
||||
|
||||
// When a back pagination occurs.
|
||||
try await performOperation(.paginate, using: server)
|
||||
|
||||
// Then the UI should remain unchanged.
|
||||
app.assertScreenshot(.roomLayoutMiddle, step: 0)
|
||||
|
||||
// When an incoming message arrives
|
||||
try await performOperation(.incomingMessage, using: server)
|
||||
|
||||
// Then the UI should still remain unchanged.
|
||||
app.assertScreenshot(.roomLayoutMiddle, step: 0)
|
||||
|
||||
// When the keyboard appears for the message composer.
|
||||
try await tapMessageComposer(in: app)
|
||||
|
||||
// Then the timeline scroll offset should remain unchanged.
|
||||
app.assertScreenshot(.roomLayoutMiddle, step: 1)
|
||||
}
|
||||
|
||||
func testTimelineLayoutAtTop() async throws {
|
||||
let server = try UITestsSignalling.Server()
|
||||
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.roomLayoutTop)
|
||||
|
||||
try await server.connect()
|
||||
defer { server.disconnect() }
|
||||
|
||||
// Given a timeline that is scrolled to the top.
|
||||
while !app.staticTexts["Bacon ipsum dolor amet commodo incididunt ribeye dolore cupidatat short ribs."].isHittable {
|
||||
app.tables.element.swipeDown()
|
||||
}
|
||||
let cropped = UIEdgeInsets(top: 150, left: 0, bottom: 0, right: 0) // Ignore the navigation bar and pagination indicator as these change.
|
||||
app.assertScreenshot(.roomLayoutTop, insets: cropped) // Assert initial state for comparison.
|
||||
|
||||
// When a back pagination occurs.
|
||||
try await performOperation(.paginate, using: server)
|
||||
|
||||
// Then the bottom of the timeline should remain unchanged (with new items having been added above).
|
||||
app.assertScreenshot(.roomLayoutTop, insets: cropped)
|
||||
}
|
||||
|
||||
func testTimelineLayoutAtBottom() async throws {
|
||||
let server = try UITestsSignalling.Server()
|
||||
|
||||
let app = Application.launch()
|
||||
app.goToScreenWithIdentifier(.roomLayoutBottom)
|
||||
|
||||
try await server.connect()
|
||||
defer { server.disconnect() }
|
||||
|
||||
// When an incoming message arrives.
|
||||
try await performOperation(.incomingMessage, using: server)
|
||||
|
||||
// Then the timeline should scroll down to reveal the message.
|
||||
app.assertScreenshot(.roomLayoutBottom, step: 0)
|
||||
|
||||
// When the keyboard appears for the message composer.
|
||||
try await tapMessageComposer(in: app)
|
||||
|
||||
// Then the timeline should still show the last message.
|
||||
app.assertScreenshot(.roomLayoutBottom, step: 1)
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
private func performOperation(_ operation: UITestsSignal, using server: UITestsSignalling.Server) async throws {
|
||||
try await server.send(operation)
|
||||
guard try await server.receive() == .success else { throw UITestsSignalError.unexpected }
|
||||
try await Task.sleep(for: .milliseconds(500)) // Allow the timeline to update
|
||||
}
|
||||
|
||||
private func tapMessageComposer(in app: XCUIApplication) async throws {
|
||||
app.textViews.element.tap()
|
||||
try await Task.sleep(for: .milliseconds(500)) // Allow the animations to complete
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9902bf55f027c729fd31b790d875699bf4a2e0f2852b9ceb2178c84bc8aca226
|
||||
size 211684
|
||||
oid sha256:32e747c2a8b7b7cd5aeee2f8e50f26ef2af0bf03d2fd4a89e25fad0104687fcf
|
||||
size 211496
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3cb8e1d07e418e7a3fecd9b0699158b8fef40609771fef9666428015d056738c
|
||||
size 305317
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:410e331dca5dbd99356ed345216f83288e231b808d4c3cbc05276a38dc5fba1f
|
||||
size 304826
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6018b19cf7b412b3a74080a4b3b4bf35ce86a4ef187f9b4945f68b9744760dd6
|
||||
size 314107
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5d507e3246043e67cc67873a83f9cb2a82e200f6debecfbf2cc4dd2e1fab86e9
|
||||
size 321969
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6aef1da13173322dbd51e933df69733a48c02b89cfdf564c41ac1933c7a4f9b8
|
||||
size 303249
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1fbf57e59072a7e06026a69a0858ed9f65a72e0bfa4fabdf4de6a28e1bc4b364
|
||||
size 211052
|
||||
oid sha256:85656b18c4ef17cf3a36aaef699fffdd34c43089749b5496b09f18fd3a520e60
|
||||
size 210875
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:089f14916f6e386fb8ea620df10e74f8f24c703ccfba65b2c46c5f5888c79f3d
|
||||
size 311767
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dd603c2e38d5281190788cfa06425a512587fbb49a32d5d895361533ab48419a
|
||||
size 373824
|
||||
oid sha256:9ba4ef4f768e1c44c0e058db23dc97e8a50516af2a2001fc5f433abf16aeac1a
|
||||
size 366658
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8943c20d22d19e720eb949a47dce8ab304da3cdb6ae292dc007d38c62ee435a8
|
||||
size 330436
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4312ba6002da73071187b871f6532f122bb6c4c3fdf3c9e7a93e9baffd27edfd
|
||||
size 323699
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ff71a79bf353a284c2c8afa92ce2562a45c25c64486fb132d795a07bf0c4b6f0
|
||||
size 335617
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a466639c4f320f4d684fe485255fd217d960665d8d9ea6cd1dd0e1f425b8cac0
|
||||
size 335663
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:01a5fa9431bdfa0dffc6ed4ec5816db6eb923b8048f3af3124eb8701dad59ba1
|
||||
size 283965
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:066c66947a2d47092dde8db56d885cd62127f24a817a06198e2ab0d29fa61857
|
||||
size 371508
|
||||
oid sha256:54301aeca9fdaea001bbabf7bd9d4624c820c6cca08bd75eaa3a66a3b88e271a
|
||||
size 365082
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3295b6e8348a37998c95f72285ae0bfc5651ec3166a376c327f482a0baa1e93d
|
||||
size 114678
|
||||
oid sha256:94c4a7bb1f6ecf74120324ab118ab314f07663d0b01889581804afc6826a70d0
|
||||
size 115882
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3f7192b4cffb7a9079b4a90a4dcb03d663cde46c0835eeb2481deead9d069d30
|
||||
size 333980
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a34acc9936b4fbff9aad17617ee77dd8c89356d652b2a7132f5bb7d20c4f715
|
||||
size 210981
|
||||
oid sha256:874b7aafe67bc385648ffdfce0d6fa8b75899dcd923de24a97b2d9a5246d4e9e
|
||||
size 210847
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e293cc224e3c71f5339ca59f10db4450d96e025738c9117b18d41b57fe49a4d5
|
||||
size 305117
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4580e0f596f44aece15bf7b8deb28b996e4c3f2132babd243d8896cdc67319f9
|
||||
size 307638
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f5bf2982f09c57a980df0dc3d3dba3d3bafa67269704aa342a2dd5d169e9971b
|
||||
size 313966
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d9ae251abae3f358ac0c2aae41ff55ff9eb209696d2dff43154679cfb2ba30ea
|
||||
size 319312
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3e2192501b67459b5d4f85353976e7d3a7b5cf55d5b1f712b41c736579a919be
|
||||
size 303063
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ec8c0bdbf78d2bd1019cd8319aed419fd729be3d8be566b729d171cc21adec8b
|
||||
size 210340
|
||||
oid sha256:e181e9ded4026b2ecfb48c7a2f9d3f4b5045c4d08608dbca45ad26cca831c160
|
||||
size 210222
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c7482b45bcde59ec93b1a3f211d2f3187357b5326be5a70dce4a309c8204b6a2
|
||||
size 311570
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f7322585bdb968fa0ad5244e5ca05401e7b32e3c27a0e3b1abb4f74f2cc736f9
|
||||
size 372873
|
||||
oid sha256:aa7c6d90c7f8021a1131a5c5db0f15aaf282548642cd97aeecac8574969771d3
|
||||
size 366679
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ba8df321653a203447b6a48facc32c1b99a3167ed3f1c44019d06aa53b07db9b
|
||||
size 330472
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:611e841427673aee43571e8b3079f9f84f9624b05efcc678f687ec496c9d66af
|
||||
size 323568
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e17cbe7c08f8feddfeaec82d64d4c7ba001c91ef3ee5a38efd4e9eafe73e9d9b
|
||||
size 335662
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:90f0175794dfebcc609105207e345e34c8d272cbd896f4bff0786ee4ecbf9c0c
|
||||
size 335528
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e9b41ae6543ffcc994c2468f48641149d1932065600f94af29379d0fbb5d1297
|
||||
size 283690
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf8edaf502cb0a8ea89699280a31ef2a8505b2107fb67d81975b9a63813bdd2e
|
||||
size 370584
|
||||
oid sha256:92e93f947f47be4f87e3c997f1e9103064771a78a95836dc38b24ed92431d581
|
||||
size 365103
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a365ef80c8bce284dfddd840433afe9fa6b3d7eee8521da98e065777a6819530
|
||||
size 114689
|
||||
oid sha256:9b780d9c6cdd369717d2e0981bcbcc641a5091b3835d4b7ac6315d3486098c1b
|
||||
size 115915
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0d6d8a1147d9d7292a3ef66658d6bece8dca96434ae7951f4400db0a081e4337
|
||||
size 334023
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aa4dc34fbb2b7412232fb048d1ddde465f949784bc4e9af0374d7cae490a5dd3
|
||||
size 211181
|
||||
oid sha256:64679be3305dcfd54950f6d00ea6de2d2156a9e91c09f1c2635f222f65cd21ac
|
||||
size 211035
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e293cc224e3c71f5339ca59f10db4450d96e025738c9117b18d41b57fe49a4d5
|
||||
size 305117
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6656cd40298881511962f8a3fd292b990b1c029f0cf4fc8f7050e5a4fba662aa
|
||||
size 305022
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a8b8dfcebe6730d4e584bf757cf0e9599130d1d9b981e77bba9a25d2cb28bc5b
|
||||
size 313911
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5d436eaaeb195d07a600fd8ae093b85bf616ca6f03bffbdf17d0f7284f2ed2c1
|
||||
size 319464
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3e2192501b67459b5d4f85353976e7d3a7b5cf55d5b1f712b41c736579a919be
|
||||
size 303063
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ca335bfdc0d02a56eb7584e9f18004361abcb0365c30733861bbd20b7a385b3b
|
||||
size 210546
|
||||
oid sha256:91060605f5e1166dc33eb3481845e60ac8c49da257395b37c513bf8477b3304d
|
||||
size 210405
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c7482b45bcde59ec93b1a3f211d2f3187357b5326be5a70dce4a309c8204b6a2
|
||||
size 311570
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:794a4b78bd5e5cda370bfac8168d19951ef5005c5d9267d372b9b55c9accfa4b
|
||||
size 373232
|
||||
oid sha256:aa7c6d90c7f8021a1131a5c5db0f15aaf282548642cd97aeecac8574969771d3
|
||||
size 366679
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ba8df321653a203447b6a48facc32c1b99a3167ed3f1c44019d06aa53b07db9b
|
||||
size 330472
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:611e841427673aee43571e8b3079f9f84f9624b05efcc678f687ec496c9d66af
|
||||
size 323568
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e17cbe7c08f8feddfeaec82d64d4c7ba001c91ef3ee5a38efd4e9eafe73e9d9b
|
||||
size 335662
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:90f0175794dfebcc609105207e345e34c8d272cbd896f4bff0786ee4ecbf9c0c
|
||||
size 335528
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e9b41ae6543ffcc994c2468f48641149d1932065600f94af29379d0fbb5d1297
|
||||
size 283690
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:39a5f3d31a13112058a4b4c3d0b83066ff1e3a546b65e82234970b76748812ed
|
||||
size 370940
|
||||
oid sha256:92e93f947f47be4f87e3c997f1e9103064771a78a95836dc38b24ed92431d581
|
||||
size 365103
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:180aae2e7f67beb59cfee31788efaf7f0a6b4fef910956ba1db1ed22edbfdc7f
|
||||
size 114685
|
||||
oid sha256:9b780d9c6cdd369717d2e0981bcbcc641a5091b3835d4b7ac6315d3486098c1b
|
||||
size 115915
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0d6d8a1147d9d7292a3ef66658d6bece8dca96434ae7951f4400db0a081e4337
|
||||
size 334023
|
||||
@@ -61,6 +61,7 @@ targets:
|
||||
- path: ../../ElementX/Sources/Screens/RoomScreen/View/Style/TimelineStyle.swift
|
||||
- path: ../../ElementX/Sources/Services/Analytics/AnalyticsConfiguration.swift
|
||||
- path: ../../ElementX/Sources/UITests/UITestScreenIdentifier.swift
|
||||
- path: ../../ElementX/Sources/UITests/UITestSignalling.swift
|
||||
- path: ../../ElementX/Sources/Generated/Strings.swift
|
||||
- path: ../../ElementX/Sources/Generated/Strings+Untranslated.swift
|
||||
- path: ../../ElementX/Resources
|
||||
|
||||
Reference in New Issue
Block a user