Skip to content

Commit

Permalink
Add support for default values in single choice questions in survey
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gersonnoboa committed Oct 18, 2023
1 parent 6777e53 commit df37271
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,35 @@ 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)
}
}
validationError.isHidden = !props.showValidationError
}

static func handleSelection(
with props: Props,
option opt: Survey.Option<String>
) -> 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
Expand All @@ -109,6 +128,7 @@ extension Survey.SingleChoiceQuestionView {
var showValidationError: Bool
var options: [Survey.Option<String>]
var selected: Survey.Option<String>?
var defaultOption: Survey.Option<String>?
var answerContainer: CoreSdkClient.SurveyAnswerContainer?
let accessibility: Accessibility

Expand All @@ -124,6 +144,7 @@ extension Survey.SingleChoiceQuestionView {
showValidationError: Bool = false,
options: [Survey.Option<String>] = [],
selected: Survey.Option<String>? = nil,
defaultOption: Survey.Option<String>? = nil,
answerContainer: CoreSdkClient.SurveyAnswerContainer? = nil,
accessibility: Accessibility
) {
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -99,6 +116,7 @@ private extension Survey.ViewController.Props {

static func makeSinglePropsMock(
selectedOption: Survey.Option<String>? = nil,
defaultOption: Survey.Option<String>? = nil,
showValidationError: Bool = false
) -> Survey.SingleChoiceQuestionView.Props {
var props = Survey.SingleChoiceQuestionView.Props(
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,24 @@ extension Survey.ViewController.Props {
? Localization.Survey.Question.Required.Accessibility.label
: nil

let defaultOption: Survey.Option<String>? = {
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<String>) in
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>(
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)
}
}
9 changes: 9 additions & 0 deletions SnapshotTests/SurveyViewControllerVoiceOverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit df37271

Please sign in to comment.