Remove unused (moved) files

pull/4102/head
Hwee-Boon Yar 3 years ago
parent 15f2756265
commit 1ace5e3225
  1. 176
      AlphaWallet/Core/Views/SegmentedControl.swift
  2. 244
      AlphaWallet/EtherClient/OpenSea.swift
  3. 84
      AlphaWallet/Tokens/ViewControllers/Collectibles/ViewModels/TokensCardCollectionInfoPageViewModel.swift
  4. 29
      AlphaWallet/Tokens/ViewModels/SendViewSectionHeaderViewModel.swift
  5. 77
      AlphaWallet/Tokens/Views/SendViewSectionHeader.swift
  6. 33
      modules/AlphaWalletAddress/AlphaWalletAddressTests/AlphaWalletAddressTests.swift

@ -1,176 +0,0 @@
// Copyright © 2020 Stormbird PTE. LTD.
import UIKit
protocol SegmentedControlDelegate: AnyObject {
//Implementations of this protocol function will have to cast `segment` to the appropriate type. Maybe some generic or associated type magic can fix this, but alas time constraints
func didTapSegment(atSelection selection: SegmentedControl.Selection, inSegmentedControl segmentedControl: SegmentedControl)
}
extension SegmentedControl {
static func tokensSegmentControl(titles: [String]) -> SegmentedControl {
let isNarrowScreen = ScreenChecker().isNarrowScreen
let spacing: CGFloat = isNarrowScreen ? 30 : 40
let inset: CGFloat = isNarrowScreen ? 7 : 20
return .init(titles: titles, segmentConfiguration: .init(spacing: spacing, selectionIndicatorInsets: .init(top: 0, left: inset, bottom: 0, right: inset), selectionBarHeight: 3, barHeight: 1))
}
}
class SegmentedControl: UIView, ReusableTableHeaderViewType {
enum Alignment {
case left
case right
case center
}
enum Selection: Equatable {
case selected(UInt)
case unselected
}
private let buttons: [UIButton]
private let highlightedBar = UIView()
private var highlightBarHorizontalConstraints: [NSLayoutConstraint]?
private lazy var viewModel = SegmentedControlViewModel(selection: selection)
weak var delegate: SegmentedControlDelegate?
var selection: Selection = .selected(0) {
didSet {
if oldValue == selection { return }
viewModel.selection = selection
configureTitleButtons()
configureHighlightedBar()
}
}
struct SegmentConfiguration {
var spacing: CGFloat = 20
var selectionIndicatorInsets: UIEdgeInsets = .init(top: 0, left: 7, bottom: 0, right: 7)
var selectionBarHeight: CGFloat = 3
var barHeight: CGFloat = 1
}
private let segmentConfiguration: SegmentConfiguration
init(titles: [String], alignment: Alignment = .left, distribution: UIStackView.Distribution = .fill, segmentConfiguration: SegmentConfiguration = .init()) {
self.buttons = SegmentedControl.createButtons(fromTitles: titles)
self.segmentConfiguration = segmentConfiguration
super.init(frame: .zero)
backgroundColor = viewModel.backgroundColor
for each in buttons {
each.addTarget(self, action: #selector(segmentTapped), for: .touchUpInside)
}
let buttonsStackView = buttons.map { $0 as UIView }.asStackView(distribution: distribution, spacing: segmentConfiguration.spacing)
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(buttonsStackView)
let fullWidthBar = UIView()
fullWidthBar.translatesAutoresizingMaskIntoConstraints = false
fullWidthBar.backgroundColor = viewModel.unselectedBarColor
addSubview(fullWidthBar)
highlightedBar.translatesAutoresizingMaskIntoConstraints = false
fullWidthBar.addSubview(highlightedBar)
let barHeightConstraint = fullWidthBar.heightAnchor.constraint(equalToConstant: segmentConfiguration.barHeight)
barHeightConstraint.priority = .defaultHigh
var contraints: [NSLayoutConstraint] = []
switch alignment {
case .left:
let stackViewLeadingConstraint = buttonsStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 17)
stackViewLeadingConstraint.priority = .defaultHigh
let stackViewWidthConstraint = buttonsStackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, constant: -17)
stackViewWidthConstraint.priority = .defaultHigh
contraints = [stackViewLeadingConstraint, stackViewWidthConstraint]
case .center:
let stackViewCenterConstraint = buttonsStackView.centerXAnchor.constraint(equalTo: centerXAnchor)
stackViewCenterConstraint.priority = .defaultHigh
let stackViewWidthConstraint = buttonsStackView.widthAnchor.constraint(equalTo: widthAnchor, constant: -34)
stackViewWidthConstraint.priority = .defaultHigh
contraints = [stackViewCenterConstraint, stackViewWidthConstraint]
case .right:
let stackViewLeadingConstraint = buttonsStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -17)
stackViewLeadingConstraint.priority = .defaultHigh
let stackViewWidthConstraint = buttonsStackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, constant: -17)
stackViewWidthConstraint.priority = .defaultHigh
contraints = [stackViewLeadingConstraint, stackViewWidthConstraint]
}
NSLayoutConstraint.activate(contraints + [
buttonsStackView.topAnchor.constraint(equalTo: topAnchor),
buttonsStackView.bottomAnchor.constraint(equalTo: fullWidthBar.topAnchor),
fullWidthBar.leadingAnchor.constraint(equalTo: leadingAnchor),
fullWidthBar.trailingAnchor.constraint(equalTo: trailingAnchor),
barHeightConstraint,
fullWidthBar.bottomAnchor.constraint(equalTo: bottomAnchor),
highlightedBar.heightAnchor.constraint(equalToConstant: segmentConfiguration.selectionBarHeight),
highlightedBar.bottomAnchor.constraint(equalTo: fullWidthBar.bottomAnchor),
])
configureTitleButtons()
configureHighlightedBar()
}
required init?(coder aDecoder: NSCoder) {
return nil
}
private static func createButtons(fromTitles titles: [String]) -> [UIButton] {
return titles.map {
let button = UIButton(type: .system)
button.setTitle($0, for: .normal)
return button
}
}
@objc private func segmentTapped(_ source: UIButton) {
guard let segment = buttons.firstIndex(of: source).flatMap({ UInt($0) }) else { return }
delegate?.didTapSegment(atSelection: .selected(segment), inSegmentedControl: self)
}
func configureTitleButtons() {
for (index, each) in buttons.enumerated() {
//This is safe only because index can't possibly be negative
let index = UInt(index)
each.setTitleColor(viewModel.titleColor(forSelection: .selected(index)), for: .normal)
each.titleLabel?.font = viewModel.titleFont(forSelection: .selected(index))
}
}
func configureHighlightedBar() {
switch selection {
case .selected(let index):
highlightedBar.backgroundColor = viewModel.selectedBarColor
let index = Int(index)
let button: UIButton = buttons[index]
if let previousConstraints = highlightBarHorizontalConstraints {
NSLayoutConstraint.deactivate(previousConstraints)
}
highlightBarHorizontalConstraints = [
highlightedBar.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: -segmentConfiguration.selectionIndicatorInsets.left),
highlightedBar.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: segmentConfiguration.selectionIndicatorInsets.right),
]
if let constraints = highlightBarHorizontalConstraints {
NSLayoutConstraint.activate(constraints)
}
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 10, options: .allowUserInteraction, animations: {
self.layoutIfNeeded()
})
case .unselected:
highlightedBar.backgroundColor = nil
}
}
}

