Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c717196
Fix Attach to roads flows after Plan Route refactor
aleksandr-tata Sep 7, 2026
7b33713
Fix Attach to roads during active navigation
aleksandr-tata Sep 7, 2026
3b5cd01
Preserve source track identity in Attach to roads flow
aleksandr-tata Sep 8, 2026
610fefd
Preserve source GPX path during active Follow Track
aleksandr-tata Sep 8, 2026
bd48146
Preserve source GPX path when entering Route Planning
aleksandr-tata Sep 9, 2026
7a81ff1
Separate Attach apply from Plan Route navigation
aleksandr-tata Sep 9, 2026
ab662ff
Handle Attach apply failures without closing Plan Route
aleksandr-tata Sep 9, 2026
2e7fb0d
Fix generic Plan Route navigation for unchanged tracks
aleksandr-tata Sep 9, 2026
505a322
Fix adding POIs to pathless Attach tracks
aleksandr-tata Sep 9, 2026
4cafd0c
Preserve source folder for pathless Attach tracks
aleksandr-tata Sep 9, 2026
3c396bf
Remove obsolete route planning controller
aleksandr-tata Sep 9, 2026
faabbc2
Remove duplicate reorder segment command
aleksandr-tata Sep 9, 2026
b2fefbe
Fix single-point Plan Route navigation
aleksandr-tata Sep 9, 2026
ae2ddbf
Restore approximation before Plan Route navigation
aleksandr-tata Sep 9, 2026
b1d76da
Restore default profile fallback in Plan Route navigation
aleksandr-tata Sep 10, 2026
90fcb4a
Fix navigation from empty Plan Route
aleksandr-tata Sep 10, 2026
25affcb
Fix Follow Track Plan Route presentation race
aleksandr-tata Sep 10, 2026
dc34c40
Fix Edit Track and Attach intent handling
aleksandr-tata Sep 10, 2026
c22709f
Move approximation callback dispatch to data provider
aleksandr-tata Sep 10, 2026
4a5530d
Deduplicate GPX test directory path
aleksandr-tata Sep 10, 2026
9f1fb73
Fix navigation between unrouted Plan Route points
aleksandr-tata Sep 10, 2026
0fd23e5
Fix Follow Track mode in Plan Route
aleksandr-tata Sep 10, 2026
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
265 changes: 265 additions & 0 deletions OsmAnd MapsTests/PlanRoute/OAPlanRouteEditingBridgeTest.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
#import <XCTest/XCTest.h>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this test written in Swift?

#import <CoreLocation/CoreLocation.h>
#import <OsmAndShared/OsmAndShared.h>
#import "OAApplicationMode.h"
#import "OAGpxData.h"
#import "OAMapActions.h"
#import "OAMeasurementEditingContext.h"
#import "OAPlanRouteEditingBridge.h"
#import "OARoutingHelper.h"

static OASWptPt *createPoint(double latitude, double longitude)
{
OASWptPt *point = [[OASWptPt alloc] init];
point.lat = latitude;
point.lon = longitude;
return point;
}

@interface OAPlanRouteEditingBridge (Testing)

+ (OAMeasurementEditingContext *)editingContextForGpxFile:(OASGpxFile *)gpxFile
applicationMode:(OAApplicationMode *)applicationMode
selectedSegment:(NSInteger)selectedSegment;
+ (nullable NSString *)navigationFilePathForExportedGpx:(OASGpxFile *)gpxFile
sourceFilePath:(nullable NSString *)sourceFilePath;
+ (BOOL)canApplyAttachedTrackWithRoute:(BOOL)hasRoute changes:(BOOL)hasChanges;
+ (EOAPlanRouteNavigationResult)genericNavigationPreflightResultWithContext:(BOOL)hasContext;
+ (BOOL)shouldNavigateDirectlyToPointWithPointCount:(NSInteger)pointCount;
+ (BOOL)shouldRequestApproximationBeforeNavigationWithPointCount:(NSInteger)pointCount
hasRoute:(BOOL)hasRoute
approximationNeeded:(BOOL)approximationNeeded;
- (nullable OASGpxFile *)navigationGpxWithEditingContext:(OAMeasurementEditingContext *)context
trackName:(NSString *)trackName;
- (void)performNavigationWithGpx:(OASGpxFile *)gpx
editingContext:(OAMeasurementEditingContext *)context
routingHelper:(OARoutingHelper *)routingHelper
mapActions:(OAMapActions *)mapActions
followTrackMode:(BOOL)followTrackMode
sourceFilePath:(nullable NSString *)sourceFilePath;
+ (EOAPlanRouteNavigationResult)attachNavigationPreflightResultWithContext:(BOOL)hasContext
hasRoute:(BOOL)hasRoute
hasChanges:(BOOL)hasChanges;

