1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
@testable import Grain
import PhotosUI
import SwiftUI
import Testing
/// Rendering each face of the story composer. The camera never reaches ready
/// in the simulator, so these cover the placeholder states of the viewfinder
/// and drive the review stage directly with a photo.
@MainActor
struct StoryComposerRenderTests {
private let photo = UIGraphicsImageRenderer(size: CGSize(width: 120, height: 160)).image { context in
UIColor.systemTeal.setFill()
context.fill(CGRect(x: 0, y: 0, width: 120, height: 160))
}
// MARK: - Capture stage
@Test func rendersTheViewfinderWhileCameraAccessIsDenied() async {
let camera = StoryCamera(requestAccess: { false })
await camera.start()
ViewRender.render(captureStage(camera: camera))
}
@Test func rendersTheViewfinderWhenThereIsNoCamera() async {
let camera = StoryCamera(requestAccess: { true })
await camera.start()
ViewRender.render(captureStage(camera: camera))
}
@Test func rendersTheViewfinderWhileStartingAndWithAnError() {
ViewRender.render(captureStage(camera: StoryCamera(requestAccess: { true }), error: "Couldn't take the photo. Try again."))
}
private func captureStage(camera: StoryCamera, error: String? = nil) -> some View {
StoryCaptureStage(
camera: camera,
selectedPhoto: .constant(nil),
errorMessage: error,
onCapture: { _ in },
onCaptureFailed: {},
onCancel: {}
)
.background(Color.black)
}
// MARK: - Review stage
@Test func rendersTheReviewStageWithNothingSet() {
ViewRender.render(reviewStage())
}
@Test func rendersTheReviewStageWithEveryDetailSet() {
ViewRender.render(reviewStage(
location: "Golden Gate Park",
labels: "Nudity, Sexual",
postToBluesky: true
))
}
@Test func rendersTheReviewStageWhilePostingAndAfterAFailure() {
ViewRender.render(reviewStage(isUploading: true))
ViewRender.render(reviewStage(error: "HTTP 413: PayloadTooLarge"))
}
private func reviewStage(
location: String? = nil,
labels: String? = nil,
postToBluesky: Bool = false,
error: String? = nil,
isUploading: Bool = false
) -> some View {
StoryReviewStage(
image: photo,
locationName: location,
labelSummary: labels,
postToBluesky: .constant(postToBluesky),
errorMessage: error,
isUploading: isUploading,
onRetake: {},
onEditLocation: {},
onClearLocation: {},
onEditLabels: {},
onClearLabels: {},
onPost: {}
)
.background(Color.black)
}
// MARK: - Detail sheets
@Test func rendersTheLocationSheetBeforeAndAfterAPick() {
let env = TestEnvironment()
ViewRender.render(
StoryLocationSheet(resolvedLocation: .constant(nil), photoLocationResult: nil) { _ in }
.withTestEnvironment(env)
)
ViewRender.render(
StoryLocationSheet(
resolvedLocation: .constant((h3: "8a2a1072b59ffff", name: "Lisboa", address: nil)),
photoLocationResult: nil
) { _ in }
.withTestEnvironment(env)
)
}
@Test func rendersTheContentLabelSheetExpanded() {
let env = TestEnvironment()
ViewRender.render(
StoryContentLabelSheet(selectedLabels: .constant(["nudity"]))
.withTestEnvironment(env)
)
}
// MARK: - The whole composer
@Test func rendersTheComposerWithADeniedCamera() async {
await withGrainEnvironment {
MockURLProtocol.respondByPath(Fixtures.routes)
defer { MockURLProtocol.handler = nil }
let env = TestEnvironment()
ViewRender.render(
StoryCreateView(client: env.client, camera: StoryCamera(requestAccess: { false }))
.withTestEnvironment(env),
settle: 0.3
)
}
}
}