2024-03-22 14:09:58 +01:00
|
|
|
//
|
|
|
|
// FeedListViewModelTests.swift
|
|
|
|
// ReviewsFeedTests
|
|
|
|
//
|
|
|
|
// Created by Javier Cicchelli on 22/03/2024.
|
|
|
|
// Copyright © 2024 Röck+Cöde. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Combine
|
|
|
|
import ReviewsFoundationKit
|
|
|
|
import ReviewsiTunesKit
|
|
|
|
import XCTest
|
|
|
|
|
|
|
|
@testable import ReviewsFeed
|
|
|
|
|
|
|
|
final class FeedListViewModelTests: XCTestCase {
|
|
|
|
|
|
|
|
// MARK: Properties
|
|
|
|
private var sut: FeedListViewController.ViewModel!
|
|
|
|
|
|
|
|
private var cancellables: Set<AnyCancellable> = []
|
|
|
|
|
|
|
|
// MARK: Setup
|
|
|
|
override func setUp() async throws {
|
|
|
|
sut = .init(
|
|
|
|
configuration: .init(session: .mock),
|
|
|
|
coordination: nil
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tearDown() async throws {
|
|
|
|
cancellables.removeAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Initialisers tests
|
|
|
|
func testInit() {
|
|
|
|
// GIVEN
|
|
|
|
// WHEN
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(sut.filter, .all)
|
|
|
|
XCTAssertFalse(sut.isFilterEnabled)
|
|
|
|
XCTAssertFalse(sut.isFiltering)
|
|
|
|
XCTAssertFalse(sut.isLoading)
|
|
|
|
XCTAssertFalse(sut.isWordsShowing)
|
|
|
|
XCTAssertTrue(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 0)
|
|
|
|
XCTAssertEqual(sut.state, .initial)
|
|
|
|
XCTAssertTrue(sut.words.isEmpty)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Functions tests
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFetch_allItems_whenResponseOK_withSomeItems() async {
|
2024-03-22 14:09:58 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var isLoading: [Bool] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$isLoading
|
|
|
|
.collect(3)
|
|
|
|
.sink { value in
|
|
|
|
isLoading.append(contentsOf: value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
await sut.fetch()
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 14:09:58 +01:00
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(isLoading, [false, true, false])
|
|
|
|
XCTAssertTrue(sut.isFilterEnabled)
|
|
|
|
XCTAssertFalse(sut.isWordsShowing)
|
|
|
|
XCTAssertFalse(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.items.count, 5)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 5)
|
|
|
|
XCTAssertEqual(sut.state, .populated)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFetch_allItems_whenResponseOK_withNoItems() async {
|
2024-03-22 14:09:58 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var isLoading: [Bool] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .none)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$isLoading
|
|
|
|
.collect(3)
|
|
|
|
.sink { value in
|
|
|
|
isLoading.append(contentsOf: value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
await sut.fetch()
|
2024-03-22 14:09:58 +01:00
|
|
|
|
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(isLoading, [false, true, false])
|
|
|
|
XCTAssertFalse(sut.isFilterEnabled)
|
|
|
|
XCTAssertFalse(sut.isWordsShowing)
|
|
|
|
XCTAssertTrue(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 0)
|
|
|
|
XCTAssertEqual(sut.state, .empty)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFetch_allItems_whenResponseNotOK() async {
|
2024-03-22 14:09:58 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var isLoading: [Bool] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(statusCode: 404)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$isLoading
|
|
|
|
.collect(3)
|
|
|
|
.sink { value in
|
|
|
|
isLoading.append(contentsOf: value)
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 14:09:58 +01:00
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
await sut.fetch()
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 14:09:58 +01:00
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(isLoading, [false, true, false])
|
|
|
|
XCTAssertFalse(sut.isFilterEnabled)
|
|
|
|
XCTAssertFalse(sut.isWordsShowing)
|
|
|
|
XCTAssertTrue(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 0)
|
|
|
|
XCTAssertEqual(sut.state, .error)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFetch_filteredItems_whenResponseOK_withSomeItems() async {
|
2024-03-22 14:09:58 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var isLoading: [Bool] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$isLoading
|
|
|
|
.collect(3)
|
|
|
|
.sink { value in
|
|
|
|
isLoading.append(contentsOf: value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
2024-03-22 14:58:13 +01:00
|
|
|
|
|
|
|
sut.filter(by: .only1Star)
|
2024-03-22 15:15:47 +01:00
|
|
|
|
|
|
|
await sut.fetch()
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 14:09:58 +01:00
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(isLoading, [false, true, false])
|
2024-03-22 14:58:13 +01:00
|
|
|
XCTAssertEqual(sut.filter, .only1Star)
|
2024-03-22 14:09:58 +01:00
|
|
|
XCTAssertTrue(sut.isFilterEnabled)
|
|
|
|
XCTAssertTrue(sut.isWordsShowing)
|
|
|
|
XCTAssertFalse(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.items.count, 1)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 2)
|
|
|
|
XCTAssertEqual(sut.state, .populated)
|
|
|
|
XCTAssertFalse(sut.words.isEmpty)
|
|
|
|
XCTAssertEqual(sut.words.count, 3)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFetch_filteredItems_whenResponseOK_withNoItems() async {
|
2024-03-22 14:09:58 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var isLoading: [Bool] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .none)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$isLoading
|
|
|
|
.collect(3)
|
|
|
|
.sink { value in
|
|
|
|
isLoading.append(contentsOf: value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2024-03-22 14:58:13 +01:00
|
|
|
sut.filter(by: .only1Star)
|
2024-03-22 15:15:47 +01:00
|
|
|
|
|
|
|
await sut.fetch()
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 14:09:58 +01:00
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(isLoading, [false, true, false])
|
2024-03-22 14:58:13 +01:00
|
|
|
XCTAssertEqual(sut.filter, .only1Star)
|
2024-03-22 14:09:58 +01:00
|
|
|
XCTAssertFalse(sut.isFilterEnabled)
|
|
|
|
XCTAssertFalse(sut.isWordsShowing)
|
|
|
|
XCTAssertTrue(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 0)
|
|
|
|
XCTAssertEqual(sut.state, .empty)
|
|
|
|
XCTAssertTrue(sut.words.isEmpty)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFetch_filteredItems_whenResponseNotOK() async {
|
2024-03-22 14:09:58 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var isLoading: [Bool] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(statusCode: 404)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$isLoading
|
|
|
|
.collect(3)
|
|
|
|
.sink { value in
|
|
|
|
isLoading.append(contentsOf: value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
2024-03-22 14:58:13 +01:00
|
|
|
|
|
|
|
sut.filter(by: .only1Star)
|
2024-03-22 15:15:47 +01:00
|
|
|
|
|
|
|
await sut.fetch()
|
2024-03-22 14:58:13 +01:00
|
|
|
|
2024-03-22 14:09:58 +01:00
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(isLoading, [false, true, false])
|
2024-03-22 14:58:13 +01:00
|
|
|
XCTAssertEqual(sut.filter, .only1Star)
|
2024-03-22 14:09:58 +01:00
|
|
|
XCTAssertFalse(sut.isFilterEnabled)
|
|
|
|
XCTAssertFalse(sut.isWordsShowing)
|
|
|
|
XCTAssertTrue(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 0)
|
|
|
|
XCTAssertEqual(sut.state, .error)
|
|
|
|
XCTAssertTrue(sut.words.isEmpty)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFilter_forNewOption_withSomeItems() async {
|
2024-03-22 14:58:13 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var filter: [FilterOption] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$filter
|
|
|
|
.dropFirst()
|
|
|
|
.sink { value in
|
|
|
|
filter.append(value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
await sut.fetch()
|
|
|
|
|
2024-03-22 14:58:13 +01:00
|
|
|
sut.filter(by: .only1Star)
|
|
|
|
|
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(filter, [.only1Star])
|
|
|
|
XCTAssertFalse(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.items.count, 1)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 2)
|
|
|
|
XCTAssertFalse(sut.words.isEmpty)
|
|
|
|
XCTAssertEqual(sut.words.count, 3)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFilter_forNewOption_withNoItems() async {
|
2024-03-22 14:58:13 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var filter: [FilterOption] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .none)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$filter
|
|
|
|
.dropFirst()
|
|
|
|
.sink { value in
|
|
|
|
filter.append(value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
await sut.fetch()
|
|
|
|
|
2024-03-22 14:58:13 +01:00
|
|
|
sut.filter(by: .only1Star)
|
|
|
|
|
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(filter, [.only1Star])
|
|
|
|
XCTAssertTrue(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.items.count, 0)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 0)
|
|
|
|
XCTAssertTrue(sut.words.isEmpty)
|
|
|
|
XCTAssertEqual(sut.words.count, 0)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFilter_forSameOption_withSomeItems() async {
|
2024-03-22 14:58:13 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var filter: [FilterOption] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$filter
|
|
|
|
.dropFirst()
|
|
|
|
.sink { value in
|
|
|
|
filter.append(value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
await sut.fetch()
|
|
|
|
|
2024-03-22 14:58:13 +01:00
|
|
|
sut.filter(by: .only1Star)
|
|
|
|
sut.filter(by: .only1Star)
|
|
|
|
|
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(filter, [.only1Star])
|
|
|
|
XCTAssertFalse(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.items.count, 1)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 2)
|
|
|
|
XCTAssertFalse(sut.words.isEmpty)
|
|
|
|
XCTAssertEqual(sut.words.count, 3)
|
|
|
|
}
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testFilter_forSameOption_withNoItems() async {
|
2024-03-22 14:58:13 +01:00
|
|
|
let expectation = XCTestExpectation()
|
|
|
|
|
|
|
|
var filter: [FilterOption] = []
|
|
|
|
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .none)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
sut.$filter
|
|
|
|
.dropFirst()
|
|
|
|
.sink { value in
|
|
|
|
filter.append(value)
|
|
|
|
|
|
|
|
expectation.fulfill()
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
await sut.fetch()
|
|
|
|
|
2024-03-22 14:58:13 +01:00
|
|
|
sut.filter(by: .only1Star)
|
|
|
|
|
|
|
|
wait(for: [expectation], timeout: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertEqual(filter, [.only1Star])
|
|
|
|
XCTAssertTrue(sut.items.isEmpty)
|
|
|
|
XCTAssertEqual(sut.items.count, 0)
|
|
|
|
XCTAssertEqual(sut.itemsCount, 0)
|
|
|
|
XCTAssertTrue(sut.words.isEmpty)
|
|
|
|
XCTAssertEqual(sut.words.count, 0)
|
|
|
|
}
|
2024-03-22 14:09:58 +01:00
|
|
|
|
2024-03-22 15:15:47 +01:00
|
|
|
func testItemFor_index_withSomeItems() async {
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
await sut.fetch()
|
|
|
|
|
|
|
|
let item = sut.item(for: 0)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertNotNil(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testItemFor_indexOutOfBounds_withSomeItems() async {
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
await sut.fetch()
|
|
|
|
|
|
|
|
let item = sut.item(for: 10)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertNil(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testItemFor_index_withFilteredItems() async {
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
await sut.fetch()
|
|
|
|
|
|
|
|
sut.filter(by: .only1Star)
|
|
|
|
|
|
|
|
let item = sut.item(for: 1)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertNil(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testItemFor_indexOutOfBounds_withFilteredItems() async {
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .sample)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
await sut.fetch()
|
|
|
|
|
|
|
|
sut.filter(by: .only1Star)
|
|
|
|
|
|
|
|
let item = sut.item(for: 2)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertNil(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testItemFor_index_withNoItems() async {
|
|
|
|
// GIVEN
|
|
|
|
MockURLProtocol.response = .init(
|
|
|
|
statusCode: 200,
|
|
|
|
object: Feed(entries: .none)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WHEN
|
|
|
|
await sut.fetch()
|
|
|
|
|
|
|
|
let item = sut.item(for: 0)
|
|
|
|
|
|
|
|
// THEN
|
|
|
|
XCTAssertNil(item)
|
|
|
|
}
|
|
|
|
|
2024-03-22 14:09:58 +01:00
|
|
|
}
|