-
Notifications
You must be signed in to change notification settings - Fork 108
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
[Woo POS] Add test coverage for PointOfSaleDashboardViewModel #13310
[Woo POS] Add test coverage for PointOfSaleDashboardViewModel #13310
Conversation
Verify isAddMoreDisabled is set appropriately for payment state and order syncing state.
Generated by 🚫 Danger |
📲 You can test the changes from this Pull Request in WooCommerce iOS by scanning the QR code below to install the corresponding build.
|
@@ -36,13 +36,12 @@ final class PointOfSaleDashboardViewModel: ObservableObject { | |||
init(itemProvider: POSItemProvider, | |||
cardPresentPaymentService: CardPresentPaymentFacade, | |||
orderService: POSOrderServiceProtocol, | |||
currencyFormatter: CurrencyFormatter) { | |||
currencyFormatter: CurrencyFormatter, | |||
totalsViewModel: TotalsViewModel) { |
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.
Since with this change the totalsViewModel is injected and not created, what was the main reason for it? And will we need to change some other parts of the POS code with this?
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.
It was a first-cut attempt to improve testability of this class.
This injection was removed in commit 35370c7
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.
Thanks for working on this! I left a couple of comments regarding the decision of injecting the TotalsViewModel
, as well as exposing private properties that were only gettable.
Perhaps we can improve the test coverage here by using Mocks/Fakes/Stubs/Spies instead? As an example we do this with the item provider
paymentState: PaymentState = .acceptingCard, | ||
isSyncingOrder: Bool = false) { |
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.
By setting these two properties and its initial values on initialization we're now setting some default initial state, when before that it was stateless. Note how the only dependencies being injected are abstractions like the payments Façade, utils like the currency formatter, or the POS interfaces.
Do we want it to always default to .acceptingCard
and not syncing
? Would it be preferable to leave the implementation details to each service? Eg: Payment state being set and updated from the CardPresentPaymentService
rather than injected in.
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.
Looks like these have initial values on trunk?
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.
Looks like these have initial values on trunk?
They are, but it's also internal state to the class. A concern here would be:
Imagine we implement a new XYZ that needs the TotalsViewModel
to be in .acceptingCard
state. Since the property is exposed, and this happens to be its default value then we just ignore setting the property specifically. Later on we update the implementation details of the class to be .notAcceptingCard
instead, which will cause XYZ to fail because it relied on default state that is no longer the default case, and would trigger no errors (unless catch by unit tests!) because the compiler would find no issues with it.
To avoid these, we can either force providing a specific initial state by not providing a default value, so the compiler enforces this, or in our case just not expose the state to be modified from outside the class, and leave the responsibility of it to the TotalsViewModel
itself, opening some getters so it can be checked (but not modified) for other objects that need to check it.
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.
That's a a good point.
Addressed in commit bb3d973.
let totalsViewModel = TotalsViewModel(orderService: POSOrderPreviewService(), | ||
cardPresentPaymentService: CardPresentPaymentPreviewService(), | ||
currencyFormatter: .init(currencySettings: .init())) |
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.
While this happens on the #Preview
, declaring a new instance of TotalsViewModel
in order to inject it into the VM for the CartView
seems to indicate that the design of the dependency graph is not quite right. I'd say the CartView
should know nothing about the Totals VM and perhaps this shouldn't be injected here.
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.
Addressed in commit 35370c7.
let totalsViewModel = TotalsViewModel(orderService: POSOrderPreviewService(), | ||
cardPresentPaymentService: CardPresentPaymentPreviewService(), | ||
currencyFormatter: .init(currencySettings: .init())) |
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.
Same as above, up until now the entry point seems to accept only abstractions either from the payment façade, or the POS interfaces, however now it would require to inject a specific POS VM.
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.
Addressed in commit 35370c7.
super.tearDown() | ||
} | ||
|
||
private func setupViewModelWithExpectationsExpectingAddMoreMatchesSyncingState(paymentState: TotalsViewModel.PaymentState, |
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.
Being helper methods, these could go into an extension. I think there's also no need in the test target for these to be private. Maybe we can also simplify the function names? Since it would seem are specific only to the AddMoreIsDisabled
property, however both payment state and syncing state are exposed. Eg:
setupViewModel(paymentState: TotalsViewModel.PaymentState, isSyncingOrder: Bool)
Keep isSyncingOrder and paymentState private.
…en payment state is idle Added PointOfSaleDashboardViewModelProtocol to simulate order syncing for use in the unit test.
WooCommerce/Classes/POS/ViewModels/PointOfSaleDashboardViewModel.swift
Outdated
Show resolved
Hide resolved
WooCommerce/WooCommerceTests/POS/ViewModels/PointOfSaleDashboardViewModelTests.swift
Outdated
Show resolved
Hide resolved
WooCommerce/WooCommerceTests/POS/ViewModels/PointOfSaleDashboardViewModelTests.swift
Outdated
Show resolved
Hide resolved
Work in progress.
Fixes a compile issue.
Fixes test: test_isAddMoreDisabled_is_true_when_order_is_syncing_and_payment_state_is_idle
Adds: test_isAddMoreDisabled_is_true_for_processingPayment.
Protocol no longer needed after refactoring to new testing approach using mocked dependencies.
Also restore read-only access control for external entities with lazy initialization for cartViewModel.
- Converted publishers to computed properties leveraging property wrappers for simplified exposure and automatic initialization. - Introduced a `bindPublishers` method to centralize the assignment of publishers, ensuring all necessary bindings are in one place. - Addressed feedback by eliminating manual publisher initialization in initializers, reducing duplication and potential errors. - Enhanced code maintainability and readability by using property wrappers and centralizing publisher assignments.
…ta-to-go-back-to-product-selector-when-order-is-being-synced-add-test Conflicts: WooCommerce/Classes/POS/Presentation/TotalsView.swift WooCommerce/Classes/POS/ViewModels/TotalsViewModel.swift
- Updated the TotalsViewModel to remove default values for `paymentState` and `isSyncingOrder`, making these parameters mandatory during initialization.
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 am wondering if we need AnyTotalsViewModel
and can just go with TotalsViewModelProtocol
and having TotalsViewModel
and MockTotalsViewModel
conform to it...
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 made another suggestion.
I hate the fact that I'm holding this PR for so long but I want to make sure we don't make things harder for ourselves.
I suggest to use concrete implementation in Views, and protocols in View Models. This way we go around any compiler issues that we had and it also transparently exposes our dependency tree that we can refactor in the future.
Thanks for your detailed feedback and WIP solution @staskus. I too would like to see this moving faster but also want to get this right and not add undesirable patterns while staying pragmatic and mindful of speed of development in the short and long term. I'll take a look and see if your WIP solution looks like a lower level of effort than the refactoring I had planned. |
- Make tests compile. - Required changing access modifiers on `PointOfSaleDashboardViewModel` from `private` to internal access.
- Added test_observeCartAddMoreAction_updates_orderStage_to_building. - Added two tests that were formerly combined and now have been broken out into separate cases. - Cleaned up other tests.
Remove trailing whitespace.
Your rearchitecture looked good to me. I merged it in and make adjustments to get the tests to compile in commit e8df637. One downside is that changed access modifiers for I also expanded test coverage and cleaned up CartViewModelTests are failing. Maybe you can have a look? Seems the issue is with |
@soundsokay yes, it could be! |
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.
It builds, I think it's good time to merge this 👍
@bozidarsevo, would be good if you could take one more look yourself, since I also made the last commits.
…t-selector-when-order-is-being-synced-add-test
Thank you for your contributions @staskus and @bozidarsevo. I'm taking a look now. |
I will fix the build now, might have made a wrong conflict merge commit |
Adds verification that orderStage is set to .building when cart becomes empty.
Remove TODO for adding test coverage for PointOfSaleDashboardViewModel now that it has been added.
I made recent commits that only change test code in PointOfSaleDashboardViewModelTests to expand test coverage and remove comments and whitespace. With all checks passing and two 👍 I'm going to merge. Thanks again for your reviews and contributions @staskus and @bozidarsevo. |
Expands test coverage for PointOfSaleDashboardViewModel to verify the
observePaymentStateForButtonDisabledProperties
method correctly setsisAddMoreDisabled
accounting for both payment state and order syncing state.Closes: #13306
Description
The Add More button was disabled in #13270 but tests weren't updated. This PR adds coverage for that.
Steps to reproduce
Testing information
Invert the values returned and used in the assignment to
isAddMoreDisabled
, run thePointOfSaleDashboardViewModelTests
, and observe failures in the newly added tests.Screenshots
N/A
RELEASE-NOTES.txt
if necessary.