@end

@interface OAPlanRouteTestRoutingHelper : NSObject

@end

@implementation OAPlanRouteTestRoutingHelper

- (BOOL)isFollowingMode
{
return NO;
}

@end

@interface OAPlanRouteTestMapActions : NSObject

@property (nonatomic) OAApplicationMode *capturedAppMode;

@end

@implementation OAPlanRouteTestMapActions

- (void)stopNavigationWithoutConfirm
{
}

- (void)enterRoutePlanningModeGivenGpx:(OASGpxFile *)gpxFile
appMode:(OAApplicationMode *)appMode
path:(NSString *)path
from:(CLLocation *)from
fromName:(OAPointDescription *)fromName
useIntermediatePointsByDefault:(BOOL)useIntermediatePointsByDefault
showDialog:(BOOL)showDialog
{
self.capturedAppMode = appMode;
}

@end

@interface OAPlanRouteEditingBridgeTest : XCTestCase

@end

@implementation OAPlanRouteEditingBridgeTest

- (void)testInMemoryGpxWithoutPathLoadsSelectedSegmentWithNavigationMode
{
OASGpxFile *gpxFile = [[OASGpxFile alloc] initWithAuthor:@"test"];
OASTrack *track = [[OASTrack alloc] init];
OASTrkSegment *firstSegment = [[OASTrkSegment alloc] init];
firstSegment.points = [NSMutableArray arrayWithObjects:createPoint(1, 2), createPoint(2, 3), nil];
OASTrkSegment *secondSegment = [[OASTrkSegment alloc] init];
secondSegment.points = [NSMutableArray arrayWithObjects:createPoint(3, 4), createPoint(4, 5), nil];
track.segments = [NSMutableArray arrayWithObjects:firstSegment, secondSegment, nil];
gpxFile.tracks = [NSMutableArray arrayWithObject:track];

OAApplicationMode *applicationMode = OAApplicationMode.BICYCLE;
OAMeasurementEditingContext *context = [OAPlanRouteEditingBridge editingContextForGpxFile:gpxFile
applicationMode:applicationMode
selectedSegment:1];
[context addPoints];

XCTAssertEqual(gpxFile.path.length, 0);
XCTAssertEqual(context.gpxData.gpxFile, gpxFile);
XCTAssertEqual(context.appMode, applicationMode);
XCTAssertEqual(context.selectedSegment, 1);
XCTAssertEqual(context.getPoints.count, 2);
XCTAssertEqualWithAccuracy(context.getPoints.firstObject.lat, 3, DBL_EPSILON);
XCTAssertEqualWithAccuracy(context.getPoints.lastObject.lon, 5, DBL_EPSILON);
}

- (void)testRoutePointProfileOverridesNavigationMode
{
OASGpxFile *gpxFile = [[OASGpxFile alloc] initWithAuthor:@"test"];
OASWptPt *firstPoint = createPoint(1, 2);
OASWptPt *lastPoint = createPoint(2, 3);
[lastPoint setProfileTypeProfileType:OAApplicationMode.CAR.stringKey];
[gpxFile addRoutePointsPoints:@[firstPoint, lastPoint] addRoute:YES];

OAMeasurementEditingContext *context = [OAPlanRouteEditingBridge editingContextForGpxFile:gpxFile
applicationMode:OAApplicationMode.BICYCLE
selectedSegment:-1];

XCTAssertEqual(context.appMode, OAApplicationMode.CAR);
}

- (void)testSourceFilePathOverridesPathlessExportForNavigation
{
OASGpxFile *exportedGpx = [[OASGpxFile alloc] initWithAuthor:@"test"];
NSString *sourceFilePath = @"/Documents/GPX/twisty-route.gpx";

NSString *navigationFilePath = [OAPlanRouteEditingBridge navigationFilePathForExportedGpx:exportedGpx
sourceFilePath:sourceFilePath];

XCTAssertEqualObjects(navigationFilePath, sourceFilePath);
XCTAssertEqual(exportedGpx.path.length, 0);
}

