Compare commits
2 Commits
73fdcf99e7
...
9a66a8a2b0
| Author | SHA1 | Date |
|---|---|---|
|
|
9a66a8a2b0 | |
|
|
b2bec44fcb |
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
## Current status
|
||||
|
||||
- main baseline updated through: feat/inventory-page-ui (Stage 5B complete)
|
||||
- next branch: feat/orders-domain (Stage 5C) or feat/bulk-operator-workflows (Stage 7B)
|
||||
- current stage: Stage 5B complete — inventory page UI with search, filter chips, detail panel, quantity adjust dialog, and snack bar feedback
|
||||
- main baseline updated through: feat/orders-domain (Stage 5C complete)
|
||||
- next branch: feat/bulk-operator-workflows (Stage 7B) or feat/orders-page-ui (Stage 5D)
|
||||
- current stage: Stage 5C complete — orders domain expansion with write operations (updateOrderStatus, addOrderNote, createOrder), OrderActionResult, copyWith, and 90 tests
|
||||
|
||||
## Slice tracker
|
||||
|
||||
|
|
@ -285,6 +285,31 @@
|
|||
- analyze: passed (dart analyze --fatal-infos — no issues found)
|
||||
- brief updated: yes
|
||||
|
||||
### feat/orders-domain
|
||||
|
||||
- status: merged to main
|
||||
- date: 2026-07-10
|
||||
- inspection: complete
|
||||
- implementation: complete
|
||||
- files changed:
|
||||
- `feature_orders/lib/src/domain/order.dart` — added `customerPhone`, `notes`, `orderNotes` fields; added `copyWith()`
|
||||
- `feature_orders/lib/src/domain/order_action_result.dart` — new value object with `success`, `updatedOrder`, `errorMessage`; named constructors `success()` and `failure()`
|
||||
- `feature_orders/lib/src/domain/orders_repository.dart` — expanded contract with `updateOrderStatus()`, `addOrderNote()`, `createOrder()`
|
||||
- `feature_orders/lib/src/data/fake_orders_repository.dart` — added `customerPhone` to seed data; implemented all write methods with duplicate-check, empty-note guard, and status auto-update
|
||||
- `feature_orders/lib/src/application/update_order_status.dart` — new use case wrapping `updateOrderStatus()`
|
||||
- `feature_orders/lib/src/application/add_order_note.dart` — new use case wrapping `addOrderNote()`
|
||||
- `feature_orders/lib/src/application/create_order.dart` — new use case wrapping `createOrder()`
|
||||
- `feature_orders/lib/src/application/orders_controller.dart` — added `isUpdating`, `lastActionResult`, `updateOrderStatus()`, `addOrderNote()`, `createOrder()`, `consumeActionResult()`, `_reloadAndRefresh()` with filter/search persistence after writes; write use cases are optional named params for backward compatibility
|
||||
- `feature_orders/lib/feature_orders.dart` — expanded barrel exports for all new types and use cases
|
||||
- `feature_orders/test/feature_orders_test.dart` — expanded domain tests: Order (copyWith, defaults, computed props), OrderItem, OrderStatus, OrderActionResult, barrel smoke test
|
||||
- `feature_orders/test/fake_orders_repository_test.dart` — expanded with write method tests: updateOrderStatus (4), addOrderNote (6), createOrder (4)
|
||||
- `feature_orders/test/orders_controller_test.dart` — expanded with write-side tests: updateOrderStatus (6), addOrderNote (6), createOrder (4), consumeActionResult (3), filter/search persistence (2); read-side tests preserved
|
||||
- `kell_web/test/dashboard/application/dashboard_controller_test.dart` — updated `_StubOrdersRepository` to implement expanded `OrdersRepository` contract
|
||||
- `kell_creations_apps/tools/run_all_tests.sh` — added `feature_orders` and `kell_mobile` to `TESTABLE` list
|
||||
- tests: passed (90/90 feature_orders, 24/24 kell_web — all passing)
|
||||
- analyze: passed (dart analyze --fatal-infos — no issues found in feature_orders and kell_web)
|
||||
- brief updated: yes
|
||||
|
||||
### chore/analyze-cleanup-and-tracker-sync
|
||||
|
||||
- status: merged to main
|
||||
|
|
|
|||
|
|
@ -152,8 +152,8 @@ No minimum thresholds are enforced — this is visibility-only tracking. Coverag
|
|||
|
||||
### Next recommended branch
|
||||
|
||||
**`feat/orders-domain`** — Stage 5C: Orders domain expansion (read + write operations, matching the pattern established by `feat/inventory-domain-expansion`), or **`feat/bulk-operator-workflows`** — Stage 7B: Additional bulk actions (bulk publish, bulk move to pending review).
|
||||
Branch from latest `main`. Stage 5B (inventory page UI) is complete. Stage 5C adds write operations and domain expansion to the orders feature. Stage 7B adds incremental bulk actions to the publishing workflow.
|
||||
**`feat/orders-page-ui`** — Stage 5D: Orders page UI hardening (status update action from detail panel, add note UI, snack bar feedback — matching the pattern established by `feat/inventory-page-ui`), or **`feat/bulk-operator-workflows`** — Stage 7B: Additional bulk actions (bulk publish, bulk move to pending review).
|
||||
Branch from latest `main`. Stage 5C (orders domain expansion) is complete. Stage 5D adds write-action UI to the orders page. Stage 7B adds incremental bulk actions to the publishing workflow.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1251,7 +1251,7 @@ Working rules:
|
|||
| ------------------- | ------------ | ----------------- | ------------ | -------------- | -------------------------------------------------- | ------- | -------------------- |
|
||||
| `feature_wordpress` | ✅ Complete | ✅ Complete | ✅ Complete | ✅ WooCommerce | ✅ Complete | 294 | **Production-ready** |
|
||||
| `feature_inventory` | ⚠️ Read-only | ⚠️ Read-only | ⚠️ Read-only | ❌ None | ⚠️ Read-only | Minimal | **Fake-only MVP** |
|
||||
| `feature_orders` | ⚠️ Read-only | ⚠️ Read-only | ⚠️ Read-only | ❌ None | ⚠️ Read-only | Some | **Fake-only MVP** |
|
||||
| `feature_orders` | ✅ Write ops | ✅ Write ops | ✅ Write ops | ❌ None | ⚠️ Read-only UI | 90 | **Fake write-ready** |
|
||||
| `feature_policy` | ✅ Complete | ✅ Complete | ✅ Complete | ❌ None | ✅ Complete | Minimal | **Fake-only MVP** |
|
||||
| `feature_finance` | ❌ Stub | ❌ Stub | ❌ Stub | ❌ None | ❌ Stub | None | **Scaffolded only** |
|
||||
| `feature_mrp` | ❌ Stub | ❌ Stub | ❌ Stub | ❌ None | ❌ Stub | None | **Scaffolded only** |
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ class _StubOrdersRepository implements OrdersRepository {
|
|||
|
||||
@override
|
||||
Future<List<Order>> getOrders() async => orders;
|
||||
|
||||
@override
|
||||
Future<OrderActionResult> updateOrderStatus(String id, OrderStatus newStatus) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<OrderActionResult> addOrderNote(String id, String note) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<OrderActionResult> createOrder(Order order) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class _FailingInventoryRepository implements InventoryRepository {
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +1,12 @@
|
|||
library;
|
||||
|
||||
export 'src/application/add_order_note.dart';
|
||||
export 'src/application/create_order.dart';
|
||||
export 'src/application/get_orders.dart';
|
||||
export 'src/application/update_order_status.dart';
|
||||
export 'src/data/fake_orders_repository.dart';
|
||||
export 'src/domain/order.dart';
|
||||
export 'src/domain/order_action_result.dart';
|
||||
export 'src/domain/order_item.dart';
|
||||
export 'src/domain/order_status.dart';
|
||||
export 'src/domain/orders_repository.dart';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import '../domain/order_action_result.dart';
|
||||
import '../domain/orders_repository.dart';
|
||||
|
||||
/// Use case: append a note to an existing order.
|
||||
class AddOrderNote {
|
||||
final OrdersRepository repository;
|
||||
|
||||
AddOrderNote(this.repository);
|
||||
|
||||
Future<OrderActionResult> call(String id, String note) => repository.addOrderNote(id, note);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import '../domain/order.dart';
|
||||
import '../domain/order_action_result.dart';
|
||||
import '../domain/orders_repository.dart';
|
||||
|
||||
/// Use case: create a new order and persist it.
|
||||
class CreateOrder {
|
||||
final OrdersRepository repository;
|
||||
|
||||
CreateOrder(this.repository);
|
||||
|
||||
Future<OrderActionResult> call(Order order) => repository.createOrder(order);
|
||||
}
|
||||
|
|
@ -1,19 +1,42 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../domain/order.dart';
|
||||
import '../domain/order_action_result.dart';
|
||||
import '../domain/order_status.dart';
|
||||
import 'add_order_note.dart';
|
||||
import 'create_order.dart';
|
||||
import 'get_orders.dart';
|
||||
import 'update_order_status.dart';
|
||||
|
||||
/// Controller that manages the orders workspace state, including
|
||||
/// filtering by order status, free-text search, and order selection.
|
||||
/// filtering by order status, free-text search, order selection,
|
||||
/// and write operations (status update, note addition, order creation).
|
||||
class OrdersController extends ChangeNotifier {
|
||||
final GetOrders _getOrders;
|
||||
final UpdateOrderStatus? _updateOrderStatus;
|
||||
final AddOrderNote? _addOrderNote;
|
||||
final CreateOrder? _createOrder;
|
||||
|
||||
OrdersController(this._getOrders);
|
||||
OrdersController(
|
||||
this._getOrders, {
|
||||
UpdateOrderStatus? updateOrderStatus,
|
||||
AddOrderNote? addOrderNote,
|
||||
CreateOrder? createOrder,
|
||||
}) : _updateOrderStatus = updateOrderStatus,
|
||||
_addOrderNote = addOrderNote,
|
||||
_createOrder = createOrder;
|
||||
|
||||
bool isLoading = false;
|
||||
|
||||
/// Whether a write operation is in progress for the selected order.
|
||||
bool isUpdating = false;
|
||||
|
||||
Object? error;
|
||||
|
||||
/// The result of the last write action, or `null` if none has been performed
|
||||
/// or the result has been consumed.
|
||||
OrderActionResult? lastActionResult;
|
||||
|
||||
/// All orders returned by the repository (unfiltered).
|
||||
List<Order> _allOrders = [];
|
||||
|
||||
|
|
@ -32,6 +55,8 @@ class OrdersController extends ChangeNotifier {
|
|||
/// The current free-text search query applied to customer name / order ID.
|
||||
String searchQuery = '';
|
||||
|
||||
// ── Read operations ────────────────────────────────────────────────────
|
||||
|
||||
/// Loads all orders and applies any current filter / search.
|
||||
Future<void> load() async {
|
||||
isLoading = true;
|
||||
|
|
@ -82,8 +107,100 @@ class OrdersController extends ChangeNotifier {
|
|||
return false;
|
||||
}
|
||||
|
||||
// ── Write operations ───────────────────────────────────────────────────
|
||||
|
||||
/// Updates the status of the order identified by [id].
|
||||
///
|
||||
/// Sets [isUpdating] during the operation and stores the result in
|
||||
/// [lastActionResult]. Reloads the order list on success and refreshes
|
||||
/// [selectedOrder] if it was the updated order.
|
||||
Future<void> updateOrderStatus(String id, OrderStatus newStatus) async {
|
||||
if (_updateOrderStatus == null) return;
|
||||
isUpdating = true;
|
||||
lastActionResult = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final result = await _updateOrderStatus(id, newStatus);
|
||||
lastActionResult = result;
|
||||
if (result.success) {
|
||||
await _reloadAndRefresh(id);
|
||||
}
|
||||
} finally {
|
||||
isUpdating = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends a note to the order identified by [id].
|
||||
///
|
||||
/// Sets [isUpdating] during the operation and stores the result in
|
||||
/// [lastActionResult]. Refreshes [selectedOrder] on success.
|
||||
Future<void> addOrderNote(String id, String note) async {
|
||||
if (_addOrderNote == null) return;
|
||||
isUpdating = true;
|
||||
lastActionResult = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final result = await _addOrderNote(id, note);
|
||||
lastActionResult = result;
|
||||
if (result.success) {
|
||||
await _reloadAndRefresh(id);
|
||||
}
|
||||
} finally {
|
||||
isUpdating = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new order and adds it to the list.
|
||||
///
|
||||
/// Sets [isUpdating] during the operation and stores the result in
|
||||
/// [lastActionResult]. Reloads the order list on success.
|
||||
Future<void> createOrder(Order order) async {
|
||||
if (_createOrder == null) return;
|
||||
isUpdating = true;
|
||||
lastActionResult = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final result = await _createOrder(order);
|
||||
lastActionResult = result;
|
||||
if (result.success) {
|
||||
await _reloadAndRefresh(null);
|
||||
}
|
||||
} finally {
|
||||
isUpdating = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes and returns [lastActionResult], clearing it to `null`.
|
||||
OrderActionResult? consumeActionResult() {
|
||||
final result = lastActionResult;
|
||||
lastActionResult = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Private helpers ────────────────────────────────────────────────────
|
||||
|
||||
/// Reloads all orders from the repository, re-applies filters, and
|
||||
/// refreshes [selectedOrder] by [selectedId] if provided.
|
||||
Future<void> _reloadAndRefresh(String? selectedId) async {
|
||||
_allOrders = await _getOrders();
|
||||
_applyFilters();
|
||||
|
||||
// Refresh the selected order from the updated list.
|
||||
final refreshId = selectedId ?? selectedOrder?.id;
|
||||
if (refreshId != null) {
|
||||
final refreshed = _allOrders.where((o) => o.id == refreshId).firstOrNull;
|
||||
if (refreshed != null) {
|
||||
selectedOrder = refreshed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFilters() {
|
||||
var result = _allOrders;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import '../domain/order_action_result.dart';
|
||||
import '../domain/order_status.dart';
|
||||
import '../domain/orders_repository.dart';
|
||||
|
||||
/// Use case: update the status of an order.
|
||||
class UpdateOrderStatus {
|
||||
final OrdersRepository repository;
|
||||
|
||||
UpdateOrderStatus(this.repository);
|
||||
|
||||
Future<OrderActionResult> call(String id, OrderStatus newStatus) =>
|
||||
repository.updateOrderStatus(id, newStatus);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import '../domain/order.dart';
|
||||
import '../domain/order_action_result.dart';
|
||||
import '../domain/order_item.dart';
|
||||
import '../domain/order_status.dart';
|
||||
import '../domain/orders_repository.dart';
|
||||
|
|
@ -11,6 +12,7 @@ class FakeOrdersRepository implements OrdersRepository {
|
|||
id: 'KC-1001',
|
||||
customerName: 'Sarah Mitchell',
|
||||
customerEmail: 'sarah.mitchell@example.com',
|
||||
customerPhone: '828-555-0101',
|
||||
orderDate: DateTime(2026, 4, 1),
|
||||
status: OrderStatus.delivered,
|
||||
shippingAddress: '123 Maple St, Asheville, NC 28801',
|
||||
|
|
@ -55,6 +57,7 @@ class FakeOrdersRepository implements OrdersRepository {
|
|||
id: 'KC-1003',
|
||||
customerName: 'Emily Chen',
|
||||
customerEmail: 'emily.chen@example.com',
|
||||
customerPhone: '704-555-0303',
|
||||
orderDate: DateTime(2026, 4, 3),
|
||||
status: OrderStatus.processing,
|
||||
shippingAddress: '789 Pine Rd, Charlotte, NC 28202',
|
||||
|
|
@ -109,6 +112,7 @@ class FakeOrdersRepository implements OrdersRepository {
|
|||
id: 'KC-1006',
|
||||
customerName: 'Maria Gonzalez',
|
||||
customerEmail: 'maria.gonzalez@example.com',
|
||||
customerPhone: '404-555-0606',
|
||||
orderDate: DateTime(2026, 4, 4),
|
||||
status: OrderStatus.pending,
|
||||
shippingAddress: '987 Cedar Dr, Atlanta, GA 30301',
|
||||
|
|
@ -141,4 +145,42 @@ class FakeOrdersRepository implements OrdersRepository {
|
|||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||
return List.unmodifiable(_orders);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<OrderActionResult> updateOrderStatus(String id, OrderStatus newStatus) async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
final index = _orders.indexWhere((o) => o.id == id);
|
||||
if (index == -1) {
|
||||
return const OrderActionResult.failure('Order not found.');
|
||||
}
|
||||
final updated = _orders[index].copyWith(status: newStatus);
|
||||
_orders[index] = updated;
|
||||
return OrderActionResult.success(updated);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<OrderActionResult> addOrderNote(String id, String note) async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
if (note.trim().isEmpty) {
|
||||
return const OrderActionResult.failure('Note cannot be empty.');
|
||||
}
|
||||
final index = _orders.indexWhere((o) => o.id == id);
|
||||
if (index == -1) {
|
||||
return const OrderActionResult.failure('Order not found.');
|
||||
}
|
||||
final existing = _orders[index];
|
||||
final updated = existing.copyWith(orderNotes: [...existing.orderNotes, note.trim()]);
|
||||
_orders[index] = updated;
|
||||
return OrderActionResult.success(updated);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<OrderActionResult> createOrder(Order order) async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
if (_orders.any((o) => o.id == order.id)) {
|
||||
return OrderActionResult.failure('Order with ID ${order.id} already exists.');
|
||||
}
|
||||
_orders.add(order);
|
||||
return OrderActionResult.success(order);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,19 +6,32 @@ class Order {
|
|||
final String id;
|
||||
final String customerName;
|
||||
final String customerEmail;
|
||||
|
||||
/// Optional customer phone number.
|
||||
final String? customerPhone;
|
||||
|
||||
final DateTime orderDate;
|
||||
final OrderStatus status;
|
||||
final List<OrderItem> items;
|
||||
final String shippingAddress;
|
||||
|
||||
/// Optional internal operator notes about this order.
|
||||
final String? notes;
|
||||
|
||||
/// Customer-visible order notes (e.g. gift message, special instructions).
|
||||
final List<String> orderNotes;
|
||||
|
||||
const Order({
|
||||
required this.id,
|
||||
required this.customerName,
|
||||
required this.customerEmail,
|
||||
this.customerPhone,
|
||||
required this.orderDate,
|
||||
required this.status,
|
||||
required this.items,
|
||||
required this.shippingAddress,
|
||||
this.notes,
|
||||
this.orderNotes = const [],
|
||||
});
|
||||
|
||||
/// The total value of the order.
|
||||
|
|
@ -26,4 +39,31 @@ class Order {
|
|||
|
||||
/// The number of individual items in the order.
|
||||
int get itemCount => items.fold(0, (sum, item) => sum + item.quantity);
|
||||
|
||||
/// Returns a copy of this order with the given fields replaced.
|
||||
Order copyWith({
|
||||
String? id,
|
||||
String? customerName,
|
||||
String? customerEmail,
|
||||
String? customerPhone,
|
||||
DateTime? orderDate,
|
||||
OrderStatus? status,
|
||||
List<OrderItem>? items,
|
||||
String? shippingAddress,
|
||||
String? notes,
|
||||
List<String>? orderNotes,
|
||||
}) {
|
||||
return Order(
|
||||
id: id ?? this.id,
|
||||
customerName: customerName ?? this.customerName,
|
||||
customerEmail: customerEmail ?? this.customerEmail,
|
||||
customerPhone: customerPhone ?? this.customerPhone,
|
||||
orderDate: orderDate ?? this.orderDate,
|
||||
status: status ?? this.status,
|
||||
items: items ?? this.items,
|
||||
shippingAddress: shippingAddress ?? this.shippingAddress,
|
||||
notes: notes ?? this.notes,
|
||||
orderNotes: orderNotes ?? this.orderNotes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import 'order.dart';
|
||||
|
||||
/// The result of an order write operation (status update, note addition, etc.).
|
||||
class OrderActionResult {
|
||||
/// Whether the operation succeeded.
|
||||
final bool success;
|
||||
|
||||
/// The updated [Order] after the operation, or `null` on failure.
|
||||
final Order? updatedOrder;
|
||||
|
||||
/// A human-readable error message when [success] is `false`.
|
||||
final String? errorMessage;
|
||||
|
||||
const OrderActionResult.success(Order order)
|
||||
: success = true,
|
||||
updatedOrder = order,
|
||||
errorMessage = null;
|
||||
|
||||
const OrderActionResult.failure(String message)
|
||||
: success = false,
|
||||
updatedOrder = null,
|
||||
errorMessage = message;
|
||||
}
|
||||
|
|
@ -1,7 +1,24 @@
|
|||
import 'order.dart';
|
||||
import 'order_action_result.dart';
|
||||
import 'order_status.dart';
|
||||
|
||||
/// Contract for fetching and managing customer orders.
|
||||
abstract class OrdersRepository {
|
||||
/// Returns all orders.
|
||||
Future<List<Order>> getOrders();
|
||||
|
||||
/// Updates the status of the order identified by [id].
|
||||
///
|
||||
/// Returns an [OrderActionResult] indicating success or failure.
|
||||
Future<OrderActionResult> updateOrderStatus(String id, OrderStatus newStatus);
|
||||
|
||||
/// Appends a note to the order identified by [id].
|
||||
///
|
||||
/// Returns an [OrderActionResult] with the updated order on success.
|
||||
Future<OrderActionResult> addOrderNote(String id, String note);
|
||||
|
||||
/// Creates a new order and persists it.
|
||||
///
|
||||
/// Returns an [OrderActionResult] with the created order on success.
|
||||
Future<OrderActionResult> createOrder(Order order);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ void main() {
|
|||
repository = FakeOrdersRepository();
|
||||
});
|
||||
|
||||
group('FakeOrdersRepository', () {
|
||||
group('FakeOrdersRepository — read', () {
|
||||
test('getOrders returns six sample orders', () async {
|
||||
final orders = await repository.getOrders();
|
||||
expect(orders.length, 6);
|
||||
|
|
@ -49,5 +49,170 @@ void main() {
|
|||
final order1001 = orders.firstWhere((o) => o.id == 'KC-1001');
|
||||
expect(order1001.itemCount, 3);
|
||||
});
|
||||
|
||||
test('some orders have customerPhone set', () async {
|
||||
final orders = await repository.getOrders();
|
||||
final withPhone = orders.where((o) => o.customerPhone != null).toList();
|
||||
expect(withPhone, isNotEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('FakeOrdersRepository — updateOrderStatus', () {
|
||||
test('updates status of existing order', () async {
|
||||
final result = await repository.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
expect(result.success, true);
|
||||
expect(result.updatedOrder!.id, 'KC-1004');
|
||||
expect(result.updatedOrder!.status, OrderStatus.processing);
|
||||
});
|
||||
|
||||
test('persists status change across subsequent getOrders calls', () async {
|
||||
await repository.updateOrderStatus('KC-1004', OrderStatus.shipped);
|
||||
|
||||
final orders = await repository.getOrders();
|
||||
final updated = orders.firstWhere((o) => o.id == 'KC-1004');
|
||||
expect(updated.status, OrderStatus.shipped);
|
||||
});
|
||||
|
||||
test('returns failure for unknown order ID', () async {
|
||||
final result = await repository.updateOrderStatus('UNKNOWN', OrderStatus.processing);
|
||||
|
||||
expect(result.success, false);
|
||||
expect(result.errorMessage, isNotNull);
|
||||
});
|
||||
|
||||
test('can update to any valid status', () async {
|
||||
for (final status in OrderStatus.values) {
|
||||
final result = await repository.updateOrderStatus('KC-1001', status);
|
||||
expect(result.success, true);
|
||||
expect(result.updatedOrder!.status, status);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('FakeOrdersRepository — addOrderNote', () {
|
||||
test('adds note to existing order', () async {
|
||||
final result = await repository.addOrderNote('KC-1001', 'Please gift wrap.');
|
||||
|
||||
expect(result.success, true);
|
||||
expect(result.updatedOrder!.orderNotes, contains('Please gift wrap.'));
|
||||
});
|
||||
|
||||
test('trims whitespace from note', () async {
|
||||
final result = await repository.addOrderNote('KC-1001', ' Trimmed note. ');
|
||||
|
||||
expect(result.updatedOrder!.orderNotes, contains('Trimmed note.'));
|
||||
});
|
||||
|
||||
test('persists note across subsequent getOrders calls', () async {
|
||||
await repository.addOrderNote('KC-1001', 'Persistent note.');
|
||||
|
||||
final orders = await repository.getOrders();
|
||||
final order = orders.firstWhere((o) => o.id == 'KC-1001');
|
||||
expect(order.orderNotes, contains('Persistent note.'));
|
||||
});
|
||||
|
||||
test('multiple notes accumulate in order', () async {
|
||||
await repository.addOrderNote('KC-1001', 'First.');
|
||||
await repository.addOrderNote('KC-1001', 'Second.');
|
||||
|
||||
final orders = await repository.getOrders();
|
||||
final order = orders.firstWhere((o) => o.id == 'KC-1001');
|
||||
expect(order.orderNotes.length, 2);
|
||||
expect(order.orderNotes[0], 'First.');
|
||||
expect(order.orderNotes[1], 'Second.');
|
||||
});
|
||||
|
||||
test('returns failure for empty note', () async {
|
||||
final result = await repository.addOrderNote('KC-1001', ' ');
|
||||
|
||||
expect(result.success, false);
|
||||
expect(result.errorMessage, isNotNull);
|
||||
});
|
||||
|
||||
test('returns failure for unknown order ID', () async {
|
||||
final result = await repository.addOrderNote('UNKNOWN', 'Some note.');
|
||||
|
||||
expect(result.success, false);
|
||||
expect(result.errorMessage, isNotNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('FakeOrdersRepository — createOrder', () {
|
||||
test('creates a new order and returns it', () async {
|
||||
final newOrder = Order(
|
||||
id: 'KC-9999',
|
||||
customerName: 'New Customer',
|
||||
customerEmail: 'new@example.com',
|
||||
orderDate: DateTime(2026, 7, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [
|
||||
OrderItem(productName: 'Test Product', sku: 'TP-001', quantity: 1, unitPrice: 15.00),
|
||||
],
|
||||
shippingAddress: '1 New St, Test City, TC 00000',
|
||||
);
|
||||
|
||||
final result = await repository.createOrder(newOrder);
|
||||
|
||||
expect(result.success, true);
|
||||
expect(result.updatedOrder!.id, 'KC-9999');
|
||||
});
|
||||
|
||||
test('new order appears in subsequent getOrders calls', () async {
|
||||
final newOrder = Order(
|
||||
id: 'KC-8888',
|
||||
customerName: 'Another Customer',
|
||||
customerEmail: 'another@example.com',
|
||||
orderDate: DateTime(2026, 7, 2),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
|
||||
await repository.createOrder(newOrder);
|
||||
|
||||
final orders = await repository.getOrders();
|
||||
expect(orders.length, 7);
|
||||
expect(orders.any((o) => o.id == 'KC-8888'), true);
|
||||
});
|
||||
|
||||
test('returns failure for duplicate order ID', () async {
|
||||
final duplicate = Order(
|
||||
id: 'KC-1001',
|
||||
customerName: 'Duplicate',
|
||||
customerEmail: 'dup@example.com',
|
||||
orderDate: DateTime(2026, 7, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
|
||||
final result = await repository.createOrder(duplicate);
|
||||
|
||||
expect(result.success, false);
|
||||
expect(result.errorMessage, isNotNull);
|
||||
});
|
||||
|
||||
test('duplicate rejection does not alter existing order', () async {
|
||||
final orders = await repository.getOrders();
|
||||
final original = orders.firstWhere((o) => o.id == 'KC-1001');
|
||||
|
||||
final duplicate = Order(
|
||||
id: 'KC-1001',
|
||||
customerName: 'Imposter',
|
||||
customerEmail: 'imposter@example.com',
|
||||
orderDate: DateTime(2026, 7, 1),
|
||||
status: OrderStatus.cancelled,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
|
||||
await repository.createOrder(duplicate);
|
||||
|
||||
final ordersAfter = await repository.getOrders();
|
||||
final unchanged = ordersAfter.firstWhere((o) => o.id == 'KC-1001');
|
||||
expect(unchanged.customerName, original.customerName);
|
||||
expect(unchanged.status, original.status);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,185 @@
|
|||
// This file ensures the barrel export compiles correctly.
|
||||
// This file covers domain model tests for the feature_orders package.
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:feature_orders/feature_orders.dart';
|
||||
|
||||
void main() {
|
||||
// ── Order domain model ─────────────────────────────────────────────────
|
||||
|
||||
group('Order', () {
|
||||
final baseOrder = Order(
|
||||
id: 'KC-0001',
|
||||
customerName: 'Test Customer',
|
||||
customerEmail: 'test@example.com',
|
||||
customerPhone: '555-0100',
|
||||
orderDate: DateTime(2026, 1, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [
|
||||
OrderItem(productName: 'Widget', sku: 'WG-001', quantity: 2, unitPrice: 10.00),
|
||||
OrderItem(productName: 'Gadget', sku: 'GD-002', quantity: 1, unitPrice: 5.50),
|
||||
],
|
||||
shippingAddress: '1 Test St, Test City, TC 00000',
|
||||
notes: 'Internal note',
|
||||
orderNotes: const ['Customer note 1'],
|
||||
);
|
||||
|
||||
test('total is sum of all line totals', () {
|
||||
// 2 * 10.00 + 1 * 5.50 = 25.50
|
||||
expect(baseOrder.total, closeTo(25.50, 0.001));
|
||||
});
|
||||
|
||||
test('itemCount is sum of all quantities', () {
|
||||
// 2 + 1 = 3
|
||||
expect(baseOrder.itemCount, 3);
|
||||
});
|
||||
|
||||
test('copyWith replaces status', () {
|
||||
final updated = baseOrder.copyWith(status: OrderStatus.processing);
|
||||
expect(updated.status, OrderStatus.processing);
|
||||
expect(updated.id, baseOrder.id);
|
||||
expect(updated.customerName, baseOrder.customerName);
|
||||
});
|
||||
|
||||
test('copyWith replaces orderNotes', () {
|
||||
final updated = baseOrder.copyWith(orderNotes: ['New note']);
|
||||
expect(updated.orderNotes, ['New note']);
|
||||
expect(updated.status, baseOrder.status);
|
||||
});
|
||||
|
||||
test('copyWith replaces notes', () {
|
||||
final updated = baseOrder.copyWith(notes: 'Updated internal note');
|
||||
expect(updated.notes, 'Updated internal note');
|
||||
});
|
||||
|
||||
test('copyWith replaces customerPhone', () {
|
||||
final updated = baseOrder.copyWith(customerPhone: '555-9999');
|
||||
expect(updated.customerPhone, '555-9999');
|
||||
});
|
||||
|
||||
test('copyWith with no arguments returns equivalent order', () {
|
||||
final copy = baseOrder.copyWith();
|
||||
expect(copy.id, baseOrder.id);
|
||||
expect(copy.customerName, baseOrder.customerName);
|
||||
expect(copy.status, baseOrder.status);
|
||||
expect(copy.orderNotes, baseOrder.orderNotes);
|
||||
});
|
||||
|
||||
test('default orderNotes is empty list', () {
|
||||
final order = Order(
|
||||
id: 'KC-0002',
|
||||
customerName: 'Minimal',
|
||||
customerEmail: 'min@example.com',
|
||||
orderDate: DateTime(2026, 1, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
expect(order.orderNotes, isEmpty);
|
||||
});
|
||||
|
||||
test('customerPhone defaults to null', () {
|
||||
final order = Order(
|
||||
id: 'KC-0003',
|
||||
customerName: 'No Phone',
|
||||
customerEmail: 'nophone@example.com',
|
||||
orderDate: DateTime(2026, 1, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
expect(order.customerPhone, isNull);
|
||||
});
|
||||
|
||||
test('notes defaults to null', () {
|
||||
final order = Order(
|
||||
id: 'KC-0004',
|
||||
customerName: 'No Notes',
|
||||
customerEmail: 'nonotes@example.com',
|
||||
orderDate: DateTime(2026, 1, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
expect(order.notes, isNull);
|
||||
});
|
||||
|
||||
test('total is zero for empty items list', () {
|
||||
final order = Order(
|
||||
id: 'KC-0005',
|
||||
customerName: 'Empty',
|
||||
customerEmail: 'empty@example.com',
|
||||
orderDate: DateTime(2026, 1, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
expect(order.total, 0.0);
|
||||
expect(order.itemCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
// ── OrderItem ──────────────────────────────────────────────────────────
|
||||
|
||||
group('OrderItem', () {
|
||||
test('lineTotal is quantity * unitPrice', () {
|
||||
const item = OrderItem(productName: 'Test', sku: 'T-001', quantity: 3, unitPrice: 7.50);
|
||||
expect(item.lineTotal, closeTo(22.50, 0.001));
|
||||
});
|
||||
|
||||
test('lineTotal is zero for zero quantity', () {
|
||||
const item = OrderItem(productName: 'Test', sku: 'T-001', quantity: 0, unitPrice: 10.00);
|
||||
expect(item.lineTotal, 0.0);
|
||||
});
|
||||
});
|
||||
|
||||
// ── OrderStatus ────────────────────────────────────────────────────────
|
||||
|
||||
group('OrderStatus', () {
|
||||
test('all statuses are distinct', () {
|
||||
final statuses = OrderStatus.values.toSet();
|
||||
expect(statuses.length, OrderStatus.values.length);
|
||||
});
|
||||
|
||||
test('contains expected values', () {
|
||||
expect(OrderStatus.values, contains(OrderStatus.pending));
|
||||
expect(OrderStatus.values, contains(OrderStatus.processing));
|
||||
expect(OrderStatus.values, contains(OrderStatus.shipped));
|
||||
expect(OrderStatus.values, contains(OrderStatus.delivered));
|
||||
expect(OrderStatus.values, contains(OrderStatus.cancelled));
|
||||
});
|
||||
});
|
||||
|
||||
// ── OrderActionResult ──────────────────────────────────────────────────
|
||||
|
||||
group('OrderActionResult', () {
|
||||
final sampleOrder = Order(
|
||||
id: 'KC-0001',
|
||||
customerName: 'Test',
|
||||
customerEmail: 'test@example.com',
|
||||
orderDate: DateTime(2026, 1, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
|
||||
test('success result has success=true and updatedOrder set', () {
|
||||
final result = OrderActionResult.success(sampleOrder);
|
||||
expect(result.success, true);
|
||||
expect(result.updatedOrder, sampleOrder);
|
||||
expect(result.errorMessage, isNull);
|
||||
});
|
||||
|
||||
test('failure result has success=false and errorMessage set', () {
|
||||
const result = OrderActionResult.failure('Something went wrong.');
|
||||
expect(result.success, false);
|
||||
expect(result.updatedOrder, isNull);
|
||||
expect(result.errorMessage, 'Something went wrong.');
|
||||
});
|
||||
});
|
||||
|
||||
// ── Barrel export smoke test ───────────────────────────────────────────
|
||||
|
||||
test('barrel export exposes Order', () {
|
||||
// Verify the domain model is accessible through the barrel export.
|
||||
final order = Order(
|
||||
id: 'test',
|
||||
customerName: 'Test',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:feature_orders/feature_orders.dart';
|
||||
import 'package:feature_orders/src/application/get_orders.dart';
|
||||
import 'package:feature_orders/src/application/orders_controller.dart';
|
||||
|
||||
void main() {
|
||||
|
|
@ -10,21 +9,30 @@ void main() {
|
|||
|
||||
setUp(() {
|
||||
repository = FakeOrdersRepository();
|
||||
controller = OrdersController(GetOrders(repository));
|
||||
controller = OrdersController(
|
||||
GetOrders(repository),
|
||||
updateOrderStatus: UpdateOrderStatus(repository),
|
||||
addOrderNote: AddOrderNote(repository),
|
||||
createOrder: CreateOrder(repository),
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
group('OrdersController', () {
|
||||
// ── Read-side tests (preserved from original) ──────────────────────────
|
||||
|
||||
group('OrdersController — read', () {
|
||||
test('starts with empty state', () {
|
||||
expect(controller.isLoading, false);
|
||||
expect(controller.isUpdating, false);
|
||||
expect(controller.orders, isEmpty);
|
||||
expect(controller.selectedOrder, isNull);
|
||||
expect(controller.activeFilter, isNull);
|
||||
expect(controller.searchQuery, '');
|
||||
expect(controller.error, isNull);
|
||||
expect(controller.lastActionResult, isNull);
|
||||
});
|
||||
|
||||
test('load populates orders and auto-selects first', () async {
|
||||
|
|
@ -116,4 +124,261 @@ void main() {
|
|||
expect(controller.selectedOrder, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Write-side tests ───────────────────────────────────────────────────
|
||||
|
||||
group('OrdersController — updateOrderStatus', () {
|
||||
test('updates status and sets success result', () async {
|
||||
await controller.load();
|
||||
controller.selectById('KC-1004');
|
||||
|
||||
await controller.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
expect(controller.isUpdating, false);
|
||||
expect(controller.lastActionResult, isNotNull);
|
||||
expect(controller.lastActionResult!.success, true);
|
||||
expect(controller.lastActionResult!.updatedOrder!.status, OrderStatus.processing);
|
||||
});
|
||||
|
||||
test('refreshes selectedOrder after status update', () async {
|
||||
await controller.load();
|
||||
controller.selectById('KC-1004');
|
||||
|
||||
await controller.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
expect(controller.selectedOrder!.status, OrderStatus.processing);
|
||||
});
|
||||
|
||||
test('reloads order list after status update', () async {
|
||||
await controller.load();
|
||||
|
||||
// Initially 2 pending orders.
|
||||
controller.setFilter('pending');
|
||||
expect(controller.orders.length, 2);
|
||||
controller.setFilter(null);
|
||||
|
||||
await controller.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
// Now only 1 pending order.
|
||||
controller.setFilter('pending');
|
||||
expect(controller.orders.length, 1);
|
||||
});
|
||||
|
||||
test('sets failure result for unknown order ID', () async {
|
||||
await controller.load();
|
||||
|
||||
await controller.updateOrderStatus('UNKNOWN', OrderStatus.processing);
|
||||
|
||||
expect(controller.lastActionResult!.success, false);
|
||||
expect(controller.lastActionResult!.errorMessage, isNotNull);
|
||||
});
|
||||
|
||||
test('isUpdating is false after completion', () async {
|
||||
await controller.load();
|
||||
|
||||
await controller.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
expect(controller.isUpdating, false);
|
||||
});
|
||||
|
||||
test('no-op when updateOrderStatus use case is not provided', () async {
|
||||
final readOnlyController = OrdersController(GetOrders(repository));
|
||||
await readOnlyController.load();
|
||||
|
||||
await readOnlyController.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
// No result set, no error.
|
||||
expect(readOnlyController.lastActionResult, isNull);
|
||||
readOnlyController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('OrdersController — addOrderNote', () {
|
||||
test('adds note and sets success result', () async {
|
||||
await controller.load();
|
||||
controller.selectById('KC-1001');
|
||||
|
||||
await controller.addOrderNote('KC-1001', 'Please gift wrap.');
|
||||
|
||||
expect(controller.lastActionResult!.success, true);
|
||||
expect(controller.lastActionResult!.updatedOrder!.orderNotes, contains('Please gift wrap.'));
|
||||
});
|
||||
|
||||
test('refreshes selectedOrder with new note', () async {
|
||||
await controller.load();
|
||||
controller.selectById('KC-1001');
|
||||
|
||||
await controller.addOrderNote('KC-1001', 'Fragile item.');
|
||||
|
||||
expect(controller.selectedOrder!.orderNotes, contains('Fragile item.'));
|
||||
});
|
||||
|
||||
test('multiple notes accumulate', () async {
|
||||
await controller.load();
|
||||
|
||||
await controller.addOrderNote('KC-1001', 'First note.');
|
||||
await controller.addOrderNote('KC-1001', 'Second note.');
|
||||
|
||||
controller.selectById('KC-1001');
|
||||
expect(controller.selectedOrder!.orderNotes.length, 2);
|
||||
});
|
||||
|
||||
test('sets failure result for empty note', () async {
|
||||
await controller.load();
|
||||
|
||||
await controller.addOrderNote('KC-1001', ' ');
|
||||
|
||||
expect(controller.lastActionResult!.success, false);
|
||||
expect(controller.lastActionResult!.errorMessage, isNotNull);
|
||||
});
|
||||
|
||||
test('sets failure result for unknown order ID', () async {
|
||||
await controller.load();
|
||||
|
||||
await controller.addOrderNote('UNKNOWN', 'Some note.');
|
||||
|
||||
expect(controller.lastActionResult!.success, false);
|
||||
});
|
||||
|
||||
test('no-op when addOrderNote use case is not provided', () async {
|
||||
final readOnlyController = OrdersController(GetOrders(repository));
|
||||
await readOnlyController.load();
|
||||
|
||||
await readOnlyController.addOrderNote('KC-1001', 'Note.');
|
||||
|
||||
expect(readOnlyController.lastActionResult, isNull);
|
||||
readOnlyController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('OrdersController — createOrder', () {
|
||||
test('creates order and adds it to the list', () async {
|
||||
await controller.load();
|
||||
expect(controller.orders.length, 6);
|
||||
|
||||
final newOrder = Order(
|
||||
id: 'KC-9999',
|
||||
customerName: 'New Customer',
|
||||
customerEmail: 'new@example.com',
|
||||
orderDate: DateTime(2026, 7, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [
|
||||
OrderItem(productName: 'Test Product', sku: 'TP-001', quantity: 1, unitPrice: 15.00),
|
||||
],
|
||||
shippingAddress: '1 New St, Test City, TC 00000',
|
||||
);
|
||||
|
||||
await controller.createOrder(newOrder);
|
||||
|
||||
expect(controller.lastActionResult!.success, true);
|
||||
expect(controller.orders.length, 7);
|
||||
});
|
||||
|
||||
test('new order appears in list after creation', () async {
|
||||
await controller.load();
|
||||
|
||||
final newOrder = Order(
|
||||
id: 'KC-8888',
|
||||
customerName: 'Another Customer',
|
||||
customerEmail: 'another@example.com',
|
||||
orderDate: DateTime(2026, 7, 2),
|
||||
status: OrderStatus.pending,
|
||||
items: const [
|
||||
OrderItem(productName: 'Widget', sku: 'WG-001', quantity: 2, unitPrice: 9.99),
|
||||
],
|
||||
shippingAddress: '2 Another St, Test City, TC 00000',
|
||||
);
|
||||
|
||||
await controller.createOrder(newOrder);
|
||||
|
||||
final ids = controller.orders.map((o) => o.id).toList();
|
||||
expect(ids, contains('KC-8888'));
|
||||
});
|
||||
|
||||
test('sets failure result for duplicate order ID', () async {
|
||||
await controller.load();
|
||||
|
||||
final duplicate = Order(
|
||||
id: 'KC-1001',
|
||||
customerName: 'Duplicate',
|
||||
customerEmail: 'dup@example.com',
|
||||
orderDate: DateTime(2026, 7, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
|
||||
await controller.createOrder(duplicate);
|
||||
|
||||
expect(controller.lastActionResult!.success, false);
|
||||
expect(controller.lastActionResult!.errorMessage, isNotNull);
|
||||
});
|
||||
|
||||
test('no-op when createOrder use case is not provided', () async {
|
||||
final readOnlyController = OrdersController(GetOrders(repository));
|
||||
await readOnlyController.load();
|
||||
|
||||
final newOrder = Order(
|
||||
id: 'KC-7777',
|
||||
customerName: 'Test',
|
||||
customerEmail: 'test@example.com',
|
||||
orderDate: DateTime(2026, 7, 1),
|
||||
status: OrderStatus.pending,
|
||||
items: const [],
|
||||
shippingAddress: '',
|
||||
);
|
||||
|
||||
await readOnlyController.createOrder(newOrder);
|
||||
|
||||
expect(readOnlyController.lastActionResult, isNull);
|
||||
expect(readOnlyController.orders.length, 6);
|
||||
readOnlyController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('OrdersController — consumeActionResult', () {
|
||||
test('consume clears lastActionResult', () async {
|
||||
await controller.load();
|
||||
await controller.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
expect(controller.lastActionResult, isNotNull);
|
||||
final consumed = controller.consumeActionResult();
|
||||
expect(consumed, isNotNull);
|
||||
expect(controller.lastActionResult, isNull);
|
||||
});
|
||||
|
||||
test('consume returns null when no result pending', () async {
|
||||
final result = controller.consumeActionResult();
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('lastActionResult starts null', () {
|
||||
expect(controller.lastActionResult, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('OrdersController — filter/search persistence after writes', () {
|
||||
test('active filter is preserved after status update', () async {
|
||||
await controller.load();
|
||||
controller.setFilter('pending');
|
||||
expect(controller.orders.length, 2);
|
||||
|
||||
await controller.updateOrderStatus('KC-1004', OrderStatus.processing);
|
||||
|
||||
// Filter should still be active; now only 1 pending order.
|
||||
expect(controller.activeFilter, 'pending');
|
||||
expect(controller.orders.length, 1);
|
||||
});
|
||||
|
||||
test('search query is preserved after note addition', () async {
|
||||
await controller.load();
|
||||
controller.setSearchQuery('sarah');
|
||||
expect(controller.orders.length, 1);
|
||||
|
||||
await controller.addOrderNote('KC-1001', 'Test note.');
|
||||
|
||||
expect(controller.searchQuery, 'sarah');
|
||||
expect(controller.orders.length, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ TESTABLE=(
|
|||
packages/core
|
||||
packages/design_system
|
||||
packages/feature_wordpress
|
||||
packages/feature_inventory
|
||||
packages/feature_orders
|
||||
apps/kell_web
|
||||
apps/kell_mobile
|
||||
)
|
||||
|
||||
# All packages/apps to analyze (includes those without tests)
|
||||
|
|
|
|||
Loading…
Reference in New Issue