Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,12 @@ let package = Package(
.target(name: "SnapshotKit"),
.target(name: "TestHostSupport"),
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
// Only `AccessibilitySnapshotCore` is imported; the umbrella
// product additionally pulls in the XCTest-facing half, widening
// the statically embedded closure of every consuming test bundle
// for no benefit.
// Keep the focused Core + SwiftUI renderer products: the
// umbrella additionally pulls in AccessibilitySnapshot's own
// SnapshotTesting integration, widening every consuming test
// bundle's statically embedded closure for no benefit.
.product(name: "AccessibilitySnapshotCore", package: "AccessibilitySnapshot"),
.product(name: "AccessibilitySnapshotPreviews", package: "AccessibilitySnapshot"),
],
path: "Shared/SnapshotKitTesting/Sources",
),
Expand Down
6 changes: 4 additions & 2 deletions Shared/SnapshotKit/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ Complements the root [`AGENTS.md`](../../AGENTS.md) — read that first.
treat it as a wire format. Adding an axis is safe only with a default that
is omitted (how `layoutDirection`/`legibilityWeight` landed).
- **`.accessibility` configs are preview-filtered.** `snapshotPreviews` drops
them because VoiceOver annotations require the test-only library; they only
render as snapshot tests. Don't "fix" previews to include them.
them because Stuff keeps AccessibilitySnapshot's annotation renderer in the
test-only `SnapshotKitTesting` product rather than linking it into shipping
UI modules; they only render as snapshot tests. Don't "fix" previews to
include them without a preview-only dependency boundary.
- **`SnapshotCase` content builders stay lazy.** Constructing a provider's
descriptor array must not instantiate every view or model; each content
access creates the independent value rendered by that configuration.
Expand Down
9 changes: 5 additions & 4 deletions Shared/SnapshotKit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ assertSnapshots(of: MyBadge.self)
## Notes