@ -1,244 +0,0 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import Alamofire
import BigInt
import PromiseKit
import Result
import SwiftyJSON
class OpenSea {
typealias PromiseResult = Promise<[AlphaWallet.Address: [OpenSeaNonFungible]]>
//Assuming 1 token (token ID, rather than a token) is 4kb, 1500 HyperDragons is 6MB. So we rate limit requests
private static let numberOfTokenIdsBeforeRateLimitingRequests = 25
private static let minimumSecondsBetweenRequests = TimeInterval(60)
private static let dateFormatter: DateFormatter = {
//Expect date string from asset_contract/created_date, etc as: "2020-05-27T16:53:32.834583"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
return dateFormatter
}()
private static var instances = [AddressAndRPCServer: WeakRef<OpenSea>]()
//NOTE: using AddressAndRPCServer fixes issue with incorrect tokens returned from makeFetchPromise
// the problem was that cached OpenSea returned tokens from multiple wallets
private let key: AddressAndRPCServer
private var recentWalletsWithManyTokens = [AlphaWallet.Address: (Date, PromiseResult)]()
private var fetch = OpenSea.makeEmptyFulfilledPromise()
private let queue = DispatchQueue.global(qos: .userInitiated)
private init(key: AddressAndRPCServer) {
self.key = key
}
static func createInstance(with key: AddressAndRPCServer) -> OpenSea {
if let instance = instances[key]?.object {
return instance
} else {
let instance = OpenSea(key: key)
instances[key] = WeakRef(object: instance)
return instance
}
}
private static func makeEmptyFulfilledPromise() -> PromiseResult {
return Promise {
$0.fulfill([:])
}
}
static func isServerSupported(_ server: RPCServer) -> Bool {
switch server {
case .main, .rinkeby:
return true
case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .custom, .goerli, .xDai, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum, .arbitrumRinkeby, .palm, .palmTestnet:
return false
}
}
static func resetInstances() {
for each in instances.values {
each.object?.reset()
}
}
///Call this after switching wallets, otherwise when the current promise is fulfilled, the switched to wallet will think the API results are for them
private func reset() {
fetch = OpenSea.makeEmptyFulfilledPromise()
}
///Uses a promise to make sure we don't fetch from OpenSea multiple times concurrently
func makeFetchPromise() -> PromiseResult {
guard OpenSea.isServerSupported(key.server) else {
fetch = .value([:])
return fetch
}
let owner = key.address
trimCachedPromises()
if let cachedPromise = cachedPromise(forOwner: owner) {
return cachedPromise
}
if fetch.isResolved {
fetch = Promise { seal in
let offset = 0
fetchPage(forOwner: owner, offset: offset) { result in
switch result {
case .success(let result):
seal.fulfill(result)
case .failure(let error):
verboseLog("[OpenSea] fetch failed: \(error) owner: \(owner.eip55String) offset: \(offset)")
seal.reject(error)
}
}
}
}
return fetch
}
private static func getBaseURLForOpensea(for server: RPCServer) -> String {
switch server {
case .main:
return Constants.openseaAPI
case .rinkeby:
return Constants.openseaRinkebyAPI
case .kovan, .ropsten, .poa, .sokol, .classic, .callisto, .xDai, .goerli, .artis_sigma1, .artis_tau1, .binance_smart_chain, .binance_smart_chain_testnet, .custom, .heco, .heco_testnet, .fantom, .fantom_testnet, .avalanche, .avalanche_testnet, .polygon, .mumbai_testnet, .optimistic, .optimisticKovan, .cronosTestnet, .arbitrum, .arbitrumRinkeby, .palm, .palmTestnet:
return Constants.openseaAPI
}
}
static func fetchAsset(for value: Eip155URL) -> Promise<URL> {
let baseURL = getBaseURLForOpensea(for: .main)
guard let url = URL(string: "\(baseURL)api/v1/asset/\(value.path)") else {
return .init(error: AnyError(OpenSeaError(localizedDescription: "Error calling \(baseURL) API isMainThread: \(Thread.isMainThread)")))
}
return Promise<URL> { seal in
Alamofire
.request(url, method: .get, headers: ["X-API-KEY": Constants.Credentials.openseaKey])
.responseJSON(queue: .main, options: .allowFragments, completionHandler: { response in
guard let data = response.data, let json = try? JSON(data: data) else {
return seal.reject(AnyError(OpenSeaError(localizedDescription: "Error calling \(baseURL) API: \(String(describing: response.error))")))
}
let image: String = json["image_url"].string ?? json["image_preview_url"].string ?? json["image_thumbnail_url"].string ?? json["image_original_url"].string ?? ""
guard let url = URL(string: image) else {
return seal.reject(AnyError(OpenSeaError(localizedDescription: "Error calling \(baseURL) API: \(String(describing: response.error))")))
}
seal.fulfill(url)
})
}
}
private func fetchPage(forOwner owner: AlphaWallet.Address, offset: Int, sum: [AlphaWallet.Address: [OpenSeaNonFungible]] = [:], completion: @escaping (ResultResult<[AlphaWallet.Address: [OpenSeaNonFungible]], AnyError>.t) -> Void) {
let baseURL = Self.getBaseURLForOpensea(for: key.server)
//Careful to `order_by` with a valid value otherwise OpenSea will return 0 results
guard let url = URL(string: "\(baseURL)api/v1/assets/?owner=\(owner.eip55String)&order_by=pk&order_direction=asc&limit=50&offset=\(offset)") else {
completion(.failure(AnyError(OpenSeaError(localizedDescription: "Error calling \(baseURL) API isMainThread: \(Thread.isMainThread)"))))
return
}
Alamofire.request(
url,
method: .get,
headers: ["X-API-KEY": Constants.Credentials.openseaKey]
).responseJSON(queue: queue, options: .allowFragments, completionHandler: { [weak self] response in
guard let strongSelf = self else { return }
guard let data = response.data, let json = try? JSON(data: data) else {
completion(.failure(AnyError(OpenSeaError(localizedDescription: "Error calling \(baseURL) API: \(String(describing: response.error))"))))
return
}
var results = sum
for (_, each): (String, JSON) in json["assets"] {
let type = each["asset_contract"]["schema_name"].stringValue
guard let tokenType = NonFungibleFromJsonTokenType(rawString: type) else { continue }
if !Features.isErc1155Enabled && tokenType == .erc1155 { continue }
let tokenId = each["token_id"].stringValue
let contractName = each["asset_contract"]["name"].stringValue
//So if it's null in OpenSea, we get a 0, as expected. And 0 works for ERC721 too
let decimals = each["decimals"].intValue
let value: BigInt
switch tokenType {
case .erc721:
value = 1
case .erc1155:
//OpenSea API doesn't include value for ERC1155, so we'll have to batch fetch it later for each contract before we update the database
value = 0
}
let symbol = each["asset_contract"]["symbol"].stringValue
let name = each["name"].stringValue
let description = each["description"].stringValue
let thumbnailUrl = each["image_thumbnail_url"].stringValue
//We'll get what seems to be the PNG version first, falling back to the sometimes PNG, but sometimes SVG version
var imageUrl = each["image_preview_url"].stringValue
if imageUrl.isEmpty {
imageUrl = each["image_url"].stringValue
}
let contractImageUrl = each["asset_contract"]["image_url"].stringValue
let externalLink = each["external_link"].stringValue
let backgroundColor = each["background_color"].stringValue
var traits = [OpenSeaNonFungibleTrait]()
for each in each["traits"].arrayValue {
let traitCount = each["trait_count"].intValue
let traitType = each["trait_type"].stringValue
let traitValue = each["value"].stringValue
let trait = OpenSeaNonFungibleTrait(count: traitCount, type: traitType, value: traitValue)
traits.append(trait)
}
if let contract = AlphaWallet.Address(string: each["asset_contract"]["address"].stringValue) {
let collectionCreatedDate = each["asset_contract"]["created_date"].string.flatMap { OpenSea.dateFormatter.date(from: $0) }
let collectionDescription = each["asset_contract"]["description"].string
let cat = OpenSeaNonFungible(tokenId: tokenId, tokenType: tokenType, value: value, contractName: contractName, decimals: decimals, symbol: symbol, name: name, description: description, thumbnailUrl: thumbnailUrl, imageUrl: imageUrl, contractImageUrl: contractImageUrl, externalLink: externalLink, backgroundColor: backgroundColor, traits: traits, collectionCreatedDate: collectionCreatedDate, collectionDescription: collectionDescription)
if var list = results[contract] {
list.append(cat)
results[contract] = list
} else {
let list = [cat]
results[contract] = list
}
}
}
let fetchedCount = json["assets"].count
verboseLog("[OpenSea] fetch page count: \(fetchedCount) owner: \(owner.eip55String) offset: \(offset)")
if fetchedCount > 0 {
strongSelf.fetchPage(forOwner: owner, offset: offset + fetchedCount, sum: results) { results in
completion(results)
}
} else {
//Ignore UEFA from OpenSea, otherwise the token type would be saved wrongly as `.erc721` instead of `.erc721ForTickets`
let excludingUefa = sum.filter { !$0.key.isUEFATicketContract }
var tokenIdCount = 0
for (_, tokenIds) in excludingUefa {
tokenIdCount += tokenIds.count
}
strongSelf.cachePromise(withTokenIdCount: tokenIdCount, forOwner: owner)
completion(.success(excludingUefa))
}
})
}
private func cachePromise(withTokenIdCount tokenIdCount: Int, forOwner wallet: AlphaWallet.Address) {
guard tokenIdCount >= OpenSea.numberOfTokenIdsBeforeRateLimitingRequests else { return }
recentWalletsWithManyTokens[wallet] = (Date(), fetch)
}
private func cachedPromise(forOwner wallet: AlphaWallet.Address) -> PromiseResult? {
guard let (_, promise) = recentWalletsWithManyTokens[wallet] else { return nil }
return promise
}
private func trimCachedPromises() {
let cachedWallets = recentWalletsWithManyTokens.keys
let now = Date()
for each in cachedWallets {
guard let (date, _) = recentWalletsWithManyTokens[each] else { continue }
if now.timeIntervalSince(date) >= OpenSea.minimumSecondsBetweenRequests {
recentWalletsWithManyTokens.removeValue(forKey: each)
}
}
}
}

