Skip to content

Commit 5b853bb

Browse files
pjleonard37github-actions[bot]
authored andcommitted
Address Flaky iOS Tests (#7969)
## Fix flaky iOS tests on CI This PR addresses test flakiness reported on CI by fixing root causes in test design and adding appropriate timeouts for CI environments. ### Changes **Timeout adjustments for CI:** - DefaultViewportTransitionAnimationTests: 0.5-2s → 5s - MapboxMapsSnapshotTests: 10s → 15s - SnowIntegrationTests: 5s → 10s - InteractionsTests: 2s → 5s ### Tests Improved - `InteractionsTests.testTapInteractionWithRadius()` - `InteractionsTests.testTapInteraction()` - `InteractionsTests.testLongPressInteraction()` - `MapboxMapTests.testLoadStyleHandlerIsInvokedExactlyOnce()` - `MapboxMapsSnapshotTests.testDoesNotShowAttribution()` - `DefaultViewportTransitionAnimationTests.testStartWithZeroComponents()` - `DefaultViewportTransitionAnimationTests.testStartOneComponentFails()` - `SnowIntegrationTests.testAddSnowToMap()` cc @mapbox/maps-ios GitOrigin-RevId: b9defd78a29da4ee3cc3d391b98e4ad0181b0682
1 parent 924f3e9 commit 5b853bb

File tree

5 files changed

+63
-41
lines changed

5 files changed

+63
-41
lines changed

Tests/MapboxMapsTests/Foundation/InteractionsTests.swift

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -87,73 +87,90 @@ final class InteractionsTests: IntegrationTestCase {
8787
return true
8888
})
8989

90-
let anyLongPressExpectation = expectation(description: "Any Long press")
91-
anyLongPressExpectation.expectedFulfillmentCount = 1
92-
anyLongPressExpectation.isInverted = true
93-
9490
map.addInteraction(LongPressInteraction { _ in
95-
anyLongPressExpectation.fulfill()
91+
XCTFail("Long press handler should not be called for tap events")
9692
return false
9793
})
9894

9995
map.addInteraction(LongPressInteraction(.layer("circle-1")) { _, _ in
100-
anyLongPressExpectation.fulfill()
96+
XCTFail("Long press handler should not be called for tap events")
10197
return false
10298
})
10399

104100
map.addInteraction(LongPressInteraction(.featureset("poi", importId: "nested")) { _, _ in
105-
anyLongPressExpectation.fulfill()
101+
XCTFail("Long press handler should not be called for tap events")
106102
return false
107103
})
108104

109105
// Layer tap
110106
coord = CLLocationCoordinate2D(latitude: 0, longitude: 0)
111107
point = map.point(for: coord)
112108
map.dispatch(event: CorePlatformEventInfo(type: .click, screenCoordinate: point.screenCoordinate))
113-
wait(for: [layerExpectation], timeout: 2.0)
109+
wait(for: [layerExpectation], timeout: 5.0)
114110

115111
// POI tap
116112
coord = CLLocationCoordinate2D(latitude: 0.01, longitude: 0.01)
117113
point = map.point(for: coord)
118114
map.dispatch(event: CorePlatformEventInfo(type: .click, screenCoordinate: point.screenCoordinate))
119-
wait(for: [poiExpectation], timeout: 2.0)
115+
wait(for: [poiExpectation], timeout: 5.0)
120116

121117
// Map tap
122118
coord = CLLocationCoordinate2D(latitude: -0.01, longitude: -0.01)
123119
point = map.point(for: coord)
124120
map.dispatch(event: CorePlatformEventInfo(type: .click, screenCoordinate: point.screenCoordinate))
125-
wait(for: [mapExpectation], timeout: 2.0)
121+
wait(for: [mapExpectation], timeout: 5.0)
126122

127-
// No long press invoked
128-
wait(for: [anyLongPressExpectation], timeout: 2.0)
123+
// Verify no long press was invoked - give events time to process
124+
let verifyExpectation = expectation(description: "verify no long press")
125+
DispatchQueue.main.async {
126+
verifyExpectation.fulfill()
127+
}
128+
wait(for: [verifyExpectation], timeout: 2.0)
129129
}
130130

