Fix various flakey tests.

This commit is contained in:
Doug 2024-09-19 11:03:26 +01:00 committed by Doug
parent d2d3b28dc7
commit 4996137006
16 changed files with 87 additions and 66 deletions

View File

@ -31,42 +31,7 @@ struct SwipeRightAction<Label: View>: ViewModifier {
content content
.offset(x: xOffset, y: 0.0) .offset(x: xOffset, y: 0.0)
.animation(.interactiveSpring().speed(0.5), value: xOffset) .animation(.interactiveSpring().speed(0.5), value: xOffset)
.gesture(DragGesture() .simultaneousGesture(gesture)
.updating($dragGestureActive) { _, state, _ in
// Available actions should be computed on the fly so we use a gesture state change
// to ask whether the move should be started or not.
state = true
}
.onChanged { value in
guard canStartAction else {
return
}
// We want to add a spring like behavior to the drag in which the view
// moves slower the more it's dragged. We use a circular easing function
// to generate those values up to the `swipeThreshold`
// The final translation will be between 0 and `swipeThreshold` with the action being enabled from
// `actionThreshold` onwards
let screenWidthNormalisedTranslation = max(0.0, min(value.translation.width, swipeThreshold)) / swipeThreshold
let easedTranslation = circularEaseOut(screenWidthNormalisedTranslation)
xOffset = easedTranslation * xOffsetThreshold
if xOffset > actionThreshold {
if !hasReachedActionThreshold {
feedbackGenerator.impactOccurred()
hasReachedActionThreshold = true
}
} else {
hasReachedActionThreshold = false
}
}
.onEnded { _ in
if xOffset > actionThreshold {
action()
}
xOffset = 0.0
})
.onChange(of: dragGestureActive) { value in .onChange(of: dragGestureActive) { value in
if value == true { if value == true {
if shouldStartAction() { if shouldStartAction() {
@ -86,6 +51,55 @@ struct SwipeRightAction<Label: View>: ViewModifier {
} }
} }
private var gesture: some Gesture {
DragGesture()
.updating($dragGestureActive) { _, state, _ in
// Available actions should be computed on the fly so we use a gesture state change
// to ask whether the move should be started or not.
state = true
}
.onChanged { value in
guard canStartAction, value.translation.width > value.translation.height else {
return
}
// Due to https://forums.developer.apple.com/forums/thread/760035 we had to make
// the drag a simultaneous gesture otherwise it was impossible to scroll the timeline.
// Therefore we need to prevent the animation to run if the user is to scrolling vertically.
// It would be nice if we could somehow abort the gesture in this case.
let width: CGFloat = if value.translation.width > abs(value.translation.height) {
value.translation.width
} else {
0.0
}
// We want to add a spring like behaviour to the drag in which the view
// moves slower the more it's dragged. We use a circular easing function
// to generate those values up to the `swipeThreshold`
// The final translation will be between 0 and `swipeThreshold` with the action being enabled from
// `actionThreshold` onwards
let screenWidthNormalisedTranslation = max(0.0, min(width, swipeThreshold)) / swipeThreshold
let easedTranslation = circularEaseOut(screenWidthNormalisedTranslation)
xOffset = easedTranslation * xOffsetThreshold
if xOffset > actionThreshold {
if !hasReachedActionThreshold {
feedbackGenerator.impactOccurred()
hasReachedActionThreshold = true
}
} else {
hasReachedActionThreshold = false
}
}
.onEnded { _ in
if xOffset > actionThreshold {
action()
}
xOffset = 0.0
}
}
/// Used to compute the horizontal translation amount. /// Used to compute the horizontal translation amount.
/// The more it's dragged the less it moves on a circular ease out curve /// The more it's dragged the less it moves on a circular ease out curve
private func circularEaseOut(_ value: Double) -> Double { private func circularEaseOut(_ value: Double) -> Double {

View File

@ -379,6 +379,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
return return
} }
await self?.updatePinnedEventIDs() await self?.updatePinnedEventIDs()
await self?.updatePermissions()
} }
} }
.store(in: &cancellables) .store(in: &cancellables)

View File

@ -380,10 +380,13 @@ class TimelineViewModelTests: XCTestCase {
func testPinnedEvents() async throws { func testPinnedEvents() async throws {
ServiceLocator.shared.settings.pinningEnabled = true ServiceLocator.shared.settings.pinningEnabled = true
// Note: We need to start the test with a non-default value so we know the view model has finished the Task.
let roomProxyMock = JoinedRoomProxyMock(.init(name: "", let roomProxyMock = JoinedRoomProxyMock(.init(name: "",
pinnedEventIDs: .init(["test1"]))) pinnedEventIDs: .init(["test1"])))
let actionsSubject = PassthroughSubject<JoinedRoomProxyAction, Never>() let actionsSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher() roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
let viewModel = TimelineViewModel(roomProxy: roomProxyMock, let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockRoomTimelineController(), timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
@ -409,9 +412,12 @@ class TimelineViewModelTests: XCTestCase {
func testCanUserPinEvents() async throws { func testCanUserPinEvents() async throws {
ServiceLocator.shared.settings.pinningEnabled = true ServiceLocator.shared.settings.pinningEnabled = true
let roomProxyMock = JoinedRoomProxyMock(.init(name: "", canUserPin: false))
// Note: We need to start the test with the non-default value so we know the view model has finished the Task.
let roomProxyMock = JoinedRoomProxyMock(.init(name: "", canUserPin: true))
let actionsSubject = PassthroughSubject<JoinedRoomProxyAction, Never>() let actionsSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher() roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
let viewModel = TimelineViewModel(roomProxy: roomProxyMock, let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
timelineController: MockRoomTimelineController(), timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
@ -423,13 +429,13 @@ class TimelineViewModelTests: XCTestCase {
analyticsService: ServiceLocator.shared.analytics) analyticsService: ServiceLocator.shared.analytics)
var deferred = deferFulfillment(viewModel.context.$viewState) { value in var deferred = deferFulfillment(viewModel.context.$viewState) { value in
!value.canCurrentUserPin value.canCurrentUserPin
} }
try await deferred.fulfill() try await deferred.fulfill()
roomProxyMock.canUserPinOrUnpinUserIDReturnValue = .success(true) roomProxyMock.canUserPinOrUnpinUserIDReturnValue = .success(false)
deferred = deferFulfillment(viewModel.context.$viewState) { value in deferred = deferFulfillment(viewModel.context.$viewState) { value in
value.canCurrentUserPin !value.canCurrentUserPin
} }
actionsSubject.send(.roomInfoUpdate) actionsSubject.send(.roomInfoUpdate)
try await deferred.fulfill() try await deferred.fulfill()