assertStatus(200) ->assertSeeHtml('name="search"'); }); it('has default property values', function () { Livewire::test(Search::class) ->assertSet('search', '') ->assertSet('fuelType', 'petrol') ->assertSet('radius', 5) ->assertSet('sort', 'reliable') ->assertSet('apiError', null) ->assertSet('hasSearched', false); }); it('validates search is required', function () { Livewire::test(Search::class) ->call('findStations') ->assertHasErrors(['search' => 'required']); }); it('validates fuelType is required', function () { Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', '') ->call('findStations') ->assertHasErrors(['fuelType' => 'required']); }); it('dispatches stations-found with results, meta, prediction and radius 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(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', true) ->assertSet('apiError', null) ->assertDispatched('stations-found', fn ($event, $params) => count($params['results']) === 1 && $params['results'][0]['name'] === 'BP Garage' && $params['meta']['count'] === 1 && $params['prediction']['action'] === 'fill_now' && $params['radius'] === 5 ); }); it('sets apiError from 422 station response and does not dispatch stations-found', function () { Http::fake([ '*/api/stations*' => Http::response([ 'errors' => ['postcode' => ['Postcode not found.']], ], 422), ]); Livewire::test(Search::class) ->set('search', 'ZZ99 9ZZ') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', false) ->assertSet('apiError', 'Postcode not found.') ->assertNotDispatched('stations-found'); }); it('sets generic apiError on server error', function () { Http::fake([ '*/api/stations*' => Http::response([], 500), ]); Livewire::test(Search::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(Search::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 apiError 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(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->set('apiError', 'Old error') ->call('findStations') ->assertSet('apiError', null); }); it('does not call findStations on updatedFuelType if not yet searched', function () { Http::fake(); Livewire::test(Search::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(Search::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('fuelType', 'diesel'); Http::assertSentCount(2); }); 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(Search::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(Search::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('sort', 'price'); Http::assertSentCount(2); }); it('prediction is null in stations-found payload when prediction api fails', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response([], 500), ]); Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', true) ->assertDispatched('stations-found', fn ($event, $params) => $params['prediction'] === null ); });