- (void)testPathlessExportWithoutSourceFilePathKeepsNavigationPathEmpty
{
OASGpxFile *exportedGpx = [[OASGpxFile alloc] initWithAuthor:@"test"];

NSString *navigationFilePath = [OAPlanRouteEditingBridge navigationFilePathForExportedGpx:exportedGpx
sourceFilePath:nil];

XCTAssertNil(navigationFilePath);
}

- (void)testAttachApplyRequiresRouteOrChanges
{
XCTAssertFalse([OAPlanRouteEditingBridge canApplyAttachedTrackWithRoute:NO changes:NO]);
XCTAssertTrue([OAPlanRouteEditingBridge canApplyAttachedTrackWithRoute:YES changes:NO]);
XCTAssertTrue([OAPlanRouteEditingBridge canApplyAttachedTrackWithRoute:NO changes:YES]);
XCTAssertTrue([OAPlanRouteEditingBridge canApplyAttachedTrackWithRoute:YES changes:YES]);
}

- (void)testGenericNavigationAllowsUnchangedPlainTrack
{
OASGpxFile *gpxFile = [[OASGpxFile alloc] initWithAuthor:@"test"];
OASTrack *track = [[OASTrack alloc] init];
OASTrkSegment *segment = [[OASTrkSegment alloc] init];
segment.points = [NSMutableArray arrayWithObjects:createPoint(1, 2), createPoint(2, 3), nil];
track.segments = [NSMutableArray arrayWithObject:segment];
gpxFile.tracks = [NSMutableArray arrayWithObject:track];

OAMeasurementEditingContext *context = [OAPlanRouteEditingBridge editingContextForGpxFile:gpxFile
applicationMode:OAApplicationMode.CAR
selectedSegment:-1];
[context addPoints];
EOAPlanRouteNavigationResult result =
[OAPlanRouteEditingBridge genericNavigationPreflightResultWithContext:context != nil];
OASGpxFile *navigationGpx = [[[OAPlanRouteEditingBridge alloc] init] navigationGpxWithEditingContext:context
trackName:@"plain-track"];

XCTAssertFalse(context.hasRoute);
XCTAssertFalse(context.hasChanges);
XCTAssertEqual(result, EOAPlanRouteNavigationResultSuccess);
XCTAssertNotNil(navigationGpx);
XCTAssertEqual(navigationGpx.path.length, 0);
}

- (void)testGenericNavigationPassesNilForDefaultEditingMode
{
OAPlanRouteEditingBridge *bridge = [[OAPlanRouteEditingBridge alloc] init];
OAMeasurementEditingContext *context = [[OAMeasurementEditingContext alloc] init];
context.appMode = OAApplicationMode.DEFAULT;
OAPlanRouteTestRoutingHelper *routingHelper = [[OAPlanRouteTestRoutingHelper alloc] init];
OAPlanRouteTestMapActions *mapActions = [[OAPlanRouteTestMapActions alloc] init];

[bridge performNavigationWithGpx:[[OASGpxFile alloc] initWithAuthor:@"test"]
editingContext:context
routingHelper:(OARoutingHelper *)routingHelper
mapActions:(OAMapActions *)mapActions
followTrackMode:NO
sourceFilePath:nil];

XCTAssertNil(mapActions.capturedAppMode);
}

- (void)testGenericNavigationPreservesExplicitEditingMode
{
OAPlanRouteEditingBridge *bridge = [[OAPlanRouteEditingBridge alloc] init];
OAMeasurementEditingContext *context = [[OAMeasurementEditingContext alloc] init];
context.appMode = OAApplicationMode.BICYCLE;
OAPlanRouteTestRoutingHelper *routingHelper = [[OAPlanRouteTestRoutingHelper alloc] init];
OAPlanRouteTestMapActions *mapActions = [[OAPlanRouteTestMapActions alloc] init];

[bridge performNavigationWithGpx:[[OASGpxFile alloc] initWithAuthor:@"test"]
editingContext:context
routingHelper:(OARoutingHelper *)routingHelper
mapActions:(OAMapActions *)mapActions
followTrackMode:NO
sourceFilePath:nil];

XCTAssertEqual(mapActions.capturedAppMode, OAApplicationMode.BICYCLE);
}

- (void)testGenericNavigationUsesDirectDestinationOnlyForOnePoint
{
XCTAssertFalse([OAPlanRouteEditingBridge shouldNavigateDirectlyToPointWithPointCount:0]);
XCTAssertTrue([OAPlanRouteEditingBridge shouldNavigateDirectlyToPointWithPointCount:1]);
XCTAssertFalse([OAPlanRouteEditingBridge shouldNavigateDirectlyToPointWithPointCount:2]);
}

