Skip to content

Commit 78ef5b2

Browse files
committed
Fix unsaved expense totals and image link names
1 parent 866461e commit 78ef5b2

8 files changed

Lines changed: 169 additions & 2 deletions

File tree

‎app/Filament/Resources/Blog/Posts/Tables/PostsTable.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public static function configure(Table $table): Table
3131
->columns([
3232
SpatieMediaLibraryImageColumn::make('image')
3333
->collection('post-images')
34-
->conversion('thumb'),
34+
->conversion('thumb')
35+
->alt(fn (Post $record): string => "View {$record->title}"),
3536

3637
TextColumn::make('title')
3738
->searchable()

‎app/Filament/Resources/HR/Expenses/Pages/CreateExpense.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ protected function getSteps(): array
7777
Repeater::make('expenseLines')
7878
->relationship()
7979
->defaultItems(1)
80+
->afterStateUpdated(function (?array $state, Set $set): void {
81+
$total = collect($state ?? [])
82+
->sum(fn (array $line): float => (int) ($line['quantity'] ?? 1) * (float) ($line['unit_price'] ?? 0));
83+
84+
$set('total_amount', number_format($total, 2, '.', ''));
85+
})
8086
->table([
8187
TableColumn::make('Description'),
8288
TableColumn::make('Quantity')

‎app/Filament/Resources/HR/Expenses/Schemas/ExpenseForm.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ public static function configure(Schema $schema): Schema
7171
->hiddenLabel()
7272
->relationship()
7373
->defaultItems(1)
74+
->afterStateUpdated(function (?array $state, Set $set): void {
75+
$total = collect($state ?? [])
76+
->sum(fn (array $line): float => (int) ($line['quantity'] ?? 1) * (float) ($line['unit_price'] ?? 0));
77+
78+
$set('total_amount', number_format($total, 2, '.', ''));
79+
})
7480
->table([
7581
TableColumn::make('Description'),
7682
TableColumn::make('Quantity')

‎app/Filament/Resources/Shop/Products/Tables/ProductsTable.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public static function configure(Table $table): Table
3838
->columns([
3939
SpatieMediaLibraryImageColumn::make('image')
4040
->collection('product-images')
41-
->conversion('thumb'),
41+
->conversion('thumb')
42+
->alt(fn (Product $record): string => "Edit {$record->name}"),
4243

4344
TextColumn::make('name')
4445
->searchable()

‎tests/Filament/Resources/Blog/Posts/Pages/ListPostsTest.php‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<?php
22

33
use App\Filament\Resources\Blog\Posts\Pages\ListPosts;
4+
use App\Filament\Resources\Blog\Posts\PostResource;
45
use App\Models\Blog\Post;
56
use Filament\Actions\DeleteAction;
67
use Filament\Actions\DeleteBulkAction;
78
use Filament\Actions\Testing\TestAction;
9+
use Illuminate\Support\Facades\Storage;
810
use Livewire\Livewire;
911

1012
it('can render the list page', function () {
@@ -15,6 +17,36 @@
1517
->assertCanSeeTableRecords($records);
1618
});
1719

20+
it('names post image links without disabling empty image cells', function () {
21+
Storage::fake('public');
22+
23+
$records = Post::factory()->createMany([
24+
['title' => 'Accessibility "with" & image', 'published_at' => null],
25+
['title' => 'Accessibility without an image'],
26+
]);
27+
$records[1]->clearMediaCollection('post-images');
28+
29+
$page = Livewire::test(ListPosts::class)
30+
->searchTable('Accessibility')
31+
->assertCanSeeTableRecords($records);
32+
33+
$document = new DOMDocument;
34+
@$document->loadHTML($page->html());
35+
$xpath = new DOMXPath($document);
36+
$url = PostResource::getUrl('view', ['record' => $records[0]]);
37+
$images = $xpath->query('//a[@href="' . $url . '"]//img');
38+
39+
expect($images->length)->toBeGreaterThan(0);
40+
expect($images->item(0)->getAttribute('alt'))->toBe('View Accessibility "with" & image');
41+
42+
$emptyImageLinks = $xpath->query('//a[@href="' . PostResource::getUrl('view', ['record' => $records[1]]) . '"][.//div[contains(@class, "fi-ta-image")] and not(.//img)]');
43+
expect($emptyImageLinks->length)->toBe(1);
44+
45+
$statusLinks = $xpath->query('//a[@href="' . $url . '"][normalize-space(.)="Draft"]');
46+
expect($statusLinks->length)->toBe(1);
47+
expect($statusLinks->item(0)->hasAttribute('aria-label'))->toBeFalse();
48+
});
49+
1850
it('can toggle publish on a draft post', function () {
1951
$record = Post::factory()->create(['published_at' => null]);
2052

‎tests/Filament/Resources/HR/Expenses/Pages/CreateExpenseTest.php‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Filament\Resources\HR\Expenses\Pages\CreateExpense;
66
use App\Models\HR\Employee;
77
use App\Models\HR\Expense;
8+
use Filament\Actions\Testing\TestAction;
89
use Filament\Forms\Components\Repeater;
910
use Illuminate\Support\Str;
1011
use Livewire\Livewire;
@@ -73,6 +74,8 @@
7374
])
7475
->goToWizardStep(2)
7576
->goToWizardStep(3)
77+
->assertSchemaStateSet(['total_amount' => '200.00'])
78+
->fillForm(['total_amount' => 999.99])
7679
->call('create')
7780
->assertHasNoFormErrors()
7881
->assertNotified();
@@ -83,6 +86,51 @@
8386
$undoRepeaterFake();
8487
});
8588

89+
it('updates the review total after editing adding and removing unsaved lines', function () {
90+
$undoRepeaterFake = Repeater::fake();
91+
92+
Livewire::test(CreateExpense::class)
93+
->fillForm([
94+
'employee_id' => Employee::factory()->create()->id,
95+
'category' => ExpenseCategory::Travel,
96+
'description' => 'Unsaved expenses',
97+
])
98+
->goToWizardStep(2)
99+
->fillForm([
100+
'expenseLines.0.description' => 'Travel',
101+
'expenseLines.0.quantity' => 5,
102+
'expenseLines.0.unit_price' => 352.97,
103+
])
104+
->assertSchemaStateSet(['total_amount' => '1764.85'])
105+
->fillForm(['expenseLines.0.quantity' => 2])
106+
->assertSchemaStateSet(['total_amount' => '705.94'])
107+
->fillForm(['expenseLines.0.unit_price' => 12.50])
108+
->assertSchemaStateSet(['total_amount' => '25.00'])
109+
->callAction(TestAction::make('add')->schemaComponent('expenseLines'))
110+
->set('data.expenseLines.1.description', 'Meals')
111+
->set('data.expenseLines.1.quantity', 3)
112+
->set('data.expenseLines.1.unit_price', 20.25)
113+
->assertSet('data.expenseLines.0.description', 'Travel')
114+
->assertSet('data.expenseLines.1.description', 'Meals')
115+
->assertSchemaStateSet(['total_amount' => '85.75'])
116+
->callAction(TestAction::make('delete')->schemaComponent('expenseLines')->arguments(['item' => 0]))
117+
->assertSchemaStateSet(['total_amount' => '60.75'])
118+
->callAction(TestAction::make('delete')->schemaComponent('expenseLines')->arguments(['item' => 1]))
119+
->assertSchemaStateSet(['total_amount' => '0.00'])
120+
->callAction(TestAction::make('add')->schemaComponent('expenseLines'))
121+
->fillForm([
122+
'expenseLines.0.description' => 'Taxi',
123+
'expenseLines.0.quantity' => 2,
124+
'expenseLines.0.unit_price' => 4.75,
125+
])
126+
->goToWizardStep(3)
127+
->assertSchemaStateSet(['total_amount' => '9.50']);
128+
129+
expect(Expense::count())->toBe(0);
130+
131+
$undoRepeaterFake();
132+
});
133+
86134
it('sets status to Draft on create', function () {
87135
$undoRepeaterFake = Repeater::fake();
88136

‎tests/Filament/Resources/HR/Expenses/Pages/EditExpenseTest.php‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Models\HR\Employee;
66
use App\Models\HR\Expense;
77
use App\Models\HR\ExpenseLine;
8+
use Filament\Actions\Testing\TestAction;
89
use Filament\Forms\Components\Repeater;
910
use Illuminate\Support\Str;
1011
use Livewire\Livewire;
@@ -45,6 +46,7 @@
4546
Livewire::test(EditExpense::class, ['record' => $record->getRouteKey()])
4647
->fillForm([
4748
'description' => 'Updated description',
49+
'total_amount' => 999.99,
4850
])
4951
->call('save')
5052
->assertHasNoFormErrors()
@@ -54,6 +56,42 @@
5456
expect((float) $record->total_amount)->toBe(100.00);
5557
});
5658

59+
it('updates the unsaved total when lines change', function () {
60+
$undoRepeaterFake = Repeater::fake();
61+
62+
$record = Expense::factory()->for(Employee::factory(), 'employee')->create([
63+
'total_amount' => 1802.35,
64+
]);
65+
$lines = $record->expenseLines()->createMany([
66+
['description' => 'Travel', 'quantity' => 5, 'unit_price' => 352.97, 'amount' => 1764.85, 'date' => now()],
67+
['description' => 'Meals', 'quantity' => 3, 'unit_price' => 12.50, 'amount' => 37.50, 'date' => now()],
68+
]);
69+
$firstLineKey = 'record-' . $lines[0]->getKey();
70+
$secondLineKey = 'record-' . $lines[1]->getKey();
71+
72+
Livewire::test(EditExpense::class, ['record' => $record->getRouteKey()])
73+
->fillForm(["expenseLines.{$firstLineKey}.quantity" => 2])
74+
->assertSchemaStateSet(['total_amount' => '743.44'])
75+
->fillForm(["expenseLines.{$secondLineKey}.unit_price" => 20.25])
76+
->assertSchemaStateSet(['total_amount' => '766.69'])
77+
->callAction(TestAction::make('delete')->schemaComponent('expenseLines')->arguments(['item' => $firstLineKey]))
78+
->assertSchemaStateSet(['total_amount' => '60.75'])
79+
->callAction(TestAction::make('delete')->schemaComponent('expenseLines')->arguments(['item' => $secondLineKey]))
80+
->assertSchemaStateSet(['total_amount' => '0.00'])
81+
->callAction(TestAction::make('add')->schemaComponent('expenseLines'))
82+
->fillForm([
83+
'expenseLines.0.description' => 'Taxi',
84+
'expenseLines.0.quantity' => 2,
85+
'expenseLines.0.unit_price' => 4.75,
86+
])
87+
->assertSchemaStateSet(['total_amount' => '9.50']);
88+
89+
expect((float) $record->fresh()->total_amount)->toBe(1802.35);
90+
expect($lines[0]->fresh()->quantity)->toBe(5);
91+
92+
$undoRepeaterFake();
93+
});
94+
5795
it('validates the form data', function (array $data, array $errors) {
5896
$record = Expense::factory()->for(Employee::factory(), 'employee')->has(ExpenseLine::factory()->count(1), 'expenseLines')->create();
5997

‎tests/Filament/Resources/Shop/Products/Pages/ListProductsTest.php‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<?php
22

33
use App\Filament\Resources\Shop\Products\Pages\ListProducts;
4+
use App\Filament\Resources\Shop\Products\ProductResource;
45
use App\Models\Shop\Product;
56
use Filament\Actions\DeleteAction;
67
use Filament\Actions\DeleteBulkAction;
78
use Filament\Actions\Testing\TestAction;
9+
use Illuminate\Support\Facades\Storage;
810
use Livewire\Livewire;
911

1012
it('can render the list page', function () {
@@ -15,6 +17,39 @@
1517
->assertCanSeeTableRecords($records);
1618
});
1719

20+
it('names product image links in search results', function () {
21+
Storage::fake('public');
22+
23+
$records = Product::factory()->createMany([
24+
['name' => 'Artisan "lamp" & shade', 'price' => 12.34],
25+
['name' => 'Artisan vase'],
26+
['name' => 'Artisan without an image'],
27+
]);
28+
$records[2]->clearMediaCollection('product-images');
29+
30+
$page = Livewire::test(ListProducts::class)
31+
->searchTable('Artisan')
32+
->assertCanSeeTableRecords($records);
33+
34+
$document = new DOMDocument;
35+
@$document->loadHTML($page->html());
36+
$xpath = new DOMXPath($document);
37+
38+
foreach ($records->take(2) as $record) {
39+
$url = ProductResource::getUrl('edit', ['record' => $record]);
40+
$images = $xpath->query('//a[@href="' . $url . '"]//img');
41+
expect($images->length)->toBeGreaterThan(0);
42+
expect($images->item(0)->getAttribute('alt'))->toBe("Edit {$record->name}");
43+
}
44+
45+
$emptyImageLinks = $xpath->query('//a[@href="' . ProductResource::getUrl('edit', ['record' => $records[2]]) . '"][.//div[contains(@class, "fi-ta-image")] and not(.//img)]');
46+
expect($emptyImageLinks->length)->toBe(1);
47+
48+
$priceLinks = $xpath->query('//a[@href="' . ProductResource::getUrl('edit', ['record' => $records[0]]) . '"][normalize-space(.)="$12.34"]');
49+
expect($priceLinks->length)->toBe(1);
50+
expect($priceLinks->item(0)->hasAttribute('aria-label'))->toBeFalse();
51+
});
52+
1853
it('can toggle product visibility', function () {
1954
$record = Product::factory()->create(['is_visible' => true]);
2055

0 commit comments

Comments
 (0)