add: adding routing styles to routing package 43 pass 6 fail
This commit is contained in:
parent
52c5fe86fc
commit
a8226d7b87
5 changed files with 882 additions and 0 deletions
117
packages/routing/lib/src/routing_style.dart
Normal file
117
packages/routing/lib/src/routing_style.dart
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import 'router.dart';
|
||||||
|
|
||||||
|
/// Base interface for all routing styles.
|
||||||
|
///
|
||||||
|
/// This allows different routing patterns to be implemented while preserving
|
||||||
|
/// the core routing functionality. Each style wraps around the base [Router]
|
||||||
|
/// implementation, providing its own API while utilizing the underlying routing
|
||||||
|
/// system.
|
||||||
|
abstract class RoutingStyle<T> {
|
||||||
|
/// The underlying router instance that handles actual routing.
|
||||||
|
Router<T> get router;
|
||||||
|
|
||||||
|
/// Unique identifier for this routing style.
|
||||||
|
String get styleName;
|
||||||
|
|
||||||
|
/// Initialize the routing style.
|
||||||
|
///
|
||||||
|
/// This is called when the style is activated through the registry.
|
||||||
|
/// Use this to set up any style-specific configuration or state.
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
/// Clean up any resources used by this style.
|
||||||
|
///
|
||||||
|
/// This is called when switching to a different style or shutting down.
|
||||||
|
void dispose() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registry for managing different routing styles.
|
||||||
|
///
|
||||||
|
/// The registry maintains a collection of available routing styles and handles
|
||||||
|
/// switching between them. It ensures only one style is active at a time while
|
||||||
|
/// preserving the underlying routing configuration.
|
||||||
|
class RoutingStyleRegistry<T> {
|
||||||
|
final Map<String, RoutingStyle<T>> _styles = {};
|
||||||
|
RoutingStyle<T>? _activeStyle;
|
||||||
|
final Router<T> _baseRouter;
|
||||||
|
|
||||||
|
/// Creates a new routing style registry.
|
||||||
|
///
|
||||||
|
/// The registry maintains its own base router instance that all styles
|
||||||
|
/// will wrap around, ensuring routing state is preserved when switching styles.
|
||||||
|
RoutingStyleRegistry() : _baseRouter = Router<T>();
|
||||||
|
|
||||||
|
/// Register a new routing style.
|
||||||
|
///
|
||||||
|
/// Each style must have a unique [styleName]. Attempting to register a style
|
||||||
|
/// with a name that's already registered will throw an exception.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// registry.registerStyle(ExpressStyle(registry.baseRouter));
|
||||||
|
/// registry.registerStyle(LaravelStyle(registry.baseRouter));
|
||||||
|
/// ```
|
||||||
|
void registerStyle(RoutingStyle<T> style) {
|
||||||
|
if (_styles.containsKey(style.styleName)) {
|
||||||
|
throw StateError(
|
||||||
|
'A style with name "${style.styleName}" is already registered');
|
||||||
|
}
|
||||||
|
_styles[style.styleName] = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Activate a registered routing style.
|
||||||
|
///
|
||||||
|
/// This makes the specified style active, initializing it and making its
|
||||||
|
/// routing pattern available. Any previously active style will be disposed.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// registry.useStyle('express'); // Use Express-style routing
|
||||||
|
/// registry.useStyle('laravel'); // Switch to Laravel-style routing
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Throws a [StateError] if the style name is not registered.
|
||||||
|
void useStyle(String styleName) {
|
||||||
|
if (!_styles.containsKey(styleName)) {
|
||||||
|
throw StateError('No routing style registered with name "$styleName"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispose previous style if exists
|
||||||
|
_activeStyle?.dispose();
|
||||||
|
|
||||||
|
// Activate new style
|
||||||
|
_activeStyle = _styles[styleName];
|
||||||
|
_activeStyle!.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The currently active routing style.
|
||||||
|
///
|
||||||
|
/// Returns null if no style is active.
|
||||||
|
RoutingStyle<T>? get activeStyle => _activeStyle;
|
||||||
|
|
||||||
|
/// The underlying router instance.
|
||||||
|
///
|
||||||
|
/// This router is shared across all styles, maintaining the routing state
|
||||||
|
/// even when switching between different routing patterns.
|
||||||
|
Router<T> get baseRouter => _baseRouter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base interface for middleware style adapters.
|
||||||
|
///
|
||||||
|
/// This allows different routing styles to adapt their middleware patterns
|
||||||
|
/// to work with the platform's middleware system.
|
||||||
|
abstract class MiddlewareStyle<T> {
|
||||||
|
/// Convert framework-specific middleware to platform middleware.
|
||||||
|
///
|
||||||
|
/// This allows each routing style to define how its middleware format
|
||||||
|
/// should be converted to work with the platform's middleware system.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// // Example Laravel-style middleware adaptation
|
||||||
|
/// T adaptMiddleware(dynamic middleware) {
|
||||||
|
/// if (middleware is String) {
|
||||||
|
/// return resolveMiddlewareFromContainer(middleware);
|
||||||
|
/// }
|
||||||
|
/// return middleware as T;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
T adaptMiddleware(dynamic originalMiddleware);
|
||||||
|
}
|
157
packages/routing/lib/src/styles/express_style.dart
Normal file
157
packages/routing/lib/src/styles/express_style.dart
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import '../router.dart';
|
||||||
|
import '../routing_style.dart';
|
||||||
|
|
||||||
|
/// Express-style routing implementation.
|
||||||
|
///
|
||||||
|
/// This is the default routing style that maintains compatibility with the
|
||||||
|
/// existing Express-like routing pattern. It provides the familiar app.get(),
|
||||||
|
/// app.post(), etc. methods while utilizing the underlying routing system.
|
||||||
|
class ExpressStyle<T> implements RoutingStyle<T> {
|
||||||
|
final Router<T> _router;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Router<T> get router => _router;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get styleName => 'express';
|
||||||
|
|
||||||
|
/// Creates a new Express-style router.
|
||||||
|
ExpressStyle(this._router);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initialize() {
|
||||||
|
// Express style is the default, so no special initialization needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Express style doesn't need special cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles GET requests.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.get('/users', (req, res) {
|
||||||
|
/// // Handle GET request
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> get(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.get(path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles POST requests.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.post('/users', (req, res) {
|
||||||
|
/// // Handle POST request
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> post(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.post(path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles PUT requests.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.put('/users/:id', (req, res) {
|
||||||
|
/// // Handle PUT request
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> put(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.put(path, handler, middleware: middleware) as Route<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles DELETE requests.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.delete('/users/:id', (req, res) {
|
||||||
|
/// // Handle DELETE request
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> delete(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.delete(path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles PATCH requests.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.patch('/users/:id', (req, res) {
|
||||||
|
/// // Handle PATCH request
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> patch(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.patch(path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles HEAD requests.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.head('/status', (req, res) {
|
||||||
|
/// // Handle HEAD request
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> head(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.head(path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles OPTIONS requests.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.options('/api', (req, res) {
|
||||||
|
/// // Handle OPTIONS request
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> options(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.options(path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route that handles all HTTP methods.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.all('/any', (req, res) {
|
||||||
|
/// // Handle any HTTP method
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Route<T> all(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return _router.all(path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use middleware for all routes.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.use((req, res, next) {
|
||||||
|
/// // Middleware logic
|
||||||
|
/// next();
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
void use(T middleware) {
|
||||||
|
_router.chain([middleware]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a route group with optional prefix and middleware.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// app.group('/api', (router) {
|
||||||
|
/// router.get('/users', handler);
|
||||||
|
/// router.post('/users', createHandler);
|
||||||
|
/// }, middleware: [authMiddleware]);
|
||||||
|
/// ```
|
||||||
|
void group(String prefix, void Function(ExpressStyle<T> router) callback,
|
||||||
|
{List<T> middleware = const []}) {
|
||||||
|
_router.group(prefix, (router) {
|
||||||
|
callback(ExpressStyle<T>(router));
|
||||||
|
}, middleware: middleware);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Express middleware adapter.
|
||||||
|
///
|
||||||
|
/// This adapter maintains compatibility with Express-style middleware,
|
||||||
|
/// which is already in the format expected by the platform.
|
||||||
|
class ExpressMiddlewareStyle<T> implements MiddlewareStyle<T> {
|
||||||
|
@override
|
||||||
|
T adaptMiddleware(dynamic originalMiddleware) {
|
||||||
|
// Express middleware is already in the correct format
|
||||||
|
return originalMiddleware as T;
|
||||||
|
}
|
||||||
|
}
|
165
packages/routing/lib/src/styles/laravel_style.dart
Normal file
165
packages/routing/lib/src/styles/laravel_style.dart
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import '../router.dart';
|
||||||
|
import '../routing_style.dart';
|
||||||
|
|
||||||
|
/// Laravel-style routing implementation.
|
||||||
|
///
|
||||||
|
/// This style provides a Laravel-like routing pattern while utilizing the
|
||||||
|
/// underlying routing system. It demonstrates how different routing styles
|
||||||
|
/// can be implemented on top of the core routing functionality.
|
||||||
|
class LaravelStyle<T> implements RoutingStyle<T> {
|
||||||
|
final Router<T> _router;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Router<T> get router => _router;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get styleName => 'laravel';
|
||||||
|
|
||||||
|
/// Creates a new Laravel-style router.
|
||||||
|
LaravelStyle(this._router);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initialize() {
|
||||||
|
// Laravel style doesn't need special initialization
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Laravel style doesn't need special cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a route with a specific HTTP method.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::get('/users', handler);
|
||||||
|
/// Route::post('/users', handler);
|
||||||
|
/// ```
|
||||||
|
Route<T> route(String method, String path, T handler,
|
||||||
|
{List<T> middleware = const []}) {
|
||||||
|
return _router.addRoute(method.toUpperCase(), path, handler,
|
||||||
|
middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a GET route.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::get('/users', handler);
|
||||||
|
/// ```
|
||||||
|
Route<T> get(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return route('GET', path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a POST route.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::post('/users', handler);
|
||||||
|
/// ```
|
||||||
|
Route<T> post(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return route('POST', path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a PUT route.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::put('/users/{id}', handler);
|
||||||
|
/// ```
|
||||||
|
Route<T> put(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return route('PUT', path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a DELETE route.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::delete('/users/{id}', handler);
|
||||||
|
/// ```
|
||||||
|
Route<T> delete(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return route('DELETE', path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a PATCH route.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::patch('/users/{id}', handler);
|
||||||
|
/// ```
|
||||||
|
Route<T> patch(String path, T handler, {List<T> middleware = const []}) {
|
||||||
|
return route('PATCH', path, handler, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a route group with shared attributes.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::group({
|
||||||
|
/// 'prefix': '/api',
|
||||||
|
/// 'middleware': ['auth'],
|
||||||
|
/// }, () {
|
||||||
|
/// Route::get('/users', handler);
|
||||||
|
/// Route::post('/users', handler);
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
void group(Map<String, dynamic> attributes, void Function() callback) {
|
||||||
|
var prefix = attributes['prefix'] as String? ?? '';
|
||||||
|
var middleware = attributes['middleware'] as List<T>? ?? const [];
|
||||||
|
|
||||||
|
_router.group(prefix, (groupRouter) {
|
||||||
|
// Store current router
|
||||||
|
var parentRouter = _router;
|
||||||
|
// Create new style instance for group
|
||||||
|
var groupStyle = LaravelStyle<T>(groupRouter);
|
||||||
|
// Set current instance as the active one
|
||||||
|
_activeInstance = groupStyle;
|
||||||
|
// Execute callback
|
||||||
|
callback();
|
||||||
|
// Restore parent instance
|
||||||
|
_activeInstance = this;
|
||||||
|
}, middleware: middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track active instance for group context
|
||||||
|
static LaravelStyle? _activeInstance;
|
||||||
|
|
||||||
|
// Forward calls to active instance
|
||||||
|
LaravelStyle<T> get _current => _activeInstance as LaravelStyle<T>? ?? this;
|
||||||
|
|
||||||
|
/// Add a name to the last registered route.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::get('/users', handler).name('users.index');
|
||||||
|
/// ```
|
||||||
|
Route<T> name(String name) {
|
||||||
|
var lastRoute = _router.routes.last;
|
||||||
|
lastRoute.name = name;
|
||||||
|
return lastRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register middleware for all routes.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Route::middleware(['auth', 'throttle']);
|
||||||
|
/// ```
|
||||||
|
void middleware(List<T> middleware) {
|
||||||
|
_router.chain(middleware);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Laravel middleware adapter.
|
||||||
|
///
|
||||||
|
/// This adapter converts Laravel-style middleware (strings or callables)
|
||||||
|
/// to the platform's middleware format.
|
||||||
|
class LaravelMiddlewareStyle<T> implements MiddlewareStyle<T> {
|
||||||
|
final Map<String, T Function()> _middlewareMap;
|
||||||
|
|
||||||
|
LaravelMiddlewareStyle(this._middlewareMap);
|
||||||
|
|
||||||
|
@override
|
||||||
|
T adaptMiddleware(dynamic originalMiddleware) {
|
||||||
|
if (originalMiddleware is String) {
|
||||||
|
var factory = _middlewareMap[originalMiddleware];
|
||||||
|
if (factory == null) {
|
||||||
|
throw StateError(
|
||||||
|
'No middleware registered for key "$originalMiddleware"');
|
||||||
|
}
|
||||||
|
return factory();
|
||||||
|
}
|
||||||
|
return originalMiddleware as T;
|
||||||
|
}
|
||||||
|
}
|
237
packages/routing/test/routing_style_test.dart
Normal file
237
packages/routing/test/routing_style_test.dart
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
import 'package:platform_routing/src/router.dart';
|
||||||
|
import 'package:platform_routing/src/routing_style.dart';
|
||||||
|
import 'package:platform_routing/src/styles/express_style.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// Test style implementation
|
||||||
|
class _TestStyle implements RoutingStyle<Function> {
|
||||||
|
final Router<Function> _router;
|
||||||
|
final void Function() _onDispose;
|
||||||
|
|
||||||
|
_TestStyle(this._router, this._onDispose);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Router<Function> get router => _router;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get styleName => 'test';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initialize() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_onDispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('RoutingStyleRegistry', () {
|
||||||
|
late RoutingStyleRegistry<Function> registry;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
registry = RoutingStyleRegistry<Function>();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registers and activates styles', () {
|
||||||
|
var expressStyle = ExpressStyle<Function>(registry.baseRouter);
|
||||||
|
|
||||||
|
// Register style
|
||||||
|
registry.registerStyle(expressStyle);
|
||||||
|
expect(registry.activeStyle, isNull);
|
||||||
|
|
||||||
|
// Activate style
|
||||||
|
registry.useStyle('express');
|
||||||
|
expect(registry.activeStyle, equals(expressStyle));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws on duplicate style registration', () {
|
||||||
|
var style1 = ExpressStyle<Function>(registry.baseRouter);
|
||||||
|
var style2 = ExpressStyle<Function>(registry.baseRouter);
|
||||||
|
|
||||||
|
registry.registerStyle(style1);
|
||||||
|
expect(
|
||||||
|
() => registry.registerStyle(style2),
|
||||||
|
throwsStateError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws when activating unregistered style', () {
|
||||||
|
expect(
|
||||||
|
() => registry.useStyle('nonexistent'),
|
||||||
|
throwsStateError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disposes previous style when switching', () {
|
||||||
|
var disposed = false;
|
||||||
|
|
||||||
|
var testStyle = _TestStyle(registry.baseRouter, () => disposed = true);
|
||||||
|
var expressStyle = ExpressStyle<Function>(registry.baseRouter);
|
||||||
|
|
||||||
|
registry.registerStyle(testStyle);
|
||||||
|
registry.registerStyle(expressStyle);
|
||||||
|
|
||||||
|
// Activate test style
|
||||||
|
registry.useStyle('test');
|
||||||
|
expect(disposed, isFalse);
|
||||||
|
|
||||||
|
// Switch to express style
|
||||||
|
registry.useStyle('express');
|
||||||
|
expect(disposed, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ExpressStyle', () {
|
||||||
|
late RoutingStyleRegistry<Function> registry;
|
||||||
|
late ExpressStyle<Function> style;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
registry = RoutingStyleRegistry<Function>();
|
||||||
|
style = ExpressStyle<Function>(registry.baseRouter);
|
||||||
|
registry.registerStyle(style);
|
||||||
|
registry.useStyle('express');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('maintains express-style routing pattern', () {
|
||||||
|
var handlerCalled = false;
|
||||||
|
var middlewareCalled = false;
|
||||||
|
|
||||||
|
// Register middleware
|
||||||
|
style.use((req, res, next) {
|
||||||
|
middlewareCalled = true;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register route
|
||||||
|
style.get('/test', (req, res) {
|
||||||
|
handlerCalled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate request
|
||||||
|
var results = registry.baseRouter.resolveAbsolute('/test', method: 'GET');
|
||||||
|
expect(results, isNotEmpty);
|
||||||
|
|
||||||
|
// Execute handlers
|
||||||
|
for (var result in results) {
|
||||||
|
for (var handler in result.handlers) {
|
||||||
|
handler(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(middlewareCalled, isTrue);
|
||||||
|
expect(handlerCalled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports route groups', () {
|
||||||
|
var routes = <String>[];
|
||||||
|
|
||||||
|
style.group('/api', (router) {
|
||||||
|
router.get('/users', (req, res) {
|
||||||
|
routes.add('/api/users');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/users', (req, res) {
|
||||||
|
routes.add('/api/users');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify routes were registered
|
||||||
|
var getResults =
|
||||||
|
registry.baseRouter.resolveAbsolute('/api/users', method: 'GET');
|
||||||
|
var postResults =
|
||||||
|
registry.baseRouter.resolveAbsolute('/api/users', method: 'POST');
|
||||||
|
|
||||||
|
expect(getResults, isNotEmpty);
|
||||||
|
expect(postResults, isNotEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports all HTTP methods', () {
|
||||||
|
var methods = [
|
||||||
|
'GET',
|
||||||
|
'POST',
|
||||||
|
'PUT',
|
||||||
|
'DELETE',
|
||||||
|
'PATCH',
|
||||||
|
'HEAD',
|
||||||
|
'OPTIONS'
|
||||||
|
];
|
||||||
|
var calledMethods = <String>[];
|
||||||
|
|
||||||
|
// Register routes for each method
|
||||||
|
for (var method in methods) {
|
||||||
|
switch (method) {
|
||||||
|
case 'GET':
|
||||||
|
style.get('/test', (req, res) => calledMethods.add(method));
|
||||||
|
break;
|
||||||
|
case 'POST':
|
||||||
|
style.post('/test', (req, res) => calledMethods.add(method));
|
||||||
|
break;
|
||||||
|
case 'PUT':
|
||||||
|
style.put('/test', (req, res) => calledMethods.add(method));
|
||||||
|
break;
|
||||||
|
case 'DELETE':
|
||||||
|
style.delete('/test', (req, res) => calledMethods.add(method));
|
||||||
|
break;
|
||||||
|
case 'PATCH':
|
||||||
|
style.patch('/test', (req, res) => calledMethods.add(method));
|
||||||
|
break;
|
||||||
|
case 'HEAD':
|
||||||
|
style.head('/test', (req, res) => calledMethods.add(method));
|
||||||
|
break;
|
||||||
|
case 'OPTIONS':
|
||||||
|
style.options('/test', (req, res) => calledMethods.add(method));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify each method resolves
|
||||||
|
for (var method in methods) {
|
||||||
|
var results =
|
||||||
|
registry.baseRouter.resolveAbsolute('/test', method: method);
|
||||||
|
expect(results, isNotEmpty, reason: 'Method $method should resolve');
|
||||||
|
|
||||||
|
// Execute handler
|
||||||
|
for (var result in results) {
|
||||||
|
for (var handler in result.handlers) {
|
||||||
|
handler(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify each method was called
|
||||||
|
expect(calledMethods, containsAll(methods));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports middleware in route groups', () {
|
||||||
|
var middlewareCalled = false;
|
||||||
|
var handlerCalled = false;
|
||||||
|
|
||||||
|
style.group('/api', (router) {
|
||||||
|
router.get('/test', (req, res) {
|
||||||
|
handlerCalled = true;
|
||||||
|
});
|
||||||
|
}, middleware: [
|
||||||
|
(req, res, next) {
|
||||||
|
middlewareCalled = true;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Simulate request
|
||||||
|
var results =
|
||||||
|
registry.baseRouter.resolveAbsolute('/api/test', method: 'GET');
|
||||||
|
expect(results, isNotEmpty);
|
||||||
|
|
||||||
|
// Execute handlers
|
||||||
|
for (var result in results) {
|
||||||
|
for (var handler in result.handlers) {
|
||||||
|
handler(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(middlewareCalled, isTrue);
|
||||||
|
expect(handlerCalled, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
206
packages/routing/test/styles/laravel_style_test.dart
Normal file
206
packages/routing/test/styles/laravel_style_test.dart
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
import 'package:platform_routing/src/routing_style.dart';
|
||||||
|
import 'package:platform_routing/src/styles/laravel_style.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
typedef NextFunction = void Function();
|
||||||
|
typedef MiddlewareFunction = void Function(dynamic, dynamic, NextFunction);
|
||||||
|
typedef RouteHandler = void Function(dynamic, dynamic);
|
||||||
|
|
||||||
|
void executeHandler(Function? handler) {
|
||||||
|
if (handler == null) return;
|
||||||
|
|
||||||
|
if (handler is RouteHandler) {
|
||||||
|
handler(null, null);
|
||||||
|
} else if (handler is MiddlewareFunction) {
|
||||||
|
handler(null, null, () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('LaravelStyle', () {
|
||||||
|
late RoutingStyleRegistry<Function> registry;
|
||||||
|
late LaravelStyle<Function> style;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
registry = RoutingStyleRegistry<Function>();
|
||||||
|
style = LaravelStyle<Function>(registry.baseRouter);
|
||||||
|
registry.registerStyle(style);
|
||||||
|
registry.useStyle('laravel');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports Laravel-style route registration', () {
|
||||||
|
var handlerCalled = false;
|
||||||
|
var middlewareCalled = false;
|
||||||
|
|
||||||
|
// Register middleware
|
||||||
|
middleware(req, res, NextFunction next) {
|
||||||
|
middlewareCalled = true;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
style.middleware([middleware]);
|
||||||
|
|
||||||
|
// Register route
|
||||||
|
handler(req, res) {
|
||||||
|
handlerCalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
style.get('/test', handler).name!;
|
||||||
|
|
||||||
|
// Simulate request
|
||||||
|
var results = registry.baseRouter.resolveAbsolute('/test', method: 'GET');
|
||||||
|
expect(results, isNotEmpty);
|
||||||
|
|
||||||
|
// Execute handlers
|
||||||
|
for (var result in results) {
|
||||||
|
for (var handler in result.handlers) {
|
||||||
|
executeHandler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(middlewareCalled, isTrue);
|
||||||
|
expect(handlerCalled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports Laravel-style route groups', () {
|
||||||
|
var routes = <String>[];
|
||||||
|
var groupMiddlewareCalled = false;
|
||||||
|
|
||||||
|
// Register middleware
|
||||||
|
middleware(req, res, NextFunction next) {
|
||||||
|
groupMiddlewareCalled = true;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
style.group({
|
||||||
|
'prefix': '/api',
|
||||||
|
'middleware': [middleware],
|
||||||
|
}, () {
|
||||||
|
getHandler(req, res) {
|
||||||
|
routes.add('/api/users');
|
||||||
|
}
|
||||||
|
|
||||||
|
postHandler(req, res) {
|
||||||
|
routes.add('/api/users');
|
||||||
|
}
|
||||||
|
|
||||||
|
style.get('/users', getHandler).name!;
|
||||||
|
style.post('/users', postHandler).name!;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify routes were registered
|
||||||
|
var getResults =
|
||||||
|
registry.baseRouter.resolveAbsolute('/api/users', method: 'GET');
|
||||||
|
var postResults =
|
||||||
|
registry.baseRouter.resolveAbsolute('/api/users', method: 'POST');
|
||||||
|
|
||||||
|
expect(getResults, isNotEmpty);
|
||||||
|
expect(postResults, isNotEmpty);
|
||||||
|
|
||||||
|
// Verify route names
|
||||||
|
var namedRoute = registry.baseRouter.routes
|
||||||
|
.firstWhere((r) => r.name == 'api.users.index');
|
||||||
|
expect(namedRoute, isNotNull);
|
||||||
|
|
||||||
|
// Execute handlers to verify middleware
|
||||||
|
for (var result in getResults) {
|
||||||
|
for (var handler in result.handlers) {
|
||||||
|
executeHandler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(groupMiddlewareCalled, isTrue);
|
||||||
|
expect(routes, contains('/api/users'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports all HTTP methods', () {
|
||||||
|
var methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
|
||||||
|
var calledMethods = <String>[];
|
||||||
|
|
||||||
|
// Register routes for each method
|
||||||
|
for (var method in methods) {
|
||||||
|
handler(req, res) {
|
||||||
|
calledMethods.add(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
style.route(method, '/test', handler).name!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify each method resolves
|
||||||
|
for (var method in methods) {
|
||||||
|
var results =
|
||||||
|
registry.baseRouter.resolveAbsolute('/test', method: method);
|
||||||
|
expect(results, isNotEmpty, reason: 'Method $method should resolve');
|
||||||
|
|
||||||
|
// Execute handler
|
||||||
|
for (var result in results) {
|
||||||
|
for (var handler in result.handlers) {
|
||||||
|
executeHandler(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify each method was called
|
||||||
|
expect(calledMethods, containsAll(methods));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports named routes', () {
|
||||||
|
handler(req, res) {}
|
||||||
|
|
||||||
|
style.get('/users', handler).name!;
|
||||||
|
style.post('/users', handler).name!;
|
||||||
|
style.get('/users/:id', handler).name!;
|
||||||
|
|
||||||
|
var routes = registry.baseRouter.routes;
|
||||||
|
expect(routes.any((r) => r.name == 'users.index'), isTrue);
|
||||||
|
expect(routes.any((r) => r.name == 'users.store'), isTrue);
|
||||||
|
expect(routes.any((r) => r.name == 'users.show'), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports middleware string resolution', () {
|
||||||
|
var authCalled = false;
|
||||||
|
var throttleCalled = false;
|
||||||
|
|
||||||
|
MiddlewareFunction createAuthMiddleware() {
|
||||||
|
return (req, res, NextFunction next) {
|
||||||
|
authCalled = true;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MiddlewareFunction createThrottleMiddleware() {
|
||||||
|
return (req, res, NextFunction next) {
|
||||||
|
throttleCalled = true;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var middlewareAdapter = LaravelMiddlewareStyle<Function>({
|
||||||
|
'auth': createAuthMiddleware,
|
||||||
|
'throttle': createThrottleMiddleware,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test string to middleware conversion
|
||||||
|
var authMiddleware =
|
||||||
|
middlewareAdapter.adaptMiddleware('auth') as MiddlewareFunction;
|
||||||
|
var throttleMiddleware =
|
||||||
|
middlewareAdapter.adaptMiddleware('throttle') as MiddlewareFunction;
|
||||||
|
|
||||||
|
// Execute middleware
|
||||||
|
doNext() {}
|
||||||
|
authMiddleware(null, null, doNext);
|
||||||
|
throttleMiddleware(null, null, doNext);
|
||||||
|
|
||||||
|
expect(authCalled, isTrue);
|
||||||
|
expect(throttleCalled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws on unknown middleware string', () {
|
||||||
|
var adapter = LaravelMiddlewareStyle<Function>({});
|
||||||
|
expect(
|
||||||
|
() => adapter.adaptMiddleware('unknown'),
|
||||||
|
throwsStateError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue