Skip to content

Commit

Permalink
Merge branch 'master' of github.com:spotify/Mobius.swift into connect…
Browse files Browse the repository at this point in the history
…able-map
  • Loading branch information
Jesper Sandström committed Apr 20, 2020
2 parents 0ed5f25 + 3c8c7c0 commit a031592
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Mobius.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
2D405B7824337E9F00A39BD4 /* MobiusThrowableAssertion.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D25B9AA24337C580077FB07 /* MobiusThrowableAssertion.h */; settings = {ATTRIBUTES = (Public, ); }; };
2D405B7924337EAB00A39BD4 /* module.modulemap in Headers */ = {isa = PBXBuildFile; fileRef = 2D25B9A724337C580077FB07 /* module.modulemap */; settings = {ATTRIBUTES = (Public, ); }; };
2D587360238EC60F001F21ED /* EventRouterDisposalLogicalRaceRegressionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D58735F238EC60F001F21ED /* EventRouterDisposalLogicalRaceRegressionTest.swift */; };
2DA1E89F2449FBA800D240B7 /* BeginnerLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA1E89A2449EF6E00D240B7 /* BeginnerLoop.swift */; };
2DA1E8A02449FBA800D240B7 /* BeginnerLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA1E89A2449EF6E00D240B7 /* BeginnerLoop.swift */; };
2DA1E8A12449FBC500D240B7 /* WikiTutorialTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA1E89D2449F1ED00D240B7 /* WikiTutorialTest.swift */; };
2DA9A41E23FAEA4800BF5534 /* AnyEffectHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA9A41D23FAEA4800BF5534 /* AnyEffectHandlerTests.swift */; };
2DB61AFD23A8F485009E55DB /* NonReentrancyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DB61AFC23A8F485009E55DB /* NonReentrancyTests.swift */; };
2DDF54C0229BDB4800D05861 /* CompositeEventSourceBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DDF54BF229BDB4700D05861 /* CompositeEventSourceBuilder.swift */; };
Expand Down Expand Up @@ -319,6 +322,8 @@
2D3EEB9523FADA9E006E478A /* AsyncStartStopStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStartStopStateMachine.swift; sourceTree = "<group>"; };
2D3F26EC237B02B8004C2B75 /* AsyncDispatchQueueConnectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AsyncDispatchQueueConnectable.swift; path = ConnectableConvenienceClasses/AsyncDispatchQueueConnectable.swift; sourceTree = "<group>"; };
2D58735F238EC60F001F21ED /* EventRouterDisposalLogicalRaceRegressionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventRouterDisposalLogicalRaceRegressionTest.swift; sourceTree = "<group>"; };
2DA1E89A2449EF6E00D240B7 /* BeginnerLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeginnerLoop.swift; sourceTree = "<group>"; };
2DA1E89D2449F1ED00D240B7 /* WikiTutorialTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WikiTutorialTest.swift; sourceTree = "<group>"; };
2DA9A41D23FAEA4800BF5534 /* AnyEffectHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEffectHandlerTests.swift; sourceTree = "<group>"; };
2DB61AFC23A8F485009E55DB /* NonReentrancyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonReentrancyTests.swift; sourceTree = "<group>"; };
2DDF54BF229BDB4700D05861 /* CompositeEventSourceBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeEventSourceBuilder.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -748,6 +753,7 @@
5BB287E7209995420043B530 /* Source */ = {
isa = PBXGroup;
children = (
2DA1E89A2449EF6E00D240B7 /* BeginnerLoop.swift */,
5BCF5F0020F3620700721C0D /* ConnectableClass.swift */,
5B9CE80421197FE000DB79A7 /* ConnectableContramap.swift */,
5BB287E8209995420043B530 /* SimpleLogger.swift */,
Expand Down Expand Up @@ -848,6 +854,7 @@
DD748324212DEEC1008EEECD /* CopyableTests.swift */,
5B1F104F21105CC00067193C /* EventSource+ExtensionsTests.swift */,
025BB52A244DA68500E80BD2 /* ConnectableMapTests.swift */,
2DA1E89D2449F1ED00D240B7 /* WikiTutorialTest.swift */,
);
path = Test;
sourceTree = "<group>";
Expand Down Expand Up @@ -1247,6 +1254,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2DA1E8A02449FBA800D240B7 /* BeginnerLoop.swift in Sources */,
DD748326212DEEC6008EEECD /* Copyable.swift in Sources */,
3EE5AF052110BF2E00CF8CA8 /* ConnectableClass.swift in Sources */,
2DF4C42420DBDF1B00A4B6DE /* SimpleLogger.swift in Sources */,
Expand Down Expand Up @@ -1352,6 +1360,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2DA1E89F2449FBA800D240B7 /* BeginnerLoop.swift in Sources */,
DD748323212DB525008EEECD /* Copyable.swift in Sources */,
5B9CE80521197FE000DB79A7 /* ConnectableContramap.swift in Sources */,
5B1F104D21105CAD0067193C /* EventSource+Extensions.swift in Sources */,
Expand Down Expand Up @@ -1422,6 +1431,7 @@
025BB52C244DA6BF00E80BD2 /* ConnectableMapTests.swift in Sources */,
5B1F105121105CC50067193C /* EventSource+ExtensionsTests.swift in Sources */,
5BCF5F0420F3636800721C0D /* ConnectableClassTests.swift in Sources */,
2DA1E8A12449FBC500D240B7 /* WikiTutorialTest.swift in Sources */,
5B9CE80721199D4400DB79A7 /* ConnectableContramapTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
45 changes: 45 additions & 0 deletions MobiusExtras/Source/BeginnerLoop.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2020 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.

import MobiusCore

public extension Mobius {

/// A simplified version of `Mobius.loop` for use in tutorials.
///
/// This helper simplifies setting up a loop with no effects.
///
/// - Parameter update: A function taking a model and event and returning a new model.
@inlinable
static func beginnerLoop<Model, Event>(
update: @escaping (Model, Event) -> Model
) -> Builder<Model, Event, Never> {
let realUpdate = Update<Model, Event, Never> { model, event in
return .next(update(model, event))
}

let effectHandler = AnyConnectable<Never, Event> { _ in
return Connection(
acceptClosure: { _ in },
disposeClosure: {}
)
}
return loop(update: realUpdate, effectHandler: effectHandler)
}
}
146 changes: 146 additions & 0 deletions MobiusExtras/Test/WikiTutorialTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) 2020 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.

import MobiusCore
import MobiusExtras
import XCTest

/// Test cases that reproduce the Getting Started section of the GitHub wiki
class WikiTutorialTest: XCTestCase {
// swiftlint:disable function_body_length

func testWikiCreatingALoop() {
// Dummy implementation of print()
var printedValues: [String] = []
func print(_ value: Any) {
printedValues.append(String(describing: value))
}

// ---8<--- Wiki content start
enum MyEvent {
case up
case down
}

func update(counter: Int, event: MyEvent) -> Int {
switch event {
case .up:
return counter + 1
case .down:
return counter > 0
? counter - 1
: counter
}
}

let loop = Mobius.beginnerLoop(update: update)
.start(from: 2)

loop.addObserver { counter in print(counter) }

loop.dispatchEvent(.down) // prints "1"
loop.dispatchEvent(.down) // prints "0"
loop.dispatchEvent(.down) // prints "0"
loop.dispatchEvent(.up) // prints "1"
loop.dispatchEvent(.up) // prints "2"
loop.dispatchEvent(.down) // prints "1"

loop.dispose()
// ---8<--- Wiki content end

XCTAssertEqual(printedValues, ["2", "1", "0", "0", "1", "2", "1"])
}

func testWikiCreatingALoop_addingEffects() {
// Dummy implementation of print()
var printedValues: [String] = []
func print(_ value: Any) {
printedValues.append(String(describing: value))
}

// Carried over from previous example
enum MyEvent {
case up
case down
}

// ---8<--- Wiki content start
enum MyEffect {
case reportErrorNegative
}

func update1(model: Int, event: MyEvent) -> Next<Int, MyEffect> {
switch event {
case .up:
return .next(model + 1)
case .down:
return model > 0
? .next(model - 1)
: .next(model)
}
}

func update2(model: Int, event: MyEvent) -> Next<Int, MyEffect> {
switch event {
case .up:
return .next(model + 1)
case .down:
return model > 0
? .next(model - 1)
: .next(model, effects: [.reportErrorNegative])
}
}

func update(model: Int, event: MyEvent) -> Next<Int, MyEffect> {
switch event {
case .up:
return .next(model + 1)
case .down:
return model > 0
? .next(model - 1)
: .dispatchEffects([.reportErrorNegative])
}
}

func handleReportErrorNegative() {
print("error!")
}

let effectHandler = EffectRouter<MyEffect, MyEvent>()
.routeCase(MyEffect.reportErrorNegative).to(handleReportErrorNegative)
.asConnectable

let loop = Mobius.loop(update: update, effectHandler: effectHandler)
.start(from: 2)

loop.addObserver { counter in print(counter) }

loop.dispatchEvent(.down) // prints "1"
loop.dispatchEvent(.down) // prints "0"
loop.dispatchEvent(.down) // followed by "error!"
loop.dispatchEvent(.up) // prints "1"
loop.dispatchEvent(.up) // prints "2"
loop.dispatchEvent(.down) // prints "1"

loop.dispose()
// ---8<--- Wiki content end

XCTAssertEqual(printedValues, ["2", "1", "0", "error!", "1", "2", "1"])
}
}

0 comments on commit a031592

Please sign in to comment.