Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@ abstract class UrlService {
String resolvePath(String path) {
final uri = Uri.parse(path);
if (uri.hasFragment) {
final fragmentUri = Uri.parse(uri.fragment);
if (fragmentUri.query.isNotEmpty) {
return '${fragmentUri.path}?${fragmentUri.query}';
}
return uri.fragment;
}
if (uri.query.isNotEmpty) {
return '${uri.path}?${uri.query}';
}
return uri.path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ class ModularRouteInformationParser
path = Modular.initialRoutePath;
}
} else {
// 3.10 wrapper
path = location;
// Preserve query parameters from the original URI
final query = routeInformation.uri.query;
path = query.isNotEmpty ? '$location?$query' : location;
}

return selectBook(
Expand Down Expand Up @@ -97,7 +98,18 @@ class ModularRouteInformationParser
book.routes.insert(0, child);
}

setArguments(modularArgs);
// Preserve query parameters from the resolved route.
// After resolving parent routes, the tracker updates arguments
// with the correct URI (including query params). Only restore
// old args if they already contain query params, otherwise
// keep the resolved args to avoid losing them.
final resolvedArgs =
getArguments().getOrElse((l) => ModularArguments.empty());
if (modularArgs.uri.hasQuery) {
setArguments(modularArgs);
} else {
setArguments(resolvedArgs);
}

for (final booksRoute in book.routes) {
reportPush(booksRoute);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ class UrlServiceMock extends Mock implements UrlService {}

class ParallelRouteFake extends Fake implements ModularRoute {}

class _ConcreteUrlService extends UrlService {
@override
String? getPath() => null;
}

void main() {
late ModularRouteInformationParser parser;
late GetRouteMock getRoute;
Expand Down Expand Up @@ -140,6 +145,49 @@ void main() {
expect(book.chapters('/').first.name, '/test');
});

test('selectBook with parents preserves query params after parent resolution',
() async {
final queryUri = Uri.parse('/auth/login?type=EMPRESA');
final routeMock = ParallelRouteMock();
when(() => routeMock.uri).thenReturn(Uri.parse('/auth/login'));
when(() => routeMock.parent).thenReturn('/auth');
when(() => routeMock.schema).thenReturn('/auth');
when(() => routeMock.name).thenReturn('/login');
when(() => routeMock.middlewares).thenReturn([Guard()]);
when(() => routeMock.copyWith(schema: any(named: 'schema')))
.thenReturn(routeMock);

final routeParent = ParallelRouteMock();
when(() => routeParent.uri).thenReturn(Uri.parse('/auth'));
when(() => routeParent.parent).thenReturn('');
when(() => routeParent.schema).thenReturn('');
when(() => routeParent.name).thenReturn('/auth');
when(() => routeParent.middlewares).thenReturn([Guard()]);
when(() => routeParent.copyWith(schema: any(named: 'schema')))
.thenReturn(routeParent);

when(() => reportPush(routeMock)).thenReturn(const Success(unit));
when(() => reportPush(routeParent)).thenReturn(const Success(unit));

when(() => getRoute.call(any())).thenAnswer((invocation) async {
final dto = invocation.positionalArguments.first as RouteParmsDTO;
if (dto.url.startsWith('/auth/login')) {
return Success(routeMock);
}
return Success(routeParent);
});

final argsWithQuery = ModularArguments(uri: queryUri);
when(() => getArguments.call()).thenReturn(Success(argsWithQuery));
when(() => setArguments.call(any())).thenReturn(const Success(unit));

await parser.selectBook('/auth/login?type=EMPRESA');

final captured = verify(() => setArguments.call(captureAny())).captured.last
as ModularArguments;
expect(captured.uri.queryParameters['type'], 'EMPRESA');
});

test('selectRoute with RedirectRoute', () async {
final redirect = RedirectRoute('/oo', to: '/test');
final modularArgument = ModularArguments.empty();
Expand Down Expand Up @@ -181,6 +229,7 @@ void main() {
expect(book.chapters().first.name, '/');
expect(book.chapters('/').first.name, '/test');
});

test('selectRoute with resolver route withless /', () async {
final args = ModularArguments.empty();

Expand Down Expand Up @@ -335,6 +384,40 @@ void main() {
expect(book.uri.toString(), '/parent/test');
expect(book.chapters().first.name, '/parent');
});

group('UrlService.resolvePath', () {
test('preserves query parameters', () {
final service = _ConcreteUrlService();
expect(
service.resolvePath('http://localhost/auth/login?type=EMPRESA'),
'/auth/login?type=EMPRESA',
);
});

test('preserves query parameters in fragment (HashStrategy)', () {
final service = _ConcreteUrlService();
expect(
service.resolvePath('http://localhost/#/auth/login?type=EMPRESA'),
'/auth/login?type=EMPRESA',
);
});

test('returns path without query when no query exists', () {
final service = _ConcreteUrlService();
expect(
service.resolvePath('http://localhost/auth/login'),
'/auth/login',
);
});

test('returns fragment without query when fragment has no query', () {
final service = _ConcreteUrlService();
expect(
service.resolvePath('http://localhost/#/auth/login'),
'/auth/login',
);
});
});
}

class Guard extends RouteGuard {
Expand Down
Loading