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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for disabling touch handling on SwiftUI views via the `allowsHitTesting` modifier
- Added SwiftUI documentation to the README.md
- Added properties to `CalendarViewProxy` for getting the `visibleMonthRange` and `visibleDayRange`
- Added ability to disable `DisplayLink` animations for test purposes (to prevent crashes) with an environment variable.

### Fixed
- Fixed an issue that could cause accessibility focus to shift unexpectedly
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Features:
- [Adding a day range indicator](#adding-a-day-range-indicator)
- [Adding grid lines](#adding-grid-lines)
- [Responding to day selection](#responding-to-day-selection)
- [Testing](#testing)
- [Accessibility Testing](#accessibility-testing)
- [Technical Details](#technical-details)
- [Contributions](#contributions)
- [Authors](#authors)
Expand Down Expand Up @@ -743,6 +745,46 @@ After building and running the app, tapping days should cause them to turn blue:

<img width="250" alt="Day Selection Screenshot" src="Docs/Images/tutorial_day_selection.png">

## Testing

### Accessibility Testing

If you're using accessibility testing frameworks like GTXiLib or performing automated UI testing, you may encounter issues with `CalendarView`'s DisplayLink
animations. To disable DisplayLink during tests, set the `HORIZON_CALENDAR_DISABLE_DISPLAY_LINK` environment variable to `true`.

#### XCTest

Add the environment variable to your test scheme:

1. Edit your test scheme in Xcode
2. Go to Test → Arguments → Environment Variables
3. Add `HORIZON_CALENDAR_DISABLE_DISPLAY_LINK` = `true`

Or set it programmatically in your test setup:
```swift
override func setUp() {
super.setUp()
setenv("HORIZON_CALENDAR_DISABLE_DISPLAY_LINK", "true", 1)
}
```

#### Launch Arguments

When launching your app for testing, pass the environment variable:
```swift
let app = XCUIApplication()
app.launchEnvironment = ["HORIZON_CALENDAR_DISABLE_DISPLAY_LINK": "true"]
app.launch()
```

#### Why This Helps

CalendarView uses `CADisplayLink` for smooth scroll animations. During accessibility testing, DisplayLink callbacks can fire before the view hierarchy is fully
initialized, causing crashes. Disabling DisplayLink in test mode prevents these crashes while still allowing you to test the calendar's accessibility features.

**Note:** When DisplayLink is disabled, scroll animations will complete immediately without animation. This is expected behavior in test mode and doesn't affect the
calendar's functionality or accessibility testing.

## Technical Details
If you'd like to learn about how `HorizonCalendar` was implemented, check out the [Technical Details](Docs/TECHNICAL_DETAILS.md) document. It provides an overview of `HorizonCalendar`'s architecture, along with information about why it's not implemented using `UICollectionView`.

Expand Down
19 changes: 19 additions & 0 deletions Sources/Public/CalendarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,13 @@ public final class CalendarView: UIView {
return false
}()

/// Checks if DisplayLink animations should be disabled for testing purposes.
/// Set the environment variable `HORIZON_CALENDAR_DISABLE_DISPLAY_LINK=true` to disable
/// DisplayLink animations during accessibility testing or other UI tests.
private var shouldDisableDisplayLink: Bool {
ProcessInfo.processInfo.environment["HORIZON_CALENDAR_DISABLE_DISPLAY_LINK"] == "true"
}

private var initialItemViewWasFocused = false {
didSet {
guard initialItemViewWasFocused != oldValue else { return }
Expand Down Expand Up @@ -833,6 +840,15 @@ public final class CalendarView: UIView {
}

private func startScrollingTowardTargetItem() {
// Skip DisplayLink setup if test mode is enabled
guard !shouldDisableDisplayLink else {
// Perform immediate scroll without animation when DisplayLink is disabled
if let scrollToItemContext {
finalizeScrollingTowardItem(for: scrollToItemContext)
}
return
}

let scrollToItemDisplayLink = CADisplayLink(
target: self,
selector: #selector(scrollToItemDisplayLinkFired))
Expand Down Expand Up @@ -1041,6 +1057,9 @@ public final class CalendarView: UIView {
func enableAutoScroll(offset: CGFloat) {
autoScrollOffset = offset

// Skip DisplayLink setup if test mode is enabled
guard !shouldDisableDisplayLink else { return }

if autoScrollDisplayLink == nil {
let autoScrollDisplayLink = CADisplayLink(
target: self,
Expand Down