Skip to content

Commit 9d53e58

Browse files
committed
Add more tests
Fixes #45
1 parent b14cc56 commit 9d53e58

7 files changed

Lines changed: 1649 additions & 9 deletions

Tests/DefaultsTests/Defaults+iCloudTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@ final class DefaultsICloudTests {
247247

248248
@Test
249249
func testAbortion() async {
250-
let name = Defaults.Key<String>("testAbortSingleKey_name", default: "0", iCloud: true) // swiftlint:disable:this discouraged_optional_boolean
251-
let quantity = Defaults.Key<Int>("testAbortSingleKey_quantity", default: 0, iCloud: true) // swiftlint:disable:this discouraged_optional_boolean
250+
let name = Defaults.Key<String>("testAbortSingleKey_name", default: "0", iCloud: true)
251+
let quantity = Defaults.Key<Int>("testAbortSingleKey_quantity", default: 0, iCloud: true)
252252
Defaults[quantity] = 1
253253
await Defaults.iCloud.waitForSyncCompletion()
254254
#expect(mockStorage.data(forKey: quantity.name) == 1)
@@ -262,8 +262,8 @@ final class DefaultsICloudTests {
262262

263263
@Test
264264
func testSyncLatestSource() async {
265-
let name = Defaults.Key<String>("testSyncLatestSource_name", default: "0", iCloud: true) // swiftlint:disable:this discouraged_optional_boolean
266-
let quantity = Defaults.Key<Int>("testSyncLatestSource_quantity", default: 0, iCloud: true) // swiftlint:disable:this discouraged_optional_boolean
265+
let name = Defaults.Key<String>("testSyncLatestSource_name", default: "0", iCloud: true)
266+
let quantity = Defaults.Key<Int>("testSyncLatestSource_quantity", default: 0, iCloud: true)
267267
// Create a timestamp in both the local and remote data sources
268268
Defaults[name] = "1"
269269
Defaults[quantity] = 1
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
import Foundation
2+
import Testing
3+
@testable import Defaults
4+
5+
private let suite_ = UserDefaults(suiteName: UUID().uuidString)!
6+
7+
// Test structs for bridge testing
8+
private struct CodableStruct: Codable, Defaults.Serializable {
9+
let name: String
10+
let value: Int
11+
}
12+
13+
private struct CodableStructWithCustomBridge: Defaults.Serializable {
14+
let data: String
15+
16+
static let bridge = CustomCodableStructBridge()
17+
}
18+
19+
private struct CustomCodableStructBridge: Defaults.Bridge {
20+
typealias Value = CodableStructWithCustomBridge
21+
typealias Serializable = String
22+
23+
func serialize(_ value: CodableStructWithCustomBridge?) -> String? {
24+
value?.data
25+
}
26+
27+
func deserialize(_ object: String?) -> CodableStructWithCustomBridge? {
28+
guard let object else {
29+
return nil
30+
}
31+
32+
return CodableStructWithCustomBridge(data: object)
33+
}
34+
}
35+
36+
private enum TestEnum: String, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable {
37+
case first = "first_value"
38+
case second = "second_value"
39+
}
40+
41+
private enum TestEnumCodable: String, Codable, Defaults.Serializable {
42+
case alpha = "alpha_value"
43+
case beta = "beta_value"
44+
}
45+
46+
@Suite(.serialized)
47+
final class DefaultsBridgeTests {
48+
init() {
49+
Defaults.removeAll(suite: suite_)
50+
}
51+
52+
deinit {
53+
Defaults.removeAll(suite: suite_)
54+
}
55+
56+
@Test
57+
func testCodableBridge() {
58+
let key = Defaults.Key<CodableStruct>("codableStruct", default: CodableStruct(name: "default", value: 0), suite: suite_)
59+
60+
// Test default value
61+
#expect(Defaults[key].name == "default")
62+
#expect(Defaults[key].value == 0)
63+
64+
// Test setting and getting
65+
let newValue = CodableStruct(name: "test", value: 42)
66+
Defaults[key] = newValue
67+
68+
#expect(Defaults[key].name == "test")
69+
#expect(Defaults[key].value == 42)
70+
71+
// Test persistence across instances
72+
let anotherKey = Defaults.Key<CodableStruct>("codableStruct", default: CodableStruct(name: "fallback", value: -1), suite: suite_)
73+
#expect(Defaults[anotherKey].name == "test")
74+
#expect(Defaults[anotherKey].value == 42)
75+
}
76+
77+
@Test
78+
func testCustomBridge() {
79+
let key = Defaults.Key<CodableStructWithCustomBridge>("customBridge", default: CodableStructWithCustomBridge(data: "default"), suite: suite_)
80+
81+
// Test default value
82+
#expect(Defaults[key].data == "default")
83+
84+
// Test setting and getting
85+
let newValue = CodableStructWithCustomBridge(data: "custom_test")
86+
Defaults[key] = newValue
87+
88+
#expect(Defaults[key].data == "custom_test")
89+
90+
// Verify the bridge is actually used by checking the stored value
91+
let storedValue = suite_.string(forKey: "customBridge")
92+
#expect(storedValue == "custom_test")
93+
}
94+
95+
@Test
96+
func testRawRepresentablePreference() {
97+
let key = Defaults.Key<TestEnum>("enumPreferRaw", default: .first, suite: suite_)
98+
99+
// Test default value
100+
#expect(Defaults[key] == .first)
101+
102+
// Test setting and getting
103+
Defaults[key] = .second
104+
#expect(Defaults[key] == .second)
105+
106+
// Verify raw value is stored (not JSON)
107+
let storedValue = suite_.string(forKey: "enumPreferRaw")
108+
#expect(storedValue == "second_value")
109+
}
110+
111+
@Test
112+
func testCodableEnum() {
113+
let key = Defaults.Key<TestEnumCodable>("enumCodable", default: .alpha, suite: suite_)
114+
115+
// Test default value
116+
#expect(Defaults[key] == .alpha)
117+
118+
// Test setting and getting
119+
Defaults[key] = .beta
120+
#expect(Defaults[key] == .beta)
121+
122+
// For Codable enums without PreferRawRepresentable, it should use Codable bridge
123+
// This means it would be stored as JSON
124+
let storedValue = suite_.object(forKey: "enumCodable")
125+
#expect(storedValue != nil)
126+
}
127+
128+
@Test
129+
func testOptionalBridge() {
130+
let key = Defaults.Key<CodableStruct?>("optionalCodable", suite: suite_)
131+
132+
// Test nil default
133+
#expect(Defaults[key] == nil)
134+
135+
// Test setting a value
136+
let value = CodableStruct(name: "optional", value: 99)
137+
Defaults[key] = value
138+
139+
#expect(Defaults[key]?.name == "optional")
140+
#expect(Defaults[key]?.value == 99)
141+
142+
// Test setting back to nil
143+
Defaults[key] = nil
144+
#expect(Defaults[key] == nil)
145+
146+
// Verify nil removes the key
147+
#expect(suite_.object(forKey: "optionalCodable") == nil)
148+
}
149+
150+
@Test
151+
func testArrayBridge() {
152+
let key = Defaults.Key<[CodableStruct]>("arrayOfStructs", default: [], suite: suite_)
153+
154+
// Test empty default
155+
#expect(Defaults[key].isEmpty)
156+
157+
// Test setting array
158+
let structs = [
159+
CodableStruct(name: "first", value: 1),
160+
CodableStruct(name: "second", value: 2)
161+
]
162+
Defaults[key] = structs
163+
164+
#expect(Defaults[key].count == 2)
165+
#expect(Defaults[key][0].name == "first")
166+
#expect(Defaults[key][1].value == 2)
167+
168+
// Test appending
169+
var currentArray = Defaults[key]
170+
currentArray.append(CodableStruct(name: "third", value: 3))
171+
Defaults[key] = currentArray
172+
173+
#expect(Defaults[key].count == 3)
174+
#expect(Defaults[key][2].name == "third")
175+
}
176+
177+
@Test
178+
func testDictionaryBridge() {
179+
let key = Defaults.Key<[String: CodableStruct]>("dictOfStructs", default: [:], suite: suite_)
180+
181+
// Test empty default
182+
#expect(Defaults[key].isEmpty)
183+
184+
// Test setting dictionary
185+
let dict = [
186+
"key1": CodableStruct(name: "value1", value: 1),
187+
"key2": CodableStruct(name: "value2", value: 2)
188+
]
189+
Defaults[key] = dict
190+
191+
#expect(Defaults[key].count == 2)
192+
#expect(Defaults[key]["key1"]?.name == "value1")
193+
#expect(Defaults[key]["key2"]?.value == 2)
194+
195+
// Test modifying
196+
var currentDict = Defaults[key]
197+
currentDict["key3"] = CodableStruct(name: "value3", value: 3)
198+
Defaults[key] = currentDict
199+
200+
#expect(Defaults[key].count == 3)
201+
#expect(Defaults[key]["key3"]?.name == "value3")
202+
}
203+
204+
@Test
205+
func testSetBridge() {
206+
let key = Defaults.Key<Set<String>>("setOfStrings", default: [], suite: suite_)
207+
208+
// Test empty default
209+
#expect(Defaults[key].isEmpty)
210+
211+
// Test setting set
212+
let stringSet: Set<String> = ["apple", "banana", "cherry"]
213+
Defaults[key] = stringSet
214+
215+
#expect(Defaults[key].count == 3)
216+
#expect(Defaults[key].contains("apple"))
217+
#expect(Defaults[key].contains("banana"))
218+
#expect(Defaults[key].contains("cherry"))
219+
220+
// Test modifying
221+
var currentSet = Defaults[key]
222+
currentSet.insert("date")
223+
Defaults[key] = currentSet
224+
225+
#expect(Defaults[key].count == 4)
226+
#expect(Defaults[key].contains("date"))
227+
}
228+
229+
@Test
230+
func testURLBridge() {
231+
let defaultURL = URL(string: "https://example.com")!
232+
let key = Defaults.Key<URL>("urlTest", default: defaultURL, suite: suite_)
233+
234+
// Test default
235+
#expect(Defaults[key] == defaultURL)
236+
237+
// Test setting new URL
238+
let newURL = URL(string: "https://test.com/path?query=value")!
239+
Defaults[key] = newURL
240+
241+
#expect(Defaults[key] == newURL)
242+
#expect(Defaults[key].absoluteString == "https://test.com/path?query=value")
243+
244+
// Test file URL
245+
let fileURL = URL(fileURLWithPath: "/tmp/test.txt")
246+
Defaults[key] = fileURL
247+
248+
#expect(Defaults[key] == fileURL)
249+
#expect(Defaults[key].isFileURL)
250+
}
251+
252+
@Test
253+
func testUUIDBridge() {
254+
let defaultUUID = UUID()
255+
let key = Defaults.Key<UUID>("uuidTest", default: defaultUUID, suite: suite_)
256+
257+
// Test default
258+
#expect(Defaults[key] == defaultUUID)
259+
260+
// Test setting new UUID
261+
let newUUID = UUID()
262+
Defaults[key] = newUUID
263+
264+
#expect(Defaults[key] == newUUID)
265+
#expect(Defaults[key].uuidString == newUUID.uuidString)
266+
267+
// Test specific UUID
268+
let specificUUID = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
269+
Defaults[key] = specificUUID
270+
271+
#expect(Defaults[key] == specificUUID)
272+
}
273+
274+
@Test
275+
func testRangeBridge() {
276+
let key = Defaults.Key<Range<Int>>("rangeTest", default: 0..<10, suite: suite_)
277+
278+
// Test default
279+
#expect(Defaults[key] == 0..<10)
280+
#expect(Defaults[key].lowerBound == 0)
281+
#expect(Defaults[key].upperBound == 10)
282+
283+
// Test setting new range
284+
let newRange = 5..<15
285+
Defaults[key] = newRange
286+
287+
#expect(Defaults[key] == newRange)
288+
#expect(Defaults[key].contains(10))
289+
#expect(!Defaults[key].contains(15))
290+
}
291+
292+
@Test
293+
func testClosedRangeBridge() {
294+
let key = Defaults.Key<ClosedRange<Int>>("closedRangeTest", default: 0...10, suite: suite_)
295+
296+
// Test default
297+
#expect(Defaults[key] == 0...10)
298+
#expect(Defaults[key].lowerBound == 0)
299+
#expect(Defaults[key].upperBound == 10)
300+
301+
// Test setting new range
302+
let newRange = 5...15
303+
Defaults[key] = newRange
304+
305+
#expect(Defaults[key] == newRange)
306+
#expect(Defaults[key].contains(15)) // Unlike Range, ClosedRange includes upperBound
307+
}
308+
309+
@Test
310+
func testSerializableTypeConversions() {
311+
// Test toValue with natively supported type
312+
let intValue: Int? = Int.toValue(42, type: Int.self)
313+
#expect(intValue == 42)
314+
315+
let stringValue: String? = String.toValue("test", type: String.self)
316+
#expect(stringValue == "test")
317+
318+
// Test toSerializable with natively supported type
319+
let serializedInt = Int.toSerializable(42)
320+
#expect(serializedInt as? Int == 42)
321+
322+
let serializedString = String.toSerializable("test")
323+
#expect(serializedString as? String == "test")
324+
}
325+
326+
@Test
327+
func testNativeTypeSupportFlags() {
328+
#expect(Int.isNativelySupportedType)
329+
#expect(String.isNativelySupportedType)
330+
#expect(Bool.isNativelySupportedType)
331+
#expect(Double.isNativelySupportedType)
332+
#expect(Data.isNativelySupportedType)
333+
#expect(Date.isNativelySupportedType)
334+
#expect(URL.isNativelySupportedType == false) // Uses bridge
335+
#expect(UUID.isNativelySupportedType == false) // Uses bridge
336+
337+
// Optional types should inherit from wrapped type
338+
#expect((Int?).isNativelySupportedType == Int.isNativelySupportedType)
339+
#expect((String?).isNativelySupportedType == String.isNativelySupportedType)
340+
341+
// Array of native types should be native
342+
#expect([String].isNativelySupportedType == true)
343+
#expect([Int].isNativelySupportedType == true)
344+
345+
// Dictionary with String keys and native values should be native
346+
#expect([String: String].isNativelySupportedType == true)
347+
#expect([String: Int].isNativelySupportedType == true)
348+
}
349+
350+
@Test
351+
func testInvalidSerializationHandling() {
352+
// This test verifies that the bridge handles invalid data gracefully
353+
let key = Defaults.Key<CodableStruct>("invalidData", default: CodableStruct(name: "fallback", value: -1), suite: suite_)
354+
355+
// Manually set invalid data in UserDefaults
356+
suite_.set("invalid_json_data", forKey: "invalidData")
357+
358+
// Should return default value when deserialization fails
359+
let result = Defaults[key]
360+
#expect(result.name == "fallback")
361+
#expect(result.value == -1)
362+
}
363+
}

0 commit comments

Comments
 (0)