[Framework] Feed List improvements #19
@ -0,0 +1,65 @@
|
||||
//
|
||||
// Array+ReviewDTOs.swift
|
||||
// ReviewsFeed
|
||||
//
|
||||
// Created by Javier Cicchelli on 22/03/2024.
|
||||
// Copyright © 2024 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ReviewsFeedKit
|
||||
|
||||
extension Array where Element == ReviewsFeedKit.Review {
|
||||
|
||||
// MARK: Constants
|
||||
static let none: [ReviewsFeedKit.Review] = []
|
||||
|
||||
static let sample: [ReviewsFeedKit.Review] = [
|
||||
.init(
|
||||
id: 1,
|
||||
author: "Some author name #1 here",
|
||||
title: "Some review title #1 goes here...",
|
||||
content: "Some long, long, explanatory review comment #1 goes here...",
|
||||
rating: 3,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 2,
|
||||
author: "Some author name #2 here",
|
||||
title: "Some review title #2 goes here...",
|
||||
content: "Some long, long, explanatory review comment #2 goes here...",
|
||||
rating: 5,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 3,
|
||||
author: "Some author name #3 here",
|
||||
title: "Some review title #3 goes here...",
|
||||
content: "Some long, long, explanatory review comment #3 goes here...",
|
||||
rating: 1,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 4,
|
||||
author: "Some author name #4 here",
|
||||
title: "Some review title #4 goes here...",
|
||||
content: "Some long, long, explanatory review comment #4 goes here...",
|
||||
rating: 4,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 5,
|
||||
author: "Some author name #5 here",
|
||||
title: "Some review title #5 goes here...",
|
||||
content: "Some long, long, explanatory review comment #5 goes here...",
|
||||
rating: 2,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
]
|
||||
|
||||
}
|
@ -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 = [:]
|
||||
@ -57,50 +57,51 @@ extension FeedListViewController {
|
||||
|
||||
var isWordsShowing: Bool {
|
||||
filter != .all
|
||||
&& !words.isEmpty
|
||||
&& !words.isEmpty
|
||||
}
|
||||
|
||||
// MARK: Functions
|
||||
func fetch() {
|
||||
Task {
|
||||
isFilterEnabled = false
|
||||
isLoading = items.isEmpty
|
||||
func fetch() async {
|
||||
isFilterEnabled = false
|
||||
isLoading = items.isEmpty
|
||||
|
||||
do {
|
||||
let output = try await iTunesService.getReviews(.init(
|
||||
appID: configuration.appID,
|
||||
countryCode: configuration.countryCode
|
||||
))
|
||||
do {
|
||||
let output = try await iTunesService.getReviews(.init(
|
||||
appID: configuration.appID,
|
||||
countryCode: configuration.countryCode
|
||||
))
|
||||
|
||||
reviewsAll = output.reviews.map(Review.init)
|
||||
reviewsFiltered = FilterOption.allCases
|
||||
.reduce(into: FilteredReviews()) { partialResult, option in
|
||||
partialResult[option] = reviewsAll.filter { $0.rating.stars == option.rawValue }
|
||||
}
|
||||
reviewsTopWords = reviewsFiltered
|
||||
.mapValues { reviews in
|
||||
reviews.map(\.comment)
|
||||
.compactMap { try? filterWords($0) }
|
||||
}
|
||||
.mapValues {
|
||||
topWords($0).map(TopWord.init)
|
||||
}
|
||||
reviewsAll = output.reviews.map(Review.init)
|
||||
reviewsFiltered = FilterOption.allCases
|
||||
.reduce(into: FilteredReviews()) { partialResult, option in
|
||||
partialResult[option] = reviewsAll.filter { $0.rating.stars == option.rawValue }
|
||||
}
|
||||
reviewsTopWords = reviewsFiltered
|
||||
.mapValues { reviews in
|
||||
reviews.map(\.comment)
|
||||
.compactMap { try? filterWords($0) }
|
||||
}
|
||||
.mapValues {
|
||||
topWords($0).map(TopWord.init)
|
||||
}
|
||||
|
||||
items = filter == .all
|
||||
? reviewsAll
|
||||
: reviewsFiltered[filter] ?? []
|
||||
items = filter == .all
|
||||
? reviewsAll
|
||||
: reviewsFiltered[filter] ?? []
|
||||
words = filter == .all
|
||||
? []
|
||||
: reviewsTopWords[filter] ?? []
|
||||
|
||||
isFilterEnabled = !items.isEmpty
|
||||
state = items.isEmpty
|
||||
? .empty
|
||||
: .populated
|
||||
} catch {
|
||||
items = []
|
||||
state = .error
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
isFilterEnabled = !items.isEmpty
|
||||
state = items.isEmpty
|
||||
? .empty
|
||||
: .populated
|
||||
} catch {
|
||||
items = []
|
||||
state = .error
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func filter(by option: FilterOption) {
|
||||
@ -115,16 +116,19 @@ extension FeedListViewController {
|
||||
}
|
||||
|
||||
func item(for index: Int) -> Review? {
|
||||
guard
|
||||
!items.isEmpty,
|
||||
index < items.count
|
||||
else {
|
||||
guard !items.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return isWordsShowing
|
||||
? items[index - 1]
|
||||
: items[index]
|
||||
let indexToUse = isWordsShowing
|
||||
? index - 1
|
||||
: index
|
||||
|
||||
guard indexToUse < items.count else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return items[indexToUse]
|
||||
}
|
||||
|
||||
func openItem(at index: Int) {
|
||||
|
@ -118,7 +118,7 @@ final class FeedListViewController: UIViewController {
|
||||
registerTableCells()
|
||||
bindViewModel()
|
||||
|
||||
viewModel.fetch()
|
||||
Task { await viewModel.fetch() }
|
||||
}
|
||||
|
||||
}
|
||||
@ -170,7 +170,7 @@ private extension FeedListViewController {
|
||||
|
||||
// MARK: Actions
|
||||
@objc func refresh(_ sender: AnyObject) {
|
||||
self.viewModel.fetch()
|
||||
Task { await self.viewModel.fetch() }
|
||||
}
|
||||
|
||||
// MARK: Functions
|
||||
@ -264,7 +264,7 @@ private extension FeedListViewController {
|
||||
)
|
||||
: nil,
|
||||
action: isErrorState
|
||||
? { self.viewModel.fetch() }
|
||||
? { Task { await self.viewModel.fetch() } }
|
||||
: nil
|
||||
)
|
||||
|
||||
@ -455,53 +455,7 @@ import ReviewsiTunesKit
|
||||
#Preview("Feed List with few reviews") {
|
||||
MockURLProtocol.response = .init(
|
||||
statusCode: 200,
|
||||
object: Feed(entries: [
|
||||
.init(
|
||||
id: 1,
|
||||
author: "Some author name #1 here",
|
||||
title: "Some review title #1 goes here...",
|
||||
content: "Some long, explanatory review comment #1 goes here...",
|
||||
rating: 3,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 2,
|
||||
author: "Some author name #2 here",
|
||||
title: "Some review title #2 goes here...",
|
||||
content: "Some long, explanatory review comment #2 goes here...",
|
||||
rating: 5,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 3,
|
||||
author: "Some author name #3 here",
|
||||
title: "Some review title #3 goes here...",
|
||||
content: "Some long, explanatory review comment #3 goes here...",
|
||||
rating: 1,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 4,
|
||||
author: "Some author name #4 here",
|
||||
title: "Some review title #4 goes here...",
|
||||
content: "Some long, explanatory review comment #4 goes here...",
|
||||
rating: 4,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
.init(
|
||||
id: 5,
|
||||
author: "Some author name #5 here",
|
||||
title: "Some review title #5 goes here...",
|
||||
content: "Some long, explanatory review comment #5 goes here...",
|
||||
rating: 2,
|
||||
version: "v1.0.0",
|
||||
updated: .init()
|
||||
),
|
||||
])
|
||||
object: Feed(entries: .sample)
|
||||
)
|
||||
|
||||
return UINavigationController(rootViewController: FeedListViewController(.init(
|
||||
|
@ -0,0 +1,21 @@
|
||||
//
|
||||
// FeedListCoordinationSpy.swift
|
||||
// ReviewsFeedTest
|
||||
//
|
||||
// Created by Javier Cicchelli on 22/03/2024.
|
||||
// Copyright © 2024 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import ReviewsFeed
|
||||
|
||||
final class FeedListCoordinationSpy: FeedListCoordination {
|
||||
|
||||
// MARK: Properties
|
||||
var itemOpened: Bool = false
|
||||
|
||||
// MARK: Functions
|
||||
func open(_ item: ReviewsFeed.Review) {
|
||||
itemOpened = true
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,581 @@
|
||||
//
|
||||
// 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: Constants
|
||||
private let coordination: FeedListCoordinationSpy = .init()
|
||||
|
||||
// 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: coordination
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
func testFetch_allItems_whenResponseOK_withSomeItems() async {
|
||||
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)
|
||||
|
||||
await sut.fetch()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func testFetch_allItems_whenResponseOK_withNoItems() async {
|
||||
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)
|
||||
|
||||
await sut.fetch()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func testFetch_allItems_whenResponseNotOK() async {
|
||||
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)
|
||||
|
||||
await sut.fetch()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func testFetch_filteredItems_whenResponseOK_withSomeItems() async {
|
||||
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)
|
||||
|
||||
sut.filter(by: .only1Star)
|
||||
|
||||
await 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)
|
||||
XCTAssertEqual(sut.items.count, 1)
|
||||
XCTAssertEqual(sut.itemsCount, 2)
|
||||
XCTAssertEqual(sut.state, .populated)
|
||||
XCTAssertFalse(sut.words.isEmpty)
|
||||
XCTAssertEqual(sut.words.count, 3)
|
||||
}
|
||||
|
||||
func testFetch_filteredItems_whenResponseOK_withNoItems() async {
|
||||
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)
|
||||
|
||||
sut.filter(by: .only1Star)
|
||||
|
||||
await 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)
|
||||
XCTAssertEqual(sut.itemsCount, 0)
|
||||
XCTAssertEqual(sut.state, .empty)
|
||||
XCTAssertTrue(sut.words.isEmpty)
|
||||
}
|
||||
|
||||
func testFetch_filteredItems_whenResponseNotOK() async {
|
||||
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)
|
||||
|
||||
sut.filter(by: .only1Star)
|
||||
|
||||
await 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)
|
||||
XCTAssertEqual(sut.itemsCount, 0)
|
||||
XCTAssertEqual(sut.state, .error)
|
||||
XCTAssertTrue(sut.words.isEmpty)
|
||||
}
|
||||
|
||||
func testFilter_forNewOption_withSomeItems() async {
|
||||
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)
|
||||
|
||||
await 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() async {
|
||||
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)
|
||||
|
||||
await 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() async {
|
||||
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)
|
||||
|
||||
await 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() async {
|
||||
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)
|
||||
|
||||
await 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 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
|
||||
XCTAssertNotNil(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)
|
||||
}
|
||||
|
||||
func testOpenItemAt_index_withSomeItems() async {
|
||||
// GIVEN
|
||||
MockURLProtocol.response = .init(
|
||||
statusCode: 200,
|
||||
object: Feed(entries: .sample)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
await sut.fetch()
|
||||
|
||||
sut.openItem(at: 0)
|
||||
|
||||
// THEN
|
||||
XCTAssertTrue(coordination.itemOpened)
|
||||
}
|
||||
|
||||
func testOpenItemAt_indexOutOfBounds_withSomeItems() async {
|
||||
// GIVEN
|
||||
MockURLProtocol.response = .init(
|
||||
statusCode: 200,
|
||||
object: Feed(entries: .sample)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
await sut.fetch()
|
||||
|
||||
sut.openItem(at: 10)
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(coordination.itemOpened)
|
||||
}
|
||||
|
||||
func testOpenItemAt_index_withFilteredItems() async {
|
||||
// GIVEN
|
||||
MockURLProtocol.response = .init(
|
||||
statusCode: 200,
|
||||
object: Feed(entries: .sample)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
sut.filter(by: .only1Star)
|
||||
|
||||
await sut.fetch()
|
||||
|
||||
sut.openItem(at: 1)
|
||||
|
||||
// THEN
|
||||
XCTAssertTrue(coordination.itemOpened)
|
||||
}
|
||||
|
||||
func testOpenItemAt_indexOutOfBounds_withFilteredItems() async {
|
||||
// GIVEN
|
||||
MockURLProtocol.response = .init(
|
||||
statusCode: 200,
|
||||
object: Feed(entries: .sample)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
sut.filter(by: .only1Star)
|
||||
|
||||
await sut.fetch()
|
||||
|
||||
sut.openItem(at: 2)
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(coordination.itemOpened)
|
||||
}
|
||||
|
||||
func testOpenItemAt_index_withNoItems() async {
|
||||
// GIVEN
|
||||
MockURLProtocol.response = .init(
|
||||
statusCode: 200,
|
||||
object: Feed(entries: .none)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
await sut.fetch()
|
||||
|
||||
sut.openItem(at: 0)
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(coordination.itemOpened)
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,10 @@
|
||||
02909E792BAB6B0200710E14 /* FilterOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E782BAB6B0200710E14 /* FilterOption.swift */; };
|
||||
02909E7B2BAB6D2E00710E14 /* Bundle+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E7A2BAB6D2E00710E14 /* Bundle+Constants.swift */; };
|
||||
02909E7D2BAB7FFE00710E14 /* Review+DTOs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02909E7C2BAB7FFE00710E14 /* Review+DTOs.swift */; };
|
||||
02B36F7D2BAD9D1A00F1A89D /* ReviewsFeed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */; platformFilter = ios; };
|
||||
02B36F852BAD9DDB00F1A89D /* FeedListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B36F842BAD9DDB00F1A89D /* FeedListViewModelTests.swift */; };
|
||||
02B36F892BADB26C00F1A89D /* Array+ReviewDTOs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B36F882BADB26C00F1A89D /* Array+ReviewDTOs.swift */; };
|
||||
02B36F8F2BADCABC00F1A89D /* FeedListCoordinationSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B36F8D2BADCA8000F1A89D /* FeedListCoordinationSpy.swift */; };
|
||||
02C1B1972BAC9BFE001781DE /* FeedListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C1B1962BAC9BFE001781DE /* FeedListCoordinator.swift */; };
|
||||
02C1B1A92BACA722001781DE /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C1B1A82BACA722001781DE /* AppCoordinator.swift */; };
|
||||
02DA924E2BAAE3FD00C47985 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 02DA924D2BAAE3FD00C47985 /* Localizable.xcstrings */; };
|
||||
@ -40,6 +44,13 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
02B36F7E2BAD9D1A00F1A89D /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 345AD11024C6EDD9004E2EE1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 02DC7F8E2BA51793000EEEBE;
|
||||
remoteInfo = Feed;
|
||||
};
|
||||
02DC7FA02BA51793000EEEBE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 345AD11024C6EDD9004E2EE1 /* Project object */;
|
||||
@ -75,6 +86,10 @@
|
||||
02909E782BAB6B0200710E14 /* FilterOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOption.swift; sourceTree = "<group>"; };
|
||||
02909E7A2BAB6D2E00710E14 /* Bundle+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Constants.swift"; sourceTree = "<group>"; };
|
||||
02909E7C2BAB7FFE00710E14 /* Review+DTOs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Review+DTOs.swift"; sourceTree = "<group>"; };
|
||||
02B36F792BAD9D1A00F1A89D /* FeedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FeedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
02B36F842BAD9DDB00F1A89D /* FeedListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewModelTests.swift; sourceTree = "<group>"; };
|
||||
02B36F882BADB26C00F1A89D /* Array+ReviewDTOs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+ReviewDTOs.swift"; sourceTree = "<group>"; };
|
||||
02B36F8D2BADCA8000F1A89D /* FeedListCoordinationSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListCoordinationSpy.swift; sourceTree = "<group>"; };
|
||||
02C1B1962BAC9BFE001781DE /* FeedListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListCoordinator.swift; sourceTree = "<group>"; };
|
||||
02C1B1A82BACA722001781DE /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
|
||||
02DA924D2BAAE3FD00C47985 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
@ -97,6 +112,14 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
02B36F762BAD9D1A00F1A89D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
02B36F7D2BAD9D1A00F1A89D /* ReviewsFeed.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
02DC7F8C2BA51793000EEEBE /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -217,6 +240,7 @@
|
||||
02DC7F912BA51793000EEEBE /* ReviewsFeed.h */,
|
||||
02DA924B2BAAE3E500C47985 /* Resources */,
|
||||
02DC7FB02BA51B4F000EEEBE /* Sources */,
|
||||
02B36F862BADB1FD00F1A89D /* Previews */,
|
||||
);
|
||||
path = Bundle;
|
||||
sourceTree = "<group>";
|
||||
@ -224,10 +248,68 @@
|
||||
02A6DA302BA5929F00B943E2 /* Test */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F8A2BADCA5B00F1A89D /* Helpers */,
|
||||
02B36F742BAD9C4500F1A89D /* Tests */,
|
||||
);
|
||||
path = Test;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02B36F742BAD9C4500F1A89D /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F832BAD9DC700F1A89D /* View Models */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02B36F832BAD9DC700F1A89D /* View Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F842BAD9DDB00F1A89D /* FeedListViewModelTests.swift */,
|
||||
);
|
||||
path = "View Models";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02B36F862BADB1FD00F1A89D /* Previews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F872BADB23200F1A89D /* Extensions */,
|
||||
);
|
||||
path = Previews;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02B36F872BADB23200F1A89D /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F882BADB26C00F1A89D /* Array+ReviewDTOs.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02B36F8A2BADCA5B00F1A89D /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F8B2BADCA6200F1A89D /* Spies */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02B36F8B2BADCA6200F1A89D /* Spies */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F8C2BADCA6A00F1A89D /* Coordination */,
|
||||
);
|
||||
path = Spies;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02B36F8C2BADCA6A00F1A89D /* Coordination */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
02B36F8D2BADCA8000F1A89D /* FeedListCoordinationSpy.swift */,
|
||||
);
|
||||
path = Coordination;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
02C1B1952BAC9BE7001781DE /* Coordinators */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -364,6 +446,7 @@
|
||||
children = (
|
||||
345AD11824C6EDD9004E2EE1 /* Reviews.app */,
|
||||
02DC7F8F2BA51793000EEEBE /* ReviewsFeed.framework */,
|
||||
02B36F792BAD9D1A00F1A89D /* FeedTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -391,6 +474,24 @@
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
02B36F782BAD9D1A00F1A89D /* FeedTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 02B36F802BAD9D1A00F1A89D /* Build configuration list for PBXNativeTarget "FeedTests" */;
|
||||
buildPhases = (
|
||||
02B36F752BAD9D1A00F1A89D /* Sources */,
|
||||
02B36F762BAD9D1A00F1A89D /* Frameworks */,
|
||||
02B36F772BAD9D1A00F1A89D /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
02B36F7F2BAD9D1A00F1A89D /* PBXTargetDependency */,
|
||||
);
|
||||
name = FeedTests;
|
||||
productName = ReviewsFeedTests;
|
||||
productReference = 02B36F792BAD9D1A00F1A89D /* FeedTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
02DC7F8E2BA51793000EEEBE /* Feed */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 02DC7FA92BA51793000EEEBE /* Build configuration list for PBXNativeTarget "Feed" */;
|
||||
@ -445,6 +546,9 @@
|
||||
LastUpgradeCheck = 1530;
|
||||
ORGANIZATIONNAME = "Röck+Cöde";
|
||||
TargetAttributes = {
|
||||
02B36F782BAD9D1A00F1A89D = {
|
||||
CreatedOnToolsVersion = 15.3;
|
||||
};
|
||||
02DC7F8E2BA51793000EEEBE = {
|
||||
CreatedOnToolsVersion = 15.3;
|
||||
};
|
||||
@ -466,13 +570,21 @@
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
02DC7F8E2BA51793000EEEBE /* Feed */,
|
||||
345AD11724C6EDD9004E2EE1 /* App */,
|
||||
02DC7F8E2BA51793000EEEBE /* Feed */,
|
||||
02B36F782BAD9D1A00F1A89D /* FeedTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
02B36F772BAD9D1A00F1A89D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
02DC7F8D2BA51793000EEEBE /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -493,11 +605,21 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
02B36F752BAD9D1A00F1A89D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
02B36F852BAD9DDB00F1A89D /* FeedListViewModelTests.swift in Sources */,
|
||||
02B36F8F2BADCABC00F1A89D /* FeedListCoordinationSpy.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
02DC7F8B2BA51793000EEEBE /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
02EACF302BABA50D00FF8ECD /* TopWordsCell.swift in Sources */,
|
||||
02B36F892BADB26C00F1A89D /* Array+ReviewDTOs.swift in Sources */,
|
||||
02620B8C2BA89C9A00DE7137 /* FeedListViewModel.swift in Sources */,
|
||||
02EACF342BABB28900FF8ECD /* TopWord.swift in Sources */,
|
||||
023AC7FC2BAA3EC10027D064 /* Int+Constants.swift in Sources */,
|
||||
@ -532,6 +654,12 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
02B36F7F2BAD9D1A00F1A89D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilter = ios;
|
||||
target = 02DC7F8E2BA51793000EEEBE /* Feed */;
|
||||
targetProxy = 02B36F7E2BAD9D1A00F1A89D /* PBXContainerItemProxy */;
|
||||
};
|
||||
02DC7FA12BA51793000EEEBE /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 02DC7F8E2BA51793000EEEBE /* Feed */;
|
||||
@ -551,6 +679,59 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
02B36F812BAD9D1A00F1A89D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7FMNM89WKG;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.rock-n-code.assignment.ing.framework.feed.test.unit";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
02B36F822BAD9D1A00F1A89D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7FMNM89WKG;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.rock-n-code.assignment.ing.framework.feed.test.unit";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
02DC7FA52BA51793000EEEBE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@ -562,6 +743,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = Frameworks/Feed/Bundle/Previews;
|
||||
DEVELOPMENT_TEAM = 7FMNM89WKG;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
@ -611,6 +793,7 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = Frameworks/Feed/Bundle/Previews;
|
||||
DEVELOPMENT_TEAM = 7FMNM89WKG;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
@ -825,6 +1008,15 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
02B36F802BAD9D1A00F1A89D /* Build configuration list for PBXNativeTarget "FeedTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
02B36F812BAD9D1A00F1A89D /* Debug */,
|
||||
02B36F822BAD9D1A00F1A89D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
02DC7FA92BA51793000EEEBE /* Build configuration list for PBXNativeTarget "Feed" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
Loading…
x
Reference in New Issue
Block a user