diff --git a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift index 0403702..992e180 100644 --- a/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift +++ b/Frameworks/Feed/Bundle/Sources/Logic/View Models/FeedListViewModel.swift @@ -20,14 +20,14 @@ extension FeedListViewController { private let topWords: TopWordsUseCase = .init() // MARK: Properties - @Published var filter: FilterOption = .all - @Published var isFilterEnabled: Bool = false - @Published var isFiltering: Bool = false - @Published var isLoading: Bool = false - @Published var state: FeedListState = .initial + @Published private(set) var filter: FilterOption = .all + @Published private(set) var isFilterEnabled: Bool = false + @Published private(set) var isFiltering: Bool = false + @Published private(set) var isLoading: Bool = false + @Published private(set) var state: FeedListState = .initial - var items: [Review] = [] - var words: [TopWord] = [] + private(set) var items: [Review] = [] + private(set) var words: [TopWord] = [] private var reviewsAll: [Review] = [] private var reviewsFiltered: FilteredReviews = [:] diff --git a/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift b/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift index cdf8634..0f9281c 100644 --- a/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift +++ b/Frameworks/Feed/Test/Tests/View Models/FeedListViewModelTests.swift @@ -61,8 +61,6 @@ final class FeedListViewModelTests: XCTestCase { ) // WHEN - sut.fetch() - sut.$isLoading .collect(3) .sink { value in @@ -71,7 +69,9 @@ final class FeedListViewModelTests: XCTestCase { expectation.fulfill() } .store(in: &cancellables) - + + sut.fetch() + wait(for: [expectation], timeout: 2) // THEN @@ -96,8 +96,6 @@ final class FeedListViewModelTests: XCTestCase { ) // WHEN - sut.fetch() - sut.$isLoading .collect(3) .sink { value in @@ -106,6 +104,8 @@ final class FeedListViewModelTests: XCTestCase { expectation.fulfill() } .store(in: &cancellables) + + sut.fetch() wait(for: [expectation], timeout: 2) @@ -127,17 +127,17 @@ final class FeedListViewModelTests: XCTestCase { MockURLProtocol.response = .init(statusCode: 404) // WHEN - sut.fetch() - sut.$isLoading .collect(3) .sink { value in isLoading.append(contentsOf: value) - + expectation.fulfill() } .store(in: &cancellables) + sut.fetch() + wait(for: [expectation], timeout: 2) // THEN @@ -155,16 +155,12 @@ final class FeedListViewModelTests: XCTestCase { var isLoading: [Bool] = [] // GIVEN - sut.filter = .only1Star - MockURLProtocol.response = .init( statusCode: 200, object: Feed(entries: .sample) ) // WHEN - sut.fetch() - sut.$isLoading .collect(3) .sink { value in @@ -173,11 +169,15 @@ final class FeedListViewModelTests: XCTestCase { expectation.fulfill() } .store(in: &cancellables) - + + sut.filter(by: .only1Star) + sut.fetch() + wait(for: [expectation], timeout: 2) // THEN XCTAssertEqual(isLoading, [false, true, false]) + XCTAssertEqual(sut.filter, .only1Star) XCTAssertTrue(sut.isFilterEnabled) XCTAssertTrue(sut.isWordsShowing) XCTAssertFalse(sut.items.isEmpty) @@ -194,16 +194,12 @@ final class FeedListViewModelTests: XCTestCase { var isLoading: [Bool] = [] // GIVEN - sut.filter = .only1Star - MockURLProtocol.response = .init( statusCode: 200, object: Feed(entries: .none) ) // WHEN - sut.fetch() - sut.$isLoading .collect(3) .sink { value in @@ -213,10 +209,14 @@ final class FeedListViewModelTests: XCTestCase { } .store(in: &cancellables) + sut.filter(by: .only1Star) + sut.fetch() + wait(for: [expectation], timeout: 2) // THEN XCTAssertEqual(isLoading, [false, true, false]) + XCTAssertEqual(sut.filter, .only1Star) XCTAssertFalse(sut.isFilterEnabled) XCTAssertFalse(sut.isWordsShowing) XCTAssertTrue(sut.items.isEmpty) @@ -231,13 +231,9 @@ final class FeedListViewModelTests: XCTestCase { var isLoading: [Bool] = [] // GIVEN - sut.filter = .only1Star - MockURLProtocol.response = .init(statusCode: 404) // WHEN - sut.fetch() - sut.$isLoading .collect(3) .sink { value in @@ -246,11 +242,15 @@ final class FeedListViewModelTests: XCTestCase { expectation.fulfill() } .store(in: &cancellables) - + + sut.filter(by: .only1Star) + sut.fetch() + wait(for: [expectation], timeout: 2) // THEN XCTAssertEqual(isLoading, [false, true, false]) + XCTAssertEqual(sut.filter, .only1Star) XCTAssertFalse(sut.isFilterEnabled) XCTAssertFalse(sut.isWordsShowing) XCTAssertTrue(sut.items.isEmpty) @@ -259,6 +259,145 @@ final class FeedListViewModelTests: XCTestCase { XCTAssertTrue(sut.words.isEmpty) } - func test + func testFilter_forNewOption_withSomeItems() { + 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) + + sut.fetch() + 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) + } + + func testFilter_forNewOption_withNoItems() { + 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) + + sut.fetch() + 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) + } + + func testFilter_forSameOption_withSomeItems() { + 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) + + sut.fetch() + 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) + } + + func testFilter_forSameOption_withNoItems() { + 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) + + sut.fetch() + 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) + } }