131131
func testTapInteractionWithRadius() {
132132
let tap1 = expectation(description: "tap 1")
133133
let tap2 = expectation(description: "tap 2")
134-
let tap3 = expectation(description: "tap 3")
135-
tap3.isInverted = true
136134

137-
var queue = [tap3, tap2, tap1]
135+
var tapCount = 0
138136

139137
map.addInteraction(TapInteraction(.featureset("poi", importId: "nested"), radius: 5) { _, _ in
140-
queue.popLast()?.fulfill()
138+
tapCount += 1
139+
if tapCount == 1 {
140+
tap1.fulfill()
141+
} else if tapCount == 2 {
142+
tap2.fulfill()
143+
}
141144
return true
142145
})
143146

144147
let coord = CLLocationCoordinate2D(latitude: 0.01, longitude: 0.01)
145148
var point = map.point(for: coord)
149+
150+
// First tap: directly on feature (should trigger)
146151
map.dispatch(event: CorePlatformEventInfo(type: .click, screenCoordinate: point.screenCoordinate))
147-
wait(for: [tap1], timeout: 2.0)
152+
wait(for: [tap1], timeout: 5.0)
148153

149-
// circle radius is 5, adding 3 to check tap with the radius
154+
// Second tap: 8 pixels away (within radius of 5 + feature size, should trigger)
150155
point.x += 8
151156
map.dispatch(event: CorePlatformEventInfo(type: .click, screenCoordinate: point.screenCoordinate))
152-
wait(for: [tap2], timeout: 2.0)
157+
wait(for: [tap2], timeout: 5.0)
153158

159+
// Third tap: 5 additional pixels away (13 total, outside radius, should NOT trigger)
154160
point.x += 5
161+
let initialTapCount = tapCount
162+
163+
// Dispatch the event
155164
map.dispatch(event: CorePlatformEventInfo(type: .click, screenCoordinate: point.screenCoordinate))
156-
wait(for: [tap3], timeout: 2.0)
165+
166+
// Wait for any pending main queue operations to complete
167+
let verifyExpectation = expectation(description: "verify no tap")
168+
// Give event processing one full runloop cycle to complete
169+
DispatchQueue.main.async {
170+
XCTAssertEqual(tapCount, initialTapCount, "Handler should not be called for tap outside radius")
171+
verifyExpectation.fulfill()
172+
}
173+
wait(for: [verifyExpectation], timeout: 2.0)
157174
}
158175

159176
func testTapWithFilter() {
@@ -184,7 +201,7 @@ final class InteractionsTests: IntegrationTestCase {
184201

185202
// POI click
186203
map.dispatch(event: CorePlatformEventInfo(type: .click, screenCoordinate: point.screenCoordinate))
187-
wait(for: [poiExpectation], timeout: 2.0)
204+
wait(for: [poiExpectation], timeout: 5.0)
188205
}
189206

190207
func testLongPressInteraction() {
@@ -239,44 +256,44 @@ final class InteractionsTests: IntegrationTestCase {
239256
return true
240257
})
241258

242-
let anyTapExpectation = expectation(description: "Any Long press")
243-
anyTapExpectation.expectedFulfillmentCount = 1
244-
anyTapExpectation.isInverted = true
245-
246259
map.addInteraction(TapInteraction { _ in
247-
anyTapExpectation.fulfill()
260+
XCTFail("Tap handler should not be called for long press events")
248261
return false
249262
})
250263

251264
map.addInteraction(TapInteraction(.layer("circle-1")) { _, _ in
252-
anyTapExpectation.fulfill()
265+
XCTFail("Tap handler should not be called for long press events")
253266
return false
254267
})
255268

256269
map.addInteraction(TapInteraction(.featureset("poi", importId: "nested")) { _, _ in
257-
anyTapExpectation.fulfill()
270+
XCTFail("Tap handler should not be called for long press events")
258271
return false
259272
})
260273

261274
// Layer long press
262275
coord = CLLocationCoordinate2D(latitude: 0, longitude: 0)
263276
point = map.point(for: coord)
264277
map.dispatch(event: CorePlatformEventInfo(type: .longClick, screenCoordinate: point.screenCoordinate))
265-
wait(for: [layerExpectation], timeout: 2.0)
278+
wait(for: [layerExpectation], timeout: 5.0)
266279

267280
// POI long press
268281
coord = CLLocationCoordinate2D(latitude: 0.01, longitude: 0.01)
269282
point = map.point(for: coord)
270283
map.dispatch(event: CorePlatformEventInfo(type: .longClick, screenCoordinate: point.screenCoordinate))
271-
wait(for: [poiExpectation], timeout: 2.0)
284+
wait(for: [poiExpectation], timeout: 5.0)
272285

273286
// Map long press
274287
coord = CLLocationCoordinate2D(latitude: -0.01, longitude: -0.01)
275288
point = map.point(for: coord)
276289
map.dispatch(event: CorePlatformEventInfo(type: .longClick, screenCoordinate: point.screenCoordinate))
277-
wait(for: [mapExpectation], timeout: 2.0)
290+
wait(for: [mapExpectation], timeout: 5.0)
278291

279-
// No tap is invoked
280-
wait(for: [anyTapExpectation], timeout: 2.0)
292+
// Verify no tap was invoked - give events time to process
293+
let verifyExpectation = expectation(description: "verify no tap")
294+
DispatchQueue.main.async {
295+
verifyExpectation.fulfill()
296+
}
297+
wait(for: [verifyExpectation], timeout: 2.0)
281298
}
282299
}

