diff --git a/draft b/draft new file mode 100644 index 0000000..e69de29 diff --git a/kell_creations_apps/packages/feature_wordpress/lib/src/data/fake_product_publishing_repository.dart b/kell_creations_apps/packages/feature_wordpress/lib/src/data/fake_product_publishing_repository.dart index 62fe4e1..99ef25f 100644 --- a/kell_creations_apps/packages/feature_wordpress/lib/src/data/fake_product_publishing_repository.dart +++ b/kell_creations_apps/packages/feature_wordpress/lib/src/data/fake_product_publishing_repository.dart @@ -103,14 +103,7 @@ class FakeProductPublishingRepository implements ProductPublishingRepository { } final original = _drafts[index]; - final updated = ProductDraft( - id: original.id, - name: original.name, - description: original.description, - price: original.price, - sku: original.sku, - category: original.category, - imageUrl: original.imageUrl, + final updated = original.copyWith( status: PublishStatus.published, lastModified: DateTime.now(), ); @@ -128,14 +121,7 @@ class FakeProductPublishingRepository implements ProductPublishingRepository { } final original = _drafts[index]; - final updated = ProductDraft( - id: original.id, - name: original.name, - description: original.description, - price: original.price, - sku: original.sku, - category: original.category, - imageUrl: original.imageUrl, + final updated = original.copyWith( status: status, lastModified: DateTime.now(), ); diff --git a/kell_creations_apps/packages/feature_wordpress/lib/src/domain/product_draft.dart b/kell_creations_apps/packages/feature_wordpress/lib/src/domain/product_draft.dart index 15917c3..fdc7816 100644 --- a/kell_creations_apps/packages/feature_wordpress/lib/src/domain/product_draft.dart +++ b/kell_creations_apps/packages/feature_wordpress/lib/src/domain/product_draft.dart @@ -23,4 +23,29 @@ class ProductDraft { required this.status, required this.lastModified, }); + + /// Returns a copy of this [ProductDraft] with the given fields replaced. + ProductDraft copyWith({ + String? id, + String? name, + String? description, + double? price, + String? sku, + String? category, + String? imageUrl, + PublishStatus? status, + DateTime? lastModified, + }) { + return ProductDraft( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + price: price ?? this.price, + sku: sku ?? this.sku, + category: category ?? this.category, + imageUrl: imageUrl ?? this.imageUrl, + status: status ?? this.status, + lastModified: lastModified ?? this.lastModified, + ); + } } diff --git a/kell_creations_apps/packages/feature_wordpress/lib/src/presentation/widgets/product_preview_panel.dart b/kell_creations_apps/packages/feature_wordpress/lib/src/presentation/widgets/product_preview_panel.dart index 278293d..3fb8efb 100644 --- a/kell_creations_apps/packages/feature_wordpress/lib/src/presentation/widgets/product_preview_panel.dart +++ b/kell_creations_apps/packages/feature_wordpress/lib/src/presentation/widgets/product_preview_panel.dart @@ -10,8 +10,8 @@ import 'publish_status_chip.dart'; /// Includes product image placeholder, description, metadata, and /// status-aware action buttons: /// - **Publish to Store** when the draft status is [PublishStatus.draft]. -/// - **Move to Draft** when the draft status is [PublishStatus.published]. -/// - No action button for other statuses. +/// - **Move to Draft** when the draft status is [PublishStatus.published], +/// [PublishStatus.unpublished], or [PublishStatus.pendingReview]. class ProductPreviewPanel extends StatelessWidget { final ProductDraft draft; final VoidCallback? onPublish; @@ -118,7 +118,9 @@ class ProductPreviewPanel extends StatelessWidget { label: const Text('Publish to Store'), ), ), - if (draft.status == PublishStatus.published) + if (draft.status == PublishStatus.published || + draft.status == PublishStatus.unpublished || + draft.status == PublishStatus.pendingReview) SizedBox( width: double.infinity, child: OutlinedButton.icon( diff --git a/kell_creations_apps/packages/feature_wordpress/test/fake_product_publishing_repository_test.dart b/kell_creations_apps/packages/feature_wordpress/test/fake_product_publishing_repository_test.dart index 40e878d..5fbbe4f 100644 --- a/kell_creations_apps/packages/feature_wordpress/test/fake_product_publishing_repository_test.dart +++ b/kell_creations_apps/packages/feature_wordpress/test/fake_product_publishing_repository_test.dart @@ -75,6 +75,20 @@ void main() { expect(product1.status, PublishStatus.draft); }); + test('unpublished to draft updates only the target product', () async { + // Product 6 starts as unpublished. + final updated = await repository.updateProductStatus('6', PublishStatus.draft); + + expect(updated.id, '6'); + expect(updated.status, PublishStatus.draft); + expect(updated.name, 'Sublimated Slate Coaster'); + + // Verify the change is persisted in the list. + final drafts = await repository.getProductDrafts(); + final product6 = drafts.firstWhere((d) => d.id == '6'); + expect(product6.status, PublishStatus.draft); + }); + test('preserves other products unchanged', () async { final draftsBefore = await repository.getProductDrafts(); diff --git a/kell_creations_apps/packages/feature_wordpress/test/product_publishing_controller_test.dart b/kell_creations_apps/packages/feature_wordpress/test/product_publishing_controller_test.dart index af08c1e..d57fda1 100644 --- a/kell_creations_apps/packages/feature_wordpress/test/product_publishing_controller_test.dart +++ b/kell_creations_apps/packages/feature_wordpress/test/product_publishing_controller_test.dart @@ -157,6 +157,30 @@ void main() { expect(controller.updatingIds, isEmpty); }); + test('pendingReview to draft updates status and reloads', () async { + await controller.load(); + + // Product 3 starts as pendingReview. + await controller.updateStatus('3', PublishStatus.draft); + + final updated = controller.drafts.firstWhere((d) => d.id == '3'); + expect(updated.status, PublishStatus.draft); + expect(controller.error, isNull); + expect(controller.updatingIds, isEmpty); + }); + + test('unpublished to draft updates status and reloads', () async { + await controller.load(); + + // Product 6 starts as unpublished. + await controller.updateStatus('6', PublishStatus.draft); + + final updated = controller.drafts.firstWhere((d) => d.id == '6'); + expect(updated.status, PublishStatus.draft); + expect(controller.error, isNull); + expect(controller.updatingIds, isEmpty); + }); + test('sets error on failed update', () async { await controller.load(); diff --git a/kell_creations_apps/packages/feature_wordpress/test/widgets/product_preview_panel_test.dart b/kell_creations_apps/packages/feature_wordpress/test/widgets/product_preview_panel_test.dart index 03a4c24..def1dec 100644 --- a/kell_creations_apps/packages/feature_wordpress/test/widgets/product_preview_panel_test.dart +++ b/kell_creations_apps/packages/feature_wordpress/test/widgets/product_preview_panel_test.dart @@ -42,6 +42,18 @@ void main() { lastModified: DateTime(2026, 4, 1), ); + final unpublishedDraft = ProductDraft( + id: '4', + name: 'Unpublished Product', + description: 'Previously published but taken down.', + price: 9.99, + sku: 'UP-001', + category: 'Coasters', + imageUrl: '', + status: PublishStatus.unpublished, + lastModified: DateTime(2026, 3, 20), + ); + Widget buildTestWidget( ProductDraft draft, { VoidCallback? onPublish, @@ -110,10 +122,21 @@ void main() { expect(find.text('Publish to Store'), findsNothing); }); - testWidgets('pending review row shows no action button', (tester) async { + testWidgets('pending review row shows Move to Draft button', (tester) async { await tester.pumpWidget(buildTestWidget(pendingDraft)); + expect(find.text('Move to Draft'), findsOneWidget); expect(find.text('Publish to Store'), findsNothing); - expect(find.text('Move to Draft'), findsNothing); + }); + + testWidgets('calls onMoveToDraft when Move to Draft is tapped for pending review', ( + tester, + ) async { + var movedToDraft = false; + await tester.pumpWidget( + buildTestWidget(pendingDraft, onMoveToDraft: () => movedToDraft = true), + ); + await tester.tap(find.text('Move to Draft')); + expect(movedToDraft, true); }); testWidgets('calls onMoveToDraft when Move to Draft is tapped', (tester) async { @@ -124,6 +147,21 @@ void main() { await tester.tap(find.text('Move to Draft')); expect(movedToDraft, true); }); + + testWidgets('unpublished row shows Move to Draft button', (tester) async { + await tester.pumpWidget(buildTestWidget(unpublishedDraft)); + expect(find.text('Move to Draft'), findsOneWidget); + expect(find.text('Publish to Store'), findsNothing); + }); + + testWidgets('calls onMoveToDraft when Move to Draft is tapped for unpublished', (tester) async { + var movedToDraft = false; + await tester.pumpWidget( + buildTestWidget(unpublishedDraft, onMoveToDraft: () => movedToDraft = true), + ); + await tester.tap(find.text('Move to Draft')); + expect(movedToDraft, true); + }); }); group('updating state', () { @@ -151,6 +189,30 @@ void main() { expect(tapped, false); }); + testWidgets('Move to Draft button is disabled while updating for pending review', ( + tester, + ) async { + var tapped = false; + await tester.pumpWidget( + buildTestWidget(pendingDraft, isUpdating: true, onMoveToDraft: () => tapped = true), + ); + + expect(find.text('Move to Draft'), findsOneWidget); + await tester.tap(find.text('Move to Draft')); + expect(tapped, false); + }); + + testWidgets('Move to Draft button is disabled while updating for unpublished', (tester) async { + var tapped = false; + await tester.pumpWidget( + buildTestWidget(unpublishedDraft, isUpdating: true, onMoveToDraft: () => tapped = true), + ); + + expect(find.text('Move to Draft'), findsOneWidget); + await tester.tap(find.text('Move to Draft')); + expect(tapped, false); + }); + testWidgets('shows progress indicator while updating', (tester) async { await tester.pumpWidget(buildTestWidget(sampleDraft, isUpdating: true)); expect(find.byType(CircularProgressIndicator), findsOneWidget);