-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MBL-1814] PLOT plan selector component #2195
Changes from 12 commits
fe44ad0
62e2ae0
b973413
c207ab4
1d3a34c
f6aa2e7
bfb8657
318a39d
27a3345
152587c
1343f23
4ca06c3
bfeb43f
28ddf83
5225a76
16830c7
fc6ac56
949dc63
1030358
3d22914
54a8669
1259d5f
a01864e
54109ce
a197ee6
64e6789
44073f0
56c0173
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import Library | ||
import UIKit | ||
|
||
protocol PledgePaymentPlansViewControllerDelegate: AnyObject { | ||
func pledgePaymentPlansViewController( | ||
_ viewController: PledgePaymentPlansViewController, | ||
didSelectPaymentPlan paymentPlan: PledgePaymentPlansType | ||
) | ||
} | ||
|
||
final class PledgePaymentPlansViewController: UIViewController { | ||
// MARK: Properties | ||
|
||
private let dataSource = PledgePaymentPlansDataSource() | ||
|
||
private lazy var tableView: UITableView = { ContentSizeTableView(frame: .zero, style: .plain) }() | ||
|
||
internal weak var delegate: PledgePaymentPlansViewControllerDelegate? | ||
|
||
private let viewModel: PledgePaymentPlansViewModelType = PledgePaymentPlansViewModel() | ||
|
||
// MARK: Lifecycle | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
|
||
self.configureSubviews() | ||
self.setupConstraints() | ||
|
||
self.viewModel.inputs.viewDidLoad() | ||
} | ||
|
||
private func configureSubviews() { | ||
self.tableView.dataSource = self.dataSource | ||
self.tableView.delegate = self | ||
|
||
self.view.addSubview(self.tableView) | ||
|
||
self.tableView.registerCellClass(PledgePaymentPlanCell.self) | ||
} | ||
|
||
private func setupConstraints() { | ||
self.tableView.translatesAutoresizingMaskIntoConstraints = false | ||
|
||
NSLayoutConstraint.activate([ | ||
self.tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), | ||
self.tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), | ||
self.tableView.topAnchor.constraint(equalTo: self.view.topAnchor), | ||
self.tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) | ||
]) | ||
} | ||
|
||
// MARK: - Bind Styles | ||
|
||
override func bindStyles() { | ||
super.bindStyles() | ||
|
||
applyWhiteBackgroundStyle(self.view) | ||
|
||
applyTableViewStyle(self.tableView) | ||
} | ||
|
||
// MARK: - View model | ||
|
||
override func bindViewModel() { | ||
super.bindViewModel() | ||
|
||
self.viewModel.outputs.reloadPaymentPlans.observeForUI().observeValues { [weak self] data in | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: delete this extra newline |
||
guard let self = self else { return } | ||
|
||
self.dataSource.load(data) | ||
self.tableView.reloadData() | ||
} | ||
|
||
self.viewModel.outputs.notifyDelegatePaymentPlanSelected | ||
.observeForUI() | ||
.observeValues { [weak self] paymentPlan in | ||
guard let self = self else { return } | ||
|
||
self.delegate?.pledgePaymentPlansViewController(self, didSelectPaymentPlan: paymentPlan) | ||
} | ||
} | ||
|
||
// MARK: - Configuration | ||
|
||
func configure(with value: PledgePaymentPlansAndSelectionData) { | ||
self.viewModel.inputs.configure(with: value) | ||
} | ||
} | ||
|
||
// MARK: - UITableViewDelegate | ||
|
||
extension PledgePaymentPlansViewController: UITableViewDelegate { | ||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | ||
tableView.deselectRow(at: indexPath, animated: true) | ||
|
||
let selectedCellData = self.dataSource[indexPath] as! PledgePaymentPlanCellData | ||
|
||
self.viewModel.inputs.didSelectRowAtIndexPath(indexPath, with: selectedCellData) | ||
} | ||
} | ||
|
||
// MARK: Styles | ||
|
||
private func applyTableViewStyle(_ tableView: UITableView) { | ||
tableView.separatorInset = .zero | ||
tableView.contentInsetAdjustmentBehavior = .never | ||
tableView.isScrollEnabled = false | ||
tableView.rowHeight = UITableView.automaticDimension | ||
|
||
applyWhiteBackgroundStyle(tableView) | ||
} | ||
|
||
private func applyWhiteBackgroundStyle(_ view: UIView) { | ||
view.backgroundColor = UIColor.ksr_white | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
@testable import Kickstarter_Framework | ||
@testable import Library | ||
import Prelude | ||
import SnapshotTesting | ||
import UIKit | ||
|
||
final class PledgePaymentPlansViewControllerTest: TestCase { | ||
override func setUp() { | ||
super.setUp() | ||
AppEnvironment.pushEnvironment(mainBundle: Bundle.framework) | ||
UIView.setAnimationsEnabled(false) | ||
} | ||
|
||
override func tearDown() { | ||
AppEnvironment.popEnvironment() | ||
UIView.setAnimationsEnabled(true) | ||
|
||
super.tearDown() | ||
} | ||
|
||
func testView_PledgeInFullSelected() { | ||
combos(Language.allLanguages, [Device.pad, Device.phone4_7inch]).forEach { language, device in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use The goal of snapshot tests is to comprehensively test with as few screenshots as possible. If "pledge in full" stays so simple, I'd recommend just having one or two snapshots where it's selected (all in english), and just doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated to use |
||
withEnvironment(language: language) { | ||
let controller = PledgePaymentPlansViewController.instantiate() | ||
|
||
let data = PledgePaymentPlansAndSelectionData(selectedPlan: .pledgeinFull) | ||
controller.configure(with: data) | ||
|
||
let (parent, _) = traitControllers(device: device, orientation: .portrait, child: controller) | ||
parent.view.frame.size.height = 400 | ||
|
||
self.scheduler.advance(by: .seconds(1)) | ||
|
||
assertSnapshot(matching: parent.view, as: .image, named: "lang_\(language)_device_\(device)") | ||
} | ||
} | ||
} | ||
|
||
func testView_PledgeOverTimeSelected() { | ||
combos(Language.allLanguages, [Device.pad, Device.phone4_7inch]).forEach { language, device in | ||
withEnvironment(language: language) { | ||
let controller = PledgePaymentPlansViewController.instantiate() | ||
|
||
let data = PledgePaymentPlansAndSelectionData(selectedPlan: .pledgeOverTime) | ||
controller.configure(with: data) | ||
|
||
let (parent, _) = traitControllers(device: device, orientation: .portrait, child: controller) | ||
parent.view.frame.size.height = 400 | ||
|
||
self.scheduler.advance(by: .seconds(1)) | ||
|
||
assertSnapshot(matching: parent.view, as: .image, named: "lang_\(language)_device_\(device)") | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the stack view does not look as good in the snapshot tests as the table view did (I assume that's because the view is conforming to the size we give it, which for stack view means extra space). I'm not sure if there's a good way to fix this, however, other than manually setting the snapshot height to something that makes more sense for the view. @scottkicks , do you have any good ideas for this? I'm also okay with leaving it for now - feel free to file a tech debt ticket and assign it to me and I can look into snapshot options. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import Library | ||
import UIKit | ||
|
||
internal final class PledgePaymentPlansDataSource: ValueCellDataSource { | ||
internal func load(_ data: PledgePaymentPlansAndSelectionData) { | ||
self.clearValues() | ||
|
||
let isPledgeInFullSelected = data.selectedPlan == PledgePaymentPlansType.pledgeinFull | ||
|
||
let pledgeInFullOption = PledgePaymentPlanCellData( | ||
type: PledgePaymentPlansType.pledgeinFull, | ||
isSelected: isPledgeInFullSelected | ||
) | ||
let pledgeOverTimeOption = PledgePaymentPlanCellData( | ||
type: PledgePaymentPlansType.pledgeOverTime, | ||
isSelected: !isPledgeInFullSelected | ||
) | ||
|
||
self.set( | ||
values: [pledgeInFullOption, pledgeOverTimeOption], | ||
cellClass: PledgePaymentPlanCell.self, | ||
inSection: 0 | ||
) | ||
} | ||
|
||
internal override func configureCell(tableCell cell: UITableViewCell, withValue value: Any) { | ||
switch (cell, value) { | ||
case let (cell as PledgePaymentPlanCell, value as PledgePaymentPlanCellData): | ||
cell.configureWith(value: value) | ||
default: | ||
assertionFailure("Unrecognized combo: \(cell), \(value).") | ||
} | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this test does a pretty good job of demonstrating why we don't need a data source here at all, since it's not testing anything useful that we're not also seeing in the snapshot tests. I still think the best option here is to get rid of the tableview entirely and just use a UIStackView. If you do want to keep the test, however, please make it a little less confusing (see further comments). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, code updated to use a UIStackView instead |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
@testable import Kickstarter_Framework | ||
@testable import KsApi | ||
@testable import Library | ||
import Prelude | ||
import XCTest | ||
|
||
final class PledgePaymentPlansDataSourceTest: XCTestCase { | ||
private let dataSource = PledgePaymentPlansDataSource() | ||
private let tableView = UITableView() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: since the tableView isn't important to the test itself and only used to test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File removed by refactor |
||
|
||
func testLoad_DefaultState() { | ||
let defaultData = PledgePaymentPlansAndSelectionData(selectedPlan: .pledgeinFull) | ||
|
||
self.dataSource.load(defaultData) | ||
|
||
XCTAssertEqual(1, self.dataSource.numberOfSections(in: self.tableView)) | ||
|
||
XCTAssertEqual(2, self.dataSource.numberOfItems(in: PledgePaymentPlansType.pledgeinFull.rawValue)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is so confusing; why are you using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File removed by refactor |
||
|
||
XCTAssertEqual( | ||
"PledgePaymentPlanCell", | ||
self.dataSource.reusableId(item: 0, section: 0) | ||
) | ||
|
||
XCTAssertEqual( | ||
"PledgePaymentPlanCell", | ||
self.dataSource.reusableId(item: 1, section: 0) | ||
) | ||
} | ||
jovaniks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still confused about this choice - can you explain the benefit of using the table view here to me, when a stack view seems like it'd be simpler? Also, if you are sticking with a table view, thoughts on subclassing
UITableViewController
instead? I think it'd simplify this class a lot.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in this comment thread with Scott, this approach was marked as tech debt. However, I’ve already addressed this feedback and resolved it within this PR by including a new commit that switches to a
UIStackView
implementation.