@ -1,84 +0,0 @@
//
// TokensCardCollectionInfoPageViewModel.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 07.09.2021.
//
import UIKit
enum TokensCardCollectionInfoPageViewConfiguration {
case field(viewModel: TokenInstanceAttributeViewModel)
case header(viewModel: TokenInfoHeaderViewModel)
}
struct TokensCardCollectionInfoPageViewModel {
var tabTitle: String {
return R.string.localizable.tokenTabInfo()
}
private let tokenObject: TokenObject
let server: RPCServer
var contractAddress: AlphaWallet.Address {
tokenObject.contractAddress
}
let tokenHolders: [TokenHolder]
var configurations: [TokensCardCollectionInfoPageViewConfiguration] = []
var image: URL? {
return tokenHolders.first.flatMap { $0.values.imageUrlUrlValue ?? $0.values.thumbnailUrlUrlValue }
}
var tokenImagePlaceholder: UIImage? {
return R.image.tokenPlaceholderLarge()
}
init(server: RPCServer, token: TokenObject, assetDefinitionStore: AssetDefinitionStore, eventsDataStore: EventsDataStoreProtocol, forWallet wallet: Wallet) {
self.server = server
self.tokenObject = token
tokenHolders = TokenAdaptor(token: token, assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore).getTokenHolders(forWallet: wallet)
configurations = generateConfigurations(token, tokenHolders: tokenHolders)
}
var createdDateViewModel: TokenInstanceAttributeViewModel {
let string: String? = tokenHolders.first?.values.collectionCreatedDateGeneralisedTimeValue?.formatAsShortDateString()
let attributedString: NSAttributedString? = string.flatMap { TokenInstanceAttributeViewModel.defaultValueAttributedString($0) }
return .init(title: R.string.localizable.semifungiblesCreatedDate(), attributedValue: attributedString)
}
var descriptionViewModel: TokenInstanceAttributeViewModel {
let string: String? = tokenHolders.first?.values.collectionDescriptionStringValue
let attributedString: NSAttributedString? = string.flatMap { TokenInstanceAttributeViewModel.defaultValueAttributedString($0, alignment: .left) }
return .init(title: nil, attributedValue: attributedString, isSeparatorHidden: true)
}
var backgroundColor: UIColor {
return Screen.TokenCard.Color.background
}
var iconImage: Subscribable<TokenImage> {
tokenObject.icon
}
var blockChainTagViewModel: BlockchainTagLabelViewModel {
.init(server: server)
}
func generateConfigurations(_ tokenObject: TokenObject, tokenHolders: [TokenHolder]) -> [TokensCardCollectionInfoPageViewConfiguration] {
var configurations: [TokensCardCollectionInfoPageViewConfiguration] = []
configurations = [
.header(viewModel: .init(title: R.string.localizable.semifungiblesDetails())),
.field(viewModel: createdDateViewModel),
]
configurations += [
.header(viewModel: .init(title: R.string.localizable.semifungiblesDescription())),
.field(viewModel: descriptionViewModel),
]
return configurations
}
}

