[96] [Scaffold] Received Transaction Details (#378)

- the view holding all requested (mocked) data is prepared
- copy to pasteboard implemented
- deeplink 'reply-to' memo implemented
- view online implemented and button visible only when the URL is valid
- asset colors for the texts
- unit tests
- snapshot tests
This commit is contained in:
Lukas Korba 2022-06-28 09:38:08 +02:00 committed by GitHub
parent e321114214
commit df2c721d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 446 additions and 20 deletions

View File

@ -120,6 +120,7 @@
9E5BF64F2823E94900BA3F17 /* TransactionAddressTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */; };
9E5BF6502823E94900BA3F17 /* TransactionAddressTextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */; };
9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; };
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; };
9E7FE0CF282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */; };
9E7FE0D3282D274E00C374E8 /* Date+Readable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */; };
9E7FE0D5282D281800C374E8 /* Array+Chunked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */; };
@ -329,6 +330,7 @@
9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextField.swift; sourceTree = "<group>"; };
9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextFieldStore.swift; sourceTree = "<group>"; };
9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = "<group>"; };
9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsSnapshotTests.swift; sourceTree = "<group>"; };
9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SDKSynchronizer+SyncStatus.swift"; sourceTree = "<group>"; };
9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Readable.swift"; sourceTree = "<group>"; };
9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Chunked.swift"; sourceTree = "<group>"; };
@ -810,6 +812,7 @@
9E391162284E3ECF0073DD9A /* SnapshotTests */ = {
isa = PBXGroup;
children = (
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */,
9E9ECC8B28589E150099D5A2 /* HomeSnapshotTests */,
9E9ECC9328589E150099D5A2 /* ImportWalletSnapshotTests */,
9E9ECC9528589E150099D5A2 /* OnboardingSnapshotTests */,
@ -858,6 +861,14 @@
path = TransactionAddress;
sourceTree = "<group>";
};
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */ = {
isa = PBXGroup;
children = (
9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */,
);
path = WalletEventsSnapshotTests;
sourceTree = "<group>";
};
9E7FE0B6282D1D9800C374E8 /* Resources */ = {
isa = PBXGroup;
children = (
@ -1543,6 +1554,7 @@
9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */,
9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */,
9E9ECC9828589E150099D5A2 /* WelcomeSnapshotTests.swift in Sources */,
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */,
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
9E9ECC9A28589E150099D5A2 /* RecoveryPhraseValidationFlowSnapshotTests.swift in Sources */,

View File

@ -342,6 +342,12 @@ extension AppReducer {
state.homeState.sendState.memo = memo
return .none
case .home(.walletEvents(.replyTo(let address))):
guard let url = URL(string: "zcash:\(address)") else {
return .none
}
return Effect(value: .deeplink(url))
/// Default is meaningful here because there's `appReducer` handling actions and this reducer is handling only routes. We don't here plenty of unused cases.
default:
break

View File

@ -181,7 +181,8 @@ extension HomeReducer {
environment: { environment in
WalletEventsFlowEnvironment(
scheduler: environment.scheduler,
SDKSynchronizer: environment.SDKSynchronizer
SDKSynchronizer: environment.SDKSynchronizer,
pasteboard: .live
)
}
)

View File

@ -50,7 +50,8 @@ extension SandboxReducer {
walletEventsAction,
WalletEventsFlowEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
pasteboard: .live
)
)
.map(SandboxAction.walletEvents)

View File

@ -1,11 +1,146 @@
import SwiftUI
import ComposableArchitecture
struct TransactionDetailView: View {
var transaction: TransactionState
var viewStore: WalletEventsFlowViewStore
var body: some View {
Text(String(dumping: transaction))
ScrollView {
HStack {
Text("\(transaction.date.asHumanReadable())")
Spacer()
Text("HEIGHT \(heightText)")
}
.padding()
.navigationTitle("Transaction: \(transaction.id)")
Text("\(amountPrefixText) \(transaction.zecAmount.decimalString()) ZEC")
.transactionDetailRow()
Text("fee \(transaction.fee.decimalString()) ZEC")
.transactionDetailRow()
Text("total amount \(transaction.totalAmount.decimalString()) ZEC")
.transactionDetailRow()
Button {
viewStore.send(.copyToPastboard(transaction.address))
} label: {
Text("\(addressPrefixText) \(transaction.address)")
.lineLimit(1)
.truncationMode(.middle)
.transactionDetailRow()
}
if let memo = transaction.memo {
Button {
viewStore.send(.copyToPastboard(memo))
} label: {
VStack {
Text("\(memo)")
.multilineTextAlignment(.leading)
HStack {
Text("reply-to address included")
Spacer()
Button {
viewStore.send(.replyTo(transaction.address))
} label: {
Text("reply now")
.padding(5)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Asset.Colors.Text.transactionDetailText.color, lineWidth: 1)
)
}
}
}
.transactionDetailRow()
}
}
HStack {
Text("Confirmed")
Spacer()
Text("\(transaction.confirmations) times")
}
.transactionDetailRow()
Spacer()
Button {
viewStore.send(.copyToPastboard(transaction.id))
} label: {
Text("txn: \(transaction.id)")
.foregroundColor(Asset.Colors.Text.transactionDetailText.color)
.font(.system(size: 14))
.fontWeight(.thin)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.horizontal, 60)
.padding(.vertical, 10)
.background(Asset.Colors.BackgroundColors.numberedChip.color)
.padding(.vertical, 30)
}
Button { } label: {
// TODO: Warn users that they will leave the App when they follow a Block explorer
// https://github.com/zcash/secant-ios-wallet/issues/379
if let viewOnlineURL = transaction.viewOnlineURL {
Link("View online", destination: viewOnlineURL)
}
}
.activeButtonStyle
.frame(height: 50)
.padding(.horizontal, 30)
}
.applyScreenBackground()
.navigationTitle("Transaction detail")
}
}
extension TransactionDetailView {
var amountPrefixText: String {
transaction.status == .received ? "You received" : "You sent"
}
var addressPrefixText: String {
transaction.status == .received ? "from" : "to"
}
var heightText: String {
transaction.minedHeight > 0 ? String(transaction.minedHeight) : "unconfirmed"
}
}
struct TransactionDetailRow: ViewModifier {
let tint: Color
let textColor: Color
let backgroundColor: Color
func body(content: Content) -> some View {
content
.foregroundColor(textColor)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(backgroundColor)
.padding(.leading, 20)
.background(tint)
}
}
extension View {
func transactionDetailRow(
_ tint: Color = Asset.Colors.BackgroundColors.red.color,
_ textColor: Color = Asset.Colors.Text.transactionDetailText.color,
_ backgroundColor: Color = Asset.Colors.BackgroundColors.numberedChip.color
) -> some View {
modifier(
TransactionDetailRow(
tint: tint,
textColor: textColor,
backgroundColor: backgroundColor
)
)
}
}
@ -14,7 +149,38 @@ struct TransactionDetailView: View {
struct TransactionDetail_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TransactionDetailView(transaction: .placeholder)
TransactionDetailView(
transaction:
TransactionState(
memo:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true),
subtitle: "",
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
),
viewStore: ViewStore(
WalletEventsFlowStore(
initialState: .placeHolder,
reducer: .default,
environment:
WalletEventsFlowEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: MockWrappedSDKSynchronizer(),
pasteboard: .test
)
)
)
)
.preferredColorScheme(.dark)
}
}
}

