-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Assorted fixes for collections of state #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
05cf55d
136f49b
8aac787
0418eb7
4e89467
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| name: Format | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branch: 'master' | ||
|
|
||
| jobs: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -194,6 +194,17 @@ public struct Reducer<State, Action, Environment> { | |
| guard let (index, localAction) = toLocalAction.extract(from: globalAction) else { | ||
| return .none | ||
| } | ||
| // NB: This does not need to be a fatal error because of the index subscript that follows it. | ||
| assert( | ||
| index < globalState[keyPath: toLocalState].endIndex, | ||
| """ | ||
| Index out of range. This can happen when a reducer that can remove the last element from \ | ||
| an array is then combined with a "forEach" from that array. To avoid this and other \ | ||
| index-related gotchas, consider using an "IdentifiedArray" of state instead. Or, combine \ | ||
| your reducers so that the "forEach" comes before any reducer that can remove elements from \ | ||
| its array. | ||
| """ | ||
| ) | ||
| return self.reducer( | ||
| &globalState[keyPath: toLocalState][index], | ||
| localAction, | ||
|
|
@@ -238,7 +249,7 @@ public struct Reducer<State, Action, Environment> { | |
| guard let (id, localAction) = toLocalAction.extract(from: globalAction) else { return .none } | ||
| return self.optional | ||
| .reducer( | ||
| &globalState[keyPath: toLocalState][id], | ||
| &globalState[keyPath: toLocalState][id: id], | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated the interface to take an explicit |
||
| localAction, | ||
| toLocalEnvironment(globalEnvironment) | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,12 +92,15 @@ where ID: Hashable { | |
| } | ||
| } | ||
|
|
||
| public subscript(id: ID) -> Element? { | ||
| public subscript(id id: ID) -> Element? { | ||
| get { | ||
| self.dictionary[id] | ||
| } | ||
| _modify { | ||
| yield &self.dictionary[id] | ||
| if self.dictionary[id] == nil { | ||
| self.ids.removeAll(where: { $0 == id }) | ||
| } | ||
| } | ||
|
Comment on lines
99
to
104
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We weren't properly cleaning up the |
||
| } | ||
|
|
||
|
|
@@ -115,6 +118,7 @@ where ID: Hashable { | |
| } | ||
| } | ||
|
|
||
| @discardableResult | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stumbled upon this warning when writing tests. |
||
| public mutating func remove(at position: Int) -> Element { | ||
| let id = self.ids.remove(at: position) | ||
| let element = self.dictionary[id]! | ||
|
|
@@ -138,7 +142,7 @@ where ID: Hashable { | |
| } | ||
|
|
||
| public mutating func remove(atOffsets offsets: IndexSet) { | ||
| for offset in offsets { | ||
| for offset in offsets.reversed() { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests caught a bug here, as well! |
||
| _ = self.remove(at: offset) | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import XCTest | ||
|
|
||
| @testable import ComposableArchitecture | ||
|
|
||
| final class IdentifiedArrayTests: XCTestCase { | ||
| func testIdSubscript() { | ||
| struct User: Equatable, Identifiable { | ||
| let id: Int | ||
| var name: String | ||
| } | ||
|
|
||
| var array: IdentifiedArray = [User(id: 1, name: "Blob")] | ||
|
|
||
| XCTAssertEqual(array[id: 1], .some(User(id: 1, name: "Blob"))) | ||
|
|
||
| array[id: 1] = nil | ||
| XCTAssertEqual(array, []) | ||
| } | ||
|
|
||
| func testInsert() { | ||
| struct User: Equatable, Identifiable { | ||
| let id: Int | ||
| var name: String | ||
| } | ||
|
|
||
| var array: IdentifiedArray = [User(id: 1, name: "Blob")] | ||
|
|
||
| array.insert(User(id: 2, name: "Blob Jr."), at: 0) | ||
| XCTAssertEqual(array, [User(id: 2, name: "Blob Jr."), User(id: 1, name: "Blob")]) | ||
| } | ||
|
|
||
| func testInsertContentsOf() { | ||
| struct User: Equatable, Identifiable { | ||
| let id: Int | ||
| var name: String | ||
| } | ||
|
|
||
| var array: IdentifiedArray = [User(id: 1, name: "Blob")] | ||
|
|
||
| array.insert(contentsOf: [User(id: 3, name: "Blob Sr."), User(id: 2, name: "Blob Jr.")], at: 0) | ||
| XCTAssertEqual( | ||
| array, | ||
| [User(id: 3, name: "Blob Sr."), User(id: 2, name: "Blob Jr."), User(id: 1, name: "Blob")] | ||
| ) | ||
| } | ||
|
|
||
| func testRemoveAt() { | ||
| struct User: Equatable, Identifiable { | ||
| let id: Int | ||
| var name: String | ||
| } | ||
|
|
||
| var array: IdentifiedArray = [ | ||
| User(id: 3, name: "Blob Sr."), | ||
| User(id: 2, name: "Blob Jr."), | ||
| User(id: 1, name: "Blob"), | ||
| ] | ||
|
|
||
| array.remove(at: 1) | ||
| XCTAssertEqual(array, [User(id: 3, name: "Blob Sr."), User(id: 1, name: "Blob")]) | ||
| } | ||
|
|
||
| func testRemoveAllWhere() { | ||
| struct User: Equatable, Identifiable { | ||
| let id: Int | ||
| var name: String | ||
| } | ||
|
|
||
| var array: IdentifiedArray = [ | ||
| User(id: 3, name: "Blob Sr."), | ||
| User(id: 2, name: "Blob Jr."), | ||
| User(id: 1, name: "Blob"), | ||
| ] | ||
|
|
||
| array.removeAll(where: { $0.name.starts(with: "Blob ") }) | ||
| XCTAssertEqual(array, [User(id: 1, name: "Blob")]) | ||
| } | ||
|
|
||
| func testRemoveAtOffsets() { | ||
| struct User: Equatable, Identifiable { | ||
| let id: Int | ||
| var name: String | ||
| } | ||
|
|
||
| var array: IdentifiedArray = [ | ||
| User(id: 3, name: "Blob Sr."), | ||
| User(id: 2, name: "Blob Jr."), | ||
| User(id: 1, name: "Blob"), | ||
| ] | ||
|
|
||
| array.remove(atOffsets: [0, 2]) | ||
| XCTAssertEqual(array, [User(id: 2, name: "Blob Jr.")]) | ||
| } | ||
|
|
||
| func testMoveFromOffsets() { | ||
| struct User: Equatable, Identifiable { | ||
| let id: Int | ||
| var name: String | ||
| } | ||
|
|
||
| var array: IdentifiedArray = [ | ||
| User(id: 3, name: "Blob Sr."), | ||
| User(id: 2, name: "Blob Jr."), | ||
| User(id: 1, name: "Blob"), | ||
| ] | ||
|
|
||
| array.move(fromOffsets: [0], toOffset: 2) | ||
| XCTAssertEqual( | ||
| array, | ||
| [User(id: 2, name: "Blob Jr."), User(id: 3, name: "Blob Sr."), User(id: 1, name: "Blob")] | ||
| ) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error message should help fix #21. We can finesse the wording if any of it could be improved.