diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f9b575..ba9ff153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. * Adds support of mutable CellViewModels. * Changes `TableViewSectionedDataSource` default parameters `canEditRowAtIndexPath` and `canMoveRowAtIndexPath` to align with iOS default behavior #383 +* Fixes failing duplicate identity detection ## [4.0.1](https://github.com/RxSwiftCommunity/RxDataSources/releases/tag/4.0.1) diff --git a/Sources/Differentiator/Diff.swift b/Sources/Differentiator/Diff.swift index 7e4b8712..a393adb0 100644 --- a/Sources/Differentiator/Diff.swift +++ b/Sources/Differentiator/Diff.swift @@ -199,11 +199,19 @@ public enum Diff { dictionary[key] = i } + var finalKeys = Set<OptimizedIdentity<Identity>>() + for (i, items) in finalItemCache.enumerated() { for j in 0 ..< items.count { let item = items[j] var identity = item.identity let key = OptimizedIdentity(&identity) + + if finalKeys.contains(key) { + throw Error.duplicateItem(item: item) + } + finalKeys.insert(key) + guard let initialItemPathIndex = dictionary[key] else { continue } @@ -518,8 +526,16 @@ public enum Diff { var initialSectionData = ContiguousArray<SectionAssociatedData>(repeating: SectionAssociatedData.initial, count: initialSections.count) var finalSectionData = ContiguousArray<SectionAssociatedData>(repeating: SectionAssociatedData.initial, count: finalSections.count) + var finalSectionIdentities = Set<Section.Identity>() + for (i, section) in finalSections.enumerated() { finalSectionData[i].itemCount = finalSections[i].items.count + + if finalSectionIdentities.contains(section.identity) { + throw Error.duplicateSection(section: section) + } + finalSectionIdentities.insert(section.identity) + guard let initialSectionIndex = initialSectionIndexes[section.identity] else { continue } diff --git a/Tests/RxDataSourcesTests/AlgorithmTests.swift b/Tests/RxDataSourcesTests/AlgorithmTests.swift index e90a464c..844b537f 100644 --- a/Tests/RxDataSourcesTests/AlgorithmTests.swift +++ b/Tests/RxDataSourcesTests/AlgorithmTests.swift @@ -320,8 +320,77 @@ extension AlgorithmTests { // errors extension AlgorithmTests { - func testThrowsErrorOnDuplicateItem() { - let initial: [s] = [ + func testThrowsErrorOnDuplicateItem_inInitialSections() { + let sections: [s] = [ + s(1, [ + i(1111, ""), + i(1111, "") + ]) + ] + + do { + _ = try Diff.differencesForSectionedView(initialSections: sections, finalSections: sections) + XCTFail("Should throw exception") + } + catch let exception { + guard case let .duplicateItem(item) = exception as! Diff.Error else { + XCTFail("Not required error") + return + } + + XCTAssertEqual(item as! i, i(1111, "")) + } + } + + func testThrowsErrorOnDuplicateItem_inFinalSections() { + let final: [s] = [ + s(1, [ + i(1111, ""), + i(1111, "") + ]) + ] + + do { + _ = try Diff.differencesForSectionedView(initialSections: [], finalSections: final) + XCTFail("Should throw exception") + } + catch let exception { + guard case let .duplicateItem(item) = exception as! Diff.Error else { + XCTFail("Not required error") + return + } + + XCTAssertEqual(item as! i, i(1111, "")) + } + } + + func testThrowsErrorOnDuplicateItemInDifferentSection_inInitialSections() { + let sections: [s] = [ + s(1, [ + i(1111, "") + ]), + s(2, [ + i(1111, "") + ]) + + ] + + do { + _ = try Diff.differencesForSectionedView(initialSections: sections, finalSections: sections) + XCTFail("Should throw exception") + } + catch let exception { + guard case let .duplicateItem(item) = exception as! Diff.Error else { + XCTFail("Not required error") + return + } + + XCTAssertEqual(item as! i, i(1111, "")) + } + } + + func testThrowsErrorOnDuplicateItemInDifferentSection_inFinalSections() { + let final: [s] = [ s(1, [ i(1111, "") ]), @@ -332,7 +401,7 @@ extension AlgorithmTests { ] do { - _ = try Diff.differencesForSectionedView(initialSections: initial, finalSections: initial) + _ = try Diff.differencesForSectionedView(initialSections: [], finalSections: final) XCTFail("Should throw exception") } catch let exception { @@ -345,8 +414,33 @@ extension AlgorithmTests { } } - func testThrowsErrorOnDuplicateSection() { - let initial: [s] = [ + func testThrowsErrorOnDuplicateSection_inInitialSections() { + let sections: [s] = [ + s(1, [ + i(1111, "") + ]), + s(1, [ + i(1112, "") + ]) + ] + + do { + _ = try Diff.differencesForSectionedView(initialSections: sections, finalSections: sections) + XCTFail("Should throw exception") + } + catch let exception { + guard case let .duplicateSection(section) = exception as! Diff.Error else { + XCTFail("Not required error") + return + } + + XCTAssertEqual(section as! s, s(1, [ + i(1112, "") + ])) + } + } + func testThrowsErrorOnDuplicateSection_inFinalSections() { + let final: [s] = [ s(1, [ i(1111, "") ]), @@ -357,7 +451,7 @@ extension AlgorithmTests { ] do { - _ = try Diff.differencesForSectionedView(initialSections: initial, finalSections: initial) + _ = try Diff.differencesForSectionedView(initialSections: [], finalSections: final) XCTFail("Should throw exception") } catch let exception {