View File

@ -23,9 +23,11 @@ struct WalletEventsFlowState: Equatable {
// MARK: - Action
enum WalletEventsFlowAction: Equatable {
case copyToPastboard(String)
case onAppear
case onDisappear
case updateRoute(WalletEventsFlowState.Route?)
case replyTo(String)
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case updateWalletEvents([WalletEvent])
}
@ -35,6 +37,7 @@ enum WalletEventsFlowAction: Equatable {
struct WalletEventsFlowEnvironment {
let scheduler: AnySchedulerOf<DispatchQueue>
let SDKSynchronizer: WrappedSDKSynchronizer
let pasteboard: WrappedPasteboard
}
// MARK: - Reducer
@ -70,9 +73,16 @@ extension WalletEventsFlowReducer {
state.walletEvents = IdentifiedArrayOf(uniqueElements: sortedWalletEvents)
return .none
case let .updateRoute(route):
case .updateRoute(let route):
state.route = route
return .none
case .copyToPastboard(let value):
environment.pasteboard.setString(value)
return .none
case .replyTo(let address):
return .none
}
}
}
@ -97,6 +107,7 @@ extension WalletEventsFlowViewStore {
extension TransactionState {
static var placeholder: Self {
.init(
fee: Zatoshi(amount: 10),
id: "2",
status: .paid(success: true),
subtitle: "",
@ -123,7 +134,8 @@ extension WalletEventsFlowStore {
reducer: .default,
environment: WalletEventsFlowEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
pasteboard: .live
)
)
}
@ -134,6 +146,7 @@ extension IdentifiedArrayOf where Element == TransactionState {
return .init(
uniqueElements: (0..<30).map {
TransactionState(
fee: Zatoshi(amount: 10),
id: String($0),
status: .paid(success: true),
subtitle: "",

View File

@ -33,11 +33,11 @@ extension WalletEventsFlowView {
ForEach(viewStore.walletEvents) { walletEvent in
WithStateBinding(binding: viewStore.bindingForSelectingWalletEvent(walletEvent)) { active in
VStack {
walletEvent.rowView()
walletEvent.rowView(viewStore)
}
.navigationLink(
isActive: active,
destination: { walletEvent.detailView() }
destination: { walletEvent.detailView(viewStore) }
)
.foregroundColor(Asset.Colors.Text.body.color)
.listRowBackground(Color.clear)

View File

@ -16,19 +16,26 @@ struct TransactionState: Equatable, Identifiable {
case failed
}
var confirmations = 0
var expirationHeight = -1
var memo: String?
var minedHeight = -1
var shielded = true
var zAddress: String?
var fee: Zatoshi
var id: String
var status: Status
var subtitle: String
var timestamp: TimeInterval
var zecAmount: Zatoshi
var address: String { zAddress ?? "" }
var date: Date { Date(timeIntervalSince1970: timestamp) }
var totalAmount: Zatoshi { Zatoshi(amount: zecAmount.amount + fee.amount) }
var viewOnlineURL: URL? {
URL(string: "https://blockchair.com/zcash/transaction/\(id)")
}
}
extension TransactionState {
@ -40,6 +47,7 @@ extension TransactionState {
subtitle = "sent"
zAddress = confirmedTransaction.toAddress
zecAmount = sent ? Zatoshi(amount: -Int64(confirmedTransaction.value)) : Zatoshi(amount: Int64(confirmedTransaction.value))
fee = Zatoshi(amount: 10)
if let memo = confirmedTransaction.memo {
self.memo = memo.asZcashTransactionMemo()
}
@ -55,6 +63,7 @@ extension TransactionState {
subtitle = "pending"
zAddress = pendingTransaction.toAddress
zecAmount = Zatoshi(amount: -Int64(pendingTransaction.value))
fee = Zatoshi(amount: 10)
if let memo = pendingTransaction.memo {
self.memo = memo.asZcashTransactionMemo()
}
@ -67,6 +76,7 @@ extension TransactionState {
extension TransactionState {
static func placeholder(
amount: Zatoshi,
fee: Zatoshi,
shielded: Bool = true,
status: Status = .received,
subtitle: String = "",
@ -79,6 +89,7 @@ extension TransactionState {
minedHeight: -1,
shielded: shielded,
zAddress: nil,
fee: fee,
id: uuid,
status: status,
subtitle: subtitle,

View File

@ -30,7 +30,7 @@ struct WalletEvent: Equatable, Identifiable {
// MARK: - Rows
extension WalletEvent {
@ViewBuilder func rowView() -> some View {
@ViewBuilder func rowView(_ viewStore: WalletEventsFlowViewStore) -> some View {
switch state {
case .send(let transaction):
TransactionRowView(transaction: transaction)
@ -51,10 +51,10 @@ extension WalletEvent {
// MARK: - Details
extension WalletEvent {
@ViewBuilder func detailView() -> some View {
@ViewBuilder func detailView(_ viewStore: WalletEventsFlowViewStore) -> some View {
switch state {
case .send(let transaction):
TransactionDetailView(transaction: transaction)
TransactionDetailView(transaction: transaction, viewStore: viewStore)
case .pending:
Text("pending transaction detail")
case .received:

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x52",
"green" : "0x31",
"red" : "0x26"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -111,6 +111,7 @@ internal enum Asset {
internal static let regular = ColorAsset(name: "Regular")
internal static let secondaryButtonText = ColorAsset(name: "SecondaryButtonText")
internal static let titleText = ColorAsset(name: "TitleText")
internal static let transactionDetailText = ColorAsset(name: "TransactionDetailText")
internal static let validMnemonic = ColorAsset(name: "ValidMnemonic")
internal static let captionText = ColorAsset(name: "captionText")
internal static let captionTextShadow = ColorAsset(name: "captionTextShadow")

View File

@ -307,6 +307,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
mocked.map {
let transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
subtitle: $0.subtitle,
@ -331,6 +332,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
mocked.map {
let transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
subtitle: $0.subtitle,
@ -368,6 +370,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
minedHeight: 50,
shielded: true,
zAddress: "tteafadlamnelkqe",
fee: Zatoshi(amount: 10),
id: "id",
status: .paid(success: true),
subtitle: "sub",
@ -417,6 +420,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
mocked.map {
let transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
subtitle: $0.subtitle,
@ -447,6 +451,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
mocked.map {
let transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
subtitle: $0.subtitle,

View File

@ -166,4 +166,19 @@ class AppTests: XCTestCase {
store.receive(.checkBackupPhraseValidation)
}
func testWalletEventReplyTo_validAddress() throws {
let store = TestStore(
initialState: .placeholder,
reducer: AppReducer.default,
environment: testEnvironment
)
let address = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
store.send(.home(.walletEvents(.replyTo(address))))
if let url = URL(string: "zcash:\(address)") {
store.receive(.deeplink(url))
}
}
}

View File

@ -77,6 +77,7 @@ class HomeTests: XCTestCase {
let walletEvents: [WalletEvent] = transactionsHelper.map {
let transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
subtitle: $0.subtitle,

View File

@ -64,6 +64,7 @@ class SendTests: XCTestCase {
minedHeight: 50,
shielded: true,
zAddress: "tteafadlamnelkqe",
fee: Zatoshi(amount: 10),
id: "id",
status: .paid(success: true),
subtitle: "sub",

View File

@ -18,9 +18,11 @@ class HomeSnapshotTests: XCTestCase {
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4), uuid: "4"),
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(amount: 5), uuid: "5")
]
let transactions: [WalletEvent] = transactionsHelper.map {
let walletEvents: [WalletEvent] = transactionsHelper.map {
let transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
subtitle: $0.subtitle,
@ -41,7 +43,7 @@ class HomeSnapshotTests: XCTestCase {
scanState: .placeholder,
synchronizerStatus: "",
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: transactions)),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents)),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
@ -61,4 +63,61 @@ class HomeSnapshotTests: XCTestCase {
HomeView(store: store)
)
}
func testWalletEventDetailSnapshot() throws {
let transaction = TransactionState(
memo:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true),
subtitle: "",
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
)
let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
let balance = Balance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatus: "",
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
environment: .demo
)
// wallet event detail
let testEnvironment = WalletEventsFlowEnvironment(
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: .test
)
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))
let walletEventsStore = WalletEventsFlowStore(
initialState: .placeHolder,
reducer: .default,
environment: testEnvironment
)
addAttachments(
name: "\(#function)_WalletEventDetail",
TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore))
)
}
}

View File

@ -0,0 +1,69 @@
//
// WalletEventsSnapshotTests.swift
// secantTests
//
// Created by Lukáš Korba on 27.06.2022.
//
import XCTest
@testable import secant_testnet
import ComposableArchitecture
class WalletEventsSnapshotTests: XCTestCase {
func testWalletEventDetailSnapshot() throws {
let transaction = TransactionState(
memo:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(amount: 1_000_000),
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
status: .paid(success: true),
subtitle: "",
timestamp: 1234567,
zecAmount: Zatoshi(amount: 25_000_000)
)
let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp)
let balance = Balance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
synchronizerStatus: "",
totalBalance: Zatoshi(amount: balance.total),
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: Zatoshi(amount: balance.verified)
),
reducer: .default,
environment: .demo
)
// wallet event detail
let testEnvironment = WalletEventsFlowEnvironment(
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: .test
)
ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent))))
let walletEventsStore = WalletEventsFlowStore(
initialState: .placeHolder,
reducer: .default,
environment: testEnvironment
)
addAttachments(
name: "\(#function)_WalletEventDetail",
TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore))
)
}
}

View File

@ -14,7 +14,8 @@ class WalletEventsTests: XCTestCase {
let testEnvironment = WalletEventsFlowEnvironment(
scheduler: testScheduler.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer()
SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: .test
)
func testSynchronizerSubscription() throws {
@ -58,6 +59,7 @@ class WalletEventsTests: XCTestCase {
let walletEvents: [WalletEvent] = mocked.map {
let transaction = TransactionState.placeholder(
amount: $0.amount,
fee: Zatoshi(amount: 10),
shielded: $0.shielded,
status: $0.status,
subtitle: $0.subtitle,
@ -71,13 +73,13 @@ class WalletEventsTests: XCTestCase {
)
}
let identifiedTransactions = IdentifiedArrayOf(uniqueElements: walletEvents)
let identifiedWalletEvents = IdentifiedArrayOf(uniqueElements: walletEvents)
let store = TestStore(
initialState: WalletEventsFlowState(
route: .latest,
isScrollable: true,
walletEvents: identifiedTransactions
walletEvents: identifiedWalletEvents
),
reducer: WalletEventsFlowReducer.default,
environment: testEnvironment
@ -88,7 +90,7 @@ class WalletEventsTests: XCTestCase {
Self.testScheduler.advance(by: 0.01)
store.receive(.updateWalletEvents(walletEvents)) { state in
let receivedTransactions = IdentifiedArrayOf(
let receivedWalletEvents = IdentifiedArrayOf(
uniqueElements:
walletEvents
.sorted(by: { lhs, rhs in
@ -96,7 +98,32 @@ class WalletEventsTests: XCTestCase {
})
)
state.walletEvents = receivedTransactions
state.walletEvents = receivedWalletEvents
}
}
func testCopyToPasteboard() throws {
let pasteboard = WrappedPasteboard.test
let testEnvironment = WalletEventsFlowEnvironment(
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
SDKSynchronizer: TestWrappedSDKSynchronizer(),
pasteboard: pasteboard
)
let store = TestStore(
initialState: WalletEventsFlowState(
route: .latest,
isScrollable: true,
walletEvents: []
),
reducer: WalletEventsFlowReducer.default,
environment: testEnvironment
)
let testText = "test text"
store.send(.copyToPastboard(testText))
XCTAssertEqual(pasteboard.getString(), testText, "WalletEvetns: `testCopyToPasteboard` is expected to match the input `\(testText)`")
}
}