Skip to content

Instantly share code, notes, and snippets.

//
// ShoppingCart.swift
// CombineShoppingCart
//
// Created by Piotr on 25/03/2020.
// Copyright © 2020 Piotr. All rights reserved.
//
import Combine
@pzmudzinski
pzmudzinski / CombineShoppingCartTests.swift
Last active May 20, 2021 01:39
CombineShoppingCartTests.swift
//
// CombineShoppingCartTests.swift
// CombineShoppingCartTests
//
// Created by Piotr on 25/03/2020.
// Copyright © 2020 Piotr. All rights reserved.
//
import XCTest
import Combine

Reactive Shopping Cart with Swift and Combine

Overview

Reactive programming was quite popular among iOS/OSX developers in recent years, although there was no "official" API. Community created wonderful RxSwit project, being one of most popular 3rd party tools used in Apple ecosystem. Things has changed little bit once Apple released their own Combine framework which introduced similar API and Rx-ish thinking.

Today, we are going to try it out via practical example of shopping cart functionality. We will be discussing how to model internal and external events happening in our buying process and thinking about posssible future extensions. Article assumes you have basic knowledge about swift and RxSwift or Combine.

Basics

@pzmudzinski
pzmudzinski / auth.js
Created October 4, 2019 15:04
Custom jest expect extension for GraphQL auth tests
expect.extend({
toBeForbidden(received) {
const forbiddenErrorCode = 'FORBIDDEN';
if (!received.errors || received.errors.length === 0) {
return {
message: `No errors in response`,
pass: false
}
extension Observable {
func rebound(after interval: RxTimeInterval, map: @escaping (Element) -> Element = { return $0 }) -> Observable<Element> {
return flatMap { element in
return Observable.just(map(element))
.delay(interval, scheduler: MainScheduler.instance)
.startWith(element)
}
}
}
let errorOrCompletedColor = viewModel.triggerDiscount
.executionObservables
.flatMapLatest { $0 }
.materialize()
.map { event -> UIColor in
switch event {
case .error(_):
return TriggerDiscountView.errorColor
case .next(_), .completed:
return TriggerDiscountView.successColor
func testDeleteCommandRemovesItemFromCollection() {
let itemsObserver = testScheduler.createObserver([Book].self)
viewModel.items.bind(to: itemsObserver)
.disposed(by: disposeBag)
testScheduler.createHotObservable(
[Recorded.next(100, IndexPath(row: 0, section: 0))]
)
.bind(to: viewModel.deleteCommand)
class BooksViewModelTest: XCTestCase {
var viewModel: BooksViewModel!
var testScheduler: TestScheduler!
var disposeBag: DisposeBag!
override func setUp() {
viewModel = BooksViewModel(api: DummyApi())
testScheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
pod 'RxBlocking', '~> 4.0'
pod 'RxTest', '~> 4.0'
api.getBooks()
.map { books -> BookCollectionAction in return .collectionRefreshed(withBooks: books) }
.bind(to: collectionActions)
// ...
let bookMarkedForDeletion = deleteCommand
.withLatestFrom(items) { index, items in return items[index.row] }
// ...