From df372715d98e54ee21077f2209ff65d448d4c0cd Mon Sep 17 00:00:00 2001 From: Gerson Noboa Date: Fri, 13 Oct 2023 13:24:35 +0300 Subject: [PATCH] Add support for default values in single choice questions in survey Default values weren't selected automatically in surveys that showed a single option. With this change, a new `defaultOption` is added to the survey and used in case the selected value is not there. Now this works as it should, and how it works in Android. The reason why a default option was added and the default value is not just set as the selected one, is because the question view starts with props, and the prop is either selected or not. However, the question view wouldn't know if an option is selected because of user input or because of the default value. If the view is selected because of user input, then `opt.select(opt)` needs to be executed only on user interaction, but in case of default value, it needs to be run automatically as soon as the view is presented. I don't know how to make this distinction without having the default value separately. Also, a snapshot test was added to ensure that when there's a default value, it's selected correctly. MOB-2747 --- .../Survey.SingleChoiceQuestionView.swift | 24 ++++- .../Survey.ViewController.Props.Mock.swift | 19 ++++ .../Survey/Survey.ViewController.Props.swift | 14 +++ ...Survey.SingleChouceQuestionViewTests.swift | 92 +++++++++++++++++++ .../SurveyViewControllerVoiceOverTests.swift | 9 ++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 GliaWidgetsTests/Sources/Survey.SingleChouceQuestionViewTests.swift diff --git a/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift b/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift index 79225afce..aac938310 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Components/SingleChoiceQuestionView/Survey.SingleChoiceQuestionView.swift @@ -85,9 +85,12 @@ extension Survey { zip(props.options, optionsStack.arrangedSubviews) .forEach { opt, view in guard let checkboxView = view as? CheckboxView else { return } + + let state = Self.handleSelection(with: props, option: opt) + checkboxView.props = .init( title: opt.name, - state: props.selected == opt ? .selected : .active + state: state ) { opt.select(opt) } @@ -95,6 +98,22 @@ extension Survey { validationError.isHidden = !props.showValidationError } + static func handleSelection( + with props: Props, + option opt: Survey.Option + ) -> CheckboxView.State { + // If user selected or it should be selected by default. + let isSelected = props.selected == opt || props.defaultOption == opt + + // Trigger selection manually because the option has + // been selected by default, not because of user input. + if isSelected, props.selected == nil { + opt.select(opt) + } + + return isSelected ? .selected : .active + } + // MARK: - Private private let style: Theme.SurveyStyle.SingleQuestion @@ -109,6 +128,7 @@ extension Survey.SingleChoiceQuestionView { var showValidationError: Bool var options: [Survey.Option] var selected: Survey.Option? + var defaultOption: Survey.Option? var answerContainer: CoreSdkClient.SurveyAnswerContainer? let accessibility: Accessibility @@ -124,6 +144,7 @@ extension Survey.SingleChoiceQuestionView { showValidationError: Bool = false, options: [Survey.Option] = [], selected: Survey.Option? = nil, + defaultOption: Survey.Option? = nil, answerContainer: CoreSdkClient.SurveyAnswerContainer? = nil, accessibility: Accessibility ) { @@ -133,6 +154,7 @@ extension Survey.SingleChoiceQuestionView { self.showValidationError = showValidationError self.options = options self.selected = selected + self.defaultOption = defaultOption self.answerContainer = answerContainer self.accessibility = accessibility } diff --git a/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift b/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift index f28070a4c..e63ffbb74 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Mocks/Survey.ViewController.Props.Mock.swift @@ -16,6 +16,23 @@ extension Survey.ViewController.Props { ) } + static func emptyPropsMockWithDefaultValue() -> Survey.ViewController.Props { + return Survey.ViewController.Props( + header: "Survey title", + props: [ + makeScalePropsMock(), + makeInputPropsMock(), + makeBooleanPropsMock(), + makeSinglePropsMock(defaultOption: .init( + name: "Second option", + value: "\(2)" + )) + ], + submit: { _ in }, + cancel: {} + ) + } + static func filledPropsMock() -> Survey.ViewController.Props { return Survey.ViewController.Props( header: "Survey title", @@ -99,6 +116,7 @@ private extension Survey.ViewController.Props { static func makeSinglePropsMock( selectedOption: Survey.Option? = nil, + defaultOption: Survey.Option? = nil, showValidationError: Bool = false ) -> Survey.SingleChoiceQuestionView.Props { var props = Survey.SingleChoiceQuestionView.Props( @@ -113,6 +131,7 @@ private extension Survey.ViewController.Props { .init(name: "Second option", value: "\(2)"), .init(name: "Third option", value: "\(3)") ].compactMap { $0 } + props.defaultOption = defaultOption props.selected = selectedOption return props } diff --git a/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift b/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift index eeb986ba3..0b75dacf0 100644 --- a/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift +++ b/GliaWidgets/Sources/ViewController/Survey/Survey.ViewController.Props.swift @@ -203,10 +203,24 @@ extension Survey.ViewController.Props { ? Localization.Survey.Question.Required.Accessibility.label : nil + let defaultOption: Survey.Option? = { + let defaultOption = sdkQuestion.options?.first { + $0.isDefault == true + } + + guard let defaultOption else { return nil } + + return .init( + name: defaultOption.label, + value: defaultOption.id.rawValue + ) + }() + var scaleProps = Survey.SingleChoiceQuestionView.Props( id: sdkQuestion.id.rawValue, title: sdkQuestion.text, isRequired: sdkQuestion.required, + defaultOption: defaultOption, accessibility: .init(value: accessibilityValue) ) let handleSingleOptionSelection = { (option: Survey.Option) in diff --git a/GliaWidgetsTests/Sources/Survey.SingleChouceQuestionViewTests.swift b/GliaWidgetsTests/Sources/Survey.SingleChouceQuestionViewTests.swift new file mode 100644 index 000000000..3a277f00b --- /dev/null +++ b/GliaWidgetsTests/Sources/Survey.SingleChouceQuestionViewTests.swift @@ -0,0 +1,92 @@ +import Foundation +import XCTest +@testable import GliaWidgets + +final class SurveySingleChoiceQuestionViewTests: XCTestCase { + static let firstOption = Survey.Option(name: "Option 1", value: "1") + static let secondOption = Survey.Option(name: "Option 2", value: "2") + + var props: Survey.SingleChoiceQuestionView.Props = { + var props = Survey.SingleChoiceQuestionView.Props( + id: "1", + title: "Survey", + isRequired: true, + accessibility: .init(value: "") + ) + props.options = [ + firstOption, + secondOption + ] + return props + }() + + func test_regularOptionIsActive() { + let regularOption = Self.firstOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: regularOption + ) + + XCTAssertEqual(selection, .active) + } + + func test_defaultOptionIsSelected() { + var hasSelectedDefaultOption = false + + let defaultOption = Survey.Option( + name: "Option 1", + value: "1", + select: { _ in + hasSelectedDefaultOption = true + } + ) + + props.defaultOption = defaultOption + props.options = [defaultOption, Self.secondOption] + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: defaultOption + ) + + XCTAssertEqual(selection, .selected) + XCTAssertEqual(hasSelectedDefaultOption, true) + } + + func test_regularOptionIsActiveWhenDefaultIsPresent() { + let defaultOption = Self.firstOption + props.defaultOption = defaultOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: Self.secondOption + ) + + XCTAssertEqual(selection, .active) + } + + func test_selectedOptionIsSelected() { + let selectedOption = Self.firstOption + props.selected = selectedOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: selectedOption + ) + + XCTAssertEqual(selection, .selected) + } + + func test_regularOptionIsActiveWhenSelectedIsPresent() { + let selectedOption = Self.firstOption + props.selected = selectedOption + + let selection = Survey.SingleChoiceQuestionView.handleSelection( + with: props, + option: Self.secondOption + ) + + XCTAssertEqual(selection, .active) + } +} diff --git a/SnapshotTests/SurveyViewControllerVoiceOverTests.swift b/SnapshotTests/SurveyViewControllerVoiceOverTests.swift index 1a41f6403..c82f891e7 100644 --- a/SnapshotTests/SurveyViewControllerVoiceOverTests.swift +++ b/SnapshotTests/SurveyViewControllerVoiceOverTests.swift @@ -13,6 +13,15 @@ final class SurveyViewControllerVoiceOverTests: SnapshotTestCase { viewController.assertSnapshot(as: .accessibilityImage) } + func test_emptySurveyWithDefaultValue() { + let viewController = Survey.ViewController( + viewFactory: .mock(), + environment: .init(notificationCenter: .mock), + props: .emptyPropsMockWithDefaultValue() + ) + viewController.assertSnapshot(as: .accessibilityImage) + } + func test_filledSurvey() { let viewController = Survey.ViewController( viewFactory: .mock(),