- Accessibility (`.accessibility`) configurations are **filtered out of the
preview cutsheet** — VoiceOver-annotated captures need the test-only library
and can't render in a plain Preview. They still run as snapshot tests. The
cutsheet also cannot reproduce the capture pipeline's UIKit-backed
`List`/`Form` height measurement, safe-area override, ready hooks, or
preview cutsheet** — Stuff keeps AccessibilitySnapshot's SwiftUI annotation
renderer in the test-only `SnapshotKitTesting` product instead of linking it
into every shipping UI module. They still run as snapshot tests. The cutsheet
also cannot reproduce the capture pipeline's UIKit-backed `List`/`Form`
height measurement, safe-area override, readiness hooks, or
tile-and-stitch pass, so CI's rendered dimensions remain authoritative.
- The Where app wraps content in its Broadway design-system root via a
`whereSnapshot(...)` adapter in `WhereUI`; SnapshotKit itself stays
Expand Down
10 changes: 5 additions & 5 deletions Shared/SnapshotKit/Sources/SnapshotCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public enum SnapshotMeasurementReadiness: Equatable, Sendable {
/// variants inside a `#Preview` — the same matrix, traits, and content the
/// snapshot tests capture. Test-only capture mechanics are documented on
/// ``previewConfigurations``.
/// Accessibility variants are excluded from that preview (see
/// ``previewConfigurations``); they only render as tests.
/// Accessibility variants are excluded from that preview because their
/// annotation renderer is test-only (see ``previewConfigurations``).
public struct SnapshotCase: Identifiable {
/// The case name — groups a component's variants and prefixes their
/// reference-image identifiers.
Expand Down Expand Up @@ -97,9 +97,9 @@ public struct SnapshotCase: Identifiable {
contentFactory = { AnyView(content()) }
}

/// The configurations that can render in a plain SwiftUI preview. Accessibility
/// captures need the test-only library's VoiceOver parser, so they're dropped
/// from the cutsheet (they still run as snapshot tests). The cutsheet also
/// The configurations that can render without linking test-only dependencies.
/// Accessibility annotations belong to `SnapshotKitTesting`, so those variants
/// are dropped from the cutsheet (they still run as snapshot tests). The cutsheet also
/// cannot perform the capture pipeline's UIKit-backed `List`/`Form`
/// measurement, safe-area override, async ready hook, or tile-and-stitch;
/// full-content preview height is therefore an approximation while the test
Expand Down
4 changes: 2 additions & 2 deletions Shared/SnapshotKit/Sources/SnapshotProviding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public protocol SnapshotProviding {

extension SnapshotProviding {
/// A scrollable cutsheet of every case's non-accessibility variants, for an
/// Xcode `#Preview`. Accessibility captures are excluded (they need the
/// test-only VoiceOver parser).
/// Xcode `#Preview`. Accessibility captures are excluded because their
/// annotation renderer belongs to the test-only `SnapshotKitTesting` product.
@MainActor public static var snapshotPreviews: some View {
SnapshotCutsheet(cases: snapshots)
}
Expand Down
9 changes: 7 additions & 2 deletions Shared/SnapshotKitTesting/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ Complements the root [`AGENTS.md`](../../AGENTS.md) — read that first.
## Scope & dependencies

- Depends on `SnapshotKit`, `TestHostSupport`, `SnapshotTesting`
(swift-snapshot-testing), and `AccessibilitySnapshot` (cashapp). It links the
comparison engine + XCTest/Testing, so it is **only** consumed by test
(swift-snapshot-testing), and AccessibilitySnapshot's focused Core + Previews
products (cashapp). It links the comparison engine + XCTest/Testing and the
SwiftUI accessibility renderer, so it is **only** consumed by test
bundles via `extraPackageProducts` — the per-module image bundles
(`WhereUISnapshotTests`, `PeriscopeToolsSnapshotTests`,
`InspectorSnapshotTests`, gathered into the `StuffSnapshotTests`
Expand All @@ -37,6 +38,10 @@ Complements the root [`AGENTS.md`](../../AGENTS.md) — read that first.
accessibility) flow through `renderSnapshotImage(...)`; its `async` is
load-bearing — a synchronous `Snapshotting` pullback could never settle
`.task`-driven content.
- **Accessibility annotations use AccessibilitySnapshot's SwiftUI renderer.**
Keep the focused `AccessibilitySnapshotCore` +
`AccessibilitySnapshotPreviews` products; the umbrella also links the
upstream SnapshotTesting integration that this module replaces.
- **The compare sees on-disk bytes.** Every capture round-trips through PNG
encoding before comparison; removing it re-opens the wide-gamut vs. sRGB
flake (see `renderSnapshotImage`'s doc).
Expand Down
6 changes: 3 additions & 3 deletions Shared/SnapshotKitTesting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ images.

It links the snapshot-comparison engine
([swift-snapshot-testing](https://github.com/pointfreeco/swift-snapshot-testing))
and the accessibility parser
and AccessibilitySnapshot's parser plus SwiftUI annotation renderer
([AccessibilitySnapshot](https://github.com/cashapp/AccessibilitySnapshot)), so
it is **only** linked by `*SnapshotTests` bundles — never a shipping app. It
re-exports `SnapshotKit` and `SnapshotTesting`, so a test author needs a single
Expand Down Expand Up @@ -70,8 +70,8 @@ re-exports `SnapshotKit` and `SnapshotTesting`, so a test author needs a single
starved CI machine) keeps waiting instead of failing falsely, giving up at a
hard cap several budgets out.
- **Accessibility captures** — for `.accessibility` configurations, content is
wrapped so the image is annotated with the VoiceOver reading order, labels,
traits, and activation points.
wrapped in AccessibilitySnapshot's SwiftUI renderer so the image is annotated
with the VoiceOver reading order, labels, traits, and activation points.
- **`\.isCapturingSnapshot`** — the pipeline overrides `SnapshotCaptureTrait`
on every captured controller, so SwiftUI content reads the SnapshotKit
environment flag as `true` and can freeze never-settling motion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import AccessibilitySnapshotCore
import AccessibilitySnapshotPreviews
import UIKit

/// Wraps a content view controller in an `AccessibilitySnapshotView` so a capture
/// is annotated with the VoiceOver reading order, labels, traits, and activation
/// points. The content is inset first (via ``InsetView``) so an all-element view's
/// region border isn't clipped, and the wrapper is sized to fit the annotations.
/// Wraps a content view controller in AccessibilitySnapshot's SwiftUI renderer
/// so a capture is annotated with the VoiceOver reading order, labels, traits,
/// and activation points. The content is inset first (via ``InsetView``) so an
/// all-element view's region border isn't clipped, and the wrapper is sized to
/// fit the annotations.
final class AccessibilitySnapshotViewController: UIViewController {
private let content: UIViewController

private var snapshotView: AccessibilitySnapshotView {
guard let snapshotView = view as? AccessibilitySnapshotView else {
private var snapshotView: SwiftUIAccessibilitySnapshotContainerView {
guard let snapshotView = view as? SwiftUIAccessibilitySnapshotContainerView else {
preconditionFailure(
"AccessibilitySnapshotViewController.view must be an AccessibilitySnapshotView.",
"AccessibilitySnapshotViewController.view must be a SwiftUIAccessibilitySnapshotContainerView.",
)
}
return snapshotView
Expand All @@ -30,7 +32,7 @@ final class AccessibilitySnapshotViewController: UIViewController {
}

override func loadView() {
view = AccessibilitySnapshotView(
view = SwiftUIAccessibilitySnapshotContainerView(
containedView: InsetView(content: content.view),
snapshotConfiguration: AccessibilitySnapshotConfiguration(
viewRenderingMode: .drawHierarchyInRect,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading