Skip to content

Commit 1a673e5

Browse files
authored
Merge pull request #1072 from kiwix/fix-unittests
Fix "flaky" unit-tests
2 parents 2ade82f + 68508dc commit 1a673e5

File tree

8 files changed

+167
-46
lines changed

8 files changed

+167
-46
lines changed

Model/CategoriesToLanguage.swift

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,23 @@
1313
// You should have received a copy of the GNU General Public License
1414
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
1515

16-
//
17-
// CategoriesToLanguage.swift
18-
// Kiwix
19-
//
20-
2116
import Foundation
22-
import Defaults
2317

24-
struct CategoriesToLanguages {
18+
protocol CategoriesProtocol {
19+
func has(category: Category, inLanguages langCodes: Set<String>) -> Bool
20+
func save(_ dictionary: [Category: Set<String>])
21+
func allCategories() -> [Category]
22+
}
2523

26-
private let dictionary: [Category: Set<String>] = Defaults[.categoriesToLanguages]
24+
struct CategoriesToLanguages: CategoriesProtocol {
25+
26+
private let defaults: Defaulting
27+
private let dictionary: [Category: Set<String>]
28+
29+
init(withDefaults defaults: Defaulting = UDefaults()) {
30+
self.defaults = defaults
31+
self.dictionary = defaults[.categoriesToLanguages]
32+
}
2733

2834
func has(category: Category, inLanguages langCodes: Set<String>) -> Bool {
2935
guard !langCodes.isEmpty, !dictionary.isEmpty else {
@@ -35,15 +41,14 @@ struct CategoriesToLanguages {
3541
return !languages.isDisjoint(with: langCodes)
3642
}
3743

38-
static func save(_ dictionary: [Category: Set<String>]) {
39-
Defaults[.categoriesToLanguages] = dictionary
44+
func save(_ dictionary: [Category: Set<String>]) {
45+
defaults[.categoriesToLanguages] = dictionary
4046
}
4147

42-
static func allCategories() -> [Category] {
43-
let categoriesToLanguages = CategoriesToLanguages()
44-
let contentLanguages = Defaults[.libraryLanguageCodes]
48+
func allCategories() -> [Category] {
49+
let contentLanguages = defaults[.libraryLanguageCodes]
4550
return Category.allCases.filter { (category: Category) in
46-
categoriesToLanguages.has(category: category, inLanguages: contentLanguages)
51+
has(category: category, inLanguages: contentLanguages)
4752
}
4853
}
4954
}

Model/Defaulting.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// This file is part of Kiwix for iOS & macOS.
2+
//
3+
// Kiwix is free software; you can redistribute it and/or modify it
4+
// under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation; either version 3 of the License, or
6+
// any later version.
7+
//
8+
// Kiwix is distributed in the hope that it will be useful, but
9+
// WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
15+
16+
import Foundation
17+
import Defaults
18+
19+
public protocol Defaulting: NSObjectProtocol {
20+
subscript<Value: Defaults.Serializable>(key: Defaults.Key<Value>) -> Value { get set }
21+
}
22+
23+
final class UDefaults: NSObject, Defaulting {
24+
subscript<Value>(key: Defaults.Key<Value>) -> Value where Value: DefaultsSerializable {
25+
get {
26+
Defaults[key]
27+
}
28+
set {
29+
Defaults[key] = newValue
30+
}
31+
}
32+
}

SwiftUI/Model/Enum.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ enum ActiveSheet: Hashable, Identifiable {
3636
case safari(url: URL)
3737
}
3838

39-
enum Category: String, CaseIterable, Identifiable, LosslessStringConvertible {
39+
enum Category: String, CaseIterable, Identifiable, LosslessStringConvertible, Hashable {
4040
var description: String { rawValue }
4141

4242
var id: String { rawValue }

Tests/LibraryRefreshViewModelTest.swift

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
6060
}
6161

6262
private func makeOPDSData(zimFileID: UUID) -> String {
63+
// swiftlint:disable line_length
6364
"""
6465
<feed xmlns="http://www.w3.org/2005/Atom"
6566
xmlns:dc="http://purl.org/dc/terms/"
@@ -88,6 +89,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
8889
</entry>
8990
</feed>
9091
"""
92+
// swiftlint:enable line_length
9193
}
9294

9395
/// Test time out fetching library data.
@@ -96,9 +98,12 @@ final class LibraryRefreshViewModelTest: XCTestCase {
9698
HTTPTestingURLProtocol.handler = { urlProtocol in
9799
urlProtocol.client?.urlProtocol(urlProtocol, didFailWithError: URLError(URLError.Code.timedOut))
98100
}
99-
101+
let testDefaults = TestDefaults()
102+
testDefaults.setup()
100103
let viewModel = LibraryViewModel(urlSession: urlSession,
101-
processFactory: { LibraryProcess() })
104+
processFactory: { LibraryProcess(defaultState: .initial) },
105+
defaults: testDefaults,
106+
categories: CategoriesToLanguages(withDefaults: testDefaults))
102107
await viewModel.start(isUserInitiated: true)
103108
XCTAssert(viewModel.error is LibraryRefreshError)
104109
XCTAssertEqual(
@@ -119,8 +124,12 @@ final class LibraryRefreshViewModelTest: XCTestCase {
119124
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
120125
}
121126

127+
let testDefaults = TestDefaults()
128+
testDefaults.setup()
122129
let viewModel = LibraryViewModel(urlSession: urlSession,
123-
processFactory: { LibraryProcess() })
130+
processFactory: { LibraryProcess(defaultState: .initial) },
131+
defaults: testDefaults,
132+
categories: CategoriesToLanguages(withDefaults: testDefaults))
124133
await viewModel.start(isUserInitiated: true)
125134
XCTAssert(viewModel.error is LibraryRefreshError)
126135
XCTAssertEqual(
@@ -137,13 +146,17 @@ final class LibraryRefreshViewModelTest: XCTestCase {
137146
url: URL.mock(),
138147
statusCode: 200, httpVersion: nil, headerFields: [:]
139148
)!
140-
urlProtocol.client?.urlProtocol(urlProtocol, didLoad: "Invalid OPDS Data".data(using: .utf8)!)
149+
urlProtocol.client?.urlProtocol(urlProtocol, didLoad: Data("Invalid OPDS Data".utf8))
141150
urlProtocol.client?.urlProtocol(urlProtocol, didReceive: response, cacheStoragePolicy: .notAllowed)
142151
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
143152
}
144153

154+
let testDefaults = TestDefaults()
155+
testDefaults.setup()
145156
let viewModel = LibraryViewModel(urlSession: urlSession,
146-
processFactory: { LibraryProcess() })
157+
processFactory: { LibraryProcess(defaultState: .initial) },
158+
defaults: testDefaults,
159+
categories: CategoriesToLanguages(withDefaults: testDefaults))
147160
await viewModel.start(isUserInitiated: true)
148161
XCTExpectFailure("Requires work in dependency to resolve the issue.")
149162
XCTAssertEqual(
@@ -154,6 +167,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
154167

155168
/// Test zim file entity is created, and metadata are saved when new zim file becomes available in online catalog.
156169
@MainActor
170+
// swiftlint:disable:next function_body_length
157171
func testNewZimFileAndProperties() async throws {
158172
let zimFileID = UUID()
159173
HTTPTestingURLProtocol.handler = { urlProtocol in
@@ -166,9 +180,12 @@ final class LibraryRefreshViewModelTest: XCTestCase {
166180
urlProtocol.client?.urlProtocol(urlProtocol, didReceive: response, cacheStoragePolicy: .notAllowed)
167181
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
168182
}
169-
183+
let testDefaults = TestDefaults()
184+
testDefaults.setup()
170185
let viewModel = LibraryViewModel(urlSession: urlSession,
171-
processFactory: { LibraryProcess() })
186+
processFactory: { LibraryProcess(defaultState: .initial) },
187+
defaults: testDefaults,
188+
categories: CategoriesToLanguages(withDefaults: testDefaults))
172189
await viewModel.start(isUserInitiated: true)
173190

174191
// check no error has happened
@@ -185,6 +202,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
185202
XCTAssertEqual(zimFile.id, zimFileID)
186203
XCTAssertEqual(zimFile.articleCount, 50001)
187204
XCTAssertEqual(zimFile.category, Category.wikipedia.rawValue)
205+
// swiftlint:disable:next force_try
188206
XCTAssertEqual(zimFile.created, try! Date("2023-01-07T00:00:00Z", strategy: .iso8601))
189207
XCTAssertEqual(
190208
zimFile.downloadURL,
@@ -211,14 +229,21 @@ final class LibraryRefreshViewModelTest: XCTestCase {
211229
XCTAssertEqual(zimFile.persistentID, "wikipedia_en_top")
212230
XCTAssertEqual(zimFile.requiresServiceWorkers, false)
213231
XCTAssertEqual(zimFile.size, 6515656704)
232+
233+
// clean up
234+
context.delete(zimFile)
214235
}
215236

216237
/// Test zim file deprecation
217238
@MainActor
218239
func testZimFileDeprecation() async throws {
240+
let testDefaults = TestDefaults()
241+
testDefaults.setup()
219242
// refresh library for the first time, which should create one zim file
220243
let viewModel = LibraryViewModel(urlSession: urlSession,
221-
processFactory: { LibraryProcess() })
244+
processFactory: { LibraryProcess(defaultState: .initial) },
245+
defaults: testDefaults,
246+
categories: CategoriesToLanguages(withDefaults: testDefaults))
222247
await viewModel.start(isUserInitiated: true)
223248
let context = Database.shared.viewContext
224249
let zimFile1 = try XCTUnwrap(try context.fetch(ZimFile.fetchRequest()).first)
@@ -231,7 +256,7 @@ final class LibraryRefreshViewModelTest: XCTestCase {
231256
XCTAssertNotEqual(zimFile1.fileID, zimFile2.fileID)
232257

233258
// set fileURLBookmark of zim file 2
234-
zimFile2.fileURLBookmark = "/Users/tester/Downloads/file_url.zim".data(using: .utf8)
259+
zimFile2.fileURLBookmark = Data("/Users/tester/Downloads/file_url.zim".utf8)
235260
try context.save()
236261

237262
// refresh library for the third time
@@ -241,6 +266,10 @@ final class LibraryRefreshViewModelTest: XCTestCase {
241266
// check there are two zim files in the database, and zim file 2 is not deprecated
242267
XCTAssertEqual(zimFiles.count, 2)
243268
XCTAssertEqual(zimFiles.filter({ $0.fileID == zimFile2.fileID }).count, 1)
269+
270+
// clean up
271+
context.delete(zimFile1)
272+
context.delete(zimFile2)
244273
}
245274
}
246275

Tests/TestDefaults.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// This file is part of Kiwix for iOS & macOS.
2+
//
3+
// Kiwix is free software; you can redistribute it and/or modify it
4+
// under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation; either version 3 of the License, or
6+
// any later version.
7+
//
8+
// Kiwix is distributed in the hope that it will be useful, but
9+
// WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
15+
16+
import Foundation
17+
import Defaults
18+
@testable import Kiwix
19+
20+
final class TestDefaults: NSObject, Defaulting {
21+
22+
var dict: [Defaults.AnyKey: any DefaultsSerializable] = [:]
23+
24+
func setup() {
25+
self[.categoriesToLanguages] = [:]
26+
self[.libraryAutoRefresh] = false
27+
self[.libraryETag] = ""
28+
self[.libraryUsingOldISOLangCodes] = false
29+
self[.libraryLanguageCodes] = Set<String>()
30+
}
31+
32+
subscript<Value>(key: Defaults.Key<Value>) -> Value where Value: DefaultsSerializable {
33+
get {
34+
// swiftlint:disable:next force_cast
35+
dict[key] as! Value
36+
}
37+
set {
38+
dict[key] = newValue
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)