What is the best practice or technique to keep a @Published property normalized? Supposed that we have a ViewModel which expose a @Published text property, which can be updated from the ViewModel / TextField (SwiftUI) and we want to remove any link that the user input in the field.
I wrote this example with test, but one of my issue is that on the subscription from the test, I'm receiving values in the wrong order (it sounds that mutating the text in the handleEvents is not so right)
class ViewModel: ObservableObject {
@Published var text: String?
private var cancellables = Set<AnyCancellable>()
init() {
setupObservers()
}
private func setupObservers() {
$text
.removeDuplicates() // use removeDuplicates to avoid infinite loop
.compactMap({ $0 })
.handleEvents(receiveOutput: { [weak self] in self?.cleanupLinks(from: $0) })
.sink(receiveValue: { _ in })
.store(in: &cancellables)
}
private func cleanupLinks(from text: String) {
//dummy implementation
self.text = text.replacingOccurrences(of: "https://any-url.com", with: "")
}
}
// TEST
class SimpleReproTests: XCTestCase {
func test_text_cleanupLinks() {
let sut = ViewModel()
let exp = expectation(description: "Wait for text updates")
exp.expectedFulfillmentCount = 2
var receivedValues = [String?]()
let cancellable = sut.$text.dropFirst(2).sink {
receivedValues.append($0)
exp.fulfill()
}
sut.text = "Hello World! https://any-url.com"
wait(for: [exp], timeout: 1.0)
XCTAssertEqual(receivedValues, ["Hello World! https://any-url.com", "Hello World! "])
cancellable.cancel()
}
}
Right now, when running the test I'm receiving the values out of order (which ofc doesn't match the expectation):
["Hello World! ", "Hello World! https://any-url.com"]