diff --git a/Sources/RangeState/RangeProcessor.swift b/Sources/RangeState/RangeProcessor.swift index 956af7e..d032d82 100644 --- a/Sources/RangeState/RangeProcessor.swift +++ b/Sources/RangeState/RangeProcessor.swift @@ -182,7 +182,12 @@ extension RangeProcessor { } public func didChangeContent(in range: NSRange, delta: Int) { - let mutation = RangeMutation(range: range, delta: delta) + let limit = (maximumProcessedLocation ?? 0) + delta + + precondition(limit >= 0) + precondition(limit <= contentLength) + + let mutation = RangeMutation(range: range, delta: delta, limit: limit) didChangeContent(mutation) } diff --git a/Tests/RangeStateTests/RangeProcessorTests.swift b/Tests/RangeStateTests/RangeProcessorTests.swift index 3bea476..acbdac3 100644 --- a/Tests/RangeStateTests/RangeProcessorTests.swift +++ b/Tests/RangeStateTests/RangeProcessorTests.swift @@ -3,6 +3,20 @@ import XCTest import RangeState import Rearrange +@MainActor +final class MockChangeHandler { + var mutations = [RangeMutation]() + + var changeCompleted: @MainActor () -> Void = { } + + func handleChange(_ mutation: RangeMutation, completion: @MainActor @escaping () -> Void) { + mutations.append(mutation) + + changeCompleted() + completion() + } +} + final class RangeProcessorTests: XCTestCase { @MainActor func testSynchronousFill() { @@ -51,4 +65,41 @@ final class RangeProcessorTests: XCTestCase { XCTAssert(processor.processed(10)) } + + @MainActor + func testInsertWithEverythingProcessed() { + let exp = expectation(description: "mutation") + exp.expectedFulfillmentCount = 2 + + let handler = MockChangeHandler() + + handler.changeCompleted = { + exp.fulfill() + } + + var content = StringContent(string: "abcde") + + let processor = RangeProcessor( + configuration: .init( + lengthProvider: { content.currentLength }, + changeHandler: handler.handleChange + ) + ) + + XCTAssertTrue(processor.processLocation(5, mode: .required)) + XCTAssertTrue(processor.processed(5)) + + // insert a character + content.string = "abcdef" + processor.didChangeContent(in: NSRange(5..<5), delta: 1) + + wait(for: [exp], enforceOrder: true) + + let expected = [ + RangeMutation(range: NSRange(0..<0), delta: 5, limit: nil), + RangeMutation(range: NSRange(5..<5), delta: 1, limit: 6), + ] + + XCTAssertEqual(handler.mutations, expected) + } } diff --git a/Tests/RangeStateTests/RangeValidatorTests.swift b/Tests/RangeStateTests/RangeValidatorTests.swift index 1248436..22bec95 100644 --- a/Tests/RangeStateTests/RangeValidatorTests.swift +++ b/Tests/RangeStateTests/RangeValidatorTests.swift @@ -5,25 +5,6 @@ import RangeState final class RangeValidatorTests: XCTestCase { typealias StringValidator = RangeValidator - struct StringContent: VersionedContent { - private var version: Int = 0 - var string: String { - didSet { version += 1 } - } - - init(string: String) { - self.string = string - } - - var currentVersion: Int { version } - - func length(for version: Version) -> Int? { - guard version == currentVersion else { return nil } - - return string.utf16.count - } - } - @MainActor func testContentChange() { var content = StringContent(string: "abc") diff --git a/Tests/RangeStateTests/StringContent.swift b/Tests/RangeStateTests/StringContent.swift new file mode 100644 index 0000000..036d657 --- /dev/null +++ b/Tests/RangeStateTests/StringContent.swift @@ -0,0 +1,23 @@ +import Foundation + +import RangeState + +struct StringContent: VersionedContent { + private var version: Int = 0 + + var string: String { + didSet { version += 1 } + } + + init(string: String) { + self.string = string + } + + var currentVersion: Int { version } + + func length(for version: Int) -> Int? { + guard version == currentVersion else { return nil } + + return string.utf16.count + } +}