@ -1,29 +0,0 @@
//
// SendViewSectionHeaderViewModel.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 01.06.2020.
//
import UIKit
struct SendViewSectionHeaderViewModel {
let text: String
var showTopSeparatorLine: Bool = true
var font: UIFont {
return Fonts.semibold(size: 15)!
}
var textColor: UIColor {
return R.color.dove()!
}
var backgroundColor: UIColor {
return R.color.alabaster()!
}
var separatorBackgroundColor: UIColor {
return R.color.mike()!
}
}

@ -1,77 +0,0 @@
//
// SendViewSectionHeader.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 01.06.2020.
//
import UIKit
class SendViewSectionHeader: UIView {
private let textLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.setContentHuggingPriority(.required, for: .vertical)
label.setContentCompressionResistancePriority(.required, for: .vertical)
return label
}()
private let topSeparatorView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let bottomSeparatorView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private var topSeparatorLineHeight: NSLayoutConstraint!
init() {
super.init(frame: .zero)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
translatesAutoresizingMaskIntoConstraints = false
addSubview(topSeparatorView)
addSubview(textLabel)
addSubview(bottomSeparatorView)
NSLayoutConstraint.activate([
textLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
textLabel.trailingAnchor.constraint(greaterThanOrEqualTo: trailingAnchor, constant: -16),
textLabel.topAnchor.constraint(equalTo: topAnchor, constant: 13),
textLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -13),
topSeparatorView.topAnchor.constraint(equalTo: topAnchor),
topSeparatorView.widthAnchor.constraint(equalTo: widthAnchor),
bottomSeparatorView.topAnchor.constraint(equalTo: bottomAnchor),
bottomSeparatorView.widthAnchor.constraint(equalTo: widthAnchor),
bottomSeparatorView.heightAnchor.constraint(equalToConstant: 1)
])
topSeparatorLineHeight = topSeparatorView.heightAnchor.constraint(equalToConstant: 1)
topSeparatorLineHeight.isActive = true
}
func configure(viewModel: SendViewSectionHeaderViewModel) {
textLabel.text = viewModel.text
textLabel.textColor = viewModel.textColor
textLabel.font = viewModel.font
backgroundColor = viewModel.backgroundColor
topSeparatorView.backgroundColor = viewModel.separatorBackgroundColor
bottomSeparatorView.backgroundColor = viewModel.separatorBackgroundColor
topSeparatorLineHeight.constant = viewModel.showTopSeparatorLine ? 1 : 0
}
}

@ -1,33 +0,0 @@
//
// AlphaWalletAddressTests.swift
// AlphaWalletAddressTests
//
// Created by Vladyslav Shepitko on 13.10.2021.
//
import XCTest
@testable import AlphaWalletAddress
class AlphaWalletAddressTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
Loading…
Cancel
Save