Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,22 @@ final class AccountCreationFormViewModel: ObservableObject {
private let analytics: Analytics
private var subscriptions: Set<AnyCancellable> = []

init(stores: StoresManager = ServiceLocator.stores,
init(debounceDuration: Double = Constants.fieldDebounceDuration,
stores: StoresManager = ServiceLocator.stores,
analytics: Analytics = ServiceLocator.analytics) {
self.stores = stores
self.analytics = analytics

$email
.removeDuplicates()
.debounce(for: .seconds(Constants.fieldDebounceDuration), scheduler: DispatchQueue.main)
.debounce(for: .seconds(debounceDuration), scheduler: DispatchQueue.main)
.sink { [weak self] email in
self?.validateEmail(email)
}.store(in: &subscriptions)

$password
.removeDuplicates()
.debounce(for: .seconds(Constants.fieldDebounceDuration), scheduler: DispatchQueue.main)
.debounce(for: .seconds(debounceDuration), scheduler: DispatchQueue.main)
.sink { [weak self] password in
self?.validatePassword(password)
}.store(in: &subscriptions)
Expand Down
12 changes: 12 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@
026D4A24280461960090164F /* CollectOrderPaymentUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026D4A23280461960090164F /* CollectOrderPaymentUseCaseTests.swift */; };
0270F47624D005B00005210A /* ProductFormViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270F47524D005B00005210A /* ProductFormViewModelProtocol.swift */; };
0270F47824D006F60005210A /* ProductFormPresentationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270F47724D006F60005210A /* ProductFormPresentationStyle.swift */; };
027111422913B9FC00F5269A /* AccountCreationFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027111412913B9FC00F5269A /* AccountCreationFormViewModelTests.swift */; };
0271125D2887D4E900FCD13C /* LoggedOutAppSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0271125C2887D4E900FCD13C /* LoggedOutAppSettingsTests.swift */; };
0271139A24DD15D800574A07 /* ProductsTabProductViewModel+VariationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0271139924DD15D800574A07 /* ProductsTabProductViewModel+VariationTests.swift */; };
0271E1642509C66200633F7A /* DefaultProductFormTableViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0271E1632509C66200633F7A /* DefaultProductFormTableViewModelTests.swift */; };
Expand Down Expand Up @@ -2189,6 +2190,7 @@
0270C0A827069BEF00FC799F /* Experiments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Experiments.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0270F47524D005B00005210A /* ProductFormViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormViewModelProtocol.swift; sourceTree = "<group>"; };
0270F47724D006F60005210A /* ProductFormPresentationStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormPresentationStyle.swift; sourceTree = "<group>"; };
027111412913B9FC00F5269A /* AccountCreationFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCreationFormViewModelTests.swift; sourceTree = "<group>"; };
0271125C2887D4E900FCD13C /* LoggedOutAppSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedOutAppSettingsTests.swift; sourceTree = "<group>"; };
0271139924DD15D800574A07 /* ProductsTabProductViewModel+VariationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductsTabProductViewModel+VariationTests.swift"; sourceTree = "<group>"; };
0271E1632509C66200633F7A /* DefaultProductFormTableViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultProductFormTableViewModelTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4486,6 +4488,14 @@
path = Variations;
sourceTree = "<group>";
};
027111402913B9D400F5269A /* Authentication */ = {
isa = PBXGroup;
children = (
027111412913B9FC00F5269A /* AccountCreationFormViewModelTests.swift */,
);
path = Authentication;
sourceTree = "<group>";
};
02759B8F28FFA06F00918176 /* Store Creation */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -6094,6 +6104,7 @@
5791FB4024EC833200117FD6 /* ViewModels */ = {
isa = PBXGroup;
children = (
027111402913B9D400F5269A /* Authentication */,
D41C9F2F26D9A41F00993558 /* WhatsNew */,
D8025469265517F9001B2CC1 /* CardPresentPayments */,
0371C36F2876ED3A00277E2C /* Feature Announcement Cards */,
Expand Down Expand Up @@ -11031,6 +11042,7 @@
4524CDA1242D045C00B2F20A /* ProductStatusSettingListSelectorCommandTests.swift in Sources */,
02077F72253816FF005A78EF /* ProductFormActionsFactory+ReadonlyProductTests.swift in Sources */,
D8C11A6222E24C4A00D4A88D /* LedgerTableViewCellTests.swift in Sources */,
027111422913B9FC00F5269A /* AccountCreationFormViewModelTests.swift in Sources */,
DE50295328BF4A8A00551736 /* JetpackConnectionWebViewModelTests.swift in Sources */,
AEA622B727468790002A9B57 /* AddOrderCoordinatorTests.swift in Sources */,
D802549126552FE1001B2CC1 /* CardPresentModalScanningForReaderTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import XCTest
import Yosemite
@testable import WooCommerce

final class AccountCreationFormViewModelTests: XCTestCase {
private var stores: MockStoresManager!
private var analyticsProvider: MockAnalyticsProvider!
private var analytics: WooAnalytics!
private var viewModel: AccountCreationFormViewModel!

override func setUp() {
super.setUp()
stores = MockStoresManager(sessionManager: SessionManager.makeForTesting())
analyticsProvider = MockAnalyticsProvider()
analytics = WooAnalytics(analyticsProvider: analyticsProvider)
viewModel = .init(debounceDuration: 0, stores: stores, analytics: analytics)
}

override func tearDown() {
viewModel = nil
analytics = nil
analyticsProvider = nil
stores = nil
super.tearDown()
}

// MARK: - `isEmailValid`

func test_isEmailValid_is_false_after_entering_invalid_email() {
// When
viewModel.email = "notanemail@woocom"

// Then
waitUntil {
self.viewModel.isEmailValid == false
}
}

func test_isEmailValid_is_true_after_entering_valid_email() {
// When
viewModel.email = "[email protected]"

// Then
waitUntil {
self.viewModel.isEmailValid == true
}
}

// MARK: - `isPasswordValid`

func test_isPasswordValid_is_false_after_entering_password_less_than_minimum_length() {
// When
viewModel.password = "minim"

// Then
waitUntil {
self.viewModel.isPasswordValid == false
}
}

func test_isPasswordValid_is_true_after_entering_password_of_minimum_length() {
// When
viewModel.password = "minimu"

// Then
waitUntil {
self.viewModel.isPasswordValid == true
}
}

// MARK: - `createAccount`

func test_createAccount_success_sets_state_to_authenticated() async {
// Given
mockAccountCreationSuccess(result: .init(authToken: "token", username: "username"))
XCTAssertFalse(stores.isAuthenticated)

// When
let result = await viewModel.createAccount()

// Then
XCTAssertTrue(result.isSuccess)
XCTAssertTrue(stores.isAuthenticated)
}

func test_createAccount_password_failure_sets_passwordErrorMessage() async {
// Given
mockAccountCreationFailure(error: .invalidPassword(message: "too complex to guess"))
XCTAssertFalse(stores.isAuthenticated)
XCTAssertNil(viewModel.passwordErrorMessage)

// When
let result = await viewModel.createAccount()

// Then
XCTAssertTrue(result.isFailure)
XCTAssertFalse(stores.isAuthenticated)
XCTAssertEqual(viewModel.passwordErrorMessage, "too complex to guess")
}

func test_createAccount_invalidEmail_failure_sets_emailErrorMessage() async {
// Given
mockAccountCreationFailure(error: .invalidEmail)
XCTAssertNil(viewModel.emailErrorMessage)

// When
let result = await viewModel.createAccount()

// Then
XCTAssertTrue(result.isFailure)
XCTAssertNotNil(viewModel.emailErrorMessage)
}

func test_passwordErrorMessage_is_cleared_after_changing_password_input() async {
// Given
mockAccountCreationFailure(error: .invalidPassword(message: "too complex to guess"))

// When
let _ = await viewModel.createAccount()
viewModel.password = "simple password"

// Then
waitUntil {
self.viewModel.passwordErrorMessage == nil
}
}

func test_emailErrorMessage_is_cleared_after_changing_email_input() async {
// Given
mockAccountCreationFailure(error: .emailExists)

// When
let _ = await viewModel.createAccount()
viewModel.email = "[email protected]"

// Then
waitUntil {
self.viewModel.emailErrorMessage == nil
}
}

// MARK: - analytics

func test_createAccount_success_tracks_expected_events() async {
// Given
mockAccountCreationSuccess(result: .init(authToken: "", username: ""))

// When
let _ = await viewModel.createAccount()

// Then
XCTAssertEqual(analyticsProvider.receivedEvents, ["signup_submitted", "signup_success"])
}

func test_createAccount_failure_tracks_expected_events() async {
// Given
mockAccountCreationFailure(error: .emailExists)

// When
let _ = await viewModel.createAccount()

// Then
XCTAssertEqual(analyticsProvider.receivedEvents, ["signup_submitted", "signup_failed"])
}
}

private extension AccountCreationFormViewModelTests {
func mockAccountCreationSuccess(result: CreateAccountResult) {
stores.whenReceivingAction(ofType: AccountCreationAction.self) { action in
switch action {
case let .createAccount(_, _, completion):
completion(.success(result))
}
}

stores.whenReceivingAction(ofType: AccountAction.self) { action in
switch action {
case let .synchronizeAccount(completion):
completion(.success(.fake()))
case let .synchronizeAccountSettings(_, completion):
completion(.success(.fake()))
case let .synchronizeSites(_, completion):
completion(.success(true))
default:
break
}
}
}

func mockAccountCreationFailure(error: CreateAccountError) {
stores.whenReceivingAction(ofType: AccountCreationAction.self) { action in
guard case let .createAccount(_, _, completion) = action else {
return
}
completion(.failure(error))
}
}
}