- (void)testGenericNavigationRequestsApproximationBeforeExport
{
XCTAssertTrue([OAPlanRouteEditingBridge shouldRequestApproximationBeforeNavigationWithPointCount:928
hasRoute:NO
approximationNeeded:YES]);
XCTAssertFalse([OAPlanRouteEditingBridge shouldRequestApproximationBeforeNavigationWithPointCount:928
hasRoute:YES
approximationNeeded:YES]);
XCTAssertFalse([OAPlanRouteEditingBridge shouldRequestApproximationBeforeNavigationWithPointCount:928
hasRoute:NO
approximationNeeded:NO]);
XCTAssertFalse([OAPlanRouteEditingBridge shouldRequestApproximationBeforeNavigationWithPointCount:1
hasRoute:NO
approximationNeeded:YES]);
}

- (void)testAttachApplyPreflightReportsFailureReason
{
EOAPlanRouteNavigationResult invalidContext =
[OAPlanRouteEditingBridge attachNavigationPreflightResultWithContext:NO
hasRoute:NO
hasChanges:NO];
EOAPlanRouteNavigationResult missingApproximation =
[OAPlanRouteEditingBridge attachNavigationPreflightResultWithContext:YES
hasRoute:NO
hasChanges:NO];
EOAPlanRouteNavigationResult success =
[OAPlanRouteEditingBridge attachNavigationPreflightResultWithContext:YES
hasRoute:YES
hasChanges:NO];

XCTAssertEqual(invalidContext, EOAPlanRouteNavigationResultInvalidContext);
XCTAssertEqual(missingApproximation, EOAPlanRouteNavigationResultMissingApproximationResult);
XCTAssertEqual(success, EOAPlanRouteNavigationResultSuccess);
}

@end
45 changes: 45 additions & 0 deletions OsmAnd MapsTests/PlanRoute/PlanRouteTrackSourceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import XCTest

final class PlanRouteTrackSourceTests: XCTestCase {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private let gpxDirectory = "/Documents/GPX"

func testPathlessGpxKeepsSourceFilePathSeparateFromEditableFilePath() {
let source = PlanRouteTrackSource(gpxFilePath: "", sourceFilePath: "/Documents/GPX/twisty-route.gpx")

XCTAssertNil(source.editableFilePath)
XCTAssertEqual(source.sourceFilePath, "/Documents/GPX/twisty-route.gpx")
}

func testPathlessGpxUsesSourceFilePathForWaypointEditing() {
let source = PlanRouteTrackSource(gpxFilePath: "", sourceFilePath: "/Documents/GPX/twisty-route.gpx")

XCTAssertEqual(source.waypointEditingFilePath, "/Documents/GPX/twisty-route.gpx")
}

func testPathlessGpxUsesSourceFolderForSaving() {
let source = PlanRouteTrackSource(gpxFilePath: "", sourceFilePath: "/Documents/GPX/import/twisty-route.gpx")

XCTAssertEqual(source.savingFolder(relativeTo: "/Documents/GPX"), "import")
}

func testGpxInRootFolderHasNoSavingSubfolder() {
let source = PlanRouteTrackSource(gpxFilePath: "/Documents/GPX/twisty-route.gpx", sourceFilePath: nil)

XCTAssertNil(source.savingFolder(relativeTo: "/Documents/GPX"))
}

func testGpxFilePathIsUsedWhenExplicitSourceFilePathIsMissing() {
let source = PlanRouteTrackSource(gpxFilePath: "/Documents/GPX/twisty-route.gpx", sourceFilePath: nil)

XCTAssertEqual(source.editableFilePath, "/Documents/GPX/twisty-route.gpx")
XCTAssertEqual(source.sourceFilePath, "/Documents/GPX/twisty-route.gpx")
}

func testPathlessGpxWithoutSourceFilePathKeepsBothPathsEmpty() {
let source = PlanRouteTrackSource(gpxFilePath: "", sourceFilePath: nil)

XCTAssertNil(source.editableFilePath)
XCTAssertNil(source.sourceFilePath)
XCTAssertNil(source.waypointEditingFilePath)
XCTAssertNil(source.savingFolder(relativeTo: "/Documents/GPX"))
}
}
Loading