Files
KanbanGit/native/tests/navigation-macos.m
T
Twyco 4afaa79834
CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m4s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m31s
CI/CD / test_and_check_app (pull_request) Successful in 6m44s
CI/CD / Publish immutable image (pull_request) Skipped
fix(KAN-71): correct Apple navigation gestures and scroll bounds
2026-09-29 07:22:45 +02:00

143 lines
6.5 KiB
Objective-C

#import "../src-tauri/src/navigation.m"
// Exercise the AppKit event adapter deterministically; macos_smoke separately
// runs the shared history script against a real WKWebView.
@interface TestWindow : NSWindow
@end
@implementation TestWindow
- (BOOL)isKeyWindow { return YES; }
@end
@interface TestWebView : WKWebView
@property(nonatomic, strong) NSWindow *testWindow;
@property(nonatomic, strong) NSURL *testURL;
@property(nonatomic) BOOL back;
@property(nonatomic) BOOL forward;
@property(nonatomic) BOOL allowsSwipe;
@property(nonatomic) BOOL deferPermission;
@property(nonatomic, copy) void (^permission)(id, NSError *);
@property(nonatomic, strong) NSMutableArray<NSString *> *steps;
@end
@implementation TestWebView
- (NSWindow *)window { return self.testWindow; }
- (NSURL *)URL { return self.testURL; }
- (BOOL)canGoBack { return self.back; }
- (BOOL)canGoForward { return self.forward; }
- (NSPoint)convertPoint:(NSPoint)point fromView:(NSView *)view { return point; }
- (void)evaluateJavaScript:(NSString *)script completionHandler:(void (^)(id, NSError *))handler {
if ([script containsString:@".step("]) [self.steps addObject:script];
else if (self.deferPermission) self.permission = handler;
else handler(@(self.allowsSwipe), nil);
}
@end
@interface TestEvent : NSEvent
@property(nonatomic, strong) NSWindow *testWindow;
@property(nonatomic) NSEventPhase testPhase;
@property(nonatomic) NSEventPhase testMomentum;
@property(nonatomic) CGFloat dx;
@property(nonatomic) CGFloat dy;
@property(nonatomic) BOOL precise;
@property(nonatomic) NSInteger trackCount;
@property(nonatomic, copy) void (^tracking)(CGFloat, NSEventPhase, BOOL, BOOL *);
@end
@implementation TestEvent
- (NSWindow *)window { return self.testWindow; }
- (NSEventPhase)phase { return self.testPhase; }
- (NSEventPhase)momentumPhase { return self.testMomentum; }
- (CGFloat)scrollingDeltaX { return self.dx; }
- (CGFloat)scrollingDeltaY { return self.dy; }
- (BOOL)hasPreciseScrollingDeltas { return self.precise; }
- (NSPoint)locationInWindow { return NSMakePoint(20, 20); }
- (void)trackSwipeEventWithOptions:(NSEventSwipeTrackingOptions)options dampenAmountThresholdMin:(CGFloat)minimum max:(CGFloat)maximum usingHandler:(void (^)(CGFloat, NSEventPhase, BOOL, BOOL *))handler {
NSCAssert(options & NSEventSwipeTrackingLockDirection, @"Direction must be locked");
self.trackCount++;
self.tracking = handler;
}
@end
@interface TestNavigation : KanbanNavigation
@property(nonatomic) BOOL enabled;
@end
@implementation TestNavigation
- (BOOL)swipeTrackingEnabled { return self.enabled; }
@end
int main(void) {
@autoreleasepool {
[NSApplication sharedApplication];
TestWindow *window = [[TestWindow alloc] initWithContentRect:NSMakeRect(0, 0, 600, 400) styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO];
TestWebView *webview = [[TestWebView alloc] initWithFrame:NSMakeRect(0, 0, 600, 400)];
webview.testWindow = window;
webview.testURL = [NSURL URLWithString:@"https://example.com/board"];
webview.back = YES;
webview.forward = YES;
webview.allowsSwipe = YES;
webview.steps = [NSMutableArray new];
TestNavigation *navigation = [TestNavigation new];
navigation.webview = webview;
navigation.enabled = YES;
TestEvent *(^event)(void) = ^{
TestEvent *value = [TestEvent new];
value.testWindow = window;
value.testPhase = NSEventPhaseBegan;
value.precise = YES;
value.dx = 15;
return value;
};
BOOL stop = NO;
TestEvent *back = event();
[navigation handleEvent:back];
NSCAssert(back.trackCount == 1, @"A horizontal gesture must use AppKit tracking");
back.tracking(0.5, NSEventPhaseChanged, NO, &stop);
NSCAssert(webview.steps.count == 0, @"A partial swipe must not navigate");
back.tracking(1, NSEventPhaseNone, YES, &stop);
NSCAssert(webview.steps.count == 1 && [webview.steps.lastObject containsString:@"step(-1)"], @"Back must request exactly one step");
TestEvent *forward = event();
forward.dx = -15;
[navigation handleEvent:forward];
forward.tracking(-1, NSEventPhaseNone, YES, &stop);
NSCAssert(webview.steps.count == 2 && [webview.steps.lastObject containsString:@"step(1)"], @"Forward must request exactly one step");
TestEvent *cancelled = event();
[navigation handleEvent:cancelled];
cancelled.tracking(0, NSEventPhaseCancelled, YES, &stop);
NSCAssert(webview.steps.count == 2, @"Cancelled swipes must not navigate");
TestEvent *interrupted = event();
[navigation handleEvent:interrupted];
webview.allowsSwipe = NO;
[navigation handleEvent:event()];
interrupted.tracking(1, NSEventPhaseNone, YES, &stop);
NSCAssert(webview.steps.count == 2, @"A new gesture must cancel the previous gesture's animation callback");
for (NSInteger test = 0; test < 7; test++) {
TestEvent *ignored = event();
navigation.enabled = test != 0;
webview.allowsSwipe = test != 1;
webview.back = webview.forward = test != 2;
if (test == 3) ignored.precise = NO;
if (test == 4) ignored.dy = 30;
if (test == 5) ignored.testMomentum = NSEventPhaseChanged;
if (test == 6) ignored.testWindow = nil;
NSCAssert([navigation handleEvent:ignored] == ignored && ignored.trackCount == 0, @"Non-navigation events must remain available for scrolling");
}
navigation.enabled = YES;
webview.allowsSwipe = YES;
webview.back = YES;
webview.deferPermission = YES;
TestEvent *stale = event();
[navigation handleEvent:stale];
TestEvent *ended = event();
ended.testPhase = NSEventPhaseEnded;
[navigation handleEvent:ended];
webview.permission(@YES, nil);
NSCAssert(stale.trackCount == 0, @"Late hit tests must not resurrect ended gestures");
TestEvent *changedPage = event();
[navigation handleEvent:changedPage];
webview.testURL = [NSURL URLWithString:@"https://example.com/settings"];
webview.permission(@YES, nil);
NSCAssert(changedPage.trackCount == 0, @"Page changes must invalidate a pending gesture");
puts("PASS: AppKit back/forward, cancelled/partial swipes, system preference, nested scroll ownership, empty history, mouse/vertical/momentum events, stale callbacks");
}
return 0;
}