Skip to content

Commit b83854a

Browse files
author
Prachi Gauriar
committed
Add UserSelection
1 parent ab82781 commit b83854a

4 files changed

Lines changed: 125 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# DevFoundation Changelog
22

33

4+
## 1.5.0: October 22, 2025
5+
6+
This release adds the `UserSelection` type, a generic structure that manages a user’s selection with
7+
a fallback to a default value. This type prioritizes explicit user choices over programmatic
8+
defaults while maintaining separate tracking of both values, ensuring user preferences are never
9+
accidentally overwritten by programmatic updates to defaults.
10+
11+
412
## 1.4.0: October 8, 2025
513

614
- We’ve added `Duration`-based alternatives to all APIs that take a `TimeInterval`. Specifically,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// UserSelection.swift
3+
// DevFoundation
4+
//
5+
// Created by Prachi Gauriar on 10/22/25.
6+
//
7+
8+
import Foundation
9+
10+
/// A generic structure that manages a user’s selection with a fallback to a default value.
11+
///
12+
/// `UserSelection` prioritizes explicit user choices over programmatic defaults while maintaining separate tracking
13+
/// of both values. When a user has made an explicit selection, that choice is always preserved and used, regardless
14+
/// of changes to the default value. This ensures user preferences are never accidentally overwritten by programmatic
15+
/// updates to defaults.
16+
///
17+
/// For example,
18+
///
19+
/// // Programmatically set an initial default theme
20+
/// var themeSelection = UserSelection(defaultValue: "light")
21+
/// print(themeSelection.value) // "light"
22+
///
23+
/// // User explicitly selects a theme
24+
/// themeSelection.selectedValue = "dark"
25+
/// print(themeSelection.value) // "dark"
26+
///
27+
/// // Programmatically update the default theme preference
28+
/// themeSelection.defaultValue = "auto"
29+
/// print(themeSelection.value) // Still "dark" - user’s choice is preserved
30+
///
31+
/// // User clears their selection to use programmatic default
32+
/// themeSelection.selectedValue = nil
33+
/// print(themeSelection.value) // "auto" - now uses updated programmatic default
34+
///
35+
/// This pattern is particularly useful when you need to distinguish between user-specified values and
36+
/// programmatically-determined defaults that may change over time, ensuring explicit user choices are never overwritten
37+
/// by updated defaults.
38+
///
39+
/// ## Protocol Conformance
40+
///
41+
/// `UserSelection` conditionally conforms to `Codable`, `Equatable`, `Hashable`, and `Sendable` when the wrapped
42+
/// `Value` type also conforms to these protocols, making it suitable for persistence, comparison, hashing, and
43+
/// concurrent programming scenarios.
44+
public struct UserSelection<Value> {
45+
/// The default value to use when no selection has been made.
46+
///
47+
/// This value is always available and serves as the fallback when `selectedValue` is `nil`.
48+
public var defaultValue: Value
49+
50+
/// The user’s optional selection.
51+
///
52+
/// When `nil`, the `value` property will return `defaultValue`. When set to a value, the `value` property will
53+
/// return this selection.
54+
public var selectedValue: Value?
55+
56+
57+
/// Creates a new user selection with the specified default value.
58+
///
59+
/// - Parameter defaultValue: The value to use when no selection has been made.
60+
public init(defaultValue: Value) {
61+
self.defaultValue = defaultValue
62+
}
63+
64+
65+
/// The effective value, either the user’s selection or the default value.
66+
///
67+
/// This computed property returns `selectedValue` if it’s not `nil`, otherwise it returns `defaultValue`.
68+
public var value: Value {
69+
selectedValue ?? defaultValue
70+
}
71+
}
72+
73+
74+
extension UserSelection: Decodable where Value: Decodable {}
75+
extension UserSelection: Encodable where Value: Encodable {}
76+
extension UserSelection: Equatable where Value: Equatable {}
77+
extension UserSelection: Hashable where Value: Hashable {}
78+
extension UserSelection: Sendable where Value: Sendable {}

Tests/DevFoundationTests/Networking/Simulated URL Request Loader/SimulatedURLRequestLoaderTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ struct SimulatedURLRequestLoaderTests: RandomValueGenerating {
117117

118118
let (actualData, response) = try await loader.data(for: urlRequest)
119119

120-
// Should return second responder's data since first doesn't match
120+
// Should return second responders data since first doesnt match
121121
#expect(actualData == data2)
122122
let httpResponse = try #require(response as? HTTPURLResponse)
123123
#expect(httpResponse.httpStatusCode == statusCode2)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// UserSelectionTests.swift
3+
// DevFoundation
4+
//
5+
// Created by Prachi Gauriar on 10/22/25.
6+
//
7+
8+
import DevFoundation
9+
import DevTesting
10+
import Foundation
11+
import Testing
12+
13+
struct UserSelectionTests: RandomValueGenerating {
14+
var randomNumberGenerator = makeRandomNumberGenerator()
15+
16+
@Test
17+
mutating func valueReturnsCorrectValueBasedOnSelection() {
18+
// set up the test by creating values
19+
let defaultValue = randomAlphanumericString()
20+
let selectedValue = randomAlphanumericString()
21+
var userSelection = UserSelection(defaultValue: defaultValue)
22+
23+
// expect defaultValue is returned when selectedValue is nil
24+
#expect(userSelection.value == defaultValue)
25+
26+
// exercise the test by setting selectedValue
27+
userSelection.selectedValue = selectedValue
28+
29+
// expect selectedValue is returned when it is non-nil
30+
#expect(userSelection.value == selectedValue)
31+
32+
// exercise the test by resetting selectedValue to nil
33+
userSelection.selectedValue = nil
34+
35+
// expect defaultValue is returned when selectedValue is reset to nil
36+
#expect(userSelection.value == defaultValue)
37+
}
38+
}

0 commit comments

Comments
 (0)