From 1f7e9349cd38bce88464af77cc0b4dda7ea36919 Mon Sep 17 00:00:00 2001 From: Tara Date: Thu, 12 Apr 2018 01:42:10 -0500 Subject: [PATCH 01/16] Removed the text telling you how to edit --- elm-todo/Todo.elm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/elm-todo/Todo.elm b/elm-todo/Todo.elm index 3a07b24..1974b94 100755 --- a/elm-todo/Todo.elm +++ b/elm-todo/Todo.elm @@ -313,8 +313,7 @@ viewControlsClear entriesCompleted = infoFooter : Html msg infoFooter = footer [ class "info" ] - [ p [] [ text "Double-click to edit a todo" ] - , p [] + [ p [] [ text "Written by " , a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ] ] From c42bd5183391997fedb3b9f1a7b3a711f6ba6548 Mon Sep 17 00:00:00 2001 From: Tara Date: Thu, 12 Apr 2018 01:47:19 -0500 Subject: [PATCH 02/16] Removed the code to trigger an edit --- elm-todo/Todo.elm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm-todo/Todo.elm b/elm-todo/Todo.elm index 1974b94..8da1658 100755 --- a/elm-todo/Todo.elm +++ b/elm-todo/Todo.elm @@ -240,7 +240,7 @@ viewEntry todo = ] [] , label - [ onDoubleClick (EditingEntry todo.id True) ] + [ ] [ text todo.description ] , button [ class "destroy" From 36d2b97089d4252fb636b06b4f4502146968e3d4 Mon Sep 17 00:00:00 2001 From: Tara Date: Thu, 12 Apr 2018 01:49:54 -0500 Subject: [PATCH 03/16] Removed all code with references to editing --- elm-todo/Todo.elm | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/elm-todo/Todo.elm b/elm-todo/Todo.elm index 8da1658..da51c2e 100755 --- a/elm-todo/Todo.elm +++ b/elm-todo/Todo.elm @@ -45,7 +45,6 @@ type alias Model = type alias Entry = { description : String , completed : Bool - , editing : Bool , id : Int } @@ -62,7 +61,6 @@ newEntry : String -> Int -> Entry newEntry desc id = { description = desc , completed = False - , editing = False , id = id } @@ -76,7 +74,6 @@ to them. -} type Msg = UpdateNewEntryField String - | EditingEntry Int Bool | UpdateEntry Int String | Add | Delete Int @@ -102,19 +99,6 @@ update msg model = UpdateNewEntryField str -> { model | newEntryField = str } - EditingEntry id isEditing -> - let - updateEntry t = - if t.id == id then - { t | editing = isEditing } - else - t - - focus = - Dom.focus ("todo-" ++ toString id) - in - { model | entries = List.map updateEntry model.entries } - UpdateEntry id task -> let updateEntry t = @@ -229,7 +213,7 @@ viewKeyedEntry todo = viewEntry : Entry -> Html Msg viewEntry todo = li - [ classList [ ( "completed", todo.completed ), ( "editing", todo.editing ) ] ] + [ classList [ ( "completed", todo.completed ) ] ] [ div [ class "view" ] [ input @@ -248,16 +232,6 @@ viewEntry todo = ] [] ] - , input - [ class "edit" - , value todo.description - , name "title" - , id ("todo-" ++ toString todo.id) - , onInput (UpdateEntry todo.id) - , onBlur (EditingEntry todo.id False) - , onEnter (EditingEntry todo.id False) - ] - [] ] From 0984636276030fb3a5f889ed146764bb813d9a96 Mon Sep 17 00:00:00 2001 From: Tara Date: Mon, 16 Apr 2018 23:06:38 -0500 Subject: [PATCH 04/16] Made Model and Entry immutable --- ruby-todo/lib/model.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruby-todo/lib/model.rb b/ruby-todo/lib/model.rb index 75a6085..e64ca6a 100644 --- a/ruby-todo/lib/model.rb +++ b/ruby-todo/lib/model.rb @@ -5,7 +5,7 @@ def initialize(entries: [], new_entry_field: "", next_id: nil) (entries.lazy.map(&:id).max || -1) + 1 end - attr_accessor :entries, :new_entry_field, :next_id + attr_reader :entries, :new_entry_field, :next_id def ==(other) !other.nil? && @@ -20,7 +20,7 @@ def initialize(id:, description:, completed: false) @description, @id, @completed = description, id, completed end - attr_accessor :description, :completed, :id + attr_reader :description, :completed, :id def ==(other) !other.nil? && From 878734d35b1598e67f84d383e30a183be4ef2e1d Mon Sep 17 00:00:00 2001 From: Tara Date: Mon, 16 Apr 2018 23:06:56 -0500 Subject: [PATCH 05/16] Made Engine assume apply_to returns a new value --- ruby-todo/lib/engine.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/ruby-todo/lib/engine.rb b/ruby-todo/lib/engine.rb index a257c20..35e6a0c 100644 --- a/ruby-todo/lib/engine.rb +++ b/ruby-todo/lib/engine.rb @@ -6,7 +6,6 @@ def self.run(*args) def self.run_with_history(model, messages) messages.map do |msg| msg.apply_to(model) - model end end end From bf6d0b7f5c55131eb874d3fc80be8a64391a23b6 Mon Sep 17 00:00:00 2001 From: Tara Date: Mon, 16 Apr 2018 23:51:30 -0500 Subject: [PATCH 06/16] Updated engine to use the updated model each step --- ruby-todo/.idea/inspectionProfiles/Project_Default.xml | 6 ++++++ ruby-todo/lib/engine.rb | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 ruby-todo/.idea/inspectionProfiles/Project_Default.xml diff --git a/ruby-todo/.idea/inspectionProfiles/Project_Default.xml b/ruby-todo/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..b0db9b0 --- /dev/null +++ b/ruby-todo/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/ruby-todo/lib/engine.rb b/ruby-todo/lib/engine.rb index 35e6a0c..c28e9a2 100644 --- a/ruby-todo/lib/engine.rb +++ b/ruby-todo/lib/engine.rb @@ -5,7 +5,8 @@ def self.run(*args) def self.run_with_history(model, messages) messages.map do |msg| - msg.apply_to(model) + model = msg.apply_to(model) + model end end end From 1adea8929a7aaf75db406509239077a15cd6acf1 Mon Sep 17 00:00:00 2001 From: Tara Date: Mon, 16 Apr 2018 23:51:48 -0500 Subject: [PATCH 07/16] Made the messages return new models and such --- ruby-todo/lib/messages.rb | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/ruby-todo/lib/messages.rb b/ruby-todo/lib/messages.rb index abe85de..1c319fb 100644 --- a/ruby-todo/lib/messages.rb +++ b/ruby-todo/lib/messages.rb @@ -2,13 +2,16 @@ module Msg class Add def apply_to(model) + new_entries = [] + model.entries + new_model = Model.new(entries: new_entries, + new_entry_field: "", + next_id: model.next_id+1) unless model.new_entry_field.blank? - model.entries << Entry.new( + new_entries << Entry.new( description: model.new_entry_field, id: model.next_id) end - model.next_id += 1 - model.new_entry_field = "" + new_model end end @@ -20,7 +23,9 @@ def initialize(str) attr_reader :str def apply_to(model) - model.new_entry_field = str + Model.new(entries: model.entries, + new_entry_field: str, + next_id: model.next_id) end end @@ -29,14 +34,22 @@ def initialize(id, is_completed) @id, @is_completed = id, is_completed end - attr_reader :id, :is_completed + attr_reader :id, :is_completed def apply_to(model) + new_entries = [] model.entries.each do |entry| if entry.id == id - entry.completed = is_completed + new_entries << Entry.new(id: entry.id, + description: entry.description, + completed: is_completed) + else + new_entries << entry end end + Model.new(entries: new_entries, + new_entry_field: model.new_entry_field, + next_id: model.next_id) end end @@ -45,16 +58,22 @@ def initialize(id) @id = id end - attr_reader :id + attr_reader :id def apply_to(model) - model.entries.reject! { |e| e.id == id } + new_entries = model.entries.reject{ |e| e.id == id } + Model.new(entries: new_entries, + new_entry_field: model.new_entry_field, + next_id: model.next_id) end end class DeleteAllCompleted def apply_to(model) - model.entries.reject!(&:completed) + new_entries = model.entries.reject(&:completed) + Model.new(entries: new_entries, + new_entry_field: model.new_entry_field, + next_id: model.next_id) end end From 9e2e2ad5ac4ac653cb2756034b22a0a58bef27ba Mon Sep 17 00:00:00 2001 From: Tara Date: Mon, 16 Apr 2018 23:51:59 -0500 Subject: [PATCH 08/16] Un-commented the time travel bit --- ruby-todo/test/todo_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby-todo/test/todo_test.rb b/ruby-todo/test/todo_test.rb index f7436c7..d3351dc 100644 --- a/ruby-todo/test/todo_test.rb +++ b/ruby-todo/test/todo_test.rb @@ -91,7 +91,7 @@ end it "supports time travel" do - skip "Mutable model does not support time travel" + # skip "Mutable model does not support time travel" actual_history = Engine.run_with_history(Model.new, [ Msg::UpdateNewEntryField.new("go forward in time"), From a8ebd1c39e4a61edfe7c18e98b1fc1878a613574 Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 17:26:15 -0500 Subject: [PATCH 09/16] Copied swift files into one file to be compiled --- swift-todo/Sources/Todo/Combined.swift | 294 +++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 swift-todo/Sources/Todo/Combined.swift diff --git a/swift-todo/Sources/Todo/Combined.swift b/swift-todo/Sources/Todo/Combined.swift new file mode 100644 index 0000000..3945409 --- /dev/null +++ b/swift-todo/Sources/Todo/Combined.swift @@ -0,0 +1,294 @@ +// +// Engine.swift +// Todo +// +// Created by Paul on 2018/4/4. +// + +import Foundation + +struct Engine { + static func run(on model: Model, applying messages: [Message]) -> Model { + return runWithHistory(on: model, applying: messages).last! + } + + static func runWithHistory(on model: Model, applying messages: [Message]) -> [Model] { + return messages.map { message in + message.apply(to: model) + return model + } + } +} + + +// +// Messages.swift +// Todo +// +// Created by Paul on 2018/4/4. +// + +import Foundation + +enum Message { + case add + case updateNewEntryField(String) + case check(Int, Bool) + case delete(Int) + case deleteAllCompleted + + func apply(to model: Model) { + switch(self) { + case .add: + if !model.newEntryField.isBlank() { + model.entries.append(Entry(id: model.nextID, description: model.newEntryField)) + } + model.nextID += 1 + model.newEntryField = "" + + case .updateNewEntryField(let str): + model.newEntryField = str + + case .check(let id, let isCompleted): + for entry in model.entries { + if(entry.id == id) { + entry.completed = isCompleted + } + } + + case .delete(let id): + model.entries.remove { $0.id == id } + + case .deleteAllCompleted: + model.entries.remove { $0.completed } + } + } +} + + +class Model { + var entries: [Entry] + var newEntryField: String + var nextID: Int + + init(nextID: Int? = nil, newEntryField: String = "", entries: [Entry] = []) { + self.entries = entries + self.newEntryField = newEntryField + self.nextID = nextID ?? + (entries.map { $0.id }.max() ?? -1) + 1 + } +} + +class Entry { + var id: Int + var description: String + var completed: Bool + + init(id: Int, description: String, completed: Bool = false) { + self.id = id + self.description = description + self.completed = completed + } +} + +extension Model: Equatable { + static func == (lhs: Model, rhs: Model) -> Bool { + return lhs.newEntryField == rhs.newEntryField + && lhs.nextID == rhs.nextID + && lhs.entries == rhs.entries + } +} + +extension Entry: Equatable { + static func == (lhs: Entry, rhs: Entry) -> Bool { + return lhs.id == rhs.id + && lhs.description == rhs.description + && lhs.completed == rhs.completed + } +} + + +// +// Utils.swift +// Todo +// +// Created by Paul on 2018/4/4. +// + +import Foundation + +extension CharacterSet { + static let nonWhitespace = CharacterSet.whitespacesAndNewlines.inverted +} + +extension String { + func isBlank() -> Bool { + return rangeOfCharacter(from: .nonWhitespace) == nil + } +} + +extension Array { + mutating func remove(where predicate: (Element) -> Bool) { + var dst = startIndex + for src in indices { + let elem = self[src] + if !predicate(elem) { + self[dst] = elem + dst += 1 + } + } + removeSubrange(dst ..< endIndex) + } +} + + +import XCTest +@testable import Todo + +class TodoTests: XCTestCase { + var model = Model( + entries: [ + Entry(id: 10, description: "Pat head"), + Entry(id: 11, description: "Rub tummy") + ] + ) + + func testModelStartsEmpty() { + XCTAssertEqual(true, Model().entries.isEmpty) + XCTAssertEqual(0, Model().nextID) + } + + func testUpdateNewEntryField() { + let newModel = Engine.run(on: model, applying: [ + .updateNewEntryField("typing away") + ]) + XCTAssertEqual("typing away", newModel.newEntryField) + } + + func testAdd() { + let newModel = Engine.run(on: model, applying: [ + .updateNewEntryField("hop on one foot"), + .add + ]) + XCTAssertEqual(3, newModel.entries.count) + let newEntry = newModel.entries.last! + XCTAssertEqual(12, newEntry.id) + XCTAssertEqual("hop on one foot", newEntry.description) + XCTAssertEqual(false, newEntry.completed) + } + + func testAddDoesNothingIfFieldIsBlank() { + let newModel = Engine.run(on: model, applying: [ + .updateNewEntryField(""), + .add, + .updateNewEntryField(" "), + .add + ]) + + XCTAssertEqual(2, newModel.entries.count) + } + + func testCheck() { + let newModel = Engine.run(on: model, applying: [ + .check(10, true), + ]) + XCTAssertEqual(true, newModel.entries[0].completed) + XCTAssertEqual(false, newModel.entries[1].completed) + } + + func testUncheck() { + let newModel = Engine.run(on: model, applying: [ + .check(10, true), + .check(11, true), + .check(10, false), + ]) + XCTAssertEqual(false, newModel.entries[0].completed) + XCTAssertEqual(true, newModel.entries[1].completed) + } + + func testDelete() { + let newModel = Engine.run(on: model, applying: [ + .delete(10) + ]) + XCTAssertEqual([11], newModel.entries.map { $0.id }) + } + + func testDeleteAllCompleted() { + let newModel = Engine.run(on: model, applying: [ + .check(10, true), + .deleteAllCompleted + ]) + XCTAssertEqual([11], newModel.entries.map { $0.id }) + } + +/* + func testTimeTravel() { + let actualHistory = Engine.runWithHistory(on: Model(), applying: [ + .updateNewEntryField("go forward in time"), + .add, + .add, // no effect + .updateNewEntryField("delete this item"), + .add, + .delete(2), + .updateNewEntryField("go in time"), + .updateNewEntryField("go backward in time"), + .add, + .check(0, true), + .check(3, true), + .check(3, false), + .deleteAllCompleted + ]) + + let expectedHistory = [ + Model(nextID: 0, newEntryField: "go forward in time", entries: []), + Model(nextID: 1, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: false) + ]), + Model(nextID: 2, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: false) + ]), + Model(nextID: 2, newEntryField: "delete this item", entries: [ + Entry(id: 0, description: "go forward in time", completed: false) + ]), + Model(nextID: 3, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: false), + Entry(id: 2, description: "delete this item", completed: false) + ]), + Model(nextID: 3, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: false) + ]), + Model(nextID: 3, newEntryField: "go in time", entries: [ + Entry(id: 0, description: "go forward in time", completed: false) + ]), + Model(nextID: 3, newEntryField: "go backward in time", entries: [ + Entry(id: 0, description: "go forward in time", completed: false) + ]), + Model(nextID: 4, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: false), + Entry(id: 3, description: "go backward in time", completed: false) + ]), + Model(nextID: 4, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: true), + Entry(id: 3, description: "go backward in time", completed: false) + ]), + Model(nextID: 4, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: true), + Entry(id: 3, description: "go backward in time", completed: true) + ]), + Model(nextID: 4, newEntryField: "", entries: [ + Entry(id: 0, description: "go forward in time", completed: true), + Entry(id: 3, description: "go backward in time", completed: false) + ]), + Model(nextID: 4, newEntryField: "", entries: [ + Entry(id: 3, description: "go backward in time", completed: false) + ]), + ] + + XCTAssertEqual(expectedHistory.count, actualHistory.count) + for (index, (expected, actual)) in zip(expectedHistory, actualHistory).enumerated() { + XCTAssertEqual(expected, actual, "History mismatch at step \(index)") + } + } +*/ + +} From d0c2463ed8d1724fab364f5c55bfb92dc186514f Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 17:38:55 -0500 Subject: [PATCH 10/16] Added working tests --- swift-todo/Sources/Todo/Combined.swift | 67 +++++++++++++++++++------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/swift-todo/Sources/Todo/Combined.swift b/swift-todo/Sources/Todo/Combined.swift index 3945409..74ddb0e 100644 --- a/swift-todo/Sources/Todo/Combined.swift +++ b/swift-todo/Sources/Todo/Combined.swift @@ -142,10 +142,8 @@ extension Array { } -import XCTest -@testable import Todo -class TodoTests: XCTestCase { +class TodoTests { var model = Model( entries: [ Entry(id: 10, description: "Pat head"), @@ -154,15 +152,15 @@ class TodoTests: XCTestCase { ) func testModelStartsEmpty() { - XCTAssertEqual(true, Model().entries.isEmpty) - XCTAssertEqual(0, Model().nextID) + XCTAssertEqual_B(true, Model().entries.isEmpty) + XCTAssertEqual_I(0, Model().nextID) } func testUpdateNewEntryField() { let newModel = Engine.run(on: model, applying: [ .updateNewEntryField("typing away") ]) - XCTAssertEqual("typing away", newModel.newEntryField) + XCTAssertEqual_S("typing away", newModel.newEntryField) } func testAdd() { @@ -170,11 +168,11 @@ class TodoTests: XCTestCase { .updateNewEntryField("hop on one foot"), .add ]) - XCTAssertEqual(3, newModel.entries.count) + XCTAssertEqual_I(3, newModel.entries.count) let newEntry = newModel.entries.last! - XCTAssertEqual(12, newEntry.id) - XCTAssertEqual("hop on one foot", newEntry.description) - XCTAssertEqual(false, newEntry.completed) + XCTAssertEqual_I(12, newEntry.id) + XCTAssertEqual_S("hop on one foot", newEntry.description) + XCTAssertEqual_B(false, newEntry.completed) } func testAddDoesNothingIfFieldIsBlank() { @@ -185,15 +183,15 @@ class TodoTests: XCTestCase { .add ]) - XCTAssertEqual(2, newModel.entries.count) + XCTAssertEqual_I(2, newModel.entries.count) } func testCheck() { let newModel = Engine.run(on: model, applying: [ .check(10, true), ]) - XCTAssertEqual(true, newModel.entries[0].completed) - XCTAssertEqual(false, newModel.entries[1].completed) + XCTAssertEqual_B(true, newModel.entries[0].completed) + XCTAssertEqual_B(false, newModel.entries[1].completed) } func testUncheck() { @@ -202,15 +200,15 @@ class TodoTests: XCTestCase { .check(11, true), .check(10, false), ]) - XCTAssertEqual(false, newModel.entries[0].completed) - XCTAssertEqual(true, newModel.entries[1].completed) + XCTAssertEqual_B(false, newModel.entries[0].completed) + XCTAssertEqual_B(true, newModel.entries[1].completed) } func testDelete() { let newModel = Engine.run(on: model, applying: [ .delete(10) ]) - XCTAssertEqual([11], newModel.entries.map { $0.id }) + XCTAssertEqual_A([11], newModel.entries.map { $0.id }) } func testDeleteAllCompleted() { @@ -218,7 +216,7 @@ class TodoTests: XCTestCase { .check(10, true), .deleteAllCompleted ]) - XCTAssertEqual([11], newModel.entries.map { $0.id }) + XCTAssertEqual_A([11], newModel.entries.map { $0.id }) } /* @@ -291,4 +289,39 @@ class TodoTests: XCTestCase { } */ + + private func XCTAssertEqual_I(_ expectedValue: Int, _ value: Int){ + if expectedValue != value{ + print("\(expectedValue) != \(value)") + } + } + private func XCTAssertEqual_B(_ expectedValue: Bool, _ value: Bool){ + if expectedValue != value{ + print("\(expectedValue) != \(value)") + } + } + private func XCTAssertEqual_S(_ expectedValue: String, _ value: String){ + if expectedValue != value{ + print("\(expectedValue) != \(value)") + } + } + private func XCTAssertEqual_A(_ expectedValue: [Int], _ value: [Int]){ + if expectedValue != value{ + print("\(expectedValue) != \(value)") + } + } + } + + + +let tests = TodoTests() +tests.testModelStartsEmpty() +tests.testUpdateNewEntryField() +tests.testAdd() +tests.testAddDoesNothingIfFieldIsBlank() +tests.testCheck() +tests.testUncheck() +tests.testDelete() +tests.testDeleteAllCompleted() + From 487cbaf5b188bb05cb022dabe2af643c51ab8807 Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 17:41:01 -0500 Subject: [PATCH 11/16] Reset test model before each test --- swift-todo/Sources/Todo/Combined.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/swift-todo/Sources/Todo/Combined.swift b/swift-todo/Sources/Todo/Combined.swift index 74ddb0e..efb2175 100644 --- a/swift-todo/Sources/Todo/Combined.swift +++ b/swift-todo/Sources/Todo/Combined.swift @@ -315,13 +315,27 @@ class TodoTests { -let tests = TodoTests() +var tests = TodoTests() tests.testModelStartsEmpty() + +tests = TodoTests() tests.testUpdateNewEntryField() + +tests = TodoTests() tests.testAdd() + +tests = TodoTests() tests.testAddDoesNothingIfFieldIsBlank() + +tests = TodoTests() tests.testCheck() + +tests = TodoTests() tests.testUncheck() + +tests = TodoTests() tests.testDelete() + +tests = TodoTests() tests.testDeleteAllCompleted() From 98c6bb2a7dd5806b52f49f9b4bc7eb45fa0cdfa1 Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 23:00:51 -0500 Subject: [PATCH 12/16] Added time travel tests --- swift-todo/Sources/Todo/Combined.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/swift-todo/Sources/Todo/Combined.swift b/swift-todo/Sources/Todo/Combined.swift index efb2175..c74515b 100644 --- a/swift-todo/Sources/Todo/Combined.swift +++ b/swift-todo/Sources/Todo/Combined.swift @@ -219,7 +219,7 @@ class TodoTests { XCTAssertEqual_A([11], newModel.entries.map { $0.id }) } -/* +//* func testTimeTravel() { let actualHistory = Engine.runWithHistory(on: Model(), applying: [ .updateNewEntryField("go forward in time"), @@ -282,12 +282,12 @@ class TodoTests { ]), ] - XCTAssertEqual(expectedHistory.count, actualHistory.count) + XCTAssertEqual_I(expectedHistory.count, actualHistory.count) for (index, (expected, actual)) in zip(expectedHistory, actualHistory).enumerated() { - XCTAssertEqual(expected, actual, "History mismatch at step \(index)") + XCTAssertEqual_mess(expected, actual, "History mismatch at step \(index)") } } -*/ +//*/ private func XCTAssertEqual_I(_ expectedValue: Int, _ value: Int){ @@ -310,6 +310,11 @@ class TodoTests { print("\(expectedValue) != \(value)") } } + private func XCTAssertEqual_mess(_ expectedValue: Model, _ value: Model, _ message: String){ + if expectedValue != value{ + print(message) + } + } } @@ -339,3 +344,6 @@ tests.testDelete() tests = TodoTests() tests.testDeleteAllCompleted() +tests = TodoTests() +tests.testTimeTravel() + From a9355ad667402244512b3e1fd92c3664dce72785 Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 23:01:07 -0500 Subject: [PATCH 13/16] Made model immutable for time travel --- swift-todo/Sources/Todo/Combined.swift | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/swift-todo/Sources/Todo/Combined.swift b/swift-todo/Sources/Todo/Combined.swift index c74515b..84621db 100644 --- a/swift-todo/Sources/Todo/Combined.swift +++ b/swift-todo/Sources/Todo/Combined.swift @@ -13,9 +13,10 @@ struct Engine { } static func runWithHistory(on model: Model, applying messages: [Message]) -> [Model] { + var lastModel = model return messages.map { message in - message.apply(to: model) - return model + lastModel = message.apply(to: lastModel) + return lastModel } } } @@ -37,7 +38,8 @@ enum Message { case delete(Int) case deleteAllCompleted - func apply(to model: Model) { + func apply(to oldModel: Model) -> Model { + var model = oldModel switch(self) { case .add: if !model.newEntryField.isBlank() { @@ -50,11 +52,17 @@ enum Message { model.newEntryField = str case .check(let id, let isCompleted): + var newEntries: [Entry] = [] for entry in model.entries { if(entry.id == id) { - entry.completed = isCompleted + var newEntry = entry + newEntry.completed = isCompleted + newEntries.append(newEntry) + } else{ + newEntries.append(entry) } } + model.entries = newEntries case .delete(let id): model.entries.remove { $0.id == id } @@ -62,11 +70,12 @@ enum Message { case .deleteAllCompleted: model.entries.remove { $0.completed } } + return model } } -class Model { +struct Model { var entries: [Entry] var newEntryField: String var nextID: Int @@ -79,7 +88,7 @@ class Model { } } -class Entry { +struct Entry { var id: Int var description: String var completed: Bool From e9f29199836389e8c59f5464143b17f0efbd34c6 Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 23:03:15 -0500 Subject: [PATCH 14/16] Made Model and Entry structs --- swift-todo/Sources/Todo/Model.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swift-todo/Sources/Todo/Model.swift b/swift-todo/Sources/Todo/Model.swift index 1cca801..d50d863 100644 --- a/swift-todo/Sources/Todo/Model.swift +++ b/swift-todo/Sources/Todo/Model.swift @@ -1,4 +1,4 @@ -class Model { +struct Model { var entries: [Entry] var newEntryField: String var nextID: Int @@ -11,7 +11,7 @@ class Model { } } -class Entry { +struct Entry { var id: Int var description: String var completed: Bool From 8e624befdae91698359c392afe97032b4e9f7ad0 Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 23:03:34 -0500 Subject: [PATCH 15/16] Updated Engine to assume new model --- swift-todo/Sources/Todo/Engine.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/swift-todo/Sources/Todo/Engine.swift b/swift-todo/Sources/Todo/Engine.swift index e3b14a7..2f23d57 100644 --- a/swift-todo/Sources/Todo/Engine.swift +++ b/swift-todo/Sources/Todo/Engine.swift @@ -13,9 +13,10 @@ struct Engine { } static func runWithHistory(on model: Model, applying messages: [Message]) -> [Model] { + var lastModel = model return messages.map { message in - message.apply(to: model) - return model + lastModel = message.apply(to: lastModel) + return lastModel } } } From 1da744a24cd0d7a21e7c4842a63ea7b1dcef7092 Mon Sep 17 00:00:00 2001 From: Tara Date: Sat, 5 May 2018 23:04:11 -0500 Subject: [PATCH 16/16] Updated apply(to:) to return updated model --- swift-todo/Sources/Todo/Message.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/swift-todo/Sources/Todo/Message.swift b/swift-todo/Sources/Todo/Message.swift index 92dd868..dcef647 100644 --- a/swift-todo/Sources/Todo/Message.swift +++ b/swift-todo/Sources/Todo/Message.swift @@ -14,7 +14,8 @@ enum Message { case delete(Int) case deleteAllCompleted - func apply(to model: Model) { + func apply(to oldModel: Model) -> Model { + var model = oldModel switch(self) { case .add: if !model.newEntryField.isBlank() { @@ -27,11 +28,17 @@ enum Message { model.newEntryField = str case .check(let id, let isCompleted): + var newEntries: [Entry] = [] for entry in model.entries { if(entry.id == id) { - entry.completed = isCompleted + var newEntry = entry + newEntry.completed = isCompleted + newEntries.append(newEntry) + } else{ + newEntries.append(entry) } } + model.entries = newEntries case .delete(let id): model.entries.remove { $0.id == id } @@ -39,5 +46,6 @@ enum Message { case .deleteAllCompleted: model.entries.remove { $0.completed } } + return model } }