From 9ed58346e8e8666784fd5f4f87fcb5fc73f97ece Mon Sep 17 00:00:00 2001 From: Steven Grosmark Date: Tue, 18 Feb 2020 12:49:09 -0500 Subject: [PATCH] Initial commit --- .swiftlint.yml | 140 ++ Example/Example_Tests/CounterStoreTests.swift | 68 + .../Example_Tests/FoodOnboardingTests.swift | 75 + Example/Example_Tests/Info.plist | 22 + Example/Example_Tests/LoginTests.swift | 173 ++ Example/Example_Tests/MyDayFlowTests.swift | 107 ++ Example/Example_Tests/MyDayTests.swift | 176 +++ Example/Example_Tests/OnboardingTests.swift | 138 ++ .../SearchAndTrackFlowTests.swift | 310 ++++ .../SearchStoreTests_FunctionalStyle1.swift | 125 ++ .../SearchStoreTests_FunctionalStyle2.swift | 149 ++ .../SearchStoreTests_LassoStoreTestCase.swift | 138 ++ .../SearchStoreTests_XCTestCase.swift | 98 ++ .../Signup/SignupFormStoreTests.swift | 176 +++ .../Signup/SignupIntroStoreTests.swift | 37 + Example/Example_Tests/SurveyFlowTests.swift | 100 ++ Example/Lasso.xcodeproj/project.pbxproj | 1396 +++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDETemplateMacros.plist | 23 + .../xcschemes/Lasso-Example.xcscheme | 184 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDETemplateMacros.plist | 23 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 10 + Example/Lasso/AppDelegate.swift | 44 + Example/Lasso/Base.lproj/LaunchScreen.xib | 46 + .../AppIcon.appiconset/Contents.json | 53 + Example/Lasso/Images.xcassets/Contents.json | 6 + .../circle.imageset/Contents.json | 15 + .../circle.imageset/circle.pdf | Bin 0 -> 2465 bytes .../square.imageset/Contents.json | 15 + .../square.imageset/square.pdf | Bin 0 -> 2423 bytes Example/Lasso/Info.plist | 37 + .../Lasso/Presentation/SimpleCounter.swift | 106 ++ Example/Lasso/Presentation/Welcome.swift | 128 ++ .../Lasso/Sample Catalog/SampleCatalog.swift | 104 ++ .../Sample Catalog/SampleCatalogFlow.swift | 207 +++ .../SampleCatalogViewController.swift | 97 ++ .../Samples/ChooseWindowTransition.swift | 195 +++ .../Lasso/Samples/Counter/CounterScreen.swift | 74 + .../Counter/CounterViewController.swift | 106 ++ Example/Lasso/Samples/Login/Login.swift | 109 ++ .../Lasso/Samples/Login/LoginService.swift | 50 + .../Samples/Login/LoginViewController.swift | 108 ++ .../Lasso/Samples/MyDay/CalendarScreen.swift | 88 ++ .../Lasso/Samples/MyDay/DailyLogScreen.swift | 237 +++ .../Samples/MyDay/MyDayCardsScreen.swift | 211 +++ .../Lasso/Samples/MyDay/MyDayController.swift | 87 + Example/Lasso/Samples/MyDay/MyDayFlow.swift | 55 + .../Samples/Onboarding/EnterNameScreen.swift | 141 ++ .../Samples/Onboarding/FoodOnboarding.swift | 90 ++ .../Lasso/Samples/Onboarding/Onboarding.swift | 170 ++ .../Lasso/Samples/PageControllerFlow.swift | 61 + .../Lasso/Samples/SearchAndTrackFlow.swift | 84 + Example/Lasso/Samples/SearchModule.swift | 301 ++++ Example/Lasso/Samples/Signup/Signup.swift | 73 + Example/Lasso/Samples/Signup/SignupForm.swift | 295 ++++ .../Lasso/Samples/Signup/SignupIntro.swift | 73 + .../Samples/SplitView/RandomItemFlow.swift | 57 + .../Lasso/Samples/SplitView/SplitView.swift | 131 ++ Example/Lasso/Samples/StrangeFlow.swift | 67 + .../Lasso/Samples/Survey/MakeListModule.swift | 179 +++ Example/Lasso/Samples/Survey/SurveyFlow.swift | 146 ++ .../Lasso/Samples/Survey/SurveyModule.swift | 115 ++ Example/Lasso/Samples/Tabs/RandomItems.swift | 200 +++ Example/Lasso/Samples/Tabs/TabsFlow.swift | 55 + Example/Lasso/Samples/TextScreen.swift | 100 ++ Example/Lasso/Samples/UIKitBindings.swift | 140 ++ Example/Lasso/Utilities/RandomStrings.swift | 62 + Example/Lasso/Utilities/Result.swift | 26 + .../ViewHelpers/UIActivityIndicatorView.swift | 31 + .../Utilities/ViewHelpers/UIButton.swift | 43 + .../Lasso/Utilities/ViewHelpers/UIColor.swift | 46 + .../Lasso/Utilities/ViewHelpers/UIImage.swift | 42 + .../Lasso/Utilities/ViewHelpers/UILabel.swift | 38 + .../Utilities/ViewHelpers/UITableView.swift | 37 + .../Utilities/ViewHelpers/UITextField.swift | 44 + .../Lasso/Utilities/ViewHelpers/UIView.swift | 40 + Example/LassoTestUtilities_Tests/Info.plist | 22 + .../LifeCycleController.swift | 64 + .../MockableExtensionTests.swift | 50 + ...stingTests+AnyModalPresentationStyle.swift | 419 +++++ .../ModalTestingTests+FullScreen.swift | 265 ++++ .../ModalTestingTests+SystemBehavior.swift | 326 ++++ .../NavigationTestingTests.swift | 575 +++++++ .../ValueDiffingTests.swift | 47 + .../VerboseLoggingTests.swift | 62 + .../ActionDispatchableBindingTests.swift | 229 +++ Example/Lasso_Tests/Info.plist | 24 + Example/Lasso_Tests/MockableTests.swift | 125 ++ Example/Lasso_Tests/ObjectBindingTests.swift | 173 ++ .../Lasso_Tests/ScreenCaptureStoreTests.swift | 154 ++ .../Lasso_Tests/StateObservationTests.swift | 161 ++ .../UIGestureRecognizer+SendActions.swift | 63 + Example/Lasso_Tests/ValueBinderTests.swift | 225 +++ Example/Podfile | 49 + Example/Podfile.lock | 33 + Example/SwiftPM/Images/app.gif | Bin 0 -> 50557 bytes Example/SwiftPM/Images/dependencies.png | Bin 0 -> 38690 bytes .../Lasso-SwiftPM.xcodeproj/project.pbxproj | 519 ++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/IDETemplateMacros.plist | 23 + .../xcschemes/Lasso-SwiftPM.xcscheme | 99 ++ .../Lasso-SwiftPM/AppDelegate.swift | 39 + .../AppIcon.appiconset/Contents.json | 98 ++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 25 + .../Lasso-SwiftPM/Lasso-SwiftPM/Info.plist | 48 + .../Lasso-SwiftPM/RestScreen.swift | 109 ++ .../Lasso-SwiftPM/WorkFlow.swift | 53 + .../Lasso-SwiftPM/WorkScreen.swift | 122 ++ .../Lasso-SwiftPMTests/Info.plist | 22 + .../Lasso-SwiftPMTests/RestScreenTests.swift | 42 + .../Lasso-SwiftPMTests/WorkFlowTests.swift | 63 + .../Lasso-SwiftPMTests/WorkScreenTests.swift | 89 ++ Example/SwiftPM/ReadMe.md | 14 + Gemfile | 6 + Gemfile.lock | 230 +++ LICENSE | 201 +++ Lasso.podspec | 23 + LassoTestUtilities.podspec | 28 + Package.swift | 28 + README.md | 143 ++ .../Flow/Flow+UINavigationController.swift | 72 + .../Flow/Flow+UIPageViewController.swift | 28 + .../Lasso/Flow/Flow+UIViewController.swift | 91 ++ Sources/Lasso/Flow/Flow.swift | 87 + Sources/Lasso/Module.swift | 128 ++ Sources/Lasso/Screen/Screen.swift | 112 ++ Sources/Lasso/Screen/ScreenFactory.swift | 49 + .../ScreenPlacer/ScreenPlacer+Abstract.swift | 135 ++ .../ScreenPlacer+UINavigationController.swift | 89 ++ .../ScreenPlacer+UIPageViewController.swift | 65 + .../ScreenPlacer+UITabBarController.swift | 56 + .../ScreenPlacer+UIViewController.swift | 32 + .../ScreenPlacer/ScreenPlacer+UIWindow.swift | 58 + Sources/Lasso/ScreenPlacer/ScreenPlacer.swift | 77 + .../ScreenPlacer/ScreenPlacerEmbedding.swift | 48 + Sources/Lasso/Store/MockStore.swift | 88 ++ Sources/Lasso/Store/PassthroughStore.swift | 30 + Sources/Lasso/Store/Store.swift | 245 +++ Sources/Lasso/Store/ViewStore.swift | 92 ++ .../Types/ActionDispatchable+Bindings.swift | 255 +++ Sources/Lasso/Types/Mockable.swift | 102 ++ Sources/Lasso/Types/ObjectBinding.swift | 63 + Sources/Lasso/Types/OutputBridge.swift | 36 + Sources/Lasso/Types/Types.swift | 103 ++ Sources/Lasso/Types/ValueBinder.swift | 78 + .../UIKit+Lasso/UIWindow+Transition.swift | 228 +++ .../ControllerLifecycle.swift | 158 ++ Sources/LassoTestUtilities/Fail.swift | 225 +++ .../LassoTestUtilities/FlowAsserting.swift | 35 + Sources/LassoTestUtilities/FlowTestCase.swift | 48 + .../LassoStoreTestCase.swift | 222 +++ Sources/LassoTestUtilities/ModalTesting.swift | 349 +++++ .../NavigationTesting.swift | 244 +++ .../ScreenModule+Testing.swift | 78 + .../StoreTesting+ThenAssertion.swift | 99 ++ .../StoreTesting+WhenStatement.swift | 34 + Sources/LassoTestUtilities/StoreTesting.swift | 135 ++ Sources/LassoTestUtilities/TypeCast.swift | 62 + Sources/LassoTestUtilities/ValueDiffing.swift | 74 + .../LassoTestUtilities/VerboseLogging.swift | 69 + .../XCTestCase+Mockable.swift | 31 + docs/Dependency Injection case study.md | 120 ++ docs/Lasso-FlowsIntro.md | 317 ++++ docs/Lasso-Introduction-part1.md | 463 ++++++ docs/images/Lasso_Logo.pdf | Bin 0 -> 138903 bytes docs/images/Lasso_Logo.png | Bin 0 -> 21431 bytes docs/images/Lasso_Logo.svg | 1 + docs/images/TutorialFlowDiagram.svg | 1 + docs/images/flow-flow-screen.png | Bin 0 -> 52173 bytes docs/images/flow-flow-screen.svg | 1 + docs/images/flow-flow.svg | 1 + docs/images/flow.gif | Bin 0 -> 35248 bytes docs/images/flow.png | Bin 0 -> 37618 bytes docs/images/flow.svg | 1 + docs/images/flow1.svg | 1 + docs/images/login.png | Bin 0 -> 41169 bytes docs/images/screen-no output.svg | 1 + docs/images/screen.png | Bin 0 -> 16658 bytes docs/images/screen.svg | 1 + docs/images/trevor-screens.png | Bin 0 -> 404539 bytes docs/images/view-store.png | Bin 0 -> 13938 bytes docs/images/view-store.svg | 1 + docs/style-guide.md | 150 ++ 188 files changed, 19246 insertions(+) create mode 100644 .swiftlint.yml create mode 100644 Example/Example_Tests/CounterStoreTests.swift create mode 100644 Example/Example_Tests/FoodOnboardingTests.swift create mode 100644 Example/Example_Tests/Info.plist create mode 100644 Example/Example_Tests/LoginTests.swift create mode 100644 Example/Example_Tests/MyDayFlowTests.swift create mode 100644 Example/Example_Tests/MyDayTests.swift create mode 100644 Example/Example_Tests/OnboardingTests.swift create mode 100644 Example/Example_Tests/SearchAndTrackFlowTests.swift create mode 100644 Example/Example_Tests/SearchStoreTests_FunctionalStyle1.swift create mode 100644 Example/Example_Tests/SearchStoreTests_FunctionalStyle2.swift create mode 100644 Example/Example_Tests/SearchStoreTests_LassoStoreTestCase.swift create mode 100644 Example/Example_Tests/SearchStoreTests_XCTestCase.swift create mode 100644 Example/Example_Tests/Signup/SignupFormStoreTests.swift create mode 100644 Example/Example_Tests/Signup/SignupIntroStoreTests.swift create mode 100644 Example/Example_Tests/SurveyFlowTests.swift create mode 100644 Example/Lasso.xcodeproj/project.pbxproj create mode 100644 Example/Lasso.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Example/Lasso.xcodeproj/xcshareddata/IDETemplateMacros.plist create mode 100644 Example/Lasso.xcodeproj/xcshareddata/xcschemes/Lasso-Example.xcscheme create mode 100644 Example/Lasso.xcworkspace/contents.xcworkspacedata create mode 100644 Example/Lasso.xcworkspace/xcshareddata/IDETemplateMacros.plist create mode 100644 Example/Lasso.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Example/Lasso.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 Example/Lasso/AppDelegate.swift create mode 100644 Example/Lasso/Base.lproj/LaunchScreen.xib create mode 100644 Example/Lasso/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Example/Lasso/Images.xcassets/Contents.json create mode 100644 Example/Lasso/Images.xcassets/circle.imageset/Contents.json create mode 100644 Example/Lasso/Images.xcassets/circle.imageset/circle.pdf create mode 100644 Example/Lasso/Images.xcassets/square.imageset/Contents.json create mode 100644 Example/Lasso/Images.xcassets/square.imageset/square.pdf create mode 100644 Example/Lasso/Info.plist create mode 100644 Example/Lasso/Presentation/SimpleCounter.swift create mode 100644 Example/Lasso/Presentation/Welcome.swift create mode 100644 Example/Lasso/Sample Catalog/SampleCatalog.swift create mode 100644 Example/Lasso/Sample Catalog/SampleCatalogFlow.swift create mode 100644 Example/Lasso/Sample Catalog/SampleCatalogViewController.swift create mode 100644 Example/Lasso/Samples/ChooseWindowTransition.swift create mode 100644 Example/Lasso/Samples/Counter/CounterScreen.swift create mode 100644 Example/Lasso/Samples/Counter/CounterViewController.swift create mode 100644 Example/Lasso/Samples/Login/Login.swift create mode 100644 Example/Lasso/Samples/Login/LoginService.swift create mode 100644 Example/Lasso/Samples/Login/LoginViewController.swift create mode 100644 Example/Lasso/Samples/MyDay/CalendarScreen.swift create mode 100644 Example/Lasso/Samples/MyDay/DailyLogScreen.swift create mode 100644 Example/Lasso/Samples/MyDay/MyDayCardsScreen.swift create mode 100644 Example/Lasso/Samples/MyDay/MyDayController.swift create mode 100644 Example/Lasso/Samples/MyDay/MyDayFlow.swift create mode 100644 Example/Lasso/Samples/Onboarding/EnterNameScreen.swift create mode 100644 Example/Lasso/Samples/Onboarding/FoodOnboarding.swift create mode 100644 Example/Lasso/Samples/Onboarding/Onboarding.swift create mode 100644 Example/Lasso/Samples/PageControllerFlow.swift create mode 100644 Example/Lasso/Samples/SearchAndTrackFlow.swift create mode 100644 Example/Lasso/Samples/SearchModule.swift create mode 100644 Example/Lasso/Samples/Signup/Signup.swift create mode 100644 Example/Lasso/Samples/Signup/SignupForm.swift create mode 100644 Example/Lasso/Samples/Signup/SignupIntro.swift create mode 100644 Example/Lasso/Samples/SplitView/RandomItemFlow.swift create mode 100644 Example/Lasso/Samples/SplitView/SplitView.swift create mode 100644 Example/Lasso/Samples/StrangeFlow.swift create mode 100644 Example/Lasso/Samples/Survey/MakeListModule.swift create mode 100644 Example/Lasso/Samples/Survey/SurveyFlow.swift create mode 100644 Example/Lasso/Samples/Survey/SurveyModule.swift create mode 100644 Example/Lasso/Samples/Tabs/RandomItems.swift create mode 100644 Example/Lasso/Samples/Tabs/TabsFlow.swift create mode 100644 Example/Lasso/Samples/TextScreen.swift create mode 100644 Example/Lasso/Samples/UIKitBindings.swift create mode 100644 Example/Lasso/Utilities/RandomStrings.swift create mode 100644 Example/Lasso/Utilities/Result.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UIActivityIndicatorView.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UIButton.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UIColor.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UIImage.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UILabel.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UITableView.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UITextField.swift create mode 100644 Example/Lasso/Utilities/ViewHelpers/UIView.swift create mode 100644 Example/LassoTestUtilities_Tests/Info.plist create mode 100644 Example/LassoTestUtilities_Tests/LifeCycleController.swift create mode 100644 Example/LassoTestUtilities_Tests/MockableExtensionTests.swift create mode 100644 Example/LassoTestUtilities_Tests/ModalTestingTests+AnyModalPresentationStyle.swift create mode 100644 Example/LassoTestUtilities_Tests/ModalTestingTests+FullScreen.swift create mode 100644 Example/LassoTestUtilities_Tests/ModalTestingTests+SystemBehavior.swift create mode 100644 Example/LassoTestUtilities_Tests/NavigationTestingTests.swift create mode 100644 Example/LassoTestUtilities_Tests/ValueDiffingTests.swift create mode 100644 Example/LassoTestUtilities_Tests/VerboseLoggingTests.swift create mode 100644 Example/Lasso_Tests/ActionDispatchableBindingTests.swift create mode 100644 Example/Lasso_Tests/Info.plist create mode 100644 Example/Lasso_Tests/MockableTests.swift create mode 100644 Example/Lasso_Tests/ObjectBindingTests.swift create mode 100644 Example/Lasso_Tests/ScreenCaptureStoreTests.swift create mode 100644 Example/Lasso_Tests/StateObservationTests.swift create mode 100644 Example/Lasso_Tests/UIGestureRecognizer+SendActions.swift create mode 100644 Example/Lasso_Tests/ValueBinderTests.swift create mode 100644 Example/Podfile create mode 100644 Example/Podfile.lock create mode 100644 Example/SwiftPM/Images/app.gif create mode 100644 Example/SwiftPM/Images/dependencies.png create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.pbxproj create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/IDETemplateMacros.plist create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/xcschemes/Lasso-SwiftPM.xcscheme create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/AppDelegate.swift create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/Contents.json create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Base.lproj/LaunchScreen.storyboard create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Info.plist create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/RestScreen.swift create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkFlow.swift create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkScreen.swift create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/Info.plist create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/RestScreenTests.swift create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkFlowTests.swift create mode 100644 Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkScreenTests.swift create mode 100644 Example/SwiftPM/ReadMe.md create mode 100755 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 Lasso.podspec create mode 100644 LassoTestUtilities.podspec create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/Lasso/Flow/Flow+UINavigationController.swift create mode 100644 Sources/Lasso/Flow/Flow+UIPageViewController.swift create mode 100644 Sources/Lasso/Flow/Flow+UIViewController.swift create mode 100644 Sources/Lasso/Flow/Flow.swift create mode 100644 Sources/Lasso/Module.swift create mode 100644 Sources/Lasso/Screen/Screen.swift create mode 100644 Sources/Lasso/Screen/ScreenFactory.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacer+Abstract.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacer+UINavigationController.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacer+UIPageViewController.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacer+UITabBarController.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacer+UIViewController.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacer+UIWindow.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacer.swift create mode 100644 Sources/Lasso/ScreenPlacer/ScreenPlacerEmbedding.swift create mode 100644 Sources/Lasso/Store/MockStore.swift create mode 100644 Sources/Lasso/Store/PassthroughStore.swift create mode 100644 Sources/Lasso/Store/Store.swift create mode 100644 Sources/Lasso/Store/ViewStore.swift create mode 100644 Sources/Lasso/Types/ActionDispatchable+Bindings.swift create mode 100644 Sources/Lasso/Types/Mockable.swift create mode 100644 Sources/Lasso/Types/ObjectBinding.swift create mode 100644 Sources/Lasso/Types/OutputBridge.swift create mode 100644 Sources/Lasso/Types/Types.swift create mode 100644 Sources/Lasso/Types/ValueBinder.swift create mode 100644 Sources/Lasso/UIKit+Lasso/UIWindow+Transition.swift create mode 100644 Sources/LassoTestUtilities/ControllerLifecycle.swift create mode 100644 Sources/LassoTestUtilities/Fail.swift create mode 100644 Sources/LassoTestUtilities/FlowAsserting.swift create mode 100644 Sources/LassoTestUtilities/FlowTestCase.swift create mode 100644 Sources/LassoTestUtilities/LassoStoreTestCase.swift create mode 100644 Sources/LassoTestUtilities/ModalTesting.swift create mode 100644 Sources/LassoTestUtilities/NavigationTesting.swift create mode 100644 Sources/LassoTestUtilities/ScreenModule+Testing.swift create mode 100644 Sources/LassoTestUtilities/StoreTesting+ThenAssertion.swift create mode 100644 Sources/LassoTestUtilities/StoreTesting+WhenStatement.swift create mode 100644 Sources/LassoTestUtilities/StoreTesting.swift create mode 100644 Sources/LassoTestUtilities/TypeCast.swift create mode 100644 Sources/LassoTestUtilities/ValueDiffing.swift create mode 100644 Sources/LassoTestUtilities/VerboseLogging.swift create mode 100644 Sources/LassoTestUtilities/XCTestCase+Mockable.swift create mode 100644 docs/Dependency Injection case study.md create mode 100644 docs/Lasso-FlowsIntro.md create mode 100644 docs/Lasso-Introduction-part1.md create mode 100644 docs/images/Lasso_Logo.pdf create mode 100644 docs/images/Lasso_Logo.png create mode 100644 docs/images/Lasso_Logo.svg create mode 100644 docs/images/TutorialFlowDiagram.svg create mode 100644 docs/images/flow-flow-screen.png create mode 100644 docs/images/flow-flow-screen.svg create mode 100644 docs/images/flow-flow.svg create mode 100644 docs/images/flow.gif create mode 100644 docs/images/flow.png create mode 100644 docs/images/flow.svg create mode 100644 docs/images/flow1.svg create mode 100644 docs/images/login.png create mode 100644 docs/images/screen-no output.svg create mode 100644 docs/images/screen.png create mode 100644 docs/images/screen.svg create mode 100644 docs/images/trevor-screens.png create mode 100644 docs/images/view-store.png create mode 100644 docs/images/view-store.svg create mode 100644 docs/style-guide.md diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..0746b33 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,140 @@ +#--- +#--- Rules + +# Find all the available rules by running: +# swiftlint rules + +disabled_rules: # rule identifiers to exclude from running + - identifier_name + - line_length + - no_extension_access_modifier + - statement_position + - shorthand_operator + +opt_in_rules: # some rules are only opt-in + - anyobject_protocol + - collection_alignment + - closure_spacing + - closure_parameter_position + - colon + - comma + - compiler_protocol_init + - contains_over_filter_count + - contains_over_filter_is_empty + - contains_over_first_not_nil + - control_statement + - convenience_type + - deployment_target + - discarded_notification_center_observer + - duplicate_enum_cases + - duplicate_imports + - empty_count + - empty_enum_arguments + - empty_string + - empty_xctest_method + - explicit_init + - force_cast + - force_try + - force_unwrapping + - inert_defer + - last_where + - legacy_multiple + - legacy_random + - multiline_arguments + - multiline_function_chains + - multiline_parameters + - no_space_in_method_call + - opening_brace + - operator_usage_whitespace + - overridden_super_call + - prohibited_super_call + - redundant_discardable_let + - redundant_nil_coalescing + - redundant_optional_initialization + - redundant_string_enum_value + - return_arrow_whitespace + - static_operator + - superfluous_disable_command + - toggle_bool + - trailing_comma + - trailing_newline + - trailing_semicolon + - trailing_whitespace + - unused_declaration + - vertical_parameter_alignment_on_call + - yoda_condition + +custom_rules: + no_space_after_opening_parentheses: + name: "No space after opening parentheses" + message: "Please avoid using space after opening parentheses" + regex: '\(\h+' + + anonymous_init: + name: "Anonymous init()" + message: "Prefer explicit type initializer over anonymous calls to init()" + regex: '(\h+|\()\.init\(' + +#--- +#--- Rule configuration + +# configurable rules can be customized from this configuration file +# binary rules can set their severity level +# rules that have both warning and error levels, can set just the warning level +# or they can set both explicitly + +cyclomatic_complexity: + warning: 10 + error: 20 + ignores_case_statements: true + +file_length: + warning: 450 + +force_cast: warning +force_try: warning +force_unwrapping: warning + +function_body_length: + warning: 50 # default is 40 + error: 120 # default is 100 + +function_parameter_count: + warning: 6 + error: 9 + +large_tuple: + warning: 3 + error: 4 + +nesting: + type_level: + warning: 3 # default is 1 + +trailing_whitespace: + ignores_empty_lines: true + +type_body_length: + warning: 500 # default is 200 + error: 1000 # default is 350 + +type_name: + max_length: 60 + +unused_setter_value: error + + +#--- +#--- Paths + +#included: # paths to include during linting. `--path` is ignored if present. +# - ../Pod +# In ios-common-config, the path to the sources will be done via the --path argument + +excluded: # paths to ignore during linting. Takes precedence over `included`. + - ./Example/Pods + - ./Pods + - ./vendor + - ./fastlane + +reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji) diff --git a/Example/Example_Tests/CounterStoreTests.swift b/Example/Example_Tests/CounterStoreTests.swift new file mode 100644 index 0000000..352791e --- /dev/null +++ b/Example/Example_Tests/CounterStoreTests.swift @@ -0,0 +1,68 @@ +// +//===----------------------------------------------------------------------===// +// +// CounterStoreTests.swift +// +// Created by Steven Grosmark on 10/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import LassoTestUtilities +@testable import Lasso_Example + +class CounterStoreTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + + override func setUp() { + super.setUp() + store = CounterStore() + XCTAssertEqual(store.state.counter, 0) + } + + func test_IncrementDecrement() { + // when - tap + 3x + for _ in 0..<3 { + store.dispatchAction(.didTapIncrement) + } + + // then - counter should be 3 + XCTAssertStateEquals(updatedMarker { state in + state.counter = 3 + }) + + // when - tap decrement + store.dispatchAction(.didTapDecrement) + + // then - counter should go down + XCTAssertStateEquals(updatedMarker { state in + state.counter = 2 + }) + + // sanity check no outputs generated + XCTAssertOutputs([]) + } + + func test_Decrement_Clamping() { + // when + store.dispatchAction(.didTapDecrement) + + // then - don't go negative + XCTAssertEqual(store.state.counter, 0) + } + + func test_Next() { + store.dispatchAction(.didTapNext) + XCTAssertOutputs([.didTapNext]) + } + +} diff --git a/Example/Example_Tests/FoodOnboardingTests.swift b/Example/Example_Tests/FoodOnboardingTests.swift new file mode 100644 index 0000000..32b29c3 --- /dev/null +++ b/Example/Example_Tests/FoodOnboardingTests.swift @@ -0,0 +1,75 @@ +// +//===----------------------------------------------------------------------===// +// +// FoodOnboardingTests.swift +// +// Created by Trevor Beasty on 7/16/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_Example + +class FoodOnboardingTests: FlowTestCase { + + var flow: FoodOnboardingFlow! + + override func setUp() { + super.setUp() + flow = FoodOnboardingFlow() + } + + override func tearDown() { + flow = nil + super.tearDown() + } + + func test() throws { + typealias State = TextScreenModule.State + + // flow start + let welcomeController: TextViewController = try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) } + ) + + XCTAssertEqual(welcomeController.store.state, State(title: "Welcome", + description: "We just need a little info to get you going...", + buttons: ["Next"])) + + // press next on welcome screen + let notificationsController: TextViewController = try assertPushed( + after: welcomeController, + when: { welcomeController.store.dispatchAction(.didTapButton(0)) } + ) + + XCTAssertEqual(notificationsController.store.state, State(title: "Notifications", + description: "Enable Notifications for the smoothest experience...", + buttons: ["Next"])) + + // press next on notifications screen + let finishController: TextViewController = try assertPushed( + after: notificationsController, + when: { notificationsController.store.dispatchAction(.didTapButton(0)) } + ) + + XCTAssertEqual(finishController.store.state, State(title: "All set", + description: "Ok, let's get started!", + buttons: ["Finish"])) + + // output + flow.assert(when: { finishController.store.dispatchAction(.didTapButton(0)) }, + outputs: .didFinish) + } + +} diff --git a/Example/Example_Tests/Info.plist b/Example/Example_Tests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Example/Example_Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example/Example_Tests/LoginTests.swift b/Example/Example_Tests/LoginTests.swift new file mode 100644 index 0000000..2a376fc --- /dev/null +++ b/Example/Example_Tests/LoginTests.swift @@ -0,0 +1,173 @@ +// +//===----------------------------------------------------------------------===// +// +// LoginTests.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import LassoTestUtilities +@testable import Lasso_Example + +class LoginFormTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + + override func setUp() { + super.setUp() + store = LoginScreenStore() + } + + func test_DefaultInitialState() { + XCTAssertStateEquals(LoginScreenModule.State( + username: "", + password: "", + canLogin: false, + error: nil, + phase: .idle)) + } + + func test_enterJustUsername() { + // when - user enters a username + store.dispatchAction(.didEditUsername("me")) + + // then - new value should be reflected in the state + XCTAssertStateEquals(updatedMarker { state in + state.username = "me" + state.canLogin = false + }) + + // when - user taps "login" + store.dispatchAction(.didTapLogin) + + // then - error + XCTAssertStateEquals(updatedMarker { state in + state.error = "Please enter your username and password" + }) + } + + func test_enterJustPassword() { + // when - user enters a username + store.dispatchAction(.didEditPassword("secret")) + + // then - new value should be reflected in the state + XCTAssertStateEquals(updatedMarker { state in + state.password = "secret" + state.canLogin = false + }) + + // when - user taps "login" + store.dispatchAction(.didTapLogin) + + // then - error + XCTAssertStateEquals(updatedMarker { state in + state.error = "Please enter your username and password" + }) + } + + func test_enterBoth() { + // when - user enters u/p combo + store.dispatchAction(.didEditUsername("me")) + store.dispatchAction(.didEditPassword("secret")) + + // then - new value should be reflected in the state + XCTAssertStateEquals(updatedMarker { state in + state.username = "me" + state.password = "secret" + state.canLogin = true + }) + } + +} + +#if swift(>=5.1) +class LoginTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + var mockService: MockLoginService! + + override func setUp() { + super.setUp() + + // start with store in form filled out, ready to login state + store = LoginScreenStore(with: LoginScreenModule.State( + username: "myname", + password: "pass", + canLogin: true, + error: nil, + phase: .idle)) + + mockService = MockLoginService() + mock(LoginService.$shared, with: mockService) + } + + func test_login() { + // when - user taps "login" + store.dispatchAction(.didTapLogin) + + // then - log service pinged, in busy state + XCTAssertStateEquals(updatedMarker { state in + state.phase = .busy + state.canLogin = false + }) + XCTAssertEqual(mockService.username, store.state.username) + XCTAssertEqual(mockService.password, store.state.password) + XCTAssertNotNil(mockService.completion) + + // when - double-tap + let savedCompletion = mockService.completion + mockService.completion = nil + store.dispatchAction(.didTapLogin) + + // then - login service shouldn't be pinged again + XCTAssertNil(mockService.completion) + + // when - successful login + savedCompletion?(.success(())) + + // then - not busy anymore, output sent + XCTAssertStateEquals(updatedMarker { state in + state.phase = .idle + }) + XCTAssertOutputs([.didLogin]) + } + + func test_loginFailure() { + // when - user taps "login" + store.dispatchAction(.didTapLogin) + + // when - successful login + mockService.completion?(.failure(MockError.failed)) + + // then - not busy anymore, output sent + XCTAssertStateEquals(updatedMarker { state in + state.phase = .idle + state.error = "Invalid login" + }) + XCTAssertOutputs([]) + } + + final class MockLoginService: LoginServiceProtocol { + var username: String? + var password: String? + var completion: ((Result) -> Void)? + + func login(_ username: String, password: String, completion: @escaping (Result) -> Void) { + self.username = username + self.password = password + self.completion = completion + } + } + +} +#endif diff --git a/Example/Example_Tests/MyDayFlowTests.swift b/Example/Example_Tests/MyDayFlowTests.swift new file mode 100644 index 0000000..66390a7 --- /dev/null +++ b/Example/Example_Tests/MyDayFlowTests.swift @@ -0,0 +1,107 @@ +// +//===----------------------------------------------------------------------===// +// +// MyDayFlowTests.swift +// +// Created by Trevor Beasty on 10/18/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_Example + +class MyDayFlowTestsWithDeepInjection: FlowTestCase { + + var flow: MyDayFlow! + + var cardsRequests: [(date: Date, completion: (Result<[String], Error>) -> Void)] = [] + + override func setUp() { + super.setUp() + flow = MyDayFlow() + + flow.createMyDay = { (date: Date) -> MyDayController in + let myDayController = MyDayController(date: date) + + myDayController.cardsScreenFactory = screenFactory(configure: { cardsStore in + cardsStore.getCards = { date, completion in + self.cardsRequests.append((date, completion)) + } + }) + + return myDayController + } + } + + override func tearDown() { + flow = nil + cardsRequests = [] + super.tearDown() + } + + func test_CardSelection() throws { + let myDayController: MyDayController = try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) } + ) + + let cards = ["A"] + cardsRequests[0].completion(.success(cards)) + + let cardsController: MyDayCardsController = try myDayController.firstChildOfType() + let cardsDetailController: TextViewController = try assertPushed( + after: myDayController, + when: { cardsController.store.dispatchAction(.didSelectCard(idx: 0)) } + ) + XCTAssertEqual(cardsDetailController.state.title, "A") + } + +} + +class MyDayFlowTestsWithShallowInjection: FlowTestCase { + + var flow: MyDayFlow! + var mockCardsScreen: MockScreen! + + override func setUp() { + super.setUp() + flow = MyDayFlow() + mockCardsScreen = MockScreen() + + flow.createMyDay = { (date: Date) -> MyDayController in + let myDayController = MyDayController(date: date) + myDayController.cardsScreenFactory = mockScreenFactory(mockScreen: self.mockCardsScreen) + return myDayController + } + } + + override func tearDown() { + flow = nil + mockCardsScreen = nil + super.tearDown() + } + + func test_CardSelection() throws { + let myDayController: MyDayController = try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) } + ) + + let cardsDetailController: TextViewController = try assertPushed( + after: myDayController, + when: { mockCardsScreen.mockStore?.dispatchMockOutput(.didSelectCard(card: "A")) } + ) + XCTAssertEqual(cardsDetailController.state.title, "A") + } + +} diff --git a/Example/Example_Tests/MyDayTests.swift b/Example/Example_Tests/MyDayTests.swift new file mode 100644 index 0000000..5711ac2 --- /dev/null +++ b/Example/Example_Tests/MyDayTests.swift @@ -0,0 +1,176 @@ +// +//===----------------------------------------------------------------------===// +// +// MyDayTests.swift +// +// Created by Trevor Beasty on 10/17/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_Example + +class MyDayTestsWithDeepInjection: XCTestCase { + + var myDayController: MyDayController! + var calendarController: CalendarController! + var dailyLogController: DailyLogController! + var cardsController: MyDayCardsController! + + var user: User! + + typealias CalendarState = CalendarScreenModule.State + typealias DailyLogState = DailyLogViewModule.ViewState + typealias CardsState = MyDayCardsScreenModule.State + + typealias DailyLogServiceRequest = (date: Date, userId: String, completion: (Result) -> Void) + typealias CardsRequest = (date: Date, completion: (Result<[String], Error>) -> Void) + + var dailyLogRequests: [DailyLogServiceRequest] = [] + var cardsRequests: [CardsRequest] = [] + + private func setUp(date: Date) throws { + super.setUp() + + self.myDayController = MyDayController(date: date) + self.user = User(id: "def456", program: .C) + + myDayController.dailyLogScreenFactory = screenFactory(configure: { dailyLogStore in + dailyLogStore.getDailyLog = { (date, userId, completion) in + self.dailyLogRequests.append((date, userId, completion)) + } + dailyLogStore.user = self.user + }) + + myDayController.cardsScreenFactory = screenFactory(configure: { myDayCardsStore in + myDayCardsStore.getCards = { date, completion in + self.cardsRequests.append((date, completion)) + } + }) + + // to trigger 'viewDidLoad', where child setup is performed + _ = myDayController.view + + calendarController = try myDayController.firstChildOfType() + dailyLogController = try myDayController.firstChildOfType() + cardsController = try myDayController.firstChildOfType() + } + + override func tearDown() { + myDayController = nil + calendarController = nil + dailyLogController = nil + cardsController = nil + dailyLogRequests = [] + cardsRequests = [] + super.tearDown() + } + + func test_DateSelection_UpdatesDailyLogAndArticles() throws { + // given + var dateComponents = DateComponents() + dateComponents.day = 1 + dateComponents.month = 1 + dateComponents.year = 2000 + guard let initialDate = Calendar.current.date(from: dateComponents) else { return XCTFail("could not create date") } + try setUp(date: initialDate) + self.dailyLogRequests = [] + self.cardsRequests = [] + + // when + guard let nextDate = Calendar.current.date(byAdding: .day, value: 1, to: initialDate) else { return XCTFail("could not create next date") } + calendarController.store.dispatchAction(.didSelectDate(nextDate)) + + // then + XCTAssertEqual(dailyLogRequests.count, 1) + XCTAssertEqual(dailyLogRequests[0].date, nextDate) + XCTAssertEqual(dailyLogRequests[0].userId, "def456") + XCTAssertTrue(dailyLogController.state.isLoading) + + XCTAssertEqual(cardsRequests.count, 1) + XCTAssertEqual(cardsRequests[0].date, nextDate) + XCTAssertEqual(cardsController.state.phase, .busy) + + // when + let dailyLog = DailyLog(program: .C, caloriesConsumed: 1, caloriesRemaining: 2, stepsTaken: 3) + dailyLogRequests[0].completion(.success(dailyLog)) + + let cards = ["A"] + cardsRequests[0].completion(.success(cards)) + + // then + let text = "program: C\ncalories consumed: 1\ncalories remaining: 2\nsteps taken: 3" + XCTAssertEqual(dailyLogController.state, DailyLogState(text: text, isLoading: false)) + + XCTAssertEqual(cardsController.state, CardsState(cards: cards, phase: .idle)) + } + +} + +class MyDayTestsWithShallowInjection: XCTestCase { + + var myDayController: MyDayController! + var mockCalendarScreen: MockScreen! + var mockCardsScreen: MockScreen! + var mockDailyLogScreen: MockScreen! + + private func setUp(date: Date) throws { + super.setUp() + + self.myDayController = MyDayController(date: date) + mockCalendarScreen = MockScreen() + mockCardsScreen = MockScreen() + mockDailyLogScreen = MockScreen() + + myDayController.calendarScreenFactory = mockScreenFactory(mockScreen: mockCalendarScreen) + myDayController.cardsScreenFactory = mockScreenFactory(mockScreen: mockCardsScreen) + myDayController.dailyLogScreenFactory = mockScreenFactory(mockScreen: mockDailyLogScreen) + + // to trigger 'viewDidLoad', where child setup is performed + _ = myDayController.view + } + + override func tearDown() { + myDayController = nil + mockCalendarScreen = nil + mockDailyLogScreen = nil + mockCardsScreen = nil + super.tearDown() + } + + func test_DateSelection_UpdatesDailyLogAndArticles() throws { + // given + var dateComponents = DateComponents() + dateComponents.day = 1 + dateComponents.month = 1 + dateComponents.year = 2000 + guard let initialDate = Calendar.current.date(from: dateComponents) else { return XCTFail("could not create date") } + try setUp(date: initialDate) + + // then + XCTAssertEqual(mockCardsScreen.mockStore?.dispatchedActions, [.updateForDate(date: initialDate)]) + XCTAssertEqual(mockDailyLogScreen.mockStore?.dispatchedActions, [.updateForDate(initialDate)]) + + // when + guard let nextDate = Calendar.current.date(byAdding: .day, value: 1, to: initialDate) else { return XCTFail("could not create next date") } + mockCalendarScreen.mockStore?.mockUpdate { $0.selectedDate = nextDate } + + // then + XCTAssertEqual(mockCardsScreen.mockStore?.dispatchedActions, [.updateForDate(date: initialDate), + .updateForDate(date: nextDate)]) + + XCTAssertEqual(mockDailyLogScreen.mockStore?.dispatchedActions, [.updateForDate(initialDate), + .updateForDate(nextDate)]) + } + +} diff --git a/Example/Example_Tests/OnboardingTests.swift b/Example/Example_Tests/OnboardingTests.swift new file mode 100644 index 0000000..5819fdf --- /dev/null +++ b/Example/Example_Tests/OnboardingTests.swift @@ -0,0 +1,138 @@ +// +//===----------------------------------------------------------------------===// +// +// OnboardingTests.swift +// +// Created by Trevor Beasty on 7/16/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_Example + +class OnboardingTests: FlowTestCase { + + var flow: OnboardingFlow! + + override func setUp() { + super.setUp() + flow = OnboardingFlow() + } + + override func tearDown() { + flow = nil + super.tearDown() + } + + func test_FullSequence() throws { + // when / then - start + let welcomeController: TextViewController = try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) }, + onViewDidAppear: { welcomeController in + XCTAssertEqual(welcomeController.store.state, TextScreenModule.State(title: "Welcome", + description: "We just need a little info to get you going...", + buttons: ["Next"])) + }) + + // when / then - tap button 0 on welcome controller + let nameController: EnterNameViewController = try assertPushed( + after: welcomeController, + when: { welcomeController.dispatchAction(.didTapButton(0)) }, + onViewDidAppear: { _, nameController in + XCTAssertEqual(nameController.store.state, EnterNameScreenModule.State(title: "Enter you name", + name: "")) + XCTAssertFalse(nameController.store.state.canProceed) + }) + + // when / then - change name on name controller + nameController.dispatchAction(.didChange("Blob")) + + XCTAssertEqual(nameController.store.state.name, "Blob") + XCTAssertTrue(nameController.store.state.canProceed) + + // when / then - tap next on name controller + let counterController: CounterViewController = try assertPushed( + after: nameController, + when: { nameController.store.dispatchAction(.didTapNext) }, + onViewDidAppear: { _, counterController in + XCTAssertEqual(counterController.store.state, CounterScreenModule.State(title: "Rollover points", + counter: 0, + style: .light)) + }) + + // when / then - increment on counter controller + (0..<3).forEach { _ in + counterController.store.dispatchAction(.didTapIncrement) + } + + XCTAssertEqual(counterController.store.state.counter, 3) + + // when / then - tap next on counter controller + let notificationsController: TextViewController = try assertPushed( + after: counterController, + when: { counterController.store.dispatchAction(.didTapNext) }, + onViewDidAppear: { _, notificationsController in + XCTAssertEqual(notificationsController.store.state, TextScreenModule.State(title: "Notifications", + description: "Enable Notifications for the smoothest experience...", + buttons: ["Next", "Start Over"])) + }) + + // when / then - tap button 0 on notifications controller + let doneController: TextViewController = try assertPushed( + after: notificationsController, + when: { notificationsController.store.dispatchAction(.didTapButton(0)) }, + onViewDidAppear: { _, doneController in + XCTAssertEqual(doneController.store.state, TextScreenModule.State(title: "All set", + description: "Ok, Blob, let's get started!\n\nBy completing this onboarding, you earned 3 rollover points!", + buttons: ["Finish"])) + }) + + // output + flow.assert(when: { doneController.store.dispatchAction(.didTapButton(0)) }, + outputs: .didFinish) + } + + func test_Restart_ClearsName() throws { + // flow start + let welcomeController: TextViewController = try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) } + ) + + // press next on welcome screen + let nameController0: EnterNameViewController = try assertPushed( + after: welcomeController, + when: { welcomeController.store.dispatchAction(.didTapButton(0)) } + ) + + // name screen - edit name then restart + nameController0.store.dispatchAction(.didChange("Blob")) + + try assertPopped( + from: nameController0, + to: welcomeController, + when: { nameController0.store.dispatchAction(.didTapRestart) } + ) + + // proceed to name screen from welcome screen + let nameController1: EnterNameViewController = try assertPushed( + after: welcomeController, + when: { welcomeController.store.dispatchAction(.didTapButton(0)) } + ) + + // name should be empty following 'restart' event + XCTAssertEqual(nameController1.store.state.name, "") + } + +} diff --git a/Example/Example_Tests/SearchAndTrackFlowTests.swift b/Example/Example_Tests/SearchAndTrackFlowTests.swift new file mode 100644 index 0000000..d148019 --- /dev/null +++ b/Example/Example_Tests/SearchAndTrackFlowTests.swift @@ -0,0 +1,310 @@ +// +//===----------------------------------------------------------------------===// +// +// SearchAndTrackFlowTests.swift +// +// Created by Trevor Beasty on 7/19/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_Example + +// 3 different testing styles are shown here. These styles use varying amounts of mocking and thus +// have varying testing "areas" - the greater the mocking, the smaller the testing area, and vice versa. + +class SearchAndTrackFlowTestsWithDeepInjection: FlowTestCase { + + typealias ViewState = SearchViewModule.ViewState + + var flow: SearchAndTrackFlow! + var searchRequests: [(searchText: String?, completion: (Result<[Food], Error>) -> Void)] = [] + + override func setUp() { + super.setUp() + flow = SearchAndTrackFlow() + flow.searchScreenFactory = screenFactory(configure: { searchStore in + searchStore.getSearchResults = { searchText, completion in + self.searchRequests.append((searchText, completion)) + } + }) + } + + override func tearDown() { + flow = nil + super.tearDown() + } + + func test_InitialFetch_Success() throws { + + let searchController: SearchViewController = try assertPresentation( + on: rootController, + when: { flow.start(with: presented(on: rootController)) }, + onViewDidLoad: { searchController in + let initialSearchState = SearchViewModule.ViewState(isLoading: false, error: nil, items: [], searchText: nil) + XCTAssertEqual(searchController.store.state, initialSearchState) + XCTAssertTrue(searchRequests.isEmpty) + }, + onViewWillAppear: { searchController in + let loadingState = SearchViewModule.ViewState(isLoading: true, error: nil, items: [], searchText: nil) + XCTAssertEqual(searchController.store.state, loadingState) + XCTAssertEqual(self.searchRequests.count, 1) + XCTAssertEqual(self.searchRequests[0].searchText, nil) + }) + + // when - request completion - success + let food = Food(name: "", points: 0, description: "") + searchRequests[0].completion(.success([food])) + + // then + let searchResultState = SearchViewModule.ViewState(isLoading: false, error: nil, items: [food], searchText: nil) + XCTAssertEqual(searchController.store.state, searchResultState) + } + + func test_InitialFetch_Failure() throws { + + let searchController: SearchViewController = try assertPresentation( + on: rootController, + when: { flow.start(with: presented(on: rootController)) }, + onViewDidLoad: { _ in + XCTAssertTrue(self.searchRequests.isEmpty) + }, + onViewWillAppear: { _ in + XCTAssertEqual(self.searchRequests.count, 1) + }) + + // when - request completion - failure + let error = NSError(domain: "", code: 0, userInfo: nil) + searchRequests[0].completion(.failure(error)) + + // then + let searchErrorState = SearchViewModule.ViewState(isLoading: false, error: "Something went wrong", items: [], searchText: nil) + XCTAssertEqual(searchController.store.state, searchErrorState) + } + + func test_Search_Success() throws { + // start flow + let searchController: SearchViewController = try assertPresentation(on: rootController, when: { flow.start(with: presented(on: rootController)) }) + + // when - search text event + searchRequests = [] + searchController.store.dispatchAction(.didUpdateSearchText("a")) + + // then + let searchPendingState = ViewState(isLoading: true, error: nil, items: [], searchText: "a") + XCTAssertEqual(searchController.store.state, searchPendingState) + XCTAssertEqual(searchRequests.count, 1) + XCTAssertEqual(searchRequests[0].searchText, "a") + + // when - request success + let food = Food(name: "", points: 0, description: "") + searchRequests[0].completion(.success([food])) + + // then + let searchResultState = SearchViewModule.ViewState(isLoading: false, error: nil, items: [food], searchText: "a") + XCTAssertEqual(searchController.store.state, searchResultState) + } + + func test_Selection_ShowsDetail_Track_ShowsSearch() throws { + // start flow + let searchController: SearchViewController = try assertPresentation( + on: rootController, + when: { flow.start(with: presented(on: rootController)) } + ) + + // search request success + let food = Food(name: "a", points: 0, description: "b") + self.searchRequests[0].completion(.success([food])) + + let detailController: TextViewController = try assertPresentation( + on: searchController, + when: { + // search screen - food selection + searchController.store.dispatchAction(.didSelectItem(idx: 0)) + }, + onViewDidLoad: { detailController in + let detailState = TextViewController.ViewState(title: "a", description: "0 Points\n\nb", buttons: ["Track"]) + XCTAssertEqual(detailController.store.state, detailState) + }) + + // detail screen - track food + try assertDismissal( + from: detailController, + to: searchController, + when: { detailController.store.dispatchAction(.didTapButton(0)) } + ) + } + +} + +class SearchAndTrackFlowTestsWithShallowInjection: FlowTestCase { + + typealias ViewState = SearchViewModule.ViewState + + var flow: SearchAndTrackFlow! + var mockSearchScreen: MockScreen>! + var mockFoodDetailScreen: MockScreen! + + override func setUp() { + super.setUp() + flow = SearchAndTrackFlow() + mockSearchScreen = MockScreen>() + mockFoodDetailScreen = MockScreen() + + flow.searchScreenFactory = mockScreenFactory(mockScreen: mockSearchScreen) + flow.foodDetailScreenFactory = mockScreenFactory(mockScreen: mockFoodDetailScreen) + } + + override func tearDown() { + flow = nil + mockSearchScreen = nil + mockFoodDetailScreen = nil + super.tearDown() + } + + func test_Selection_ShowsDetail_Track_ShowsSearch() throws { + let searchController: UIViewController = try assertPresentation( + on: rootController, + when: { flow.start(with: presented(on: rootController)) } + ) + XCTAssertEqual(searchController, mockSearchScreen.mockController) + + let food = Food(name: "a", points: 0, description: "b") + let foodDetailController: UIViewController = try assertPresentation( + on: searchController, + when: { mockSearchScreen.mockStore?.dispatchMockOutput(.didSelectItem(food)) } + ) + XCTAssertEqual(foodDetailController, mockFoodDetailScreen.mockController) + let detailState = TextViewController.ViewState(title: "a", description: "0 Points\n\nb", buttons: ["Track"]) + XCTAssertEqual(mockFoodDetailScreen.mockStore?.state, detailState) + + try assertDismissal( + from: foodDetailController, + to: searchController, + when: { mockFoodDetailScreen.mockStore?.dispatchMockOutput(.didTapButton(0)) } + ) + } + +} + +class SearchAndTrackFlowTestsWithMockController: FlowTestCase { + + typealias SearchState = SearchScreenModule.State + + var flow: SearchAndTrackFlow! + var searchRequests: [(searchText: String?, completion: (Result<[Food], Error>) -> Void)] = [] + + override func setUp() { + super.setUp() + flow = SearchAndTrackFlow() + + flow.searchScreenFactory = mockControllerScreenFactory(configure: { searchStore in + searchStore.getSearchResults = { searchText, completion in + self.searchRequests.append((searchText, completion)) + } + }) + } + + override func tearDown() { + flow = nil + super.tearDown() + } + + private func start() throws -> SearchScreenModule.MockController { + return try assertPresentation( + on: rootController, + when: { flow.start(with: presented(on: rootController)) } + ) + } + + func test_InitialFetch_Success() throws { + let searchController = try start() + + let initialSearchState = SearchState(searchText: nil, items: [], phase: .idle, viewDidAppear: false) + XCTAssertEqual(searchController.store.state, initialSearchState) + XCTAssertTrue(searchRequests.isEmpty) + + // viewWillAppear + searchController.store.dispatchAction(.viewWillAppear) + + let loadingState = SearchState(searchText: nil, items: [], phase: .searching, viewDidAppear: true) + XCTAssertEqual(searchController.store.state, loadingState) + XCTAssertEqual(self.searchRequests.count, 1) + XCTAssertEqual(self.searchRequests[0].searchText, nil) + + // request completion + let food = Food(name: "", points: 0, description: "") + searchRequests[0].completion(.success([food])) + + let searchResultState = SearchState(searchText: nil, items: [food], phase: .idle, viewDidAppear: true) + XCTAssertEqual(searchController.store.state, searchResultState) + } + + func test_InitialFetch_Failure() throws { + let searchController = try start() + + // request completion + let error = NSError(domain: "", code: 0, userInfo: nil) + searchController.store.dispatchAction(.viewWillAppear) + searchRequests[0].completion(.failure(error)) + + let searchErrorState = SearchState(searchText: nil, items: [], phase: .error(message: "Something went wrong"), viewDidAppear: true) + XCTAssertEqual(searchController.store.state, searchErrorState) + } + + func test_Search_Success() throws { + // start flow + let searchController = try start() + + // search text event + searchController.store.dispatchAction(.didUpdateSearchText("a")) + + let searchPendingState = SearchState(searchText: "a", items: [], phase: .searching, viewDidAppear: false) + XCTAssertEqual(searchController.store.state, searchPendingState) + XCTAssertEqual(searchRequests.count, 1) + XCTAssertEqual(searchRequests[0].searchText, "a") + + // request success + let food = Food(name: "", points: 0, description: "") + searchRequests[0].completion(.success([food])) + + let searchResultState = SearchState(searchText: "a", items: [food], phase: .idle, viewDidAppear: false) + XCTAssertEqual(searchController.store.state, searchResultState) + } + + func test_Selection_ShowsDetail_Track_ShowsSearch() throws { + // start flow + let searchController = try start() + + // search request success + let food = Food(name: "a", points: 0, description: "b") + searchController.store.dispatchAction(.viewWillAppear) + self.searchRequests[0].completion(.success([food])) + + let detailController: TextViewController = try assertPresentation( + on: searchController, + when: { searchController.store.dispatchAction(.didSelectItem(idx: 0)) } + ) + + let detailState = TextViewController.ViewState(title: "a", description: "0 Points\n\nb", buttons: ["Track"]) + XCTAssertEqual(detailController.store.state, detailState) + + // detail screen - track food + try assertDismissal( + from: detailController, + to: searchController, + when: { detailController.store.dispatchAction(.didTapButton(0)) } + ) + } + +} diff --git a/Example/Example_Tests/SearchStoreTests_FunctionalStyle1.swift b/Example/Example_Tests/SearchStoreTests_FunctionalStyle1.swift new file mode 100644 index 0000000..a7ca924 --- /dev/null +++ b/Example/Example_Tests/SearchStoreTests_FunctionalStyle1.swift @@ -0,0 +1,125 @@ +// +//===----------------------------------------------------------------------===// +// +// SearchStoreTests_FunctionalStyle1.swift +// +// Created by Trevor Beasty on 9/3/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import Lasso_Example +import LassoTestUtilities + +/// These test represent the preferred, more concise usage of the functional style. +/// This style should be used when State and Outputs are Equatable. + +/// You must conform to LassoStoreTesting +class SearchStoreTestsFunctionalStyle1: XCTestCase, LassoStoreTesting { + /// Be sure to explicitly declare the Store type to avoid compiler issues. + typealias Store = SearchStore + + struct Item: SearchListRepresentable, Equatable { + let searchListTitle: String + } + + var searchRequests = [(query: String?, completion: (Result<[Item], Error>) -> Void)]() + + /// This is the factory for all your test cases. Because is accesses 'self', it must be lazy. + lazy var test = TestFactory>( + /// Provide some default initial state. + initialState: State(searchText: nil, + items: [], + phase: .idle, + viewDidAppear: false), + /// Set up the store. This is your chance to mock store dependencies, like network requests. + setUpStore: { (store: SearchStore) -> Void in + store.getSearchResults = { self.searchRequests.append(($0, $1)) } + }, + /// Perform your typical teardown, resetting mocked values. + tearDown: { + self.searchRequests = [] + }) + + func test_InitialFetch() { + let items = [Item(searchListTitle: "a")] + + // loading state with pending request + let loading = test + /// Specify the values of the initial state that matter for this test + .given({ initialState in + initialState.viewDidAppear = false + initialState.searchText = nil + }) + .when(actions(.viewWillAppear)) + /// Make assertions relevant to the prior when statement. + .then( + /// 'update' describes the difference between the Store's state before and after the 'when' event + update { state in + state.phase = .searching + state.viewDidAppear = true + }, + /// 'sideEffects' describe expectations unrelated to the Store + sideEffects { + XCTAssertEqual(self.searchRequests.count, 1) + XCTAssertEqual(self.searchRequests[0].query, nil) + }) + + /// You may branch your test logic to reuse shared setup. Here, we want to test + /// both the success and failure of the network request. + + // successful request + loading + .when(sideEffects { + self.searchRequests[0].completion(.success(items)) + }) + /// 'singleUpdate' is more strict than 'update'. It requires that only 1 new State was produced. + .then(singleUpdate { state in + state.phase = .idle + state.items = items + }) + /// Because all statement execution is deferred, 'execute' must be called to trigger the test. + .execute() + + // request failure + loading + .when(sideEffects { + self.searchRequests[0].completion(.failure(NSError())) + }) + /// You can also provide State instances when making State assertions + .then( + state(State(searchText: nil, + items: [], + phase: .error(message: "Something went wrong"), + viewDidAppear: true))) + /// You may chain along as many 'when' and 'then' statements as you would like. + .when(actions(.didAcknowledgeError)) + .then(singleUpdate { state in + state.phase = .idle + }) + /// Don't forget to call execute!! + .execute() + } + + func test_Selection() { + let items = [Item(searchListTitle: "a")] + + test + .given({ + $0.items = items + }) + .when(actions(.didSelectItem(idx: 0))) + /// You can also make assertions about Outputs from the Store. + .then(outputs(.didSelectItem(items[0]))) + .execute() + } + +} diff --git a/Example/Example_Tests/SearchStoreTests_FunctionalStyle2.swift b/Example/Example_Tests/SearchStoreTests_FunctionalStyle2.swift new file mode 100644 index 0000000..8467b7b --- /dev/null +++ b/Example/Example_Tests/SearchStoreTests_FunctionalStyle2.swift @@ -0,0 +1,149 @@ +// +//===----------------------------------------------------------------------===// +// +// SearchStoreTests_FunctionalStyle2.swift +// +// Created by Trevor Beasty on 10/3/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import Lasso_Example +import LassoTestUtilities + +/// These test represent the more general functional style. +/// This style should be used when State and Output are not NOT Equatable. + +/// You must conform to LassoStoreTesting +class SearchStoreTestsFunctionalStyle2: XCTestCase, LassoStoreTesting { + /// Be sure to explicitly declare the Store type to avoid compiler issues. + typealias Store = SearchStore + + struct Item: SearchListRepresentable, Equatable { + let searchListTitle: String + } + + var searchRequests = [(query: String?, completion: (Result<[Item], Error>) -> Void)]() + + /// This is the factory for all your test cases. Because is accesses 'self', it must be lazy. + lazy var test = TestFactory>( + /// Provide some default initial state. + initialState: State(searchText: nil, + items: [], + phase: .idle, + viewDidAppear: false), + /// Set up the store. This is your chance to mock store dependencies, like network requests. + setUpStore: { (store: SearchStore) -> Void in + store.getSearchResults = { self.searchRequests.append(($0, $1)) } + }, + /// Perform your typical teardown, resetting mocked values. + tearDown: { + self.searchRequests = [] + }) + + func test_InitialFetch() { + let items = [Item(searchListTitle: "a")] + + // loading state with pending request + let loading = test + .given({ + $0.viewDidAppear = false + $0.searchText = nil + }) + /// This is the most general form of 'when' statement you can make. You can dispatch Action's via the single argument. + .when({ dispatchAction in + dispatchAction(.viewWillAppear) + }) + /// This is the most general form of 'then' assertion you can make. + /// The single argument contains the emitted values. It has the members: + /// previousState: State + /// states: [State] + /// outputs: [Output] + .then(assert { emitted in + XCTAssertEqual(emitted.states.last, State(searchText: nil, items: [], phase: .searching, viewDidAppear: true)) + XCTAssertEqual(self.searchRequests.count, 1) + XCTAssertEqual(self.searchRequests[0].query, nil) + }) + + /// You may branch your test logic to reuse shared setup. Here, we want to test + /// both the success and failure of the network request. + + // successful request + loading + /// You can ignore 'dispatchAction' when performing just side effects. + .when({ _ in + self.searchRequests[0].completion(.success(items)) + }) + .then(assert { emitted in + XCTAssertEqual(emitted.states.last, State(searchText: nil, items: items, phase: .idle, viewDidAppear: true)) + }) + .execute() + + // request failure + loading + .when({ _ in + self.searchRequests[0].completion(.failure(NSError())) + }) + .then(assert { + XCTAssertEqual($0.states, [State(searchText: nil, items: [], phase: .error(message: "Something went wrong"), viewDidAppear: true)]) + }) + .when({ dispatchAction in dispatchAction(.didAcknowledgeError) }) + .then(assert { emitted in + XCTAssertEqual(emitted.states, [State(searchText: nil, items: [], phase: .idle, viewDidAppear: true)]) + }) + .execute() + } + + /// This is a repeat of the above test's success case shown in a different way. + func test_InitialFetchSuccess_Condensed() { + let items = [Item(searchListTitle: "a")] + + test + .given({ + $0.viewDidAppear = false + $0.searchText = nil + }) + /// If you need to both dispatch Action's and invoke side effects, you need to open up a closure. + .when({ dispatchAction in + dispatchAction(.viewWillAppear) + self.searchRequests[0].completion(.success(items)) + }) + /// You can make a large variety of assertions inside the 'assert' closure. + .then(assert { (emitted) in + let expectedStates = [ + State(searchText: nil, items: [], phase: .searching, viewDidAppear: true), + State(searchText: nil, items: items, phase: .idle, viewDidAppear: true) + ] + XCTAssertEqual(emitted.states, expectedStates) + + XCTAssertTrue(emitted.outputs.isEmpty) + + XCTAssertEqual(self.searchRequests.count, 1) + XCTAssertEqual(self.searchRequests[0].query, nil) + }) + .execute() + } + + func test_Selection() { + let items = [Item(searchListTitle: "a")] + + test + .given({ + $0.items = items + }) + .when(actions(.didSelectItem(idx: 0))) + .then(assert { emitted in + XCTAssertEqual(emitted.outputs, [.didSelectItem(items[0])]) + }) + .execute() + } + +} diff --git a/Example/Example_Tests/SearchStoreTests_LassoStoreTestCase.swift b/Example/Example_Tests/SearchStoreTests_LassoStoreTestCase.swift new file mode 100644 index 0000000..733ac0f --- /dev/null +++ b/Example/Example_Tests/SearchStoreTests_LassoStoreTestCase.swift @@ -0,0 +1,138 @@ +// +//===----------------------------------------------------------------------===// +// +// SearchStoreTests_LassoStoreTestCase.swift +// +// Created by Steven Grosmark on 9/19/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import LassoTestUtilities +@testable import Lasso_Example + +/// +/// Example of Store unit testing using `LassoStoreTestCase` and `TestableStore`. +/// + +struct Item: SearchListRepresentable, Equatable { + let searchListTitle: String +} + +enum MockError: Error { + case failed +} + +// MARK: - Lifecycle tests + +/// Test case that tests the normal lifecycle: +/// viewDidAppear, +/// viewDidAppear + successful request, +/// viewDidAppear + failed request +class SearchStoreLifecycleTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore>() + + var searchRequests = [(query: String?, completion: (Result<[Item], Error>) -> Void)]() + + override func setUp() { + super.setUp() + + // create the store + store = SearchStore(with: State(searchText: nil, items: [], phase: .idle, viewDidAppear: false)) + + // setup initial state - a.k.a. the "given" for each test + store.getSearchResults = { self.searchRequests.append(($0, $1)) } + searchRequests = [] + store.dispatchAction(.viewWillAppear) + syncState() + } + + func test_InitialFetchOnViewWillAppear() { + // validate initial state + XCTAssertStateEquals(updatedMarker { state in + state.phase = .searching + state.viewDidAppear = true + }) + XCTAssertEqual(searchRequests.count, 1) + XCTAssertEqual(searchRequests[0].query, nil) + } + + func test_RequestSuccess() { + // when - search service completes with success + let items = [Item(searchListTitle: "a")] + searchRequests[0].completion(.success(items)) + + // then + XCTAssertStateEquals(updatedMarker { state in + state.phase = .idle + state.items = items + }) + } + + func test_RequestFailure() { + // when - search service completes with failure + searchRequests[0].completion(.failure(MockError.failed)) + + // then + XCTAssertStateEquals(updatedMarker { state in + state.phase = .error(message: "Something went wrong") + }) + + // when - user acknowledges error + store.dispatchAction(.didAcknowledgeError) + + // then + XCTAssertStateEquals(updatedMarker { state in + state.phase = .idle + }) + } + +} + +// MARK: - Selection tests + +/// Test case that tests selecting an item. +/// +/// Create a unique test case subclass when you want to run +/// a number of tests that all have the same "given" state. +class SearchStoreSelectionTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore>() + let item = Item(searchListTitle: "a") + + override func setUp() { + super.setUp() + + // create the store with an initial state of being after a successful search + // i.e., given: store has completed a successful search + store = SearchStore(with: State(searchText: "a", items: [item], phase: .idle, viewDidAppear: true)) + } + + func test_Selection_Valid() { + // when + store.dispatchAction(.didSelectItem(idx: 0)) + + // then + XCTAssertLastOutput(.didSelectItem(item)) // assert on the last dispatched output, or + XCTAssertOutputs([.didSelectItem(item)]) // assert on the array of outputs + } + + func test_Selection_InvalidIndex() { + // when + store.dispatchAction(.didSelectItem(idx: -1)) + store.dispatchAction(.didSelectItem(idx: 999)) + + // then + XCTAssertOutputs([]) + } + +} diff --git a/Example/Example_Tests/SearchStoreTests_XCTestCase.swift b/Example/Example_Tests/SearchStoreTests_XCTestCase.swift new file mode 100644 index 0000000..c43efc2 --- /dev/null +++ b/Example/Example_Tests/SearchStoreTests_XCTestCase.swift @@ -0,0 +1,98 @@ +// +//===----------------------------------------------------------------------===// +// +// SearchStoreTests_XCTestCase.swift +// +// Created by Steven Grosmark on 9/19/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import Lasso_Example + +/// +/// Example of Store unit testing w/no support utilities. +/// + +class SearchStoreAltTests: XCTestCase { + + struct Item: SearchListRepresentable, Equatable { + let searchListTitle: String + } + + typealias Module = SearchScreenModule + typealias State = Module.State + + var searchRequests = [(query: String?, completion: (Result<[Item], Error>) -> Void)]() + + var store: SearchStore! + + enum MockError: Error { + case failed + } + + override func setUp() { + super.setUp() + + store = SearchStore(with: State(searchText: nil, items: [], phase: .idle, viewDidAppear: false)) + store.getSearchResults = { self.searchRequests.append(($0, $1)) } + searchRequests = [] + store.dispatchAction(.viewWillAppear) + } + + func test_InitialFetchOnViewWillAppear() { + // TEST: loading state with pending request + XCTAssertEqual(store.state, State(searchText: nil, items: [], phase: .searching, viewDidAppear: true)) + XCTAssertEqual(self.searchRequests.count, 1) + XCTAssertEqual(self.searchRequests[0].query, nil) + } + + func test_RequestSuccess() { + + // when + let items = [Item(searchListTitle: "a")] + searchRequests[0].completion(.success(items)) + + // then + XCTAssertEqual(store.state, State(searchText: nil, items: items, phase: .idle, viewDidAppear: true)) + } + + func test_RequestFailure() { + + // when + searchRequests[0].completion(.failure(MockError.failed)) + + // then + XCTAssertEqual(store.state, State(searchText: nil, items: [], phase: .error(message: "Something went wrong"), viewDidAppear: true)) + + // when + store.dispatchAction(.didAcknowledgeError) + + // then + XCTAssertEqual(store.state, State(searchText: nil, items: [], phase: .idle, viewDidAppear: true)) + } + + func test_Selection() { + + // given + let items = [Item(searchListTitle: "a")] + searchRequests[0].completion(.success(items)) + var outputs = [Module.Output]() + store.observeOutput { outputs.append($0) } + + // when + store.dispatchAction(.didSelectItem(idx: 0)) + + // then + XCTAssertEqual(outputs, [.didSelectItem(items[0])]) + } + +} diff --git a/Example/Example_Tests/Signup/SignupFormStoreTests.swift b/Example/Example_Tests/Signup/SignupFormStoreTests.swift new file mode 100644 index 0000000..ecdf1a2 --- /dev/null +++ b/Example/Example_Tests/Signup/SignupFormStoreTests.swift @@ -0,0 +1,176 @@ +// +//===----------------------------------------------------------------------===// +// +// SignupFormStoreTests.swift +// +// Created by Steven Grosmark on 10/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import LassoTestUtilities +@testable import Lasso_Example + +class SignupFormStoreTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + + typealias Validated = SignupForm.Validated + + override func setUp() { + super.setUp() + store = Store(with: State()) + } + + func test_DefaultInitialState() { + // test the default State initializer - a.k.a. the "given" state for all the other states + for field in SignupForm.Field.allCases { + XCTAssertEqual(store.state[keyPath: field.stateKey], Validated(value: "", error: nil)) + } + XCTAssertEqual(store.state.phase, .idle) + XCTAssertEqual(store.state.formIsValid, false) + } + + func test_Invalid() { + for field in SignupForm.Field.allCases { + for value in field.invalidEntries { + + // when - update field with invalid value + store.dispatchAction(.didUpdate(field, value)) + + // then - field should be marked as invalid + XCTAssertStateEquals(updatedMarker { state in + state[keyPath: field.stateKey] = Validated(value: value, error: "Invalid") + }) + } + } + } + + func test_Valid() { + for field in SignupForm.Field.allCases { + for value in field.validEntries { + + // when - update field with valid value + store.dispatchAction(.didUpdate(field, value)) + + // then - field should be marked as valid, + // and if this is the password field, the form should now be valid + XCTAssertStateEquals(updatedMarker { state in + state[keyPath: field.stateKey] = Validated(value: value, error: nil) + state.formIsValid = field == .password + }) + + // when - update field with valid value surrounded by whitespace + store.dispatchAction(.didUpdate(field, " \t\n\(value) \t\n")) + + // then - whitespace should be stripped, & field marked as valid + XCTAssertStateEquals(updatedMarker { state in + state[keyPath: field.stateKey] = Validated(value: value, error: nil) + }) + } + } + } + + func test_Submit_Invalid() { + // when - tap signup w/invalid form + store.dispatchAction(.didTapSignup) + + // then - no outputs or state change + XCTAssertOutputs([]) + XCTAssertStateEquals(markerState) + } + +} + +class SignupFormStoreSubmitTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + + let validFields = SignupForm.Output.Fields(name: "billie", email: "b@bb.ie", username: "billie", password: "billie") + + private var mockService = MockSignupService() + + override func setUp() { + super.setUp() + store = Store(with: State(from: validFields)) + mockService = MockSignupService() + store.signupService = mockService + } + + func test_Submit() { + // given + XCTAssertEqual(store.state.formIsValid, true) + XCTAssertEqual(store.state.completedFields, validFields) + + // when - tap signup w/valid form + store.dispatchAction(.didTapSignup) + + // then - form should be processing, no output yet + XCTAssertStateEquals(updatedMarker { state in + state.phase = .working + }) + XCTAssertNotNil(mockService.completion) + XCTAssertOutputs([]) + + // when - call completion + mockService.completion?(.success("test")) + + // then - output with validated fields + XCTAssertOutputs([.didSignup(validFields)]) + } + + func test_NoDoubleSubmit() { + // given - valid form has been submitted + store.update { state in + state.phase = .working + } + syncState() + + // when - tap signup w/valid form, already being submitted + store.dispatchAction(.didTapSignup) + + // then - no outputs or state change, or completion handler + XCTAssertOutputs([]) + XCTAssertStateEquals(markerState) + XCTAssertNil(mockService.completion) + } + +} + +extension SignupForm.Field { + + var invalidEntries: [String] { + switch self { + case .name: return ["a", "aa", String(repeating: "a", count: 100)] + case .email: return ["as@as.", "asd.com", "jhg@.com", "jhg"] + case .username: return ["a", "aa", String(repeating: "a", count: 100), "aaa*****"] + case .password: return ["a", "aa", String(repeating: "a", count: 100)] + } + } + + var validEntries: [String] { + switch self { + case .name: return ["abc", "123", String(repeating: "a", count: 64)] + case .email: return ["pat@hope.abc", "Gavin@hoolie.io"] + case .username: return ["abc123_", "__3"] + case .password: return ["abc", "123", "!@#$%^&*()))+_", String(repeating: "a", count: 64)] + } + } + +} + +private final class MockSignupService: SignupServiceProtocol { + var completion: ((Result) -> Void)? + + func signup(_ fields: SignupForm.Output.Fields, completion: @escaping (Result) -> Void) { + self.completion = completion + } +} diff --git a/Example/Example_Tests/Signup/SignupIntroStoreTests.swift b/Example/Example_Tests/Signup/SignupIntroStoreTests.swift new file mode 100644 index 0000000..8c98bc0 --- /dev/null +++ b/Example/Example_Tests/Signup/SignupIntroStoreTests.swift @@ -0,0 +1,37 @@ +// +//===----------------------------------------------------------------------===// +// +// SignupIntroStoreTests.swift +// +// Created by Steven Grosmark on 10/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_Example + +class SignupIntroStoreTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + + override func setUp() { + super.setUp() + store = Store(with: EmptyState()) + } + + func test_TheOneAndOnlyButton() { + store.dispatchAction(.didTapNext) + XCTAssertOutputs([.didTapNext]) + } + +} diff --git a/Example/Example_Tests/SurveyFlowTests.swift b/Example/Example_Tests/SurveyFlowTests.swift new file mode 100644 index 0000000..c6dc9f7 --- /dev/null +++ b/Example/Example_Tests/SurveyFlowTests.swift @@ -0,0 +1,100 @@ +// +//===----------------------------------------------------------------------===// +// +// SurveyFlowTests.swift +// +// Created by Trevor Beasty on 7/16/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_Example + +class SurveyFlowTests: FlowTestCase { + + var flow: SurveyFlow! + + override func tearDown() { + flow = nil + super.tearDown() + } + + private func setUp(questions: [String]) { + flow = SurveyFlow(questions: questions) + } + + func test_NoPrize() throws { + typealias ViewState = MakeListViewModule.ViewState + + // setup + setUp(questions: ["question0"]) + flow.getPrize = { _, completion in completion(nil) } + let navigationController = UINavigationController() + + // start flow + let question0Controller: MakeListViewController = try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) } + ) + + XCTAssertEqual(question0Controller.store.state, ViewState(header: "question0", + placeholder: "", + proposed: nil, + submitted: [])) + + // question screen - answering and submitting + question0Controller.store.dispatchAction(.didEditProposed("foo")) + question0Controller.store.dispatchAction(.didPressAdd) + + XCTAssertEqual(question0Controller.store.state.proposed, nil) + XCTAssertEqual(question0Controller.store.state.submitted, ["foo"]) + + flow.assert(when: { question0Controller.store.dispatchAction(.didPressSubmit) }, + outputs: .didFinish(responses: ["question0": ["foo"] ], prize: nil)) + } + + func test_Prize() throws { + typealias State = TextScreenModule.State + + // setup + setUp(questions: ["question0"]) + let prize = SurveyFlowModule.Prize(name: "foo", + description: "bar", + dollarValue: 1) + + flow.getPrize = { _, completion in + completion(prize) + } + + // start flow + let question0Controller: MakeListViewController = try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) } + ) + + // question screen - submit + let prizeController: TextViewController = try assertPushed( + after: question0Controller, + when: { question0Controller.store.dispatchAction(.didPressSubmit) } + ) + + XCTAssertEqual(prizeController.store.state, State(title: "You won a foo!!", + description: "bar\n\nA $1 value!!", + buttons: ["OMG!!"])) + + // prize screen - press button + flow.assert(when: { prizeController.store.dispatchAction(.didTapButton(0)) }, + outputs: .didFinish(responses: ["question0": []], prize: prize)) + } + +} diff --git a/Example/Lasso.xcodeproj/project.pbxproj b/Example/Lasso.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b225394 --- /dev/null +++ b/Example/Lasso.xcodeproj/project.pbxproj @@ -0,0 +1,1396 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 36653CE23A04A3982474C23A /* Pods_Lasso_Example_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BE369290A02520225FECA3B /* Pods_Lasso_Example_Tests.framework */; }; + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; + 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; + 7917711A22BB1545007BCFBA /* SimpleCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7917711922BB1545007BCFBA /* SimpleCounter.swift */; }; + 7917711C22BB16CC007BCFBA /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7917711B22BB16CC007BCFBA /* Welcome.swift */; }; + 7932794423A1291200654D8A /* Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932794323A1291200654D8A /* Login.swift */; }; + 7932794623A12A0D00654D8A /* LoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932794523A12A0D00654D8A /* LoginService.swift */; }; + 7932794823A12C8B00654D8A /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932794723A12C8B00654D8A /* LoginViewController.swift */; }; + 7932794B23A12D9D00654D8A /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932794A23A12D9D00654D8A /* Result.swift */; }; + 7932794E23A12DF900654D8A /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932794D23A12DF900654D8A /* UITableView.swift */; }; + 7932795023A12E2700654D8A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932794F23A12E2700654D8A /* UIButton.swift */; }; + 7932795223A12E4100654D8A /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795123A12E4100654D8A /* UIView.swift */; }; + 7932795423A12E6500654D8A /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795323A12E6500654D8A /* UIImage.swift */; }; + 7932795623A12E9A00654D8A /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795523A12E9A00654D8A /* UILabel.swift */; }; + 7932795823A12F3A00654D8A /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795723A12F3A00654D8A /* UITextField.swift */; }; + 7932795A23A12FE700654D8A /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795923A12FE700654D8A /* UIActivityIndicatorView.swift */; }; + 7932795C23A134A500654D8A /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795B23A134A500654D8A /* UIColor.swift */; }; + 7932795E23A13EEF00654D8A /* MockableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795D23A13EEF00654D8A /* MockableTests.swift */; }; + 7932796023A1448200654D8A /* MockableExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932795F23A1448200654D8A /* MockableExtensionTests.swift */; }; + 7932796223A146A800654D8A /* LoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7932796123A146A800654D8A /* LoginTests.swift */; }; + 7953AE4A229AEDCA00B9D63D /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7953AE49229AEDCA00B9D63D /* SplitView.swift */; }; + 796786592290EDF700CBE588 /* SignupForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796786582290EDF700CBE588 /* SignupForm.swift */; }; + 7967865B2292D1B100CBE588 /* FoodOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7967865A2292D1B100CBE588 /* FoodOnboarding.swift */; }; + 7967865D2292DB1E00CBE588 /* StrangeFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7967865C2292DB1E00CBE588 /* StrangeFlow.swift */; }; + 7978A6AE22A830A200B8B844 /* ActionDispatchableBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7978A6AD22A830A200B8B844 /* ActionDispatchableBindingTests.swift */; }; + 7978A6B222A864BF00B8B844 /* UIGestureRecognizer+SendActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7978A6B122A864BF00B8B844 /* UIGestureRecognizer+SendActions.swift */; }; + 7978A6B522A876E400B8B844 /* ObjectBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7978A6B422A876E400B8B844 /* ObjectBindingTests.swift */; }; + 7978A6BA22A9484E00B8B844 /* ChooseWindowTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7978A6B922A9484E00B8B844 /* ChooseWindowTransition.swift */; }; + 798C6CA6229491B500B52EFA /* RandomItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798C6CA5229491B500B52EFA /* RandomItems.swift */; }; + 79939B052333B92B00C55BA4 /* SearchStoreTests_XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79939B042333B92B00C55BA4 /* SearchStoreTests_XCTestCase.swift */; }; + 79C8FAF523DF623A00FB9579 /* ScreenCaptureStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C8FAF423DF623A00FB9579 /* ScreenCaptureStoreTests.swift */; }; + 79C9267422899B4F00F94A56 /* RandomStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9266422899B4F00F94A56 /* RandomStrings.swift */; }; + 79C9267522899B4F00F94A56 /* TabsFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9266722899B4F00F94A56 /* TabsFlow.swift */; }; + 79C9267622899B4F00F94A56 /* SearchModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9266822899B4F00F94A56 /* SearchModule.swift */; }; + 79C9267722899B4F00F94A56 /* SampleCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9266922899B4F00F94A56 /* SampleCatalog.swift */; }; + 79C9267822899B4F00F94A56 /* SampleCatalogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9266A22899B4F00F94A56 /* SampleCatalogViewController.swift */; }; + 79C9267922899B4F00F94A56 /* SampleCatalogFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9266B22899B4F00F94A56 /* SampleCatalogFlow.swift */; }; + 79C9267B22899B4F00F94A56 /* TextScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9266E22899B4F00F94A56 /* TextScreen.swift */; }; + 79C9267C22899B4F00F94A56 /* CounterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9267022899B4F00F94A56 /* CounterViewController.swift */; }; + 79C9267D22899B4F00F94A56 /* CounterScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9267122899B4F00F94A56 /* CounterScreen.swift */; }; + 79C9267E22899B4F00F94A56 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9267322899B4F00F94A56 /* Onboarding.swift */; }; + 79C926A32289E48000F94A56 /* EnterNameScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C926A22289E48000F94A56 /* EnterNameScreen.swift */; }; + 79CE63E323F2F854003FFBF4 /* VerboseLoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79CE63E223F2F854003FFBF4 /* VerboseLoggingTests.swift */; }; + 79E001B822906A1800944209 /* Signup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79E001B722906A1800944209 /* Signup.swift */; }; + 79E001BA22906F6F00944209 /* SignupIntro.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79E001B922906F6F00944209 /* SignupIntro.swift */; }; + 79E2808D2338EB490016A859 /* SearchStoreTests_LassoStoreTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79E2808C2338EB490016A859 /* SearchStoreTests_LassoStoreTestCase.swift */; }; + 9E8673C663191C820D8283F0 /* Pods_Lasso_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81ABDEA302F8D875DEB1958C /* Pods_Lasso_Example.framework */; }; + A2914468D21ED959531A7C0F /* Pods_Lasso_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55A962BA7AEB74577C6668F1 /* Pods_Lasso_Tests.framework */; }; + ADF94DC1962D2F14DC10C806 /* Pods_Lasso_TestUtilities_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E2C50A38EB52974125F2836 /* Pods_Lasso_TestUtilities_Tests.framework */; }; + B01C1F832323EA250084A149 /* StateObservationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B01C1F822323EA250084A149 /* StateObservationTests.swift */; }; + B0922A5C22AAE2A100EBE5DE /* UIKitBindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0922A5B22AAE2A100EBE5DE /* UIKitBindings.swift */; }; + B094CBCD23494C60004C5CBE /* CounterStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B094CBCC23494C60004C5CBE /* CounterStoreTests.swift */; }; + B094CBD0234951C8004C5CBE /* SignupIntroStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B094CBCF234951C8004C5CBE /* SignupIntroStoreTests.swift */; }; + B094CBD2234952A1004C5CBE /* SignupFormStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B094CBD1234952A1004C5CBE /* SignupFormStoreTests.swift */; }; + B0C37243229C0FAF00FB214D /* RandomItemFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C37242229C0FAF00FB214D /* RandomItemFlow.swift */; }; + E207AAE322F99F8B00D58979 /* SearchAndTrackFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E207AAE222F99F8A00D58979 /* SearchAndTrackFlow.swift */; }; + E207AAF822F9A58800D58979 /* SearchAndTrackFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E207AAF422F9A58800D58979 /* SearchAndTrackFlowTests.swift */; }; + E207AAF922F9A58800D58979 /* SurveyFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E207AAF522F9A58800D58979 /* SurveyFlowTests.swift */; }; + E207AAFB22F9A58800D58979 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E207AAF722F9A58800D58979 /* OnboardingTests.swift */; }; + E207AAFD22F9A59000D58979 /* FoodOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E207AAFC22F9A59000D58979 /* FoodOnboardingTests.swift */; }; + E215043F231EE4900027C4A9 /* SearchStoreTests_FunctionalStyle1.swift in Sources */ = {isa = PBXBuildFile; fileRef = E215043E231EE4900027C4A9 /* SearchStoreTests_FunctionalStyle1.swift */; }; + E23337E72358CA2100F26802 /* MyDayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E23337E62358CA2100F26802 /* MyDayTests.swift */; }; + E25C6E4D23195C450091BC72 /* ModalTestingTests+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25C6E4C23195C450091BC72 /* ModalTestingTests+FullScreen.swift */; }; + E25C6E4F23195CF40091BC72 /* ModalTestingTests+AnyModalPresentationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25C6E4E23195CF40091BC72 /* ModalTestingTests+AnyModalPresentationStyle.swift */; }; + E25C6E5123195DDA0091BC72 /* ModalTestingTests+SystemBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25C6E5023195DDA0091BC72 /* ModalTestingTests+SystemBehavior.swift */; }; + E274FB7C235A3612006832CC /* MyDayFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E274FB7B235A3612006832CC /* MyDayFlowTests.swift */; }; + E2772B6B22A7097E007A3D51 /* MakeListModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2772B6A22A7097E007A3D51 /* MakeListModule.swift */; }; + E2772B6D22A7FFE7007A3D51 /* SurveyModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2772B6C22A7FFE7007A3D51 /* SurveyModule.swift */; }; + E2772B6F22A80C93007A3D51 /* SurveyFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2772B6E22A80C93007A3D51 /* SurveyFlow.swift */; }; + E2AF6D4C235DF40A001ED33A /* MyDayFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AF6D4B235DF40A001ED33A /* MyDayFlow.swift */; }; + E2AF6D4E235DF443001ED33A /* MyDayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AF6D4D235DF442001ED33A /* MyDayController.swift */; }; + E2AF6D50235DF47A001ED33A /* CalendarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AF6D4F235DF47A001ED33A /* CalendarScreen.swift */; }; + E2AF6D52235DF49B001ED33A /* DailyLogScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AF6D51235DF49B001ED33A /* DailyLogScreen.swift */; }; + E2AF6D54235DF4C1001ED33A /* MyDayCardsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AF6D53235DF4C1001ED33A /* MyDayCardsScreen.swift */; }; + E2AF95CA23463BCD00A524AB /* SearchStoreTests_FunctionalStyle2.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AF95C923463BCD00A524AB /* SearchStoreTests_FunctionalStyle2.swift */; }; + E2DF79062396FB1200CF353B /* PageControllerFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DF79052396FB1200CF353B /* PageControllerFlow.swift */; }; + E2EAE721231024AF002CD16B /* ValueBinderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EAE720231024AF002CD16B /* ValueBinderTests.swift */; }; + E2F4BDC622FC439D003423CE /* LifeCycleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F4BDC522FC439D003423CE /* LifeCycleController.swift */; }; + E2F4BDC822FC43E6003423CE /* NavigationTestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F4BDC722FC43E6003423CE /* NavigationTestingTests.swift */; }; + E2FD5AA0232694600008DE77 /* ValueDiffingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD5A9F232694600008DE77 /* ValueDiffingTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = Lasso; + }; + E207AADD22F999D800D58979 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = Lasso_Example; + }; + E207AB0B22F9AE3400D58979 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = Lasso_Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 260B429F3723771BE87707D9 /* Pods-Lasso_TestUtilities_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_TestUtilities_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Lasso_TestUtilities_Tests/Pods-Lasso_TestUtilities_Tests.debug.xcconfig"; sourceTree = ""; }; + 2F2C2BDE9BE069562029E7DF /* Pods-Lasso_Tests_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Tests_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Lasso_Tests_Tests/Pods-Lasso_Tests_Tests.debug.xcconfig"; sourceTree = ""; }; + 34719C9657C178C3FDF5D88E /* Pods-Lasso_Example_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Example_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Lasso_Example_Tests/Pods-Lasso_Example_Tests.debug.xcconfig"; sourceTree = ""; }; + 3B191A7FBD67B5EA14BD1B5E /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + 4BE369290A02520225FECA3B /* Pods_Lasso_Example_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Lasso_Example_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 55A962BA7AEB74577C6668F1 /* Pods_Lasso_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Lasso_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD01AFB9204008FA782 /* Lasso_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Lasso_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 607FACE51AFB9204008FA782 /* Lasso_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Lasso_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6623FF5B72712910EBFF7255 /* Pods-Lasso_Example_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Example_Tests.release.xcconfig"; path = "Target Support Files/Pods-Lasso_Example_Tests/Pods-Lasso_Example_Tests.release.xcconfig"; sourceTree = ""; }; + 7917711922BB1545007BCFBA /* SimpleCounter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleCounter.swift; sourceTree = ""; }; + 7917711B22BB16CC007BCFBA /* Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = ""; }; + 7932794323A1291200654D8A /* Login.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Login.swift; sourceTree = ""; }; + 7932794523A12A0D00654D8A /* LoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginService.swift; sourceTree = ""; }; + 7932794723A12C8B00654D8A /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 7932794A23A12D9D00654D8A /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 7932794D23A12DF900654D8A /* UITableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; + 7932794F23A12E2700654D8A /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = ""; }; + 7932795123A12E4100654D8A /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; + 7932795323A12E6500654D8A /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; + 7932795523A12E9A00654D8A /* UILabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UILabel.swift; sourceTree = ""; }; + 7932795723A12F3A00654D8A /* UITextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextField.swift; sourceTree = ""; }; + 7932795923A12FE700654D8A /* UIActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorView.swift; sourceTree = ""; }; + 7932795B23A134A500654D8A /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; + 7932795D23A13EEF00654D8A /* MockableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockableTests.swift; sourceTree = ""; }; + 7932795F23A1448200654D8A /* MockableExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockableExtensionTests.swift; sourceTree = ""; }; + 7932796123A146A800654D8A /* LoginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginTests.swift; sourceTree = ""; }; + 7953AE49229AEDCA00B9D63D /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; + 796786582290EDF700CBE588 /* SignupForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupForm.swift; sourceTree = ""; }; + 7967865A2292D1B100CBE588 /* FoodOnboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoodOnboarding.swift; sourceTree = ""; }; + 7967865C2292DB1E00CBE588 /* StrangeFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrangeFlow.swift; sourceTree = ""; }; + 7978A6AD22A830A200B8B844 /* ActionDispatchableBindingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDispatchableBindingTests.swift; sourceTree = ""; }; + 7978A6B122A864BF00B8B844 /* UIGestureRecognizer+SendActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+SendActions.swift"; sourceTree = ""; }; + 7978A6B422A876E400B8B844 /* ObjectBindingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectBindingTests.swift; sourceTree = ""; }; + 7978A6B922A9484E00B8B844 /* ChooseWindowTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseWindowTransition.swift; sourceTree = ""; }; + 798090DB22C5550B00C993C1 /* Lasso.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lasso.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 798C6CA5229491B500B52EFA /* RandomItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomItems.swift; sourceTree = ""; }; + 79939B042333B92B00C55BA4 /* SearchStoreTests_XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchStoreTests_XCTestCase.swift; sourceTree = ""; }; + 79C8FAF423DF623A00FB9579 /* ScreenCaptureStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenCaptureStoreTests.swift; sourceTree = ""; }; + 79C9266422899B4F00F94A56 /* RandomStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomStrings.swift; sourceTree = ""; }; + 79C9266722899B4F00F94A56 /* TabsFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsFlow.swift; sourceTree = ""; }; + 79C9266822899B4F00F94A56 /* SearchModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchModule.swift; sourceTree = ""; }; + 79C9266922899B4F00F94A56 /* SampleCatalog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleCatalog.swift; sourceTree = ""; }; + 79C9266A22899B4F00F94A56 /* SampleCatalogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleCatalogViewController.swift; sourceTree = ""; }; + 79C9266B22899B4F00F94A56 /* SampleCatalogFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleCatalogFlow.swift; sourceTree = ""; }; + 79C9266E22899B4F00F94A56 /* TextScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextScreen.swift; sourceTree = ""; }; + 79C9267022899B4F00F94A56 /* CounterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CounterViewController.swift; sourceTree = ""; }; + 79C9267122899B4F00F94A56 /* CounterScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CounterScreen.swift; sourceTree = ""; }; + 79C9267322899B4F00F94A56 /* Onboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; + 79C926A22289E48000F94A56 /* EnterNameScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterNameScreen.swift; sourceTree = ""; }; + 79CE63E223F2F854003FFBF4 /* VerboseLoggingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerboseLoggingTests.swift; sourceTree = ""; }; + 79E001B722906A1800944209 /* Signup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signup.swift; sourceTree = ""; }; + 79E001B922906F6F00944209 /* SignupIntro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupIntro.swift; sourceTree = ""; }; + 79E2808C2338EB490016A859 /* SearchStoreTests_LassoStoreTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchStoreTests_LassoStoreTestCase.swift; sourceTree = ""; }; + 7E2C50A38EB52974125F2836 /* Pods_Lasso_TestUtilities_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Lasso_TestUtilities_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 81ABDEA302F8D875DEB1958C /* Pods_Lasso_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Lasso_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 885F03E2605819B59538B4A6 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + 889DD24F0B30E93CC321A9AD /* Pods-Lasso_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Example.release.xcconfig"; path = "Target Support Files/Pods-Lasso_Example/Pods-Lasso_Example.release.xcconfig"; sourceTree = ""; }; + A9CE3E88582F327FDE8253B6 /* Pods-Lasso_Tests_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Tests_Tests.release.xcconfig"; path = "Target Support Files/Pods-Lasso_Tests_Tests/Pods-Lasso_Tests_Tests.release.xcconfig"; sourceTree = ""; }; + B01C1F822323EA250084A149 /* StateObservationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateObservationTests.swift; sourceTree = ""; }; + B0922A5B22AAE2A100EBE5DE /* UIKitBindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBindings.swift; sourceTree = ""; }; + B094CBCC23494C60004C5CBE /* CounterStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterStoreTests.swift; sourceTree = ""; }; + B094CBCF234951C8004C5CBE /* SignupIntroStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupIntroStoreTests.swift; sourceTree = ""; }; + B094CBD1234952A1004C5CBE /* SignupFormStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupFormStoreTests.swift; sourceTree = ""; }; + B0C37242229C0FAF00FB214D /* RandomItemFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomItemFlow.swift; sourceTree = ""; }; + D2BCE54DB43C58C9997169C3 /* Pods-Lasso_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Example.debug.xcconfig"; path = "Target Support Files/Pods-Lasso_Example/Pods-Lasso_Example.debug.xcconfig"; sourceTree = ""; }; + D566303CD3701DDD57B6EBF3 /* Lasso.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Lasso.podspec; path = ../Lasso.podspec; sourceTree = ""; }; + E207AAD822F999D800D58979 /* Lasso_Example_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Lasso_Example_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E207AADC22F999D800D58979 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E207AAE222F99F8A00D58979 /* SearchAndTrackFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchAndTrackFlow.swift; sourceTree = ""; }; + E207AAF422F9A58800D58979 /* SearchAndTrackFlowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchAndTrackFlowTests.swift; sourceTree = ""; }; + E207AAF522F9A58800D58979 /* SurveyFlowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurveyFlowTests.swift; sourceTree = ""; }; + E207AAF722F9A58800D58979 /* OnboardingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = ""; }; + E207AAFC22F9A59000D58979 /* FoodOnboardingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoodOnboardingTests.swift; sourceTree = ""; }; + E207AB0622F9AE3400D58979 /* Lasso_TestUtilities_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Lasso_TestUtilities_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E207AB0A22F9AE3400D58979 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E215043E231EE4900027C4A9 /* SearchStoreTests_FunctionalStyle1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchStoreTests_FunctionalStyle1.swift; sourceTree = ""; }; + E23337E62358CA2100F26802 /* MyDayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyDayTests.swift; sourceTree = ""; }; + E25C6E4C23195C450091BC72 /* ModalTestingTests+FullScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModalTestingTests+FullScreen.swift"; sourceTree = ""; }; + E25C6E4E23195CF40091BC72 /* ModalTestingTests+AnyModalPresentationStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModalTestingTests+AnyModalPresentationStyle.swift"; sourceTree = ""; }; + E25C6E5023195DDA0091BC72 /* ModalTestingTests+SystemBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModalTestingTests+SystemBehavior.swift"; sourceTree = ""; }; + E274FB7B235A3612006832CC /* MyDayFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyDayFlowTests.swift; sourceTree = ""; }; + E2772B6A22A7097E007A3D51 /* MakeListModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeListModule.swift; sourceTree = ""; }; + E2772B6C22A7FFE7007A3D51 /* SurveyModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyModule.swift; sourceTree = ""; }; + E2772B6E22A80C93007A3D51 /* SurveyFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyFlow.swift; sourceTree = ""; }; + E2AF6D4B235DF40A001ED33A /* MyDayFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyDayFlow.swift; sourceTree = ""; }; + E2AF6D4D235DF442001ED33A /* MyDayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyDayController.swift; sourceTree = ""; }; + E2AF6D4F235DF47A001ED33A /* CalendarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarScreen.swift; sourceTree = ""; }; + E2AF6D51235DF49B001ED33A /* DailyLogScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyLogScreen.swift; sourceTree = ""; }; + E2AF6D53235DF4C1001ED33A /* MyDayCardsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyDayCardsScreen.swift; sourceTree = ""; }; + E2AF95C923463BCD00A524AB /* SearchStoreTests_FunctionalStyle2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchStoreTests_FunctionalStyle2.swift; sourceTree = ""; }; + E2DF79052396FB1200CF353B /* PageControllerFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControllerFlow.swift; sourceTree = ""; }; + E2EAE720231024AF002CD16B /* ValueBinderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueBinderTests.swift; sourceTree = ""; }; + E2F4BDC522FC439D003423CE /* LifeCycleController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifeCycleController.swift; sourceTree = ""; }; + E2F4BDC722FC43E6003423CE /* NavigationTestingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTestingTests.swift; sourceTree = ""; }; + E2FD5A9F232694600008DE77 /* ValueDiffingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueDiffingTests.swift; sourceTree = ""; }; + F0755858700BA9D6F536608C /* Pods-Lasso_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Tests.release.xcconfig"; path = "Target Support Files/Pods-Lasso_Tests/Pods-Lasso_Tests.release.xcconfig"; sourceTree = ""; }; + F45A3FD9721EB5031A827ABA /* Pods-Lasso_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Lasso_Tests/Pods-Lasso_Tests.debug.xcconfig"; sourceTree = ""; }; + F4AC36172259D284CA7245F3 /* Pods-Lasso_TestUtilities_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Lasso_TestUtilities_Tests.release.xcconfig"; path = "Target Support Files/Pods-Lasso_TestUtilities_Tests/Pods-Lasso_TestUtilities_Tests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607FACCD1AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9E8673C663191C820D8283F0 /* Pods_Lasso_Example.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE21AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A2914468D21ED959531A7C0F /* Pods_Lasso_Tests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E207AAD522F999D800D58979 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 36653CE23A04A3982474C23A /* Pods_Lasso_Example_Tests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E207AB0322F9AE3400D58979 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ADF94DC1962D2F14DC10C806 /* Pods_Lasso_TestUtilities_Tests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 428E8825219D2F83C23F1265 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 798090DB22C5550B00C993C1 /* Lasso.framework */, + 81ABDEA302F8D875DEB1958C /* Pods_Lasso_Example.framework */, + 55A962BA7AEB74577C6668F1 /* Pods_Lasso_Tests.framework */, + 4BE369290A02520225FECA3B /* Pods_Lasso_Example_Tests.framework */, + 7E2C50A38EB52974125F2836 /* Pods_Lasso_TestUtilities_Tests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 607FACC71AFB9204008FA782 = { + isa = PBXGroup; + children = ( + 607FACD21AFB9204008FA782 /* Example for Lasso */, + E207AAD922F999D800D58979 /* Example_Tests */, + 607FACE81AFB9204008FA782 /* Lasso_Tests */, + E207AB0722F9AE3400D58979 /* LassoTestUtilities_Tests */, + 607FACF51AFB993E008FA782 /* Podspec Metadata */, + 607FACD11AFB9204008FA782 /* Products */, + B48222572740F8DAE18E6948 /* Pods */, + 428E8825219D2F83C23F1265 /* Frameworks */, + ); + sourceTree = ""; + }; + 607FACD11AFB9204008FA782 /* Products */ = { + isa = PBXGroup; + children = ( + 607FACD01AFB9204008FA782 /* Lasso_Example.app */, + 607FACE51AFB9204008FA782 /* Lasso_Tests.xctest */, + E207AAD822F999D800D58979 /* Lasso_Example_Tests.xctest */, + E207AB0622F9AE3400D58979 /* Lasso_TestUtilities_Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607FACD21AFB9204008FA782 /* Example for Lasso */ = { + isa = PBXGroup; + children = ( + 607FACD51AFB9204008FA782 /* AppDelegate.swift */, + 7917711822BB1545007BCFBA /* Presentation */, + 79C9269F2289A22500F94A56 /* Sample Catalog */, + 79C9266522899B4F00F94A56 /* Samples */, + 7932794923A12D7B00654D8A /* Utilities */, + 607FACDC1AFB9204008FA782 /* Images.xcassets */, + 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, + 607FACD31AFB9204008FA782 /* Supporting Files */, + ); + name = "Example for Lasso"; + path = Lasso; + sourceTree = ""; + }; + 607FACD31AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACD41AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACE81AFB9204008FA782 /* Lasso_Tests */ = { + isa = PBXGroup; + children = ( + 7978A6AD22A830A200B8B844 /* ActionDispatchableBindingTests.swift */, + 7978A6B422A876E400B8B844 /* ObjectBindingTests.swift */, + 79C8FAF423DF623A00FB9579 /* ScreenCaptureStoreTests.swift */, + B01C1F822323EA250084A149 /* StateObservationTests.swift */, + E2EAE720231024AF002CD16B /* ValueBinderTests.swift */, + 7932795D23A13EEF00654D8A /* MockableTests.swift */, + 7978A6B322A876A900B8B844 /* Helpers */, + 607FACE91AFB9204008FA782 /* Supporting Files */, + ); + path = Lasso_Tests; + sourceTree = ""; + }; + 607FACE91AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACEA1AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { + isa = PBXGroup; + children = ( + D566303CD3701DDD57B6EBF3 /* Lasso.podspec */, + 885F03E2605819B59538B4A6 /* README.md */, + 3B191A7FBD67B5EA14BD1B5E /* LICENSE */, + ); + name = "Podspec Metadata"; + sourceTree = ""; + }; + 7917711822BB1545007BCFBA /* Presentation */ = { + isa = PBXGroup; + children = ( + 7917711922BB1545007BCFBA /* SimpleCounter.swift */, + 7917711B22BB16CC007BCFBA /* Welcome.swift */, + ); + path = Presentation; + sourceTree = ""; + }; + 7932794223A128FA00654D8A /* Login */ = { + isa = PBXGroup; + children = ( + 7932794323A1291200654D8A /* Login.swift */, + 7932794523A12A0D00654D8A /* LoginService.swift */, + 7932794723A12C8B00654D8A /* LoginViewController.swift */, + ); + path = Login; + sourceTree = ""; + }; + 7932794923A12D7B00654D8A /* Utilities */ = { + isa = PBXGroup; + children = ( + 79C9266422899B4F00F94A56 /* RandomStrings.swift */, + 7932794A23A12D9D00654D8A /* Result.swift */, + 7932794C23A12DE200654D8A /* ViewHelpers */, + ); + path = Utilities; + sourceTree = ""; + }; + 7932794C23A12DE200654D8A /* ViewHelpers */ = { + isa = PBXGroup; + children = ( + 7932795923A12FE700654D8A /* UIActivityIndicatorView.swift */, + 7932794F23A12E2700654D8A /* UIButton.swift */, + 7932795B23A134A500654D8A /* UIColor.swift */, + 7932795323A12E6500654D8A /* UIImage.swift */, + 7932795523A12E9A00654D8A /* UILabel.swift */, + 7932794D23A12DF900654D8A /* UITableView.swift */, + 7932795723A12F3A00654D8A /* UITextField.swift */, + 7932795123A12E4100654D8A /* UIView.swift */, + ); + path = ViewHelpers; + sourceTree = ""; + }; + 7978A6B322A876A900B8B844 /* Helpers */ = { + isa = PBXGroup; + children = ( + 7978A6B122A864BF00B8B844 /* UIGestureRecognizer+SendActions.swift */, + ); + name = Helpers; + sourceTree = ""; + }; + 79C9266522899B4F00F94A56 /* Samples */ = { + isa = PBXGroup; + children = ( + E2AF6D4A235DF3E7001ED33A /* MyDay */, + E207AAE222F99F8A00D58979 /* SearchAndTrackFlow.swift */, + 7978A6B922A9484E00B8B844 /* ChooseWindowTransition.swift */, + 79C9266822899B4F00F94A56 /* SearchModule.swift */, + 7967865C2292DB1E00CBE588 /* StrangeFlow.swift */, + 79C9266E22899B4F00F94A56 /* TextScreen.swift */, + B0922A5B22AAE2A100EBE5DE /* UIKitBindings.swift */, + 7932794223A128FA00654D8A /* Login */, + 79C9266F22899B4F00F94A56 /* Counter */, + 79C9267222899B4F00F94A56 /* Onboarding */, + 79E001B6229069DF00944209 /* Signup */, + B0C37241229C0F4900FB214D /* SplitView */, + E2772B6922A708BB007A3D51 /* Survey */, + 79C9266622899B4F00F94A56 /* Tabs */, + E2DF79052396FB1200CF353B /* PageControllerFlow.swift */, + ); + path = Samples; + sourceTree = ""; + }; + 79C9266622899B4F00F94A56 /* Tabs */ = { + isa = PBXGroup; + children = ( + 79C9266722899B4F00F94A56 /* TabsFlow.swift */, + 798C6CA5229491B500B52EFA /* RandomItems.swift */, + ); + path = Tabs; + sourceTree = ""; + }; + 79C9266F22899B4F00F94A56 /* Counter */ = { + isa = PBXGroup; + children = ( + 79C9267122899B4F00F94A56 /* CounterScreen.swift */, + 79C9267022899B4F00F94A56 /* CounterViewController.swift */, + ); + path = Counter; + sourceTree = ""; + }; + 79C9267222899B4F00F94A56 /* Onboarding */ = { + isa = PBXGroup; + children = ( + 79C9267322899B4F00F94A56 /* Onboarding.swift */, + 7967865A2292D1B100CBE588 /* FoodOnboarding.swift */, + 79C926A22289E48000F94A56 /* EnterNameScreen.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; + 79C9269F2289A22500F94A56 /* Sample Catalog */ = { + isa = PBXGroup; + children = ( + 79C9266B22899B4F00F94A56 /* SampleCatalogFlow.swift */, + 79C9266922899B4F00F94A56 /* SampleCatalog.swift */, + 79C9266A22899B4F00F94A56 /* SampleCatalogViewController.swift */, + ); + path = "Sample Catalog"; + sourceTree = ""; + }; + 79E001B6229069DF00944209 /* Signup */ = { + isa = PBXGroup; + children = ( + 79E001B722906A1800944209 /* Signup.swift */, + 79E001B922906F6F00944209 /* SignupIntro.swift */, + 796786582290EDF700CBE588 /* SignupForm.swift */, + ); + path = Signup; + sourceTree = ""; + }; + B094CBCE2349519D004C5CBE /* Signup */ = { + isa = PBXGroup; + children = ( + B094CBCF234951C8004C5CBE /* SignupIntroStoreTests.swift */, + B094CBD1234952A1004C5CBE /* SignupFormStoreTests.swift */, + ); + path = Signup; + sourceTree = ""; + }; + B0C37241229C0F4900FB214D /* SplitView */ = { + isa = PBXGroup; + children = ( + 7953AE49229AEDCA00B9D63D /* SplitView.swift */, + B0C37242229C0FAF00FB214D /* RandomItemFlow.swift */, + ); + path = SplitView; + sourceTree = ""; + }; + B48222572740F8DAE18E6948 /* Pods */ = { + isa = PBXGroup; + children = ( + D2BCE54DB43C58C9997169C3 /* Pods-Lasso_Example.debug.xcconfig */, + 889DD24F0B30E93CC321A9AD /* Pods-Lasso_Example.release.xcconfig */, + F45A3FD9721EB5031A827ABA /* Pods-Lasso_Tests.debug.xcconfig */, + F0755858700BA9D6F536608C /* Pods-Lasso_Tests.release.xcconfig */, + 34719C9657C178C3FDF5D88E /* Pods-Lasso_Example_Tests.debug.xcconfig */, + 6623FF5B72712910EBFF7255 /* Pods-Lasso_Example_Tests.release.xcconfig */, + 2F2C2BDE9BE069562029E7DF /* Pods-Lasso_Tests_Tests.debug.xcconfig */, + A9CE3E88582F327FDE8253B6 /* Pods-Lasso_Tests_Tests.release.xcconfig */, + 260B429F3723771BE87707D9 /* Pods-Lasso_TestUtilities_Tests.debug.xcconfig */, + F4AC36172259D284CA7245F3 /* Pods-Lasso_TestUtilities_Tests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + E207AAD922F999D800D58979 /* Example_Tests */ = { + isa = PBXGroup; + children = ( + B094CBCC23494C60004C5CBE /* CounterStoreTests.swift */, + E207AAFC22F9A59000D58979 /* FoodOnboardingTests.swift */, + E207AADC22F999D800D58979 /* Info.plist */, + 7932796123A146A800654D8A /* LoginTests.swift */, + E207AAF722F9A58800D58979 /* OnboardingTests.swift */, + E207AAF422F9A58800D58979 /* SearchAndTrackFlowTests.swift */, + E215043E231EE4900027C4A9 /* SearchStoreTests_FunctionalStyle1.swift */, + E2AF95C923463BCD00A524AB /* SearchStoreTests_FunctionalStyle2.swift */, + 79E2808C2338EB490016A859 /* SearchStoreTests_LassoStoreTestCase.swift */, + 79939B042333B92B00C55BA4 /* SearchStoreTests_XCTestCase.swift */, + B094CBCE2349519D004C5CBE /* Signup */, + E207AAF522F9A58800D58979 /* SurveyFlowTests.swift */, + E23337E62358CA2100F26802 /* MyDayTests.swift */, + E274FB7B235A3612006832CC /* MyDayFlowTests.swift */, + ); + path = Example_Tests; + sourceTree = ""; + }; + E207AB0722F9AE3400D58979 /* LassoTestUtilities_Tests */ = { + isa = PBXGroup; + children = ( + E207AB0A22F9AE3400D58979 /* Info.plist */, + E2F4BDC522FC439D003423CE /* LifeCycleController.swift */, + 7932795F23A1448200654D8A /* MockableExtensionTests.swift */, + E25C6E4C23195C450091BC72 /* ModalTestingTests+FullScreen.swift */, + E25C6E4E23195CF40091BC72 /* ModalTestingTests+AnyModalPresentationStyle.swift */, + E25C6E5023195DDA0091BC72 /* ModalTestingTests+SystemBehavior.swift */, + E2F4BDC722FC43E6003423CE /* NavigationTestingTests.swift */, + E2FD5A9F232694600008DE77 /* ValueDiffingTests.swift */, + 79CE63E223F2F854003FFBF4 /* VerboseLoggingTests.swift */, + ); + path = LassoTestUtilities_Tests; + sourceTree = ""; + }; + E2772B6922A708BB007A3D51 /* Survey */ = { + isa = PBXGroup; + children = ( + E2772B6A22A7097E007A3D51 /* MakeListModule.swift */, + E2772B6C22A7FFE7007A3D51 /* SurveyModule.swift */, + E2772B6E22A80C93007A3D51 /* SurveyFlow.swift */, + ); + path = Survey; + sourceTree = ""; + }; + E2AF6D4A235DF3E7001ED33A /* MyDay */ = { + isa = PBXGroup; + children = ( + E2AF6D4B235DF40A001ED33A /* MyDayFlow.swift */, + E2AF6D4D235DF442001ED33A /* MyDayController.swift */, + E2AF6D4F235DF47A001ED33A /* CalendarScreen.swift */, + E2AF6D51235DF49B001ED33A /* DailyLogScreen.swift */, + E2AF6D53235DF4C1001ED33A /* MyDayCardsScreen.swift */, + ); + path = MyDay; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607FACCF1AFB9204008FA782 /* Lasso_Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "Lasso_Example" */; + buildPhases = ( + D2F94155062378B68F89169F /* [CP] Check Pods Manifest.lock */, + 607FACCC1AFB9204008FA782 /* Sources */, + 607FACCD1AFB9204008FA782 /* Frameworks */, + 607FACCE1AFB9204008FA782 /* Resources */, + 191876CD3C60989C243DAF5D /* [CP] Embed Pods Frameworks */, + 26451D8F07122B70C26F0DE9 /* [CP-User] SwiftLint */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Lasso_Example; + productName = Lasso; + productReference = 607FACD01AFB9204008FA782 /* Lasso_Example.app */; + productType = "com.apple.product-type.application"; + }; + 607FACE41AFB9204008FA782 /* Lasso_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "Lasso_Tests" */; + buildPhases = ( + B59EA31026C739093E07378B /* [CP] Check Pods Manifest.lock */, + 607FACE11AFB9204008FA782 /* Sources */, + 607FACE21AFB9204008FA782 /* Frameworks */, + 607FACE31AFB9204008FA782 /* Resources */, + B56840B983C9F65861DAA33C /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 607FACE71AFB9204008FA782 /* PBXTargetDependency */, + ); + name = Lasso_Tests; + productName = Tests; + productReference = 607FACE51AFB9204008FA782 /* Lasso_Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + E207AAD722F999D800D58979 /* Lasso_Example_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E207AAE122F999D800D58979 /* Build configuration list for PBXNativeTarget "Lasso_Example_Tests" */; + buildPhases = ( + 68508589E547BDE64A185A92 /* [CP] Check Pods Manifest.lock */, + E207AAD422F999D800D58979 /* Sources */, + E207AAD522F999D800D58979 /* Frameworks */, + E207AAD622F999D800D58979 /* Resources */, + 8B5E20975827D968B25853F0 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + E207AADE22F999D800D58979 /* PBXTargetDependency */, + ); + name = Lasso_Example_Tests; + productName = Lasso_Example_Tests; + productReference = E207AAD822F999D800D58979 /* Lasso_Example_Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + E207AB0522F9AE3400D58979 /* Lasso_TestUtilities_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E207AB0F22F9AE3500D58979 /* Build configuration list for PBXNativeTarget "Lasso_TestUtilities_Tests" */; + buildPhases = ( + 6ACA20614262066F5C98C3AB /* [CP] Check Pods Manifest.lock */, + E207AB0222F9AE3400D58979 /* Sources */, + E207AB0322F9AE3400D58979 /* Frameworks */, + E207AB0422F9AE3400D58979 /* Resources */, + E523D2EA07B400E7E8DE6C80 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + E207AB0C22F9AE3400D58979 /* PBXTargetDependency */, + ); + name = Lasso_TestUtilities_Tests; + productName = Lasso_Tests_Tests; + productReference = E207AB0622F9AE3400D58979 /* Lasso_TestUtilities_Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607FACC81AFB9204008FA782 /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Original; + LastSwiftUpdateCheck = 1020; + LastUpgradeCheck = 1100; + ORGANIZATIONNAME = "WW International"; + TargetAttributes = { + 607FACCF1AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 1030; + }; + 607FACE41AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 1030; + TestTargetID = 607FACCF1AFB9204008FA782; + }; + E207AAD722F999D800D58979 = { + CreatedOnToolsVersion = 10.2.1; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + TestTargetID = 607FACCF1AFB9204008FA782; + }; + E207AB0522F9AE3400D58979 = { + CreatedOnToolsVersion = 10.2.1; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + TestTargetID = 607FACCF1AFB9204008FA782; + }; + }; + }; + buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "Lasso" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 607FACC71AFB9204008FA782; + productRefGroup = 607FACD11AFB9204008FA782 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607FACCF1AFB9204008FA782 /* Lasso_Example */, + 607FACE41AFB9204008FA782 /* Lasso_Tests */, + E207AAD722F999D800D58979 /* Lasso_Example_Tests */, + E207AB0522F9AE3400D58979 /* Lasso_TestUtilities_Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607FACCE1AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE31AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E207AAD622F999D800D58979 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E207AB0422F9AE3400D58979 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 191876CD3C60989C243DAF5D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Lasso_Example/Pods-Lasso_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Lasso/Lasso.framework", + "${BUILT_PRODUCTS_DIR}/WWLayout/WWLayout.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lasso.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WWLayout.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lasso_Example/Pods-Lasso_Example-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 26451D8F07122B70C26F0DE9 /* [CP-User] SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] SwiftLint"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${PODS_ROOT}/SwiftLint/swiftlint --config ${SRCROOT}/../.swiftlint.yml --path ${SRCROOT}/../\n"; + showEnvVarsInLog = 0; + }; + 68508589E547BDE64A185A92 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Lasso_Example_Tests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6ACA20614262066F5C98C3AB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Lasso_TestUtilities_Tests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8B5E20975827D968B25853F0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Lasso_Example_Tests/Pods-Lasso_Example_Tests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Lasso/Lasso.framework", + "${BUILT_PRODUCTS_DIR}/LassoTestUtilities/LassoTestUtilities.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lasso.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LassoTestUtilities.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lasso_Example_Tests/Pods-Lasso_Example_Tests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B56840B983C9F65861DAA33C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Lasso_Tests/Pods-Lasso_Tests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Lasso/Lasso.framework", + "${BUILT_PRODUCTS_DIR}/WWLayout/WWLayout.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lasso.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WWLayout.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lasso_Tests/Pods-Lasso_Tests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B59EA31026C739093E07378B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Lasso_Tests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D2F94155062378B68F89169F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Lasso_Example-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E523D2EA07B400E7E8DE6C80 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Lasso_TestUtilities_Tests/Pods-Lasso_TestUtilities_Tests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Lasso/Lasso.framework", + "${BUILT_PRODUCTS_DIR}/LassoTestUtilities/LassoTestUtilities.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lasso.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LassoTestUtilities.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lasso_TestUtilities_Tests/Pods-Lasso_TestUtilities_Tests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 607FACCC1AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E2AF6D54235DF4C1001ED33A /* MyDayCardsScreen.swift in Sources */, + E2AF6D52235DF49B001ED33A /* DailyLogScreen.swift in Sources */, + 7967865D2292DB1E00CBE588 /* StrangeFlow.swift in Sources */, + 7932795823A12F3A00654D8A /* UITextField.swift in Sources */, + E2772B6F22A80C93007A3D51 /* SurveyFlow.swift in Sources */, + E2AF6D4C235DF40A001ED33A /* MyDayFlow.swift in Sources */, + 79E001BA22906F6F00944209 /* SignupIntro.swift in Sources */, + E2DF79062396FB1200CF353B /* PageControllerFlow.swift in Sources */, + 79C9267D22899B4F00F94A56 /* CounterScreen.swift in Sources */, + 79C9267522899B4F00F94A56 /* TabsFlow.swift in Sources */, + 7932795A23A12FE700654D8A /* UIActivityIndicatorView.swift in Sources */, + 79C9267B22899B4F00F94A56 /* TextScreen.swift in Sources */, + 798C6CA6229491B500B52EFA /* RandomItems.swift in Sources */, + 7932795623A12E9A00654D8A /* UILabel.swift in Sources */, + 7932795223A12E4100654D8A /* UIView.swift in Sources */, + 79C9267422899B4F00F94A56 /* RandomStrings.swift in Sources */, + E2AF6D50235DF47A001ED33A /* CalendarScreen.swift in Sources */, + 79C9267822899B4F00F94A56 /* SampleCatalogViewController.swift in Sources */, + 79C9267922899B4F00F94A56 /* SampleCatalogFlow.swift in Sources */, + 7917711A22BB1545007BCFBA /* SimpleCounter.swift in Sources */, + 79C9267E22899B4F00F94A56 /* Onboarding.swift in Sources */, + E2772B6B22A7097E007A3D51 /* MakeListModule.swift in Sources */, + 796786592290EDF700CBE588 /* SignupForm.swift in Sources */, + B0C37243229C0FAF00FB214D /* RandomItemFlow.swift in Sources */, + E2AF6D4E235DF443001ED33A /* MyDayController.swift in Sources */, + 7932794423A1291200654D8A /* Login.swift in Sources */, + 79E001B822906A1800944209 /* Signup.swift in Sources */, + 7932794E23A12DF900654D8A /* UITableView.swift in Sources */, + B0922A5C22AAE2A100EBE5DE /* UIKitBindings.swift in Sources */, + 7932794823A12C8B00654D8A /* LoginViewController.swift in Sources */, + 7932795423A12E6500654D8A /* UIImage.swift in Sources */, + E207AAE322F99F8B00D58979 /* SearchAndTrackFlow.swift in Sources */, + 7967865B2292D1B100CBE588 /* FoodOnboarding.swift in Sources */, + 7932795C23A134A500654D8A /* UIColor.swift in Sources */, + 7978A6BA22A9484E00B8B844 /* ChooseWindowTransition.swift in Sources */, + 7917711C22BB16CC007BCFBA /* Welcome.swift in Sources */, + 79C9267C22899B4F00F94A56 /* CounterViewController.swift in Sources */, + 79C9267622899B4F00F94A56 /* SearchModule.swift in Sources */, + E2772B6D22A7FFE7007A3D51 /* SurveyModule.swift in Sources */, + 7932794623A12A0D00654D8A /* LoginService.swift in Sources */, + 7953AE4A229AEDCA00B9D63D /* SplitView.swift in Sources */, + 7932795023A12E2700654D8A /* UIButton.swift in Sources */, + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, + 79C9267722899B4F00F94A56 /* SampleCatalog.swift in Sources */, + 7932794B23A12D9D00654D8A /* Result.swift in Sources */, + 79C926A32289E48000F94A56 /* EnterNameScreen.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE11AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7978A6B522A876E400B8B844 /* ObjectBindingTests.swift in Sources */, + 7932795E23A13EEF00654D8A /* MockableTests.swift in Sources */, + E2EAE721231024AF002CD16B /* ValueBinderTests.swift in Sources */, + 7978A6B222A864BF00B8B844 /* UIGestureRecognizer+SendActions.swift in Sources */, + 7978A6AE22A830A200B8B844 /* ActionDispatchableBindingTests.swift in Sources */, + B01C1F832323EA250084A149 /* StateObservationTests.swift in Sources */, + 79C8FAF523DF623A00FB9579 /* ScreenCaptureStoreTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E207AAD422F999D800D58979 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 79E2808D2338EB490016A859 /* SearchStoreTests_LassoStoreTestCase.swift in Sources */, + E207AAF822F9A58800D58979 /* SearchAndTrackFlowTests.swift in Sources */, + E23337E72358CA2100F26802 /* MyDayTests.swift in Sources */, + B094CBCD23494C60004C5CBE /* CounterStoreTests.swift in Sources */, + E274FB7C235A3612006832CC /* MyDayFlowTests.swift in Sources */, + 7932796223A146A800654D8A /* LoginTests.swift in Sources */, + E215043F231EE4900027C4A9 /* SearchStoreTests_FunctionalStyle1.swift in Sources */, + E207AAF922F9A58800D58979 /* SurveyFlowTests.swift in Sources */, + E207AAFD22F9A59000D58979 /* FoodOnboardingTests.swift in Sources */, + E207AAFB22F9A58800D58979 /* OnboardingTests.swift in Sources */, + 79939B052333B92B00C55BA4 /* SearchStoreTests_XCTestCase.swift in Sources */, + B094CBD0234951C8004C5CBE /* SignupIntroStoreTests.swift in Sources */, + B094CBD2234952A1004C5CBE /* SignupFormStoreTests.swift in Sources */, + E2AF95CA23463BCD00A524AB /* SearchStoreTests_FunctionalStyle2.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E207AB0222F9AE3400D58979 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E2F4BDC822FC43E6003423CE /* NavigationTestingTests.swift in Sources */, + E2FD5AA0232694600008DE77 /* ValueDiffingTests.swift in Sources */, + 7932796023A1448200654D8A /* MockableExtensionTests.swift in Sources */, + E25C6E5123195DDA0091BC72 /* ModalTestingTests+SystemBehavior.swift in Sources */, + 79CE63E323F2F854003FFBF4 /* VerboseLoggingTests.swift in Sources */, + E25C6E4D23195C450091BC72 /* ModalTestingTests+FullScreen.swift in Sources */, + E25C6E4F23195CF40091BC72 /* ModalTestingTests+AnyModalPresentationStyle.swift in Sources */, + E2F4BDC622FC439D003423CE /* LifeCycleController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* Lasso_Example */; + targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; + }; + E207AADE22F999D800D58979 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* Lasso_Example */; + targetProxy = E207AADD22F999D800D58979 /* PBXContainerItemProxy */; + }; + E207AB0C22F9AE3400D58979 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* Lasso_Example */; + targetProxy = E207AB0B22F9AE3400D58979 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 607FACDF1AFB9204008FA782 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 607FACED1AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + }; + name = Debug; + }; + 607FACEE1AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.2; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607FACF01AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D2BCE54DB43C58C9997169C3 /* Pods-Lasso_Example.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Lasso/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "com.ww.lasso-flow"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACF11AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 889DD24F0B30E93CC321A9AD /* Pods-Lasso_Example.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Lasso/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "com.ww.lasso-flow"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 607FACF31AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F45A3FD9721EB5031A827ABA /* Pods-Lasso_Tests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Lasso_Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Lasso_Example.app/Lasso_Example"; + }; + name = Debug; + }; + 607FACF41AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F0755858700BA9D6F536608C /* Pods-Lasso_Tests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = Lasso_Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Lasso_Example.app/Lasso_Example"; + }; + name = Release; + }; + E207AADF22F999D800D58979 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 34719C9657C178C3FDF5D88E /* Pods-Lasso_Example_Tests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Example_Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "WW.Lasso-Example-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Lasso_Example.app/Lasso_Example"; + }; + name = Debug; + }; + E207AAE022F999D800D58979 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6623FF5B72712910EBFF7255 /* Pods-Lasso_Example_Tests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Example_Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "WW.Lasso-Example-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Lasso_Example.app/Lasso_Example"; + }; + name = Release; + }; + E207AB0D22F9AE3500D58979 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 260B429F3723771BE87707D9 /* Pods-Lasso_TestUtilities_Tests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = LassoTestUtilities_Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "WW.Lasso-Tests-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Lasso_Example.app/Lasso_Example"; + }; + name = Debug; + }; + E207AB0E22F9AE3500D58979 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4AC36172259D284CA7245F3 /* Pods-Lasso_TestUtilities_Tests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = LassoTestUtilities_Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "WW.Lasso-Tests-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Lasso_Example.app/Lasso_Example"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "Lasso" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACED1AFB9204008FA782 /* Debug */, + 607FACEE1AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "Lasso_Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF01AFB9204008FA782 /* Debug */, + 607FACF11AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "Lasso_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF31AFB9204008FA782 /* Debug */, + 607FACF41AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E207AAE122F999D800D58979 /* Build configuration list for PBXNativeTarget "Lasso_Example_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E207AADF22F999D800D58979 /* Debug */, + E207AAE022F999D800D58979 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E207AB0F22F9AE3500D58979 /* Build configuration list for PBXNativeTarget "Lasso_TestUtilities_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E207AB0D22F9AE3500D58979 /* Debug */, + E207AB0E22F9AE3500D58979 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 607FACC81AFB9204008FA782 /* Project object */; +} diff --git a/Example/Lasso.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Lasso.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ee02c68 --- /dev/null +++ b/Example/Lasso.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/Lasso.xcodeproj/xcshareddata/IDETemplateMacros.plist b/Example/Lasso.xcodeproj/xcshareddata/IDETemplateMacros.plist new file mode 100644 index 0000000..0ca2416 --- /dev/null +++ b/Example/Lasso.xcodeproj/xcshareddata/IDETemplateMacros.plist @@ -0,0 +1,23 @@ + + + + + FILEHEADER + +//===----------------------------------------------------------------------===// +// +// ___FILENAME___ +// +// Created by ___FULLUSERNAME___ on ___DATE___ +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-___YEAR___ WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + + diff --git a/Example/Lasso.xcodeproj/xcshareddata/xcschemes/Lasso-Example.xcscheme b/Example/Lasso.xcodeproj/xcshareddata/xcschemes/Lasso-Example.xcscheme new file mode 100644 index 0000000..b9f4c89 --- /dev/null +++ b/Example/Lasso.xcodeproj/xcshareddata/xcschemes/Lasso-Example.xcscheme @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Lasso.xcworkspace/contents.xcworkspacedata b/Example/Lasso.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..09e384b --- /dev/null +++ b/Example/Lasso.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/Lasso.xcworkspace/xcshareddata/IDETemplateMacros.plist b/Example/Lasso.xcworkspace/xcshareddata/IDETemplateMacros.plist new file mode 100644 index 0000000..0ca2416 --- /dev/null +++ b/Example/Lasso.xcworkspace/xcshareddata/IDETemplateMacros.plist @@ -0,0 +1,23 @@ + + + + + FILEHEADER + +//===----------------------------------------------------------------------===// +// +// ___FILENAME___ +// +// Created by ___FULLUSERNAME___ on ___DATE___ +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-___YEAR___ WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + + diff --git a/Example/Lasso.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Lasso.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/Lasso.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/Lasso.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Example/Lasso.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..530b833 --- /dev/null +++ b/Example/Lasso.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + BuildSystemType + Latest + PreviewsEnabled + + + diff --git a/Example/Lasso/AppDelegate.swift b/Example/Lasso/AppDelegate.swift new file mode 100644 index 0000000..4542e1f --- /dev/null +++ b/Example/Lasso/AppDelegate.swift @@ -0,0 +1,44 @@ +// +//===----------------------------------------------------------------------===// +// +// AppDelegate.swift +// +// Created by yuichi-kuroda-ww on 04/30/2019. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + let window = UIWindow(frame: UIScreen.main.bounds) + self.window = window + + if Testing.active { + window.rootViewController = UIViewController() + } + else { + SampleCatalogFlow().start(with: root(of: window).withNavigationEmbedding()) + } + + window.backgroundColor = .background + window.makeKeyAndVisible() + + return true + } + +} diff --git a/Example/Lasso/Base.lproj/LaunchScreen.xib b/Example/Lasso/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..8bfcad2 --- /dev/null +++ b/Example/Lasso/Base.lproj/LaunchScreen.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Lasso/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/Lasso/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..7006c9e --- /dev/null +++ b/Example/Lasso/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Example/Lasso/Images.xcassets/Contents.json b/Example/Lasso/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Example/Lasso/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Lasso/Images.xcassets/circle.imageset/Contents.json b/Example/Lasso/Images.xcassets/circle.imageset/Contents.json new file mode 100644 index 0000000..b73baca --- /dev/null +++ b/Example/Lasso/Images.xcassets/circle.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "circle.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Example/Lasso/Images.xcassets/circle.imageset/circle.pdf b/Example/Lasso/Images.xcassets/circle.imageset/circle.pdf new file mode 100644 index 0000000000000000000000000000000000000000..47d911dea647d55983671ead4d08b6f6b3600715 GIT binary patch literal 2465 zcmai03se(l7FLvisY+L#f-EBsK`11Xkc1=%MIujmB|xQ!>5vQ%APFP`Mlf5nSPOKE z2-^C>7DZkM!J;Da5{kfrC?Kvp3J41-3shFXvhuJqKo*oer*r0H{{P-P_x0cTzLzjD!bE z0qT;#Q7l4Gy%fMoXJaKT`@{5#R(MOqJPwQifv8iK6A%Ot9L14h2`38T!2s4PM=1!< zmL06}VYAA|ay#jZRs>HpA%X+eQW4rufWU%d1w5GTy!X#LeZsF_+~ccZmn3Fi)v^Z; zIG;?uU*yLLEYs61tjD>gXOFvSWsh{48xJvPNqKrIJtMdCz2cA2aC7TF?b@K`V!Lw- zE;zpH&ApqhoRjAHt}gK}>(qAc8dvrkD31*`<5<%C(&4_LqhQ3-GOf8~n^leG?+-@@^pjPa$J2gW@O)!b9hdTJ zTauyIJ&~rqeEZC1p9dWgx7{_WRc2=drMO=wcT7B{Zd58z`d)r_36r4V<6PGkCjpGfbnVMtm@;b}F!T?)Qav$_y7H5T;Mf!T}M zWxP9TNqrV?e5;b|pWd3^u2KAatk&3aDB@0 zZBaQNdCZ2#fblzYZnRCCjQ-GQWb-s8bX&<)?SxnUGdDYVFVk`xIf7@g3~$DX`H71_;r5OSZz1p`$_8y_9 z{;MJJ+n=>7Ewg;GnGHoz)&ID0z@F2!e$F7cWQ?d6s(!VY)_Gw})xCyMvsD={5i&H* zAIr_ACo8;Se6<*!-mm9Am79Iz^RVlc?%S5sg|E*SyIV{dd9{Mpf#d3cih5WKt=%ps zBEo)bt8EjmeCFYJRYU|b7d`p+-V|X2wOCYtyLP6t=!WH-kgdf0A};ytPfZiCwVPx{ z`g;zpe{8a4RQxQUwVU02<4X3w|9h;}XjhGWquqn{vVqq8g{*}BQF9=ybe_TM*!M=rFs1pN)yzctIXAoicsCe6>fit>wgZ#vp^hZOY0`J`rx zwdSK?GwR_xm9;5XjH|vf{O+Yg-)z;s*xt>;-vU`D-_SK<=~4eEVQkV~57{vyH6ebu>DqwE~Mx_A31Lg~$ zy`i52ew?GEluE%AN>ju*5z~dx2QQLP-G+E>o|En2+s&<^4*d zWD+<8PXs_Jp7_5Dpi(Gg3J?M;%Rn0{dcxQRh!ip+iHJ_e`!bM1MGxwI8Iedr+x>wI zq@&04fsADHfBccq^gpm8Q_u-~Uj|Z$YwSR(^_mzUb!}Xb2Cj`uqphg}v_Y%?Fs=;= zWq3s{hy)Tu!ji@RJsd$C+G{F6V2j1*Xk+7xo|LO7M2yb)(wc=%x-AclP9pLsbTF7m tf?*P!2EjJu5MD57P32Q4pdJ3tO_shCDT2=K;$ebx8ymcd$v!V9{2$sQiO>K5 literal 0 HcmV?d00001 diff --git a/Example/Lasso/Images.xcassets/square.imageset/Contents.json b/Example/Lasso/Images.xcassets/square.imageset/Contents.json new file mode 100644 index 0000000..e1f975e --- /dev/null +++ b/Example/Lasso/Images.xcassets/square.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "square.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Example/Lasso/Images.xcassets/square.imageset/square.pdf b/Example/Lasso/Images.xcassets/square.imageset/square.pdf new file mode 100644 index 0000000000000000000000000000000000000000..401614e288b4b160471c2776bed6f09762af3e1c GIT binary patch literal 2423 zcmai02~-nz8dqq7rWU#)x2z)&iMWtV?gK$2a)?|BP!TbkV}J-Gl1wmy+3G9S0=uOY zwCjai6uBOPMMdNiN`VDYKwLQr2#W^`R93*Ua=0^)EGYY4=grIf|KIohziYnv{elnR zdN_hk6db<(+3RPcxs!iut8c(j0TLh=9mY931H^rhEE0|aXo!dh5Iv+ZFr+}9F+v#P zLSnfD0$f~hN>~92V{z)WJA&2)5rW!|I?;o?oI*%zejJiuY>hFLQwKlAa9O+PnPh#% zJp~JAfs-De6u@;{cREMs}D-sGib_4>50m8_|Uj-zC0m@Qx zJV+^5Da4Qxpf3sBtKZs<_aI$Cb1T+h<->yL^rfiGNelYkqgC3#SqVP_iy*_RPcbjH)xYf zvYdC-)D_t8c{=5%;$VJQxs_#61J*kuXMC?|&PbPo-7uSxoNr~&J94|gKk+03JGXXM zx4w~=^VUzWqQveu_ilRgPdgNNdnLZCH`=;w91}cP5ihdm*B_jI+rk=Pz2Lk!)XUu| zkls3P5&YHJ!t0kF+MUVBHOAx)a zy|JxHNd37z*~b5#%*0w_F|#4?erNP;pG`Fevoq2%?9Y>%Cmzv1(ks;aPIGaiy1mhU zr{7lB^Mem&`QK{b{uiDC zLD8muY(FNk{YdV|zO#o_VB%);8|~Bh(Z4p?GM}g-HN2;q7~YNl-lD~HZ$taYw$Fzbn6c?c5eo=@`i>()el#qwB)hI-gp0 ztsw@dh-I5NJkk)RCoBC`L8ffKfY)=oG=#4LA9TO6eA}9~@a6dupB+=S{%s&NY}^0GY`hAqod)uxXHitrN%bWOJohV>t?!&Z#d4y^7J@=Oh`HV zee**4j>xRUd)|D0^I-B#<`m1-5PO{)!v(q?ecFYqr_8Q{+z7#n0uFbq%_?hZ-Ck(6NRn=vDS7Nb1Xxx_)9`!i- zGR>av*PJ+P_AGO*a_$FbtCCISg5PK44p&W#t$)+HVcxpR9LAL%_Pv$p`0tO*sYZD0 zCG3-ipr4WoVbjpw%Nf^r^rnWcDcjumgBPEjO;NM6QtZ8wU#0rjHx5zs%CahF4$W*U zxu!q8kYP+>v!BNPVIRJUGIWU@7qVvx2VZDw4gV8%z%bjpF#aF(W|PmliwlakuRq>= zhaCRc^R!7T)#Twb0{!Tns=CxGcGX|leErg`f3|v0eBXxHU&6Sj-_>zOL!!d+j5Xt* zPS;rWd^zEF{5d5%)nNK=)zFJ8Uc=aQKD%UK)_TNes1Gh{SD*Mb_@O` z7*zs9Uq~Vq?v|^8FcM-yLGMKzR3YFB0z@CFM2X-dio%Gu6&MJj=I?uS7sA3AdE^p8 zE49$PjJzPIJwfn;jso7X2#jynr3dO@p5QAK1O9wqKLC=PkQjj~p#n|-5O`ihbTzzu zL{}2QEe28pJYfVZRW56FL`LvO&|C^1Th{0}hQMW$Ng$QPpnzlwnMnf=kVy8(y==L% zLMa>r0gELdT + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + + + diff --git a/Example/Lasso/Presentation/SimpleCounter.swift b/Example/Lasso/Presentation/SimpleCounter.swift new file mode 100644 index 0000000..7b7b6a3 --- /dev/null +++ b/Example/Lasso/Presentation/SimpleCounter.swift @@ -0,0 +1,106 @@ +// +//===----------------------------------------------------------------------===// +// +// SimpleCounter.swift +// +// Created by Steven Grosmark on 6/16/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso +import WWLayout + +enum SimpleCounter: ScreenModule { + + enum Action: Equatable { + case didTapIncrement + case didTapDecrement + } + + struct State: Equatable { + var count: Int = 0 + } + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: SimpleCounterStore) -> Screen { + let controller = SimpleCounterViewController(store: store.asViewStore()) + return Screen(store, controller) + } + +} + +class SimpleCounterStore: LassoStore { + + override func handleAction(_ action: Action) { + switch action { + + case .didTapIncrement: + update { state in + state.count += 1 + } + + case .didTapDecrement: + update { state in + state.count = max(state.count - 1, 0) + } + } + } +} + +class SimpleCounterViewController: UIViewController, LassoView { + + let store: SimpleCounter.ViewStore + + init(store: SimpleCounter.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .background + + let label = UILabel() + label.font = .monospacedDigitSystemFont(ofSize: 64, weight: .bold) + label.textAlignment = .center + label.backgroundColor = UIColor.yellow.withAlphaComponent(0.25) + label.set(cornerRadius: 11) + + let incButton = UIButton(standardButtonWithTitle: "+1") + let decButton = UIButton(standardButtonWithTitle: "-1") + + view.addSubviews(label, incButton, decButton) + + label.layout + .center(in: .safeArea) + .size(200, 200) + + incButton.layout + .below(label, offset: 20) + .right(to: label) + .width(90) + decButton.layout + .below(label, offset: 20) + .left(to: label) + .width(90) + + store.observeState(\.count) { [weak label] count in + label?.text = "\(count)" + } + + incButton.bind(to: store, action: .didTapIncrement) + decButton.bind(to: store, action: .didTapDecrement) + } +} diff --git a/Example/Lasso/Presentation/Welcome.swift b/Example/Lasso/Presentation/Welcome.swift new file mode 100644 index 0000000..eaea086 --- /dev/null +++ b/Example/Lasso/Presentation/Welcome.swift @@ -0,0 +1,128 @@ +// +//===----------------------------------------------------------------------===// +// +// Welcome.swift +// +// Created by Steven Grosmark on 6/19/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +// MARK: - Welcome onboarding flow + +enum WelcomeOnboarding: NavigationFlowModule { + enum Output: Equatable { + case didFinish + } +} + +class WelcomeOnboardingFlow: Flow { + + override func createInitialController() -> UIViewController { + return assembleFirstScreen().controller + } + + private func assembleFirstScreen() -> Welcome.Screen { + return Welcome + .createScreen(with: Welcome.State(text: "Welcome!")) + .observeOutput { [weak self] output in + guard output == .didTapNext, let self = self else { return } + self.assembleFinalScreen().place(with: self.nextPushedInFlow) + } + } + + private func assembleFinalScreen() -> Welcome.Screen { + return Welcome + .createScreen(with: Welcome.State(text: "Have fun!")) + .observeOutput { [weak self] output in + guard output == .didTapNext, let self = self else { return } + self.dispatchOutput(.didFinish) + } + } + +} + +// MARK: - Welcome screen + +enum Welcome: ScreenModule { + + struct State: Equatable { + let text: String + init(text: String = "") { + self.text = text + } + } + + enum Action: Equatable { + case didTapNext + } + + enum Output: Equatable { + case didTapNext + } + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: WelcomeStore) -> Screen { + let controller = WelcomeViewController(store: store.asViewStore()) + return Screen(store, controller) + } + +} + +class WelcomeStore: LassoStore { + + override func handleAction(_ action: Welcome.Action) { + switch action { + + case .didTapNext: + dispatchOutput(.didTapNext) + } + } +} + +class WelcomeViewController: UIViewController, LassoView { + + let store: Welcome.ViewStore + + init(store: Welcome.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .background + + let label = UILabel() + label.font = UIFont(name: "SnellRoundhand-Bold", size: 64) + label.textAlignment = .center + label.text = store.state.text + + let button = UIButton(standardButtonWithTitle: "Next") + + view.addSubviews(label, button) + + label.layout + .fill(.safeArea, except: .bottom, inset: 30) + + button.layout + .fillWidth(of: .safeArea, maximum: 300) + .bottom(to: .safeArea, offset: -30) + + button.bind(to: store, action: .didTapNext) + } +} diff --git a/Example/Lasso/Sample Catalog/SampleCatalog.swift b/Example/Lasso/Sample Catalog/SampleCatalog.swift new file mode 100644 index 0000000..893865b --- /dev/null +++ b/Example/Lasso/Sample Catalog/SampleCatalog.swift @@ -0,0 +1,104 @@ +// +//===----------------------------------------------------------------------===// +// +// SampleCatalog.swift +// +// Created by Steven Grosmark on 5/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum SampleCatalog: ScreenModule { + + static var defaultInitialState: State { + return State(title: "Lasso Examples") + } + + static func createScreen(with store: SampleCatalogStore) -> Screen { + let controller = SampleCatalogViewController(store: store.asViewStore()) + return Screen(store, controller) + } + + enum Action: Equatable { + case didSelectItem(item: CatalogItem) + } + + enum Output: Equatable { + case didSelectItem(item: CatalogItem) + } + + struct State: Equatable { + var title: String + let sections: [Section] + + init(title: String) { + self.title = title + sections = Section.allCases + } + } + + enum Section: String, CaseIterable, CustomStringConvertible { + case presentation + case simple + case fancy + + var items: [SampleCatalog.CatalogItem] { + switch self { + case .presentation: return [.presentationSimpleCounter, .presentationOnboarding] + case .simple: return [.counter, .search, .randomItems, .bindings, .login] + case .fancy: return [.tabs, .splitView, .foodOnboarding, .onboarding, .signup, .strangeFlow, .onTheFly, .windowTransition, .survey, .searchAndTrack, .myDay, .pageController] + } + } + + var description: String { return "\(self.rawValue)".capitalized } + } + + enum CatalogItem: String, CustomStringConvertible { + case bindings = "UIKit Bindings" + case counter + case foodOnboarding = "Food onboarding" + case login + case myDay = "My Day (Child View Controller Pattern)" + case onboarding + case onTheFly = "On the fly Flow (Onboarding -> Text)" + case pageController = "Page Controller (Non-standard Flow ARC)" + case presentationOnboarding = "welcome onboarding" + case presentationSimpleCounter = "simple counter" + case randomItems = "Random items" + case search + case searchAndTrack = "Search & Track" + case signup = "Signup Flow" + case splitView = "Split view" + case strangeFlow = "Strange flow" + case survey + case tabs = "Tab bar controller" + case windowTransition = "Window Transition" + + var description: String { return "\(self.rawValue)".capitalized } + } + +} + +// MARK: - SampleCatalogStore + +class SampleCatalogStore: LassoStore { + + override func handleAction(_ action: Action) { + switch action { + + case .didSelectItem(let item): + dispatchOutput(.didSelectItem(item: item)) + } + } + +} diff --git a/Example/Lasso/Sample Catalog/SampleCatalogFlow.swift b/Example/Lasso/Sample Catalog/SampleCatalogFlow.swift new file mode 100644 index 0000000..3c465fb --- /dev/null +++ b/Example/Lasso/Sample Catalog/SampleCatalogFlow.swift @@ -0,0 +1,207 @@ +// +//===----------------------------------------------------------------------===// +// +// SampleCatalogFlow.swift +// +// Created by Steven Grosmark on 5/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +class SampleCatalogFlow: Flow { + + override func createInitialController() -> UIViewController { + return SampleCatalog.createScreen() + .observeOutput { [weak self] in self?.handleOutput($0) } + .controller + } + + private func handleOutput(_ output: SampleCatalog.Output) { + guard case let .didSelectItem(item) = output else { return } + switch item { + + case .presentationSimpleCounter: + SimpleCounter.createScreen().place(with: nextPushedInFlow) + + case .presentationOnboarding: + WelcomeOnboardingFlow() + .observeOutput { [weak self] _ in self?.unwind() } + .start(with: nextPresentedInFlow?.withDismissibleNavigationEmbedding()) + + case .counter: + showCounterScreen() + + case .search: + showSearchScreen() + + case .randomItems: + RandomItemsFlow().start(with: nextPushedInFlow) + + case .tabs: + TabsFlow().start(with: nextPresentedInFlow?.withDismissibleNavigationEmbedding()) + + case .splitView: + showSplitViewFlow() + + case .bindings: + UIKitBindingsScreenModule.createScreen().place(with: nextPushedInFlow) + + case .foodOnboarding: + showFoodOnboardingFlow() + + case .onboarding: + showOnboardingFlow() + + case .signup: + showSignupFlow() + + case .strangeFlow: + StrangeFlow().start(with: nextPushedInFlow) + + case .onTheFly: + showOnTheFlyFlow() + + case .survey: + showSurveyFlow() + + case .windowTransition: + ChooseWindowTransitionFlow().start(with: rootOfApplicationWindow(using: .push)) + + case .searchAndTrack: + SearchAndTrackFlow().start(with: nextPresentedInFlow?.withDismissibleNavigationEmbedding()) + + case .myDay: + MyDayFlow().start(with: nextPushedInFlow) + + case .login: + LoginScreenModule + .createScreen() + .observeOutput { [ weak self] _ in self?.unwind() } + .place(with: nextPushedInFlow) + + case .pageController: + showPageController() + } + } + + private func showPageController() { + PageControllerFlow() + .observeOutput({ [weak self] output in + switch output { + + case .didFinish: + self?.unwind() + } + + }) + .start(with: nextPresentedInFlow?.withPageEmbedding()) + } + + private func showCounterScreen() { + CounterScreenModule.createScreen(with: CounterScreenModule.State(title: "How many?")) + .observeOutput { [weak self] output in + if output == .didTapNext { + self?.unwind() + } + } + .place(with: nextPushedInFlow) + } + + private func showSearchScreen() { + SearchScreenModule + .createScreen(configure: { searchStore in + searchStore.getSearchResults = Food.getFoods + }) + .observeOutput({ [weak self] output in + switch output { + + case .didSelectItem: + self?.unwind() + } + }) + .place(with: nextPushedInFlow) + } + + private func showFoodOnboardingFlow() { + FoodOnboardingFlow() + .observeOutput { [weak self] _ in + self?.unwind() + } + .start(with: nextPresentedInFlow?.withDismissibleNavigationEmbedding()) + } + + private func showOnboardingFlow() { + OnboardingFlow() + .observeOutput { [weak self] output in + switch output { + case .didFinish: + self?.unwind() + } + } + .start(with: nextPresentedInFlow?.withDismissibleNavigationEmbedding()) + } + + private func showSignupFlow() { + SignupFlow() + .observeOutput { [weak self] _ in + self?.unwind() + } + .start(with: nextPushedInFlow) + } + + private func showOnTheFlyFlow() { + FoodOnboardingFlow() + .observeOutput { [weak self] output in + switch output { + + case .didFinish: + let textState = TextScreenModule.State(title: "Wo0t!", description: "Welcome to the machine!", buttons: ["Okay!"]) + TextScreenModule.createScreen(with: textState) + .observeOutput { [weak self] output in + switch output { + + case .didTapButton: + self?.unwind() + } + } + .place(with: self?.nextPresentedInFlow) + } + } + .start(with: nextPushedInFlow) + } + + private func showSurveyFlow() { + SurveyFlow.favorites + .observeOutput { [weak self] output in + switch output { + case .didFinish(responses: let responses): + dump(responses) + self?.unwind() + } + } + .start(with: nextPresentedInFlow?.withNavigationEmbedding()) + } + + private func showSplitViewFlow() { + SplitViewFlow() + .observeOutput({ output in + switch output { + + case .didPressDone: + SampleCatalogFlow().start(with: rootOfApplicationWindow(using: .slide(from: .left))?.withNavigationEmbedding()) + } + }) + .start(with: rootOfApplicationWindow(using: .slide(from: .top))) + } + +} diff --git a/Example/Lasso/Sample Catalog/SampleCatalogViewController.swift b/Example/Lasso/Sample Catalog/SampleCatalogViewController.swift new file mode 100644 index 0000000..a3b6c63 --- /dev/null +++ b/Example/Lasso/Sample Catalog/SampleCatalogViewController.swift @@ -0,0 +1,97 @@ +// +//===----------------------------------------------------------------------===// +// +// SampleCatalogViewController.swift +// +// Created by Steven Grosmark on 5/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +class SampleCatalogViewController: UIViewController, LassoView { + + private let tableView: UITableView + let store: SampleCatalog.ViewStore + + init(store: SampleCatalog.ViewStore) { + self.tableView = UITableView() + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override open func viewDidLoad() { + super.viewDidLoad() + + setupViews() + edgesForExtendedLayout = UIRectEdge() + + store.observeState(\.title) { [weak self] title in + self?.title = title + } + } + + private func setupViews() { + view.backgroundColor = .background + + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 80 + tableView.register(type: UITableViewCell.self) + tableView.dataSource = self + tableView.delegate = self + + view.addSubview(tableView) + tableView.layout.fill(.safeArea) + } +} + +extension SampleCatalogViewController: UITableViewDataSource { + + public func numberOfSections(in tableView: UITableView) -> Int { + return store.state.sections.count + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return store.state.sections[section].items.count + } + + public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return store.state.sections[section].description + } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + return UIView() + } + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return 20 + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let section = store.state.sections[indexPath.section] + let cell = tableView.dequeueCell(type: UITableViewCell.self, indexPath: indexPath) + cell.textLabel?.text = section.items[indexPath.row].description + return cell + } +} + +extension SampleCatalogViewController: UITableViewDelegate { + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let section = store.state.sections[indexPath.section] + let item = section.items[indexPath.row] + store.dispatchAction(.didSelectItem(item: item)) + } +} diff --git a/Example/Lasso/Samples/ChooseWindowTransition.swift b/Example/Lasso/Samples/ChooseWindowTransition.swift new file mode 100644 index 0000000..94dd693 --- /dev/null +++ b/Example/Lasso/Samples/ChooseWindowTransition.swift @@ -0,0 +1,195 @@ +// +//===----------------------------------------------------------------------===// +// +// ChooseWindowTransition.swift +// +// Created by Steven Grosmark on 6/6/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +// MARK: - Flow + +/// A flow that recursively restarts itself to demonstrate various +/// transition types for replacing a UIWindow's rootViewController. +/// When finished, the SampleCatalogFlow is re-started at the root +/// of the application window. +class ChooseWindowTransitionFlow: Flow { + + override func createInitialController() -> UIViewController { + return ChooseWindowTransition + .createScreen() + .observeOutput { output in + switch output { + + case .didSelectTransition(let transition): + ChooseWindowTransitionFlow().start(with: rootOfApplicationWindow(using: transition)) + + case .didTapDone: + SampleCatalogFlow().start(with: rootOfApplicationWindow(using: .pop)?.withNavigationEmbedding()) + } + } + .controller + } + +} + +// MARK: - Module + +enum ChooseWindowTransition: ScreenModule { + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: ChooseWindowTransitionStore) -> Screen { + let controller = ChooseWindowTransitionController(store: store.asViewStore()) + return Screen(store, controller) + } + + enum Action: Equatable { + case didSelectTransition(UIWindow.Transition) + case didTapDone + } + + typealias Output = Action + + struct State: Equatable { + let background: UIColor + let items: [Item] + } + + struct Item: Equatable { + let name: String + let transition: UIWindow.Transition + init(_ name: String, _ transition: UIWindow.Transition) { + self.name = name + self.transition = transition + } + } + +} + +class ChooseWindowTransitionStore: LassoStore { + + override func handleAction(_ action: Action) { + dispatchOutput(action) + } + +} + +extension ChooseWindowTransition.State { + typealias Item = ChooseWindowTransition.Item + + init() { + background = UIColor(red: .random(in: 0.75...1.0), green: .random(in: 0.75...1.0), blue: .random(in: 0.75...1.0), alpha: 1.0) + items = [ + Item("Fade\n(fade)", .crossfade), + Item("Slide ←\n(push)", .slide(from: .right)), + Item("Slide →\n(pop)", .slide(from: .left)), + Item("Slide ↑", .slide(from: .bottom)), + Item("Slide ↓", .slide(from: .top)), + + Item("Cover ←", .cover(from: .right)), + Item("Cover →", .cover(from: .left)), + Item("Cover ↑\n(present)", .cover(from: .bottom)), + Item("Cover ↓", .cover(from: .top)), + + Item("Reveal ←", .reveal(from: .right)), + Item("Reveal →", .reveal(from: .left)), + Item("Reveal ↑", .reveal(from: .bottom)), + Item("Reveal ↓\n(dismiss)", .reveal(from: .top)) + ] + } +} + +// MARK: - View controller + +class ChooseWindowTransitionController: UIViewController, LassoView { + + let store: ChooseWindowTransition.ViewStore + private var collectionView: UICollectionView! + + init(store: ChooseWindowTransition.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = store.state.background + + let button = UIButton(standardButtonWithTitle: "Done 👋") + view.addSubview(button) + button.layout.fill(.safeArea, axis: .x, inset: 30).bottom(to: .safeArea, offset: -30) + + button.bind(to: store, action: .didTapDone) + + let flowLayout = UICollectionViewFlowLayout() + collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: flowLayout) + collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell") + + let w = floor(min(view.frame.width, view.frame.height) / 3) - 28 + flowLayout.itemSize = CGSize(width: w, height: w) + collectionView.dataSource = self + collectionView.delegate = self + + collectionView.backgroundColor = store.state.background + view.addSubview(collectionView) + collectionView.layout + .top(to: .safeArea, offset: 20) + .fill(.safeArea, axis: .x, inset: 20) + .bottom(to: button, edge: .top, offset: -30) + } + +} + +extension ChooseWindowTransitionController: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return store.state.items.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) + let item = store.state.items[indexPath.row] + + cell.contentView.subviews.reversed().forEach { $0.removeFromSuperview() } + + let borderView = UIView() + .set(cornerRadius: 7) + .set(borderColor: .black, thickness: 1) + + cell.contentView.addSubview(borderView) + borderView.layout.fill(.superview, inset: 8) + + let label = UILabel() + label.text = item.name + label.textColor = .black + label.numberOfLines = 0 + label.textAlignment = .center + cell.contentView.addSubview(label) + label.layout.fill(.superview, inset: 5) + + return cell + } +} + +extension ChooseWindowTransitionController: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let item = store.state.items[indexPath.row] + store.dispatchAction(.didSelectTransition(item.transition)) + } + +} diff --git a/Example/Lasso/Samples/Counter/CounterScreen.swift b/Example/Lasso/Samples/Counter/CounterScreen.swift new file mode 100644 index 0000000..f5da611 --- /dev/null +++ b/Example/Lasso/Samples/Counter/CounterScreen.swift @@ -0,0 +1,74 @@ +// +//===----------------------------------------------------------------------===// +// +// CounterModule.swift +// +// Created by Steven Grosmark on 5/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum CounterScreenModule: ScreenModule { + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: CounterStore) -> Screen { + let controller = CounterViewController(store: store.asViewStore()) + return Screen(store, controller) + } + + enum Action: Equatable { + case didTapIncrement + case didTapDecrement + case didTapNext + } + + enum Output: Equatable { + case didTapNext + } + + struct State: Equatable { + let style: Style + let title: String + var counter: Int + + enum Style { case light, dark, purple } + + init(title: String = "", counter: Int = 0, style: Style = .light) { + self.title = title + self.counter = max(counter, 0) + self.style = style + } + } +} + +class CounterStore: LassoStore { + + override func handleAction(_ action: Action) { + switch action { + + case .didTapIncrement: + update { state in + state.counter += 1 + } + + case .didTapDecrement: + update { state in + state.counter = max(state.counter - 1, 0) + } + + case .didTapNext: + dispatchOutput(.didTapNext) + } + } +} diff --git a/Example/Lasso/Samples/Counter/CounterViewController.swift b/Example/Lasso/Samples/Counter/CounterViewController.swift new file mode 100644 index 0000000..acc9de9 --- /dev/null +++ b/Example/Lasso/Samples/Counter/CounterViewController.swift @@ -0,0 +1,106 @@ +// +//===----------------------------------------------------------------------===// +// +// CounterViewController.swift +// +// Created by Steven Grosmark on 5/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +class CounterViewController: UIViewController, LassoView { + + let store: CounterScreenModule.ViewStore + + init(store: CounterScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = store.state.style.backgroundColor + + let titleLabel = UILabel(headline: store.state.title) + + let decrementButton = UIButton(type: .system).set(title: "-", with: .systemFont(ofSize:32)) + let incrementButton = UIButton(type: .system).set(title: "+", with: .systemFont(ofSize:32)) + + let display = UILabel() + display.font = .systemFont(ofSize: 64, weight: .bold) + display.textAlignment = .center + display.backgroundColor = store.state.style.counterColor + display.set(cornerRadius: 11) + + let nextButton = UIButton(standardButtonWithTitle: "Next") + + view.addSubviews(titleLabel, decrementButton, display, incrementButton, nextButton) + + titleLabel.layout + .fill(.safeArea, except: .bottom, inset: 20) + + decrementButton.layout + .leading(to: .safeArea, offset: 20) + .centerY(to: .safeArea) + .size(44) + + incrementButton.layout + .trailing(to: .safeArea, offset: -20) + .centerY(to: .safeArea) + .size(44) + + display.layout + .leading(to: decrementButton, edge: .trailing, offset: 20) + .trailing(to: incrementButton, edge: .leading, offset: -20) + .height(toWidth: 1.0, priority: .high) + .top(.greaterOrEqual, to: titleLabel, edge: .bottom, offset: 20) + .bottom(.lessOrEqual, to: nextButton, edge: .top, offset: -20) + .centerY(to: .safeArea) + + nextButton.layout + .fillWidth(of: .safeArea, inset: 20, maximum: 300) + .bottom(to: .safeArea, edge: .bottom, offset: -20) + + decrementButton.bind(to: store, action: .didTapDecrement) + incrementButton.bind(to: store, action: .didTapIncrement) + nextButton.bind(to: store, action: .didTapNext) + + store.observeState(\.counter) { value in + display.text = "\(value)" + } + } + +} + +extension CounterScreenModule.State.Style { + + fileprivate var backgroundColor: UIColor { + switch self { + case .light: return .white + case .dark: return .lightGray + case .purple: return .lightGray + } + } + + fileprivate var counterColor: UIColor { + switch self { + case .light: return .lightGray + case .dark: return .white + case .purple: return .purple + } + } +} diff --git a/Example/Lasso/Samples/Login/Login.swift b/Example/Lasso/Samples/Login/Login.swift new file mode 100644 index 0000000..b9663db --- /dev/null +++ b/Example/Lasso/Samples/Login/Login.swift @@ -0,0 +1,109 @@ +// +//===----------------------------------------------------------------------===// +// +// Login.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum LoginScreenModule: ScreenModule { + + enum Action: Equatable { + case didEditUsername(String) + case didEditPassword(String) + case didTapLogin + } + + enum Output: Equatable { + case didLogin + } + + struct State: Equatable { + var username: String = "" + var password: String = "" + var canLogin: Bool = false + var error: String? + var phase: Phase = .idle + + enum Phase { case idle, busy } + } + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: LoginScreenStore) -> Screen { + let controller = LoginViewController(store: store.asViewStore()) + return Screen(store, controller) + } + +} + +final class LoginScreenStore: LassoStore { + + override func handleAction(_ action: LoginScreenModule.Action) { + switch action { + + case .didEditUsername(let username): + update { state in + state.username = username + state.canLogin = !username.isEmpty && !state.password.isEmpty + } + + case .didEditPassword(let password): + update { state in + state.password = password + state.canLogin = !state.username.isEmpty && !password.isEmpty + } + + case .didTapLogin: + login() + } + } + + private func login() { + guard state.phase == .idle else { return } + guard state.canLogin else { + update { state in + state.error = "Please enter your username and password" + } + return + } + + update { state in + state.phase = .busy + state.error = nil + state.canLogin = false + } + + LoginService.shared.login(state.username, password: state.password) { [weak self] result in + guard let self = self else { return } + + switch result { + + case .success: + self.update { state in + state.phase = .idle + } + self.dispatchOutput(.didLogin) + + case .failure: + self.update { state in + state.phase = .idle + state.canLogin = !state.username.isEmpty && !state.password.isEmpty + state.error = "Invalid login" + } + } + } + } +} diff --git a/Example/Lasso/Samples/Login/LoginService.swift b/Example/Lasso/Samples/Login/LoginService.swift new file mode 100644 index 0000000..e721165 --- /dev/null +++ b/Example/Lasso/Samples/Login/LoginService.swift @@ -0,0 +1,50 @@ +// +//===----------------------------------------------------------------------===// +// +// LoginService.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation +import Lasso + +protocol LoginServiceProtocol { + func login(_ username: String, password: String, completion: @escaping (Result) -> Void) +} + +struct LoginService: LoginServiceProtocol { + + #if swift(>=5.1) + @Mockable static private(set) var shared: LoginServiceProtocol = LoginService() + #else + static let shared: LoginServiceProtocol = LoginService() + #endif + + enum LoginError: Error { + case invalidCredentials + } + + func login(_ username: String, password: String, completion: @escaping (Result) -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + let isLoggedIn = Int.random(in: 0...100) < LoginService.successRate + switch isLoggedIn { + case true: completion(.success(())) + case false: completion(.failure(LoginError.invalidCredentials)) + } + } + } + + /// Chance of success, from 0 through 100 percent + static var successRate: Int = 100 + +} diff --git a/Example/Lasso/Samples/Login/LoginViewController.swift b/Example/Lasso/Samples/Login/LoginViewController.swift new file mode 100644 index 0000000..0e0392b --- /dev/null +++ b/Example/Lasso/Samples/Login/LoginViewController.swift @@ -0,0 +1,108 @@ +// +//===----------------------------------------------------------------------===// +// +// LoginViewController.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout + +final class LoginViewController: UIViewController { + + private let store: LoginScreenModule.ViewStore + + private let headerLabel = UILabel(headline: "Login") + private let usernameField = UITextField(placeholder: "Username") + private let passwordField = UITextField(placeholder: "Password") + private let loginButton = UIButton(standardButtonWithTitle: "Login") + private let activityIndicator = UIActivityIndicatorView(style: .gray) + private let errorLabel = UILabel() + + init(store: LoginScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .background + + setupSubviews() + setupConstraints() + setupBindings() + } + + private func setupSubviews() { + passwordField.isSecureTextEntry = true + activityIndicator.hidesWhenStopped = true + errorLabel.numberOfLines = 0 + + view.addSubviews(headerLabel, usernameField, passwordField, loginButton, activityIndicator, errorLabel) + } + + private func setupConstraints() { + headerLabel.layout + .top(to: .safeArea, offset: 50) + .centerX(to: .safeArea) + + usernameField.layout + .below(headerLabel, offset: 50) + .fillWidth(of: .safeArea, inset: 20, maximum: 280) + + passwordField.layout + .below(usernameField, offset: 30) + .fillWidth(of: .safeArea, inset: 20, maximum: 280) + + loginButton.layout + .below(passwordField, offset: 50) + .fillWidth(of: .safeArea, inset: 20, maximum: 280) + + activityIndicator.layout + .below(loginButton, offset: 20) + .centerX(to: .safeArea) + + errorLabel.layout + .below(activityIndicator, offset: 20) + .fill(.safeArea, axis: .x, inset: 20) + } + + private func setupBindings() { + // State observations + store.observeState(\.username) { [weak self] username in + self?.usernameField.text = username + } + store.observeState(\.password) { [weak self] password in + self?.passwordField.text = password + } + store.observeState(\.canLogin) { [weak self] canLogin in + self?.loginButton.isEnabled = canLogin + } + store.observeState(\.error) { [weak self] error in + self?.errorLabel.text = error + } + store.observeState(\.phase) { [weak self] phase in + self?.activityIndicator.animating = phase == .busy + self?.view.isUserInteractionEnabled = phase == .idle + } + + // User actions + loginButton.bind(to: store, action: .didTapLogin) + usernameField.bindTextDidChange(to: store, mapping: LoginScreenModule.Action.didEditUsername) + passwordField.bindTextDidChange(to: store, mapping: LoginScreenModule.Action.didEditPassword) + } + +} diff --git a/Example/Lasso/Samples/MyDay/CalendarScreen.swift b/Example/Lasso/Samples/MyDay/CalendarScreen.swift new file mode 100644 index 0000000..e996fe1 --- /dev/null +++ b/Example/Lasso/Samples/MyDay/CalendarScreen.swift @@ -0,0 +1,88 @@ +// +//===----------------------------------------------------------------------===// +// +// CalendarScreen.swift +// +// Created by Trevor Beasty on 10/21/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Lasso +import WWLayout + +enum CalendarScreenModule: ScreenModule { + + struct State: Equatable { + var selectedDate = Date() + } + + enum Action: Equatable { + case didSelectDate(Date) + } + + enum Output: Equatable { } + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: CalendarStore) -> Screen { + let controller = CalendarController(store: store.asViewStore()) + return Screen(store, controller) + } + +} + +class CalendarStore: LassoStore { + + override func handleAction(_ action: CalendarScreenModule.Action) { + switch action { + + case .didSelectDate(let date): + update { $0.selectedDate = date } + } + } + +} + +class CalendarController: UIViewController, LassoView { + + let store: CalendarScreenModule.ViewStore + + private let datePicker = UIDatePicker() + + init(store: CalendarScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { fatalError() } + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + bind() + } + + private func setUpView() { + view.addSubview(datePicker) + datePicker.layout + .fill(.superview) + datePicker.datePickerMode = .date + } + + private func bind() { + store.observeState(\.selectedDate) { [weak self] (date) in + self?.datePicker.setDate(date, animated: true) + } + + datePicker.bindDateChange(to: store) { .didSelectDate($0) } + } + +} diff --git a/Example/Lasso/Samples/MyDay/DailyLogScreen.swift b/Example/Lasso/Samples/MyDay/DailyLogScreen.swift new file mode 100644 index 0000000..4e40254 --- /dev/null +++ b/Example/Lasso/Samples/MyDay/DailyLogScreen.swift @@ -0,0 +1,237 @@ +// +//===----------------------------------------------------------------------===// +// +// DailyLogScreen.swift +// +// Created by Trevor Beasty on 10/21/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Lasso +import WWLayout + +// MARK: - Module + +enum DailyLogScreenModule: ScreenModule { + + struct State: Equatable { + var display: Display + var isLoading: Bool + + enum Display: Equatable { + case main(log: DailyLog) + case error + case empty + } + } + + enum Action: Equatable { + case updateForDate(Date) + } + + static var defaultInitialState: DailyLogScreenModule.State { + return State(display: .empty, isLoading: false) + } + + static func createScreen(with store: DailyLogStore) -> Screen { + let viewStore: DailyLogViewModule.ViewStore = store.asViewStore(stateMap: { $0.toViewState() }) + let controller = DailyLogController(store: viewStore) + return Screen(store, controller) + } + +} + +extension DailyLogScreenModule.State { + + func toViewState() -> DailyLogViewModule.ViewState { + let text: String + switch display { + + case .empty: + text = "" + + case .error: + text = "Error" + + case .main(log: let log): + text = """ + program: \(log.program) + calories consumed: \(log.caloriesConsumed) + calories remaining: \(log.caloriesRemaining) + steps taken: \(log.stepsTaken) + """ + } + return DailyLogViewModule.ViewState(text: text, isLoading: isLoading) + } + +} + +// MARK: - Store + +class DailyLogStore: LassoStore { + + var getDailyLog = DailyLogService.getDailyLog + var user = User.current + + override func handleAction(_ action: DailyLogScreenModule.Action) { + switch action { + + case .updateForDate(let date): + update { $0.isLoading = true } + getDailyLog(date, user.id) { [weak self] result in + self?.batchUpdate { $0.isLoading = false } + switch result { + + case .success(let log): + self?.update { $0.display = .main(log: log) } + + case .failure: + self?.update { $0.display = .error } + } + } + } + } + +} + +// MARK: - View + +enum DailyLogViewModule: ViewModule { + + struct ViewState: Equatable { + var text: String + var isLoading: Bool + } + + typealias ViewAction = DailyLogScreenModule.Action + +} + +class DailyLogController: UIViewController, LassoView { + + let store: DailyLogViewModule.ViewStore + + private let label = UILabel() + private let activityIndicator = UIActivityIndicatorView(style: .whiteLarge) + private let titleLabel = UILabel() + + init(store: DailyLogViewModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { fatalError() } + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + bind() + } + + private func setUpView() { + view.addSubview(titleLabel) + titleLabel.layout + .centerX(to: .superview) + .top(to: .superview) + .height(50) + [label, activityIndicator].forEach { + view.addSubview($0) + $0.layout + .left(to: .superview) + .right(to: .superview) + .top(to: titleLabel, edge: .bottom) + .bottom(to: .superview, offset: -8) + .height(120) + } + + label.textAlignment = .center + label.textColor = .white + label.numberOfLines = 0 + label.font = .boldSystemFont(ofSize: 20) + activityIndicator.hidesWhenStopped = true + view.backgroundColor = .blue + view.layer.masksToBounds = true + view.layer.cornerRadius = 4 + titleLabel.textAlignment = .center + titleLabel.text = "Daily Log" + titleLabel.font = .boldSystemFont(ofSize: 20) + titleLabel.textColor = .white + } + + private func bind() { + store.observeState(\.text) { [weak self] (text) in + self?.label.text = text + } + + store.observeState(\.isLoading) { [weak self] (isLoading) in + if isLoading { + self?.activityIndicator.startAnimating() + self?.label.alpha = 0.3 + } + else { + self?.activityIndicator.stopAnimating() + self?.label.alpha = 1.0 + } + } + } + +} + +// MARK: - User & service + +class User { + + static let current = User(id: "abc123", program: .B) + + let id: String + var program: Program + + init(id: String, program: Program) { + self.id = id + self.program = program + } + +} + +enum Program: String, CaseIterable { + case A + case B + case C + + static func random() -> Program { + return allCases.randomElement() ?? .A + } +} + +struct DailyLog: Equatable { + let program: Program + let caloriesConsumed: Int + let caloriesRemaining: Int + let stepsTaken: Int +} + +enum DailyLogService { + + static func getDailyLog(date: Date, userId: String, completion: @escaping (Result) -> Void) { + let components = Calendar.current.dateComponents([.month, .day], from: date) + let day = components.day ?? 0 + let month = components.month ?? 0 + let caloriesConsumed = 20 * day + let log = DailyLog(program: .random(), + caloriesConsumed: caloriesConsumed, + caloriesRemaining: 2500 - caloriesConsumed, + stepsTaken: month * 500 + day * 10) + DispatchQueue.global().asyncAfter(deadline: .now() + 0.4) { + completion(.success(log)) + } + } + +} diff --git a/Example/Lasso/Samples/MyDay/MyDayCardsScreen.swift b/Example/Lasso/Samples/MyDay/MyDayCardsScreen.swift new file mode 100644 index 0000000..e8f88f6 --- /dev/null +++ b/Example/Lasso/Samples/MyDay/MyDayCardsScreen.swift @@ -0,0 +1,211 @@ +// +//===----------------------------------------------------------------------===// +// +// MyDayCardsScreen.swift +// +// Created by Trevor Beasty on 10/21/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Lasso +import WWLayout + +enum MyDayCardsScreenModule: ScreenModule { + + struct State: Equatable { + var cards: [String] = [] + var phase: Phase = .idle + + enum Phase { + case idle + case busy + case error + } + + } + + enum Action: Equatable { + case updateForDate(date: Date) + case didSelectCard(idx: Int) + } + + enum Output: Equatable { + case didSelectCard(card: String) + } + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: MyDayCardsStore) -> Screen { + let controller = MyDayCardsController(store: store.asViewStore()) + return Screen(store, controller) + } + +} + +class MyDayCardsStore: LassoStore { + + var getCards = { (date: Date, completion: @escaping (Result<[String], Error>) -> Void) in + DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { + let allCards = ["Yay apples", "Yay grapes", "Whoa exercise", "Get fit!!", "Healthy desserts", "Yay tomatoes"] + var cards = [String]() + for card in allCards { + if Bool.random() { cards.append(card) } + } + completion(.success(cards.shuffled())) + } + } + + override func handleAction(_ action: MyDayCardsScreenModule.Action) { + switch action { + + case .didSelectCard(idx: let idx): + let card = state.cards[idx] + dispatchOutput(.didSelectCard(card: card)) + + case .updateForDate(date: let date): + update { $0.phase = .busy } + getCards(date) { [weak self] result in + guard let self = self else { return } + switch result { + + case .success(let cards): + self.update { + $0.cards = cards + $0.phase = .idle + } + + case .failure: + self.update { + $0.phase = .error + } + } + } + } + } + +} + +class MyDayCardsController: UIViewController, LassoView { + + let store: MyDayCardsScreenModule.ViewStore + + private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionLayout) + private let collectionLayout = UICollectionViewFlowLayout() + private let activityIndicator = UIActivityIndicatorView(style: .gray) + private let errorLabel = UILabel() + private let titleLabel = UILabel() + + init(store: MyDayCardsScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { fatalError() } + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + bind() + } + + private func setUpView() { + view.addSubview(titleLabel) + titleLabel.layout + .left(to: .superview) + .right(to: .superview) + .top(to: .superview) + .height(50) + [collectionView, activityIndicator, errorLabel].forEach { + view.addSubview($0) + $0.layout + .left(to: .superview) + .right(to: .superview) + .top(to: titleLabel, edge: .bottom, offset: 8) + .bottom(to: .superview, offset: -8) + .height(100) + } + collectionView.register(CardCell.self, forCellWithReuseIdentifier: "CardCell") + collectionView.dataSource = self + collectionView.delegate = self + collectionLayout.scrollDirection = .horizontal + collectionView.backgroundColor = .white + activityIndicator.hidesWhenStopped = true + errorLabel.textAlignment = .center + errorLabel.text = "Something went wrong" + errorLabel.font = .boldSystemFont(ofSize: 16) + titleLabel.textAlignment = .center + titleLabel.text = "Articles" + titleLabel.font = .boldSystemFont(ofSize: 20) + } + + private func bind() { + store.observeState(\.cards) { [weak self] _ in + self?.collectionView.reloadData() + } + + store.observeState(\.phase) { [weak self] (phase) in + if phase == .busy { self?.activityIndicator.startAnimating() } + else { self?.activityIndicator.stopAnimating() } + self?.collectionView.isHidden = phase == .error + self?.collectionView.alpha = phase == .busy ? 0.4 : 1.0 + self?.errorLabel.isHidden = !(phase == .error) + } + } + +} + +extension MyDayCardsController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return store.state.cards.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath) as? CardCell else { fatalError() } + let card = store.state.cards[indexPath.row] + cell.label.text = card + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let height = collectionView.frame.height - 8.0 + return CGSize(width: 120, height: height) + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + store.dispatchAction(.didSelectCard(idx: indexPath.row)) + } + +} + +class CardCell: UICollectionViewCell { + + let label = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.addSubview(label) + label.layout.fill(.superview) + label.textAlignment = .center + label.textColor = .white + label.backgroundColor = .orange + label.numberOfLines = 0 + label.layer.masksToBounds = true + label.layer.cornerRadius = 4 + } + + required init?(coder: NSCoder) { fatalError() } + +} diff --git a/Example/Lasso/Samples/MyDay/MyDayController.swift b/Example/Lasso/Samples/MyDay/MyDayController.swift new file mode 100644 index 0000000..240653b --- /dev/null +++ b/Example/Lasso/Samples/MyDay/MyDayController.swift @@ -0,0 +1,87 @@ +// +//===----------------------------------------------------------------------===// +// +// MyDayController.swift +// +// Created by Trevor Beasty on 10/21/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Lasso +import WWLayout + +class MyDayController: UIViewController { + + var calendarScreenFactory = CalendarScreenModule.ScreenFactory() + var dailyLogScreenFactory = DailyLogScreenModule.ScreenFactory() + var cardsScreenFactory = MyDayCardsScreenModule.ScreenFactory() + + private lazy var calendarScreen = calendarScreenFactory.createScreen(with: CalendarScreenModule.State(selectedDate: initialDate)) + private(set) lazy var cardsScreen = cardsScreenFactory.createScreen() + private lazy var dailyLogScreen = dailyLogScreenFactory.createScreen() + + private let initialDate: Date + + init(date: Date = Date()) { + self.initialDate = date + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { fatalError() } + + private let myDayLabel = UILabel() + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + bind() + } + + private func setUpView() { + [calendarScreen.controller, dailyLogScreen.controller, cardsScreen.controller].forEach { + addChild($0) + view.addSubview($0.view) + } + view.addSubview(myDayLabel) + myDayLabel.layout + .centerX(to: .superview) + .top(to: .safeArea) + .height(100) + calendarScreen.controller.view.layout + .left(to: .superview) + .right(to: .superview) + .top(to: myDayLabel, edge: .bottom) + .height(120) + cardsScreen.controller.view.layout + .left(to: .superview) + .right(to: .superview) + .top(to: calendarScreen.controller.view, edge: .bottom, offset: 8) + dailyLogScreen.controller.view.layout + .left(to: .superview, offset: 8) + .right(to: .superview, offset: -8) + .top(to: cardsScreen.controller.view, edge: .bottom, offset: 24) + [calendarScreen.controller, dailyLogScreen.controller, cardsScreen.controller].forEach { + $0.didMove(toParent: self) + } + myDayLabel.font = UIFont.boldSystemFont(ofSize: 24) + myDayLabel.textAlignment = .center + myDayLabel.textColor = .purple + myDayLabel.text = "My Day" + view.backgroundColor = .background + } + + private func bind() { + calendarScreen.store.observeState(\.selectedDate) { [weak self] (date) in + self?.dailyLogScreen.store.dispatchAction(.updateForDate(date)) + self?.cardsScreen.store.dispatchAction(.updateForDate(date: date)) + } + } +} diff --git a/Example/Lasso/Samples/MyDay/MyDayFlow.swift b/Example/Lasso/Samples/MyDay/MyDayFlow.swift new file mode 100644 index 0000000..a41b7c8 --- /dev/null +++ b/Example/Lasso/Samples/MyDay/MyDayFlow.swift @@ -0,0 +1,55 @@ +// +//===----------------------------------------------------------------------===// +// +// MyDayFlow.swift +// +// Created by Trevor Beasty on 10/21/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Lasso + +class MyDayFlow: Flow { + + var createMyDay = MyDayController.init(date:) + + private let initialDate: Date + + init(date: Date = Date()) { + self.initialDate = date + } + + override func createInitialController() -> UIViewController { + return assembleMyDayController() + } + + private func assembleMyDayController() -> UIViewController { + let myDay = createMyDay(initialDate) + + myDay.cardsScreen.store.observeOutput { [weak self] (output) in + guard let self = self else { return } + switch output { + + case .didSelectCard(card: let card): + let cardController = self.assembleCardController(card: card) + self.context?.pushViewController(cardController, animated: true) + } + } + + return myDay + } + + private func assembleCardController(card: String) -> UIViewController { + let state = TextScreenModule.State(title: card, description: "Blah blah, blah blah") + return TextScreenModule.createScreen(with: state) + .controller + } +} diff --git a/Example/Lasso/Samples/Onboarding/EnterNameScreen.swift b/Example/Lasso/Samples/Onboarding/EnterNameScreen.swift new file mode 100644 index 0000000..998c829 --- /dev/null +++ b/Example/Lasso/Samples/Onboarding/EnterNameScreen.swift @@ -0,0 +1,141 @@ +// +//===----------------------------------------------------------------------===// +// +// EnterNameModule.swift +// +// Created by Steven Grosmark on 5/13/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum EnterNameScreenModule: ScreenModule { + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: EnterNameStore) -> Screen { + let controller = EnterNameViewController(viewStore: store.asViewStore()) + return Screen(store, controller) + } + + enum Action: Equatable { + case didChange(_ name: String) + case didTapNext + case didTapRestart + } + + enum Output: Equatable { + case didTapNext + case didTapRestart + } + + struct State: Equatable { + let title: String? + var name: String + var canProceed: Bool { return !name.isEmpty } + + init(title: String? = nil, name: String = "") { + self.title = title + self.name = name + } + } + +} + +class EnterNameStore: LassoStore { + + override func handleAction(_ action: Action) { + switch action { + + case .didChange(let name): + update { state in + state.name = name + } + + case .didTapNext: dispatchOutput(.didTapNext) + case .didTapRestart: dispatchOutput(.didTapRestart) + } + } +} + +class EnterNameViewController: UIViewController, LassoView { + + let store: EnterNameScreenModule.ViewStore + + private let titleLabel = UILabel() + private let nameField = UITextField() + private let nextButton = UIButton(standardButtonWithTitle: "Next") + private let resetButton = UIButton(standardButtonWithTitle: "Start Over") + + init(viewStore: EnterNameScreenModule.ViewStore) { + self.store = viewStore + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .background + + setupViews() + setupConstraints() + setupBindings() + } + + private func setupViews() { + titleLabel.numberOfLines = 0 + titleLabel.textAlignment = .left + titleLabel.lineBreakMode = .byWordWrapping + titleLabel.font = .systemFont(ofSize: 48, weight: .bold) + titleLabel.text = store.state.title + + nameField.placeholder = "Enter your name" + + view.addSubviews(titleLabel, nameField, nextButton, resetButton) + } + + private func setupConstraints() { + titleLabel.layout + .fill(.safeArea, except: .bottom, inset: 20) + + nameField.layout + .below(titleLabel, offset: 50) + .fillWidth(of: .safeArea, inset: 20, maximum: 300) + + nextButton.layout + .below(nameField, offset: 50) + .fillWidth(of: .safeArea, inset: 20, maximum: 300) + .height(44) + + resetButton.layout + .below(nextButton, offset: 20) + .fillWidth(of: .safeArea, inset: 20, maximum: 300) + .height(44) + } + + private func setupBindings() { + nextButton.bind(to: store, action: .didTapNext) + resetButton.bind(to: store, action: .didTapRestart) + + nameField.bindTextDidChange(to: store) { .didChange($0) } + + store.observeState(\.name) { [weak self] name in + self?.nameField.text = name + } + store.observeState { [weak self] state in + self?.nextButton.isEnabled = state.canProceed + } + } + +} diff --git a/Example/Lasso/Samples/Onboarding/FoodOnboarding.swift b/Example/Lasso/Samples/Onboarding/FoodOnboarding.swift new file mode 100644 index 0000000..1f578a7 --- /dev/null +++ b/Example/Lasso/Samples/Onboarding/FoodOnboarding.swift @@ -0,0 +1,90 @@ +// +//===----------------------------------------------------------------------===// +// +// FoodOnboarding.swift +// +// Created by Steven Grosmark on 5/20/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum FoodOnboarding: NavigationFlowModule { + + enum Output: Equatable { + case didFinish + } + +} + +/// Displays a series of Food Onboarding screens. +/// +/// This flow emits the Output `.didFinish` once all of the onboarding screens have been shown. +/// This flow requires that it is placed in a navigation controller. +class FoodOnboardingFlow: Flow { + + override func createInitialController() -> UIViewController { + return assembleScreen(for: Stage.first).controller + } + + /// Create and return the Screen for a specific onboarding Stage + private func assembleScreen(for stage: Stage) -> TextScreenModule.Screen { + return TextScreenModule + .createScreen(with: textState(for: stage)) + .observeOutput { [weak self] output in + guard let self = self else { return } + switch output { + + case .didTapButton: + guard let nextStage = stage.next else { + self.dispatchOutput(.didFinish) + return + } + self.assembleScreen(for: nextStage) + .place(with: self.nextPushedInFlow) + } + } + } + + /// Retrieve the appropriate State for a specific onboarding Stage + private func textState(for stage: Stage) -> TextScreenModule.State { + switch stage { + + case .welcome: + return TextScreenModule.State(title: "Welcome", + description: "We just need a little info to get you going...", + buttons: ["Next"]) + + case .notifications: + return TextScreenModule.State(title: "Notifications", + description: "Enable Notifications for the smoothest experience...", + buttons: ["Next"]) + + case .done: + return TextScreenModule.State(title: "All set", + description: "Ok, let's get started!", + buttons: ["Finish"]) + } + } + + private enum Stage: Int { + case welcome = 1, notifications, done + + static var first: Stage { return .welcome } + + var next: Stage? { + return Stage(rawValue: rawValue + 1) + } + + } + +} diff --git a/Example/Lasso/Samples/Onboarding/Onboarding.swift b/Example/Lasso/Samples/Onboarding/Onboarding.swift new file mode 100644 index 0000000..56a3552 --- /dev/null +++ b/Example/Lasso/Samples/Onboarding/Onboarding.swift @@ -0,0 +1,170 @@ +// +//===----------------------------------------------------------------------===// +// +// Onboarding.swift +// +// Created by Steven Grosmark on 5/13/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +/// A sample onboarding-type Flow with five screens sequentially pushed onto a navigation stack. +/// +/// The five stages are: +/// - welcome ("welcome to the thing") +/// - name ("enter your name") +/// - counter ("pick a number") +/// - notifications ("hey - turn on notifications") +/// - done ("all done") +enum OnboardingFlowModule: NavigationFlowModule { + + enum Output: Equatable { + case didFinish + } + +} + +// sample sequential navigation flow +class OnboardingFlow: Flow { + + private var nameStore: EnterNameScreenModule.Store? + private var counterStore: CounterScreenModule.Store? + + override func createInitialController() -> UIViewController { + return assembleController(for: Stage.first) + } + + private func assembleController(for stage: Stage) -> UIViewController { + + switch initialScreenState(for: stage) { + + case .text(let textState): + return TextScreenModule + .createScreen(with: textState) + .observeOutput({ [weak self] output in + switch output { + case .didTapButton(let index): + switch index { + case 0: self?.showNextStage(currentStage: stage) + case 1: self?.restart() + default: return + } + } + + }) + .controller + + case .enterName(let enterNameState): + return EnterNameScreenModule.createScreen(with: enterNameState) + .observeOutput({ [weak self] output in + switch output { + case .didTapRestart: self?.restart() + case .didTapNext: self?.showNextStage(currentStage: stage) + } + }) + .captureStore(as: &nameStore) + .controller + + case .counter(let counterState): + return CounterScreenModule.createScreen(with: counterState) + .observeOutput({ [weak self] output in + switch output { + case .didTapNext: self?.showNextStage(currentStage: stage) + } + }) + .captureStore(as: &counterStore) + .controller + } + + } + + private func showNextStage(currentStage: Stage) { + guard let nextStage = currentStage.next else { + return dispatchOutput(.didFinish) + } + let nextController = assembleController(for: nextStage) + nextController.place(with: nextPushedInFlow) + } + + private func restart() { + guard let welcomeController = initialController else { return } + context?.popToViewController(welcomeController, animated: true) + nameStore = nil + counterStore = nil + } + + private enum Stage: Int, CaseIterable { + case welcome, name, counter, notifications, done + + static var first: Stage { return .welcome } + + var next: Stage? { + return Stage(rawValue: rawValue + 1) + } + + } + + private enum ScreenState { + case text(TextScreenModule.State) + case enterName(EnterNameScreenModule.State) + case counter(CounterScreenModule.State) + } + + private func initialScreenState(for stage: Stage) -> ScreenState { + switch stage { + + case .welcome: + let state = TextScreenModule.State(title: "Welcome", + description: "We just need a little info to get you going...", + buttons: ["Next"]) + return .text(state) + + case .notifications: + let state = TextScreenModule.State(title: "Notifications", + description: "Enable Notifications for the smoothest experience...", + buttons: ["Next", "Start Over"]) + return .text(state) + + case .name: + let state = EnterNameScreenModule.State(title: "Enter you name", + name: name) + return .enterName(state) + + case .counter: + let state = CounterScreenModule.State(title: "Rollover points", + counter: counter, + style: .light) + return .counter(state) + + case .done: + let description = """ + Ok, \(name), let's get started! + + By completing this onboarding, you earned \(counter) rollover points! + """ + let state = TextScreenModule.State(title: "All set", + description: description, + buttons: ["Finish"]) + return .text(state) + } + } + + private var name: String { + return nameStore?.state.name ?? "" + } + + private var counter: Int { + return counterStore?.state.counter ?? 0 + } + +} diff --git a/Example/Lasso/Samples/PageControllerFlow.swift b/Example/Lasso/Samples/PageControllerFlow.swift new file mode 100644 index 0000000..05d6bb9 --- /dev/null +++ b/Example/Lasso/Samples/PageControllerFlow.swift @@ -0,0 +1,61 @@ +// +//===----------------------------------------------------------------------===// +// +// PageControllerFlow.swift +// +// Created by Trevor Beasty on 12/3/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Lasso + +enum PageControllerFlowModule: FlowModule { + + typealias RequiredContext = UIPageViewController + + enum Output: Equatable { + case didFinish + } + +} + +class PageControllerFlow: Flow { + + override func createInitialController() -> UIViewController { + return assembleController(0) + } + + private func assembleController(_ order: Int) -> UIViewController { + let state = TextScreenModule.State(title: "Screen \(order)", description: nil, buttons: order == 0 ? ["Next"] : ["Finish"]) + return TextScreenModule + .createScreen(with: state) + .observeOutput({ [weak self] output in + guard let self = self, case .didTapButton = output else { return } + switch order { + + case 0: + let controller1 = self.assembleController(1) + // When controller1 is set on the pageController, the initial controller is deallocated. B/c the initial controller was + // the only object retaining this Flow, a new strong reference must be created from controller 1 to this Flow + controller1.holdReference(to: self) + controller1.place(with: self.nextPageInFlow) + + case 1: + self.dispatchOutput(.didFinish) + + default: + return + } + }) + .controller + } + +} diff --git a/Example/Lasso/Samples/SearchAndTrackFlow.swift b/Example/Lasso/Samples/SearchAndTrackFlow.swift new file mode 100644 index 0000000..ee87933 --- /dev/null +++ b/Example/Lasso/Samples/SearchAndTrackFlow.swift @@ -0,0 +1,84 @@ +// +//===----------------------------------------------------------------------===// +// +// SearchAndTrackFlow.swift +// +// Created by Trevor Beasty on 7/18/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +struct Food: SearchListRepresentable, Equatable { + let name: String + let points: Int + let description: String + + var searchListTitle: String { + return "\(name), \(String(points)) points" + } + + // service call + static func getFoods(searchText: String?, completion: @escaping (Result<[Food], Error>) -> Void) { + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: { + if Bool.random() { + let paulsToast = Food(name: "Paul's Toast", points: 2, description: "Extra toasted with light butter") + completion(.success([paulsToast])) + } + else { + completion(.failure(NSError())) + } + }) + } + +} + +class SearchAndTrackFlow: Flow { + + var searchScreenFactory = SearchScreenModule.ScreenFactory(configure: { searchStore in + searchStore.getSearchResults = Food.getFoods + }) + var foodDetailScreenFactory = TextScreenModule.ScreenFactory() + + override func createInitialController() -> UIViewController { + return assembleSearch() + } + + private func assembleSearch() -> UIViewController { + return searchScreenFactory.createScreen() + .observeOutput({ [weak self] output in + guard let self = self else { return } + switch output { + + case .didSelectItem(let food): + let food = self.assembleFoodDetail(food) + self.nextPresentedInFlow?.place(food) + } + }) + .controller + } + + private func assembleFoodDetail(_ food: Food) -> UIViewController { + let description = "\(food.points) Points\n\n\(food.description)" + let state = TextScreenModule.State(title: food.name, description: description, buttons: ["Track"]) + return foodDetailScreenFactory.createScreen(with: state) + .observeOutput({ [weak self] output in + switch output { + + case .didTapButton: + self?.unwind() + } + }) + .controller + } + +} diff --git a/Example/Lasso/Samples/SearchModule.swift b/Example/Lasso/Samples/SearchModule.swift new file mode 100644 index 0000000..3ad437a --- /dev/null +++ b/Example/Lasso/Samples/SearchModule.swift @@ -0,0 +1,301 @@ +// +//===----------------------------------------------------------------------===// +// +// SearchModule.swift +// +// Created by Trevor Beasty on 5/2/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +protocol SearchListRepresentable { + var searchListTitle: String { get } +} + +enum SearchScreenModule: ScreenModule { + + struct State: Equatable { + var searchText: String? + var items: [Item] + var phase: Phase + var viewDidAppear: Bool + + enum Phase: Equatable { + case idle + case searching + case error(message: String) + } + + } + + enum Action: Equatable { + case didUpdateSearchText(String?) + case didPressClear + case didSelectItem(idx: Int) + case viewWillAppear + case didAcknowledgeError + } + + enum Output: Equatable { + case didSelectItem(Item) + } + + struct Item: Equatable { + let id: String + let name: String + let points: Int + } + + static var defaultInitialState: State { + return State(searchText: nil, items: [], phase: .idle, viewDidAppear: false) + } + + static func createScreen(with store: SearchStore) -> Screen { + let viewStore = store.asViewStore(stateMap: { $0.asViewState }) + let controller = SearchViewController(store: viewStore) + return Screen(store, controller) + } + +} + +enum SearchViewModule: ViewModule { + + struct ViewState: Equatable { + var isLoading: Bool + var error: String? + var items: [Item] + var searchText: String? + } + + typealias ViewAction = SearchScreenModule.Action + +} + +class SearchStore: LassoStore> { + + var getSearchResults: (String?, @escaping (Result<[Item], Error>) -> Void) -> Void = { _, _ in } + + override func handleAction(_ action: Action) { + switch action { + case .didPressClear: + search(nil) + + case .didSelectItem(let idx): + guard idx >= 0, idx < state.items.count else { return } + dispatchOutput(.didSelectItem(state.items[idx])) + + case .didUpdateSearchText(let searchText): + search(searchText) + + case .viewWillAppear: + handleViewWillAppear() + + case .didAcknowledgeError: + update { state in + state.phase = .idle + } + } + } + + private func search(_ searchText: String?) { + update { state in + state.searchText = searchText + state.phase = .searching + } + getSearchResults(searchText) { [weak self] (result) in + guard let self = self else { return } + switch result { + + case .success(let items): + self.update { state in + state.items = items + state.phase = .idle + } + case .failure: + self.update { state in + state.items = [] + state.phase = .error(message: "Something went wrong") + } + } + } + } + + private func handleViewWillAppear() { + if !state.viewDidAppear { + batchUpdate { state in state.viewDidAppear = true } + search(state.searchText) + } + } + +} + +class SearchViewController: UIViewController, LassoView, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate { + + let store: SearchViewModule.ViewStore + + private let searchBar = UISearchBar() + private let itemsTable = UITableView() + private let activityIndicator = UIActivityIndicatorView(style: .gray) + private weak var alertController: UIAlertController? + + init(store: SearchViewModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { fatalError() } + + override func viewDidLoad() { + super.viewDidLoad() + setUp() + bind() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + store.dispatchAction(.viewWillAppear) + } + + private func setUp() { + + func setUpConstraints() { + [searchBar, itemsTable, activityIndicator].forEach({ + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + }) + var constraints = [ + searchBar.leftAnchor.constraint(equalTo: view.leftAnchor), + searchBar.rightAnchor.constraint(equalTo: view.rightAnchor), + searchBar.heightAnchor.constraint(equalToConstant: 50), + itemsTable.topAnchor.constraint(equalTo: searchBar.bottomAnchor), + itemsTable.leftAnchor.constraint(equalTo: view.leftAnchor), + itemsTable.rightAnchor.constraint(equalTo: view.rightAnchor), + itemsTable.bottomAnchor.constraint(equalTo: view.bottomAnchor), + activityIndicator.topAnchor.constraint(equalTo: view.topAnchor), + activityIndicator.leftAnchor.constraint(equalTo: view.leftAnchor), + activityIndicator.rightAnchor.constraint(equalTo: view.rightAnchor), + activityIndicator.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ] + if #available(iOS 11.0, *) { + constraints.append(searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)) + } else { + constraints.append(searchBar.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor)) + } + NSLayoutConstraint.activate(constraints) + } + + func setUpItemsTable() { + itemsTable.dataSource = self + itemsTable.delegate = self + itemsTable.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + } + + func setUpSearchBar() { + searchBar.delegate = self + } + + func style() { + view.backgroundColor = .background + } + + setUpConstraints() + setUpItemsTable() + setUpSearchBar() + style() + } + + private func bind() { + + store.observeState(\.isLoading) { [activityIndicator] (_, isLoading) in + if isLoading { + if !activityIndicator.isAnimating { activityIndicator.startAnimating() } + } + else { + if activityIndicator.isAnimating { activityIndicator.stopAnimating() } + } + } + + store.observeState(\.items) { [weak self] (_, _) in + self?.itemsTable.reloadData() + } + + store.observeState(\.searchText) { [searchBar] (_, searchText) in + searchBar.text = searchText + } + + store.observeState(\.error) { [weak self] (_, error) in + if let error = error { + if self?.alertController == nil { + self?.showError(error) + } + } + else { + if let alertController = self?.alertController { + alertController.dismiss(animated: true, completion: nil) + } + } + } + + } + + private func showError(_ error: String) { + let alertController = UIAlertController(title: error, message: nil, preferredStyle: .alert) + let continueAction = UIAlertAction(title: "Continue", style: .default) { [store] (_) in + store.dispatchAction(.didAcknowledgeError) + } + alertController.addAction(continueAction) + present(alertController, animated: true, completion: nil) + self.alertController = alertController + } + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return store.state.items.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell() + let item = store.state.items[indexPath.row] + cell.textLabel?.text = item.searchListTitle + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + store.dispatchAction(.didSelectItem(idx: indexPath.row)) + } + + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + store.dispatchAction(.didUpdateSearchText(searchText)) + } + + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + store.dispatchAction(.didPressClear) + } + +} + +extension SearchScreenModule.State { + + var asViewState: SearchViewModule.ViewState { + let error: String? + switch phase { + case .error(message: let message): error = message + case .idle, .searching: error = nil + } + return SearchViewModule.ViewState(isLoading: phase == .searching, error: error, items: items, searchText: searchText) + } + +} diff --git a/Example/Lasso/Samples/Signup/Signup.swift b/Example/Lasso/Samples/Signup/Signup.swift new file mode 100644 index 0000000..e1bdd59 --- /dev/null +++ b/Example/Lasso/Samples/Signup/Signup.swift @@ -0,0 +1,73 @@ +// +//===----------------------------------------------------------------------===// +// +// Signup.swift +// +// Created by Steven Grosmark on 5/18/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +enum Signup: NavigationFlowModule { + + enum Output: Equatable { + case didCompleteSignup + case didFailSignup + } + +} + +class SignupFlow: Flow { + + override func createInitialController() -> UIViewController { + return + SignupIntro + .createScreen() + .observeOutput { [weak self] in self?.handle(ScreenOutput.intro($0)) } + .controller + } + + private func handle(_ output: ScreenOutput) { + switch output { + + case .intro(.didTapNext): + SignupForm + .createScreen() + .observeOutput { [weak self] in self?.handle(ScreenOutput.form($0)) } + .place(with: nextPushedInFlow) + + case .form(.didSignup(let fields)): + formFields = fields + let textState = TextScreenModule.State(title: "All set", + description: "Ok, \(formFields.name), let's get started!", + buttons: ["Finish"]) + TextScreenModule + .createScreen(with: textState) + .place(with: nextPushedInFlow) + .setUpController { $0.navigationItem.hidesBackButton = true } + .observeOutput { [weak self] in self?.handle(ScreenOutput.finished($0)) } + + case .finished(.didTapButton): + dispatchOutput(.didCompleteSignup) + } + } + + enum ScreenOutput { + case intro(SignupIntro.Output) + case form(SignupForm.Output) + case finished(TextScreenModule.Output) + } + + private var formFields = SignupForm.Output.Fields() +} diff --git a/Example/Lasso/Samples/Signup/SignupForm.swift b/Example/Lasso/Samples/Signup/SignupForm.swift new file mode 100644 index 0000000..72be269 --- /dev/null +++ b/Example/Lasso/Samples/Signup/SignupForm.swift @@ -0,0 +1,295 @@ +// +//===----------------------------------------------------------------------===// +// +// SignupForm.swift +// +// Created by Steven Grosmark on 5/18/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +// MARK: - Module + +enum SignupForm: ScreenModule { + + enum Output: Equatable { + case didSignup(Fields) + + struct Fields: Equatable { + let name: String + let email: String + let username: String + let password: String + } + } + + enum Action: Equatable { + case didUpdate(Field, String) + case didTapSignup + } + + struct State: Equatable { + var name: Validated + var email: Validated + var username: Validated + var password: Validated + var formIsValid: Bool = false + var phase: Phase + enum Phase: Equatable { case idle, working, error(String) } + } + + struct Validated: Equatable { + let value: String + let error: String? + } + + enum Field: CaseIterable { + case name, email, username, password + } + + static func createScreen(with formFields: Output.Fields) -> Screen { + return SignupForm.createScreen(with: State(from: formFields)) + } + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: SignupFormStore) -> Screen { + let controller = SignupFormViewController(viewStore: store.asViewStore()) + return Screen(store, controller) + } + +} + +// MARK: - Store + +class SignupFormStore: LassoStore { + + var signupService: SignupServiceProtocol = SignupService() + + override func handleAction(_ action: SignupForm.Action) { + switch action { + + case .didUpdate(let field, let value): + update { state in + state[keyPath: field.stateKey] = field.validate(value.trimmingCharacters(in: .whitespacesAndNewlines)) + state.formIsValid = state.areAllFormFieldsValid() + } + + case .didTapSignup: + guard state.formIsValid, state.phase == .idle else { return } + goSignup() + } + } + + private func goSignup() { + update { state in state.phase = .working } + signupService.signup(state.completedFields) { [ weak self] _ in + guard let self = self else { return } + self.dispatchOutput(.didSignup(self.state.completedFields)) + } + } +} + +protocol SignupServiceProtocol { + func signup(_ fields: SignupForm.Output.Fields, completion: @escaping (Result) -> Void) +} + +struct SignupService: SignupServiceProtocol { + + func signup(_ fields: SignupForm.Output.Fields, completion: @escaping (Result) -> Void) { + DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { + completion(.success(UUID().uuidString)) + } + } + +} + +extension SignupForm.Output.Fields { + init() { + name = "" + email = "" + username = "" + password = "" + } +} + +extension SignupForm.Field { + typealias State = SignupForm.State + typealias Validated = SignupForm.Validated + + var label: String { return "\(self)".capitalized } + + var stateKey: WritableKeyPath { + switch self { + case .name: return \.name + case .email: return \.email + case .username: return \.username + case .password: return \.password + } + } + + func validate(_ value: String) -> Validated { + switch self { + case .name: return validate(value, using: "^.{3,64}$") + case .email: return validate(value, using: "^[^@]+@.+\\.[^.]{2,}$") + case .username: return validate(value, using: "^[a-zA-Z0-9_]{3,64}$") + case .password: return validate(value, using: "^.{3,64}$") + } + } + + func validate(_ value: String, using regex: String) -> Validated { + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + let isValid = trimmed.range(of: regex, options: .regularExpression) != nil + return Validated(value: value, error: isValid ? nil : "Invalid") + } +} + +extension SignupForm.State { + typealias Field = SignupForm.Field + typealias Validated = SignupForm.Validated + + init() { + name = Validated() + email = Validated() + username = Validated() + password = Validated() + phase = .idle + formIsValid = false + } + + init(from formFields: SignupForm.Output.Fields) { + name = Field.name.validate(formFields.name) + email = Field.email.validate(formFields.email) + username = Field.username.validate(formFields.username) + password = Field.name.validate(formFields.password) + phase = .idle + formIsValid = areAllFormFieldsValid() + } + + var completedFields: SignupForm.Output.Fields { + return SignupForm.Output.Fields(name: name.value, + email: email.value, + username: username.value, + password: password.value) + } + + fileprivate func areAllFormFieldsValid() -> Bool { + for field in SignupForm.Field.allCases { + let validated = self[keyPath: field.stateKey] + if validated.error != nil || validated.value.isEmpty { + return false + } + } + return true + } +} + +extension SignupForm.Validated { + init() { + value = "" + error = nil + } +} + +// MARK: - View Controller + +class SignupFormViewController: UIViewController { + + private let viewStore: SignupForm.ViewStore + private let activityIndicator = UIActivityIndicatorView() + + init(viewStore: SignupForm.ViewStore) { + self.viewStore = viewStore + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .background + + let headerLabel = UILabel(headline: "Setup your account") + + view.addSubview(headerLabel) + headerLabel.layout.fill(.safeArea, except: .bottom, inset: 30) + + var last: UIView = headerLabel + + for field in SignupForm.Field.allCases { + let (textField, errorLabel) = makeFormFieldAndLabel(for: field) + + view.addSubviews(textField, errorLabel) + textField.layout + .below(last, offset: 30) + .fillWidth(of: .safeArea, inset: 30, maximum: 420) + errorLabel.layout + .below(textField, offset: 10) + .fill(textField, axis: .x) + last = errorLabel + + textField.bindTextDidChange(to: viewStore) { text in + .didUpdate(field, text) + } + viewStore.observeState(field.stateKey) { value in + textField.text = value.value + errorLabel.text = value.error ?? " " + } + } + + let button = UIButton(standardButtonWithTitle: "Register") + + view.addSubview(button) + + button.layout + .below(last, offset: 30) + .fillWidth(of: .safeArea, inset: 20, maximum: 300) + + button.bind(to: viewStore, action: .didTapSignup) + + viewStore.observeState(\.formIsValid) { [weak button] isValid in + button?.isEnabled = isValid + } + + activityIndicator.hidesWhenStopped = true + activityIndicator.style = .gray + view.addSubview(activityIndicator) + activityIndicator.layout + .below(button, offset: 30) + .centerX(to: .safeArea) + + viewStore.observeState(\.phase) { [weak self] phase in + if phase == .working { + self?.activityIndicator.startAnimating() + } + else { + self?.activityIndicator.stopAnimating() + } + self?.view.isUserInteractionEnabled = phase != .working + } + } + + private func makeFormFieldAndLabel(for field: SignupForm.Field) -> (UITextField, UILabel) { + let textField = UITextField(placeholder: field.label, + autocapitalize: (field == .name) ? .words : .none) + textField.isSecureTextEntry = field == .password + + let errorLabel = UILabel() + errorLabel.textColor = .red + errorLabel.numberOfLines = 1 + + return (textField, errorLabel) + } +} diff --git a/Example/Lasso/Samples/Signup/SignupIntro.swift b/Example/Lasso/Samples/Signup/SignupIntro.swift new file mode 100644 index 0000000..80f30cc --- /dev/null +++ b/Example/Lasso/Samples/Signup/SignupIntro.swift @@ -0,0 +1,73 @@ +// +//===----------------------------------------------------------------------===// +// +// SignupIntro.swift +// +// Created by Steven Grosmark on 5/18/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +enum SignupIntro: ScreenModule { + + static func createScreen(with store: IntroStore) -> Screen { + let controller = SignupIntroViewController(store: store.asViewStore()) + return Screen(store, controller) + } + + enum Action: Equatable { + case didTapNext + } + + typealias Output = Action + + class IntroStore: LassoStore { + override func handleAction(_ action: Action) { + dispatchOutput(action) + } + } + +} + +class SignupIntroViewController: UIViewController, LassoView { + + let store: SignupIntro.ViewStore + + init(store: SignupIntro.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .background + + let label = UILabel(body: "Please sign up for our delightful experience.") + let button = UIButton(standardButtonWithTitle: "Begin") + + view.addSubviews(label, button) + + label.layout + .fill(.safeArea, except: .bottom, inset: 30) + .height(to: .safeArea, multiplier: 0.5) + button.layout + .below(label, offset: 20) + .fillWidth(of: .safeArea, inset: 30, maximum: 300) + + button.bind(to: store, action: .didTapNext) + } +} diff --git a/Example/Lasso/Samples/SplitView/RandomItemFlow.swift b/Example/Lasso/Samples/SplitView/RandomItemFlow.swift new file mode 100644 index 0000000..1fa21e9 --- /dev/null +++ b/Example/Lasso/Samples/SplitView/RandomItemFlow.swift @@ -0,0 +1,57 @@ +// +//===----------------------------------------------------------------------===// +// +// RandomItemFlow.swift +// +// Created by Steven Grosmark on 5/27/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +class RandomItemFlow: Flow { + + private let item: RandomItems.Item + + init(for item: RandomItems.Item) { + self.item = item + } + + override func createInitialController() -> UIViewController { + let screen = TextScreenModule.createScreen(with: item.textState) + screen.observeOutput { [weak self] in self?.handleScreenOutput($0) } + + screen.controller.title = item.name + return screen.controller + } + + private func handleScreenOutput(_ output: TextScreenModule.Output) { + switch output { + + case .didTapButton: + TextScreenModule + .createScreen(with: TextScreenModule.State(description: .loremIpsum(paragraphs: 3))) + .place(with: nextPushedInFlow) + } + } +} + +extension RandomItems.Item { + + fileprivate var textState: TextScreenModule.State { + if name.isEmpty, description.isEmpty { + return TextScreenModule.State() + } + return TextScreenModule.State(title: name, description: description, buttons: ["More..."]) + } + +} diff --git a/Example/Lasso/Samples/SplitView/SplitView.swift b/Example/Lasso/Samples/SplitView/SplitView.swift new file mode 100644 index 0000000..c0ba6d0 --- /dev/null +++ b/Example/Lasso/Samples/SplitView/SplitView.swift @@ -0,0 +1,131 @@ +// +//===----------------------------------------------------------------------===// +// +// SplitView.swift +// +// Created by Steven Grosmark on 5/26/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum SplitViewFlowModule: FlowModule { + + enum Output: Equatable { + case didPressDone + } + + typealias RequiredContext = UIViewController + + enum Option { + case lessDetail + case moreDetail + } + +} + +class SplitViewFlow: Flow, UISplitViewControllerDelegate { + + // This switch dictates what the SplitViewFlow will place as its 'detail' controller. + // + // lessDetail -> place a screen embedded in a nav controller; add the 'expand' button + // to that nav controller + // + // moreDetail -> place a flow which supports a sequence of screens embedded in a nav controller + // + private let option: SplitViewFlowModule.Option + private weak var splitController: UISplitViewController? + private var didSelectItem = false + + init(option: SplitViewFlowModule.Option = .lessDetail) { + self.option = option + super.init() + } + + override func createInitialController() -> UIViewController { + + let splitController = UISplitViewController() + splitController.preferredDisplayMode = .allVisible + splitController.delegate = self + + let primaryNavigationController = UINavigationController(rootViewController: assemblePrimaryScreen().controller) + let emptyController = UIViewController() + emptyController.view.backgroundColor = .background + let detailNavigationController = UINavigationController(rootViewController: emptyController) + splitController.viewControllers = [primaryNavigationController, detailNavigationController] + + self.splitController = splitController + return splitController + } + + private func assemblePrimaryScreen() -> RandomItems.Screen { + + return RandomItems.createScreen() + .observeOutput({ [weak self] output in + switch output { + + case .didSelectItem(let item): + self?.didSelectItem = true + self?.showItem(item) + } + }) + .setUpController({ + $0.title = "Random Items" + let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(didPressDone)) + $0.navigationItem.leftBarButtonItem = button + }) + } + + @objc private func didPressDone() { + dispatchOutput(.didPressDone) + } + + private func showItem(_ item: RandomItems.Item) { + + guard let splitController = splitController else { return } + let itemController: UIViewController + + switch option { + + case .lessDetail: + let screen = TextScreenModule.createScreen(with: item.asTextState) + .setUpController({ + $0.title = item.name + $0.navigationItem.leftBarButtonItem = splitController.displayModeButtonItem + $0.navigationItem.leftItemsSupplementBackButton = true + }) + + itemController = UINavigationController(rootViewController: screen.controller) + + case .moreDetail: + let navigationController = UINavigationController() + RandomItemFlow(for: item).start(with: root(of: navigationController)) + itemController = navigationController + } + + splitController.showDetailViewController(itemController, sender: nil) + } + + func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { + + return !didSelectItem + } + +} + +extension RandomItems.Item { + + fileprivate var asTextState: TextScreenModule.State { + return TextScreenModule.State(title: name, description: description) + } + +} diff --git a/Example/Lasso/Samples/StrangeFlow.swift b/Example/Lasso/Samples/StrangeFlow.swift new file mode 100644 index 0000000..42273d6 --- /dev/null +++ b/Example/Lasso/Samples/StrangeFlow.swift @@ -0,0 +1,67 @@ +// +//===----------------------------------------------------------------------===// +// +// StrangeFlow.swift +// +// Created by Steven Grosmark on 5/20/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +class StrangeFlow: Flow { + + override func createInitialController() -> UIViewController { + let state = TextScreenModule.State(title: "My Day", description: "Tap \"Track\" to track something.", buttons: ["Track"]) + return TextScreenModule.createScreen(with: state) + .observeOutput { [weak self] in self?.handleScreenOutput(.myday($0)) } + .controller + } + + private func handleScreenOutput(_ output: ScreenOutput) { + switch output { + + case .myday(.didTapButton): + let state = TextScreenModule.State(title: "Track Something", buttons: ["Food", "Weight", "Activity"]) + TextScreenModule.createScreen(with: state) + .observeOutput { [weak self] in self?.handleScreenOutput(.trackMenu($0)) } + .captureController(as: &trackSomethingController) + .place(with: nextPresentedInFlow?.withDismissibleNavigationEmbedding()) + + case .trackMenu(.didTapButton(let index)): + guard let navigationController = context else { return } + let thing = ["Food", "Weight", "Activity"][index] + let state = TextScreenModule.State(title: "Track \(thing)", description: "Tap \"Track\" to track a \(thing).", buttons: ["Track \(thing)"]) + + let strangePlacer = makePlacer(base: navigationController) { (navigationController, toPlace) -> UINavigationController in + navigationController.pushViewController(toPlace, animated: true) + self.trackSomethingController?.dismiss(animated: true) + return navigationController + } + TextScreenModule.createScreen(with: state) + .observeOutput { [weak self] in self?.handleScreenOutput(.track($0)) } + .place(with: strangePlacer) + + case .track(.didTapButton): + initialController?.navigationController?.popViewController(animated: true) + } + } + + enum ScreenOutput { + case myday(TextScreenModule.Output) + case trackMenu(TextScreenModule.Output) + case track(TextScreenModule.Output) + } + + private weak var trackSomethingController: UIViewController? + +} diff --git a/Example/Lasso/Samples/Survey/MakeListModule.swift b/Example/Lasso/Samples/Survey/MakeListModule.swift new file mode 100644 index 0000000..82f5637 --- /dev/null +++ b/Example/Lasso/Samples/Survey/MakeListModule.swift @@ -0,0 +1,179 @@ +// +//===----------------------------------------------------------------------===// +// +// MakeListModule.swift +// +// Created by Trevor Beasty on 6/4/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum MakeListViewModule: ViewModule { + + struct ViewState: Equatable { + var header: String + var placeholder: String + var proposed: String? + var submitted: [String] + } + + enum ViewAction: Equatable { + case didEditProposed(String?) + case didPressSubmit + case didPressAdd + } + +} + +class MakeListViewController: UIViewController, LassoView { + + private let headerLabel = UILabel() + private let field = UITextField() + private let table = UITableView() + private let addButton = UIButton() + private let submitButton = UIButton() + + let store: MakeListViewModule.ViewStore + + init(store: MakeListViewModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { fatalError() } + + override func viewDidLoad() { + super.viewDidLoad() + setUp() + bind() + } + + private func setUp() { + + func setUpConstraints() { + view.addSubviews(headerLabel, field, addButton, table, submitButton) + + headerLabel.layout + .fill(.superview, axis: .x, inset: 20) + .top(to: .safeArea, offset: 20) + + addButton.layout + .left(to: field, edge: .right, offset: 10) + .right(to: .superview, offset: -20) + .centerY(to: field) + addButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + field.layout + .top(to: headerLabel, edge: .bottom, offset: 20) + .left(to: .superview, offset: 20) + .height(50) + + table.layout + .top(to: field, edge: .bottom, offset: 20) + .fill(.superview, axis: .x, inset: 20) + .bottom(to: submitButton, edge: .top, offset: -20) + + submitButton.layout + .bottom(to: .safeArea, offset: -20) + .fill(.superview, axis: .x, inset: 20) + .height(50) + } + + func setUpTable() { + table.dataSource = self + table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + } + + func setUpInteractions() { + submitButton.addTarget(self, action: #selector(didPressSubmit), for: .touchUpInside) + addButton.addTarget(self, action: #selector(didPressAdd), for: .touchUpInside) + field.addTarget(self, action: #selector(didEditProposed), for: .editingChanged) + } + + func configureStaticValues() { + addButton.setTitle("Add", for: .normal) + submitButton.setTitle("Submit", for: .normal) + } + + func style() { + view.backgroundColor = .background + headerLabel.textAlignment = .left + headerLabel.numberOfLines = 0 + headerLabel.lineBreakMode = .byWordWrapping + addButton.backgroundColor = .white + addButton.setTitleColor(.blue, for: .normal) + submitButton.backgroundColor = .blue + submitButton.setTitleColor(.white, for: .normal) + field.layer.borderColor = UIColor.black.cgColor + field.layer.borderWidth = 1.0 + } + + setUpConstraints() + setUpTable() + setUpInteractions() + configureStaticValues() + style() + } + + private func bind() { + + store.observeState(\.proposed) { [weak self] proposed in + self?.field.text = proposed + } + + store.observeState(\.submitted) { [weak self] _ in + self?.table.reloadData() + } + + store.observeState(\.header) { [weak self] header in + self?.headerLabel.text = header + } + + store.observeState(\.placeholder) { [weak self] placeholder in + self?.field.placeholder = placeholder + } + + } + + @objc private func didPressSubmit() { + store.dispatchAction(.didPressSubmit) + } + + @objc private func didPressAdd() { + store.dispatchAction(.didPressAdd) + } + + @objc private func didEditProposed() { + store.dispatchAction(.didEditProposed(field.text)) + } + +} + +extension MakeListViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return store.state.submitted.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + let submission = store.state.submitted[indexPath.row] + cell.textLabel?.text = submission + return cell + } + +} diff --git a/Example/Lasso/Samples/Survey/SurveyFlow.swift b/Example/Lasso/Samples/Survey/SurveyFlow.swift new file mode 100644 index 0000000..2a5cccb --- /dev/null +++ b/Example/Lasso/Samples/Survey/SurveyFlow.swift @@ -0,0 +1,146 @@ +// +//===----------------------------------------------------------------------===// +// +// SurveyFlow.swift +// +// Created by Trevor Beasty on 6/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +enum SurveyFlowModule: NavigationFlowModule { + + enum Output: Equatable { + case didFinish(responses: SurveyStoreModule.Responses, prize: Prize?) + } + + struct Prize: Equatable { + let name: String + let description: String + let dollarValue: Int + } + +} + +class SurveyFlow: Flow { + + var getPrize = { (responses: SurveyStoreModule.Responses, completion: @escaping (SurveyFlowModule.Prize?) -> Void) in + DispatchQueue.global().asyncAfter(deadline: .now() + 2) { + var prize: SurveyFlowModule.Prize? + if Bool.random() { + prize = SurveyFlowModule.Prize(name: "Free Cruise", + description: "All expenses paid! Dinner with Oprah!", + dollarValue: 10000) + } + completion(prize) + } + } + + private let questions: [SurveyStoreModule.Question] + + private lazy var surveyStore: SurveyStoreModule.Store = { + let initialState = SurveyStore.State(questions: questions) + let store = SurveyStore(with: initialState) + + store.observeOutput({ [weak self] (output) in + switch output { + case .didPressSubmit(question: let question): + self?.handleDidPressSubmit(for: question) + } + }) + + return store.asAnyStore() + }() + + init(questions: [SurveyStoreModule.Question]) { + self.questions = questions + } + + override func createInitialController() -> UIViewController { + guard let firstQuestion = questions.first else { return UIViewController() } + return createController(for: firstQuestion) + } + + private func createController(for question: SurveyStoreModule.Question) -> UIViewController { + + let controller = SurveyStoreModule.createQuestionController(using: surveyStore, for: question) + controller.title = progressString(for: question) + return controller + } + + private func assemblePrizeScreen(prize: SurveyFlowModule.Prize) -> TextScreenModule.Screen { + + let initialState = TextScreenModule.State(title: "You won a \(prize.name)!!", description: "\(prize.description)\n\nA $\(prize.dollarValue) value!!", buttons: ["OMG!!"]) + + return TextScreenModule.createScreen(with: initialState) + .observeOutput({ [weak self] output in + switch output { + case .didTapButton: + self?.finish(prize: prize) + } + + }) + } + + private func handleDidPressSubmit(for question: SurveyStoreModule.Question) { + guard let i = questions.firstIndex(where: { $0 == question }) else { return } + if i < questions.count - 1 { + let nextQuestion = questions[i + 1] + createController(for: nextQuestion).place(with: nextPushedInFlow) + } + else { + submitForPrize() + } + } + + private func submitForPrize() { + getPrize(responses) { [weak self] prize in + executeOnMainThread { + guard let self = self else { return } + if let prize = prize { + self.assemblePrizeScreen(prize: prize).controller.place(with: self.nextPushedInFlow) + } + else { + self.finish() + } + } + } + } + + private func finish(prize: SurveyFlowModule.Prize? = nil) { + dispatchOutput(.didFinish(responses: responses, prize: prize)) + } + + private var responses: SurveyStoreModule.Responses { + return surveyStore.state.responses + } + + private func progressString(for question: SurveyStoreModule.Question) -> String? { + guard let i = questions.firstIndex(where: { $0 == question }) else { return nil } + return "\(i + 1) of \(questions.count)" + } + +} + +extension SurveyFlow { + + static var favorites: SurveyFlow { + let questions = [ + "What are your favorite foods?", + "If you were a superhero, what superpowers would you have?", + "What are your favorite kinds of dogs?" + ] + return SurveyFlow(questions: questions) + } + +} diff --git a/Example/Lasso/Samples/Survey/SurveyModule.swift b/Example/Lasso/Samples/Survey/SurveyModule.swift new file mode 100644 index 0000000..0934f60 --- /dev/null +++ b/Example/Lasso/Samples/Survey/SurveyModule.swift @@ -0,0 +1,115 @@ +// +//===----------------------------------------------------------------------===// +// +// SurveyModule.swift +// +// Created by Trevor Beasty on 6/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation +import Lasso + +enum SurveyStoreModule: StoreModule { + + struct State: Equatable { + let questions: [Question] + var proposed: [Question: String] + var responses: Responses + } + + typealias Question = String + typealias Answer = String + typealias Responses = [Question: [Answer]] + + enum Action: Equatable { + case didPressSubmit(question: Question) + case didPressAdd(question: Question) + case didEditProposed(question: Question, proposed: String?) + } + + enum Output: Equatable { + case didPressSubmit(question: Question) + } + + static func createQuestionController(using store: Store, for question: Question) -> UIViewController { + + let stateMap = { (state: State) -> MakeListViewModule.ViewState in + return MakeListViewModule.ViewState( + header: question, + placeholder: "", + proposed: state.proposed[question], + submitted: state.responses[question] ?? [] + ) + } + + let actionMap = { (viewAction: MakeListViewModule.ViewAction) -> Action in + switch viewAction { + + case .didEditProposed(let proposed): + return .didEditProposed(question: question, proposed: proposed) + + case .didPressAdd: + return .didPressAdd(question: question) + + case .didPressSubmit: + return .didPressSubmit(question: question) + } + } + + let viewStore = store.asViewStore(stateMap: stateMap, actionMap: actionMap) + return MakeListViewController(store: viewStore) + } + +} + +extension SurveyStoreModule.State { + + init(questions: [SurveyStoreModule.Question]) { + self.init(questions: questions, proposed: [:], responses: questions.asDictionary([])) + } + +} + +class SurveyStore: LassoStore { + + override func handleAction(_ action: Action) { + switch action { + + case .didPressSubmit(question: let question): + dispatchOutput(.didPressSubmit(question: question)) + + case .didPressAdd(question: let question): + update { state in + if let proposed = state.proposed[question], !proposed.isEmpty { + state.responses[question]?.append(proposed) + state.proposed[question] = nil + } + } + + case let .didEditProposed(question: question, proposed: proposed): + update { state in + state.proposed[question] = proposed + } + } + } + +} + +private extension Array where Element: Hashable { + + func asDictionary(_ value: Value) -> [Element: Value] { + return reduce(into: [:], { (dict, element) in + dict[element] = value + }) + } + +} diff --git a/Example/Lasso/Samples/Tabs/RandomItems.swift b/Example/Lasso/Samples/Tabs/RandomItems.swift new file mode 100644 index 0000000..fd9884b --- /dev/null +++ b/Example/Lasso/Samples/Tabs/RandomItems.swift @@ -0,0 +1,200 @@ +// +//===----------------------------------------------------------------------===// +// +// RandomItems.swift +// +// Created by Steven Grosmark on 5/21/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +/// Displays a searchable table view of items. +/// Selecting an item shows details for the item +/// +/// This flow emits No output. +/// This flow requires that it is placed in a navigation controller. +class RandomItemsFlow: Flow { + + /// Creates the initial view controller for the RandomItemsFlow - + /// the searchable table view of items. + override func createInitialController() -> UIViewController { + return RandomItems + .createScreen() + .observeOutput(handleOutput) + .controller + } + + /// Handles the Output from the RandomItems screen - the table view + private func handleOutput(_ output: RandomItems.Output) { + switch output { + + case .didSelectItem(let item): + let state = TextScreenModule.State(title: item.name, + description: item.description) + TextScreenModule + .createScreen(with: state) + .place(with: nextPushedInFlow) + } + } +} + +enum RandomItems: ScreenModule { + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: RandomItemsStore) -> Screen { + let controller = RandomItemsViewController(store: store.asViewStore()) + return Screen(store, controller) + } + + enum Action: Equatable { + case didSelectItem(Item) + case didUpdateSearchQuery(String?) + } + + enum Output: Equatable { + case didSelectItem(Item) + } + + struct State: Equatable { + let items: [Item] + var query: String? + var foundItems: [Item]? + } + + struct Item: Equatable { + let name: String + let description: String + } + +} + +class RandomItemsStore: LassoStore { + + override func handleAction(_ action: RandomItems.Action) { + switch action { + + case .didSelectItem(let item): + dispatchOutput(.didSelectItem(item)) + + case .didUpdateSearchQuery(let query): + if let query = query, !query.isEmpty { + update { state in + state.query = query + state.foundItems = state.items.filter { $0.name.range(of: query, options: .caseInsensitive) != nil } + } + } + else { + update { state in + state.query = nil + state.foundItems = nil + } + } + } + } +} + +extension RandomItems.State { + init() { + var items = [RandomItems.Item]() + for _ in 0..<30 { + let item = RandomItems.Item(name: String.randomWord().capitalized, + description: .loremIpsum(sentences: Int.random(in: 1...4))) + items.append(item) + } + self.items = items + } +} + +class RandomItemsViewController: UIViewController, LassoView { + + let store: RandomItems.ViewStore + private let tableView = UITableView() + private let searchController = UISearchController(searchResultsController: nil) + + init(store: RandomItems.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .background + + tableView.register(type: UITableViewCell.self) + tableView.delegate = self + tableView.dataSource = self + + searchController.searchResultsUpdater = self + searchController.hidesNavigationBarDuringPresentation = false + searchController.dimsBackgroundDuringPresentation = false + tableView.tableHeaderView = searchController.searchBar + + // Allows search controller to get pushed along with everything else, and maintain its state: + definesPresentationContext = true + automaticallyAdjustsScrollViewInsets = false + + view.addSubview(tableView) + + tableView.layout.fill(.safeArea) + + // State observations: + // (note that `items` is a `let` property - meaning it won't change - so no need to observe it) + store.observeState(\.query) { [weak self] query in + self?.searchController.searchBar.text = query + } + store.observeState(\.foundItems) { [weak self] _ in + self?.tableView.reloadData() + } + } + +} + +extension RandomItemsViewController: UISearchResultsUpdating { + + func updateSearchResults(for searchController: UISearchController) { + store.dispatchAction(.didUpdateSearchQuery(searchController.searchBar.text)) + } + +} + +extension RandomItemsViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let items = store.state.foundItems ?? store.state.items + let item = items[indexPath.row] + + store.dispatchAction(.didSelectItem(item)) + } +} + +extension RandomItemsViewController: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return store.state.foundItems?.count ?? store.state.items.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueCell(type: UITableViewCell.self, indexPath: indexPath) + let items = store.state.foundItems ?? store.state.items + let item = items[indexPath.row] + cell.textLabel?.text = item.name + return cell + } + +} diff --git a/Example/Lasso/Samples/Tabs/TabsFlow.swift b/Example/Lasso/Samples/Tabs/TabsFlow.swift new file mode 100644 index 0000000..512ce62 --- /dev/null +++ b/Example/Lasso/Samples/Tabs/TabsFlow.swift @@ -0,0 +1,55 @@ +// +//===----------------------------------------------------------------------===// +// +// TabsFlow.swift +// +// Created by Steven Grosmark on 5/10/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +class TabsFlow: Flow { + + override func createInitialController() -> UIViewController { + + let tabBarController = UITabBarController() + let tabBarPlacers = tabBarEmbedding(tabBarController, tabsCount: 4) + + let circleState = CounterScreenModule.State(counter: 0, style: .dark) + CounterScreenModule.createScreen(with: circleState) + .setUpController { $0.tabBarItem = UITabBarItem(title: "Circles", image: UIImage(named: "circle"), tag: 0) } + .place(with: tabBarPlacers[0]) + + for (index, name) in [(1, "Two"), (2, "Three")] { + let textState = TextScreenModule.State(title: "Tab #\(index + 1)", description: "text text text.") + TextScreenModule + .createScreen(with: textState) + .setUpController { $0.tabBarItem = UITabBarItem(title: name, image: UIImage(named: "square"), tag: index) } + .place(with: tabBarPlacers[index]) + } + + let navigationController = UINavigationController() + navigationController.tabBarItem = UITabBarItem(title: "Onboarding", image: UIImage(named: "circle"), tag: 0) + FoodOnboardingFlow() + .observeOutput({ [weak self] output in + switch output { + case .didFinish: + self?.dismiss() + } + }) + .start(with: tabBarPlacers[3].withNavigationEmbedding(navigationController)) + + return tabBarController + } + +} diff --git a/Example/Lasso/Samples/TextScreen.swift b/Example/Lasso/Samples/TextScreen.swift new file mode 100644 index 0000000..e67e4c3 --- /dev/null +++ b/Example/Lasso/Samples/TextScreen.swift @@ -0,0 +1,100 @@ +// +//===----------------------------------------------------------------------===// +// +// TextScreenModule.swift +// +// Created by Trevor Beasty on 5/10/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +enum TextScreenModule: PassthroughScreenModule { + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: PassthroughStore) -> Screen { + let controller = TextViewController(viewStore: store.asViewStore()) + return Screen(store, controller) + } + + struct State: Equatable { + let title: String? + let description: String? + let buttons: [String] + + init(title: String? = nil, description: String? = nil, buttons: [String] = []) { + self.title = title + self.description = description + self.buttons = buttons + } + } + + enum Action: Equatable { + case didTapButton(_ index: Int) + } + + typealias Output = Action + +} + +class TextViewController: UIViewController, LassoView { + + let store: TextScreenModule.ViewStore + + init(viewStore: TextScreenModule.ViewStore) { + self.store = viewStore + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { fatalError() } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .background + + // set up labels + let titleLabel = UILabel(headline: store.state.title) + let descriptionLabel = UILabel(body: store.state.description) + + // arrange labels + view.addSubviews(titleLabel, descriptionLabel) + + titleLabel.layout + .fill(.safeArea, except: .bottom, inset: 20) + + descriptionLabel.layout + .below(titleLabel, offset: 20) + .fill(.safeArea, axis: .x, inset: 20) + + // add buttons + var previous: UIView = descriptionLabel + var offset: CGFloat = 50 + + for (index, title) in store.state.buttons.enumerated() { + let button = UIButton(standardButtonWithTitle: title) + + view.addSubview(button) + + button.layout + .below(previous, offset: offset) + .fillWidth(of: .safeArea, inset: 20, maximum: 300) + + button.bind(to: store, action: .didTapButton(index)) + previous = button + offset = 20 + } + } + +} diff --git a/Example/Lasso/Samples/UIKitBindings.swift b/Example/Lasso/Samples/UIKitBindings.swift new file mode 100644 index 0000000..4f94d09 --- /dev/null +++ b/Example/Lasso/Samples/UIKitBindings.swift @@ -0,0 +1,140 @@ +// +//===----------------------------------------------------------------------===// +// +// UIKitBindings.swift +// +// Created by Steven Grosmark on 6/7/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout +import Lasso + +enum UIKitBindingsScreenModule: ScreenModule { + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: UIKitBindingsStore) -> Screen { + let controller = UIKitBindingsViewController(store: store.asViewStore()) + return Screen(store, controller) + } + + enum Action: Equatable { + case didToggleOverdrive(Bool) + case didChangeVolume(Float) + case didSelectMode(Int) + case didSelectPage(Int) + case didChangeAmount(Double) + case didEditText(String) + case didChangeDate(Date) + case didTapButton + } + + struct State: Equatable { + var lastAction: String = "" + } +} + +class UIKitBindingsStore: LassoStore { + + override func handleAction(_ action: Action) { + update { state in + state.lastAction = "\(action)" + } + } + +} + +class UIKitBindingsViewController: UIViewController, LassoView { + + let store: UIKitBindingsScreenModule.ViewStore + + init(store: UIKitBindingsScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { return nil } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .background + + let stack = UIStackView() + stack.axis = .vertical + stack.alignment = .center + stack.spacing = 20 + + view.addSubview(stack) + stack.layout.fill(.safeArea, except: .bottom, inset: 20) + + let onOffSwitch = UISwitch() + + let slider = UISlider() + slider.isContinuous = true + slider.layout.width(280) + + let segmentedControl = UISegmentedControl(items: ["Dark", "Light", "Auto"]) + segmentedControl.layout.width(280) + + let pageControl = UIPageControl() + pageControl.numberOfPages = 8 + pageControl.pageIndicatorTintColor = .darkGray + pageControl.currentPageIndicatorTintColor = .green + pageControl.layout.width(280) + + let stepper = UIStepper() + stepper.minimumValue = 0 + stepper.maximumValue = 99 + stepper.isContinuous = true + + let textField = UITextField(placeholder: "Enter something") + textField.layout.width(280) + + let button = UIButton(standardButtonWithTitle: "Button") + button.layout.width(280) + + let datePicker = UIDatePicker() + datePicker.datePickerMode = .date + + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + label.layout.size(280, 80) + + stack.addArrangedSubviews(onOffSwitch, slider, segmentedControl, pageControl, stepper, textField, button, datePicker, label) + + // bind value changed events to dispatch an action on the store: + onOffSwitch.bindValueChange(to: store) { .didToggleOverdrive($0) } + slider.bindValueChange(to: store) { .didChangeVolume($0) } + segmentedControl.bindValueChange(to: store) { .didSelectMode($0) } + pageControl.bindValueChange(to: store) { .didSelectPage($0) } + stepper.bindValueChange(to: store) { .didChangeAmount($0) } + textField.bindTextDidChange(to: store) { .didEditText($0) } + datePicker.bindDateChange(to: store) { .didChangeDate($0) } + + // bind .touchUpInside directly to a dispatched action on the store: + button.bind(to: store, action: .didTapButton) + + store.observeState(\.lastAction) { [weak label] lastAction in + label?.text = lastAction + } + } + +} + +extension UIStackView { + + public func addArrangedSubviews(_ subviews: UIView...) { + subviews.forEach(addArrangedSubview) + } +} diff --git a/Example/Lasso/Utilities/RandomStrings.swift b/Example/Lasso/Utilities/RandomStrings.swift new file mode 100644 index 0000000..7e33885 --- /dev/null +++ b/Example/Lasso/Utilities/RandomStrings.swift @@ -0,0 +1,62 @@ +// +//===----------------------------------------------------------------------===// +// +// Utilities.swift +// +// Created by Steven Grosmark on 5/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +extension String { + + static func loremIpsum(paragraphs count: Int) -> String { + var paragraphs = [String]() + for _ in 0.. String { + var sentences = [String]() + for _ in 0.. String { + let words = ["fusce", "nam", "mollis", "ultrices", "vehicula", "congue", "nunc", "condimentum", "sapien", "porttitor", "risus", "id", "donec", "feugiat", "fames", "fringilla", "ipsum", "quis", "at", "dictum", "adipiscing", "faucibus", "lacinia", "facilisis", "magna", "vitae", "leo", "porta", "tortor", "dignissim", "est", "suspendisse", "egestas", "augue", "pulvinar", "venenatis", "iaculis", "suscipit", "velit", "eget", "volutpat", "vivamus", "posuere", "pellentesque", "arcu", "cras", "enim", "sollicitudin", "consequat", "mattis", "vestibulum", "sed", "nisi", "ante", "rhoncus", "ut", "ligula", "dapibus", "lobortis", "odio", "ultricies", "elementum", "molestie", "mi", "lorem", "viverra", "a", "tellus", "euismod", "dui", "massa", "aliquet", "lectus", "diam", "neque", "sem", "sagittis", "pharetra", "efficitur", "aliquam", "nullam", "vulputate", "mauris", "cursus", "ac", "interdum", "maecenas", "facilisi", "ex", "malesuada", "sit", "felis", "varius", "turpis", "hendrerit", "accumsan", "tempus", "auctor", "integer", "proin", "convallis", "libero", "tincidunt", "sodales", "morbi", "gravida", "orci", "elit", "non", "nibh", "nulla", "placerat", "praesent", "dolor", "vel", "lacus", "amet", "et", "urna", "fermentum", "eros", "erat", "purus", "aenean", "finibus", "consectetur", "laoreet", "primis", "in", "tristique", "ornare", "etiam", "maximus", "nec", "quam"].shuffled() + return words[0.. String { + let consonants = "bcdfghjklmnprstvwz" + let vowels = "aeiou" + var s = "" + for _ in 0.. Character { + return randomElement() ?? "a" + } + + var firstLetter: String { + guard !isEmpty else { return "" } + return String(self[startIndex..=5.0) +#else +enum Result { + case success(T) + case failure(E) +} +#endif diff --git a/Example/Lasso/Utilities/ViewHelpers/UIActivityIndicatorView.swift b/Example/Lasso/Utilities/ViewHelpers/UIActivityIndicatorView.swift new file mode 100644 index 0000000..0313552 --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UIActivityIndicatorView.swift @@ -0,0 +1,31 @@ +// +//===----------------------------------------------------------------------===// +// +// UIActivityIndicatorView.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UIActivityIndicatorView { + + public var animating: Bool { + set { + if newValue { startAnimating() } + else { stopAnimating() } + } + get { + return isAnimating + } + } +} diff --git a/Example/Lasso/Utilities/ViewHelpers/UIButton.swift b/Example/Lasso/Utilities/ViewHelpers/UIButton.swift new file mode 100644 index 0000000..f05914a --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UIButton.swift @@ -0,0 +1,43 @@ +// +//===----------------------------------------------------------------------===// +// +// UIButton.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UIButton { + + convenience init(standardButtonWithTitle title: String) { + self.init(type: .custom) + setTitle(title, for: .normal) + set(cornerRadius: 5) + setBackgroundImage(.create(withColor: UIColor.blue.withAlphaComponent(0.5)), for: .normal) + setBackgroundImage(.create(withColor: UIColor.gray.withAlphaComponent(0.5)), for: .disabled) + layout.height(44) + } + + @discardableResult + public func set(title: String, with font: UIFont?) -> UIButton { + if let font = font { + let attribs: [NSAttributedString.Key: Any] = [.font: font] + self.setAttributedTitle(NSAttributedString(string: title, attributes: attribs), for: .normal) + } + else { + self.setTitle(title, for: .normal) + } + return self + } + +} diff --git a/Example/Lasso/Utilities/ViewHelpers/UIColor.swift b/Example/Lasso/Utilities/ViewHelpers/UIColor.swift new file mode 100644 index 0000000..6f8f8ff --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UIColor.swift @@ -0,0 +1,46 @@ +// +//===----------------------------------------------------------------------===// +// +// UIColor.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UIColor { + + static var background: UIColor { + if #available(iOS 13.0, *) { + return UIColor.systemBackground + } + else { + return UIColor.white + } + } + + static var text: UIColor { + if #available(iOS 13.0, *) { + return UIColor.systemFill + } + else { + return UIColor.black + } + } + + #if canImport(SwiftUI) + #else + static let systemBackground: UIColor = .white + static let systemFill: UIColor = .black + static let systemGray2: UIColor = .lightGray + #endif +} diff --git a/Example/Lasso/Utilities/ViewHelpers/UIImage.swift b/Example/Lasso/Utilities/ViewHelpers/UIImage.swift new file mode 100644 index 0000000..72719fa --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UIImage.swift @@ -0,0 +1,42 @@ +// +//===----------------------------------------------------------------------===// +// +// UIImage.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UIImage { + + /// Create a UIImage of a solid color + /// + /// - Parameters: + /// - color: The color of the resulting image + /// - size: The new image dimensions + /// - Returns: a new UIImage + public static func create(withColor color: UIColor, size: CGSize = CGSize(width: 1.0, height: 1.0)) -> UIImage? { + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + + UIGraphicsBeginImageContext(rect.size) + defer { UIGraphicsEndImageContext() } + + let context = UIGraphicsGetCurrentContext() + + context?.setFillColor(color.cgColor) + context?.fill(rect) + + return UIGraphicsGetImageFromCurrentImageContext() + } + +} diff --git a/Example/Lasso/Utilities/ViewHelpers/UILabel.swift b/Example/Lasso/Utilities/ViewHelpers/UILabel.swift new file mode 100644 index 0000000..94b97ef --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UILabel.swift @@ -0,0 +1,38 @@ +// +//===----------------------------------------------------------------------===// +// +// UILabel.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UILabel { + + public convenience init(headline: String?) { + self.init() + commonInit(.systemFont(ofSize: 40, weight: .regular), text: headline) + } + + public convenience init(body: String?, size: CGFloat = 20, weight: UIFont.Weight = .regular) { + self.init() + commonInit(.systemFont(ofSize: size, weight: weight), text: body) + } + + private func commonInit(_ font: UIFont, text: String?) { + self.font = font + self.numberOfLines = 0 + self.text = text + } + +} diff --git a/Example/Lasso/Utilities/ViewHelpers/UITableView.swift b/Example/Lasso/Utilities/ViewHelpers/UITableView.swift new file mode 100644 index 0000000..c44d89f --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UITableView.swift @@ -0,0 +1,37 @@ +// +//===----------------------------------------------------------------------===// +// +// UITableView.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UITableViewCell { + static var identifier: String { return "\(type(of: self))" } +} + +extension UITableView { + + func register(type: CellType.Type) { + register(CellType.self, forCellReuseIdentifier: CellType.identifier) + } + + func dequeueCell(type: CellType.Type, indexPath: IndexPath) -> CellType { + guard let cell = dequeueReusableCell(withIdentifier: CellType.identifier, for: indexPath) as? CellType else { + return CellType() + } + return cell + } + +} diff --git a/Example/Lasso/Utilities/ViewHelpers/UITextField.swift b/Example/Lasso/Utilities/ViewHelpers/UITextField.swift new file mode 100644 index 0000000..eeb6922 --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UITextField.swift @@ -0,0 +1,44 @@ +// +//===----------------------------------------------------------------------===// +// +// UITextField.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import WWLayout + +extension UITextField { + + public convenience init(placeholder: String, + text: String = "", + autocorrect: UITextAutocorrectionType = .no, + autocapitalize: UITextAutocapitalizationType = .none) { + self.init() + let border = UIView() + + self.autocorrectionType = autocorrect + self.autocapitalizationType = autocapitalize + self.placeholder = placeholder + self.addSubview(border) + + border.layout.fill(self, except: .top, inset: Insets(0, -4)).height(2) + if #available(iOS 13.0, *) { + border.backgroundColor = .systemGray2 + } + else { + border.backgroundColor = .lightGray + } + } + +} diff --git a/Example/Lasso/Utilities/ViewHelpers/UIView.swift b/Example/Lasso/Utilities/ViewHelpers/UIView.swift new file mode 100644 index 0000000..f4349c5 --- /dev/null +++ b/Example/Lasso/Utilities/ViewHelpers/UIView.swift @@ -0,0 +1,40 @@ +// +//===----------------------------------------------------------------------===// +// +// UIView.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UIView { + + public func addSubviews(_ views: UIView ...) { + views.forEach(addSubview) + } + + @discardableResult + public func set(cornerRadius: CGFloat) -> UIView { + layer.cornerRadius = cornerRadius + clipsToBounds = true + return self + } + + @discardableResult + public func set(borderColor: UIColor, thickness: CGFloat) -> UIView { + layer.borderColor = borderColor.cgColor + layer.borderWidth = thickness + return self + } + +} diff --git a/Example/LassoTestUtilities_Tests/Info.plist b/Example/LassoTestUtilities_Tests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Example/LassoTestUtilities_Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example/LassoTestUtilities_Tests/LifeCycleController.swift b/Example/LassoTestUtilities_Tests/LifeCycleController.swift new file mode 100644 index 0000000..6af84a4 --- /dev/null +++ b/Example/LassoTestUtilities_Tests/LifeCycleController.swift @@ -0,0 +1,64 @@ +// +//===----------------------------------------------------------------------===// +// +// LifeCycleController.swift +// +// Created by Trevor Beasty on 8/8/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +class LifeCycleController: UIViewController { + + enum LifeCycleEvent: Hashable, CaseIterable { + case viewDidLoad + case viewWillAppear + case viewDidAppear + case viewWillDisappear + case viewDidDisappear + } + + var lifeCycleEvents: [LifeCycleEvent] = [] + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + let label = UILabel() + label.text = "LifeCycleController" + label.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(label) + NSLayoutConstraint.activate([view.centerXAnchor.constraint(equalTo: label.centerXAnchor), + view.centerYAnchor.constraint(equalTo: label.centerYAnchor)]) + lifeCycleEvents.append(.viewDidLoad) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + lifeCycleEvents.append(.viewWillAppear) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + lifeCycleEvents.append(.viewDidAppear) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + lifeCycleEvents.append(.viewWillDisappear) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + lifeCycleEvents.append(.viewDidDisappear) + } + +} diff --git a/Example/LassoTestUtilities_Tests/MockableExtensionTests.swift b/Example/LassoTestUtilities_Tests/MockableExtensionTests.swift new file mode 100644 index 0000000..57010c6 --- /dev/null +++ b/Example/LassoTestUtilities_Tests/MockableExtensionTests.swift @@ -0,0 +1,50 @@ +// +//===----------------------------------------------------------------------===// +// +// MockableExtensionTests.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities + +#if swift(>=5.1) +class MockableExtensionTests: XCTestCase { + + @Mockable var number: Int = 1 + + func test_mock() { + // given - initial value + XCTAssertEqual(number, 1) + + // when - apply mock + mock($number, with: 123) + + // then - mocked value + XCTAssertEqual(number, 123) + } + + func test_mockAgain() { + // given - initial value + XCTAssertEqual(number, 1) + + // when - apply mock + mock($number, with: 999) + + // then - mocked value + XCTAssertEqual(number, 999) + } + +} +#endif diff --git a/Example/LassoTestUtilities_Tests/ModalTestingTests+AnyModalPresentationStyle.swift b/Example/LassoTestUtilities_Tests/ModalTestingTests+AnyModalPresentationStyle.swift new file mode 100644 index 0000000..ddcfd30 --- /dev/null +++ b/Example/LassoTestUtilities_Tests/ModalTestingTests+AnyModalPresentationStyle.swift @@ -0,0 +1,419 @@ +// +//===----------------------------------------------------------------------===// +// +// ModalTestingTests+AnyModalPresentationStyle.swift +// +// Created by Trevor Beasty on 8/30/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import LassoTestUtilities + +class ModalTestingTestsAnyModalPresentationStyle: XCTestCase { + + private func testSupportedStyles(_ test: (UIModalPresentationStyle) throws -> Void) throws { + let styles: [UIModalPresentationStyle] = [.pageSheet, .fullScreen] + try styles.forEach(test) + } + + // MARK: - Presentation + + func test_Animated_Presentation() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let result: LifeCycleController = try self.assertPresentation( + on: vc0, + when: { vc0.present(vc1, animated: true, completion: nil) }, + onViewDidLoad: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad]) + }, + onViewWillAppear: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear]) + }, + onViewDidAppear: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }) + XCTAssertTrue(result === vc1) + } + + try testSupportedStyles(test) + } + + func test_Presentation_WithPresentingEmbedding_Succeeds() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let nav0 = UINavigationController(rootViewController: vc0) + let vc1 = UIViewController() + window.rootViewController = nav0 + waitForEvents(in: window) + + // when / then + let result: UIViewController = try self.assertPresentation( + on: vc0, + when: { vc0.present(vc1, animated: true, completion: nil) } + ) + XCTAssertTrue(result === vc1) + } + + func test_Presentation_WithModalStackLength3_Succeeds() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let nav0 = UINavigationController(rootViewController: vc0) + let vc1 = UIViewController() + let vc2 = UIViewController() + window.rootViewController = nav0 + waitForEvents(in: window) + nav0.present(vc1, animated: false, completion: nil) + waitForEvents(in: window) + + // when / then + let result: UIViewController = try self.assertPresentation( + on: vc1, + when: { vc1.present(vc2, animated: false, completion: nil) } + ) + XCTAssertTrue(result === vc2) + } + + // No guarantees are made with respect to viewWillAppear for unanimated presentations. It + // does not make sense to hook into viewWillAppear b/c it is conceptually irrelevant - when + // there is no animation, appearance happens all at once and there is no difference between 'will' + // and 'did' appear. 'did' is the only appropriate hook here. + func test_NotAnimated_Presentation() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let result: LifeCycleController = try self.assertPresentation( + on: vc0, + when: { vc0.present(vc1, animated: false, completion: nil) }, + onViewDidLoad: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad]) + }, + onViewDidAppear: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }) + XCTAssertTrue(result === vc1) + } + + try testSupportedStyles(test) + } + + func test_NoPresentationOnWhen_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { try _ = assertPresentation(on: vc0, when: { }, failTest: silent) }, + eval: { + switch $0 { + case ModalPresentationError.noPresentationOccurred: () + default: unexpectedErrorType() + } + }) + } + + func test_PresentingWrongType_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + let _: UINavigationController = try assertPresentation( + on: vc0, + when: { vc0.present(vc1, animated: false, completion: nil) }, + failTest: silent + ) + }, + eval: { + switch $0 { + case ModalPresentationTypeError.unexpectedPresentedTypeFollowingEvent(realized: let realized): + XCTAssertTrue(realized === vc1) + + default: + unexpectedErrorType() + } + }) + } + + func test_PresentingNotModallyForemostPrecedingPresentationEvent_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.present(vc1, animated: false, completion: nil) + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { let _: UIViewController = try assertPresentation(on: vc0, when: { }, failTest: silent) }, + eval: { + switch $0 { + case ModalPresentationError.unexpectedModallyForemostPrecedingEvent(expected: let expected, realized: let realized): + XCTAssertTrue(expected === vc0) + XCTAssertTrue(realized === vc1) + + default: + unexpectedErrorType() + } + }) + } + + func test_PresentedNotModallyForemostFollowingPresentationEvent_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let vc2 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + let _: UIViewController = try assertPresentation( + on: vc0, + when: { + vc0.present(vc1, animated: false, completion: nil) + waitForEvents(in: window) + vc1.present(vc2, animated: false, completion: nil) + waitForEvents(in: window) + }, + failTest: silent) + }, + eval: { + switch $0 { + case ModalPresentationError.unexpectedModallyForemostFollowingEvent(expected: let expected, realized: let realized): + XCTAssertTrue(expected == [vc0, vc1]) + XCTAssertTrue(realized == [vc1, vc2]) + + default: + unexpectedErrorType() + } + }) + } + + // MARK: - Dismissal + + func test_Animated_Dismissal() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.present(vc1, animated: true, completion: nil) + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + try self.assertDismissal( + from: vc1, + to: vc0, + when: { vc0.dismiss(animated: true, completion: nil) }, + onViewWillAppear: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear]) + }, + onViewDidAppear: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + } + + try testSupportedStyles(test) + } + + func test_NotAnimated_Dismissal() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.present(vc1, animated: true, completion: nil) + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + try self.assertDismissal( + from: vc1, + to: vc0, + when: { vc0.dismiss(animated: false, completion: nil) }, + onViewDidAppear: { presented in + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + } + + try testSupportedStyles(test) + } + + func test_PresentedNotForemostPrecedingDismissalEvent_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let vc2 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.present(vc1, animated: false, completion: nil) + waitForEvents(in: window) + vc1.present(vc2, animated: false, completion: nil) + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { try _ = assertDismissal(from: vc1, to: vc0, when: { }, failTest: silent) }, + eval: { + switch $0 { + + case ModalPresentationError.unexpectedModallyForemostPrecedingEvent(expected: let expected, realized: let realized): + XCTAssertTrue(expected === vc1) + XCTAssertTrue(realized === vc2) + + default: + unexpectedErrorType() + } + }) + } + + func test_EmptyDismissalEvent_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let vc2 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.present(vc1, animated: false, completion: nil) + waitForEvents(in: window) + vc1.present(vc2, animated: false, completion: nil) + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertDismissal( + from: vc2, + to: vc0, + when: { }, + failTest: silent + ) + }, + eval: { + switch $0 { + + case ModalPresentationError.noDismissalOccurred: + () + + default: + unexpectedErrorType() + } + }) + } + + func test_PresentingNotForemostFollowingDismissalEvent_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let vc2 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.present(vc1, animated: false, completion: nil) + waitForEvents(in: window) + vc1.present(vc2, animated: false, completion: nil) + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertDismissal( + from: vc2, + to: vc0, + when: { vc1.dismiss(animated: false, completion: nil) }, + failTest: silent + ) + }, + eval: { + switch $0 { + + case ModalPresentationError.unexpectedModallyForemostFollowingEvent(expected: let expected, realized: let realized): + XCTAssertTrue(expected == [vc0]) + XCTAssertTrue(realized == [vc1]) + + default: + unexpectedErrorType() + } + }) + } + +} diff --git a/Example/LassoTestUtilities_Tests/ModalTestingTests+FullScreen.swift b/Example/LassoTestUtilities_Tests/ModalTestingTests+FullScreen.swift new file mode 100644 index 0000000..eae9dd5 --- /dev/null +++ b/Example/LassoTestUtilities_Tests/ModalTestingTests+FullScreen.swift @@ -0,0 +1,265 @@ +// +//===----------------------------------------------------------------------===// +// +// ModalTestingTests+FullScreen.swift +// +// Created by Trevor Beasty on 8/30/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import LassoTestUtilities + +class ModalTestingTestsFullScreen: XCTestCase { + + // MARK: - Success + + func test_Animated_Presentation() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = .fullScreen + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let _: UIViewController = try assertFullScreenPresentation( + on: vc0, + when: { vc0.present(vc1, animated: true, completion: nil) }, + onViewDidLoad: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, []) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad]) + }, + onViewWillAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear]) + }, + onViewDidAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }) + } + + func test_NotAnimated_Presentation() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = .fullScreen + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let _: UIViewController = try assertFullScreenPresentation( + on: vc0, + when: { vc0.present(vc1, animated: false, completion: nil) }, + onViewDidLoad: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, []) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad]) + }, + // For non-animated modal presentations, viewWillAppear is not distinguishable from viewDidAppear. + onViewWillAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }, + onViewDidAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }) + } + + // Tests should pass if assertPresentation is called where 'presenting' is a child of a navigation controller + func test_Presentation_OnNavigationEmbeddedController() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let navigationController = UINavigationController(rootViewController: vc0) + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = .fullScreen + window.rootViewController = navigationController + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let _: UIViewController = try assertFullScreenPresentation( + on: vc0, + when: { vc0.present(vc1, animated: true, completion: nil) }, + onViewDidLoad: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, []) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad]) + }, + onViewWillAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear]) + }, + onViewDidAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }) + } + + func test_Animated_Dismissal() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = .fullScreen + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.present(vc1, animated: true, completion: nil) + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + try assertFullScreenDismissal( + from: vc1, + to: vc0, + when: { vc1.dismiss(animated: true, completion: nil) }, + onViewWillAppear: { presented, presenting in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear]) + }, + onViewDidAppear: { presented, presenting in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + } + + func test_NotAnimated_Dismissal() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = .fullScreen + window.rootViewController = vc0 + waitForEvents(in: window) + vc0.present(vc1, animated: true, completion: nil) + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + try assertFullScreenDismissal( + from: vc1, + to: vc0, + when: { vc1.dismiss(animated: false, completion: nil) }, + // For non-animated modal dismissals, viewWillAppear is not distinguishable from viewDidAppear. + onViewWillAppear: { presented, presenting in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }, + onViewDidAppear: { presented, presenting in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + } + + // MARK: - Failure + + // TODO: pass locally but not on circle for <= Xcode 10.3. Likely due to system overrides for modalPresentationStyle + // based on device 'size class'. There is no available api to configure this. +// func test_PresentingWrongModalStyle_Fails() { +// // given +// let window = UIWindow() +// window.makeKeyAndVisible() +// let vc0 = UIViewController() +// let vc1 = UIViewController() +// vc1.modalPresentationStyle = .pageSheet +// window.rootViewController = vc0 +// waitForEvents(in: window) +// +// // when / then +// assertThrowsError( +// expr: { +// try _ = assertFullScreenPresentation(on: vc0, when: { vc0.present(vc1, animated: false, completion: nil) }, failTest: silent) +// }, +// eval: { +// switch $0 { +// +// case ModalPresentationError.unexpectedModalPresentationStyle(expected: let expected, realized: let realized): +// XCTAssertEqual(expected, .fullScreen) +// XCTAssertEqual(realized, .pageSheet) +// +// default: +// unexpectedErrorType() +// } +// }) +// } +// +// func test_DismissingWrongModalStyle_Fails() { +// // given +// let window = UIWindow() +// window.makeKeyAndVisible() +// let vc0 = UIViewController() +// let vc1 = UIViewController() +// vc1.modalPresentationStyle = .pageSheet +// window.rootViewController = vc0 +// waitForEvents(in: window) +// vc0.present(vc1, animated: false, completion: nil) +// waitForEvents(in: window) +// +// // when / then +// assertThrowsError( +// expr: { +// try assertFullScreenDismissal(from: vc1, to: vc0, when: { vc1.dismiss(animated: false, completion: nil) }, failTest: silent) +// }, +// eval: { +// switch $0 { +// +// case ModalPresentationError.unexpectedModalPresentationStyle(expected: let expected, realized: let realized): +// XCTAssertEqual(expected, .fullScreen) +// XCTAssertEqual(realized, .pageSheet) +// +// default: +// unexpectedErrorType() +// } +// }) +// } + +} diff --git a/Example/LassoTestUtilities_Tests/ModalTestingTests+SystemBehavior.swift b/Example/LassoTestUtilities_Tests/ModalTestingTests+SystemBehavior.swift new file mode 100644 index 0000000..6af4ac8 --- /dev/null +++ b/Example/LassoTestUtilities_Tests/ModalTestingTests+SystemBehavior.swift @@ -0,0 +1,326 @@ +// +//===----------------------------------------------------------------------===// +// +// ModalTestingTests+SystemBehavior.swift +// +// Created by Trevor Beasty on 8/30/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import LassoTestUtilities + +class ModalTestingTestsSystemBehavior: XCTestCase { + + private func testSupportedStyles(_ test: (UIModalPresentationStyle) throws -> Void) throws { + let styles: [UIModalPresentationStyle] = [.pageSheet, .fullScreen] + try styles.forEach(test) + } + + func test_Animated_Presentation() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let _: UIViewController = try self.assertControllerEvent( + from: vc0, + to: { vc1 }, + when: { vc0.present(vc1, animated: true, completion: nil) }, + onViewDidLoad: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, []) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad]) + + }, + onViewWillAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + } + else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear]) + } + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear]) + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear]) + + default: + () + } + + }, + onViewDidAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + } + else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + } + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + + default: + () + } + + }) + } + + try testSupportedStyles(test) + } + + func test_NotAnimated_Presentation() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let _: UIViewController = try self.assertControllerEvent( + from: vc0, + to: { vc1 }, + when: { vc0.present(vc1, animated: false, completion: nil) }, + onViewDidLoad: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + XCTAssertEqual(vc0.lifeCycleEvents, []) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad]) + + }, + onViewWillAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear]) + } + else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + } + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + + default: + () + } + + }, + onViewDidAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + } + else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + } + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + + default: + () + } + + }) + } + + try testSupportedStyles(test) + } + + func test_Animated_Dismissal() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.present(vc1, animated: true, completion: nil) + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let _: UIViewController = try self.assertControllerEvent( + from: vc0, + to: { vc1 }, + when: { vc0.dismiss(animated: true, completion: nil) }, + onViewWillAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + } + else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear]) + } + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear]) + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear]) + + default: + () + } + + }, + onViewDidAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + } + else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + } + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + + default: + () + } + + }) + } + + try testSupportedStyles(test) + } + + func test_NotAnimated_Dismissal() throws { + + let test = { (style: UIModalPresentationStyle) throws in + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + vc1.modalPresentationStyle = style + window.rootViewController = vc0 + self.waitForEvents(in: window) + vc0.present(vc1, animated: true, completion: nil) + self.waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let _: UIViewController = try self.assertControllerEvent( + from: vc0, + to: { vc1 }, + when: { vc0.dismiss(animated: false, completion: nil) }, + onViewWillAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear]) + } else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + } + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + + default: + () + } + + }, + onViewDidAppear: { presenting, presented in + XCTAssertTrue(presenting === vc0) + XCTAssertTrue(presented === vc1) + switch style { + + case .pageSheet: + if #available(iOS 13.0, *) { + XCTAssertEqual(vc0.lifeCycleEvents, []) + } else { + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + } + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + + case .fullScreen: + XCTAssertEqual(vc0.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(vc1.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + + default: + () + } + + }) + } + + try testSupportedStyles(test) + } + +} diff --git a/Example/LassoTestUtilities_Tests/NavigationTestingTests.swift b/Example/LassoTestUtilities_Tests/NavigationTestingTests.swift new file mode 100644 index 0000000..530d11f --- /dev/null +++ b/Example/LassoTestUtilities_Tests/NavigationTestingTests.swift @@ -0,0 +1,575 @@ +// +//===----------------------------------------------------------------------===// +// +// NavigationTestingTests.swift +// +// Created by Trevor Beasty on 8/8/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import UIKit +@testable import LassoTestUtilities + +// swiftlint:disable file_length + +class NavigationTestingTests: XCTestCase { + + // MARK: - Success + + // assertPushed + + func test_AssertPushed_Animated() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let result: LifeCycleController = try assertPushed( + after: vc0, + when: { nav.pushViewController(vc1, animated: true) }, + onViewDidLoad: { previous, pushed in + XCTAssertTrue(previous === vc0) + XCTAssertTrue(pushed === vc1) + XCTAssertEqual(pushed.lifeCycleEvents, [.viewDidLoad]) + XCTAssertEqual(previous.lifeCycleEvents, []) + }, + onViewWillAppear: { previous, pushed in + XCTAssertTrue(previous === vc0) + XCTAssertTrue(pushed === vc1) + XCTAssertEqual(pushed.lifeCycleEvents, [.viewDidLoad, .viewWillAppear]) + XCTAssertEqual(previous.lifeCycleEvents, [.viewWillDisappear]) + }, + onViewDidAppear: { previous, pushed in + XCTAssertTrue(previous === vc0) + XCTAssertTrue(pushed === vc1) + XCTAssertEqual(pushed.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + XCTAssertEqual(previous.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + XCTAssertTrue(result === vc1) + } + + func test_AssertPushed_NotAnimated() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + let result: LifeCycleController = try assertPushed( + after: vc0, + when: { nav.pushViewController(vc1, animated: false) }, + onViewDidLoad: { previous, pushed in + XCTAssertTrue(previous === vc0) + XCTAssertTrue(pushed === vc1) + XCTAssertEqual(pushed.lifeCycleEvents, [.viewDidLoad]) + XCTAssertEqual(previous.lifeCycleEvents, []) + }, + onViewWillAppear: { previous, pushed in + XCTAssertTrue(previous === vc0) + XCTAssertTrue(pushed === vc1) + XCTAssertEqual(pushed.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + XCTAssertEqual(previous.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }, + onViewDidAppear: { previous, pushed in + XCTAssertTrue(previous === vc0) + XCTAssertTrue(pushed === vc1) + XCTAssertEqual(pushed.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + XCTAssertEqual(previous.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + XCTAssertTrue(result === vc1) + } + + // assertRoot + + func test_AssertRoot() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc = LifeCycleController() + let nav = UINavigationController() + window.rootViewController = nav + waitForEvents(in: window) + vc.lifeCycleEvents = [] + + // when / then + let result: LifeCycleController = try assertRoot( + of: nav, + when: { nav.viewControllers = [vc] }, + onViewDidLoad: { root in + XCTAssertTrue(root === vc) + XCTAssertEqual(root.lifeCycleEvents, [.viewDidLoad]) + }, + onViewWillAppear: { root in + XCTAssertTrue(root === vc) + XCTAssertEqual(root.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }, + onViewDidAppear: { root in + XCTAssertTrue(root === vc) + XCTAssertEqual(root.lifeCycleEvents, [.viewDidLoad, .viewWillAppear, .viewDidAppear]) + }) + XCTAssertTrue(result === vc) + } + + // assertPopped + + func test_AssertPopped_Animated() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + nav.pushViewController(vc1, animated: true) + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + try assertPopped( + from: vc1, + to: vc0, + when: { nav.popViewController(animated: true) }, + onViewWillAppear: { from, to in + XCTAssertTrue(to === vc0) + XCTAssertTrue(from === vc1) + XCTAssertEqual(to.lifeCycleEvents, [.viewWillAppear]) + XCTAssertEqual(from.lifeCycleEvents, [.viewWillDisappear]) + }, + onViewDidAppear: { from, to in + XCTAssertTrue(to === vc0) + XCTAssertTrue(from === vc1) + XCTAssertEqual(to.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(from.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + } + + func test_AssertPopped_NotAnimated() throws { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = LifeCycleController() + let vc1 = LifeCycleController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + nav.pushViewController(vc1, animated: true) + waitForEvents(in: window) + vc0.lifeCycleEvents = [] + vc1.lifeCycleEvents = [] + + // when / then + try assertPopped( + from: vc1, + to: vc0, + when: { nav.popViewController(animated: false) }, + onViewWillAppear: { from, to in + XCTAssertTrue(to === vc0) + XCTAssertTrue(from === vc1) + XCTAssertEqual(to.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(from.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }, + onViewDidAppear: { from, to in + XCTAssertTrue(to === vc0) + XCTAssertTrue(from === vc1) + XCTAssertEqual(to.lifeCycleEvents, [.viewWillAppear, .viewDidAppear]) + XCTAssertEqual(from.lifeCycleEvents, [.viewWillDisappear, .viewDidDisappear]) + }) + } + + // MARK: - Failure + + // push + + func test_PushingWrongType_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + let _: UITabBarController = try assertPushed(after: vc0, when: { nav.pushViewController(vc1, animated: false) }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationTypeError.unexpectedPushedType(realized: let realized): + XCTAssertTrue(realized === vc1) + + default: + unexpectedErrorType() + } + }) + } + + func test_NoNavEmbeddingPrecedingPush_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPushed(after: vc0, when: { }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.noNavigationEmbedding(target: let target): + XCTAssertTrue(target === vc0) + + default: + unexpectedErrorType() + } + }) + } + + func test_PreviousNotTopPrecedingPush_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + nav.pushViewController(vc1, animated: false) + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPushed(after: vc0, when: { }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.unexpectedTopPrecedingEvent(expected: let expected, navigationController: let _nav): + XCTAssertTrue(expected === vc0) + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + + func test_EmptyStackFollowingPush_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPushed(after: vc0, when: { nav.viewControllers = [] }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.unexpectedEmptyStackFollowingEvent(navigationController: let _nav): + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + + func test_BadTopStackFollowingPush_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let vc2 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPushed( + after: vc0, + when: { + nav.pushViewController(vc1, animated: false) + waitForEvents(in: window) + nav.pushViewController(vc2, animated: false) + waitForEvents(in: window) + }, + failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.unexpectedTopStackFollowingEvent(expected: let expected, navigationController: let _nav): + XCTAssertEqual(expected, [vc0, vc2]) + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + + func test_NoPush_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPushed( + after: vc0, + when: { () }, + failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.noPushOccurred(previous: let previous): + XCTAssertEqual(previous, vc0) + + default: + unexpectedErrorType() + } + }) + } + + // root + + func test_NoNewRootReplacingOld_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + let _: UITabBarController = try assertRoot(of: nav, when: { }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.oldRootRemainsFollowingRootEvent(oldRoot: let oldRoot, navigationController: let _nav): + XCTAssertTrue(oldRoot === vc0) + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + + func test_RootingWrongType_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + let _: UITabBarController = try assertRoot(of: nav, when: { nav.viewControllers = [vc1] }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationTypeError.unexpectedRootType(realized: let realized): + XCTAssertTrue(realized === vc1) + + default: + unexpectedErrorType() + } + }) + } + + func test_EmptyStackFollowingRootEvent_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let nav = UINavigationController() + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertRoot(of: nav, when: { }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.unexpectedStackFollowingRootEvent(navigationController: let _nav): + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + + func test_ManyInStackFollowingRootEvent_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertRoot(of: nav, when: { nav.pushViewController(vc1, animated: false) }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.unexpectedStackFollowingRootEvent(navigationController: let _nav): + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + + // pop + + func test_NoNavigationEmbeddingPrecedingPop_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + window.rootViewController = vc0 + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPopped(from: vc0, to: UIViewController(), when: { }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.noNavigationEmbedding(target: let target): + XCTAssertTrue(target === vc0) + + default: + unexpectedErrorType() + } + }) + } + + func test_FromControllerNotTopPrecedingPop_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + nav.pushViewController(UIViewController(), animated: false) + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPopped(from: vc0, to: UIViewController(), when: { }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.unexpectedTopPrecedingEvent(expected: let expected, navigationController: let _nav): + XCTAssertTrue(expected === vc0) + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + + func test_ToControllerNotTopFollowingPop_Fails() { + // given + let window = UIWindow() + window.makeKeyAndVisible() + let vc0 = UIViewController() + let vc1 = UIViewController() + let nav = UINavigationController(rootViewController: vc0) + window.rootViewController = nav + waitForEvents(in: window) + nav.pushViewController(vc1, animated: false) + waitForEvents(in: window) + + // when / then + assertThrowsError( + expr: { + try _ = assertPopped(from: vc1, to: vc0, when: { }, failTest: silent) + }, + eval: { + switch $0 { + + case NavigationPresentationError.unexpectedTopFollowingEvent(expected: let expected, navigationController: let _nav): + XCTAssertTrue(expected === vc0) + XCTAssertTrue(_nav === nav) + + default: + unexpectedErrorType() + } + }) + } + +} diff --git a/Example/LassoTestUtilities_Tests/ValueDiffingTests.swift b/Example/LassoTestUtilities_Tests/ValueDiffingTests.swift new file mode 100644 index 0000000..6cc2ce4 --- /dev/null +++ b/Example/LassoTestUtilities_Tests/ValueDiffingTests.swift @@ -0,0 +1,47 @@ +// +//===----------------------------------------------------------------------===// +// +// ValueDiffingTests.swift +// +// Created by Trevor Beasty on 9/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import LassoTestUtilities + +class ValueDiffingTests: XCTestCase { + + struct User { + let age: Int + let name: String + } + + func test_0() { + let value0 = User(age: 0, name: "a") + let value1 = User(age: 0, name: "b") + + let _diff = diff(realized: value0, expected: value1) + + XCTAssertEqual(_diff, [Diff(key: "name", type: "String", realized: "a", expected: "b")]) + } + + func test_1() { + let value0 = User(age: 0, name: "a") + let value1 = User(age: 1, name: "b") + + let _diff = diff(realized: value0, expected: value1) + + XCTAssertEqual(_diff, [Diff(key: "age", type: "Int", realized: "0", expected: "1"), + Diff(key: "name", type: "String", realized: "a", expected: "b")]) + } + +} diff --git a/Example/LassoTestUtilities_Tests/VerboseLoggingTests.swift b/Example/LassoTestUtilities_Tests/VerboseLoggingTests.swift new file mode 100644 index 0000000..e07a15b --- /dev/null +++ b/Example/LassoTestUtilities_Tests/VerboseLoggingTests.swift @@ -0,0 +1,62 @@ +// +//===----------------------------------------------------------------------===// +// +// VerboseLoggingTests.swift +// +// Created by Trevor Beasty on 12/10/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import LassoTestUtilities + +class VerboseLoggingTests: XCTestCase { + + func test_NavigationEmbedding() { + let navigationController = UINavigationController(rootViewController: VC1()) + + let description = describeViewControllerHierarchy(navigationController) + + XCTAssertEqual(description, "") + } + + func test_ModalSequence() { + let window = UIWindow() + window.makeKeyAndVisible() + let vc1 = VC1() + let vc2 = VC2() + window.rootViewController = vc1 + vc1.present(vc2, animated: false, completion: nil) + + let description = describeViewControllerHierarchy(vc2) + + XCTAssertEqual(description, " --> ") + } + + func test_ModalSequenceWithNavigationController() { + let window = UIWindow() + window.makeKeyAndVisible() + let navigationController = UINavigationController(rootViewController: VC1()) + let vc2 = VC2() + window.rootViewController = navigationController + navigationController.pushViewController(UIViewController(), animated: false) + navigationController.present(vc2, animated: false, completion: nil) + + let description = describeViewControllerHierarchy(vc2) + + XCTAssertEqual(description, " --> ") + } + +} + +private class VC1: UIViewController { } + +private class VC2: UIViewController { } diff --git a/Example/Lasso_Tests/ActionDispatchableBindingTests.swift b/Example/Lasso_Tests/ActionDispatchableBindingTests.swift new file mode 100644 index 0000000..7378eb1 --- /dev/null +++ b/Example/Lasso_Tests/ActionDispatchableBindingTests.swift @@ -0,0 +1,229 @@ +// +//===----------------------------------------------------------------------===// +// +// ActionDispatchableBindingTests.swift +// +// Created by Steven Grosmark on 6/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso + +class ActionDispatchableBindingTests: XCTestCase { + + func test_BindButton_ToAction() { + // given + let store = TestModule.createScreen().store + let button = UIButton() + button.bind(to: store, action: .buttonTapped) + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + button.sendActions(for: .touchUpInside) + + // then + XCTAssertEqual(store.state.actions, [TestModule.Action.buttonTapped], "Expecting exactly one button tapped actions") + + // when + button.sendActions(for: .touchUpInside) + + // then + XCTAssertEqual(store.state.actions, [TestModule.Action.buttonTapped, .buttonTapped], "Expecting exactly two button tapped actions") + } + + func test_BindButton_ToClosure() { + // given + let store = TestModule.createScreen().store + let button = UIButton() + button.bind(to: store) { btn in + XCTAssertEqual(button, btn) + return .buttonTapped + } + button.bind(to: store) { _ in .gestureTapped } + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + button.sendActions(for: .touchUpInside) + + // then + var expectation = [TestModule.Action.buttonTapped, .gestureTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly two actions") + + // when + button.sendActions(for: .touchUpInside) + + // then + expectation = [TestModule.Action.buttonTapped, .gestureTapped, .buttonTapped, .gestureTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly four actions") + } + + func test_BindButton_ToMultipleActions() { + // given + let store = TestModule.createScreen().store + let button = UIButton() + button.bind(to: store, action: .buttonTapped) + button.bind(to: store, action: .gestureTapped) + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + button.sendActions(for: .touchUpInside) + + // then + let expectation = [TestModule.Action.buttonTapped, .gestureTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly two actions") + } + + func test_BindButton_ToActionAndClosure() { + // given + let store = TestModule.createScreen().store + let button = UIButton() + button.bind(to: store, action: .buttonTapped) + button.bind(to: store) { _ in .gestureTapped } + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + button.sendActions(for: .touchUpInside) + + // then + let expectation = [TestModule.Action.buttonTapped, .gestureTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly two actions") + } + + func test_BindTapGesture_ToAction() { + // given + let store = TestModule.createScreen().store + let tap = UITapGestureRecognizer() + tap.bind(to: store, action: .gestureTapped) + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + tap.sendActions() + + // then + XCTAssertEqual(store.state.actions, [TestModule.Action.gestureTapped], "Expecting exactly one gesture tapped actions") + + // when + tap.sendActions() + + // then + XCTAssertEqual(store.state.actions, [TestModule.Action.gestureTapped, .gestureTapped], "Expecting exactly two gesture tapped actions") + } + + func test_BindTapGesture_ToClosure() { + // given + let store = TestModule.createScreen().store + let tap = UITapGestureRecognizer() + tap.bind(to: store) { gesture in + XCTAssertEqual(tap, gesture) + return .gestureTapped + } + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + tap.sendActions() + + // then + var expectation = [TestModule.Action.gestureTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly one actions") + + // when + tap.sendActions() + + // then + expectation = [TestModule.Action.gestureTapped, .gestureTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly two actions") + } + + func test_BindTapGesture_ToActionAndClosure() { + // given + let store = TestModule.createScreen().store + let tap = UITapGestureRecognizer() + tap.bind(to: store, action: .gestureTapped) + tap.bind(to: store) { _ in .buttonTapped } + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + tap.sendActions() + + // then + var expectation = [TestModule.Action.gestureTapped, .buttonTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly two actions") + + // when + tap.sendActions() + + // then + expectation = [TestModule.Action.gestureTapped, .buttonTapped, .gestureTapped, .buttonTapped] + XCTAssertEqual(store.state.actions, expectation, "Expecting exactly four actions") + } + + func test_BindTextChange_ToAction() { + let store = TestModule.createScreen().store + let textField = UITextField() + textField.bindTextDidChange(to: store) { .textChanged($0) } + XCTAssertTrue(store.state.actions.isEmpty, "no actions should have been dispatched at the start") + + // when + textField.text = "hello" + + // then + XCTAssertTrue(store.state.actions.isEmpty, "manually assigning text shouldn't trigger the action") + + // when + NotificationCenter.default.post(name: UITextField.textDidChangeNotification, object: textField) + + // then + XCTAssertEqual(store.state.actions, [TestModule.Action.textChanged("hello")], "Expecting to see \"hello\"") + + // when + textField.text = "hello, mother" + NotificationCenter.default.post(name: UITextField.textDidChangeNotification, object: textField) + + // then + XCTAssertEqual(store.state.actions, [TestModule.Action.textChanged("hello"), .textChanged("hello, mother")], "Expecting to see text text change actions") + } + +} + +private enum TestModule: ScreenModule { + + enum Action: Equatable { + case buttonTapped + case gestureTapped + case textChanged(String) + } + + struct State: Equatable { + var actions = [Action]() + } + + static var defaultInitialState: State { return State() } + + static func createScreen(with store: TestStore) -> Screen { + let controller = UIViewController() + return Screen(store, controller) + } + +} + +private class TestStore: LassoStore { + + required init(with initialState: State? = nil) { + super.init(with: initialState ?? State()) + } + + override func handleAction(_ action: Action) { + update { state in + state.actions.append(action) + } + } +} diff --git a/Example/Lasso_Tests/Info.plist b/Example/Lasso_Tests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Example/Lasso_Tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Example/Lasso_Tests/MockableTests.swift b/Example/Lasso_Tests/MockableTests.swift new file mode 100644 index 0000000..672c512 --- /dev/null +++ b/Example/Lasso_Tests/MockableTests.swift @@ -0,0 +1,125 @@ +// +//===----------------------------------------------------------------------===// +// +// MockableTests.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import Lasso + +#if swift(>=5.1) +class MockableTests: XCTestCase { + + struct Group { + let name: String + let value: Int + } + + enum GlobalStuff { + + @Mockable static var number: Int = 42 + @Mockable static private(set) var staticFunc: () -> String = { "real" } + @Mockable static private(set) var group: Group = Group(name: "fizz", value: 1) + + } + + override func setUp() { + super.setUp() + GlobalStuff.number = 42 + } + + override class func tearDown() { + GlobalStuff.$number.reset() + GlobalStuff.$staticFunc.reset() + GlobalStuff.$group.reset() + super.tearDown() + } + + func test_mocking() { + // given - un-mocked things + XCTAssertEqual(GlobalStuff.number, 42) + XCTAssertEqual(GlobalStuff.staticFunc(), "real") + XCTAssertEqual(GlobalStuff.group.name, "fizz") + XCTAssertEqual(GlobalStuff.group.value, 1) + + // when - apply mock values + GlobalStuff.$number.mock(with: 999) + GlobalStuff.$staticFunc.mock(with: { "fake" }) + GlobalStuff.$group.mock(with: Group(name: "buzz", value: 3)) + + // then - mockables should vend mock values + XCTAssertEqual(GlobalStuff.number, 999) + XCTAssertEqual(GlobalStuff.staticFunc(), "fake") + XCTAssertEqual(GlobalStuff.group.name, "buzz") + XCTAssertEqual(GlobalStuff.group.value, 3) + + // when - reset mockables + GlobalStuff.$number.reset() + GlobalStuff.$staticFunc.reset() + GlobalStuff.$group.reset() + + // then - mockables should vend real values + XCTAssertEqual(GlobalStuff.number, 42) + XCTAssertEqual(GlobalStuff.staticFunc(), "real") + XCTAssertEqual(GlobalStuff.group.name, "fizz") + XCTAssertEqual(GlobalStuff.group.value, 1) + } + + func test_modifyMockable() { + // given - un-mocked value + XCTAssertEqual(GlobalStuff.number, 42) + + // when - apply mock value + GlobalStuff.$number.mock(with: 999) + + // then - mockable should vend mock values + XCTAssertEqual(GlobalStuff.number, 999) + + // when - "real" value for mockable changed: + GlobalStuff.number = 101 + + // then - mockable should still vend mock value + XCTAssertEqual(GlobalStuff.number, 999) + + // when - reset mockable + GlobalStuff.$number.reset() + + // then - mockable should vend new real value + XCTAssertEqual(GlobalStuff.number, 101) + + // when - "real" value changed while not mocked: + GlobalStuff.number = 88 + + // then - mockable should vend new value + XCTAssertEqual(GlobalStuff.number, 88) + } + + func test_noMockWhenNotTesting() { + // given - un-mocked value + XCTAssertEqual(GlobalStuff.number, 42) + + // given - force a non-test state + Testing.active = false + defer { Testing.active = true } + + // when - apply mock value + GlobalStuff.$number.mock(with: 999) + + // then - mockable should still vend un-mocked value + XCTAssertEqual(GlobalStuff.number, 42) + } + +} + +#endif diff --git a/Example/Lasso_Tests/ObjectBindingTests.swift b/Example/Lasso_Tests/ObjectBindingTests.swift new file mode 100644 index 0000000..7abb213 --- /dev/null +++ b/Example/Lasso_Tests/ObjectBindingTests.swift @@ -0,0 +1,173 @@ +// +//===----------------------------------------------------------------------===// +// +// ObjectBindingTests.swift +// +// Created by Steven Grosmark on 6/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import Lasso + +class ObjectBindingTests: XCTestCase { + + private var instance: TestClass? + + override func setUp() { + super.setUp() + instance = TestClass() + } + + func test_ObjectBinding_ReleaseWhenOwnerReleased() { + + // given + weak var weakInstance = instance + XCTAssertNotNil(instance, "instance shouldn't be nil") + XCTAssertNotNil(weakInstance, "weakInstance shouldn't be nil") + + autoreleasepool { + + // when + var view: UIView? = UIView() + if let notOptionalInstance = instance { + view?.holdReference(to: notOptionalInstance) + instance = nil + } + + // then + XCTAssertNil(instance, "instance should be nil") + XCTAssertNotNil(weakInstance, "weakInstance shouldn't be nil") + + // when + view = nil + } + + // then + XCTAssertNil(weakInstance, "weakInstance should be nil") + } + + func test_ObjectBinding_ManyObjectsSingleOwner() { + + // given + var instances: [TestClass]? = [TestClass(), TestClass(), TestClass(), TestClass(), TestClass()] + let weakInstances = instances?.map { WeakBox($0) } ?? [] + XCTAssertEqual(instances?.count, 5) + XCTAssertEqual(instances?.count, weakInstances.compactMap({ $0.value }).count) + + autoreleasepool { + + // when + var view: UIView? = UIView() + instances?.forEach { + view?.holdReference(to: $0) + } + instances = nil + + // then + XCTAssertEqual(weakInstances.compactMap({ $0.value }).count, 5, "There should be 5 remaining") + + // when + view = nil + } + + // then + XCTAssertTrue(weakInstances.compactMap({ $0.value }).isEmpty, "None should be remaining") + } + + func test_WeakBox() { + + // given + let weakBox = WeakBox(instance.unsafelyUnwrapped) + XCTAssertNotNil(weakBox.value, "instance should be nil") + + // when + instance = nil + + // then + XCTAssertNil(weakBox.value, "instance should be nil") + } + + func test_ObjectBinding_ManualRelease() { + + // given + let view: UIView = UIView() + let weakBox = WeakBox(instance.unsafelyUnwrapped) + XCTAssertNotNil(instance, "instance shouldn't be nil") + XCTAssertNotNil(weakBox.value, "weakInstance shouldn't be nil") + + autoreleasepool { + + // when + view.holdReference(to: instance.unsafelyUnwrapped) + instance = nil + + // then + XCTAssertNil(instance, "instance should be nil") + XCTAssertNotNil(weakBox.value, "weakInstance shouldn't be nil") + + // when + view.releaseReference(to: weakBox.value.unsafelyUnwrapped) + } + + // then + XCTAssertNil(weakBox.value, "weakInstance should be nil") + XCTAssertNotNil(view, "view shouldn't be nil") + } + + func test_ObjectBinding_ManualReleaseOneOfMany() { + + // given + var extraInstance: TestClass? = TestClass() + let view: UIView = UIView() + let weakBox1 = WeakBox(instance.unsafelyUnwrapped) + let weakBox2 = WeakBox(extraInstance.unsafelyUnwrapped) + XCTAssertNotNil(instance, "instance shouldn't be nil") + XCTAssertNotNil(extraInstance, "extraInstance shouldn't be nil") + XCTAssertNotNil(weakBox1.value, "weakInstance1 shouldn't be nil") + XCTAssertNotNil(weakBox2.value, "weakInstance2 shouldn't be nil") + + autoreleasepool { + + // when + view.holdReference(to: instance.unsafelyUnwrapped) + view.holdReference(to: extraInstance.unsafelyUnwrapped) + instance = nil + extraInstance = nil + + // then + XCTAssertNil(instance, "instance should be nil") + XCTAssertNil(extraInstance, "extraInstance should be nil") + XCTAssertNotNil(weakBox1.value, "weakInstance1 shouldn't be nil") + XCTAssertNotNil(weakBox2.value, "weakInstance2 shouldn't be nil") + + // when + view.releaseReference(to: weakBox1.value.unsafelyUnwrapped) + } + + // then + XCTAssertNil(instance, "instance should be nil") + XCTAssertNil(extraInstance, "extraInstance should be nil") + XCTAssertNil(weakBox1.value, "weakInstance1 should be nil") + XCTAssertNotNil(weakBox2.value, "weakInstance2 shouldn't be nil") + XCTAssertNotNil(view, "view shouldn't be nil") + } + +} + +private class TestClass { } + +private class WeakBox { + weak var value: Value? + init(_ value: Value) { + self.value = value + } +} diff --git a/Example/Lasso_Tests/ScreenCaptureStoreTests.swift b/Example/Lasso_Tests/ScreenCaptureStoreTests.swift new file mode 100644 index 0000000..e66afe2 --- /dev/null +++ b/Example/Lasso_Tests/ScreenCaptureStoreTests.swift @@ -0,0 +1,154 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenCaptureStoreTests.swift +// +// Created by Steven Grosmark on 1/27/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso + +class ScreenCaptureStoreTests: XCTestCase { + + func test_weakReference_usingScreen() { + weak var weakStore: MyScreenModule.Store? + + // when - create screen and capture the type-erased store + var screen: MyScreenModule.Screen? = + MyScreenModule + .createScreen() + .captureStore(as: &weakStore) + + // then - should have non-nil refs + XCTAssertNotNil(screen) + XCTAssertNotNil(weakStore) + + // when - ref to screen is let go of + screen = nil + + // then - underlying ref to type-erased store becomes nil + XCTAssertNil(weakStore) + } + + func test_weakReference_usingViewController() { + weak var weakStore: MyScreenModule.Store? + + // when - create screen and capture the type-erased store + var controller: UIViewController? = + MyScreenModule + .createScreen() + .captureStore(as: &weakStore) + .controller + + // then - should have non-nil refs + XCTAssertNotNil(controller) + XCTAssertNotNil(weakStore) + + // when - ref to view controller is let go of + controller = nil + + // then - underlying ref to type-erased store becomes nil + XCTAssertNil(weakStore) + } + + func test_weakReference_usingFlow() throws { + + // given - a flow and a place to start it + let flow = MyFlow() + let navigationController = UINavigationController(rootViewController: UIViewController()) + + try autoreleasepool { + + // when - flow started in the nav + flow.start(with: root(of: navigationController)) + + // then + XCTAssertNil(flow.store) + + // when - next screen in flow is pushed + let controller = try unwrap(flow.initialController as? MyController) + controller.store.dispatchAction(.something) + + // then - flow has captured a reference to the 2nd screen's store + XCTAssertNotNil(flow.store) + + // when - ref to 2nd screen's view controller is let go of (via navigation pop) + flow.unwind() + + } + + // then - weak reference to store in the flow has been released + XCTAssertNil(flow.store) + } + +} + +extension XCTestCase { + + fileprivate func unwrap(_ optional: T?, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) throws -> T { + guard let value = optional else { + XCTFail(message ?? "\(T.self) value is nil", file: file, line: line) + throw NilError.nilValueEncountered + } + return value + } + + fileprivate enum NilError: Error { + case nilValueEncountered + } + +} + +private enum MyScreenModule: ScreenModule { + static func createScreen(with store: MyStore) -> Screen { + return Screen(store, MyController(store.asViewStore())) + } + enum Action: Equatable { case something } + enum Output: Equatable { case something } +} + +private final class MyStore: LassoStore { + override func handleAction(_ action: Action) { + dispatchOutput(.something) + } +} + +private final class MyController: UIViewController { + let store: MyScreenModule.ViewStore + + init(_ store: MyScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { return nil } +} + +private final class MyFlow: Flow { + weak var store: MyScreenModule.Store? + + override func createInitialController() -> UIViewController { + return MyScreenModule + .createScreen() + .observeOutput { [weak self] _ in self?.createNext() } + .controller + } + + private func createNext() { + guard let nav = context else { return } + MyScreenModule + .createScreen() + .captureStore(as: &store) + .place(with: pushed(in: nav, animated: false)) + } +} diff --git a/Example/Lasso_Tests/StateObservationTests.swift b/Example/Lasso_Tests/StateObservationTests.swift new file mode 100644 index 0000000..ed73fd9 --- /dev/null +++ b/Example/Lasso_Tests/StateObservationTests.swift @@ -0,0 +1,161 @@ +// +//===----------------------------------------------------------------------===// +// +// StateObservationTests.swift +// +// Created by Steven Grosmark on 9/7/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import Lasso + +class StateObservationTests: XCTestCase { + + func test_Store_State_Equatable() { + enum TestModule: StoreModule { + struct State: Equatable { + let name: String + let num: Int + init(_ name: String, _ num: Int) { + self.name = name + self.num = num + } + } + } + typealias State = TestModule.State + + // given + class TestStore: LassoStore { } + let store = TestStore(with: State("A", 2)) + + var states: [State] = [] + var oldStates: [State?] = [] + + // when + store.observeState { old, new in + oldStates.append(old) + states.append(new) + } + + // then + XCTAssertEqual(oldStates, [nil], "Initial binding should trigger the notification with nil for old state") + XCTAssertEqual(states, [State("A", 2)], "Initial binding should trigger the notification") + + store.update(states: State("A", 2), State("A", 3), State("B", 3), State("B", 2), State("B", 2)) + XCTAssertEqual(states, [State("A", 2), State("A", 3), State("B", 3), State("B", 2)], "update with same state should NOT trigger the notification") + XCTAssertEqual(oldStates, [nil, State("A", 2), State("A", 3), State("B", 3)], "update with same state should NOT trigger the notification") + } + + func test_ViewStore_State_Equatable() { + enum TestModule: StoreModule { + struct State: Equatable { + let name: String + let num: Int + init(_ name: String, _ num: Int) { + self.name = name + self.num = num + } + } + } + typealias State = TestModule.State + + // given + class TestStore: LassoStore { } + let store = TestStore(with: State("A", 2)) + let viewStore = store.asViewStore() + + var states: [State] = [] + var oldStates: [State?] = [] + + // when + viewStore.observeState { old, new in + oldStates.append(old) + states.append(new) + } + + // then + XCTAssertEqual(oldStates, [nil], "Initial binding should trigger the notification with nil for old state") + XCTAssertEqual(states, [State("A", 2)], "Initial binding should trigger the notification") + + store.update(states: State("A", 2), State("A", 3), State("B", 3), State("B", 2), State("B", 2)) + XCTAssertEqual(states, [State("A", 2), State("A", 3), State("B", 3), State("B", 2)], "update with same state should NOT trigger the notification") + XCTAssertEqual(oldStates, [nil, State("A", 2), State("A", 3), State("B", 3)], "update with same state should NOT trigger the notification") + } + + func test_Store_State_NotEquatable_KeyPath_Equatable() { + enum TestModule: StoreModule { + struct State { + var name: String + init(_ name: String) { self.name = name } + } + } + typealias State = TestModule.State + + class TestStore: LassoStore { } + let store = TestStore(with: State("A")) + + var names: [String] = [] + var newNames: [String] = [] + + // when + store.observeState(\.name) { names.append($0) } + store.observeState(\.name) { _, new in newNames.append(new) } + + // then + XCTAssertEqual(names, ["A"], "Initial binding should trigger the notification") + XCTAssertEqual(newNames, ["A"], "Initial binding should trigger the notification") + + store.update(states: State("A"), State("B"), State("B"), State("A")) + XCTAssertEqual(names, ["A", "B", "A"], "update with same (equatable) value should NOT trigger the notification") + XCTAssertEqual(newNames, ["A", "B", "A"], "update with same (equatable) value should NOT trigger the notification") + } + + func test_ViewStore_State_NotEquatable_OptionalKeyPath_Equatable() { + enum TestModule: StoreModule { + struct State { + var name: String? + init(_ name: String?) { self.name = name } + } + } + typealias State = TestModule.State + + class TestStore: LassoStore { } + let store = TestStore(with: State("A")) + let viewStore = store.asViewStore() + + var newNames: [String?] = [] + var names: [String?] = [] + + // when + viewStore.observeState(\.name) { names.append($0) } + viewStore.observeState(\.name) { _, new in newNames.append(new) } + + // then + XCTAssertEqual(names, ["A"], "Initial binding should trigger the notification") + XCTAssertEqual(newNames, ["A"], "Initial binding should trigger the notification") + + store.update(states: State("A"), State("B"), State("B"), State(nil), State(nil), State("A")) + XCTAssertEqual(names, ["A", "B", nil, "A"], "update with same (equatable) value should NOT trigger the notification") + XCTAssertEqual(newNames, ["A", "B", nil, "A"], "update with same (equatable) value should NOT trigger the notification") + } + +} + +// MARK: - Helper to set multiple state values in succession + +extension LassoStore { + fileprivate func update(states: State...) { + states.forEach { newState in + self.update { $0 = newState } + } + } +} diff --git a/Example/Lasso_Tests/UIGestureRecognizer+SendActions.swift b/Example/Lasso_Tests/UIGestureRecognizer+SendActions.swift new file mode 100644 index 0000000..783d734 --- /dev/null +++ b/Example/Lasso_Tests/UIGestureRecognizer+SendActions.swift @@ -0,0 +1,63 @@ +// +//===----------------------------------------------------------------------===// +// +// UIGestureRecognizer+SendActions.swift +// +// Created by Steven Grosmark on 6/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// UIGestureRecognizer extension +extension UIGestureRecognizer { + + /// Executes all targets on a gesture recognizer + func sendActions() { + let targetsInfo = getTargetInfo() + for info in targetsInfo { + _ = info.target.perform(info.action) + //info.target.performSelector(onMainThread: info.action, with: self, waitUntilDone: true) + } + } + + // MARK: Retrieving targets from gesture recognizers + + /// Returns all actions and selectors for a gesture recognizer + /// This method uses private API's and will most likely cause your app to be rejected if used outside of your test target + /// - Returns: [(target: AnyObject, action: Selector)] Array of action/selector tuples + private func getTargetInfo() -> TargetActionInfo { + guard let targets = value(forKeyPath: "_targets") as? [NSObject] else { + return [] + } + var targetsInfo: TargetActionInfo = [] + for target in targets { + // Getting selector by parsing the description string of a UIGestureRecognizerTarget + let description = String(describing: target).trimmingCharacters(in: CharacterSet(charactersIn: "()")) + var selectorString = description.components(separatedBy: ", ").first ?? "" + selectorString = selectorString.components(separatedBy: "=").last ?? "" + let selector = NSSelectorFromString(selectorString) + + // Getting target from iVars + if let targetActionPairClass = NSClassFromString("UIGestureRecognizerTarget"), + let targetIvar = class_getInstanceVariable(targetActionPairClass, "_target"), + let targetObject = object_getIvar(target, targetIvar) { + targetsInfo.append((target: targetObject as AnyObject, action: selector)) + } + } + + return targetsInfo + } + +} + +// Return type alias +private typealias TargetActionInfo = [(target: AnyObject, action: Selector)] diff --git a/Example/Lasso_Tests/ValueBinderTests.swift b/Example/Lasso_Tests/ValueBinderTests.swift new file mode 100644 index 0000000..5a2b688 --- /dev/null +++ b/Example/Lasso_Tests/ValueBinderTests.swift @@ -0,0 +1,225 @@ +// +//===----------------------------------------------------------------------===// +// +// ValueBinderTests.swift +// +// Created by Steven Grosmark on 6/10/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +@testable import Lasso + +class ValueBinderTests: XCTestCase { + + func test_ValueBinder_IntValue_Equatable() { + // given + let observable = ValueBinder(1) + var changes: [Int] = [] + + // when + observable.bind { _, newValue in changes.append(newValue) } + + // then + XCTAssertEqual(changes, [1], "Initial binding should trigger the notification") + + observable.set(values: 1, 2, 2, 1) + XCTAssertEqual(changes, [1, 2, 1], "update with same (equatable) value should NOT trigger the notification") + } + + func test_ValueBinder_Struct_NotEquatable() { + struct Test { + let name: String + init(_ name: String) { self.name = name } + } + + // given + let observable = ValueBinder(Test("A")) + var changes: [String] = [] + + // when + observable.bind { _, newValue in changes.append(newValue.name) } + + // then + XCTAssertEqual(changes, ["A"], "Initial binding should trigger the notification") + + observable.set(values: Test("A"), Test("B"), Test("B")) + XCTAssertEqual(changes, ["A", "A", "B", "B"], "update with same (not-equatable) value should trigger the notification") + } + + func test_ValueBinder_KeyPath_NotEquatable() { + struct Test { + // 'name' is the keyPath to be tested. It is not equatable, as mandated. + let name: Name + + init(_ name: String) { + self.name = Name(value: name) + } + + struct Name { + let value: String + } + + } + + // given + let observable = ValueBinder(Test("A")) + var names: [Test.Name] = [] + + var nameValues: [String] { + return names.map { $0.value } + } + + // when + observable.bind(\.name) { _, newName in names.append(newName) } + + // then + XCTAssertEqual(nameValues, ["A"], "Initial binding should trigger the notification") + + observable.set(values: Test("A"), Test("B"), Test("B")) + XCTAssertEqual(nameValues, ["A", "A", "B", "B"], "update with same (not-equatable) value should trigger the notification") + } + + func test_ValueBinder_OptionalKeyPath_NotEquatable() { + struct Test { + // 'name' is the keyPath to be tested. It is not equatable, as mandated. + let name: Name? + + init(_ name: String?) { + if let name = name { + self.name = Name(value: name) + } + else { + self.name = nil + } + } + + struct Name { + let value: String + } + + } + + // given + let observable = ValueBinder(Test("A")) + var names: [Test.Name?] = [] + + var nameValues: [String?] { + return names.map { $0.map { $0.value } } + } + + // when + observable.bind(\.name) { _, newName in names.append(newName) } + + // then + XCTAssertEqual(nameValues, ["A"], "Initial binding should trigger the notification") + + observable.set(values: Test("A"), Test(nil), Test(nil), Test("B")) + XCTAssertEqual(nameValues, ["A", "A", nil, nil, "B"], "update with same (not-equatable) value should trigger the notification") + } + + func test_ValueBinder_Struct_NotEquatable_OptionalKeyPath_Equatable() { + struct Test { + let name: String? + init(_ name: String?) { self.name = name } + } + + // given + let observable = ValueBinder(Test("A")) + var names: [String?] = [] + + // when + observable.bind(\.name) { _, newName in names.append(newName) } + + // then + XCTAssertEqual(names, ["A"], "Initial binding should trigger the notification") + + observable.set(values: Test("A"), Test("B"), Test("B"), Test("A")) + XCTAssertEqual(names, ["A", "B", "A"], "update with same (equatable) value should NOT trigger the notification") + } + + func test_ValueBinder_Struct_Equatable() { + struct Test: Equatable { + let name: String + init(_ name: String) { self.name = name } + } + + // given + let observable = ValueBinder(Test("A")) + var changes: [String] = [] + + // when + observable.bind { _, newValue in changes.append(newValue.name) } + + // then + XCTAssertEqual(changes, ["A"], "Initial binding should trigger the notification") + + observable.set(values: Test("A"), Test("B"), Test("B"), Test("A")) + XCTAssertEqual(changes, ["A", "B", "A"], "update with same (equatable) value should NOT trigger the notification") + } + + func test_ValueBinder_KeyPath_Equatable() { + struct Test: Equatable { + let name: String + let num: Int + init(_ name: String, _ num: Int) { + self.name = name + self.num = num + } + } + + // given + let observable = ValueBinder(Test("A", 2)) + var changes: [String] = [] + + // when + observable.bind(\.name) { _, newName in changes.append(newName) } + + // then + XCTAssertEqual(changes, ["A"], "Initial binding should trigger the notification") + + observable.set(values: Test("A", 2), Test("A", 3), Test("B", 3), Test("B", 2), Test("A", 2)) + XCTAssertEqual(changes, ["A", "B", "A"], "update with same (equatable) value should NOT trigger the notification") + } + + func test_ValueBinder_OptionalKeyPath_Equatable() { + struct Test: Equatable { + let name: String? + let num: Int + init(_ name: String?, _ num: Int) { + self.name = name + self.num = num + } + } + + // given + let observable = ValueBinder(Test("A", 2)) + var changes: [String?] = [] + + // when + observable.bind(\.name) { _, newName in changes.append(newName) } + + // then + XCTAssertEqual(changes, ["A"], "Initial binding should trigger the notification") + + observable.set(values: Test("A", 2), Test(nil, 3), Test(nil, 2), Test("B", 2), Test("B", 2)) + XCTAssertEqual(changes, ["A", nil, "B"], "update with same (equatable) value should NOT trigger the notification") + } + +} + +// MARK: - Helper to set multiple values in succession + +extension ValueBinder { + fileprivate func set(values: Value...) { + values.forEach(set) + } +} diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 0000000..a646d57 --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,49 @@ +source 'https://cdn.cocoapods.org/' + +platform :ios, '10.0' +use_frameworks! + +target 'Lasso_Example' do + + # Set up SwiftLint + pod 'SwiftLint' + script_phase :name => 'SwiftLint', + :script => "${PODS_ROOT}/SwiftLint/swiftlint" \ + " --config ${PODS_ROOT}/../.swiftlint.yml" \ + " --path ${PODS_ROOT}/../" + + # WWLayout for easy constraints + pod 'WWLayout' + + # Lasso + pod 'Lasso', :path => '../' + +end + +target 'Lasso_Tests' do + pod 'WWLayout' + pod 'Lasso', :path => '../' +end + +target 'Lasso_TestUtilities_Tests' do + pod 'LassoTestUtilities', :path => '../' +end + +target 'Lasso_Example_Tests' do + pod 'LassoTestUtilities', :path => '../' +end + +post_install do |installer| + installer.pods_project.root_object.attributes["ORGANIZATIONNAME"] = "WW International" + + # Support for CircleCI to run unit tests against multiple swift versions. + # Don't include this logic (or a `.swift-version` file) in your projects. + if File.file?('.swift-version') + swift_version = File.read('.swift-version') + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = "#{swift_version.strip}" + end + end + end +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock new file mode 100644 index 0000000..e1931b7 --- /dev/null +++ b/Example/Podfile.lock @@ -0,0 +1,33 @@ +PODS: + - Lasso (1.0.0) + - LassoTestUtilities (1.0.0): + - Lasso + - SwiftLint (0.39.1) + - WWLayout (0.7.0) + +DEPENDENCIES: + - Lasso (from `../`) + - LassoTestUtilities (from `../`) + - SwiftLint + - WWLayout + +SPEC REPOS: + trunk: + - SwiftLint + - WWLayout + +EXTERNAL SOURCES: + Lasso: + :path: "../" + LassoTestUtilities: + :path: "../" + +SPEC CHECKSUMS: + Lasso: 58dbb9e853c847f2eeef6af063783bf480e446c9 + LassoTestUtilities: c96a202eb433421df2ecf03a8a734fc92c590377 + SwiftLint: 55e96a4a4d537d4a3156859fc1c54bd24851a046 + WWLayout: 0785bb486fb4b6aeb52665eb35db73c14e120fd6 + +PODFILE CHECKSUM: c43584f6adba334ac82a3b55b5284836942f739a + +COCOAPODS: 1.8.4 diff --git a/Example/SwiftPM/Images/app.gif b/Example/SwiftPM/Images/app.gif new file mode 100644 index 0000000000000000000000000000000000000000..821828fd71e5f52bab262706dd47f55b47ff3b6b GIT binary patch literal 50557 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 + + + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/IDETemplateMacros.plist b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/IDETemplateMacros.plist new file mode 100644 index 0000000..0ca2416 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/IDETemplateMacros.plist @@ -0,0 +1,23 @@ + + + + + FILEHEADER + +//===----------------------------------------------------------------------===// +// +// ___FILENAME___ +// +// Created by ___FULLUSERNAME___ on ___DATE___ +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-___YEAR___ WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/xcschemes/Lasso-SwiftPM.xcscheme b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/xcschemes/Lasso-SwiftPM.xcscheme new file mode 100644 index 0000000..ec9a5c2 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM.xcodeproj/xcshareddata/xcschemes/Lasso-SwiftPM.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/AppDelegate.swift b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/AppDelegate.swift new file mode 100644 index 0000000..6ca8bd6 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/AppDelegate.swift @@ -0,0 +1,39 @@ +// +//===----------------------------------------------------------------------===// +// +// AppDelegate.swift +// +// Created by Steven Grosmark on 1/6/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + let window = UIWindow(frame: UIScreen.main.bounds) + self.window = window + + WorkFlow().start(with: root(of: window)) + + window.makeKeyAndVisible() + + return true + } + +} diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/Contents.json b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Base.lproj/LaunchScreen.storyboard b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Info.plist b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Info.plist new file mode 100644 index 0000000..efb0ed9 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/Info.plist @@ -0,0 +1,48 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/RestScreen.swift b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/RestScreen.swift new file mode 100644 index 0000000..c0e6b8a --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/RestScreen.swift @@ -0,0 +1,109 @@ +// +//===----------------------------------------------------------------------===// +// +// RestScreen.swift +// +// Created by Steven Grosmark on 1/6/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +// MARK: - Module + +enum RestScreenModule: ScreenModule { + + enum Action: Equatable { + case didTapBackToWork + } + + enum Output: Equatable { + case didRestEnough + } + + struct State: Equatable { + let title: String = "Rest" + let message: String = "Take a break" + } + + static var defaultInitialState: State { State() } + + static func createScreen(with store: RestStore) -> Screen { + Screen(store, RestViewController(store.asViewStore())) + } + +} + +// MARK: - Store + +final class RestStore: LassoStore { + + override func handleAction(_ action: RestScreenModule.Action) { + switch action { + + case .didTapBackToWork: + dispatchOutput(.didRestEnough) + } + } + +} + +// MARK: - View + +final class RestViewController: UIViewController, LassoView { + + let store: RestScreenModule.ViewStore + + init(_ store: RestScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { nil } + + override func viewDidLoad() { + super.viewDidLoad() + + title = state.title + view.backgroundColor = .white + + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + label.text = state.message + + let button = UIButton() + button.setTitle("Feeling Rested", for: .normal) + button.setTitleColor(view.tintColor, for: .normal) + button.layer.borderColor = view.tintColor.cgColor + button.layer.borderWidth = 2 + + guard let view = view else { return } + + view.addSubview(button) + view.addSubview(label) + + label.translatesAutoresizingMaskIntoConstraints = false + button.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30), + label.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 30), + label.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -30), + button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30), + button.centerXAnchor.constraint(equalTo: view.centerXAnchor), + button.widthAnchor.constraint(equalToConstant: 200) + ]) + + button.bind(to: store, action: .didTapBackToWork) + } + +} diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkFlow.swift b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkFlow.swift new file mode 100644 index 0000000..6747dc3 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkFlow.swift @@ -0,0 +1,53 @@ +// +//===----------------------------------------------------------------------===// +// +// WorkFlow.swift +// +// Created by Steven Grosmark on 1/6/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +final class WorkFlow: Flow { + + var workStore: WorkScreenModule.Store? + + override func createInitialController() -> UIViewController { + WorkScreenModule + .createScreen() + .captureStore(as: &workStore) + .observeOutput { [weak self] output in + switch output { + + case .didWorkEnough: + self?.timeToRest() + } + } + .controller + } + + private func timeToRest() { + RestScreenModule + .createScreen() + .observeOutput { [weak self] output in + switch output { + + case .didRestEnough: + self?.workStore?.dispatchAction(.didGetSomeRest) + self?.unwind() + } + } + .place(with: nextPresentedInFlow) + } + +} diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkScreen.swift b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkScreen.swift new file mode 100644 index 0000000..86ab2a2 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPM/WorkScreen.swift @@ -0,0 +1,122 @@ +// +//===----------------------------------------------------------------------===// +// +// WorkScreen.swift +// +// Created by Steven Grosmark on 1/6/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +// MARK: - Module + +enum WorkScreenModule: ScreenModule { + + enum Action: Equatable { + case didTapWork + case didGetSomeRest + } + + enum Output: Equatable { + case didWorkEnough + } + + struct State: Equatable { + let title: String = "Work" + var work: [String] = [] + } + + static var defaultInitialState: State { State() } + + static func createScreen(with store: WorkStore) -> Screen { + Screen(store, WorkViewController(store.asViewStore())) + } + +} + +// MARK: - Store + +final class WorkStore: LassoStore { + + override func handleAction(_ action: Action) { + switch action { + + case .didTapWork: + update { state in + state.work.append("work") + } + if state.work.count >= 5 { + dispatchOutput(.didWorkEnough) + } + + case .didGetSomeRest: + update { state in + state.work = [] + } + } + } + +} + +// MARK: - View + +final class WorkViewController: UIViewController, LassoView { + + let store: WorkScreenModule.ViewStore + + init(_ store: WorkScreenModule.ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { nil } + + override func viewDidLoad() { + super.viewDidLoad() + + title = state.title + view.backgroundColor = .white + + let button = UIButton() + button.setTitle("Work", for: .normal) + button.setTitleColor(view.tintColor, for: .normal) + button.layer.borderColor = view.tintColor.cgColor + button.layer.borderWidth = 2 + + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + + guard let view = view else { return } + + view.addSubview(button) + view.addSubview(label) + + button.translatesAutoresizingMaskIntoConstraints = false + label.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30), + button.centerXAnchor.constraint(equalTo: view.centerXAnchor), + button.widthAnchor.constraint(equalToConstant: 200), + label.topAnchor.constraint(equalTo: button.bottomAnchor, constant: 30), + label.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 30), + label.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -30) + ]) + + store.observeState(\.work) { [weak label] work in + label?.text = work.joined(separator: "\n") + } + button.bind(to: store, action: .didTapWork) + } + +} diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/Info.plist b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/RestScreenTests.swift b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/RestScreenTests.swift new file mode 100644 index 0000000..86799f9 --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/RestScreenTests.swift @@ -0,0 +1,42 @@ +// +//===----------------------------------------------------------------------===// +// +// RestScreenTests.swift +// +// Created by Steven Grosmark on 1/6/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import LassoTestUtilities +@testable import Lasso_SwiftPM + +class RestScreenTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + + override func setUp() { + super.setUp() + store = RestStore() + } + + func test_rest() { + // given + XCTAssertStateEquals(RestScreenModule.State()) + + // when + store.dispatchAction(.didTapBackToWork) + + // then + XCTAssertOutputs([.didRestEnough]) + } + +} diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkFlowTests.swift b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkFlowTests.swift new file mode 100644 index 0000000..d52c12c --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkFlowTests.swift @@ -0,0 +1,63 @@ +// +//===----------------------------------------------------------------------===// +// +// WorkFlowTests.swift +// +// Created by Steven Grosmark on 1/6/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso +import LassoTestUtilities +@testable import Lasso_SwiftPM + +class WorkFlowTests: FlowTestCase { + + var flow: WorkFlow! + + override func setUp() { + super.setUp() + flow = WorkFlow() + } + + override func tearDown() { + flow = nil + super.tearDown() + } + + func testExample() throws { + + let workController: WorkViewController = + try assertRoot( + of: navigationController, + when: { flow.start(with: root(of: navigationController)) }) + + let restController: RestViewController = + try assertPresentation( + on: navigationController, + when: { + for _ in 0..<5 { + workController.store.dispatchAction(.didTapWork) + } + } + ) + XCTAssertEqual(workController.state.work, ["work", "work", "work", "work", "work"]) + + try assertDismissal( + from: restController, + to: navigationController, + when: { restController.store.dispatchAction(.didTapBackToWork) } + ) + XCTAssertEqual(workController.state.work, []) + } + +} diff --git a/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkScreenTests.swift b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkScreenTests.swift new file mode 100644 index 0000000..1576a4f --- /dev/null +++ b/Example/SwiftPM/Lasso-SwiftPM/Lasso-SwiftPMTests/WorkScreenTests.swift @@ -0,0 +1,89 @@ +// +//===----------------------------------------------------------------------===// +// +// WorkScreenTests.swift +// +// Created by Steven Grosmark on 1/6/20. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import LassoTestUtilities +@testable import Lasso_SwiftPM + +class WorkScreenTests: XCTestCase, LassoStoreTestCase { + + let testableStore = TestableStore() + + override func setUp() { + super.setUp() + store = WorkStore() + } + + func test_initialState() { + XCTAssertStateEquals(WorkScreenModule.defaultInitialState) + XCTAssertTrue(store.state.work.isEmpty) + } + + func test_workButton() { + // when + store.dispatchAction(.didTapWork) + + // then + XCTAssertStateEquals(updatedMarker { state in + state.work = ["work"] + }) + XCTAssertOutputs([]) + + // when + store.dispatchAction(.didTapWork) + + // then + XCTAssertStateEquals(updatedMarker { state in + state.work = ["work", "work"] + }) + XCTAssertOutputs([]) + } + + func test_workedEnough() { + // when + for _ in 0..<5 { + store.dispatchAction(.didTapWork) + } + + // then + XCTAssertStateEquals(updatedMarker { state in + state.work = ["work", "work", "work", "work", "work"] + }) + XCTAssertOutputs([.didWorkEnough]) + } + + func test_rested() { + // when + store.dispatchAction(.didTapWork) + + // then + XCTAssertStateEquals(updatedMarker { state in + state.work = ["work"] + }) + XCTAssertOutputs([]) + + // when + store.dispatchAction(.didGetSomeRest) + + // then + XCTAssertStateEquals(updatedMarker { state in + state.work = [] + }) + XCTAssertOutputs([]) + } + +} diff --git a/Example/SwiftPM/ReadMe.md b/Example/SwiftPM/ReadMe.md new file mode 100644 index 0000000..d151272 --- /dev/null +++ b/Example/SwiftPM/ReadMe.md @@ -0,0 +1,14 @@ +# Lasso Swift Package Manager Sample + +This is a sample standalone iOS app that uses Swift Package Manager to bring in Lasso and LassoTestUtilities as dependencies. This sample app contains a very simple Flow with two Screens - along with unit tests: + +Untitled + +The simplest way to add Lasso to your project is through Xcode's `File` -> `Swift Packages` -> `Add Package Dependency...` menu. Enter `https://github.com/ww-tech/lasso.git` for the package URL. + +One thing you might need to manually add is the LassoTestUtilities dependency to your test target: + +![dependencies](Images/dependencies.png) + + + diff --git a/Gemfile b/Gemfile new file mode 100755 index 0000000..7453bf6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +# Gemfile +source 'https://rubygems.org' + +gem 'cocoapods', '>= 1.8.4' +gem 'fastlane', '>= 2.129.0' +gem 'slather', '>= 2.4.7' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..7683d97 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,230 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.2) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.1) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + babosa (1.0.3) + claide (1.0.3) + clamp (1.3.1) + cocoapods (1.8.4) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.8.4) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.11.1, < 2.0) + cocoapods-core (1.8.4) + activesupport (>= 4.0.2, < 6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.3.0) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.4.1) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + concurrent-ruby (1.1.5) + declarative (0.0.10) + declarative-option (0.1.0) + digest-crc (0.4.1) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.5) + emoji_regex (1.0.1) + escape (0.0.4) + excon (0.72.0) + faraday (0.17.3) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.7) + fastlane (2.140.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 0.17) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 0.13.1) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.29.2, < 0.37.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (~> 2.1.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + public_suffix (~> 2.0.0) + rubyzip (>= 1.3.0, < 2.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + google-api-client (0.36.4) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.3.0) + faraday (~> 0.11) + google-cloud-errors (1.0.0) + google-cloud-storage (1.25.1) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.10.0) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.12) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + json (2.3.0) + jwt (2.1.0) + memoist (0.16.2) + mini_magick (4.10.1) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + minitest (5.14.0) + molinillo (0.6.6) + multi_json (1.14.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.2.6) + nap (1.1.0) + naturally (2.2.0) + netrc (0.11.0) + nokogiri (1.10.7) + mini_portile2 (~> 2.4.0) + os (1.0.1) + plist (3.5.0) + public_suffix (2.0.5) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + ruby-macho (1.4.0) + rubyzip (1.3.0) + security (0.1.3) + signet (0.12.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.7) + CFPropertyList + naturally + slack-notifier (2.3.2) + slather (2.4.7) + CFPropertyList (>= 2.2, < 4) + activesupport (>= 4.0.2, < 5) + clamp (~> 1.3) + nokogiri (~> 1.8) + xcodeproj (~> 1.7) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + tty-cursor (0.7.1) + tty-screen (0.7.0) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + tzinfo (1.2.6) + thread_safe (~> 0.1) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.6) + unicode-display_width (1.6.1) + word_wrap (1.0.0) + xcodeproj (1.14.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (>= 1.8.4) + fastlane (>= 2.129.0) + slather (>= 2.4.7) + +BUNDLED WITH + 2.0.2 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/Lasso.podspec b/Lasso.podspec new file mode 100644 index 0000000..326b621 --- /dev/null +++ b/Lasso.podspec @@ -0,0 +1,23 @@ +# +# Be sure to run `pod lib lint Lasso.podspec' to ensure this is a +# valid spec before submitting. +# + +Pod::Spec.new do |s| + s.name = 'Lasso' + s.version = '1.0.0' + s.summary = 'An iOS architectural framework.' + s.description = 'Lasso is an iOS application architecture for building discrete, composable and testable compenents both big and small - from single one-off screens, through complex flows, to high-level application structures.' + + s.homepage = 'https://github.com/ww-tech/lasso' + s.license = { :type => 'Apache License 2.0', :file => 'LICENSE' } + s.authors = { 'Steven Grosmark' => 'steven.grosmark@ww.com', + 'Trevor Beasty' => 'trevor.beasty@ww.com' } + + s.source = { :git => 'https://github.com/ww-tech/lasso.git', :tag => s.version.to_s } + + s.swift_versions = '4.2', '5', '5.1' + + s.ios.deployment_target = '10.0' + s.source_files = 'Sources/Lasso/**/*' +end \ No newline at end of file diff --git a/LassoTestUtilities.podspec b/LassoTestUtilities.podspec new file mode 100644 index 0000000..258f2f9 --- /dev/null +++ b/LassoTestUtilities.podspec @@ -0,0 +1,28 @@ +# +# Be sure to run `pod lib lint Lasso.podspec' to ensure this is a +# valid spec before submitting. +# + +Pod::Spec.new do |s| + s.name = 'LassoTestUtilities' + s.version = '1.0.0' + s.summary = 'Unit test support for the Lasso framework.' + s.description = 'Lasso is an iOS application architecture for building discrete, composable and testable compenents both big and small - from single one-off screens, through complex flows, to high-level application structures.' + + s.homepage = 'https://github.com/ww-tech/lasso' + s.license = { :type => 'Apache License 2.0', :file => 'LICENSE' } + s.authors = { 'Steven Grosmark' => 'steven.grosmark@ww.com', + 'Trevor Beasty' => 'trevor.beasty@ww.com' } + + s.source = { :git => 'https://github.com/ww-tech/lasso.git', :tag => s.version.to_s } + s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO' } + + s.swift_versions = '4.2', '5', '5.1' + + s.ios.deployment_target = '10.0' + s.framework = 'XCTest' + + s.dependency 'Lasso' + + s.source_files = 'Sources/LassoTestUtilities/**/*' +end diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..252d2e4 --- /dev/null +++ b/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "Lasso", + platforms: [ + .iOS(.v10) + ], + products: [ + .library( + name: "Lasso", + targets: ["Lasso"]), + .library( + name: "LassoTestUtilities", + targets: ["LassoTestUtilities"]) + ], + dependencies: [ + ], + targets: [ + .target( + name: "Lasso", + dependencies: []), + .target( + name: "LassoTestUtilities", + dependencies: []) + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1593765 --- /dev/null +++ b/README.md @@ -0,0 +1,143 @@ +

Lasso

+

+ + +

+ +Lasso is an iOS application architecture for building discrete, composable and testable compenents both big and small - from single one-off screens, through complex flows, to high-level application structures. + + + +## Why Lasso? + +Without a set of structural principles, it's very easy for an application's code base to become hard to both reason about, and maintain. In particular, these problems will eventually arise: + +- tight coupling of components makes it hard to change/test things +- business logic living in strange places makes it hard to modify/reuse/debug/test existing code +- view presentation choices made in inappropriate places makes it hard to refactor/reorganize/test flows +- inconsistent organization across team makes it hard to cross-contribute + + + +## The Lasso way + +Lasso encourages a strong separation of concerns by clearly defining discrete, single-responsibility components where specific types of code should live, and a clear, flexible way for these components to communicate. Larger units of behavior are easily composed, and re-composed. + +### Screens + +We generally think of a screen as a single page/view in an app - e.g., a login view, a contacts list view, an audio settings view; etc. + +In Lasso, a `Screen` is the collection of types used to implement a single view: + +

+ +The `View` (i.e. a `UIViewController`) is responsible for: + +- accurately rendering the current `State` (i.e. the content) of the screen +- forwarding user interactions to the `Store` (i.e. the decision maker) +- responding to state changes to keep the presentation up to date + +Lasso views tend to be small, with practically zero logic in them. + +A **unidirectional data flow** is used to ensure consistency: a `View` never re-renders anything in direct response to a user action. `Views` send `Actions` to the `Store`, which updates the `State`, which come back to the `View` as `State` change notifications. + +The `Store` is where a screen's decisions are made, and is the source of truth for the screen's `State`. A `Store` is responsible for: + +- all busniess logic for the screen +- responding to `Actions` (i.e. events sent from the `View`) +- updates to the screen's `State` + +A `Store` can also generate an `Output` when an event occurs that is more appropriately handled elsewhere. E.g., a login screen might generate a "user did login" output as a signal to a higher-level system to move to a logged-in portion of an app; or a contact list screen might generate a "did select contact" output as a signal to a higher-level flow to either present or push a contact detail screen. + +### Flows + +A `Flow` represents a feature - or area - of an app, and is commonly composed of a collection of `Screens`. For example, a "new user" flow might be composed of a series of one-time informational screens followed by a single "let's get started" screen. + +

+ +A `Flow` is instantiated and started within an appropriate context of a view hierarchy (e.g., a "sign up" flow might be presented on a menu screen, or a "welcome" flow might be pushed onto a navigation stack). The `Flow` starts by creating its initial `Screen`, and listens for `Output` signals. As `Outputs` arrive, the `Flow` decides what to do with them - it can create and place another `Screen` into the view hierarchy, emit its own `Output` (when an event occurs that is more appropriately handled elsewhere), or whatever is appropriate for the `Flow`. + +Since `Screens` and `Flows` are encapsulated modules with discrete entry and exit points, it's quite easy and common, for a `Flow` to manage both `Screens` _and_ `Flows`. In this way, it becomes possible to define your application as a hierarchy of components, reducing complexity from the top level down. + +

+ +## Example App + +To run the example project + +1. clone the Lasso repo; +2. run `pod install` from the `Example` directory; and +3. open up `Lasso.xcworkspace` + + + +## Learn more + +Article that introduces Lasso, with a concrete example of creating a `Screen`: +[Lasso: Introducing a new architectural framework for iOS](docs/Lasso-Introduction-part1.md) + +Tips for writing Swifty Lasso +[Lasso coding style guilde](docs/style-guide.md) + + + +## Requirements + +Lasso supports **iOS 10 and up**, and can be compiled with **Swift 4.2 and up**. + + + +## Installation + +### Cocoapods + +The core Lasso framework is added to the primary target in your Podfile: + +```ruby +Pod 'Lasso' +``` + +There is a separate pod that you can add to your test targets: + +```ruby +Pod 'LassoTestUtilities' +``` + +### Swift Package Manager + +The Lasso package URL is: + +``` +`https://github.com/ww-tech/lasso.git` +``` + +For sample usage, see: [Swift Package Manager Sample](Example/SwiftPM/ReadMe.md). + + + +## Contributing + +We love contributions! + +If you have a feature in mind, and/or have found a bug, the best thing to do is: + +1. Search the issues to see if someone has already brought it up! +2. Create a new issue that explains in detail the improvements you'd like to see. +3. If you have a code change in mind, that's awesome! + 1. Fork the Lasso repo + 2. Create a branch for your feature change + 3. Open a PR! + + + +## Authors + +Steven Grosmark, Trevor Beasty, Yuichi Kuroda, and the WW iOS Team. + + + +## License + +Lasso is licensed under the [Apache-2.0 Open Source license](http://choosealicense.com/licenses/apache-2.0/). + +You are free to do with it as you please. We _do_ welcome attribution, and would love to hear from you if you are using it in a project! diff --git a/Sources/Lasso/Flow/Flow+UINavigationController.swift b/Sources/Lasso/Flow/Flow+UINavigationController.swift new file mode 100644 index 0000000..1839e2d --- /dev/null +++ b/Sources/Lasso/Flow/Flow+UINavigationController.swift @@ -0,0 +1,72 @@ +// +//===----------------------------------------------------------------------===// +// +// Flow+UINavigationController.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// All flow placement utilities necessarily return optional placers b/c flows are required +// to keep weak references to their placedContext, which such conveniences are built upon. +// +// Failure (nil return value) for these conveniences reflects the fact that the placedContext was deallocated. +// This occurs when the placedContext in unexpectedly removed from the view controller hierarchy. + +public extension Flow where RequiredContext: UINavigationController { + + /// Push on top of the navigation controller's stack. + var nextPushedInFlow: ScreenPlacer? { + guard let navigationController = context else { return nil } + return pushed(in: navigationController) + } + + /// Place as the root of the navigation controller, removing all other controllers in the stack + var rootOfFlow: ScreenPlacer? { + guard let navigationController = context else { return nil } + return root(of: navigationController) + } + + /// Regress to the navigation controller, removing all controllers following the initial controller + /// in the navigation controller's stack. + /// + /// - Parameter animated: animate the regression + func unwind(animated: Bool = true) { + guard let initialController = initialController, let navigationController = context else { return } + navigationController.popToViewController(initialController, animated: animated) + if navigationController.presentedViewController != nil { + navigationController.dismiss(animated: animated, completion: nil) + } + } + + /// Regress to the controller preceding the initial controller in the navigation controller's stack. + /// If the initial controller is in the zeroth position, regress to the controller modally preceding + /// the navigation controller. + /// + /// - Parameter animated: animate the regression + func dismiss(animated: Bool = true) { + guard let initialController = initialController, let navigationController = context, let initialIndex = navigationController.viewControllers.firstIndex(of: initialController) else { return } + if initialIndex == 0 { + if let presenting = navigationController.presentingViewController { + presenting.dismiss(animated: animated, completion: nil) + } + else { + navigationController.popToRootViewController(animated: animated) + } + } + else { + navigationController.popToViewController(navigationController.viewControllers[initialIndex - 1], animated: animated) + } + } + +} diff --git a/Sources/Lasso/Flow/Flow+UIPageViewController.swift b/Sources/Lasso/Flow/Flow+UIPageViewController.swift new file mode 100644 index 0000000..9d483ab --- /dev/null +++ b/Sources/Lasso/Flow/Flow+UIPageViewController.swift @@ -0,0 +1,28 @@ +// +//===----------------------------------------------------------------------===// +// +// Flow+UIPageViewController.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +public extension Flow where RequiredContext: UIPageViewController { + + /// Set as the page controller's page with a forward animation. + var nextPageInFlow: ScreenPlacer? { + guard let pageController = context else { return nil } + return nextPage(in: pageController) + } + +} diff --git a/Sources/Lasso/Flow/Flow+UIViewController.swift b/Sources/Lasso/Flow/Flow+UIViewController.swift new file mode 100644 index 0000000..34d890f --- /dev/null +++ b/Sources/Lasso/Flow/Flow+UIViewController.swift @@ -0,0 +1,91 @@ +// +//===----------------------------------------------------------------------===// +// +// Flow+UIViewController.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// Convenience for upcasting a ScreenPlacer generic on some UIViewController subclass +// to a ScreenPlacer. +// This allows for starting a Flow in a ScreenPlacer, +// for example - a situation where the Flow's placement requirement is less specific than +// the provided ScreenPlacer. +public extension Flow where RequiredContext == UIViewController { + + /// Starts the flow + /// + /// Places the controller returned by 'createInitialController' with the provided placer. + /// Handles ARC considerations relative to the Flow, creating a strong reference from + /// the initial controller to the Flow. + /// + /// - Parameter placer: ScreenPlacer with 'placedContext' that is a subclass of UIViewController + func start(with placer: ScreenPlacer?) where Placed: UIViewController { + + let upcastedPlacer = placer.map { placer in + ScreenPlacer { toPlace in + return placer.place(toPlace) as UIViewController + } + } + + start(with: upcastedPlacer) + } + +} + +// Utilities available to a Flow that has specified a placedContext that is a UIViewController. +// B/c all contexts must subclass UIViewController, this will be available to all flows. +public extension Flow where RequiredContext: UIViewController { + + /// Modal presentation into the modally top-most position (relative to the initialController). + var nextPresentedInFlow: ScreenPlacer? { + guard let context = context else { return nil } + return presented(on: context.topController) + } + + /// Regress to the initial controller, dismissing all presented controllers. + /// + /// - Parameter animated: animate the regression + func unwind(animated: Bool = true) { + guard let context = context, context.presentedViewController != nil else { return } + context.dismiss(animated: animated, completion: nil) + } + + /// Regress to the view controller modally preceding the initialController. If no such controller exists, + /// regress to the initial controller. + /// + /// - Parameter animated: animate the regression + func dismiss(animated: Bool = true) { + guard let context = context else { return } + if let presenting = context.presentingViewController { + presenting.dismiss(animated: animated, completion: nil) + } + else if context.presentedViewController != nil { + context.dismiss(animated: animated, completion: nil) + } + } + +} + +private extension UIViewController { + + var topController: UIViewController { + var topController: UIViewController = self + while let presented = topController.presentedViewController { + topController = presented + } + return topController + } + +} diff --git a/Sources/Lasso/Flow/Flow.swift b/Sources/Lasso/Flow/Flow.swift new file mode 100644 index 0000000..c8c3677 --- /dev/null +++ b/Sources/Lasso/Flow/Flow.swift @@ -0,0 +1,87 @@ +// +//===----------------------------------------------------------------------===// +// +// Flow.swift +// +// Created by Steven Grosmark on 4/15/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +/// A sequence of screens which can be started in the RequiredContext. +/// +/// The typical Flow creation cycle is +/// ```swift +/// let flow = Flow() +/// flow.start(with: ScreenPlacer) +/// ``` +/// Optionally, you can observe the output of a flow: +/// flow.observeOutput { [weak self] output in /* do something /* } +open class Flow { + + public typealias Output = Module.Output + public typealias RequiredContext = Module.RequiredContext + + public private(set) weak var context: RequiredContext? + public private(set) weak var initialController: UIViewController? + + public init() { + } + + /// Starts the flow + /// + /// Places the controller returned by 'createInitialController' with the provided placer. + /// Handles ARC considerations relative to the Flow, creating a strong reference from + /// the initial controller to the Flow. + /// + /// - Parameter placer: ScreenPlacer with 'placedContext' that is compatible with the Flow's 'RequiredContext' + public func start(with placer: ScreenPlacer?) { + lassoPrecondition(placer != nil, "\(self).start(with: placer) placer is nil!") + guard let placer = placer else { return } + + let initialController = createInitialController() + + initialController.holdReference(to: self) + + self.context = initialController.place(with: placer) + self.initialController = initialController + } + + /// Creates the initial view controller for the Flow. + /// + /// Do not call this directly, instead use the `start` function. + open func createInitialController() -> UIViewController { + return lassoAbstractMethod() + } + + @discardableResult + public func observeOutput(_ handler: @escaping (Output) -> Void) -> Self { + outputBridge.register(handler) + return self + } + + // Convenience for simple mapping from the Screen's Output type to another OtherOutput type + // - so callers don't have to create a closure do perform simple mapping. + @discardableResult + public func observeOutput(_ handler: @escaping (OtherOutput) -> Void, mapping: @escaping (Output) -> OtherOutput) -> Self { + observeOutput { output in + handler(mapping(output)) + } + return self + } + + public func dispatchOutput(_ output: Output) { + outputBridge.dispatch(output) + } + + private let outputBridge = OutputBridge() +} diff --git a/Sources/Lasso/Module.swift b/Sources/Lasso/Module.swift new file mode 100644 index 0000000..3e2598f --- /dev/null +++ b/Sources/Lasso/Module.swift @@ -0,0 +1,128 @@ +// +//===----------------------------------------------------------------------===// +// +// Module.swift +// +// Created by Steven Grosmark on 5/13/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +/// A `StoreModule` is used to define the set of types involved in creating a store. +/// +/// All communication follows a unidirectional data flow: +/// - `Action`s are typically sent from a view controller to the `Store`. +/// - The `Store` updates it's `State`. +/// - `State` changes are received by setting up observations on the `Store`. +/// +/// The `Store` may also emit `Output`. +public protocol StoreModule { + + /// `Action` defines the actions that can be dispatched to the `Store`. + associatedtype Action = NoAction + + /// `State` defines the set of properties the `Store` manages. + associatedtype State = EmptyState + + /// `Output` defines the set of signals the `Store` can generate. + associatedtype Output = NoOutput + + /// The primary, type-erased, public access `Store` for the module. + typealias Store = AnyStore + + /// The `ViewStore` is a type-erased lens into the `Store`, that hides the `Output`. + /// The `ViewStore` is typically used as the interface the view controller sees. + typealias ViewStore = AnyViewStore + +} + +/// A `ScreenModule` is used to define the set of types involved in creating a "screen". +/// +/// A `ScreenModule` is essentially a StoreModule with some extra expectations and +/// conveniences in regards to creating `Store` / `UIViewController` combinations - i.e. `Screen`s +/// +/// Typically, there is a 1:1 relationship between a `ScreenModule` and a `UIViewController`. +/// This is realized in the `createScreen` function, where a concrete `UIViewController` must +/// be instantiated and 'hooked into' the controlling `Store`. +public protocol ScreenModule: StoreModule { + + associatedtype ConcreteStore: Lasso.ConcreteStore where ConcreteStore.State == State, ConcreteStore.Action == Action, ConcreteStore.Output == Output + + typealias Screen = AnyScreen + + static var defaultInitialState: State { get } + + static func createScreen(with store: ConcreteStore) -> Screen + +} + +extension ScreenModule { + + /// Helper to create a `ScreenModule`'s `Screen` with an initial `State` different from the module's `defaultInitialState`, + /// _and_ an opportunity to configure the `ConcreteStore` after it is created. + /// + /// - Parameter initialState: optional initial `State` (default is the module's `defaultInitialState`). + /// - Parameter configure: optional closure for configuring the `ConcreteStore` once it has been created - e.g., to inject dependencies. + public static func createScreen(with initialState: State? = nil, configure: ((ConcreteStore) -> Void)? = nil) -> Screen { + let concreteStore = ConcreteStore(with: initialState ?? defaultInitialState) + configure?(concreteStore) + return createScreen(with: concreteStore) + } + +} + +extension ScreenModule where State == EmptyState { + + public static var defaultInitialState: State { return EmptyState() } + +} + +/// A ViewModule is used when creating a set of types a view controller cares about. +/// +/// A ViewModule: +/// 1) 'Erases' the Output from an AbstractStore. The resulting abstraction is more appropriate for use by a +/// a view controller, which should NEVER observe Outputs. +/// 2) Provides an opportunity to transform state into a form that can be more directly digested by a controller. +/// It is thus possible to define all state mappings in a context outside of the view layer, maximizing the amount +/// of testable business logic. In this fashion, it is possible to extract all business logic from the view layer. +public protocol ViewModule { + + associatedtype ViewAction = NoAction + associatedtype ViewState = EmptyState + + typealias ViewStore = AnyViewStore +} + +/// A FlowModule describes the types that can be used in a flow. +/// FlowModules have Output, and can specify what kind of view controller they are placed in. +public protocol FlowModule { + + associatedtype Output = NoOutput + + associatedtype RequiredContext: UIViewController = UIViewController + +} + +/// A FlowModule where the RequiredContext is bound to a UINavigationController. +public protocol NavigationFlowModule: FlowModule where RequiredContext == UINavigationController { } + +/// A FlowModule with no output. +public enum NoOutputFlow: FlowModule { + public typealias Output = NoOutput + public typealias RequiredContext = UIViewController +} + +/// A FlowModule with no output, requiring placement in a UINavigationController. +public enum NoOutputNavigationFlow: FlowModule { + public typealias Output = NoOutput + public typealias RequiredContext = UINavigationController +} diff --git a/Sources/Lasso/Screen/Screen.swift b/Sources/Lasso/Screen/Screen.swift new file mode 100644 index 0000000..d22e680 --- /dev/null +++ b/Sources/Lasso/Screen/Screen.swift @@ -0,0 +1,112 @@ +// +//===----------------------------------------------------------------------===// +// +// Screen.swift +// +// Created by Steven Grosmark on 5/16/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +/// A simple struct to hold the screen / view controller created by a ScreenModule's `createScreen` function. +public struct AnyScreen { + public typealias State = Module.State + public typealias Action = Module.Action + public typealias Output = Module.Output + public typealias Store = AnyStore + + /// The screen's Store + public let store: Store + + /// The screen's view controller + public let controller: UIViewController + + public init(_ store: S, _ controller: UIViewController) + where S.State == State, S.Action == Action, S.Output == Output { + self.store = store.asAnyStore() + self.controller = controller + self.controller.holdReference(to: self.store) + } + +} + +public protocol LassoView { + + associatedtype ViewState + + associatedtype ViewAction + + typealias ViewStore = AnyViewStore + + var store: ViewStore { get } + +} + +extension LassoView { + + public var state: ViewState { + return store.state + } + + public func dispatchAction(_ viewAction: ViewAction) { + store.dispatchAction(viewAction) + } + +} + +extension AnyScreen { + + // Provide a convenience for chaining creation -> observation -> placement + @discardableResult + public func place(with placer: ScreenPlacer?) -> AnyScreen { + lassoPrecondition(placer != nil, "\(self).place(with:) - placer is nil") + placer?.place(controller) + return self + } + + // Provide a convenience for chaining creation -> observation -> placement + @discardableResult + public func observeOutput(_ handler: @escaping (Output) -> Void) -> AnyScreen { + store.observeOutput(handler) + return self + } + + // Provide a convenience for chaining creation -> observation -> placement + // This version allows simple mapping from the Screen's Output type to another OtherOutput type + // - so callers don't have to create a closure do perform simple mapping. + @discardableResult + func observeOutput(_ handler: @escaping (OtherOutput) -> Void, mapping: @escaping (Output) -> OtherOutput) -> AnyScreen { + observeOutput { output in + handler(mapping(output)) + } + return self + } + + @discardableResult + public func setUpController(_ setUp: (UIViewController) -> Void) -> AnyScreen { + setUp(controller) + return self + } + + @discardableResult + public func captureController(as ref: inout UIViewController?) -> AnyScreen { + ref = controller + return self + } + + @discardableResult + public func captureStore(as ref: inout Store?) -> AnyScreen { + ref = store + return self + } + +} diff --git a/Sources/Lasso/Screen/ScreenFactory.swift b/Sources/Lasso/Screen/ScreenFactory.swift new file mode 100644 index 0000000..57600b4 --- /dev/null +++ b/Sources/Lasso/Screen/ScreenFactory.swift @@ -0,0 +1,49 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenFactory.swift +// +// Created by Trevor Beasty on 10/18/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +extension ScreenModule { + + public typealias ScreenFactory = Lasso.ScreenFactory + +} + +public struct ScreenFactory { + + public typealias Screen = Module.Screen + + private let _createScreen: (Module.State?) -> Screen + + public init(createScreen: @escaping (Module.State?) -> Screen) { + self._createScreen = createScreen + } + + public init(configure: ((Module.ConcreteStore) -> Void)? = nil) { + self._createScreen = { initialState in + return Module.createScreen(with: initialState, configure: configure) + } + } + + public func createScreen(with initialState: Module.State? = nil) -> Screen { + return _createScreen(initialState) + } +} + +public func screenFactory(configure: @escaping (Module.ConcreteStore) -> Void) -> Module.ScreenFactory { + return Module.ScreenFactory(configure: configure) +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacer+Abstract.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacer+Abstract.swift new file mode 100644 index 0000000..33b0a34 --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacer+Abstract.swift @@ -0,0 +1,135 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacer+Abstract.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// Embodiment of the supported ScreenPlacer structures. Clients can call these utilities to make custom ScreenPlacers. +// B/c the init on ScreenPlacer is not publicly exposed, these utilities represent the only paths +// through which clients can create custom ScreenPlacers. +// MARK: - Instance Placement + +/// Place some UIViewController relative to some Base artifact. +/// +/// Choosing the appropriate NextContext is up to the discretion of the implementer. Intuitively, the NextContext +/// should reflect some artifact which allows placement of some 'next controller' in a position 'immediately tangent' +/// to the originally placed controller. By following this rule, one can place a sequence of screens in a reasonable way. +/// +/// - Parameters: +/// - base: any object into which the controller can be placed +/// - place: a closure which executes the placement of the controller relative to the base +/// - Returns: the logical 'next' screen context +public func makePlacer(base: Base, + place: @escaping (Base, UIViewController) -> NextContext) -> ScreenPlacer { + + return ScreenPlacer { toPlace in + return place(base, toPlace) + } +} + +// MARK: - Embedding +// +// ScreenPlacers are composable with respect to embeddings - any placer instance can be embedded. To embed is simply to +// to say "you're actually going place this container controller, and I'm going to place controller(s) in that container". +// All embeddings are deferred - the container is not placed until all requisite children of that container are placed. +// Embedding placers are required (by definition) to return the container as the PlacedContext. This is intuitively +// pleasing - the next logical target for placement following placement into a container is some other placement into that container. +// In this way, the container is a sort of persistent context with respect to embedding placers. + +/// Make a ScreenPlacer which places a child controller into a container. +/// +/// - Parameters: +/// - embedder: the container +/// - place: a closure which executes the placement of the child into the container +/// - onDidPlaceEmbedded: a closure to be executed immediately following placement of the child into the container +/// - Returns: a ScreenPlacer which places the child into the container +public func makeEmbeddingPlacer(into embedder: Embedder, + place: @escaping (Embedder, UIViewController) -> Void, + onDidPlaceEmbedded: @escaping () -> Void = { }) -> ScreenPlacer { + + return ScreenPlacer { toPlace in + place(embedder, toPlace) + onDidPlaceEmbedded() + return embedder + } +} + +/// Make many ScreenPlacers which each place a child controller into the embedding container. +/// +/// Placement of the many children into the container is deferred until all children have been placed. +/// Failure to place all children will result in the container never being placed. +/// +/// - Parameters: +/// - embedder: the container +/// - count: the number of children which need to be placed into the container +/// - place: a closure which executes the placement of the many children into the container +/// - onDidPlaceEmbedded: a closure to be executed immediately following placement of the many children into the container +/// - Returns: many ScreenPlacers which each place a child into the container +public func makeEmbeddingPlacers(into embedder: Embedder, + count: Int, + place: @escaping (Embedder, [UIViewController]) -> Void, + onDidPlaceEmbedded: @escaping () -> Void = { }) -> [ScreenPlacer] { + + var buffer = [UIViewController?](repeating: nil, count: count) { + didSet { + let embedded = buffer.compactMap({ $0 }) + guard embedded.count == count else { return } + place(embedder, embedded) + onDidPlaceEmbedded() + } + } + + return (0.. { toPlace in + buffer[i] = toPlace + return embedder + } + }) +} + +extension ScreenPlacer { + + /// Compose a singular embedding into an existing placer. + /// + /// - Parameters: + /// - embedder: the container + /// - place: a closure which executes the placement of the child into the container + /// - Returns: a ScreenPlacer which places the child into the container + public func withEmbedding(into embedder: Embedder, + place: @escaping (Embedder, UIViewController) -> Void) -> ScreenPlacer { + + return makeEmbeddingPlacer(into: embedder, place: place) { + self.place(embedder) + } + } + + /// Compose a plural embedding into an existing placer. + /// + /// - Parameters: + /// - embedder: the container + /// - count: the number of children which need to be placed into the container + /// - place: a closure which executes the placement of the many children into the container + /// - Returns: many ScreenPlacers which each place a child into the container + public func withEmbedding(into embedder: Embedder, + count: Int, + place: @escaping (Embedder, [UIViewController]) -> Void) -> [ScreenPlacer] { + + return makeEmbeddingPlacers(into: embedder, count: count, place: place) { + self.place(embedder) + } + } + +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacer+UINavigationController.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UINavigationController.swift new file mode 100644 index 0000000..8a2b84d --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UINavigationController.swift @@ -0,0 +1,89 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacer+UINavigationController.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// MARK: - Embedding + +/// Place some controller as the root of the navigation controller. +/// +/// - Parameters: +/// - navigationController: the navigation controller to be placed into +/// - onDidPlaceEmbedded: a closure to be executed immediately following placement of the child into the navigation controller +/// - Returns: the placer into the navigation controller +public func navigationEmbedding(_ navigationController: UINavigationController = ScreenPlacerEmbedding.makeNavigationController(), + onDidPlaceEmbedded: @escaping () -> Void = { }) -> ScreenPlacer { + + let place = { (navigationController: UINavigationController, toPlace: UIViewController) in + navigationController.viewControllers = [toPlace] + } + + return makeEmbeddingPlacer(into: navigationController, place: place, onDidPlaceEmbedded: onDidPlaceEmbedded) +} + +extension ScreenPlacer { + + /// Compose a navigation embedding into an existing placer. + /// + /// - Parameter navigationController: the navigation controller to be placed into + /// - Returns: the placer into the navigation controller + public func withNavigationEmbedding(_ navigationController: UINavigationController = ScreenPlacerEmbedding.makeNavigationController()) -> ScreenPlacer { + + return navigationEmbedding(navigationController, onDidPlaceEmbedded: { + self.place(navigationController) + }) + } + + /// Compose a dismissible navigation embedding into an existing placer. + /// + /// - Parameter navigationController: the dismissible navigation controller to be placed into + /// - Returns: the placer into the dismissible navigation controller + public func withDismissibleNavigationEmbedding(_ navigationController: UINavigationController = ScreenPlacerEmbedding.makeNavigationController()) -> ScreenPlacer { + + return navigationEmbedding(navigationController, onDidPlaceEmbedded: { + self.place(navigationController) + ScreenPlacerEmbedding.makeDismissible(navigationController) + }) + } + +} + +// MARK: - Instance Placement + +/// Push some controller on top of the navigation stack. +/// +/// - Parameter navigationController: the navigation controller to be pushed onto +/// - Returns: the placer into the navigation controller +public func pushed(in navigationController: NavigationController, animated: Bool = true) -> ScreenPlacer { + + return makePlacer(base: navigationController) { navigationController, toPlace in + navigationController.pushViewController(toPlace, animated: animated) + return navigationController + } +} + +/// Push some controller at the root of the navigation stack, removing all other controllers in the stack. +/// +/// - Parameter navigationController: the navigation controller to be placed into +/// - Returns: the placer into the navigation controller +public func root(of navigationController: NavigationController, animated: Bool = false) -> ScreenPlacer { + + return makePlacer(base: navigationController) { navigationController, toPlace in + navigationController.setViewControllers([toPlace], animated: animated) + return navigationController + } +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIPageViewController.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIPageViewController.swift new file mode 100644 index 0000000..4d8c93e --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIPageViewController.swift @@ -0,0 +1,65 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacer+UIPageViewController.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// MARK: - Embedding + +/// Place some controller as the single page of the page controller. +/// +/// - Parameters: +/// - pageController: the page controller to be placed into +/// - onDidPlaceEmbedded: a closure to be executed immediately following placement of the child into the page controller +/// - Returns: the placer into the page controller +public func pageEmbedding(_ pageController: UIPageViewController = ScreenPlacerEmbedding.makePageController(), + onDidPlaceEmbedded: @escaping () -> Void = { }) -> ScreenPlacer { + + let place = { (pageController: UIPageViewController, toPlace: UIViewController) in + pageController.setViewControllers([toPlace], direction: .forward, animated: false, completion: nil) + } + + return makeEmbeddingPlacer(into: pageController, place: place, onDidPlaceEmbedded: onDidPlaceEmbedded) +} + +extension ScreenPlacer { + + /// Compose a page embedding into an existing placer. + /// + /// - Parameter pageController: the page controller to be placed into + /// - Returns: the placer into the page controller + public func withPageEmbedding(_ pageController: UIPageViewController = ScreenPlacerEmbedding.makePageController()) -> ScreenPlacer { + + return pageEmbedding(pageController, onDidPlaceEmbedded: { + self.place(pageController) + }) + } + +} + +// MARK: Instance Placement + +/// Set some controller as the single page of the page controller with a forward animation. +/// +/// - Parameter pageController: the page controller to be placed into +/// - Returns: the placer into the page controller +public func nextPage(in pageController: PageController, animated: Bool = true) -> ScreenPlacer { + + return makePlacer(base: pageController, place: { (pageController, toPlace) -> PageController in + pageController.setViewControllers([toPlace], direction: .forward, animated: animated, completion: nil) + return pageController + }) +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacer+UITabBarController.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UITabBarController.swift new file mode 100644 index 0000000..011b387 --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UITabBarController.swift @@ -0,0 +1,56 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacer+UITabBarController.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// MARK: - Embedding + +/// Place many controllers as the controllers for the tab controller. +/// +/// - Parameters: +/// - tabBarController: the tab controller to be placed into +/// - tabsCount: the desired number of tabs +/// - onDidPlaceEmbedded: a closure to be executed immediately following placement of the children into the tab controller +/// - Returns: the placers into the tab controller; equal in count to the 'tabsCount' arg; indices correspond to those of the tab controller's 'viewControllers' +public func tabBarEmbedding(_ tabBarController: UITabBarController = ScreenPlacerEmbedding.makeTabBarController(), + tabsCount: Int, + onDidPlaceEmbedded: @escaping () -> Void = { }) -> [ScreenPlacer] { + + let place = { (tabBarController: UITabBarController, manyToPlace: [UIViewController]) in + tabBarController.viewControllers = manyToPlace + } + + return makeEmbeddingPlacers(into: tabBarController, count: tabsCount, place: place, onDidPlaceEmbedded: onDidPlaceEmbedded) +} + +extension ScreenPlacer { + + /// Compose a tab controller embedding into an existing placer. + /// + /// - Parameters: + /// - tabBarController: the tab controller to be placed into + /// - tabsCount: the desired number of tabs + /// - Returns: the placers into the tab controller; equal in count to the 'tabsCount' arg; indices correspond to those of the tab controller's 'viewControllers' + public func withTabBarEmbedding(_ tabBarController: UITabBarController = ScreenPlacerEmbedding.makeTabBarController(), + tabsCount: Int) -> [ScreenPlacer] { + + return tabBarEmbedding(tabBarController, tabsCount: tabsCount, onDidPlaceEmbedded: { + self.place(tabBarController) + }) + } + +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIViewController.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIViewController.swift new file mode 100644 index 0000000..f41e230 --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIViewController.swift @@ -0,0 +1,32 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacer+UIViewController.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// MARK: - Instance Placement + +/// Modally present on some other controller. The next context is the controller which is placed. +/// +/// - Parameter controller: the controller to be presented on +/// - Returns: the placer +public func presented(on controller: UIViewController, animated: Bool = true) -> ScreenPlacer { + + return makePlacer(base: controller) { viewController, toPlace in + viewController.present(toPlace, animated: animated, completion: nil) + return toPlace + } +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIWindow.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIWindow.swift new file mode 100644 index 0000000..ec26243 --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacer+UIWindow.swift @@ -0,0 +1,58 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacer+UIWindow.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +// MARK: - Instance Placement + +/// Place some controller as the root of the window with an optional animation. The next context +/// is the controller which is placed. +/// +/// An exhaustive variety of built-in transition animations are available here. Some transitions reflect typical +/// UIKit transitions (e.g. pushing onto a navigation controller). Use of these transitions can drastically decrease +/// the complexity of view controller hierarchies while maintaining familiar transition animations. This is achieved by +/// setting some 'next' controller as the root of the window with the desired transition, as opposed to placing that +/// 'next' controller on top of some existing view controller hierarchy. +/// +/// - Parameters: +/// - window: the window to be placed into +/// - transition: the transition to be animated +/// - Returns: the placer +public func root(of window: UIWindow, using transition: UIWindow.Transition? = nil) -> ScreenPlacer { + + return makePlacer(base: window) { window, toPlace in + if let transition = transition { + window.setRootViewController(toPlace, with: transition) + } + else { + window.rootViewController = toPlace + } + return toPlace + } +} + +/// Place some controller as the root of the application window with an optional animation. The next context +/// is the controller which is placed. This call fails if the application window cannot be found. +/// +/// - Parameters: +/// - transition: the transition to be animated +/// - Returns: the placer +public func rootOfApplicationWindow(using transition: UIWindow.Transition? = nil) -> ScreenPlacer? { + + guard let window = UIApplication.shared.keyWindow else { return nil } + return root(of: window, using: transition) +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacer.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacer.swift new file mode 100644 index 0000000..8ae756e --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacer.swift @@ -0,0 +1,77 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacer.swift +// +// Created by Trevor Beasty on 5/30/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// +import UIKit + +/// An ephemeral object which places a controller into the view controller hierarchy. Upon placement, the caller is given +/// the resulting context - the environment into which the controller has been placed. +/// +/// The ScreenPlacer client needs: +/// - to be able to place a view controller into the view controller hierarchy +/// - to be able to effect changes in the view controller hierarchy following initial controller placement +/// +/// The ScreenPlacer client does not care about: +/// - artifacts of the view controller hierarchy preceding the immediate, placed context +/// +/// Thus, the ScreenPlacer 'erases' the previous context. This allows us to implement controller sequences that are +/// modular with respect to the view controller hierarchy. +/// +/// ScreenPlacers retain strong references to their dependent controllers. Retaining a ScreenPlacer will retain those dependent controllers. +/// +/// Any ScreenPlacer instance will only place once. Any further call to 'place' will simply return the PlacedContext without creating any side +/// effects in the existing view controller hierarchy. +/// +public class ScreenPlacer { + internal typealias Place = (UIViewController) -> PlacedContext + + private let _place: Place + private var placedContext: PlacedContext? + + internal init(place: @escaping Place) { + self._place = place + } + + /// Place a controller into the view controller hierarchy. + /// + /// - Parameter viewController: the view controller to be placed + /// - Returns: the context into which the view controller was placed + @discardableResult + public func place(_ viewController: UIViewController) -> PlacedContext { + /// Silently fail if a client attempts to place twice. + lassoPrecondition(placedContext == nil, "multiple placements attempted; ScreenPlacers may only place once") + if let placedContext = self.placedContext { + return placedContext + } + let placedContext = _place(viewController) + self.placedContext = placedContext + return placedContext + } + +} + +extension UIViewController { + + /// Place a controller into the view controller hierarchy. + /// + /// - Parameter placer: some ScreenPlacer + /// - Returns: the context into which the view controller was placed + @discardableResult + public func place(with placer: ScreenPlacer?) -> PlacedContext? { + lassoPrecondition(placer != nil, "attempt to place \(type(of: self)) with nil placer") + return placer?.place(self) + } + +} diff --git a/Sources/Lasso/ScreenPlacer/ScreenPlacerEmbedding.swift b/Sources/Lasso/ScreenPlacer/ScreenPlacerEmbedding.swift new file mode 100644 index 0000000..274fa5c --- /dev/null +++ b/Sources/Lasso/ScreenPlacer/ScreenPlacerEmbedding.swift @@ -0,0 +1,48 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenPlacerEmbedding.swift +// +// Created by Trevor Beasty on 6/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +/// Mechanism for overriding default UIKit containers with more specific types with respect to +/// the build-in screen placing utilities. +public enum ScreenPlacerEmbedding { + + /// Makes the default navigation controller. + public static var makeNavigationController: () -> UINavigationController = UINavigationController.init + + /// Makes the default 'dismissible' navigation controller. + public static var makeDismissible: (UINavigationController) -> Void = { navigationController in + guard let rootViewController = navigationController.viewControllers.first else { return } + if rootViewController.navigationItem.leftBarButtonItem == nil { + let button = UIBarButtonItem(barButtonSystemItem: .done, target: navigationController, action: #selector(navigationController.dismissController)) + rootViewController.navigationItem.leftBarButtonItem = button + } + } + + /// Makes the default tab bar controller. + public static var makeTabBarController: () -> UITabBarController = UITabBarController.init + + /// Makes the default page controller. + public static var makePageController: () -> UIPageViewController = UIPageViewController.init + +} + +extension UIViewController { + @objc fileprivate func dismissController() { + dismiss(animated: true, completion: nil) + } +} diff --git a/Sources/Lasso/Store/MockStore.swift b/Sources/Lasso/Store/MockStore.swift new file mode 100644 index 0000000..d1f3d0b --- /dev/null +++ b/Sources/Lasso/Store/MockStore.swift @@ -0,0 +1,88 @@ +// +//===----------------------------------------------------------------------===// +// +// MockStore.swift +// +// Created by Trevor Beasty on 10/18/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +public final class MockLassoStore: ConcreteStore { + public typealias State = Module.State + public typealias Action = Module.Action + public typealias Output = Module.Output + + public var dispatchedActions = [Action]() + + private let binder: ValueBinder + + private var outputBridge = OutputBridge() + + public required init(with initialState: State) { + self.binder = ValueBinder(initialState) + } + + // state + public var state: State { + return binder.value + } + + public func observeState(handler: @escaping (State?, State) -> Void) { + binder.bind(to: handler) + } + + public func observeState(handler: @escaping (State) -> Void) { + observeState { _, newState in handler(newState) } + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value?, Value) -> Void) { + binder.bind(keyPath, to: handler) + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value) -> Void) { + observeState(keyPath) { _, newValue in handler(newValue) } + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value?, Value) -> Void) where Value: Equatable { + binder.bind(keyPath, to: handler) + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value) -> Void) where Value: Equatable { + observeState(keyPath) { _, newValue in handler(newValue) } + } + + // actions + public func dispatchAction(_ action: Action) { + dispatchedActions.append(action) + } + + // outputs + public func observeOutput(_ observer: @escaping (Output) -> Void) { + outputBridge.register(observer) + } + + public func dispatchMockOutput(_ output: Output) { + outputBridge.dispatch(output) + } + + // updates + + public typealias Update = (inout T) -> Void + + public func mockUpdate(_ update: @escaping Update = { _ in return }) { + var newState = state + update(&newState) + binder.set(newState) + } + +} diff --git a/Sources/Lasso/Store/PassthroughStore.swift b/Sources/Lasso/Store/PassthroughStore.swift new file mode 100644 index 0000000..307a7d4 --- /dev/null +++ b/Sources/Lasso/Store/PassthroughStore.swift @@ -0,0 +1,30 @@ +// +//===----------------------------------------------------------------------===// +// +// PassthroughStore.swift +// +// Created by Trevor Beasty on 11/20/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +public protocol PassthroughScreenModule: ScreenModule where Action == Output { + static func createScreen(with store: PassthroughStore) -> Screen +} + +public final class PassthroughStore: LassoStore { + + override public func handleAction(_ action: Action) { + dispatchOutput(action) + } + +} diff --git a/Sources/Lasso/Store/Store.swift b/Sources/Lasso/Store/Store.swift new file mode 100644 index 0000000..d41e9d7 --- /dev/null +++ b/Sources/Lasso/Store/Store.swift @@ -0,0 +1,245 @@ +// +//===----------------------------------------------------------------------===// +// +// Store.swift +// +// Created by Trevor Beasty on 5/2/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +public protocol AbstractStore: StateObservable, ActionDispatchable, OutputObservable { } + +public protocol ConcreteStore: AbstractStore { + init(with initialState: State) +} + +open class LassoStore: ConcreteStore { + public typealias State = Module.State + public typealias Action = Module.Action + public typealias Output = Module.Output + + private let binder: ValueBinder + + private var outputBridge = OutputBridge() + private var pendingUpdates: [Update] = [] + + public required init(with initialState: State) { + self.binder = ValueBinder(initialState) + } + + // state + public var state: State { + return binder.value + } + + public func observeState(handler: @escaping (State?, State) -> Void) { + binder.bind(to: handler) + } + + public func observeState(handler: @escaping (State) -> Void) { + observeState { _, newState in handler(newState) } + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value?, Value) -> Void) { + binder.bind(keyPath, to: handler) + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value) -> Void) { + observeState(keyPath) { _, newValue in handler(newValue) } + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value?, Value) -> Void) where Value: Equatable { + binder.bind(keyPath, to: handler) + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value) -> Void) where Value: Equatable { + observeState(keyPath) { _, newValue in handler(newValue) } + } + + // actions + public func dispatchAction(_ action: Action) { + handleAction(action) + } + + open func handleAction(_ action: Action) { + return lassoAbstractMethod() + } + + // outputs + public func observeOutput(_ observer: @escaping (Output) -> Void) { + outputBridge.register(observer) + } + + public func dispatchOutput(_ output: Output) { + outputBridge.dispatch(output) + } + + // updates + + public typealias Update = (inout T) -> Void + + public func update(_ update: @escaping Update = { _ in return }) { + pendingUpdates.append(update) + applyUpdates() + } + + public func batchUpdate(_ update: @escaping Update) { + pendingUpdates.append(update) + } + + private func applyUpdates() { + let newState = pendingUpdates.reduce(into: state) { state, update in + update(&state) + } + binder.set(newState) + pendingUpdates = [] + } + +} + +extension LassoStore where State: Equatable { + + public func observeState(handler: @escaping (State?, State) -> Void) { + binder.bind(to: handler) + } + + public func observeState(handler: @escaping (State) -> Void) { + observeState { _, newState in handler(newState) } + } + +} + +extension LassoStore where Module.State == EmptyState { + + public convenience init() { + self.init(with: EmptyState()) + } + +} + +extension LassoStore where Module: ScreenModule { + + public convenience init() { + self.init(with: Module.defaultInitialState) + } + +} + +extension AbstractStore { + + public typealias ActionMap
= (A) -> Action + public typealias StateMap = (State) -> S + + /// Create a ViewStore with a ViewState that can be initialized from the Store's State, + /// using a ViewModule to define the relevant ViewState & ViewAction types. + /// + /// - Parameters: + /// - viewModuleType: the ViewModule that defines the target ViewState + /// - stateMap: a closure that converts the Store's State to the ViewState + /// - Returns: a new ViewStore + public func asViewStore(for viewModuleType: Module.Type, + stateMap: @escaping StateMap) -> AnyViewStore + where Module.ViewAction == Action { + return asViewStore(stateMap: stateMap, actionMap: { $0 }) + } + + /// Create a ViewStore using a subset of actions, with the Store's State type, + /// using a ViewModule to define the relevant ViewState & ViewAction types. + /// + /// - Parameters: + /// - viewModuleType: the ViewModule that defines the target ViewAction + /// - actionMap: a closure that maps the View's actions to the Store's actions + /// - Returns: a new ViewStore + public func asViewStore(for viewModuleType: Module.Type, + actionMap: @escaping ActionMap) -> AnyViewStore + where Module.ViewState == State { + return asViewStore(stateMap: { $0 }, actionMap: actionMap) + } + + /// Create a ViewStore using a subset of actions, with a ViewState that can be initialized from the Store's State, + /// using a ViewModule to define the relevant ViewState & ViewAction types. + /// + /// - Parameters: + /// - viewModuleType: the ViewModule that defines the target ViewState and ViewAction + /// - stateMap: a closure that converts the Store's State to the ViewState + /// - actionMap: a closure that maps the View's actions to the Store's actions + /// - Returns: a new ViewStore + public func asViewStore(for viewModuleType: Module.Type, + stateMap: @escaping StateMap, + actionMap: @escaping ActionMap) -> AnyViewStore { + return asViewStore(stateMap: stateMap, actionMap: actionMap) + } + + /// Create a ViewStore using the Store's State and Action types, + /// that provides access to just the dispatchAction and observeState methods. + /// + /// - Returns: a new ViewStore + public func asViewStore() -> AnyViewStore { + return asViewStore(stateMap: { $0 }, actionMap: { $0 }) + } + + /// Create a ViewStore with a ViewState that can be initialized from the Store's State + /// + /// - Parameter stateMap: a closure that maps the View's actions to the Store's actions + /// - Returns: a new ViewStore + public func asViewStore(stateMap: @escaping StateMap) -> AnyViewStore { + return asViewStore(stateMap: stateMap, actionMap: { $0 }) + } + + /// Create a ViewStore using a subset of actions, with the Store's State type. + /// + /// - Parameter actionMap: a closure that maps the View's actions to the Store's actions + /// - Returns: a new ViewStore + public func asViewStore(actionMap: @escaping ActionMap) -> AnyViewStore { + return asViewStore(stateMap: { $0 }, actionMap: actionMap) + } + + /// Create a ViewStore using a subset of actions, with a ViewState that can be initialized from the Store's State + /// + /// - Parameters: + /// - actionMap: a closure that maps the View's actions to the Store's actions + /// - stateMap: a closure that converts the Store's State to the ViewState + /// - Returns: a new ViewStore + public func asViewStore(stateMap: @escaping StateMap, + actionMap: @escaping ActionMap) -> AnyViewStore { + return AnyViewStore(self, stateMap: stateMap, actionMap: actionMap) + } + +} + +/// Public-access, type-erased Store +/// - receives actions +/// - readable, observable state +/// - receives outputs, observable outputs +public class AnyStore: AnyViewStore, AbstractStore { + + internal init(_ store: Store) where Store.State == State, Store.Action == Action, Store.Output == Output { + self._observeOutput = store.observeOutput + super.init(store, stateMap: { $0 }, actionMap: { $0 }) + } + + public func observeOutput(_ observer: @escaping (Output) -> Void) { + _observeOutput(observer) + } + + private let _observeOutput: (@escaping (Output) -> Void) -> Void + +} + +extension AbstractStore { + + public func asAnyStore() -> AnyStore { + return AnyStore(self) + } + +} diff --git a/Sources/Lasso/Store/ViewStore.swift b/Sources/Lasso/Store/ViewStore.swift new file mode 100644 index 0000000..1300638 --- /dev/null +++ b/Sources/Lasso/Store/ViewStore.swift @@ -0,0 +1,92 @@ +// +//===----------------------------------------------------------------------===// +// +// ViewStore.swift +// +// Created by Trevor Beasty on 5/2/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +public protocol AbstractViewStore: StateObservable, ActionDispatchable { } + +/// Public-access, type-erased View Store +/// - receives actions +/// - readable, observable state +public class AnyViewStore: AbstractViewStore { + + /// Create a ViewStore + /// + /// - Parameters: + /// - store: the concrete, source store + /// - stateMap: pure function; maps store state to view state + /// - actionMap: pure function; maps view action to store action + internal init(_ store: Store, stateMap: @escaping (Store.State) -> ViewState, actionMap: @escaping (ViewAction) -> Store.Action) { + self.binder = ValueBinder(stateMap(store.state)) + + self._dispatchAction = { viewAction in + store.dispatchAction(actionMap(viewAction)) + } + + store.observeState { [weak self] (_, newState) in + self?.binder.set(stateMap(newState)) + } + + } + + public func dispatchAction(_ viewAction: ViewAction) { + _dispatchAction(viewAction) + } + + public var state: ViewState { + return binder.value + } + + public func observeState(handler: @escaping (ViewState?, ViewState) -> Void) { + binder.bind(to: handler) + } + + public func observeState(handler: @escaping (ViewState) -> Void) { + observeState { _, newState in handler(newState) } + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value?, Value) -> Void) { + binder.bind(keyPath, to: handler) + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value) -> Void) { + observeState(keyPath) { _, newValue in handler(newValue) } + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value?, Value) -> Void) where Value: Equatable { + binder.bind(keyPath, to: handler) + } + + public func observeState(_ keyPath: WritableKeyPath, handler: @escaping (Value) -> Void) where Value: Equatable { + observeState(keyPath) { _, newValue in handler(newValue) } + } + + private let binder: ValueBinder + private let _dispatchAction: (ViewAction) -> Void +} + +extension AnyViewStore where ViewState: Equatable { + + public func observeState(handler: @escaping (ViewState?, ViewState) -> Void) { + binder.bind(to: handler) + } + + public func observeState(handler: @escaping (ViewState) -> Void) { + observeState { _, newState in handler(newState) } + } + +} diff --git a/Sources/Lasso/Types/ActionDispatchable+Bindings.swift b/Sources/Lasso/Types/ActionDispatchable+Bindings.swift new file mode 100644 index 0000000..5c36562 --- /dev/null +++ b/Sources/Lasso/Types/ActionDispatchable+Bindings.swift @@ -0,0 +1,255 @@ +// +//===----------------------------------------------------------------------===// +// +// ActionDispatchable+Bindings.swift +// +// +// Helpers for binding various UIKit elements to Store Actions. +// +// Created by Steven Grosmark on 6/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +extension UIControl { + + /// Bind a UIControl event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - event: The UIControl.Event that will trigger the dispatchAction call on the target. + /// - target: The ActionDispatchable to receive the Action. + /// - action: An ActionDispatchable.Action to send to the target via its `dispatchAction` method. + public func bind(_ event: UIControl.Event = .touchUpInside, to target: Target, action: Target.Action) { + bind(event, to: target) { _ in return action } + } + +} + +extension UIPageControl { + + /// Bind the UIPageControl's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the `currentPage` value to an ActionDispatchable.Action to be dispatched to the target. + /// - currentPage: the new `currentPage` value. + public func bindValueChange(to target: Target, mapping: @escaping (_ currentPage: Int) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0.currentPage) } + } + +} + +extension UISegmentedControl { + + /// Bind the UISegmentedControl's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the `selectedSegmentIndex` value to an ActionDispatchable.Action to be dispatched to the target. + /// - selectedIndex: the new `selectedSegmentIndex` value. + public func bindValueChange(to target: Target, mapping: @escaping (_ selectedIndex: Int) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0.selectedSegmentIndex) } + } + +} + +extension UISlider { + + /// Bind the UISlider's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the slider `value` to an ActionDispatchable.Action to be dispatched to the target. + /// - newValue: the new `value`. + public func bindValueChange(to target: Target, mapping: @escaping (_ newValue: Float) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0.value) } + } + +} + +extension UISwitch { + + /// Bind the UISwitch's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the `isOn` value to an ActionDispatchable.Action to be dispatched to the target. + /// - isOn: the new `isOn` value. + public func bindValueChange(to target: Target, mapping: @escaping (_ isOn: Bool) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0.isOn) } + } + +} + +extension UIStepper { + + /// Bind the UIStepper's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the stepper `value` to an ActionDispatchable.Action to be dispatched to the target. + /// - newValue: the new stepper `value`. + public func bindValueChange(to target: Target, mapping: @escaping (_ newValue: Double) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0.value) } + } + +} + +extension UIDatePicker { + + /// Bind a UIDatePicker's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// Use when the UIDatePicker's mode is `date`, `time`, or `dateAndTime`. + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the `date` value to an ActionDispatchable.Action to be dispatched to the target. + /// - date: the picker's new `date` value. + public func bindDateChange(to target: Target, mapping: @escaping (_ date: Date) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0.date) } + } + + /// Bind a UIDatePicker's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// Use when the UIDatePicker's mode is `countDownTimer`. + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the `countDownDuration` value to an ActionDispatchable.Action to be dispatched to the target. + /// - countDownDuration: the picker's new `countDownDuration` value. + public func bindDurationChange(to target: Target, mapping: @escaping (_ countDownDuration: TimeInterval) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0.countDownDuration) } + } + + /// Bind the UIDatePicker's `.valueChanged` event to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the UIDatePicker to an ActionDispatchable.Action to be dispatched to the target. + /// - datePicker: the UIDatePicker triggering the event. + public func bindValueChange(to target: Target, mapping: @escaping (_ datePicker: UIDatePicker) -> Target.Action) { + bind(.valueChanged, to: target) { mapping($0) } + } + +} + +public protocol ControlActionBindable where Self: UIControl { } + +extension UIControl: ControlActionBindable { } + +extension ControlActionBindable { + + /// Bind a UIControl event to an ActionDispatchable where the Action is a function of the control at the time of invocation. + /// + /// - Parameters: + /// - event: The UIControl.Event that will trigger the action. + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the control's current state to an ActionDispatchable.Action to be dispatched to the target. + /// - sender: The control + public func bind(_ event: UIControl.Event = .touchUpInside, + to target: Target, + mapping: @escaping (_ sender: Self) -> Target.Action) { + let eventHandler = EventHandler { [weak self, weak target] in + self.map { target?.dispatchAction(mapping($0)) } + } + addTarget(eventHandler, action: #selector(eventHandler.execute), for: event) + holdReference(to: eventHandler) + } + +} + +// MARK: - Gesture recognizer action binding + +extension UIGestureRecognizer { + + /// Bind a gesture recognizer's action to an ActionDispatchable (a.k.a. Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - toAction: A Store.Action to send to the target via its `dispatchAction` method. + public func bind(to target: Target, action: Target.Action) { + bind(to: target) { _ in return action } + } + +} + +public protocol GestureActionBindable: UIGestureRecognizer { } + +extension UIGestureRecognizer: GestureActionBindable { } + +extension GestureActionBindable { + + /// Bind a UIGestureRecognizer event to an ActionDispatchable where the Action is a function of the gesture recognizer at the time of invocation. + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the gesture recognizer's current state to an ActionDispatchable.Action to be dispatched to the target. + /// - sender: The UIGestureRecognizer triggering the action + public func bind(to target: Target, mapping: @escaping (_ sender: Self) -> Target.Action) { + let eventHandler = EventHandler { [weak self, weak target] in + self.map { target?.dispatchAction(mapping($0)) } + } + addTarget(eventHandler, action: #selector(eventHandler.execute)) + holdReference(to: eventHandler) + } +} + +// MARK: - Text change notification binding + +extension UITextField { + + /// Bind the UITextField's `didChange` notification to dispatch an Action to an ActionDispatchable (a.k.a Store). + /// + /// - Parameters: + /// - target: The ActionDispatchable to receive the Action. + /// - mapping: A function that maps the `text` value to an ActionDispatchable.Action to be dispatched to the target. + /// - newText: The updated text. + public func bindTextDidChange(to target: Target, mapping: @escaping (_ newText: String) -> Target.Action) { + bind(UITextField.textDidChangeNotification) { [weak self, weak target] _ in + self.map { target?.dispatchAction(mapping($0.text ?? "")) } + } + } + + private func bind(_ notificationName: NSNotification.Name, to handler: @escaping (Notification) -> Void) { + let observer = NotificationObserver(self, notificationName, handler) + holdReference(to: observer) + } + +} + +/// Helper to wrap the @obj aspect of listening to a notification +private class NotificationObserver { + + init(_ object: AnyObject, _ notificationName: NSNotification.Name, _ handler: @escaping (Notification) -> Void) { + self.observation = NotificationCenter.default.addObserver(forName: notificationName, object: object, queue: .main) { [weak object] notification in + if object != nil { handler(notification) } + } + } + + deinit { + NotificationCenter.default.removeObserver(observation) + } + + let observation: NSObjectProtocol +} + +/// Helper to wrap the `@obj` aspect of UIKit action handling +private class EventHandler { + + init(_ execute: @escaping () -> Void) { + self._execute = execute + } + + @objc func execute() { + _execute() + } + + let _execute: () -> Void +} diff --git a/Sources/Lasso/Types/Mockable.swift b/Sources/Lasso/Types/Mockable.swift new file mode 100644 index 0000000..88f32cd --- /dev/null +++ b/Sources/Lasso/Types/Mockable.swift @@ -0,0 +1,102 @@ +// +//===----------------------------------------------------------------------===// +// +// Mockable.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +#if swift(>=5.1) + +/// Property wrapper for allowing things to be mocked for unit test purposes. +/// +/// Mark a property as `@Mockable`: +/// ``` +/// enum Services { +/// @Mockable static var auth: AuthServiceProtocol = AuthService() +/// } +/// ``` +/// +/// `@Mockable` properties are used as plain properties in production code: +/// ``` +/// Services.auth.login(username, password) { success in ... } +/// ``` +/// +/// A property can be mocked by accessing it's projected (`$`) value: +/// ``` +/// // set up the mock: +/// Services.$auth.mock(with: MockAuthService()) +/// +/// // run some tests +/// +/// // remove the mock: +/// Services.$auth.reset() +/// ``` +/// +/// Better yet, use the `XCTestCase` in `LassoTestUtilities` to handle resetting mocks automatically: +/// ``` +/// func test_something() { +/// mock(Services.$auth, with: MockAuthService()) +/// // run some tests +/// } +/// ``` +@propertyWrapper +public struct Mockable { + + /// Current, public-facing value. Will either be the mocked value, or the default value. + public var wrappedValue: T { + get { return projectedValue.value } + set { projectedValue.value = newValue } + } + + public init(wrappedValue: T) { + self.projectedValue = Projected(wrappedValue) + } + + /// The value returned when accessing the wrapper using `$` notation + public let projectedValue: Projected + + public final class Projected { + + /// A `Mockable` value can only be mocked when running tests. + public func mock(with mock: @autoclosure () -> T?) { + mocked = Testing.active ? mock() : nil + } + public func reset() { + mocked = nil + } + + fileprivate var value: T { + get { return mocked ?? _value } + set { _value = newValue } + } + + private var _value: T + private var mocked: T? + + fileprivate init(_ value: T) { + _value = value + } + } + +} + +#endif + +public enum Testing { + + /// Is the current code running under a unit test environment - i.e. is XCTestCase present? + public static internal(set) var active: Bool = { return NSClassFromString("XCTestCase") != nil }() + +} diff --git a/Sources/Lasso/Types/ObjectBinding.swift b/Sources/Lasso/Types/ObjectBinding.swift new file mode 100644 index 0000000..6a5e492 --- /dev/null +++ b/Sources/Lasso/Types/ObjectBinding.swift @@ -0,0 +1,63 @@ +// +//===----------------------------------------------------------------------===// +// +// ObjectBinding.swift +// +// Created by Steven Grosmark on 6/5/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +/// Objects that can hold strong references to other objects +public protocol ObjectBindable: AnyObject { + func holdReference(to object: AnyObject) + func releaseReference(to object: AnyObject) +} + +// This is a common ancestor to UIGestureRecognizers, as well as all other UIKit classes +extension NSObject: ObjectBindable { } + +extension ObjectBindable { + + /// Hold a strong reference to `object`. + /// The reference will be released when the target ObjectBindable is released. + /// + /// - Parameter object: The instance to hold a strong reference to. + public func holdReference(to object: AnyObject) { + var boundObjects = objc_getAssociatedObject(self, &BoundObjects.associationKey) as? BoundObjects.Dictionary ?? [:] + boundObjects[ObjectIdentifier(object)] = object + objc_setAssociatedObject(self, &BoundObjects.associationKey, boundObjects, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + + /// Release a previously held strong reference to `object` + /// + /// - Parameter object: The instance to let go of. + public func releaseReference(to object: AnyObject) { + guard var boundObjects = objc_getAssociatedObject(self, &BoundObjects.associationKey) as? BoundObjects.Dictionary else { return } + boundObjects.removeValue(forKey: ObjectIdentifier(object)) + if boundObjects.isEmpty { + objc_setAssociatedObject(self, &BoundObjects.associationKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + else { + objc_setAssociatedObject(self, &BoundObjects.associationKey, boundObjects, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + +} + +private enum BoundObjects { + + typealias Dictionary = [ObjectIdentifier: AnyObject] + + static var associationKey: Int = 0 + +} diff --git a/Sources/Lasso/Types/OutputBridge.swift b/Sources/Lasso/Types/OutputBridge.swift new file mode 100644 index 0000000..2ed20cc --- /dev/null +++ b/Sources/Lasso/Types/OutputBridge.swift @@ -0,0 +1,36 @@ +// +//===----------------------------------------------------------------------===// +// +// OutputBridge.swift +// +// Created by Steven Grosmark on 5/12/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +internal final class OutputBridge { + + public init() { + } + + internal func register(_ handler: @escaping (Output) -> Void) { + outputObservers.append(handler) + } + + internal func dispatch(_ output: Output) { + executeOnMainThread { [weak self] in + self?.outputObservers.forEach { $0(output) } + } + } + + private var outputObservers: [(Output) -> Void] = [] +} diff --git a/Sources/Lasso/Types/Types.swift b/Sources/Lasso/Types/Types.swift new file mode 100644 index 0000000..64a0783 --- /dev/null +++ b/Sources/Lasso/Types/Types.swift @@ -0,0 +1,103 @@ +// +//===----------------------------------------------------------------------===// +// +// Types.swift +// +// Created by Trevor Beasty on 5/7/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +#if swift(>=4.2) +#else +#error("Lasso is only supported for versions of Swift >= 4.2") +#endif + +public enum NoAction: Equatable { } +public enum NoOutput: Equatable { } + +public struct EmptyState: Equatable { + public init() { } +} + +// meta + +/// These kinds of types have a `state` value, which is: +/// - gettable +/// - observable +public protocol StateObservable: AnyObject { + associatedtype State + + var state: State { get } + + /// Get notifications when the `state` value changes. + /// + /// - Parameter handler: The closure to be called when `state` changes + /// - Parameter oldValue: the previous value, `nil` when called for the first time + /// - Parameter newValue: the new value. + func observeState(handler: @escaping (_ oldValue: State?, _ newValue: State) -> Void) + + /// Get notifications when a `state` property changes. + /// + /// - Parameter keyPath: the KeyPath to the property of `state` + /// - Parameter handler: the closure to be called when the property changes + /// - Parameter oldValue: the previous value, `nil` when called for the first time + /// - Parameter newValue: the new value. + func observeState(_ keyPath: WritableKeyPath, handler: @escaping (_ oldValue: Value?, _ newValue: Value) -> Void) +} + +/// These kinds of types have an `Action` type, which is +/// - an enum +/// - dispatchable +public protocol ActionDispatchable: AnyObject { + associatedtype Action + + func dispatchAction(_ action: Action) +} + +/// These kinds of types have an `Output` type, which is +/// - an enum +/// - observable +public protocol OutputObservable: AnyObject { + associatedtype Output + + func observeOutput(_ observer: @escaping (Output) -> Void) +} + +internal func lassoAbstractMethod(line: UInt = #line, function: String = #function, file: StaticString = #file) -> T { + fatalError("abstract method \(function) must be overridden by subclass", file: file, line: line) +} + +internal func lassoPrecondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) { + if !condition() { + let filename = file.description.components(separatedBy: "/").last(where: { !$0.isEmpty }) ?? "" + lassoPreconditionFailure(""" + Lasso ERROR: \(message()) (\(filename) line \(line)) + Add a symbolic breakpoint on lassoPreconditionFailure to catch this in the debugger + """) + } +} + +private func lassoPreconditionFailure(_ message: String) { + print(message) +} + +public func executeOnMainThread(_ toExecute: @escaping () -> Void) { + if Thread.isMainThread { + toExecute() + } + else { + DispatchQueue.main.async { + toExecute() + } + } +} diff --git a/Sources/Lasso/Types/ValueBinder.swift b/Sources/Lasso/Types/ValueBinder.swift new file mode 100644 index 0000000..ab5d678 --- /dev/null +++ b/Sources/Lasso/Types/ValueBinder.swift @@ -0,0 +1,78 @@ +// +//===----------------------------------------------------------------------===// +// +// ValueBinder.swift +// +// Created by Trevor Beasty on 5/2/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +internal final class ValueBinder { + + internal typealias Observer = (T?, T) -> Void + + public private(set) var value: Value + private var observers: [Observer] = [] + + internal init(_ value: Value) { + self.value = value + } + + internal func set(_ newValue: Value) { + let oldValue = value + value = newValue + executeOnMainThread { [weak self] in + // Dispatch to all observers which exist at execution time - it is possible that additional + // observers could be added b/w queuing and execution. + self?.observers.forEach({ $0(oldValue, newValue) }) + } + } + + private func observe(_ handler: @escaping Observer) { + handler(nil, value) + observers.append(handler) + } + + internal func bind(to handler: @escaping Observer) { + observe(handler) + } + + internal func bind(_ keyPath: KeyPath, to handler: @escaping Observer) { + observe { oldValue, newValue in + let oldKeyValue = oldValue?[keyPath: keyPath] + let newKeyValue = newValue[keyPath: keyPath] + handler(oldKeyValue, newKeyValue) + } + } + + internal func bind(_ keyPath: KeyPath, to handler: @escaping Observer) where T: Equatable { + observe { oldValue, newValue in + let oldKeyValue = oldValue?[keyPath: keyPath] + let newKeyValue = newValue[keyPath: keyPath] + guard oldKeyValue != newKeyValue else { return } + handler(oldKeyValue, newKeyValue) + } + } + +} + +extension ValueBinder where Value: Equatable { + + internal func bind(to handler: @escaping Observer) { + observe { oldValue, newValue in + guard oldValue != newValue else { return } + handler(oldValue, newValue) + } + } + +} diff --git a/Sources/Lasso/UIKit+Lasso/UIWindow+Transition.swift b/Sources/Lasso/UIKit+Lasso/UIWindow+Transition.swift new file mode 100644 index 0000000..6aa996b --- /dev/null +++ b/Sources/Lasso/UIKit+Lasso/UIWindow+Transition.swift @@ -0,0 +1,228 @@ +// +//===----------------------------------------------------------------------===// +// +// UIWindow+Transition.swift +// +// Created by Steven Grosmark on 6/6/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// +import UIKit + +extension UIWindow { + + /// Set a new rootViewController using a transition + /// + /// - Parameters: + /// - controller: the new rootViewController + /// - transition: the Transition to use + public func setRootViewController(_ controller: UIViewController, with transition: Transition, completion: (() -> Void)? = nil) { + + // Plain transitions animate the opacity of the "behind" view controller using a minimum + // opacity of 0. This results in partially revealing a black background during the transition. + var backgroundWindow: UIWindow? + if let backgroundColorOpacity = transition.backgroundColorOpacity, + let color = rootViewController?.view.backgroundColor ?? backgroundColor { + backgroundWindow = UIWindow() + backgroundWindow?.backgroundColor = color.withAlphaComponent(backgroundColorOpacity) + backgroundWindow?.makeKeyAndVisible() + } + + CATransaction.begin() + CATransaction.setCompletionBlock { + if let backgroundWindow = backgroundWindow { + backgroundWindow.removeFromSuperview() + } + completion?() + } + + layer.add(transition.animation, forKey: kCATransition) + rootViewController = controller + makeKeyAndVisible() + + CATransaction.commit() + } + + public struct Transition: Equatable { + + // Standard cross-fade transition + public static let crossfade = Transition.fade() + + /// Standard navigation-style push + public static let push = Transition.slide(from: .right) + + /// Standard navigation-style pop + public static let pop = Transition.slide(from: .left) + + /// Standard modal-style present + public static let present = Transition.cover(from: .bottom) + + /// Standard modal-style dismiss + public static let dismiss = Transition.reveal(from: .top) + + /// Create a fade transition + /// Fades in the new view controller + public static func fade(duration: TimeInterval = Transition.standardDuration, + easing: TransitionEasing = .easeInOut, + backgroundColorOpacity: CGFloat? = Transition.standardBackgroundColorOpacity) -> Transition { + return Transition(type: .fade, duration: duration, easing: easing) + } + + /// Create a slide (a.k.a push) transition + /// New controller slides in as the old one slides out in the same direction. + public static func slide(from direction: TransitionDirection, + duration: TimeInterval = Transition.standardDuration, + easing: TransitionEasing = .easeInOut, + backgroundColorOpacity: CGFloat? = Transition.standardBackgroundColorOpacity) -> Transition { + return Transition(type: .slide, direction: direction, duration: duration, easing: easing) + } + + /// Create a cover transition + /// New view controller slides in on top of old view controller (which remains stationary). + public static func cover(from direction: TransitionDirection, + duration: TimeInterval = Transition.standardDuration, + easing: TransitionEasing = .easeInOut, + backgroundColorOpacity: CGFloat? = Transition.standardBackgroundColorOpacity) -> Transition { + return Transition(type: .cover, direction: direction, duration: duration, easing: easing) + } + + /// Create a reveal transition + /// Old view controller slides out, revealing the new view controller (which remains stationary) + public static func reveal(from direction: TransitionDirection, + duration: TimeInterval = Transition.standardDuration, + easing: TransitionEasing = .easeInOut, + backgroundColorOpacity: CGFloat? = Transition.standardBackgroundColorOpacity) -> Transition { + return Transition(type: .reveal, direction: direction, duration: duration, easing: easing) + } + + /// Default duration of transitions + public static let standardDuration: TimeInterval = 0.33 + + /// Default minimum opacity used to animate the opacity of the "behind" view controller + public static let standardBackgroundColorOpacity: CGFloat? = 1.0 + + private let type: TransitionType + private let direction: TransitionDirection? + private let duration: TimeInterval + private let easing: TransitionEasing + fileprivate let backgroundColorOpacity: CGFloat? + + fileprivate init(type: TransitionType, + direction: TransitionDirection? = nil, + duration: TimeInterval = Transition.standardDuration, + easing: TransitionEasing = .easeInOut, + backgroundColorOpacity: CGFloat? = Transition.standardBackgroundColorOpacity) { + self.type = type + self.direction = direction + self.easing = easing + self.duration = duration + self.backgroundColorOpacity = backgroundColorOpacity + } + + fileprivate var animation: CATransition { + return CATransition() + .set(type: type.caType, subtype: direction?.caType) + .set(duration: duration, function: easing.function) + } + } + + public enum TransitionType { + + /// Fade between view controller + case fade + + /// New controller slides in as the old one slides out in the same direction + case slide + + /// New view controller slides in on top of old view controller (which remains stationary) + case cover + + /// Old view controller slides out, revealing the new view controller (which remains stationary) + case reveal + } + + public enum TransitionDirection { + + /// → from left to right + case left + + /// ← from right to left + case right + + /// ↓ from top to bottom + case top + + /// ↑ from bottom to top + case bottom + } + + public enum TransitionEasing { + + /// Constant speed from beginning to end + case linear + + /// Begins slowly, speeds up as it progresses + case easeIn + + /// Begins quickly, slows as it progresses, + case easeOut + + /// Begins slowly, accelerates through the middle, then slows again near completion + case easeInOut + } + +} + +extension UIWindow.TransitionType { + fileprivate var caType: CATransitionType { + switch self { + case .fade: return .fade + case .slide: return .push + case .cover: return .moveIn + case .reveal: return .reveal + } + } +} + +extension UIWindow.TransitionDirection { + fileprivate var caType: CATransitionSubtype { + switch self { + case .left: return .fromLeft + case .right: return .fromRight + case .top: return .fromBottom + case .bottom: return .fromTop + } + } +} + +extension UIWindow.TransitionEasing { + fileprivate var function: CAMediaTimingFunction { + switch self { + case .linear: return CAMediaTimingFunction(name: .linear) + case .easeIn: return CAMediaTimingFunction(name: .easeIn) + case .easeOut: return CAMediaTimingFunction(name: .easeOut) + case .easeInOut: return CAMediaTimingFunction(name: .easeInEaseOut) + } + } +} + +extension CATransition { + fileprivate func set(type: CATransitionType, subtype: CATransitionSubtype? = nil) -> CATransition { + self.type = type + self.subtype = subtype + return self + } + + fileprivate func set(duration: CFTimeInterval, function: CAMediaTimingFunction) -> CATransition { + self.duration = duration + self.timingFunction = function + return self + } +} diff --git a/Sources/LassoTestUtilities/ControllerLifecycle.swift b/Sources/LassoTestUtilities/ControllerLifecycle.swift new file mode 100644 index 0000000..df57797 --- /dev/null +++ b/Sources/LassoTestUtilities/ControllerLifecycle.swift @@ -0,0 +1,158 @@ +// +//===----------------------------------------------------------------------===// +// +// ControllerLifecycle.swift +// +// Created by Trevor Beasty on 8/16/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest + +// swiftlint:disable opening_brace + +extension XCTestCase { + + /// Generalized utility for controller lifecycle hooks with respect to view controller hierarchy events. + /// No guarantees are made regarding how lifecycle hooks will be called. + /// This utility should only be used where a more specific utility is not available. + /// - Parameter fromController: the foremost controller preceding the event + /// - Parameter resolveTarget: how to 'get' the target following the event + /// - Parameter event: triggers the view controller hierarchy side effects + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear and viewWillDisappear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear and viewDidDisappear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + public func assertControllerEvent( + from fromController: From, + to resolveTarget: () throws -> UIViewController?, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewDidLoad: (From, To) -> Void = { _, _ in }, + onViewWillAppear: @escaping (From, To) -> Void = { _, _ in }, + onViewDidAppear: @escaping (From, To) -> Void = { _, _ in }, + file: StaticString = #file, + line: UInt = #line) throws -> To + { + + event() + + let _toController = try resolveTarget() + let toController: To = try typeCast(_toController, file: file, line: line) + + _ = toController.view + onViewDidLoad(fromController, toController) + + let mainQueueExhaustion = expectMainQueueExhaustion() + wait(for: [mainQueueExhaustion], timeout: timeout) + onViewWillAppear(fromController, toController) + + if let transitionCompletion = expectTransitionCompletion(toController) { + wait(for: [transitionCompletion], timeout: timeout) + } + onViewDidAppear(fromController, toController) + + return toController + } + + /// Generalized utility for controller lifecycle hooks with respect to view controller hierarchy events. + /// No guarantees are made regarding how lifecycle hooks will be called. + /// This utility should only be used where a more specific utility is not available. + /// - Parameter resolveTarget: how to 'get' the target following the event + /// - Parameter event: triggers the view controller hierarchy side effects + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + public func assertControllerEvent( + to resolveTarget: () throws -> UIViewController?, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewDidLoad: (To) -> Void = { _ in }, + onViewWillAppear: @escaping (To) -> Void = { _ in }, + onViewDidAppear: @escaping (To) -> Void = { _ in }, + file: StaticString = #file, + line: UInt = #line) throws -> To + { + + event() + + let _toController = try resolveTarget() + let toController: To = try typeCast(_toController, file: file, line: line) + + _ = toController.view + onViewDidLoad(toController) + + let mainQueueExhaustion = expectMainQueueExhaustion() + wait(for: [mainQueueExhaustion], timeout: timeout) + onViewWillAppear(toController) + + if let transitionCompletion = expectTransitionCompletion(toController) { + wait(for: [transitionCompletion], timeout: timeout) + } + onViewDidAppear(toController) + + return toController + } + + /// Wait for execution of all items enqueued on the main queue and completion of any view controller hierarchy + /// transition. While no guarantees are made regarding lifecycle, it is expected that all pending controller lifecycle events + /// have fired following this call. + /// - Parameter window: the parent window of the target controllers + /// - Parameter timeout: maximum time allowance for the events + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + public func waitForEvents(in window: UIWindow, timeout: TimeInterval = 1, file: StaticString = #file, line: UInt = #line) { + let mainQueueExhaustion = expectMainQueueExhaustion() + let transitionCompletion = expectTransitionCompletion(in: window) + let expectations = [mainQueueExhaustion, transitionCompletion].compactMap({ $0 }) + wait(for: expectations, timeout: timeout) + } + + internal func expectMainQueueExhaustion() -> XCTestExpectation { + let exhaustion = expectation(description: "main queue exhaustion") + DispatchQueue.main.async { + exhaustion.fulfill() + } + return exhaustion + } + + internal func expectTransitionCompletion(_ controller: UIViewController) -> XCTestExpectation? { + if let transitionCoordinator = modallyForemostController(on: controller).transitionCoordinator { + let transitionComplete = expectation(description: "controller transition complete") + transitionCoordinator.animate(alongsideTransition: nil, completion: { _ in + transitionComplete.fulfill() + }) + return transitionComplete + } + else { + return nil + } + } + + internal func expectTransitionCompletion(in window: UIWindow) -> XCTestExpectation? { + guard let root = window.rootViewController else { return nil } + return expectTransitionCompletion(root) + } + + internal func modallyForemostController(on controller: UIViewController) -> UIViewController { + var foremost = controller + while let presented = foremost.presentedViewController { + foremost = presented + } + return foremost + } + +} diff --git a/Sources/LassoTestUtilities/Fail.swift b/Sources/LassoTestUtilities/Fail.swift new file mode 100644 index 0000000..9f4347d --- /dev/null +++ b/Sources/LassoTestUtilities/Fail.swift @@ -0,0 +1,225 @@ +// +//===----------------------------------------------------------------------===// +// +// Fail.swift +// +// Created by Trevor Beasty on 8/16/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest + +/// Describes the side effect of a test failure +public typealias FailTest = (FailedTest) -> TestFailureAction + +public struct FailedTest { + let error: LassoError + let file: StaticString + let line: UInt + let verbose: Bool + + init(error: LassoError, file: StaticString, line: UInt, verbose: Bool = false) { + self.error = error + self.file = file + self.line = line + self.verbose = verbose + } + +} + +/// Logs error messages at the call site on test failure. +public let log = { (failedTest: FailedTest) -> TestFailureAction in + + let message = failedTest.error.message(verbose: failedTest.verbose) + + return TestFailureAction(failedTest: failedTest) { + XCTFail(message, file: failedTest.file, line: failedTest.line) + } + +} + +/// Does nothing on test failure - needed to test that tests fail appropriately. +internal let silent = { (failedTest: FailedTest) -> TestFailureAction in + + return TestFailureAction(failedTest: failedTest) { } + +} + +/// An error with a message. +public protocol LassoError: Error { + func message(verbose: Bool) -> String +} + +extension LassoError { + + var message: String { + return message(verbose: false) + } + +} + +/// An error that invokes an action upon initialization. +/// This abstraction is needed to make assertions of the form "this test should fail". +public struct TestFailureAction: Error { + + let failedTest: FailedTest + + public init(failedTest: FailedTest, action: () -> Void) { + self.failedTest = failedTest + action() + } +} + +enum ModalPresentationError: LassoError { + case unexpectedModallyForemostPrecedingEvent(expected: UIViewController, realized: UIViewController) + case unexpectedModallyForemostFollowingEvent(expected: [UIViewController], realized: [UIViewController]) + case noPresentationOccurred + case noDismissalOccurred + case unexpectedModalPresentationStyle(expected: UIModalPresentationStyle, realized: UIModalPresentationStyle) + + func message(verbose: Bool) -> String { + switch self { + + case .unexpectedModallyForemostPrecedingEvent(expected: let expected, realized: let realized): + var main = "unexpected modally foremost preceding event" + if verbose { + main += " \(describeViewControllerHierarchy(realized))" + } + return main + formattedText(describing: (expectedKey, expected), (realizedKey, realized)) + + case .unexpectedModallyForemostFollowingEvent(expected: let expected, realized: let realized): + var main = "unexpected modally foremost following event" + if verbose, let first = realized.first { + main += " \(describeViewControllerHierarchy(first))" + } + return main + formattedText(describing: (expectedKey, expected), (realizedKey, realized)) + + case .noPresentationOccurred: + return "no presentation occurred" + + case .noDismissalOccurred: + return "no dismissal occurred" + + case .unexpectedModalPresentationStyle(expected: let expected, realized: let realized): + return "unexpected modal transition style" + formattedText(describing: (expectedKey, expected.rawValue), (realizedKey, realized.rawValue)) + } + } + +} + +enum ModalPresentationTypeError: LassoError { + case unexpectedPresentedTypeFollowingEvent(realized: UIViewController) + + func message(verbose: Bool) -> String { + switch self { + + case .unexpectedPresentedTypeFollowingEvent(realized: let realized): + return "expected presented to be of type \(String(describing: Type.self)), instead found \(String(describing: type(of: realized)))" + } + } + +} + +enum NavigationPresentationError: LassoError { + case noNavigationEmbedding(target: UIViewController) + case unexpectedTopPrecedingEvent(expected: UIViewController, navigationController: UINavigationController) + case unexpectedEmptyStackFollowingEvent(navigationController: UINavigationController) + case unexpectedTopFollowingEvent(expected: UIViewController, navigationController: UINavigationController) + case unexpectedTopStackFollowingEvent(expected: [UIViewController], navigationController: UINavigationController) + case unexpectedStackFollowingRootEvent(navigationController: UINavigationController) + case oldRootRemainsFollowingRootEvent(oldRoot: UIViewController, navigationController: UINavigationController) + case noPushOccurred(previous: UIViewController) + + func message(verbose: Bool) -> String { + switch self { + + case .noNavigationEmbedding(target: let target): + return "nil navigation controller for controller" + formattedText(describing: (controllerKey, target)) + + case .unexpectedTopPrecedingEvent(expected: let expected, navigationController: let nav): + return "expected controller to be at top of navigation stack preceding event" + formattedText(describing: (controllerKey, expected), (navKey, nav)) + + case .unexpectedEmptyStackFollowingEvent(navigationController: let nav): + return "expected non-empty navigation stack following event" + formattedText(describing: (navKey, nav)) + + case .unexpectedTopFollowingEvent(expected: let expected, navigationController: let nav): + return "expected controller to be at top of navigation stack following event" + formattedText(describing: (controllerKey, expected), (navKey, nav)) + + case .unexpectedTopStackFollowingEvent(expected: let expected, navigationController: let nav): + return "expected controllers to be at top of navigation stack following event" + formattedText(describing: (controllersKey, expected), (navKey, nav)) + + case .unexpectedStackFollowingRootEvent(navigationController: let nav): + return "unexpected navigation stack following event" + formattedText(describing: (navKey, nav), (stackKey, nav.viewControllers)) + + case .oldRootRemainsFollowingRootEvent(oldRoot: let oldRoot, navigationController: let nav): + return "expected new root following event" + formattedText(describing: ("old root", oldRoot), (navKey, nav)) + + case .noPushOccurred(previous: let previous): + return "no push occurred\nRealized: 'previous' \(String(describing: previous)) at top of navigation stack following event" + } + } + +} + +enum NavigationPresentationTypeError: LassoError { + case unexpectedPushedType(realized: UIViewController) + case unexpectedRootType(realized: UIViewController) + + func message(verbose: Bool) -> String { + switch self { + + case .unexpectedPushedType(realized: let realized): + return "expected pushed to be of type \(String(describing: Type.self))" + formattedText(describing: ("Realized pushed", realized)) + + case .unexpectedRootType(realized: let realized): + return "expected root to be of type \(String(describing: Type.self))" + formattedText(describing: ("Realized root", realized)) + } + } + +} + +enum TypeCastError: Error { + case failedToResolveInstance + case unexpectedInstanceType(instance: Instance) +} + +extension XCTest { + + internal func unexpectedErrorType(file: StaticString = #file, line: UInt = #line) { + XCTFail("unexpected error type", file: file, line: line) + } + + internal func assertThrowsError(expr: () throws -> Void, eval: (LassoError) -> Void, file: StaticString = #file, line: UInt = #line) { + do { + try expr() + } + catch let error { + guard let testFailureAction = error as? TestFailureAction else { fatalError("should always be of type TestFailureAction") } + eval(testFailureAction.failedTest.error) + return + } + XCTFail("expected error to be thrown", file: file, line: line) + } + +} + +private let navKey = "navigation controller" +private let targetKey = "target" +private let controllersKey = "controllers" +private let controllerKey = "controller" +private let expectedKey = "expected" +private let realizedKey = "realized" +private let stackKey = "stack" + +private func formattedText(describing pairs: (String, CustomStringConvertible)...) -> String { + return pairs.map { "\n\($0.0): \(String(describing: $0.1))" } + .joined() +} diff --git a/Sources/LassoTestUtilities/FlowAsserting.swift b/Sources/LassoTestUtilities/FlowAsserting.swift new file mode 100644 index 0000000..8288e11 --- /dev/null +++ b/Sources/LassoTestUtilities/FlowAsserting.swift @@ -0,0 +1,35 @@ +// +//===----------------------------------------------------------------------===// +// +// FlowAsserting.swift +// +// Created by Trevor Beasty on 9/4/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso + +public extension Flow where Module.Output: Equatable { + + /// Assert that the event results in the stated outputs from the Flow. + /// - Parameter event: the event which results in the outputs + /// - Parameter outputs: the outputs resulting from the event + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + func assert(when event: () -> Void, outputs: Output..., file: StaticString = #file, line: UInt = #line) { + var _outputs: [Output] = [] + observeOutput { _outputs.append($0) } + event() + XCTAssertEqual(_outputs, outputs, file: file, line: line) + } + +} diff --git a/Sources/LassoTestUtilities/FlowTestCase.swift b/Sources/LassoTestUtilities/FlowTestCase.swift new file mode 100644 index 0000000..d63b3b8 --- /dev/null +++ b/Sources/LassoTestUtilities/FlowTestCase.swift @@ -0,0 +1,48 @@ +// +//===----------------------------------------------------------------------===// +// +// FlowTestCase.swift +// +// Created by Trevor Beasty on 8/7/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import UIKit + +/// Provides conveniences for Flow unit testing. +/// Key features: +/// 1) a UIWindow which is made 'key and visible'. This is required for lifecycle hooks to work. +/// 2) convenience controllers in the root of the window which cover the most common use cases +open class FlowTestCase: XCTestCase { + + public private(set) var window: UIWindow! + public private(set) var navigationController: UINavigationController! + public private(set) var rootController: UIViewController! + + open override func setUp() { + super.setUp() + window = UIWindow() + window.makeKeyAndVisible() + rootController = UIViewController() + navigationController = UINavigationController(rootViewController: rootController) + window.rootViewController = navigationController + waitForEvents(in: window) + } + + open override func tearDown() { + window = nil + navigationController = nil + rootController = nil + super.tearDown() + } + +} diff --git a/Sources/LassoTestUtilities/LassoStoreTestCase.swift b/Sources/LassoTestUtilities/LassoStoreTestCase.swift new file mode 100644 index 0000000..623ee09 --- /dev/null +++ b/Sources/LassoTestUtilities/LassoStoreTestCase.swift @@ -0,0 +1,222 @@ +// +//===----------------------------------------------------------------------===// +// +// LassoStoreTestCase.swift +// +// Created by Steven Grosmark on 10/2/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso + +/// Wrapper used to test a Store. +/// +/// Works in conjunction with the `LassoStoreTestCase` protocol that: +/// - requires a `testableStore` var +/// - exposes access to the underlying `store`, a `markerState`, and `outputs`, `states` arrays +/// - the `store` is the store under test +/// - `markerState` starts out equal to the store's initial state, +/// and is used as a marker for testing a `store`'s state after some action(s) occurs +/// - `states` is an array of all `State` values emitted by the `store` +/// - `outputs` is an array of all `Output` values dispatched by the `store` +/// +/// Usage: +/// ``` +/// // Declare your test case, and adopt the LassoStoreTestCase protocol: +/// class MyStoreTests: XCTestCase, LassoStoreTestCase { +/// +/// // declare and create a TestableStore instance: +/// let testableStore = TestableStore() +/// +/// // initialize the `store` in your `setUp()` override: +/// override func setUp() { +/// super.setUp() +/// store = MyStoreModule.createScreen().store +/// +/// // optionally dispatch any actions, and capture the state as an initial starting point for all tests +/// // e.g.: +/// // store.dispatchAction(.viewDidAppear) +/// // syncState() // sets `markerState` = `store.state`, and `states` = `[store.state]` +/// } +/// +/// // write some tests +/// func test_MyStore() { +/// // access the store directly: +/// store.dispatchAction(.didTapSomething) +/// +/// // assert the current state against the `markerState`: +/// XCTAssertStateEquals(updatedMarker: { state in +/// state.someProperty = "something" +/// }) +/// +/// // `markerState` now reflects the applied updates +/// XCTAssertEqual(store.state, markerState) +/// // or: +/// XCTAssertStateEquals(markerState) +/// +/// // test outputs: +/// XCTAssertOutputs([.didSomething]) +/// XCTAssertLastOutput(.didSomething) +/// } +/// } +/// ``` +public class TestableStore { + + public init() { } + + public var store: Store! { + didSet { + markerState = store.state + outputs = [] + states = [] + store.observeOutput { [weak self] output in self?.outputs.append(output) } + store.observeState { [weak self] _, state in self?.states.append(state) } + } + } + + var markerState: Store.State! + var outputs = [Store.Output]() + var states = [Store.State]() +} + +public protocol LassoStoreTestCase: XCTestCase { + associatedtype Store: AbstractStore + + typealias State = Store.State + typealias Action = Store.Action + typealias Output = Store.Output + + var testableStore: TestableStore { get } +} + +extension LassoStoreTestCase { + + /// The `store` under test. + public var store: Store! { + get { return testableStore.store } + set { testableStore.store = newValue } + } + + /// An instance of `State` used to track alongside the `store`'s `state`. + /// + /// `markerState` will start out equal to the `store.state`, and is used as a starting point + /// for asserting expectations against `store.state` + /// The `markerState` is modifiable via the `updatedMarker` func, typically used when + /// making assertions. E.g.: + /// ``` + /// store.dispatchAction(.didSomething) + /// XCTAssertStateEquals(updatedMarker { state in + /// state.name = "something" + /// }) + /// ``` + public var markerState: State! { return testableStore.markerState } + + /// A collection `State` values, as emitted by the `store` + public var states: [State] { return testableStore.states } + + /// Update the `markerState` + /// - Parameter update: closure that updates the `markerState` + /// - Returns: the updated `markerState` + @discardableResult + public func updatedMarker(_ update: (inout State) -> Void) -> State { + update(&testableStore.markerState) + return testableStore.markerState + } + + /// Resets the `markerState` and `states` properties to match the current `store.state`, + /// as if the store was just created. + /// + /// Sets the `markerState` to the current `store.state`. + /// Sets the `states` array to `[store.state]` + public func syncState() { + testableStore.markerState = store.state + testableStore.states = [store.state] + } + + /// A collection of `Output` values as emitted by the `store` + /// + /// Use `XCTAssertOutputs` and/or `XCTAssertLastOutput` to make assertions. + public var outputs: [Output] { return testableStore.outputs } + + /// Resets the `outputs` to `[]`. + public func resetOutputs() { + testableStore.outputs = [] + } + +} + +// MARK: XCTestCase + nicer assertions + +extension LassoStoreTestCase where Output: Equatable { + + public func XCTAssertOutputs(_ expected: [Output], file: StaticString = #file, line: UInt = #line) { + LassoAssertEqual(outputs, expected, file: file, line: line) + } + + public func XCTAssertLastOutput(_ expected: Output, file: StaticString = #file, line: UInt = #line) { + LassoAssertEqual(outputs.last, expected, file: file, line: line) + } + +} + +extension LassoStoreTestCase where State: Equatable { + + /// Asserts `store.state` equals an expected State value. + /// + /// The most common use case is to assert that `store.state` is equal + /// to the `markerState` with some property updates: + /// ``` + /// XCTAssertStateEquals(updatedMarker { state in + /// state.name = "something" + /// }) + /// ``` + /// `markerState` is reset to `store.state` after the assertion is made. + /// + /// - Parameter expected: the expected State value + public func XCTAssertStateEquals(_ expected: State, file: StaticString = #file, line: UInt = #line) { + LassoAssertEqual(store.state, expected, file: file, line: line) + testableStore.markerState = store.state + } + + public func XCTAssertStates(_ expected: [State], file: StaticString = #file, line: UInt = #line) { + LassoAssertEqual(states, expected, file: file, line: line) + } + +} + +extension XCTestCase { + + public func LassoAssertEqual(_ realized: A, _ expected: A, file: StaticString = #file, line: UInt = #line) { + if realized != expected { + XCTFail("realized differs from expected" + messageDescribingDiffs(realized, expected), file: file, line: line) + } + } + + public func LassoAssertEqual(_ realized: A?, _ expected: A?, file: StaticString = #file, line: UInt = #line) { + if realized != expected { + if realized == nil || expected == nil { + return XCTAssertEqual(realized, expected, file: file, line: line) + } + XCTFail("realized differs from expected" + messageDescribingDiffs(realized, expected), file: file, line: line) + } + } + + public func LassoAssertEqual(_ realized: [A], _ expected: [A], file: StaticString = #file, line: UInt = #line) { + if realized != expected { + if realized.isEmpty || expected.isEmpty { + return XCTAssertEqual(realized, expected, file: file, line: line) + } + XCTFail("realized differs from expected" + messageDescribingDiffs(realized, expected), file: file, line: line) + } + } + +} diff --git a/Sources/LassoTestUtilities/ModalTesting.swift b/Sources/LassoTestUtilities/ModalTesting.swift new file mode 100644 index 0000000..0635e32 --- /dev/null +++ b/Sources/LassoTestUtilities/ModalTesting.swift @@ -0,0 +1,349 @@ +// +//===----------------------------------------------------------------------===// +// +// ModalTesting.swift +// +// Created by Trevor Beasty on 8/6/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest + +// swiftlint:disable opening_brace function_parameter_count + +extension XCTestCase { + + /// Assert that the event will result in a modal presentation on the presenting controller. + /// The only lifecycle hooks provided are those which are common across all modalPresentationStyles + /// and iOS versions - namely, all lifecycle events for the presented controller. + /// CRITICAL: Controllers must be descendants of a UIWindow which is 'key and visible', otherwise, + /// lifecycle hooks will not work. + /// 1) presenting must be modally foremost preceding event + /// 2) presenting and presented must be modally foremost following event + /// - Parameter presenting: the base of the presentation + /// - Parameter event: the event which triggers the presentation + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear and viewWillDisappear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear and viewDidDisappear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + /// - Parameter failTest: the side effect of test failure + public func assertPresentation( + on presenting: Presenting, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewDidLoad: (Presented) -> Void = { _ in }, + onViewWillAppear: @escaping (Presented) -> Void = { _ in }, + onViewDidAppear: @escaping (Presented) -> Void = { _ in }, + file: StaticString = #file, + line: UInt = #line, + failTest: FailTest = log, + verbose: Bool = true) throws -> Presented + { + return try _assertPresentation( + on: presenting, + when: event, + timeout: timeout, + onViewDidLoad: { onViewDidLoad($1) }, + onViewWillAppear: { onViewWillAppear($1) }, + onViewDidAppear: { onViewDidAppear($1) }, + file: file, + line: line, + failTest: failTest, + verbose: verbose + ) + } + + /// Assert that the event will result in a modal dismissal from the presented to the presenting controller. + /// The only lifecycle hooks provided are those which are common across all modalPresentationStyles + /// and iOS versions - namely, all lifecycle events for the presented controller. + /// CRITICAL: Controllers must be descendants of a UIWindow which is 'key and visible', otherwise, + /// lifecycle hooks will not work. + /// 1) presented must be modally foremost preceding event + /// 2) presenting must be modally foremost following event + /// - Parameter presented: the modally foremost controller preceding the event + /// - Parameter presenting: the modally foremost controller following the event + /// - Parameter event: the event which triggers the dismissal + /// - Parameter timeout: maximum time allowance for the presentation + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear and viewWillDisappear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear and viewDidDisappear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + /// - Parameter failTest: the side effect of test failure + public func assertDismissal( + from presented: Presented, + to presenting: Presenting, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewWillAppear: @escaping (Presented) -> Void = { _ in }, + onViewDidAppear: @escaping (Presented) -> Void = { _ in }, + file: StaticString = #file, + line: UInt = #line, + failTest: FailTest = log) throws + { + try _assertDismissal( + from: presented, + to: presenting, + when: event, + timeout: timeout, + onViewWillAppear: { presented, _ in + onViewWillAppear(presented) + }, + onViewDidAppear: { presented, _ in + onViewDidAppear(presented) + }, + file: file, + line: line, + failTest: failTest + ) + } + + /// Assert that the event will result in a modal presentation on the presenting controller with a fullScreen + /// modalPresentationStyle. The presenting and presented controllers receive the full set of lifecycle hooks. + /// CRITICAL: Controllers must be descendants of a UIWindow which is 'key and visible', otherwise, + /// lifecycle hooks will not work. + /// 1) presenting must be modally foremost preceding event + /// 2) presenting and presented must be modally foremost following event + /// 3) presented must have a fullScreen modalPresentationStyle + /// - Parameter presenting: the base of the presentation + /// - Parameter event: the event which triggers the presentation + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear and viewWillDisappear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear and viewDidDisappear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + /// - Parameter failTest: the side effect of test failure + public func assertFullScreenPresentation( + on presenting: Presenting, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewDidLoad: (Presenting, Presented) -> Void = { _, _ in }, + onViewWillAppear: @escaping (Presenting, Presented) -> Void = { _, _ in }, + onViewDidAppear: @escaping (Presenting, Presented) -> Void = { _, _ in }, + file: StaticString = #file, + line: UInt = #line, + failTest: FailTest = log, + verbose: Bool = true) throws -> Presented + { + let presented = try _assertPresentation( + on: presenting, + when: event, + timeout: timeout, + onViewDidLoad: onViewDidLoad, + onViewWillAppear: onViewWillAppear, + onViewDidAppear: onViewDidAppear, + file: file, + line: line, + failTest: failTest, + verbose: verbose + ) + + guard presented.modalPresentationStyle == .fullScreen else { + let failedTest = FailedTest(error: ModalPresentationError.unexpectedModalPresentationStyle(expected: .fullScreen, realized: presented.modalPresentationStyle), + file: file, + line: line, + verbose: verbose) + throw failTest(failedTest) + } + + return presented + } + + /// Assert that the event will result in a modal dismissal from the presented to the presenting controller with a fullScreen + /// modalPresentationStyle. The presenting and presented controllers receive the full set of lifecycle hooks. + /// CRITICAL: Controllers must be descendants of a UIWindow which is 'key and visible', otherwise, + /// lifecycle hooks will not work. + /// 1) presented must be modally foremost preceding event + /// 2) presenting must be modally foremost following event + /// 3) presented must have a fullScreen modalPresentationStyle + /// - Parameter presented: the modally foremost controller preceding the event + /// - Parameter presenting: the modally foremost controller following the event + /// - Parameter event: the event which triggers the dismissal + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear and viewWillDisappear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear and viewDidDisappear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + /// - Parameter failTest: the side effect of test failure + public func assertFullScreenDismissal( + from presented: Presented, + to presenting: Presenting, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewWillAppear: @escaping (Presented, Presenting) -> Void = { _, _ in }, + onViewDidAppear: @escaping (Presented, Presenting) -> Void = { _, _ in }, + file: StaticString = #file, + line: UInt = #line, + failTest: FailTest = log) throws + { + guard presented.modalPresentationStyle == .fullScreen else { + let failedTest = FailedTest(error: ModalPresentationError.unexpectedModalPresentationStyle(expected: .fullScreen, realized: presented.modalPresentationStyle), + file: file, + line: line) + throw failTest(failedTest) + } + + try _assertDismissal( + from: presented, + to: presenting, + when: event, + timeout: timeout, + onViewWillAppear: onViewWillAppear, + onViewDidAppear: onViewDidAppear, + file: file, + line: line, + failTest: failTest + ) + } + + private func _assertPresentation( + on presenting: Presenting, + when event: () -> Void, + timeout: TimeInterval, + onViewDidLoad: (Presenting, Presented) -> Void, + onViewWillAppear: @escaping (Presenting, Presented) -> Void, + onViewDidAppear: @escaping (Presenting, Presented) -> Void, + file: StaticString, + line: UInt, + failTest: FailTest, + verbose: Bool) throws -> Presented + { + + let modallyForemost = modallyForemostController(on: presenting) + guard modallyForemost === presenting else { + let failedTest = FailedTest(error: ModalPresentationError.unexpectedModallyForemostPrecedingEvent(expected: presenting, realized: modallyForemost), + file: file, + line: line, + verbose: verbose) + throw failTest(failedTest) + } + + do { + let presented: Presented = try assertControllerEvent( + from: presenting, + to: { presenting.presentedViewController }, + when: event, + timeout: timeout, + onViewDidLoad: onViewDidLoad, + onViewWillAppear: onViewWillAppear, + onViewDidAppear: onViewDidAppear + ) + + let modallyForemost = modallyForemostController(on: presented) + let followingStack = modalStack(preceding: modallyForemost) + guard followingStack.suffix(2) == [topParent(of: presenting), presented] else { + throw ModalPresentationError.unexpectedModallyForemostFollowingEvent(expected: [topParent(of: presenting), presented], realized: followingStack.suffix(2)) + } + + return presented + } + catch let error { + switch error { + + case TypeCastError.failedToResolveInstance: + let failedTest = FailedTest(error: ModalPresentationError.noPresentationOccurred, + file: file, + line: line, + verbose: verbose) + throw failTest(failedTest) + + case TypeCastError.unexpectedInstanceType(instance: let instance): + let failedTest = FailedTest(error: ModalPresentationTypeError.unexpectedPresentedTypeFollowingEvent(realized: instance), + file: file, + line: line, + verbose: verbose) + throw failTest(failedTest) + + default: + guard let lassoError = error as? LassoError else { + fatalError("should never execute") + } + let failedTest = FailedTest(error: lassoError, + file: file, + line: line, + verbose: verbose) + throw failTest(failedTest) + + } + } + + } + + private func _assertDismissal( + from presented: Presented, + to presenting: Presenting, + when event: () -> Void, + timeout: TimeInterval, + onViewWillAppear: @escaping (Presented, Presenting) -> Void, + onViewDidAppear: @escaping (Presented, Presenting) -> Void, + file: StaticString, + line: UInt, + failTest: FailTest) throws + { + + let modallyForemostPreceding = modallyForemostController(on: presented) + guard modallyForemostPreceding === presented else { + let failedTest = FailedTest(error: ModalPresentationError.unexpectedModallyForemostPrecedingEvent(expected: presented, realized: modallyForemostPreceding), + file: file, + line: line) + throw failTest(failedTest) + } + let precedingStack = modalStack(preceding: modallyForemostPreceding) + + _ = try assertControllerEvent( + from: presented, + to: { presenting }, + when: event, + timeout: timeout, + onViewDidLoad: { _, _ in }, + onViewWillAppear: onViewWillAppear, + onViewDidAppear: onViewDidAppear, + file: file, + line: line + ) + + let modallyForemostFollowing = modallyForemostController(on: presenting) + let followingStack = modalStack(preceding: modallyForemostFollowing) + guard !(followingStack == precedingStack) else { + let failedTest = FailedTest(error: ModalPresentationError.noDismissalOccurred, + file: file, + line: line) + throw failTest(failedTest) + } + guard modallyForemostFollowing === presenting else { + let failedTest = FailedTest(error: ModalPresentationError.unexpectedModallyForemostFollowingEvent(expected: [presenting], realized: followingStack.suffix(1)), + file: file, + line: line) + throw failTest(failedTest) + } + } + +} + +private func modalStack(preceding controller: UIViewController) -> [UIViewController] { + var stack = [UIViewController]() + var last: UIViewController? = controller + while let _last = last { + stack.append(_last) + last = _last.presentingViewController + } + return stack.reversed() +} + +private func topParent(of controller: UIViewController) -> UIViewController { + var top = controller + while let parent = top.parent { top = parent } + return top +} diff --git a/Sources/LassoTestUtilities/NavigationTesting.swift b/Sources/LassoTestUtilities/NavigationTesting.swift new file mode 100644 index 0000000..c7119bf --- /dev/null +++ b/Sources/LassoTestUtilities/NavigationTesting.swift @@ -0,0 +1,244 @@ +// +//===----------------------------------------------------------------------===// +// +// NavigationTesting.swift +// +// Created by Trevor Beasty on 8/13/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest + +// swiftlint:disable opening_brace + +public extension XCTestCase { + + /// Assert that the event will result in a push in the navigation controller of the previous controller. + /// The previous and pushed controllers receive the full set of lifecycle hooks. + /// CRITICAL: Controllers must be descendants of a UIWindow which is 'key and visible', otherwise, + /// lifecycle hooks will not work. + /// 1) the previous controller must be embedded in a navigation controller + /// 2) the previous controller must be at the top of the navigation stack preceding the event + /// 3) the previous and pushed controllers must be at the top of the navigation stack following the event + /// - Parameter previous: the controller at the top of the navigation stack to be pushed on + /// - Parameter event: the event which triggers the push + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear and viewWillDisappear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear and viewDidDisappear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + /// - Parameter failTest: the side effect of test failure + func assertPushed( + after previous: Previous, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewDidLoad: (Previous, Pushed) -> Void = { _, _ in }, + onViewWillAppear: @escaping (Previous, Pushed) -> Void = { _, _ in }, + onViewDidAppear: @escaping (Previous, Pushed) -> Void = { _, _ in }, + file: StaticString = #file, + line: UInt = #line, + failTest: FailTest = log) throws -> Pushed + { + + guard let nav = previous.navigationController else { + let failedTest = FailedTest(error: NavigationPresentationError.noNavigationEmbedding(target: previous), + file: file, + line: line) + throw failTest(failedTest) + } + guard nav.viewControllers.suffix(1) == [previous] else { + let failedTest = FailedTest(error: NavigationPresentationError.unexpectedTopPrecedingEvent(expected: previous, navigationController: nav), + file: file, + line: line) + throw failTest(failedTest) + } + + do { + return try assertControllerEvent( + from: previous, + to: { + guard let newTop = nav.topViewController else { + throw NavigationPresentationError.unexpectedEmptyStackFollowingEvent(navigationController: nav) + } + guard !(newTop === previous) else { + throw NavigationPresentationError.noPushOccurred(previous: previous) + } + guard nav.viewControllers.suffix(2) == [previous, newTop] else { + throw NavigationPresentationError.unexpectedTopStackFollowingEvent(expected: [previous, newTop], navigationController: nav) + } + return newTop + }, + when: event, + timeout: timeout, + onViewDidLoad: onViewDidLoad, + onViewWillAppear: onViewWillAppear, + onViewDidAppear: onViewDidAppear, + file: file, + line: line + ) + } + catch let error { + switch error { + + case TypeCastError.unexpectedInstanceType(instance: let instance): + let failedTest = FailedTest(error: NavigationPresentationTypeError.unexpectedPushedType(realized: instance), + file: file, + line: line) + throw failTest(failedTest) + + default: + guard let lassoError = error as? LassoError else { + fatalError("should never execute") + } + let failedTest = FailedTest(error: lassoError, + file: file, + line: line) + throw failTest(failedTest) + } + } + } + + /// Assert that the event will result in a new root for the navigation controller, where the new root + /// is the only controller in the navigation stack. The new root receives the full set of lifecycle hooks. + /// CRITICAL: Controllers must be descendants of a UIWindow which is 'key and visible', otherwise, + /// lifecycle hooks will not work. + /// 1) a new root must be set + /// 2) the new root must be the only controller in the navigation stack following the event + /// - Parameter nav: the navigation controller which will be configured + /// - Parameter event: the event which triggers the setting of the root + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + /// - Parameter failTest: the side effect of test failure + func assertRoot( + of nav: UINavigationController, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewDidLoad: (Root) -> Void = { _ in }, + onViewWillAppear: @escaping (Root) -> Void = { _ in }, + onViewDidAppear: @escaping (Root) -> Void = { _ in }, + file: StaticString = #file, + line: UInt = #line, + failTest: FailTest = log) throws -> Root + { + let precedingRoot = nav.viewControllers.first + do { + return try assertControllerEvent( + to: { + guard let root = nav.viewControllers.first, nav.viewControllers == [root] else { + throw NavigationPresentationError.unexpectedStackFollowingRootEvent(navigationController: nav) + } + return root + }, + when: event, + timeout: timeout, + onViewDidLoad: onViewDidLoad, + onViewWillAppear: onViewWillAppear, + onViewDidAppear: onViewDidAppear, + file: file, + line: line + ) + } + catch let error { + switch error { + + case TypeCastError.unexpectedInstanceType(instance: let instance): + if let precedingRoot = precedingRoot, instance === precedingRoot { + let failedTest = FailedTest(error: NavigationPresentationError.oldRootRemainsFollowingRootEvent(oldRoot: precedingRoot, navigationController: nav), + file: file, + line: line) + throw failTest(failedTest) + } + let failedTest = FailedTest(error: NavigationPresentationTypeError.unexpectedRootType(realized: instance), + file: file, + line: line) + throw failTest(failedTest) + + default: + guard let lassoError = error as? LassoError else { + fatalError("should never execute") + } + let failedTest = FailedTest(error: lassoError, + file: file, + line: line) + throw failTest(failedTest) + } + } + } + + /// Assert that the event will result in a pop in the navigation controller. + /// The old and new top controllers receive the full set of lifecycle hooks. + /// CRITICAL: Controllers must be descendants of a UIWindow which is 'key and visible', otherwise, + /// lifecycle hooks will not work. + /// 1) the fromController must be embedded in a navigation controller + /// 2) the fromController must be at the top of the navigation stack preceding the event + /// 3) the toController must be at the top of the navigation stack following the event + /// - Parameter fromController: the controller at the top of the navigation stack preceding the event + /// - Parameter toController: the controller at the top of the navigation stack following the event + /// - Parameter event: the event which triggers the pop + /// - Parameter timeout: maximum time allowance for the event + /// - Parameter onViewDidLoad: hook corresponding to viewDidLoad + /// - Parameter onViewWillAppear: hook corresponding to viewWillAppear and viewWillDisappear + /// - Parameter onViewDidAppear: hook corresponding to viewDidAppear and viewDidDisappear + /// - Parameter file: the file of the caller + /// - Parameter line: the line of the caller + /// - Parameter failTest: the side effect of test failure + func assertPopped( + from fromController: From, + to toController: To, + when event: () -> Void, + timeout: TimeInterval = 1, + onViewWillAppear: @escaping (From, To) -> Void = { _, _ in }, + onViewDidAppear: @escaping (From, To) -> Void = { _, _ in }, + file: StaticString = #file, + line: UInt = #line, + failTest: FailTest = log) throws + { + + guard let nav = fromController.navigationController else { + let failedTest = FailedTest(error: NavigationPresentationError.noNavigationEmbedding(target: fromController), + file: file, + line: line) + throw failTest(failedTest) + } + guard nav.viewControllers.suffix(1) == [fromController] else { + let failedTest = FailedTest(error: NavigationPresentationError.unexpectedTopPrecedingEvent(expected: fromController, navigationController: nav), + file: file, + line: line) + throw failTest(failedTest) + } + + _ = try assertControllerEvent( + from: fromController, + to: { toController }, + when: event, + timeout: timeout, + onViewDidLoad: { _, _ in }, + onViewWillAppear: onViewWillAppear, + onViewDidAppear: onViewDidAppear, + file: file, + line: line + ) + + guard nav.viewControllers.suffix(1) == [toController] else { + let failedTest = FailedTest(error: NavigationPresentationError.unexpectedTopFollowingEvent(expected: toController, navigationController: nav), + file: file, + line: line) + throw failTest(failedTest) + } + + } + +} diff --git a/Sources/LassoTestUtilities/ScreenModule+Testing.swift b/Sources/LassoTestUtilities/ScreenModule+Testing.swift new file mode 100644 index 0000000..db22094 --- /dev/null +++ b/Sources/LassoTestUtilities/ScreenModule+Testing.swift @@ -0,0 +1,78 @@ +// +//===----------------------------------------------------------------------===// +// +// ScreenModule+Testing.swift +// +// Created by Trevor Beasty on 10/17/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit +import Lasso + +extension ScreenModule { + + public static func mockScreenFactory(mockScreen: MockScreen) -> ScreenFactory { + return ScreenFactory { initialState in + let mockStore = MockLassoStore(with: initialState ?? defaultInitialState) + let mockController = UIViewController() + mockScreen.mockStore = mockStore + mockScreen.mockController = mockController + return AnyScreen(mockStore, mockController) + } + } + + public static func mockControllerScreenFactory(configure: ((ConcreteStore) -> Void)? = nil) -> ScreenFactory { + return ScreenFactory { initialState in + let store = ConcreteStore(with: initialState ?? defaultInitialState) + configure?(store) + let mockController = MockController(store: store.asViewStore()) + return AnyScreen(store, mockController) + } + } + +} + +public func mockScreenFactory(mockScreen: MockScreen) -> Module.ScreenFactory { + return Module.mockScreenFactory(mockScreen: mockScreen) +} + +public func mockControllerScreenFactory(configure: ((Module.ConcreteStore) -> Void)? = nil) -> Module.ScreenFactory { + return Module.mockControllerScreenFactory(configure: configure) +} + +public class MockScreen { + + public internal(set) var mockStore: MockLassoStore? + public internal(set) var mockController: UIViewController? + + public init() { } + +} + +public final class MockController: UIViewController, LassoView { + + public let store: ViewStore + + public init(store: ViewStore) { + self.store = store + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { fatalError() } + +} + +extension ScreenModule { + + public typealias MockController = LassoTestUtilities.MockController + +} diff --git a/Sources/LassoTestUtilities/StoreTesting+ThenAssertion.swift b/Sources/LassoTestUtilities/StoreTesting+ThenAssertion.swift new file mode 100644 index 0000000..97f43c5 --- /dev/null +++ b/Sources/LassoTestUtilities/StoreTesting+ThenAssertion.swift @@ -0,0 +1,99 @@ +// +//===----------------------------------------------------------------------===// +// +// StoreTesting+ThenAssertion.swift +// +// Created by Trevor Beasty on 9/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation +import XCTest +import Lasso + +public typealias ThenAssertion = (_ thenable: EmittedValues, _ file: StaticString, _ line: UInt) -> Void + +public func assert(_ assert: @escaping (EmittedValues) -> Void) -> ThenAssertion { + return { thenable, _, _ in + assert(thenable) + } +} + +public func singleState(_ state: Store.State) -> ThenAssertion where Store.State: Equatable { + return { thenable, file, line in + guard assertSingleElement(in: thenable.states, elementNames: stateNames, file: file, line: line) else { return } + assertEqual(realized: thenable.states[0], expected: state, file: file, line: line) + } +} + +public func singleUpdate(_ update: @escaping (inout Store.State) -> Void) -> ThenAssertion where Store.State: Equatable { + return { thenable, file, line in + guard assertSingleElement(in: thenable.states, elementNames: stateNames, file: file, line: line) else { return } + var expected = thenable.previousState + update(&expected) + assertEqual(realized: thenable.states[0], expected: expected, file: file, line: line) + } +} + +public func state(_ state: Store.State) -> ThenAssertion where Store.State: Equatable { + return { thenable, file, line in + guard let state = thenable.states.last else { + XCTFail("no states emitted", file: file, line: line) + return + } + assertEqual(realized: state, expected: state, file: file, line: line) + } +} + +public func update(_ update: @escaping (inout Store.State) -> Void) -> ThenAssertion where Store.State: Equatable { + return { thenable, file, line in + guard let state = thenable.states.last else { + XCTFail("no states emitted", file: file, line: line) + return + } + var expected = thenable.previousState + update(&expected) + assertEqual(realized: state, expected: expected, file: file, line: line) + } +} + +public func outputs(_ outputs: Store.Output...) -> ThenAssertion where Store.Output: Equatable { + return { thenable, file, line in + assertEqual(realized: thenable.outputs, expected: outputs, file: file, line: line) + } +} + +public func sideEffects(_ sideEffect: @escaping () -> Void) -> ThenAssertion where Store.State: Equatable { + return { _, _, _ in + sideEffect() + } +} + +private func assertEqual(realized: A, expected: A, file: StaticString, line: UInt) { + if realized != expected { + XCTFail("realized differs from expected" + messageDescribingDiffs(realized, expected), file: file, line: line) + } +} + +private func assertEqual(realized: [A], expected: [A], file: StaticString, line: UInt) { + if realized != expected { + XCTFail("realized differs from expected" + messageDescribingDiffs(realized, expected), file: file, line: line) + } +} + +private let stateNames = ("state", "states") + +private func assertSingleElement(in array: [A], elementNames: (String, String), file: StaticString, line: UInt) -> Bool { + if array.count != 1 { + XCTFail("expected 1 \(elementNames.0), realized \(array.count) \(elementNames.1)", file: file, line: line) + } + return array.count == 1 +} diff --git a/Sources/LassoTestUtilities/StoreTesting+WhenStatement.swift b/Sources/LassoTestUtilities/StoreTesting+WhenStatement.swift new file mode 100644 index 0000000..ef9dee4 --- /dev/null +++ b/Sources/LassoTestUtilities/StoreTesting+WhenStatement.swift @@ -0,0 +1,34 @@ +// +//===----------------------------------------------------------------------===// +// +// StoreTesting+WhenStatement.swift +// +// Created by Trevor Beasty on 9/30/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation +import XCTest +import Lasso + +public typealias WhenStatement = (_ dispatchAction: (Action) -> Void) -> Void + +public func actions(_ actions: Action...) -> WhenStatement { + return { dispatchAction in + actions.forEach(dispatchAction) + } +} + +public func sideEffects(_ sideEffects: @escaping () -> Void) -> WhenStatement { + return { _ in + sideEffects() + } +} diff --git a/Sources/LassoTestUtilities/StoreTesting.swift b/Sources/LassoTestUtilities/StoreTesting.swift new file mode 100644 index 0000000..cda6f6d --- /dev/null +++ b/Sources/LassoTestUtilities/StoreTesting.swift @@ -0,0 +1,135 @@ +// +//===----------------------------------------------------------------------===// +// +// StoreTesting.swift +// +// Created by Trevor Beasty on 9/3/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation +import Lasso +import XCTest + +public struct EmittedValues { + let store: Store + let teardown: () -> Void + public let previousState: Store.State + public let states: [Store.State] + public let outputs: [Store.Output] +} + +public protocol LassoStoreTesting { + associatedtype Store: ConcreteStore + typealias State = Store.State + typealias Action = Store.Action + typealias Output = Store.Output + var test: TestFactory { get } +} + +public struct TestFactory { + + public let initialState: Store.State + private let setUpStore: (Store) -> Void + private let tearDown: () -> Void + + public init(initialState: Store.State, setUpStore: @escaping (Store) -> Void, tearDown: @escaping () -> Void) { + self.initialState = initialState + self.setUpStore = setUpStore + self.tearDown = tearDown + } + + public func given(_ given: @escaping (inout Store.State) -> Void) -> TestNode.When { + return TestNode.When { + var state = self.initialState + given(&state) + let store = Store(with: state) + self.setUpStore(store) + return Whenable(store: store, teardown: self.tearDown) + } + } + +} + +public enum TestNode { + + public struct When { + let runPrevious: () -> Whenable + + public func when(_ statement: @escaping WhenStatement) -> Then { + return Then { + let info = self.runPrevious() + var states = [Store.State]() + var outputs = [Store.Output]() + info.store.observeState { _, new in states.append(new) } + info.store.observeOutput { outputs.append($0) } + let previousState = states.removeFirst() + statement(info.store.dispatchAction) + return EmittedValues(store: info.store, teardown: info.teardown, previousState: previousState, states: states, outputs: outputs) + } + } + + public func execute() { + let info = runPrevious() + info.teardown() + } + + } + + public class Then { + let runPrevious: () -> EmittedValues + let didExecute: () -> Bool + var file: StaticString? + var line: UInt? + + init(runPrevious: @escaping () -> EmittedValues) { + var didExecute = false + self.runPrevious = { + defer { didExecute = true } + return runPrevious() + } + self.didExecute = { return didExecute } + } + + deinit { + if !didExecute(), let file = file, let line = line { + XCTFail("CRITICAL: 'then assertion' never executed", file: file, line: line) + } + } + + public func then(_ assertions: ThenAssertion..., file: StaticString = #file, line: UInt = #line) -> When { + self.file = file + self.line = line + return When { + let info = self.runPrevious() + assertions.forEach { $0(info, file, line) } + return Whenable(store: info.store, teardown: info.teardown) + } + } + + } + +} + +struct Whenable { + let store: Store + let teardown: () -> Void +} + +private func valuesFor(_ initialValue: A, _ updates: [(inout A) -> Void]) -> [A] { + var current = initialValue + var values = [A]() + for update in updates { + update(¤t) + values.append(current) + } + return values +} diff --git a/Sources/LassoTestUtilities/TypeCast.swift b/Sources/LassoTestUtilities/TypeCast.swift new file mode 100644 index 0000000..7e00324 --- /dev/null +++ b/Sources/LassoTestUtilities/TypeCast.swift @@ -0,0 +1,62 @@ +// +//===----------------------------------------------------------------------===// +// +// TypeCast.swift +// +// Created by Trevor Beasty on 9/4/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest + +extension XCTestCase { + + internal func typeCast(_ instance: @autoclosure () -> Instance?, file: StaticString = #file, line: UInt = #line) throws -> Type { + + guard let instance = instance() else { + throw TypeCastError.failedToResolveInstance + } + + guard let casted = instance as? Type else { + throw TypeCastError.unexpectedInstanceType(instance: instance) + } + + return casted + } + +} + +extension UIViewController { + + public func firstChildOfType(file: StaticString = #file, line: UInt = #line) throws -> A { + guard let child = children.first(where: { $0 is A }) as? A else { + let failedTest = FailedTest(error: ChildViewControllerError.controllerDoesNotHaveChildOfType(controller: self, childType: A.self), + file: file, + line: line) + throw log(failedTest) + } + return child + } + +} + +enum ChildViewControllerError: LassoError { + case controllerDoesNotHaveChildOfType(controller: UIViewController, childType: ChildType) + + func message(verbose: Bool) -> String { + switch self { + + case let .controllerDoesNotHaveChildOfType(controller: controller, childType: childType): + return "Expected controller \(String(describing: controller)) to have child of type \(String(describing: childType))" + } + } + +} diff --git a/Sources/LassoTestUtilities/ValueDiffing.swift b/Sources/LassoTestUtilities/ValueDiffing.swift new file mode 100644 index 0000000..7cb573f --- /dev/null +++ b/Sources/LassoTestUtilities/ValueDiffing.swift @@ -0,0 +1,74 @@ +// +//===----------------------------------------------------------------------===// +// +// ValueDiffing.swift +// +// Created by Trevor Beasty on 9/9/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +func messageDescribingDiffs(_ realized: A, _ expected: A) -> String { + return diffsHeader + stringDescribing(diff(realized: realized, expected: expected)) +} + +func messageDescribingDiffs(_ realized: [A], _ expected: [A]) -> String { + guard realized.count == expected.count else { + return "\n\nCOUNTS DIFFER:\n realized count --> \(realized.count)\n expected count --> \(expected.count)" + } + if realized.count == 1 { + return messageDescribingDiffs(realized[0], expected[0]) + } + return diffsHeader + zip(realized, expected).enumerated() + .compactMap({ + let diffs = diff(realized: $0.1.0, expected: $0.1.1) + guard !diffs.isEmpty else { return nil } + return "\nindex \($0.0)\n\(stringDescribing(diffs))" + }) + .joined() +} + +func diff(realized: A, expected: A) -> [Diff] { + return zip(stringsDescribing(realized), stringsDescribing(expected)) + .compactMap({ + if $0.0.value != $0.1.value { + return Diff(key: $0.0.key, type: $0.0.type, realized: $0.0.value, expected: $0.1.value) + } + return nil + }) +} + +struct Diff: Equatable { + let key: String? + let type: String + let realized: String + let expected: String +} + +private func stringsDescribing(_ value: A) -> [(key: String?, type: String, value: String)] { + return Mirror(reflecting: value) + .children + .map({ + let _type = type(of: $0.value) + return ($0.label, String(describing: _type), String(describing: $0.value)) + }) +} + +private func stringDescribing(_ diffs: [Diff]) -> String { + return diffs + .map({ + return "\($0.key ?? "_ "): \($0.type) --> \($0.realized) != \($0.expected)" + }) + .joined(separator: "\n") +} + +private let diffsHeader = "\n\nDIFFS: _ : T --> realized != expected\n\n" diff --git a/Sources/LassoTestUtilities/VerboseLogging.swift b/Sources/LassoTestUtilities/VerboseLogging.swift new file mode 100644 index 0000000..6309722 --- /dev/null +++ b/Sources/LassoTestUtilities/VerboseLogging.swift @@ -0,0 +1,69 @@ +// +//===----------------------------------------------------------------------===// +// +// VerboseLogging.swift +// +// Created by Trevor Beasty on 12/10/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import UIKit + +func describeViewControllerHierarchy(_ controller: UIViewController) -> String { + return modalStack(from: controller.rootController) + .map({ "<\(describe($0))>" }) + .joined(separator: " --> ") +} + +private extension UIViewController { + + var rootController: UIViewController { + var root = self + while let presenting = root.presentingViewController { + root = presenting + } + return root + } + +} + +private func modalStack(from controller: UIViewController) -> [UIViewController] { + var stack = [controller] + var current = controller + while let presented = current.presentedViewController { + stack.append(presented) + current = presented + } + return stack +} + +private func describe(_ controller: UIViewController) -> String { + let embedded: [UIViewController] + if let navigationController = controller as? UINavigationController { + embedded = navigationController.viewControllers + } + else { + embedded = [] + } + if embedded.isEmpty { + return className(controller) + } + else { + let embeddedDescription = embedded + .map({ return className($0) }) + .joined(separator: ", ") + return "\(className(controller)) : [\(embeddedDescription)]" + } +} + +private func className(_ value: A) -> String { + return String(describing: type(of: value)) +} diff --git a/Sources/LassoTestUtilities/XCTestCase+Mockable.swift b/Sources/LassoTestUtilities/XCTestCase+Mockable.swift new file mode 100644 index 0000000..3cc460c --- /dev/null +++ b/Sources/LassoTestUtilities/XCTestCase+Mockable.swift @@ -0,0 +1,31 @@ +// +//===----------------------------------------------------------------------===// +// +// XCTestCase+Mockable.swift +// +// Created by Steven Grosmark on 12/11/19. +// +// +// This source file is part of the Lasso open source project +// +// https://github.com/ww-tech/lasso +// +// Copyright © 2019-2020 WW International, Inc. +// +//===----------------------------------------------------------------------===// +// + +import XCTest +import Lasso + +#if swift(>=5.1) +extension XCTestCase { + + public func mock(_ mockable: Mockable.Projected, with mock: @autoclosure () -> T?) { + mockable.mock(with: mock()) + addTeardownBlock { + mockable.reset() + } + } +} +#endif diff --git a/docs/Dependency Injection case study.md b/docs/Dependency Injection case study.md new file mode 100644 index 0000000..29b1251 --- /dev/null +++ b/docs/Dependency Injection case study.md @@ -0,0 +1,120 @@ +## Dependency Injection case study + + + +How to deal with legacy classes + +That's a great question. + +#### Push.Manager + +The `Push.Manager` is set up for mocking, but not in a way that would be very useful to you. + +What you really want to mock is your interface with the manager, rather than the manager's interface with an HTTP service and data model. + +Since that manager is a class with no protocol, I think you have two options: +1. make a mock subclass, and override the functions you need in your store +2. make a protocol that represents what you need in the store, and then create a mock that conforms to that protocol. + +There's pros and cons to both approaches, but personally I would lean towards a protocol. Since the manager is a pretty big class that does a lot, if you overrode a small subset of those functions, there's a risk that some other weird side-effects will happen. In other words, the mock shouldn't really have any functionality in it, except for what is needed to pretend to be a service for your store. + +So, what you want to do in my opinion, is to collect all methods you need, and copy/paste those function signatures into a new protocol. + +Here's an example with just the `setupReminder` function - this would all go in your reminder store file: +```swift +// set up a base protocol for the reminder store +protocol RemiderServiceProtocol { + func setupReminder(service: HTTPProtocol, for date: Date, weekday: Weekday, notificationType: String, completion: (() -> Void)?) +} + +// set up an extension to handle default parameters +extension RemiderServiceProtocol { + func setupReminder(for date: Date, weekday: Weekday, notificationType: String, completion: (() -> Void)?) { + setupReminder(service: Services.notifications, for: date, weekday: weekday, notificationType: notificationType, completion: completion) + } +} + +// make the push manager conform to the protocol: +extension Push.Manager: RemiderServiceProtocol { } + +// in the store, just use the protocol +public final class ReminderStore: LassoStore { + var service: RemiderServiceProtocol = Push.shared + ... +} +``` + +Then, create your mock: +```swift +final class MockReminderService: RemiderServiceProtocol { + + // capture the completion handler: + var setupReminderCompletion: (() -> Void)? + + // implement the protocol function, and just grab the completion: + func setupReminder(service: HTTPProtocol, for date: Date, weekday: Weekday, notificationType: String, completion: (() -> Void)?) { + setupReminderCompletion = completion + } +} +``` + +Inject it when creating your store: +```swift +mockService = MockReminderService() +store.service = mockService +``` + +Then, in your tests: +```swift +// when you run your unit test, you can then make sure the service function was called: +store.dispatchAction(.submit) +XCTAssertNotNil(mockService.setupReminderCompletion) + +// and then call the completion, and test for how the store handles it: +mockService.setupReminderCompletion?() +XCTAssertOutputs([.dismiss]) +``` + +#### PreAuthorizationAlert + +Similar to the Push manager, you just want to test your interface with the pre-auth alert, and ignore what happens with UIKit. + +Existing: + +```swift +public enum PreAuthorizationAlert { + + public static func presentPreAuthorizationAlert() { ... } + +} +``` + + + +This is a little easier to deal with. Since it's a case-less enum, I would change it to be a protocol / struct - something like this (only the public funcs would go into the protocol): + +```swift +protocol PreAuthorizationAlertProtocol { + public func presentPreAuthorizationAlert() +} + +struct PreAuthorizationAlert: PreAuthorizationAlertProtocol { + + public static let shared: PreAuthorizationAlertProtocol = PreAuthorizationAlert() + + public func presentPreAuthorizationAlert() { + ... + } +} +``` + +You can do the same mocking as described above for the Push manager. +```swift +public final class ReminderStore: LassoStore { + var preAuthAlert: PreAuthorizationAlertProtocol = PreAuthorizationAlert.shared + ... +} +``` +etc. + +I think this is the longest review comment I've ever written. \ No newline at end of file diff --git a/docs/Lasso-FlowsIntro.md b/docs/Lasso-FlowsIntro.md new file mode 100644 index 0000000..62aeed9 --- /dev/null +++ b/docs/Lasso-FlowsIntro.md @@ -0,0 +1,317 @@ +# Lasso: An introduction to Flows +Most all iOS applications have some sense of navigation - a user will progress from screen to screen as they utilize the features of an app. In iOS development, the class `UIViewController` is primarily responsible for such navigation. Transitions from screen to screen are realized as side effects in the view controller hierarchy (navigation hierarchy). + +Typically, view controllers are responsible for creating and presenting other controllers. This results in lots of coupling between controllers. As a result, it becomes very difficult to: + +* Reuse similar sequences of screens in varying contexts +* Easily modify an app's high level navigation structure +* Effectively implement complex relationships across screens +* Effectively test related sequences of screens + +Lasso solves all of these problems via its `Flow` abstraction. + +## What is a Flow? + +A `Flow` represents a feature - or area - of an app, and is commonly composed of a collection of `Screens`. For example, a "new user" flow might be composed of a series of one-time informational screens followed by a single "let's get started" screen. + + + +A `Flow` is instantiated and started within an appropriate context of a navigation hierarchy (e.g., a "sign up" flow might be presented on a menu screen, or a "welcome" flow might be pushed onto a navigation stack). The `Flow` starts by creating its initial `Screen`, and listens for `Output` signals. As `Outputs` arrive, the `Flow` decides what to do with them - it can create and place another `Screen` into the navigation hierarchy, emit its own `Output` (when an event occurs that is more appropriately handled elsewhere), or whatever is appropriate for the `Flow`. + +Since `Screens` and `Flows` are encapsulated modules with discrete entry and exit points, it's quite easy and common for a `Flow` to manage both `Screens` _and_ `Flows`. + + + +From a functional perspective, `Flows` are a mechanism for composition - they aggregate smaller independent units of behavior into larger ones. `Flows` themselves may also be composed. This makes it possible to define the views that constitute an application as a hierarchy of `Flows`. Describing an app's features in this way drastically reduces complexity and increases maintainability. + +## Implementing a Flow +Business has crunched the numbers, and they've decided that we need to provide a short tutorial to our members to enhance new member onboarding. The feature calls for a series of views with images and text describing the program. A user completes the tutorial by progressing forward through all the views. + + + +How can we implement this the Lasso way? First, we must define the structural types of our `Flow`. + +The types that constitute a `Flow's` structure are defined in a `FlowModule`. This is simply a convenience for grouping the member types of a specific `Flow` - namely, its `Output` and `RequiredContext`. + +`Output` defines the messages that a `Flow` can propagate outward to some unknown higher level object. These `Output` messages constitute the modular boundary of the `Flow`. + +`RequiredContext` describes the type of `UIViewController` artifact a `Flow` requires to make side effects in the navigation hierarchy. A `Flow` uses this object's native API to present and dismiss `Screens`. `RequiredContext` makes it possible to implement the behavior of a set of `Screens` agnostic to any other (unknown) `Screens` that may precede or follow them. + +```swift +enum TutorialFlowModule: FlowModule { + + enum Output: Equatable { + case didFinish + case didPressSkip + } + + typealias RequiredContext = UINavigationController + +} +``` +There are several user actions our tutorial `Flow` must respond to. On the first screen, we see a "next" and a "skip" button. Our `Flow` must execute some logic in response to the user pressing these buttons. It will handle the "next" button press by creating and showing the second screen. However, it is not responsible for handling the "skip" button press - some higher level object will be responsible for driving the app in response to this event. This logical design is realized by including `didPressSkip` as an `Output` case. When the user presses "skip", our `Flow` will simply emit `didPressSkip` as an `Output`. + +On the second screen, we see a "done" button. When this button is pressed, our `Flow` is finished - it has no further screens to show. This is embodied by the `didFinish` case of `Output`. Our `Flow` will emit this message when the "OK" button is pressed. As before, some higher level object will need to respond to this message, progressing the application according to its broader business logic. + +We would like to create the effect that the user is progressing forwards through the tutorial screens. To satisfy this, we will use the "push" API on `UINavigationController`. Our `Flow` needs a way to specify this requirement - our `Flow` must be _started_ in a navigation controller and be able to make "push" calls on that navigation controller. This is achieved by stating that the `RequiredContext` is of type `UINavigationController`. + +We are now ready to implement our `Flow`: + +```swift +class TutorialFlow: Flow { + + override func createInitialController() -> UIViewController { + // create, configure and return the first view controller + } + +} +``` +`TutorialFlow` inherits from `Flow` and is generic over `TutorialFlowModule`. We begin by overriding its `createInitialController()` method. This is the entry point for a `Flow` implementation. The `UIViewController` returned by this method will be placed into the navigation hierarchy when the `Flow` is _started_. + +The screen we'll use in `createInitialController` will be from `TutorialScreenModule`. In fact, this module is set up so that we can use it for the second screen of the tutorial as well. The important parts of this module are: + +```swift +enum TutorialScreenModule: ScreenModule { + + struct State: Equatable { + let title: String + let body: String + let buttonTitles: [String] + } + + enum Output: Equatable { + case didPressButton(index: Int) + } + + ... +} +``` +We can create conveniences that describe the two screens of `TutorialFlow` in terms of `TutorialScreenModule.State`. + +```swift +extension TutorialScreenModule.State { + + static var welcome: State { + return State(title: "Welcome!", body: "...", buttonTitles: ["Skip", "Next"]) + } + + static var finish: State { + return State(title: "That was fun!", body: "...", buttonTitles: ["OK"]) + } + +} +``` +We are now ready to implement `createInitialController()`, using `TutorialScreenModule` to create and return the "welcome" screen. + +```swift +override func createInitialController() -> UIViewController { + let welcomeState = TutorialScreenModule.State.welcome + let introScreen = TutorialScreenModule.createScreen(with: welcomeState) + return introScreen.controller +} +``` +It is important to note that a `Flow` is retained by the view controller returned in `createInitialController()`. This ARC consideration is handled behind the scenes by creating a strong reference from the 'initial view controller' to the `Flow`. This means that a `Flow` will live as long as its initial controller. + +Next, we must implement the second screen in this `Flow`. We want to show the "finish" screen when the user presses the "next" button. This is accomplished by observing the `Output` of the welcome screen and responding appropriately. + +```swift +class TutorialFlow: Flow { + + override func createInitialController() -> UIViewController { + let welcomeState = TutorialScreenModule.State.welcome + let introScreen = TutorialScreenModule.createScreen(with: welcomeState) + + introScreen.observeOutput({ [weak self] output in + guard let self = self else { return } + switch output { + + case .didPressButton(index: let index): + switch index { + case 0: + // handle skip + + case 1: + let finishController = self.assembleFinishController() + self.context?.pushViewController(finishController, animated: true) + + default: + return + } + } + }) + + return introScreen.controller + } + + private func assembleFinishController() -> UIViewController { + let finishState = TutorialScreenModule.State.finish + let finishScreen = TutorialScreenModule.createScreen(with: finishState) + return finishScreen.controller + } + +} +``` +`TutorialFlow` observes the `Output` of the "welcome" screen, capturing `self` weakly in order to avoid retain cycles. When `TutorialFlow` receives the `didPressButton` message from that screen, it must evaluate the associated `index` value to determine which button was tapped. We know that this screen has two buttons, "skip" and "next". The indices of these buttons correspond to the `buttonTitles` property on `State` - "skip" corresponds to an index value of 0, and "next" to a value of 1. + +The "finish" screen is constructed in the same manner as the welcome screen. It is placed into the navigation hierarchy by calling `pushViewController(:animated:)` on the local `context: UINavigationController?`. By definition, the `context` property always reflects the `RequiredContext` type. It is an optional because it is weakly owned by the `Flow`, which is necessary to avoid retain cycles. + +We can now finish our `TutorialFlow` implementation. When the user clicks the "OK" button on the finish screen, the tutorial is completed. + +```swift +private func assembleFinishController() -> UIViewController { + let finishState = TutorialScreenModule.State.finish + let finishScreen = TutorialScreenModule.createScreen(with: finishState) + + finishScreen.observeOutput({ [weak self] output in + guard let self = self else { return } + switch output { + + case .didPressButton(index: let index): + switch index { + + case 0: + self.dispatchOutput(.didFinish) + + default: + return + } + }) + + return finishScreen.controller +} +``` +When the "finish" screen emits the `didPressButton` output, `TutorialFlow` does not execute any side effects. Instead, it simply emits the `didFinish` output via its `dispatchOutput(:)` method. `TutorialFlow` is not aware of what may happen following the tutorial, but it is responsible for communicating that the tutorial was completed. It will be the role of some other object to drive the app when the tutorial completes. + +The "skip" button press on the welcome screen can be handled in a similar fashion. Our `TutorialFlow` is only responsible for propagating this event as an output. + +```swift +introScreen.observeOutput({ [weak self] output in + guard let self = self else { return } + switch output { + + case .didPressButton(index: let index): + switch index { + case 0: + // handle skip + self.dispatchOutput(.didPressSkip) + + ... + } +}) +``` + +## Starting a Flow +Now that we have defined our `Flow`, we need some way of _starting_ it. We would like to present the screens that constitute our `Flow`, making the requisite side effects in the navigation hierarchy. + +`TutorialFlow` has a `RequiredContext` of `UINavigationController`. This is equivalent to the `TutorialFlow` saying: + +"I need some UINavigationController to work with. It doesn't matter if I start at the root of that navigation controller, or on top of a navigation stack with many controllers in it. As long as I'm provided a starting point in some navigation controller, and am able to make calls on it, I will be able to do my job" + +`TutorialFlow` has been defined in isolation and does not currently participate in the broader app. We can potentially show it in many different places with varying navigation hierarchy characteristics. + +We could start `TutorialFlow` in some navigation controller, pushing its initial controller on top of the navigation stack. + +```swift +let tutorialFlow = TutorialFlow() +tutorialFlow.start(with: pushed(in: navigationController)) +``` + +Alternatively, we could start `TutorialFlow` at the root of some navigation controller, removing any view controllers currently in its stack. + +```swift +let tutorialFlow = TutorialFlow() +tutorialFlow.start(with: root(of: navigationController)) +``` + +Here, `root(of:)` and `pushed(in:)` are utilities that return a `ScreenPlacer`, a novel Lasso type. The `ScreenPlacer` abstraction is critical to `Flow` modularity - `ScreenPlacer` bridges the gap between UIKit and `Flows`, allowing us to _start_ a sequence of screens defined in isolation. It is important to understand the mechanics and intuition of the `ScreenPlacer` type. + +### Understanding `ScreenPlacer` +A `ScreenPlacer` creates side effects in the navigation hierarchy. Specifically, its job is to _place_ a single `UIViewController` into the navigation hierarchy. `ScreenPlacer` has a notion of "navigation context" - it describes the resulting context of the _placed_ controller. A `ScreenPlacer` does not reveal how such placement will occur. This enables `ScreenPlacer` clients to be agnostic to the details of placement. + +`ScreenPlacer` is a general structure for which any number of instances may be defined. This structure is flexible enough to satisfy all navigation hierarchy use cases possible in `UIKit`. + +To understand `ScreenPlacer` more deeply, we must first examine the `Flow` base class. + +```swift +open class Flow { + + public private(set) weak var context: Module.RequiredContext? + + public func start(with placer: ScreenPlacer) { ... } + +} +``` +Here, we see that the `start(with:)` method requires a `ScreenPlacer` argument. The passed-in `ScreenPlacer` must be generic over the `RequiredContext` of the `Flow`. We also see that `Flow` holds a weak reference to its context, which is of type `RequiredContext`. + +`ScreenPlacer` is simply a value wrapper over a`place(:)` function. + +```swift +public struct ScreenPlacer { + + public func place(_ viewController: UIViewController) -> PlacedContext { ... } + +} +``` +Conceptually, a `ScreenPlacer` abstracts away the specifics of making side effects in the navigation hierarchy. A `ScreenPlacer` client only needs to provide the `UIViewController` to be _placed_ without caring about how that placement will occur. The `place(:)` function returns the `PlacedContext` - an object reflecting the navigation hierarchy that the `UIViewController` was placed into. As seen in the definition above, the `PlacedContext` is simply some `UIViewController` type. The returned `PlacedContext` object can be used to execute subsequent navigation hierarchy side effects. + +When _started_, a `Flow` _places_ its initial controller into the navigation hierarchy by calling `place(:)` on the passed-in `ScreenPlacer`. The `PlacedContext` object returned by `place(:)` is then written to `context`. The `Flow` can make calls on this object to show other screens later on. + +We can now revisit the `pushed(in:)` placer and study it in more detail, outlining its implementation details. + +```swift +let tutorialFlow = TutorialFlow() +tutorialFlow.start(with: pushed(in: navigationController)) +``` + +The `ScreenPlacer` created by `pushed(in:)` _places_ the initial controller of `TutorialFlow` into the navigation hierarchy by calling the native method `pushViewController(:animated:)` on the passed-in navigation controller. The `pushed(in:)` placer then returns the passed-in navigation controller as the `PlacedContext`. This navigation controller is set as `TutorialFlow'`s `context`. `TutorialFlow` executes subsequent navigation hierarchy side effects by making calls on this `context`. In the case of `TutorialFlow`, we know that it will eventually call `pushViewController(:animated)` on its `context` in order to show the "finish" screen. + +A compilation error will occur if we attempt to start a `Flow` with an inappropriate `ScreenPlacer`: + +```swift +let tutorialFlow = TutorialFlow() +tutorialFlow.start(with: presented(on: viewController)) // ERROR +``` + +Here, `presented(on:)` returns a `ScreenPlacer`. This means that `TutorialFlow` will be provided a context object of type `UIViewController`. This is inadequate - we know that `TutorialFlow` needs to be able to push controllers onto a navigation stack, requiring a context object of type `UINavigationController`. + +This compilation failure is incredibly valuable - our code will not compile if we violate the explicit requirements of a `Flow`. `Flows` can thus be chained together with confidence - the compiler will tell us when we have broken a screen sequence. + +### ScreenPlacers and UIViewController Embeddings + +`ScreenPlacers` also support view controller embeddings. In Lasso, view controller embeddings are composable with respect to `ScreenPlacers` - an embedding can be "chained along" to any `ScreenPlacer` instance. Intuitively, if I have some `ScreenPlacer` instance, I can place some container view controller with that placer. I can then place some other view controller into that container. This is precisely how `ScreenPlacer` embeddings work. + +We could start `TutorialFlow` in a modally presented navigation controller. + +```swift +let tutorialFlow = TutorialFlow() +tutorialFlow.start(with: presented(on: viewController).withNavigationEmbedding()) +``` + +We could even start `TutorialFlow` as one of many tabs in a `UITabBarController`. Here, `TutorialFlow` is placed as the second of two tabs. + +```swift +let tutorialFlow = TutorialFlow() +let tabPlacers: [ScreenPlacer] = tabBarEmbedding(UITabBarController(), tabsCount: 2) +tutorialFlow.start(with: tabPlacers[1].withNavigationEmbedding()) +``` + +We have seen many built-in `ScreenPlacer` conveniences. Lasso contians many such conveniences covering typical UIKit use cases. Lasso clients are not limited to this built-in set of placers - the `ScreenPlacer` API is completely extensible. Clients can create custom extensions as desired in support of custom containers and less common use cases. + +## What's the Point? +In concert, `Flows` and `ScreenPlacers` are the ultimate tools for describing and implementing an application's sequences of screens. + +Lasso flips the implicit model provided by Apple on its head. In typical iOS development, view controllers are responsible for creating and presenting other controllers. This results in lots of coupling between controllers, which destroys modularity and makes reuse / testing very difficult. As new features are added, the codebase inevitably warps into a monolith. + +In Lasso, `Flows` encapsulate sequences of `Screens`. Thanks to `ScreenPlacers`, these `Flows` can be started anywhere in the application. What's more, because both `Screens` and `Flows` are explicitly modular, they can be freely composed, all the while maintaining modularity of the resulting higher level objects. This design pattern results in minimal coupling and scales well as the feature set grows. + +Thus, Lasso moves us from a world with inherent coupling to one with inherent modularity. This quality incurs many benefits: + +* Reusing related collections of `Screens` is trivial +* Related collections of `Screens` can be effectively tested in isolation +* Construction and mocking is clean +* Composition is at our fingertips: we can create all types of new behaviors by aggregating smaller, existing ones +* Top level application code is much more expressive / maintainable: where we used to have references to low level controllers, we now have references to higher level abstractions that encapsulate feature sets + +### Is that All? + +We have only begun to scratch the surface: `Stores`, `Screens`, and `Flows` can be wielded together in a surprising number of ways, giving the developer precision control over the logical design of their application. What's more, Lasso's companion library, `LassoTestUtilities`, provides a mechanism for writing expressive and succinct `Flow` unit tests. These topics will be treated in future articles. \ No newline at end of file diff --git a/docs/Lasso-Introduction-part1.md b/docs/Lasso-Introduction-part1.md new file mode 100644 index 0000000..281d5e5 --- /dev/null +++ b/docs/Lasso-Introduction-part1.md @@ -0,0 +1,463 @@ +# Lasso: Introducing a new architectural framework for iOS + +### Problem statement + +Without a set of structural principles, it's easy for an application's code base to become difficult to reason about and maintain. In particular, there's a good chance that you'll eventually start to see these kinds of problems: + +- tight coupling of components, making it hard to change or test things +- business logic living in strange places, making it difficult to modify, reuse, debug, and test existing code +- view presentation choices made in strange places make it hard to refactor, reorganize, and test flows +- inconsistent organization across the team makes it hard to cross-contribute + +### Use an architecture! + +If you're starting a new project, then you have the luxury of putting some sort of architectural standard in place *before* you start writing any code. If you already have a code base set up (and are probably experiencing some of the above issues), then it will be a little more difficult to adopt something new. + +What are your choices? Well, you know you need *some* kind of guiding structure, so you just need to pick one of the many options: MVC, MVVM, VIPER, MVI, or the topic of this article: **Lasso**! + +### Introducing Lasso + +Lasso is an iOS application architecture for building discrete, reusable, and testable components both big and small–from single one-off screens to complex flows and high-level application structures. + +There are two main areas of writing an application where Lasso aims to help: creating individual view controllers and managing the interplay between your view controllers. In Lasso, we call these these **screens** and **flows**. + +## Screens + +We generally think of a screen as a single page/view in an app. For example: a login view, a contacts list view, or an audio settings view. + +In Lasso, a `Screen` is the collection of types used to implement a single view: + +- `Store` - the business logic +- `Action` - a message [usually] sent from a view controller to the Store in response to some sort of user interaction +- `State` - the content to be presented in the view controller +- `Output` - a message sent from a screen that enables composing screens into flows + +A `View` isn't a formal type in Lasso, but we do talk about them a lot. When we do, we almost always mean a `UIViewController`. + +Drawing this up in a diagram allows us to see how these types work together: + +

+You should notice what looks like a **unidirectional data flow**. The `View` translates user interactions into `Action` messages, which are sent to the `Store`. The `Store` processes the `Action` messages and updates the `State`. Changes to the `State` are sent back to the `View` as they happen. + +### Building a login screen + +To provide a context for talking about how to create and show a `Screen`, we'll use the task of creating a simple login screen. + +

+ +#### Defining a `ScreenModule` + +The first step in constructing a screen is to declare the needed `ScreenModule` namespace. The `Screen`'s value types (i.e. `Action` and `State`) are declared here. The `ScreenModule` protocol glues these types together and serves as the main "public" entry point for creating a `Screen`. + +We declare a case-less enum that conforms to `ScreenModule` for our login screen: + +```swift +enum LoginScreenModule: ScreenModule { + +} +``` + +The first value type to add to the `LoginScreenModule` is `State`. `State` will contain all of the data needed for the `Screen`. Our login screen will track the username and password as it is entered by the user and also display any errors that come up. + +```swift + struct State: Equatable { + var username: String = "" + var password: String = "" + var error: String? = nil + } +``` + +The `ScreenModule` additionally needs to know what a default `State` value looks like, so that it can use one as a part of the `Screen` creation process. This is done by implementing the required `defaultInitialState` computed static var. Since each of the properties in our `State` declaration has a default value assigned to it, this function is super simple: + +```swift + static var defaultInitialState: State { + return State() + } +``` + +Next, we declare all of the `Action` values for our login screen. `Actions` are single-purpose messages sent from the `View` (i.e. `UIViewController`) in response to a user interaction. The simplest set of actions we can have for our login screen includes when each of the text fields' value changes and when the user taps the "login" button: + +```swift + enum Action: Equatable { + case didUpdateUsername(String) + case didUpdatePassword(String) + case didTapLogin + } +``` + +It's up to you to name the cases in a way that makes sense, but using a past-tense verb is preferred since it emphasizes the fact that the view is a thin layer of user interactions in a unidirectional data flow system. + +The next type to define in the module is the `Output`. An `Output` is a message emitted from a screen to communicate events that the screen itself doesn't handle. For example, our login screen can't (and shouldn't) make any decisions about what happens when a user successfully logs in, so it will emit a "user is now logged in" `Output` message. A `Flow` (which is described in detail later), will listen for these signals, and act accordingly by replacing the login screen with a screen only available to logged in users. + +Our login screen will have a single `Output` (using the same past-tense feel): + +```swift + enum Output: Equatable { + case userDidLogin + } +``` + +The [almost] complete module declaration now looks like this: + +```swift +enum LoginScreenModule: ScreenModule { + + enum Action: Equatable { + case didUpdateUsername(String) + case didUpdatePassword(String) + case didTapLogin + } + + enum Output: Equatable { + case userDidLogin + } + + struct State: Equatable { + var username: String = "" + var password: String = "" + var error: String? = nil + } + + static var defaultInitialState: State { + return State() + } + +} +``` + +It's _almost_ the complete declaration because we need to define the `Store` in order to complete the module. + +#### Defining a `Store` + +Every `Screen` has a `Store` associated with it, which is defined as a `LassoStore` subclass. + +```swift +final class LoginScreenStore: LassoStore { + +} +``` + +The `LassoStore` base class is generic on your `ScreenModule` so it can have knowledge of the module's types, and provide the functionality that makes it a `Store`. Because of the generic requirement, our `LoginScreenStore` is declared outside of the `LoginScreenModule` namespace. + +There is one required override for handling `Actions` that we need to add, where we simply `switch` over all possible `Action` values. + +```swift + override handleAction(_ action: Action) { + switch action { + + case .didUpdateUsername(let username): + + case .didUpdatePassword(let password): + + case .didTapLogin: + } + } +``` + +When a new `username` or `password` arrives, the `Store` needs to update the current `state` with the new values. Updates to the state are achieved by using an `update` function provided by the `LassoStore` base class, like this: + +```swift + case .didUpdateUsername(let username): + update { state in + state.username = username + } + + case .didUpdatePassword(let password): + update { state in + state.password = password + } +``` + +To specify the changes to make, you provide the `update` function with a closure that does the actual updating, similar to how you would provide a closure to `UIView.animate` to change the view properties to animate. The `update` function executes your closure with a `state` value for you to modify, and then makes sure all of the appropriate "state change" notifications are sent to the `View`. + +Handling the button tap `Action` is a little different, because there isn't a field to update. This action will initiate an asynchronous call to a login service, and on success emit the `userDidLogin` `Output`. This is what the login service API looks like: + +```swift +enum LoginService { + static func login(_ username: String, + _ password: String, + completion: @escaping (Bool) -> Void) { + ... + } +} +``` + +Using the login service, the handler for the `didTapLogin` action will need to: + +1. Call `LoginService.login` to initiate the login +2. Update the `error` state property if needed +3. Emit the `userDidLogin` `Output` if needed, using the `dispatchOutput()` function (provided by the `LassoStore` base class) + +```swift + case .didTapLogin: + LoginService.login(state.username, + state.password) { [weak self] success in + self?.update { state in + state.error = success ? nil : "Invalid login" + } + if success { + self?.dispatchOutput(.userDidLogin) + } + } +``` + +The complete `LoginScreenStore` now looks like this: + +```swift +final class LoginScreenStore: LassoStore { + + override handleAction(_ action: Action) { + switch action { + + case .didUpdateUsername(let username): + update { state in + state.username = username + } + + case .didUpdatePassword(let password): + update { state in + state.password = password + } + + case .didTapLogin: + LoginService.login(state.username, + state.password) { [weak self] success in + self?.update { state in + state.error = success ? nil : "Invalid login" + } + if success { + self?.dispatchOutput(.userDidLogin) + } + } + } + } + +} +``` + +#### Defining the view controller + +The final part of the `Screen`-types puzzle is the view controller. Our `LoginScreenViewController` will just be a plain `UIViewController`, constructed through code, and conforming to `LassoView` + +```swift +final class LoginScreenViewController: UIViewController, LassoView { + +} +``` + +The first thing we need to add to our `LoginScreenViewController` is a reference to the `Store` that is driving it. Before we do that though, there is a separation-of-concerns issue that needs to be dealt with. In theory, we could simply provide the login view controller a reference to a `LoginScreenStore`. This would certainly _function_, but it exposes much more of the store than the view should have access to, such as emitting outputs or modifying the state. + +Instead of the full `LoginScreenStore`, we'll use a more restricted version that's specifically designed for use in a view - a `ViewStore`. A `ViewStore` is a proxy to a `Store` that is strictly limited to read-only `state` access and the ability to dispatch an `Action` - this provides a `View` with _exactly_ what it needs. + +

+ +A `ViewStore` type is automatically declared for you as a part of a `ScreenModule`, based on the declared `Action` and `State` types, so we can start to use the `LoginScreenModule`'s `ViewStore` immediately: + +```swift +final class LoginScreenViewController: UIViewController, LassoView { + + let store: LoginScreenModule.ViewStore + + init(_ store: ViewStore) { + self.store = store + } +} +``` + +Now we can start connecting the store (i.e. the _view_ store) to the UIKit components in the view controller. We won't go into the details of creating all the controls and setting up their layout constraints here, but you can see the full source code in the GitHub repo for this article. Suffice it to say that we have the following components properly created and laid out in our view controller: + +```swift + private let usernameField: UITextField + private let passwordField: UITextField + private let errorLabel: UILabel + private let loginButton: UIButton +``` + +The fields and label need to get populated with the content in the `Store`'s state, _and_ get updated when that state changes. This is all accomplished by setting up state observations on the store. We can set up all our observations in a helper function, using the `observeState` method on the `store`: + +```swift + private func setUpBindings() { + store.observeState(\.username) { [weak self] username in + self?.usernameField.text = username + } + store.observeState(\.password) { [weak self] password in + self?.passwordField.text = password + } + store.observeState(\.error) { [weak self] error in + self?.errorLabel.text = error + } + } +``` + +`observeState` has a few variations - the one above uses a `KeyPath` to specific `State` properties to listen for changes to individual fields. The closure will be called whenever the field's value changes _and_ when setting up the observation – this is a good way to set up the initial view state. + +That takes care of all the state change notifications coming into the `View`. Now we need to send some actions as the user interacts with the view. You can send an action directly to the `store` by calling its `dispatchAction()` method. For example, if we had an action for when the view appeared (say for analytics purposes), we could write this: + +```swift + override func viewDidAppear() { + store.dispatchAction(.viewDidAppear) + } +``` + +For our login screen, we are only sending actions in direct response to user input, and we can take advantage of some of the many helpers for binding UIKit controls to actions. + +For text changes, we can call `bindTextDidChange(to:)` on the UITextField. The closure maps the new text value to one of the `Action` values: + +```swift + usernameField.bindTextDidChange(to: store) { text in + .didUpdateUsername(text) + } +``` + +For button taps, we can call `bind(to: action:)` on the UIButton: + +```swift + loginButton.bind(to: store, action: .didTapLogin) +``` + +All of these helpers set up the UIKit notifications/actions/etc., and call `dispatchAction()` on the `store` for us. + +Our complete (minus the control set up) `LoginScreenViewController` now looks like this: + +```swift +final class LoginScreenViewController: UIViewController, LassoView { + + let store: LoginScreenModule.ViewStore + + init(_ store: ViewStore) { + self.store = store + } + + override func viewDidLoad() { + // omitted for clarity: create/layout the UIKit controls + setUpBindings() + } + + private let usernameField: UITextField + private let passwordField: UITextField + private let errorLabel: UILabel + private let loginButton: UIButton + + private func setUpBindings() { + // State change inputs: + store.observeState(\.username) { [weak self] username in + self?.usernameField.text = username + } + store.observeState(\.password) { [weak self] password in + self?.passwordField.text = password + } + store.observeState(\.error) { [weak self] error in + self?.errorLabel.text = error + } + + // Action outputs: + usernameField.bindTextDidChange(to: store) { text in + .didUpdateUsername(text) + } + passwordField.bindTextDidChange(to: store) { text in + .didUpdatePassword(text) + } + loginButton.bind(to: store, action: .didTapLogin) + } + +} +``` + + + +#### Pulling it all together + +We can now add the last required part of the `LoginScreenModule`, which is a static `createScreen` function that provides clients of the module a method for creating an instance of our login screen. We waited until the end because this function needs to reference both the `LoginScreenStore` and `LoginScreenViewController`. + +```swift +enum LoginScreenModule: ScreenModule { + ... + + static func createScreen(with store: LoginScreenStore) -> Screen { + return Screen(store, LoginScreenViewController(store.asViewStore())) + } +} +``` + +Declaring `createScreen` fulfills the remaining type requirement of a `ScreenModule`, which is the concrete `Store` type associated with the `Screen` – in our case the `LoginScreenStore`. + +#### Instantiating the screen + +With all of the types defined, we can finally instantiate one of our login screens, and fortunately, it's super easy. + +```swift +let screen = LoginScreenModule.createScreen() +``` + +The above version of `createScreen` will use the `defaultInitialState` value we defined (in the `LoginScreenModule`) to create our `LoginScreenStore`. This is perfect for when our pretend app launches for the first time, but let's say we wanted to remember our users and pre-populate the login screen so only the password is needed. We can achieve this by calling `createScreen` with a different initial state, like this: + +```swift +let prePopulated = LoginScreenModule.State(username: "Billie") +let screen = LoginScreenModule.createScreen(with: prePopulated) +``` + +The actual `Screen` type is really just a container for the created `Store` and `UIViewController`: + +```swift +screen.store // LoginScreenStore +screen.controller // LoginScreenViewController +``` + +We can push the controller onto a navigation stack: + +```swift +navigationController.pushViewController(screen.controller, animated: true) +``` + +There are also some convenience functions on `Screen` that essentially pass through to the `Store`. For example, if we want to observe the `Output` from the screen, we can call `observeOutput()` right on the `screen` variable, which simply calls the store's function: + +```swift +screen.observeOutput { [weak self] output in + switch output { + case .userDidLogin: + // do something post-login + } +} +``` + +#### Unit testing + +Creating unit tests for a `Screen` is quite straight forward, no excuses! Since the view controllers are used purely for displaying the current `State` to a user, you can instantiate a `Store`, send it some actions, and test against the resulting state: + +```swift +func test_LoginScreen_changeUsername() { + let screen = LoginScreenModule.createScreen() + screen.store.dispatchAction(.didChangeUsername("mom")) + XCTAssertEqual(screen.store.state.username, "mom") +} +``` + +Similarly, it's easy to listen for outputs, and test against them: + +```swift +func test_LoginScreen_didLogin() { + var outputs = [LoginScreenModule.Output]() + let screen = LoginScreenModule + .createScreen() + .observeOutput { outputs.append($0) } + screen.store.dispatchAction(.didTapLogin) + XCTAssertEqual(outputs, [.userDidLogin]) +} +``` + +In addition to the above ad-hoc style of testing, there are a few different helpers that make writing unit tests a joy. Take a look at the [Lasso GitHub repo](https://github.com/ww-tech/lasso) for a bunch of examples! + +### Recap + +Hopefully, you've gained a sense of how easy it is to use Lasso as an architectural framework for the views in your app. The process we followed to create the sample login screen in this article is the same process you can use to create your own screens: + +- Create a `ScreenModule` that defines a set of `Action`, `State`, and `Output` value types +- Create a `LassoStore` subclass to hold all your business logic, responding to your `Action` values by updating your `State`, and emitting `Output` when appropriate. +- Create a `UIViewController` that forwards all user interactions as `Action` values to the `Store` and observes the `State` to keep the UI in sync. + +Once you have more than one screen defined, you'll want to start exploring how you can tie them together using a `Flow`. Flows employ the same ideology of separation of concerns to enable a highly modular, easy to understand, and testable application code base. + +The source code for this article is available in its own project [here](https://github.com/ww-tech/lasso-article-examples). + +Check out [Lasso on GitHub](https://github.com/ww-tech/lasso), take it for a spin, and let us know what you think! + diff --git a/docs/images/Lasso_Logo.pdf b/docs/images/Lasso_Logo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..38c2e75ecbd0d5d387086a3d98d7c9d98f79f32b GIT binary patch literal 138903 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 GWEYqi6{bN9s`JI?_; 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≤-mP?%N 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�wfDqt8|_C_Sui-8(BB^4))Fb@8a$U8mi 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

Mba;;H7FT%Bc z_zp_6Ny7xTzJLH_3O?_=n5$m!CDi3q)T>-z3Kg83!<5#QIx?ebrb9uwVRq%_n&Z@f z$?$xzd54SYGE)Ps7xo;hLq131ou*fZQI0csk&t^58&IH9Jw;SZuI}6qP*-N96T-|k z@9UyN2Jt<6>0hLw~O?>)!L$M>!QtL&=_RHayq-T?&#Mq01^FPxcnUViO@-;+qrD!ax2FAS6lcDCti%bsc#FuM^8`vLf12g*^j z0)?x-44`-*K@l@DU)8sB4$OixDIcJ0zK&iP)+O&*+?AXH?z+bpg~YetNC#)g#z1kR zOcoIWeMK|C)&9C$*X5s;?s1&NB8Hi* zVA_QN(p)~8n})k5DSFY#{opAJCE>5gCu);obD^C06q25x$S8OO8A-xXQrsGqPya)r z0E(DAtFPJ{lsrE!eFP2gSNoGRb;5wJ=3nhyseTIEZ$!azoryh!AkzU$W1!T-%sju- z3T%z~S%SnWh$ep<~`K+uPA1DAQFt8XVlQg`;q(V1Z#o)2P$EJHM8?C32CRPtK$ z?E1O`3Ou=VQlMQ29yvn}_CG6FXt)X4e-Ki=Z8jt}E2kPX?P#-i6-zO6#BoP~;JxCh78 znm1vXa(Hp^B4^?(u|dRi75=&+>vd=50=6 z#;IuW5fzPZXjQr`-zaEpZ4>He!0&d5tJ2hP1Bx@O#`X~PDq|J7U zr-5$c@i<5#QUAvC`eJ@q>wPqY;)523O-G)^!gIPlkdC&fuYi-I^8=%VpEtX@+e=1E=p9qieNi?Q@2 zj_N{7VTaa4+vQ^3iL17zi1CQO@m0~C?g62sc!C_hpZms74!&dB>D6|xuJyYj8qb*I zmQFke&yxc2#Z=5y5C5)o-A{7(qIqMIcGaCh(=q9~u+Q(DjmLB)e!bHG<}8JYb70~w z_3Pm7FXaZiROt%&s47c*F}GGA&3^QeL$0PQp8O0?=`nzZXEgr8oLwEae*Jmp zN#SAQQfk!rHMyuCAp0%R%>)%`JvkG@TumPS{Zu?a%1G_qs`!mD0LI)xUxq>0UMp$_jH!1xP@dWz^yc%;Q>*+6w%A)#wDB?Em-!Y(q07q8$f ziwE5KlZ$9xLo}*W4vLKQCu@C6Tq~WzjywHqbcbtkjCEM23MeMn?gqagpQl09C280> z4z&x)l^AV_P`w|a)nOB)n;kulb`I+s*%LTzRf?QMH<#_4}q?IA;AAYVi41Y zD0(kk_-!Mne6o9U_*|33`&3EUPn4%HD)x3{ATwh!NvUq3$H3!7iJFBurTTo%JbKVMsWiCURz}mnaLX8GMy5>^5_1e z<*;5|MFL`_yZUjxBR+Vule7hwHVpZTJpdj^G}N%H@y(r*1AN1-SBqxXs3z8XZt!wU zM|i?{La7)cL0EnDDnw2@93{ErK%hYF*|guHX8{(fF(J(Zr*M$a)1wLZ(A>a~k*%K7GO%-RMc`wjmO9R{;lWv{YdJP4_FQ$QD& z&94?w%gAEJ@Cx({`9hAMoMNF{mE3QAm&@SSvP*?B`jQ1RKQiG}Qh_u7wDuhCrKR_2gRz~;c)Hm zerjg4+@s^iA-dMnc{JweAaZD6j$51bPy%l%O}%3H^PJ(`Aom5<<)-K)4`466Rw!&4 z4J-BB=H20xa_2Nm>=iaHB-=S~wyYfb??fudae!R~e9|xvn)^|0i84JB>r<<~TyJ!)-JQB{_p z=FJAwvkAsDWEPYW==*F@MdPctj@#z=7o|j*!gGz@$sCvBk z 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 S(t&5<6XwTdVvr-z5*PYX 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 IM6N<$f{YZRfB*mh literal 0 HcmV?d00001 diff --git a/docs/images/Lasso_Logo.svg b/docs/images/Lasso_Logo.svg new file mode 100644 index 0000000..41b0539 --- /dev/null +++ b/docs/images/Lasso_Logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/TutorialFlowDiagram.svg b/docs/images/TutorialFlowDiagram.svg new file mode 100644 index 0000000..5f1bece --- /dev/null +++ b/docs/images/TutorialFlowDiagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/flow-flow-screen.png b/docs/images/flow-flow-screen.png new file mode 100644 index 0000000000000000000000000000000000000000..e157f8a7168de70746d236f2f242fe2782b84b8e GIT binary patch literal 52173 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 B-ctYo literal 0 HcmV?d00001 diff --git a/docs/images/flow-flow-screen.svg b/docs/images/flow-flow-screen.svg new file mode 100644 index 0000000..748c179 --- /dev/null +++ b/docs/images/flow-flow-screen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/flow-flow.svg b/docs/images/flow-flow.svg new file mode 100644 index 0000000..9130cef --- /dev/null +++ b/docs/images/flow-flow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/flow.gif b/docs/images/flow.gif new file mode 100644 index 0000000000000000000000000000000000000000..edb7a4cba0403801c37df3a0bc83495766954d53 GIT binary patch literal 35248 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 \ No newline at end of file diff --git a/docs/images/flow1.svg b/docs/images/flow1.svg new file mode 100644 index 0000000..fb0d835 --- /dev/null +++ b/docs/images/flow1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/login.png b/docs/images/login.png new file mode 100644 index 0000000000000000000000000000000000000000..11d32b9f42d1c14ff3593971d6fdbc70206aa48a GIT binary patch literal 41169 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 D#D>2O literal 0 HcmV?d00001 diff --git a/docs/images/screen-no output.svg b/docs/images/screen-no output.svg new file mode 100644 index 0000000..fdc484d --- /dev/null +++ b/docs/images/screen-no output.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/screen.png b/docs/images/screen.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f4b04e2a84d0c945fc3705b2fe40d3ee6b910a GIT binary patch literal 16658 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 \ No newline at end of file diff --git a/docs/images/trevor-screens.png b/docs/images/trevor-screens.png new file mode 100644 index 0000000000000000000000000000000000000000..96a9c1c69a2eb47e4c7f830aa282b9f072d37a57 GIT binary patch literal 404539 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

Ek$N>?Hx{ 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�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 z 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

wbjsU6oBz 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 e7M3%fTUu{U9c1v`7>s4= \ No newline at end of file diff --git a/docs/style-guide.md b/docs/style-guide.md new file mode 100644 index 0000000..2737a4a --- /dev/null +++ b/docs/style-guide.md @@ -0,0 +1,150 @@ +## Lasso style guide + +Some general notes about writing idiomatic Lasso code. + +Contents: + +- [Naming conventions](#naming-conventions) +- [Module definitions](#module-definitions) +- [`switch` case spacing](#switch-case-spacing) + + + +### Naming conventions + +When conforming to a Lasso Module (`ScreenModule`, `StoreModule`, `FlowModule`), use the module type in your module name: + +```swift +enum MyLoginScreenModule: ScreenModule { ... } +enum CrazyPeopleStoreModule: StoreModule { ... } +enum CoolFeatureFlowModule: FlowModule { ... } +``` + +When declaring related types (i.e. view controllers, stores), just use the prefix from the module definition, without the extra module type: + +```swift +final class MyLoginStore: LassoStore { ... } +final class MyLoginViewController: UIViewController, LassoView { ... } +final class CrazyPeopleStore: LassoStore { ... } +``` + +`Flow` subclasses should include `Flow` in their name: + +```swift +final class CoolFeatureFlow: Flow { ... } +``` +
+ +### Module definitions + +Try to limit module definitions to type declarations. + +When defining your `ScreenModule`, `ViewModule`, and `FlowModule` enums, try to limit the code to just declaring new types. If you need to add functionality (e.g., convenience initializers, etc.), it's better to add these in extensions so that readers can more easily get the big picture of the module. This allows for easy scanning of the module definition, to quickly get a clear picture of all the module's types. + +For example, consider a module definition that mixes type declarations with conveniences and functionality: + +```swift +enum MyModule: ScreenModule { + + struct State: Equatable { + let name: String + var value: Int + var canSubmit: Bool + var error: Error? + + enum Error: Swift.Error { + case badInput + case unknown + + var description: String { + switch self { + case .badInput: return "invalid input" + case .unknown: return "unknow error" + } + } + } + + init(name: String = "", value: Int = 1) { + self.name = name + self.value = value + } + + func updateCanSubmit() { + canSubmit = !name.isEmpty && value > 1 + } + } + +} +``` + +By moving those conveniences and helpers into extensions outside of the module definition, it becomes easier to read + +```swift +enum MyModule: ScreenModule { + + struct State: Equatable { + let name: String + var value: Int + var canSubmit: Bool + var error: Error? + + enum Error: Swift.Error { + case badInput + case unknown + } + } + +} + +extension MyModule.State.Error { + + var description: String { + switch self { + case .badInput: return "invalid input" + case .unknown: return "unknow error" + } + } + +} + +extension MyModule.State { + + init(name: String = "", value: Int = 1) { + self.name = name + self.value = value + } + + func updateCanSubmit() { + canSubmit = !name.isEmpty && value > 1 + } + +} +``` + + + +
+ +### `switch` case spacing + +Precede each `switch` `case` statement with an empty line. + +Lasso replaces delegate-style protocols with `Action` and `Output` enums. This allows for precise and flexible conections between types, but also necessitates writing lots of `switch` statements, with potentially non-trivial code per `case`. For readability purposes, it's a best practice in Lasso to have a single empty line preceding each `case` statement - just like it's best to have an empty line preceding each function: + +```swift +override func handleAction(_ action: Action) { + switch action { + + case .didSelectItem(let idx): + guard idx >= 0, idx < state.items.count else { return } + dispatchOutput(.didSelectItem(state.items[idx])) + + case .didAcknowledgeError: + update { state in + state.phase = .idle + } + } +} +``` + +