fix(feature_wordpress): allow pending review products to move to draft
Publish Docs / publish-docs (push) Successful in 1m12s
Details
Publish Docs / publish-docs (push) Successful in 1m12s
Details
This commit is contained in:
parent
f9c5ef36da
commit
f8f373b018
|
|
@ -103,14 +103,7 @@ class FakeProductPublishingRepository implements ProductPublishingRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
final original = _drafts[index];
|
final original = _drafts[index];
|
||||||
final updated = ProductDraft(
|
final updated = original.copyWith(
|
||||||
id: original.id,
|
|
||||||
name: original.name,
|
|
||||||
description: original.description,
|
|
||||||
price: original.price,
|
|
||||||
sku: original.sku,
|
|
||||||
category: original.category,
|
|
||||||
imageUrl: original.imageUrl,
|
|
||||||
status: PublishStatus.published,
|
status: PublishStatus.published,
|
||||||
lastModified: DateTime.now(),
|
lastModified: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
@ -128,14 +121,7 @@ class FakeProductPublishingRepository implements ProductPublishingRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
final original = _drafts[index];
|
final original = _drafts[index];
|
||||||
final updated = ProductDraft(
|
final updated = original.copyWith(
|
||||||
id: original.id,
|
|
||||||
name: original.name,
|
|
||||||
description: original.description,
|
|
||||||
price: original.price,
|
|
||||||
sku: original.sku,
|
|
||||||
category: original.category,
|
|
||||||
imageUrl: original.imageUrl,
|
|
||||||
status: status,
|
status: status,
|
||||||
lastModified: DateTime.now(),
|
lastModified: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,29 @@ class ProductDraft {
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.lastModified,
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import 'publish_status_chip.dart';
|
||||||
/// Includes product image placeholder, description, metadata, and
|
/// Includes product image placeholder, description, metadata, and
|
||||||
/// status-aware action buttons:
|
/// status-aware action buttons:
|
||||||
/// - **Publish to Store** when the draft status is [PublishStatus.draft].
|
/// - **Publish to Store** when the draft status is [PublishStatus.draft].
|
||||||
/// - **Move to Draft** when the draft status is [PublishStatus.published].
|
/// - **Move to Draft** when the draft status is [PublishStatus.published],
|
||||||
/// - No action button for other statuses.
|
/// [PublishStatus.unpublished], or [PublishStatus.pendingReview].
|
||||||
class ProductPreviewPanel extends StatelessWidget {
|
class ProductPreviewPanel extends StatelessWidget {
|
||||||
final ProductDraft draft;
|
final ProductDraft draft;
|
||||||
final VoidCallback? onPublish;
|
final VoidCallback? onPublish;
|
||||||
|
|
@ -118,7 +118,9 @@ class ProductPreviewPanel extends StatelessWidget {
|
||||||
label: const Text('Publish to Store'),
|
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(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,20 @@ void main() {
|
||||||
expect(product1.status, PublishStatus.draft);
|
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 {
|
test('preserves other products unchanged', () async {
|
||||||
final draftsBefore = await repository.getProductDrafts();
|
final draftsBefore = await repository.getProductDrafts();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,30 @@ void main() {
|
||||||
expect(controller.updatingIds, isEmpty);
|
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 {
|
test('sets error on failed update', () async {
|
||||||
await controller.load();
|
await controller.load();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,18 @@ void main() {
|
||||||
lastModified: DateTime(2026, 4, 1),
|
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(
|
Widget buildTestWidget(
|
||||||
ProductDraft draft, {
|
ProductDraft draft, {
|
||||||
VoidCallback? onPublish,
|
VoidCallback? onPublish,
|
||||||
|
|
@ -110,10 +122,21 @@ void main() {
|
||||||
expect(find.text('Publish to Store'), findsNothing);
|
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));
|
await tester.pumpWidget(buildTestWidget(pendingDraft));
|
||||||
|
expect(find.text('Move to Draft'), findsOneWidget);
|
||||||
expect(find.text('Publish to Store'), findsNothing);
|
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 {
|
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'));
|
await tester.tap(find.text('Move to Draft'));
|
||||||
expect(movedToDraft, true);
|
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', () {
|
group('updating state', () {
|
||||||
|
|
@ -151,6 +189,30 @@ void main() {
|
||||||
expect(tapped, false);
|
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 {
|
testWidgets('shows progress indicator while updating', (tester) async {
|
||||||
await tester.pumpWidget(buildTestWidget(sampleDraft, isUpdating: true));
|
await tester.pumpWidget(buildTestWidget(sampleDraft, isUpdating: true));
|
||||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue