diff --git a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryButton.swift b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryButton.swift index 9c122fe1818..d7e5ede01e7 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryButton.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryButton.swift @@ -30,9 +30,13 @@ struct StoreCreationCountryButton_Previews: PreviewProvider { static var previews: some View { VStack { StoreCreationCountryButton(countryCode: .US, - viewModel: .init(storeName: "", onContinue: { _ in })) + viewModel: .init(storeName: "", + onContinue: { _ in }, + onSupport: {})) StoreCreationCountryButton(countryCode: .UM, - viewModel: .init(storeName: "", onContinue: { _ in })) + viewModel: .init(storeName: "", + onContinue: { _ in }, + onSupport: {})) } } } diff --git a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionView.swift b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionView.swift index 7bf465fd1fd..75db2240b64 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionView.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionView.swift @@ -58,7 +58,8 @@ struct StoreCreationCountryQuestionView_Previews: PreviewProvider { NavigationView { StoreCreationCountryQuestionView(viewModel: .init(storeName: "only in 2023", currentLocale: Locale.init(identifier: "en_US"), - onContinue: { _ in })) + onContinue: { _ in }, + onSupport: {})) } } } diff --git a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionViewModel.swift b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionViewModel.swift index 5a8f893b0d1..b30b942a8f6 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionViewModel.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountryQuestionViewModel.swift @@ -26,12 +26,15 @@ final class StoreCreationCountryQuestionViewModel: StoreCreationProfilerQuestion @Published private var isContinueButtonEnabledValue: Bool = false private let onContinue: (CountryCode) -> Void + private let onSupport: () -> Void init(storeName: String, currentLocale: Locale = .current, - onContinue: @escaping (CountryCode) -> Void) { + onContinue: @escaping (CountryCode) -> Void, + onSupport: @escaping () -> Void) { self.topHeader = storeName self.onContinue = onContinue + self.onSupport = onSupport currentCountryCode = currentLocale.regionCode.map { CountryCode(rawValue: $0) } ?? nil selectedCountryCode = currentCountryCode @@ -65,6 +68,10 @@ extension StoreCreationCountryQuestionViewModel: RequiredStoreCreationProfilerQu } onContinue(selectedCountryCode) } + + func supportButtonTapped() { + onSupport() + } } extension StoreCreationCountryQuestionViewModel { diff --git a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountrySectionView.swift b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountrySectionView.swift index 8358552c520..3ed7b6033f3 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountrySectionView.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/Profiler/Country/StoreCreationCountrySectionView.swift @@ -28,6 +28,6 @@ struct StoreCreationCountrySectionView: View { struct StoreCreationCountrySectionView_Previews: PreviewProvider { static var previews: some View { - StoreCreationCountrySectionView(header: "EXAMPLES", countryCodes: [.FJ, .UM, .US], viewModel: .init(storeName: "", onContinue: { _ in })) + StoreCreationCountrySectionView(header: "EXAMPLES", countryCodes: [.FJ, .UM, .US], viewModel: .init(storeName: "", onContinue: { _ in }, onSupport: {})) } } diff --git a/WooCommerce/Classes/Authentication/Store Creation/Profiler/RequiredStoreCreationProfilerQuestionView.swift b/WooCommerce/Classes/Authentication/Store Creation/Profiler/RequiredStoreCreationProfilerQuestionView.swift index 7eb9d9d01df..38a2a93db9b 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/Profiler/RequiredStoreCreationProfilerQuestionView.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/Profiler/RequiredStoreCreationProfilerQuestionView.swift @@ -7,6 +7,9 @@ protocol RequiredStoreCreationProfilerQuestionViewModel { /// Called when the continue button is tapped. func continueButtonTapped() async + /// Called when the Help & Support button is tapped. + func supportButtonTapped() + /// Whether the continue button is enabled for the user to continue. var isContinueButtonEnabled: AnyPublisher { get } } @@ -46,6 +49,13 @@ struct RequiredStoreCreationProfilerQuestionView: View { } .background(Color(.systemBackground)) } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + SupportButton { + viewModel.supportButtonTapped() + } + } + } // Disables large title to avoid a large gap below the navigation bar. .navigationBarTitleDisplayMode(.inline) .onReceive(viewModel.isContinueButtonEnabled) { isContinueButtonEnabled in @@ -75,6 +85,7 @@ private final class StoreCreationQuestionPreviewViewModel: StoreCreationProfiler $isContinueButtonEnabledValue.eraseToAnyPublisher() } func continueButtonTapped() async {} + func supportButtonTapped() {} } struct RequiredStoreCreationProfilerQuestionView_Previews: PreviewProvider { diff --git a/WooCommerce/Classes/Authentication/Store Creation/Store name/StoreNameForm.swift b/WooCommerce/Classes/Authentication/Store Creation/Store name/StoreNameForm.swift index 8a12231771d..23ba8b00615 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/Store name/StoreNameForm.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/Store name/StoreNameForm.swift @@ -2,18 +2,12 @@ import SwiftUI /// Hosting controller that wraps the `StoreNameForm`. final class StoreNameFormHostingController: UIHostingController { - private let onContinue: (String) -> Void - private let onClose: () -> Void - init(onContinue: @escaping (String) -> Void, - onClose: @escaping () -> Void) { - self.onContinue = onContinue - self.onClose = onClose - super.init(rootView: StoreNameForm()) - - rootView.onContinue = { [weak self] storeName in - self?.onContinue(storeName) - } + onClose: @escaping () -> Void, + onSupport: @escaping () -> Void) { + super.init(rootView: StoreNameForm(onContinue: onContinue, + onClose: onClose, + onSupport: onSupport)) } @available(*, unavailable) @@ -24,38 +18,15 @@ final class StoreNameFormHostingController: UIHostingController { override func viewDidLoad() { super.viewDidLoad() - configureNavigationBarAppearance() - } - - /// Shows a transparent navigation bar without a bottom border and with a close button to dismiss. - func configureNavigationBarAppearance() { - addCloseNavigationBarButton(title: Localization.cancelButtonTitle, - target: self, - action: #selector(closeButtonTapped)) - let appearance = UINavigationBarAppearance() - appearance.configureWithTransparentBackground() - appearance.backgroundColor = .systemBackground - - navigationItem.standardAppearance = appearance - navigationItem.scrollEdgeAppearance = appearance - navigationItem.compactAppearance = appearance - } - - @objc private func closeButtonTapped() { - onClose() - } -} - -private extension StoreNameFormHostingController { - enum Localization { - static let cancelButtonTitle = NSLocalizedString("Cancel", comment: "Navigation bar button on the store name form to leave the store creation flow.") + configureTransparentNavigationBar() } } /// Allows the user to enter a store name during the store creation flow. struct StoreNameForm: View { - /// Set in the hosting controller. - var onContinue: (String) -> Void = { _ in } + let onContinue: (String) -> Void + let onClose: () -> Void + let onSupport: () -> Void @State private var name: String = "" @@ -104,6 +75,19 @@ struct StoreNameForm: View { .buttonStyle(PrimaryButtonStyle()) .disabled(name.isEmpty) } + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(Localization.cancelButtonTitle) { + onClose() + } + .buttonStyle(TextButtonStyle()) + } + ToolbarItem(placement: .navigationBarTrailing) { + SupportButton { + onSupport() + } + } + } // Disables large title to avoid a large gap below the navigation bar. .navigationBarTitleDisplayMode(.inline) // Hides the back button and shows a close button in the hosting controller instead. @@ -148,11 +132,19 @@ private extension StoreNameForm { "Continue", comment: "Title of the button on the store creation store name form to continue." ) + static let cancelButtonTitle = NSLocalizedString( + "Cancel", + comment: "Navigation bar button on the store name form to leave the store creation flow." + ) } } struct StoreNameForm_Previews: PreviewProvider { static var previews: some View { - StoreNameForm() + NavigationView { + StoreNameForm(onContinue: { _ in }, + onClose: {}, + onSupport: {}) + } } } diff --git a/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift b/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift index 240fb674a43..aa215895c2f 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/StoreCreationCoordinator.swift @@ -148,6 +148,8 @@ private extension StoreCreationCoordinator { } } onClose: { [weak self] in self?.showDiscardChangesAlert(flow: .native) + } onSupport: { [weak self] in + self?.showSupport(from: navigationController) } navigationController.pushViewController(storeNameForm, animated: true) analytics.track(event: .StoreCreation.siteCreationStep(step: .storeName)) @@ -297,6 +299,10 @@ private extension StoreCreationCoordinator { // Presents the alert with the presented webview. navigationController.presentedViewController?.present(alert, animated: true) } + + func showSupport(from navigationController: UINavigationController) { + ZendeskProvider.shared.showNewRequestIfPossible(from: navigationController, with: "origin:store-creation") + } } // MARK: - Store creation M2 @@ -349,13 +355,15 @@ private extension StoreCreationCoordinator { planToPurchase: WPComPlanProduct) { let questionController = StoreCreationCountryQuestionHostingController(viewModel: .init(storeName: storeName) { [weak self] countryCode in - guard let self else { return } - self.showDomainSelector(from: navigationController, - storeName: storeName, - categoryName: categoryName, - countryCode: countryCode, - planToPurchase: planToPurchase) - }) + guard let self else { return } + self.showDomainSelector(from: navigationController, + storeName: storeName, + categoryName: categoryName, + countryCode: countryCode, + planToPurchase: planToPurchase) + } onSupport: { [weak self] in + self?.showSupport(from: navigationController) + }) navigationController.pushViewController(questionController, animated: true) // TODO: analytics } @@ -375,6 +383,8 @@ private extension StoreCreationCoordinator { countryCode: countryCode, domain: domain, planToPurchase: planToPurchase) + }, onSupport: { [weak self] in + self?.showSupport(from: navigationController) }) navigationController.pushViewController(domainSelector, animated: true) analytics.track(event: .StoreCreation.siteCreationStep(step: .domainPicker)) @@ -426,6 +436,8 @@ private extension StoreCreationCoordinator { planToPurchase: planToPurchase, siteID: result.siteID, siteSlug: result.siteSlug) + } onSupport: { [weak self] in + self?.showSupport(from: navigationController) } navigationController.pushViewController(storeSummary, animated: true) analytics.track(event: .StoreCreation.siteCreationStep(step: .storeSummary)) diff --git a/WooCommerce/Classes/Authentication/Store Creation/StoreCreationSummaryView.swift b/WooCommerce/Classes/Authentication/Store Creation/StoreCreationSummaryView.swift index 4785b82db69..5232de10983 100644 --- a/WooCommerce/Classes/Authentication/Store Creation/StoreCreationSummaryView.swift +++ b/WooCommerce/Classes/Authentication/Store Creation/StoreCreationSummaryView.swift @@ -2,16 +2,12 @@ import SwiftUI /// Hosting controller that wraps the `StoreCreationSummaryView`. final class StoreCreationSummaryHostingController: UIHostingController { - private let onContinueToPayment: () -> Void - init(viewModel: StoreCreationSummaryViewModel, - onContinueToPayment: @escaping () -> Void) { - self.onContinueToPayment = onContinueToPayment - super.init(rootView: StoreCreationSummaryView(viewModel: viewModel)) - - rootView.onContinueToPayment = { [weak self] in - self?.onContinueToPayment() - } + onContinueToPayment: @escaping () -> Void, + onSupport: @escaping () -> Void) { + super.init(rootView: StoreCreationSummaryView(viewModel: viewModel, + onContinueToPayment: onContinueToPayment, + onSupport: onSupport)) } @available(*, unavailable) @@ -52,13 +48,17 @@ struct StoreCreationSummaryViewModel { /// Displays a summary of the store creation flow with the store information (e.g. store name, store slug). struct StoreCreationSummaryView: View { - /// Set in the hosting controller. - var onContinueToPayment: (() -> Void) = {} + private let onContinueToPayment: () -> Void + private let onSupport: () -> Void private let viewModel: StoreCreationSummaryViewModel - init(viewModel: StoreCreationSummaryViewModel) { + init(viewModel: StoreCreationSummaryViewModel, + onContinueToPayment: @escaping () -> Void, + onSupport: @escaping () -> Void) { self.viewModel = viewModel + self.onContinueToPayment = onContinueToPayment + self.onSupport = onSupport } var body: some View { @@ -126,6 +126,13 @@ struct StoreCreationSummaryView: View { .padding(Layout.defaultButtonPadding) } } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + SupportButton { + onSupport() + } + } + } .navigationTitle(Localization.title) .navigationBarTitleDisplayMode(.large) } @@ -168,12 +175,16 @@ struct StoreCreationSummaryView_Previews: PreviewProvider { .init(storeName: "Fruity shop", storeSlug: "fruityshop.com", categoryName: "Arts and Crafts", - countryCode: .UG)) + countryCode: .UG), + onContinueToPayment: {}, + onSupport: {}) StoreCreationSummaryView(viewModel: .init(storeName: "Fruity shop", storeSlug: "fruityshop.com", categoryName: "Arts and Crafts", - countryCode: nil)) + countryCode: nil), + onContinueToPayment: {}, + onSupport: {}) .preferredColorScheme(.dark) } } diff --git a/WooCommerce/Classes/Authentication/Store Creation/SupportButton.swift b/WooCommerce/Classes/Authentication/Store Creation/SupportButton.swift new file mode 100644 index 00000000000..c68e5d97fcf --- /dev/null +++ b/WooCommerce/Classes/Authentication/Store Creation/SupportButton.swift @@ -0,0 +1,32 @@ +import SwiftUI + +/// A support button with a help icon that is currently shown in the navigation bar. +struct SupportButton: View { + let onTapped: () -> Void + + var body: some View { + Button { + onTapped() + } label: { + Image(uiImage: .helpOutlineImage) + .renderingMode(.template) + .linkStyle() + } + .accessibilityLabel(Localization.accessibilityLabel) + } +} + +private extension SupportButton { + enum Localization { + static let accessibilityLabel = NSLocalizedString( + "Help & Support", + comment: "Accessibility label for the Help & Support image navigation bar button in the store creation flow." + ) + } +} + +struct SupportButton_Previews: PreviewProvider { + static var previews: some View { + SupportButton(onTapped: {}) + } +} diff --git a/WooCommerce/Classes/Extensions/UIImage+Woo.swift b/WooCommerce/Classes/Extensions/UIImage+Woo.swift index 9a42b0d57e6..5fd0c606c82 100644 --- a/WooCommerce/Classes/Extensions/UIImage+Woo.swift +++ b/WooCommerce/Classes/Extensions/UIImage+Woo.swift @@ -361,6 +361,12 @@ extension UIImage { return UIImage.gridicon(.heartOutline) } + /// Help Outline + /// + static var helpOutlineImage: UIImage { + return UIImage.gridicon(.helpOutline) + } + /// House Image /// static var houseImage: UIImage { diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorView.swift index e799d5f367d..e8e8b722070 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorView.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/Domains/DomainSelectorView.swift @@ -7,14 +7,14 @@ final class DomainSelectorHostingController: UIHostingController Void) { + onDomainSelection: @escaping (String) async -> Void, + onSupport: @escaping () -> Void) { self.viewModel = viewModel - super.init(rootView: DomainSelectorView(viewModel: viewModel)) - - rootView.onDomainSelection = { domain in - await onDomainSelection(domain) - } + super.init(rootView: DomainSelectorView(viewModel: viewModel, + onDomainSelection: onDomainSelection, + onSupport: onSupport)) } required dynamic init?(coder aDecoder: NSCoder) { @@ -55,8 +55,8 @@ struct DomainSelectorView: View { case results(domains: [String]) } - /// Set in the hosting controller. - var onDomainSelection: ((String) async -> Void) = { _ in } + private let onDomainSelection: (String) async -> Void + private let onSupport: () -> Void /// View model to drive the view. @ObservedObject private var viewModel: DomainSelectorViewModel @@ -70,8 +70,12 @@ struct DomainSelectorView: View { @FocusState private var textFieldIsFocused: Bool - init(viewModel: DomainSelectorViewModel) { + init(viewModel: DomainSelectorViewModel, + onDomainSelection: @escaping (String) async -> Void, + onSupport: @escaping () -> Void) { self.viewModel = viewModel + self.onDomainSelection = onDomainSelection + self.onSupport = onSupport } var body: some View { @@ -177,6 +181,13 @@ struct DomainSelectorView: View { .background(Color(.systemBackground)) } } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + SupportButton { + onSupport() + } + } + } .navigationTitle(Localization.title) .navigationBarTitleDisplayMode(.large) .onChange(of: viewModel.isLoadingDomainSuggestions) { isLoadingDomainSuggestions in @@ -237,7 +248,9 @@ struct DomainSelectorView_Previews: PreviewProvider { // Empty query state. DomainSelectorView(viewModel: .init(initialSearchTerm: "", - stores: DomainSelectorViewStores(result: nil))) + stores: DomainSelectorViewStores(result: nil)), + onDomainSelection: { _ in }, + onSupport: {}) // Results state. DomainSelectorView(viewModel: .init(initialSearchTerm: "Fruit smoothie", @@ -248,18 +261,24 @@ struct DomainSelectorView_Previews: PreviewProvider { .init(name: "freesmoothieeee.com", isFree: true), .init(name: "greatfruitsmoothie1.com", isFree: true), .init(name: "tropicalsmoothie.com", isFree: true) - ])))) + ]))), + onDomainSelection: { _ in }, + onSupport: {}) // Error state. DomainSelectorView(viewModel: .init(initialSearchTerm: "test", stores: DomainSelectorViewStores(result: .failure( DotcomError.unknown(code: "invalid_query", message: "Domain searches must contain a word with the following characters.") - )))) + ))), + onDomainSelection: { _ in }, + onSupport: {}) // Loading state. DomainSelectorView(viewModel: .init(initialSearchTerm: "test", - stores: DomainSelectorViewStores(result: nil))) + stores: DomainSelectorViewStores(result: nil)), + onDomainSelection: { _ in }, + onSupport: {}) } } } diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 2630f232b71..859e20489d7 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -413,6 +413,7 @@ 02CEBB8024C9869E002EDF35 /* ProductFormActionsFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CEBB7F24C9869E002EDF35 /* ProductFormActionsFactoryProtocol.swift */; }; 02CEBB8224C98861002EDF35 /* ProductFormDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CEBB8124C98861002EDF35 /* ProductFormDataModel.swift */; }; 02CEBB8424C99A10002EDF35 /* Product+ShippingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CEBB8324C99A10002EDF35 /* Product+ShippingTests.swift */; }; + 02D3B68529657061009BF0BC /* SupportButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D3B68429657061009BF0BC /* SupportButton.swift */; }; 02D45647231CB1FB008CF0A9 /* UIImage+Dot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D45646231CB1FB008CF0A9 /* UIImage+Dot.swift */; }; 02DAE7FC291B7B8B009342B7 /* DomainSelectorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DAE7FB291B7B8B009342B7 /* DomainSelectorViewModel.swift */; }; 02DAE7FF291B8C8A009342B7 /* DomainSelectorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DAE7FE291B8C8A009342B7 /* DomainSelectorViewModelTests.swift */; }; @@ -2468,6 +2469,7 @@ 02CEBB7F24C9869E002EDF35 /* ProductFormActionsFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormActionsFactoryProtocol.swift; sourceTree = ""; }; 02CEBB8124C98861002EDF35 /* ProductFormDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormDataModel.swift; sourceTree = ""; }; 02CEBB8324C99A10002EDF35 /* Product+ShippingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Product+ShippingTests.swift"; sourceTree = ""; }; + 02D3B68429657061009BF0BC /* SupportButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButton.swift; sourceTree = ""; }; 02D45646231CB1FB008CF0A9 /* UIImage+Dot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Dot.swift"; sourceTree = ""; }; 02DAE7FB291B7B8B009342B7 /* DomainSelectorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSelectorViewModel.swift; sourceTree = ""; }; 02DAE7FE291B8C8A009342B7 /* DomainSelectorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainSelectorViewModelTests.swift; sourceTree = ""; }; @@ -4808,6 +4810,7 @@ 02E3B63029066858007E0F13 /* StoreCreationCoordinator.swift */, 02EAA4C7290F992B00918DAB /* LoggedOutStoreCreationCoordinator.swift */, 021940E7291FDBF90090354E /* StoreCreationSummaryView.swift */, + 02D3B68429657061009BF0BC /* SupportButton.swift */, ); path = "Store Creation"; sourceTree = ""; @@ -10337,6 +10340,7 @@ 451A04EC2386D2B300E368C9 /* ProductImagesCollectionViewDataSource.swift in Sources */, 02DD81F9242CAA400060E50B /* WordPressMediaLibraryImagePickerViewController.swift in Sources */, D8C2A28823190B2300F503E9 /* StorageProductReview+Woo.swift in Sources */, + 02D3B68529657061009BF0BC /* SupportButton.swift in Sources */, 2664210126F3E1BB001FC5B4 /* ModalHostingPresentationController.swift in Sources */, D8B4D5F026C2C7EC00F34E94 /* InPersonPaymentsStripeAccountPendingView.swift in Sources */, B92639FF293E2D4C00A257E0 /* JustInTimeMessagesProvider.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/Authentication/Store Creation/Profiler/StoreCreationCountryQuestionViewModelTests.swift b/WooCommerce/WooCommerceTests/Authentication/Store Creation/Profiler/StoreCreationCountryQuestionViewModelTests.swift index 2be781cb9b5..f5039d4eb57 100644 --- a/WooCommerce/WooCommerceTests/Authentication/Store Creation/Profiler/StoreCreationCountryQuestionViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/Authentication/Store Creation/Profiler/StoreCreationCountryQuestionViewModelTests.swift @@ -8,7 +8,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_topHeader_is_set_to_store_name() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store 🌟") { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store 🌟") { _ in } onSupport: {} // Then XCTAssertEqual(viewModel.topHeader, "store 🌟") @@ -16,7 +16,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_currentCountryCode_and_initial_selectedCountryCode_are_set_by_locale() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "fr_FR")) { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "fr_FR")) { _ in } onSupport: {} // Then XCTAssertEqual(viewModel.currentCountryCode, .FR) @@ -25,7 +25,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_currentCountryCode_and_initial_selectedCountryCode_are_nil_with_an_invalid_locale() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "zzzz")) { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "zzzz")) { _ in } onSupport: {} // Then XCTAssertNil(viewModel.currentCountryCode) @@ -34,7 +34,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_countryCodes_include_all_countries_with_an_invalid_locale() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "zzzz")) { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "zzzz")) { _ in } onSupport: {} // Then XCTAssertEqual(viewModel.countryCodes, SiteAddress.CountryCode.allCases.sorted(by: { $0.readableCountry < $1.readableCountry })) @@ -42,7 +42,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_countryCodes_do_not_include_currentCountryCode_from_locale() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "fr_FR")) { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "fr_FR")) { _ in } onSupport: {} // Then XCTAssertFalse(viewModel.countryCodes.contains(.FR)) @@ -51,7 +51,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_selecting_a_country_updates_selectedCountryCode() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store") { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store") { _ in } onSupport: {} // When viewModel.selectCountry(.FJ) @@ -62,7 +62,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_selecting_a_country_sets_isContinueButtonEnabled_to_true_with_an_invalid_locale() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "zzzz")) { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "zzzz")) { _ in } onSupport: {} var isContinueButtonEnabledValues = [Bool]() viewModel.isContinueButtonEnabled.removeDuplicates().sink { isEnabled in isContinueButtonEnabledValues.append(isEnabled) @@ -79,7 +79,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { func test_isContinueButtonEnabled_stays_true_with_a_valid_locale() throws { // Given - let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "fr_FR")) { _ in } + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", currentLocale: .init(identifier: "fr_FR")) { _ in } onSupport: {} var isContinueButtonEnabledValues = [Bool]() viewModel.isContinueButtonEnabled.removeDuplicates().sink { isEnabled in isContinueButtonEnabledValues.append(isEnabled) @@ -102,7 +102,7 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { // Then XCTAssertEqual(countryCode, .JP) promise(()) - } + } onSupport: {} // When viewModel.selectCountry(.JP) @@ -118,10 +118,24 @@ final class StoreCreationCountryQuestionViewModelTests: XCTestCase { currentLocale: .init(identifier: "zzzz")) { countryCode in // Then XCTFail("Should not be invoked without selecting a country.") - } + } onSupport: {} // When Task { @MainActor in await viewModel.continueButtonTapped() } } + + func test_supportButtonTapped_invokes_onSupport() throws { + waitFor { promise in + // Given + let viewModel = StoreCreationCountryQuestionViewModel(storeName: "store", + currentLocale: .init(identifier: "")) { _ in } onSupport: { + // Then + promise(()) + } + + // When + viewModel.supportButtonTapped() + } + } } diff --git a/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift b/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift index 2511eb78e11..32d954d896a 100644 --- a/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift +++ b/WooCommerce/WooCommerceTests/Extensions/IconsTests.swift @@ -204,6 +204,10 @@ final class IconsTests: XCTestCase { XCTAssertNotNil(UIImage.heartOutlineImage) } + func test_helpOutlineImage_icon_is_not_nil() { + XCTAssertNotNil(UIImage.helpOutlineImage) + } + func testHouseImageIconIsNotNil() { XCTAssertNotNil(UIImage.houseImage) }