assertStatus(200) ->assertSeeHtml('name="search"'); }); it('has default property values', function () { Livewire::test(FuelFinder::class) ->assertSet('search', '') ->assertSet('fuelType', 'petrol') ->assertSet('radius', 5) ->assertSet('sort', 'reliable') ->assertSet('results', []) ->assertSet('meta', []) ->assertSet('prediction', null) ->assertSet('apiError', null) ->assertSet('hasSearched', false); }); it('validates search is required', function () { Livewire::test(FuelFinder::class) ->call('findStations') ->assertHasErrors(['search' => 'required']); }); it('validates fuelType is required', function () { Livewire::test(FuelFinder::class) ->set('search', 'SW1A 1AA') ->set('fuelType', '') ->call('findStations') ->assertHasErrors(['fuelType' => 'required']); }); it('populates results, meta, and prediction on successful search', function () { Http::fake([ '*/api/stations*' => Http::response([ 'data' => [ [ 'station_id' => 'abc123', 'name' => 'BP Garage', 'brand' => 'BP', 'is_supermarket' => false, 'address' => '1 High Street', 'postcode' => 'SW1A 1AA', 'lat' => 51.5074, 'lng' => -0.1278, 'distance_km' => 1.5, 'fuel_type' => 'e10', 'price_pence' => 14390, 'price' => 143.9, 'price_updated_at' => '2026-04-05T08:00:00.000Z', 'price_classification' => 'current', 'price_classification_label' => 'Current', ], ], 'meta' => [ 'count' => 1, 'lowest_pence' => 14390, 'avg_pence' => 14390.0, ], ], 200), '*/api/prediction*' => Http::response([ 'action' => 'fill_now', 'confidence_score' => 80.0, 'confidence_label' => 'high', 'reasoning' => 'Prices rising.', 'predicted_direction' => 'up', 'predicted_change_pence' => 3.5, ], 200), ]); Livewire::test(FuelFinder::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', true) ->assertSet('apiError', null) ->assertSet('results', fn (array $r) => count($r) === 1 && $r[0]['name'] === 'BP Garage') ->assertSet('meta', fn (array $m) => $m['count'] === 1) ->assertSet('prediction', fn (?array $p) => $p !== null && $p['action'] === 'fill_now'); }); it('sets apiError from 422 station response and leaves prediction null', function () { Http::fake([ '*/api/stations*' => Http::response([ 'errors' => ['postcode' => ['Postcode not found.']], ], 422), ]); Livewire::test(FuelFinder::class) ->set('search', 'ZZ99 9ZZ') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('results', []) ->assertSet('meta', []) ->assertSet('prediction', null) ->assertSet('hasSearched', false) ->assertSet('apiError', 'Postcode not found.'); }); it('sets generic apiError on server error', function () { Http::fake([ '*/api/stations*' => Http::response([], 500), ]); Livewire::test(FuelFinder::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('apiError', 'Unable to fetch stations. Please try again.'); }); it('converts radius from miles to km in the outgoing stations request', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(FuelFinder::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->set('radius', 5) ->call('findStations'); Http::assertSent(function ($request) { if (! str_contains($request->url(), 'api/stations')) { return false; } $data = $request->data(); return isset($data['radius']) && abs((float) $data['radius'] - 8.05) < 0.01; }); }); it('resets state before each new search', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(FuelFinder::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->set('results', [['name' => 'Old Result']]) ->set('apiError', 'Old error') ->call('findStations') ->assertSet('apiError', null) ->assertSet('results', []); }); it('does not call findStations on updatedFuelType if not yet searched', function () { Http::fake(); Livewire::test(FuelFinder::class) ->set('fuelType', 'diesel'); Http::assertNothingSent(); }); it('re-runs findStations on updatedFuelType when already searched', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(FuelFinder::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('fuelType', 'diesel'); Http::assertSentCount(2); // stations + prediction }); it('re-runs findStations on updatedRadius when already searched', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(FuelFinder::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('radius', 10); Http::assertSentCount(2); }); it('re-runs findStations on updatedSort when already searched', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(FuelFinder::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('sort', 'price'); Http::assertSentCount(2); }); it('prediction remains null when prediction api fails', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response([], 500), ]); Livewire::test(FuelFinder::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', true) ->assertSet('prediction', null); });