Skip to content

Commit 0b683bf

Browse files
committed
Raise SwiftNEW coverage above 90 percent
1 parent f5d783c commit 0b683bf

2 files changed

Lines changed: 343 additions & 0 deletions

File tree

Sources/SwiftNEW/SwiftNEW.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,62 @@ public struct SwiftNEW: View {
145145
compareVersion()
146146
}
147147
}
148+
149+
#if DEBUG
150+
@available(iOS 15.0, watchOS 8.0, macOS 12.0, tvOS 17.0, *)
151+
extension SwiftNEW {
152+
init(
153+
testingShow: Bool = false,
154+
items: [Vmodel] = [],
155+
loading: Bool = true,
156+
loadErrorMessage: String? = nil,
157+
loadedDataSource: String? = nil,
158+
historySheet: Bool = false,
159+
showSearch: Bool = false,
160+
searchText: String = "",
161+
debouncedSearchText: String = "",
162+
align: HorizontalAlignment = .center,
163+
color: Color = .accentColor,
164+
size: String = "simple",
165+
label: String = "Show Release Note",
166+
labelImage: String = "arrow.up.circle.fill",
167+
history: Bool = true,
168+
data: String = "data",
169+
showDrop: Bool = false,
170+
mesh: Bool = false,
171+
specialEffect: SwiftNEWSpecialEffect = .none,
172+
glass: Bool = true,
173+
presentation: SwiftNEWPresentation = .sheet,
174+
showBuild: Bool = true,
175+
headingStyle: SwiftNEWHeadingStyle = .version,
176+
iconStyle: SwiftNEWIconStyle = .filled
177+
) {
178+
_version = AppStorage(wrappedValue: "", "swiftnew.version")
179+
_build = AppStorage(wrappedValue: "", "swiftnew.build")
180+
_items = State(initialValue: items)
181+
_loading = State(initialValue: loading)
182+
_loadErrorMessage = State(initialValue: loadErrorMessage)
183+
_loadedDataSource = State(initialValue: loadedDataSource)
184+
_historySheet = State(initialValue: historySheet)
185+
_showSearch = State(initialValue: showSearch)
186+
_searchText = State(initialValue: searchText)
187+
_debouncedSearchText = State(initialValue: debouncedSearchText)
188+
_show = .constant(testingShow)
189+
_align = .constant(align)
190+
_color = .constant(color)
191+
_size = .constant(size)
192+
_label = .constant(label)
193+
_labelImage = .constant(labelImage)
194+
_history = .constant(history)
195+
_data = .constant(data)
196+
_showDrop = .constant(showDrop)
197+
_mesh = .constant(mesh)
198+
_specialEffect = .constant(specialEffect)
199+
_glass = .constant(glass)
200+
_presentation = .constant(presentation)
201+
_showBuild = .constant(showBuild)
202+
_headingStyle = .constant(headingStyle)
203+
_iconStyle = .constant(iconStyle)
204+
}
205+
}
206+
#endif
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
//
2+
// SwiftNEWViewCoverageTests.swift
3+
// SwiftNEW
4+
//
5+
6+
import SwiftUI
7+
import Testing
8+
@testable import SwiftNEW
9+
10+
#if os(macOS)
11+
import AppKit
12+
#endif
13+
14+
@MainActor
15+
@Test func swiftNEWInitializersPreserveConfiguration() {
16+
let direct = SwiftNEW(
17+
show: .constant(false),
18+
align: .trailing,
19+
color: .red,
20+
size: "mini",
21+
label: "Open",
22+
labelImage: "sparkles",
23+
history: false,
24+
data: "missing",
25+
showDrop: true,
26+
mesh: true,
27+
specialEffect: .particles,
28+
glass: false,
29+
presentation: .embed,
30+
showBuild: false,
31+
headingStyle: .appName,
32+
iconStyle: .plain
33+
)
34+
35+
#expect(direct.align == .trailing)
36+
#expect(direct.size == "mini")
37+
#expect(direct.label == "Open")
38+
#expect(direct.labelImage == "sparkles")
39+
#expect(direct.history == false)
40+
#expect(direct.data == "missing")
41+
#expect(direct.showDrop)
42+
#expect(direct.mesh)
43+
#expect(direct.specialEffect == .particles)
44+
#expect(direct.glass == false)
45+
#expect(direct.presentation == .embed)
46+
#expect(direct.showBuild == false)
47+
#expect(direct.headingStyle == .appName)
48+
#expect(direct.iconStyle == .plain)
49+
50+
let bound = SwiftNEW(
51+
show: .constant(false),
52+
align: .constant(.leading),
53+
color: .constant(.blue),
54+
size: .constant("invisible"),
55+
label: .constant("Hidden"),
56+
labelImage: .constant("eye.slash"),
57+
history: .constant(true),
58+
data: .constant("data"),
59+
showDrop: .constant(false),
60+
mesh: .constant(false),
61+
specialEffect: .constant(.christmas),
62+
glass: .constant(true),
63+
presentation: .constant(.sheet),
64+
showBuild: .constant(true),
65+
headingStyle: .constant(.versionOnly),
66+
iconStyle: .constant(.filled)
67+
)
68+
69+
#expect(bound.align == .leading)
70+
#expect(bound.size == "invisible")
71+
#expect(bound.label == "Hidden")
72+
#expect(bound.labelImage == "eye.slash")
73+
#expect(bound.specialEffect == .christmas)
74+
#expect(bound.presentation == .sheet)
75+
#expect(bound.headingStyle == .versionOnly)
76+
#expect(bound.iconStyle == .filled)
77+
}
78+
79+
@MainActor
80+
@Test func headingsReturnExpectedSubtitles() {
81+
let versionHeading = makeSwiftNEW(showBuild: true, headingStyle: .version)
82+
#expect(versionHeading.headingSubtitle == "Version \(Bundle.versionBuild)")
83+
84+
let versionHeadingWithoutBuild = makeSwiftNEW(showBuild: false, headingStyle: .version)
85+
#expect(versionHeadingWithoutBuild.headingSubtitle == "Version \(Bundle.version)")
86+
87+
let versionOnly = makeSwiftNEW(showBuild: false, headingStyle: .versionOnly)
88+
#expect(versionOnly.headingSubtitle == Bundle.version)
89+
90+
let appName = makeSwiftNEW(headingStyle: .appName)
91+
#expect(appName.headingSubtitle == Bundle.appName)
92+
}
93+
94+
@MainActor
95+
@Test func colorAndBundleHelpersReturnStableValues() {
96+
#expect(Color.white.adaptedTextColor == .black)
97+
#expect(Color.black.adaptedTextColor == .white)
98+
#expect(Bundle.versionBuild.contains("("))
99+
#expect(Bundle.appName == Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "")
100+
#expect(Bundle.main.iconFileName == nil)
101+
}
102+
103+
@MainActor
104+
@Test func versionComparisonPathIsCallable() async throws {
105+
let sut = makeSwiftNEW()
106+
107+
sut.compareVersion()
108+
try await Task.sleep(for: .milliseconds(150))
109+
110+
#expect(sut.version == Bundle.version || sut.version.isEmpty)
111+
}
112+
113+
@MainActor
114+
@Test func searchTextUpdatePathIsCallable() async throws {
115+
let sut = makeSwiftNEW(showSearch: true)
116+
117+
sut.updateSearchText("coverage")
118+
try await Task.sleep(for: .milliseconds(350))
119+
120+
#expect(sut.matchesSearch(Model(icon: "sparkles", title: "Search", subtitle: "Filter", body: "Coverage")))
121+
}
122+
123+
@MainActor
124+
@Test func loadDataReportsMissingLocalFiles() async throws {
125+
let sut = makeSwiftNEW(data: "missing-release-notes-file")
126+
127+
sut.loadData()
128+
try await Task.sleep(for: .milliseconds(400))
129+
130+
#expect(sut.data == "missing-release-notes-file")
131+
}
132+
133+
@MainActor
134+
@Test func loadDataHandlesInvalidRemoteURLs() async throws {
135+
let sut = makeSwiftNEW(data: "http://%")
136+
137+
sut.loadData()
138+
try await Task.sleep(for: .milliseconds(400))
139+
140+
#expect(sut.data == "http://%")
141+
}
142+
143+
#if os(macOS)
144+
@MainActor
145+
@Test func renderSwiftNEWEntryPoints() {
146+
render(makeSwiftNEW(size: "simple", glass: true, presentation: .sheet).body)
147+
render(makeSwiftNEW(size: "mini", glass: false, presentation: .fullScreenCover).body)
148+
render(makeSwiftNEW(mesh: true, specialEffect: .particles, presentation: .embed).body)
149+
render(makeSwiftNEW(mesh: false, specialEffect: .christmas, presentation: .embed).body)
150+
render(makeSwiftNEW(size: "invisible", presentation: .sheet).body)
151+
}
152+
153+
@MainActor
154+
@Test func renderComponentsForAlignmentAndIconStyles() {
155+
let model = sampleModel()
156+
157+
for alignment in [HorizontalAlignment.leading, .center, .trailing] {
158+
let filled = makeSwiftNEW(items: sampleItems(), loading: false, align: alignment, iconStyle: .filled)
159+
render(filled.headings)
160+
render(filled.iconBadge(systemName: model.icon))
161+
render(filled.releaseRow(model, bodyFont: .caption))
162+
render(filled.showHistoryButton)
163+
render(filled.searchButton)
164+
render(filled.closeCurrentButton)
165+
render(filled.closeHistoryButton)
166+
167+
let plain = makeSwiftNEW(align: alignment, iconStyle: .plain)
168+
render(plain.iconBadge(systemName: model.icon))
169+
render(plain.releaseRow(model, bodyFont: .footnote, spacing: 2))
170+
}
171+
}
172+
173+
@MainActor
174+
@Test func renderCurrentSheetStates() {
175+
let loading = makeSwiftNEW(loading: true)
176+
render(loading.sheetCurrent)
177+
178+
let error = makeSwiftNEW(loading: false, loadErrorMessage: "Unable to load release notes.")
179+
render(error.sheetCurrent)
180+
181+
let populated = makeSwiftNEW(
182+
items: sampleItems(),
183+
loading: false,
184+
showSearch: true,
185+
searchText: "coverage",
186+
debouncedSearchText: "coverage",
187+
history: true
188+
)
189+
render(populated.sheetCurrent)
190+
191+
let noHistory = makeSwiftNEW(
192+
items: sampleItems(),
193+
loading: false,
194+
showSearch: false,
195+
history: false
196+
)
197+
render(noHistory.sheetCurrent)
198+
}
199+
200+
@MainActor
201+
@Test func renderHistorySheetAndEffects() {
202+
let sut = makeSwiftNEW(items: sampleItems(), loading: false, mesh: true)
203+
204+
render(sut.sheetHistory)
205+
render(MeshView(color: .constant(.purple)))
206+
render(NoiseView(size: 128))
207+
render(SnowfallView())
208+
render(FloatingParticlesView())
209+
render(Color.red.swiftNEWGlass(radius: 8, color: .blue.opacity(0.2)))
210+
}
211+
#endif
212+
213+
@MainActor
214+
private func makeSwiftNEW(
215+
items: [Vmodel] = [],
216+
loading: Bool = true,
217+
loadErrorMessage: String? = nil,
218+
showSearch: Bool = false,
219+
searchText: String = "",
220+
debouncedSearchText: String = "",
221+
align: HorizontalAlignment = .center,
222+
color: Color = .accentColor,
223+
size: String = "simple",
224+
history: Bool = true,
225+
data: String = "data",
226+
mesh: Bool = false,
227+
specialEffect: SwiftNEWSpecialEffect = .none,
228+
glass: Bool = true,
229+
presentation: SwiftNEWPresentation = .sheet,
230+
showBuild: Bool = true,
231+
headingStyle: SwiftNEWHeadingStyle = .version,
232+
iconStyle: SwiftNEWIconStyle = .filled
233+
) -> SwiftNEW {
234+
SwiftNEW(
235+
testingShow: false,
236+
items: items,
237+
loading: loading,
238+
loadErrorMessage: loadErrorMessage,
239+
showSearch: showSearch,
240+
searchText: searchText,
241+
debouncedSearchText: debouncedSearchText,
242+
align: align,
243+
color: color,
244+
size: size,
245+
label: "Show Release Note",
246+
labelImage: "arrow.up.circle.fill",
247+
history: history,
248+
data: data,
249+
showDrop: false,
250+
mesh: mesh,
251+
specialEffect: specialEffect,
252+
glass: glass,
253+
presentation: presentation,
254+
showBuild: showBuild,
255+
headingStyle: headingStyle,
256+
iconStyle: iconStyle
257+
)
258+
}
259+
260+
private func sampleItems() -> [Vmodel] {
261+
[
262+
Vmodel(version: Bundle.version, subVersion: "6.3.1", new: [
263+
sampleModel(),
264+
Model(icon: "checkmark.shield", title: "Compatibility", subtitle: "Fixes", body: "Older compiler coverage")
265+
]),
266+
Vmodel(version: "6.2", subVersion: "6.2.0", new: [
267+
Model(icon: "clock", title: "History", subtitle: "Entry", body: "Previous release")
268+
])
269+
]
270+
}
271+
272+
private func sampleModel() -> Model {
273+
Model(icon: "sparkles", title: "Search", subtitle: "Filter", body: "Coverage")
274+
}
275+
276+
#if os(macOS)
277+
@MainActor
278+
private func render<V: View>(_ view: V) {
279+
let host = NSHostingView(rootView: AnyView(view))
280+
host.frame = NSRect(x: 0, y: 0, width: 900, height: 900)
281+
host.layoutSubtreeIfNeeded()
282+
_ = host.fittingSize
283+
}
284+
#endif

0 commit comments

Comments
 (0)