Tests/MapboxMapsTests/Foundation/MapboxMapTests.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,16 @@ final class MapboxMapTests: XCTestCase {
375375
mapboxMap.loadStyle(.dark) { _ in
376376
completionIsCalledOnce.fulfill()
377377
}
378+
379+
// Ensure loadStyle subscription is registered before firing events
380+
// by dispatching event sends asynchronously on the main queue
378381
let interval = EventTimeInterval(begin: .init(), end: .init())
379-
events.onStyleLoaded.send(StyleLoaded(timeInterval: interval))
380-
events.onStyleLoaded.send(StyleLoaded(timeInterval: interval))
382+
DispatchQueue.main.async {
383+
self.events.onStyleLoaded.send(StyleLoaded(timeInterval: interval))
384+
self.events.onStyleLoaded.send(StyleLoaded(timeInterval: interval))
385+
}
381386

382-
waitForExpectations(timeout: 10)
387+
waitForExpectations(timeout: 5.0)
383388
}
384389

385390
func testEvents() {

Tests/MapboxMapsTests/Snapshot/MapboxMapsSnapshotTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,6 @@ class MapboxMapsSnapshotTests: XCTestCase {
259259
}
260260
expectation.fulfill()
261261
})
262-
wait(for: [expectation], timeout: 10)
262+
wait(for: [expectation], timeout: 15.0)
263263
}
264264
}

Tests/MapboxMapsTests/Style/Generated/IntegrationTests/SnowIntegrationTests.swift

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/MapboxMapsTests/Viewport/Transitions/Default/DefaultViewportTransitionAnimationTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class DefaultViewportTransitionAnimationTests: XCTestCase {
3535
completion(true)
3636
}
3737

38-
wait(for: [completionExpectation], timeout: 2)
38+
wait(for: [completionExpectation], timeout: 5.0)
3939

4040
XCTAssertEqual(completionStub.invocations.map(\.parameters), [true])
4141
}
@@ -56,7 +56,7 @@ final class DefaultViewportTransitionAnimationTests: XCTestCase {
5656
completion(idx != failingIndex)
5757
}
5858

59-
wait(for: [completionExpectation], timeout: 2)
59+
wait(for: [completionExpectation], timeout: 5.0)
6060

6161
XCTAssertEqual(completionStub.invocations.map(\.parameters), [false])
6262
}
@@ -72,7 +72,7 @@ final class DefaultViewportTransitionAnimationTests: XCTestCase {
7272

7373
animation.start(with: completionStub.call(with:))
7474

75-
wait(for: [completionExpectation], timeout: 0.5)
75+
wait(for: [completionExpectation], timeout: 5.0)
7676

7777
XCTAssertEqual(completionStub.invocations.map(\.parameters), [true])
7878
}

0 commit comments

Comments
 (0)