This is the honest cost of being codegen-free rather than a bug, but it changes how apps test and is currently undocumented.
With a route table (auto_route, go_router) you can walk it at runtime and assert over every route: "these N are auth-protected", "these are the analytics names", "no route is unreachable". With sealed types, Dart cannot enumerate a sealed types subtypes at runtime, so those invariants have nowhere typed to iterate.
What apps end up doing
Both such tests in one migration became regex passes over the route source files:
final routeFiles = Directory(lib/.../routes).listSync().whereType<File>();
RegExp("String get routeName => ([A-Za-z0-9_]+)")
.allMatches(file.readAsStringSync());
That catches drift, but a text search is standing in for a type-level property.
It bites hardest on safety-critical invariants. An "is this screen reachable while locked?" classification is a switch over route types with a default. Verifying it exhaustively needs an instance of every route — and routes carrying domain objects cannot be cheaply constructed in a test. In that app only 28 of 41 relevant routes could be instantiated; the rest had to be covered by grepping the classifications own source.
What already works
Exhaustiveness is fully solved for anything that dispatches on a route — page builders, codecs, transition wrappers all get compile-time totality from switch. The gap is specifically invariants over the set of routes.
Suggestion
At minimum, document the trade-off and the source-level testing pattern so teams do not discover it when writing their first whole-table test.
Optionally, endorse a convention the compiler can help with:
sealed class AppRoute extends KaiselRoute {
const AppRoute();
static const all = <AppRoute>[Home(), Settings(), /* ... */];
}
Hand-maintained, but usable by whole-table tests, and drift is at least visible in review. A lint (pkg:kaisel_lint) that flags a route variant missing from such a list would make it reliable.
This is the honest cost of being codegen-free rather than a bug, but it changes how apps test and is currently undocumented.
With a route table (auto_route, go_router) you can walk it at runtime and assert over every route: "these N are auth-protected", "these are the analytics names", "no route is unreachable". With sealed types, Dart cannot enumerate a sealed types subtypes at runtime, so those invariants have nowhere typed to iterate.
What apps end up doing
Both such tests in one migration became regex passes over the route source files:
That catches drift, but a text search is standing in for a type-level property.
It bites hardest on safety-critical invariants. An "is this screen reachable while locked?" classification is a
switchover route types with a default. Verifying it exhaustively needs an instance of every route — and routes carrying domain objects cannot be cheaply constructed in a test. In that app only 28 of 41 relevant routes could be instantiated; the rest had to be covered by grepping the classifications own source.What already works
Exhaustiveness is fully solved for anything that dispatches on a route — page builders, codecs, transition wrappers all get compile-time totality from
switch. The gap is specifically invariants over the set of routes.Suggestion
At minimum, document the trade-off and the source-level testing pattern so teams do not discover it when writing their first whole-table test.
Optionally, endorse a convention the compiler can help with:
Hand-maintained, but usable by whole-table tests, and drift is at least visible in review. A lint (
pkg:kaisel_lint) that flags a route variant missing from such a list would make it reliable.