Repository: symfony/ux
Pull request #2551 (2025-02-06 00:15:55)

[Map] Add new helpers: DistanceUnit, DistanceCalculator, CoordinateUtils
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Issues        | Fix #
| License       | MIT

To ease the work on distances while managing maps, this PR introduce some new tools

- `DistanceUnit` enum, that contains the most common distance units and their conversion ratios
- A `DistanceCalculatorInterface`, and 3 implementations (most commonly used formulas)
- A `DistanceCalculator` final service, that defaults to Vincenty formula and Meter unit
- A `CoordinateUtils` helper to convert DMS coordinates (`48° 7′ 3″ N`) in decimal ones (`48.1175 N`)

Also, added `getLatitude()` and `getLongitude()` on `Point`, avoiding the use of toArray()



Patch:
diff --git a/src/Autocomplete/CHANGELOG.md b/src/Autocomplete/CHANGELOG.md
index 8720236c..1f8cea05 100644
--- a/src/Autocomplete/CHANGELOG.md
+++ b/src/Autocomplete/CHANGELOG.md
@@ -4,7 +4,6 @@
 
 -   Deprecate `ExtraLazyChoiceLoader` in favor of `Symfony\Component\Form\ChoiceList\Loader\LazyChoiceLoader`
 -   Reset TomSelect when updating url attribute #1505
--   Add `getAttributes()` method to define additional attributes for autocomplete results #2541
 
 ## 2.22.0
 
diff --git a/src/Autocomplete/src/AutocompleteResultsExecutor.php b/src/Autocomplete/src/AutocompleteResultsExecutor.php
index 03d10e03..0061ca8a 100644
--- a/src/Autocomplete/src/AutocompleteResultsExecutor.php
+++ b/src/Autocomplete/src/AutocompleteResultsExecutor.php
@@ -73,7 +73,10 @@ final class AutocompleteResultsExecutor
 
         if (!method_exists($autocompleter, 'getGroupBy') || null === $groupBy = $autocompleter->getGroupBy()) {
             foreach ($paginator as $entity) {
-                $results[] = $this->formatResult($autocompleter, $entity);
+                $results[] = [
+                    'value' => $autocompleter->getValue($entity),
+                    'text' => $autocompleter->getLabel($entity),
+                ];
             }
 
             return new AutocompleteResults($results, $hasNextPage);
@@ -101,7 +104,10 @@ final class AutocompleteResultsExecutor
         $optgroupLabels = [];
 
         foreach ($paginator as $entity) {
-            $result = $this->formatResult($autocompleter, $entity);
+            $result = [
+                'value' => $autocompleter->getValue($entity),
+                'text' => $autocompleter->getLabel($entity),
+            ];
 
             $groupLabels = $groupBy($entity, $result['value'], $result['text']);
 
@@ -118,21 +124,4 @@ final class AutocompleteResultsExecutor
 
         return new AutocompleteResults($results, $hasNextPage, $optgroups);
     }
-
-    /**
-     * @return array<string, mixed>
-     */
-    private function formatResult(EntityAutocompleterInterface $autocompleter, object $entity): array
-    {
-        $attributes = [];
-        if (method_exists($autocompleter, 'getAttributes')) {
-            $attributes = $autocompleter->getAttributes($entity);
-        }
-
-        return [
-            ...$attributes,
-            'value' => $autocompleter->getValue($entity),
-            'text' => $autocompleter->getLabel($entity),
-        ];
-    }
 }
diff --git a/src/Autocomplete/src/EntityAutocompleterInterface.php b/src/Autocomplete/src/EntityAutocompleterInterface.php
index 4ad8a855..df0874fd 100644
--- a/src/Autocomplete/src/EntityAutocompleterInterface.php
+++ b/src/Autocomplete/src/EntityAutocompleterInterface.php
@@ -18,50 +18,30 @@ use Symfony\Bundle\SecurityBundle\Security;
 /**
  * Interface for classes that will have an "autocomplete" endpoint exposed.
  *
- * @template T of object
- *
- * TODO Remove next lines for Symfony UX 3
- *
- * @method array getAttributes(object $entity) Returns extra attributes to add to the autocomplete result.
- * @method mixed getGroupBy()                  Return group_by option.
+ * @method mixed getGroupBy() Return group_by option.
  */
 interface EntityAutocompleterInterface
 {
     /**
      * The fully-qualified entity class this will be autocompleting.
-     *
-     * @return class-string<T>
      */
     public function getEntityClass(): string;
 
     /**
      * Create a query builder that filters for the given "query".
-     *
-     * @param EntityRepository<T> $repository
      */
     public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder;
 
     /**
      * Returns the "choice_label" used to display this entity.
-     *
-     * @param T $entity
      */
     public function getLabel(object $entity): string;
 
     /**
      * Returns the "value" attribute for this entity, usually the id.
-     *
-     * @param T $entity
      */
     public function getValue(object $entity): mixed;
 
-    /**
-     * Returns extra attributes to add to the autocomplete result.
-     *
-     * TODO Uncomment for Symfony UX 3
-     */
-    /* public function getAttributes(object $entity): array; */
-
     /**
      * Return true if access should be granted to the autocomplete results for the current user.
      *
@@ -71,8 +51,6 @@ interface EntityAutocompleterInterface
 
     /*
      * Return group_by option.
-     *
-     * TODO Uncomment for Symfony UX 3
      */
     /* public function getGroupBy(): mixed; */
 }
diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md
index 7ba03516..a43e9518 100644
--- a/src/LiveComponent/CHANGELOG.md
+++ b/src/LiveComponent/CHANGELOG.md
@@ -3,7 +3,6 @@
 ## 2.23.0
 
 -   Allow configuring the secret used to compute fingerprints and checksums.
--   Prevent `__component` property to be serialized when called `JSON.stringify()`
 
 ## 2.22.0
 
diff --git a/src/LiveComponent/assets/dist/live_controller.js b/src/LiveComponent/assets/dist/live_controller.js
index 41177512..375a5b15 100644
--- a/src/LiveComponent/assets/dist/live_controller.js
+++ b/src/LiveComponent/assets/dist/live_controller.js
@@ -3059,10 +3059,7 @@ class LiveControllerDefault extends Controller {
         const id = this.element.id || null;
         this.component = new Component(this.element, this.nameValue, this.propsValue, this.listenersValue, id, LiveControllerDefault.backendFactory(this), new StimulusElementDriver(this));
         this.proxiedComponent = proxifyComponent(this.component);
-        Object.defineProperty(this.element, '__component', {
-            value: this.proxiedComponent,
-            writable: true,
-        });
+        this.element.__component = this.proxiedComponent;
         if (this.hasDebounceValue) {
             this.component.defaultDebounce = this.debounceValue;
         }
diff --git a/src/LiveComponent/assets/src/live_controller.ts b/src/LiveComponent/assets/src/live_controller.ts
index a9ea7f11..278915e0 100644
--- a/src/LiveComponent/assets/src/live_controller.ts
+++ b/src/LiveComponent/assets/src/live_controller.ts
@@ -285,10 +285,8 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
         );
         this.proxiedComponent = proxifyComponent(this.component);
 
-        Object.defineProperty(this.element, '__component', {
-            value: this.proxiedComponent,
-            writable: true,
-        });
+        // @ts-ignore Adding the dynamic property
+        this.element.__component = this.proxiedComponent;
 
         if (this.hasDebounceValue) {
             this.component.defaultDebounce = this.debounceValue;
diff --git a/src/LiveComponent/doc/index.rst b/src/LiveComponent/doc/index.rst
index 69bc0937..1b6b58be 100644
--- a/src/LiveComponent/doc/index.rst
+++ b/src/LiveComponent/doc/index.rst
@@ -1333,7 +1333,7 @@ Downloading files
 
 Currently, Live Components do not natively support returning file responses directly from a LiveAction. However, you can implement file downloads by redirecting to a route that handles the file response.
 
-Create a LiveAction that generates the URL for the file download and returns a ``RedirectResponse``::
+Create a LiveAction that generates the URL for the file download and returns a `RedirectResponse`::
 
         #[LiveAction]
         public function initiateDownload(UrlGeneratorInterface $urlGenerator): RedirectResponse
@@ -3722,9 +3722,6 @@ Test Helper
 
     The test helper was added in LiveComponents 2.11.
 
-Interact With Live-Components
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
 For testing, you can use the ``InteractsWithLiveComponents`` trait which
 uses Symfony's test client to render and make requests to your components::
 
@@ -3811,54 +3808,6 @@ uses Symfony's test client to render and make requests to your components::
     The ``InteractsWithLiveComponents`` trait can only be used in tests that extend
     ``Symfony\Bundle\FrameworkBundle\Test\KernelTestCase``.
 
-Test LiveCollectionType
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To test the submission of a form within a Live Component (with the above ``submitForm`` helper) containing a ``LiveCollectionType``, you first need to programmatically add the desired number of entries to the form, replicating the action of clicking the "Add" button.
-
-So, if the following are the forms used::
-
-    use Symfony\UX\LiveComponent\Form\Type\LiveCollectionType;
-
-    // Parent FormType used in the Live Component
-    class LiveCollectionFormType extends AbstractType
-    {
-        public function buildForm(FormBuilderInterface $builder, array $options): void
-        {
-            $builder->add('children', LiveCollectionType::class, [
-                    'entry_type' => ChildFormType::class,
-                ])
-            ;
-        }
-    }
-
-    // Child Form Type used for each entry in the collection
-    class ChildFormType extends AbstractType
-    {
-        public function buildForm(FormBuilderInterface $builder, array $options): void
-        {
-            $builder
-                ->add('name', TextType::class)
-                ->add('age', IntegerType::class)
-            ;
-        }
-    }
-
-Use the addCollectionItem method from the LiveCollectionTrait to dynamically add entries to the children field of the form before submitting it::
-
-    // Call the addCollectionItem method as many times as needed, specifying the name of the collection field.
-    $component->call('addCollectionItem', ['name' => 'children']);
-    $component->call('addCollectionItem', ['name' => 'children']);
-    //... can be called as many times as you need entries in your 'children' field
-
-    // ... then submit the form by providing data for all the fields in the ChildFormType for each added entry:
-    $component->submitForm([ 'live_collection_form' => [
-        'children' => [
-            ['name' => 'childName1', 'age' => 10],
-            ['name' => 'childName2', 'age' => 15],
-        ]
-    ]]);
-
 Backward Compatibility promise
 ------------------------------
 
diff --git a/src/Map/CHANGELOG.md b/src/Map/CHANGELOG.md
index cdad7ae9..75f70821 100644
--- a/src/Map/CHANGELOG.md
+++ b/src/Map/CHANGELOG.md
@@ -1,5 +1,13 @@
 # CHANGELOG
  
+## 2.23
+
+-  Add `DistanceUnit` to represent distance units (`m`, `km`, `miles`, `nmi`) and
+   ease conversion between units.
+-  Add `DistanceCalculatorInterface` interface and three implementations:
+   `HaversineDistanceCalculator`, `SphericalCosineDistanceCalculator` and `VincentyDistanceCalculator`.
+-  Add `CoordinateUtils` helper, to convert decimal coordinates (`43.2109`) in DMS (`56° 78' 90"`)
+
 ## 2.22
 
 -   Add method `Symfony\UX\Map\Renderer\AbstractRenderer::tapOptions()`, to allow Renderer to modify options before rendering a Map.
diff --git a/src/Map/src/Distance/DistanceCalculator.php b/src/Map/src/Distance/DistanceCalculator.php
new file mode 100644
index 00000000..46e7193d
--- /dev/null
+++ b/src/Map/src/Distance/DistanceCalculator.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Distance;
+
+use Symfony\UX\Map\Point;
+
+/**
+ * @author Simon André <smn.andre@gmail.com>
+ */
+final readonly class DistanceCalculator implements DistanceCalculatorInterface
+{
+    public function __construct(
+        private DistanceCalculatorInterface $calculator = new VincentyDistanceCalculator(),
+        private DistanceUnit $unit = DistanceUnit::Meter,
+    ) {
+    }
+
+    public function calculateDistance(Point $point1, Point $point2): float
+    {
+        return $this->calculator->calculateDistance($point1, $point2)
+            * $this->unit->getConversionFactor();
+    }
+}
diff --git a/src/Map/src/Distance/DistanceCalculatorInterface.php b/src/Map/src/Distance/DistanceCalculatorInterface.php
new file mode 100644
index 00000000..1019118d
--- /dev/null
+++ b/src/Map/src/Distance/DistanceCalculatorInterface.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Distance;
+
+use Symfony\UX\Map\Point;
+
+/**
+ * Interface for distance calculators.
+ *
+ * @author Simon André <smn.andre@gmail.com>
+ */
+interface DistanceCalculatorInterface
+{
+    /**
+     * Returns the distance between two points given their coordinates.
+     *
+     * @return float the distance between the two points, in meters
+     */
+    public function calculateDistance(Point $point1, Point $point2): float;
+}
diff --git a/src/Map/src/Distance/DistanceUnit.php b/src/Map/src/Distance/DistanceUnit.php
new file mode 100644
index 00000000..fa581e6a
--- /dev/null
+++ b/src/Map/src/Distance/DistanceUnit.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Distance;
+
+/**
+ * Represents a distance unit used in mapping and geospatial calculations.
+ *
+ * This enum defines common units for measuring distances. Each unit has an associated
+ * conversion factor which is used to convert a value from meters to that unit.
+ *
+ * @author Simon André <smn.andre@gmail.com>
+ */
+enum DistanceUnit: string
+{
+    /**
+     * The "meter" unit.
+     *
+     * This is the International System of Units (SI) base unit for length.
+     */
+    case Meter = 'm';
+
+    /**
+     * The "kilometer" unit.
+     *
+     * This unit is commonly used for longer distances.
+     */
+    case Kilometer = 'km';
+
+    /**
+     * The "mile" unit.
+     *
+     * This unit is widely used in the United States.
+     */
+    case Mile = 'mi';
+
+    /**
+     * The "nautical mile" unit.
+     *
+     * This unit is typically used in navigation.
+     */
+    case NauticalMile = 'nmi';
+
+    /**
+     * Returns the conversion factor to convert this unit to meters.
+     */
+    public function getConversionFactor(): float
+    {
+        return match ($this) {
+            self::Meter => 1.0,
+            self::Kilometer => 0.001,
+            self::Mile => 0.000621371,
+            self::NauticalMile => 0.000539957,
+        };
+    }
+
+    /**
+     * Returns the conversion factor to convert this unit to another unit.
+     */
+    public function getConversionFactorTo(DistanceUnit $unit): float
+    {
+        return $this->getConversionFactor() / $unit->getConversionFactor();
+    }
+
+    /**
+     * Returns the conversion factor to convert another unit to this unit.
+     */
+    public function getConversionFactorFrom(DistanceUnit $unit): float
+    {
+        return $unit->getConversionFactor() / $this->getConversionFactor();
+    }
+}
diff --git a/src/Map/src/Distance/HaversineDistanceCalculator.php b/src/Map/src/Distance/HaversineDistanceCalculator.php
new file mode 100644
index 00000000..32b18689
--- /dev/null
+++ b/src/Map/src/Distance/HaversineDistanceCalculator.php
@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Distance;
+
+use Symfony\UX\Map\Point;
+
+/**
+ * Haversine formula-based distance calculator.
+ *
+ * This calculator is accurate but slower than the spherical cosine formula.
+ *
+ * @author Simon André <smn.andre@gmail.com>
+ */
+final readonly class HaversineDistanceCalculator implements DistanceCalculatorInterface
+{
+    /**
+     * @const float The Earth's radius in meters.
+     */
+    private const EARTH_RADIUS = 6371000.0;
+
+    public function calculateDistance(Point $point1, Point $point2): float
+    {
+        $lat1Rad = deg2rad($point1->getLatitude());
+        $lat2Rad = deg2rad($point2->getLatitude());
+        $deltaLat = deg2rad($point2->getLatitude() - $point1->getLatitude());
+        $deltaLng = deg2rad($point2->getLongitude() - $point1->getLongitude());
+
+        $a = sin($deltaLat / 2) ** 2 + cos($lat1Rad) * cos($lat2Rad) * sin($deltaLng / 2) ** 2;
+        $c = 2 * asin(min(1.0, sqrt($a)));
+
+        return self::EARTH_RADIUS * $c;
+    }
+}
diff --git a/src/Map/src/Distance/SphericalCosineDistanceCalculator.php b/src/Map/src/Distance/SphericalCosineDistanceCalculator.php
new file mode 100644
index 00000000..7e5c1f63
--- /dev/null
+++ b/src/Map/src/Distance/SphericalCosineDistanceCalculator.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Distance;
+
+use Symfony\UX\Map\Point;
+
+/**
+ * Sphere-based distance calculator using the cosine of the spherical distance.
+ *
+ * This calculator is faster than the Haversine formula, but less accurate.
+ *
+ * @author Simon André <smn.andre@gmail.com>
+ */
+final readonly class SphericalCosineDistanceCalculator implements DistanceCalculatorInterface
+{
+    /**
+     * @const float The Earth's radius in meters.
+     */
+    private const EARTH_RADIUS = 6371000.0;
+
+    public function calculateDistance(Point $point1, Point $point2): float
+    {
+        $lat1Rad = deg2rad($point1->getLatitude());
+        $lat2Rad = deg2rad($point2->getLatitude());
+        $lng1Rad = deg2rad($point1->getLongitude());
+        $lng2Rad = deg2rad($point2->getLongitude());
+
+        $cosDistance = sin($lat1Rad) * sin($lat2Rad) + cos($lat1Rad) * cos($lat2Rad) * cos($lng2Rad - $lng1Rad);
+
+        // Correct for floating-point errors.
+        $cosDistance = min(1.0, max(-1.0, $cosDistance));
+        $angle = acos($cosDistance);
+
+        return self::EARTH_RADIUS * $angle;
+    }
+}
diff --git a/src/Map/src/Distance/VincentyDistanceCalculator.php b/src/Map/src/Distance/VincentyDistanceCalculator.php
new file mode 100644
index 00000000..7a08184a
--- /dev/null
+++ b/src/Map/src/Distance/VincentyDistanceCalculator.php
@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Distance;
+
+use Symfony\UX\Map\Point;
+
+/**
+ * Vincenty formula-based distance calculator.
+ *
+ * This calculator is more accurate than the Haversine formula, but slower.
+ *
+ * @author Simon André <smn.andre@gmail.com>
+ */
+final readonly class VincentyDistanceCalculator implements DistanceCalculatorInterface
+{
+    /**
+     * WS-84 ellipsoid parameters.
+     */
+    // Major Axis in meters
+    private const A = 6378137.0;
+    // Flattening
+    private const F = 1 / 298.257223563;
+    // Minor Axis in meters
+    private const B = 6356752.314245;
+
+    public function calculateDistance(Point $point1, Point $point2): float
+    {
+        $phi1 = deg2rad($point1->getLatitude());
+        $phi2 = deg2rad($point2->getLatitude());
+        $lambda1 = deg2rad($point1->getLongitude());
+        $lambda2 = deg2rad($point2->getLongitude());
+
+        $L = $lambda2 - $lambda1;
+        $U1 = atan((1 - self::F) * tan($phi1));
+        $U2 = atan((1 - self::F) * tan($phi2));
+        $sinU1 = sin($U1);
+        $cosU1 = cos($U1);
+        $sinU2 = sin($U2);
+        $cosU2 = cos($U2);
+
+        $lambda = $L;
+        $iterLimit = 100;
+        do {
+            $sinLambda = sin($lambda);
+            $cosLambda = cos($lambda);
+            $sinSigma = sqrt(($cosU2 * $sinLambda) ** 2
+                + ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) ** 2);
+
+            if (0.0 === $sinSigma) {
+                return 0.0;
+            }
+
+            $cosSigma = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
+            $sigma = atan2($sinSigma, $cosSigma);
+            $sinAlpha = $cosU1 * $cosU2 * $sinLambda / $sinSigma;
+            $cosSqAlpha = 1 - $sinAlpha * $sinAlpha;
+            $cos2SigmaM = (0.0 === $cosSqAlpha) ? 0.0 : $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;
+            $C = self::F / 16 * $cosSqAlpha * (4 + self::F * (4 - 3 * $cosSqAlpha));
+
+            $lambdaPrev = $lambda;
+            $lambda = $L + (1 - $C) * self::F * $sinAlpha
+                * ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * $cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)));
+        } while (abs($lambda - $lambdaPrev) > 1e-12 && --$iterLimit > 0);
+
+        if (0 === $iterLimit) {
+            throw new \RuntimeException('Vincenty formula failed to converge.');
+        }
+
+        $uSq = $cosSqAlpha * (self::A * self::A - self::B * self::B) / (self::B * self::B);
+        $Acoeff = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
+        $Bcoeff = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));
+        $deltaSigma = $Bcoeff * $sinSigma * ($cos2SigmaM + $Bcoeff / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)
+                    - $Bcoeff / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma) * (-3 + 4 * $cos2SigmaM * $cos2SigmaM)));
+        $distance = self::B * $Acoeff * ($sigma - $deltaSigma);
+
+        return $distance;
+    }
+}
diff --git a/src/Map/src/Point.php b/src/Map/src/Point.php
index f34f37a2..864041e2 100644
--- a/src/Map/src/Point.php
+++ b/src/Map/src/Point.php
@@ -33,6 +33,16 @@ final readonly class Point
         }
     }
 
+    public function getLatitude(): float
+    {
+        return $this->latitude;
+    }
+
+    public function getLongitude(): float
+    {
+        return $this->longitude;
+    }
+
     /**
      * @return array{lat: float, lng: float}
      */
diff --git a/src/Map/src/Utils/CoordinateUtils.php b/src/Map/src/Utils/CoordinateUtils.php
new file mode 100644
index 00000000..6730080d
--- /dev/null
+++ b/src/Map/src/Utils/CoordinateUtils.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Utils;
+
+/**
+ * Utility class to convert between decimal and DMS coordinates.
+ *
+ * @author Simon André <smn.andre@gmail.com>
+ */
+final class CoordinateUtils
+{
+    /**
+     * Converts a decimal coordinate to DMS (degrees, minutes, seconds).
+     *
+     * CoordinateUtils::decimalToDMS(48.8588443)
+     *      --> [48, 51, 31.8388]
+     *
+     * @param float $decimal the decimal coordinate to convert to DMS
+     *
+     * @return array{0: int, 1: int, 2: float}
+     */
+    public static function decimalToDMS(float $decimal): array
+    {
+        $sign = $decimal < 0 ? -1 : 1;
+        $decimal = abs($decimal);
+        $degrees = (int) $decimal * $sign;
+        $minutes = (int) (($decimal - abs($degrees)) * 60);
+        $seconds = ($decimal - abs($degrees) - $minutes / 60) * 3600;
+
+        return [$degrees, $minutes, round($seconds, 6)];
+    }
+
+    /**
+     * Converts a DMS (degrees, minutes, seconds) coordinate to decimal.
+     *
+     *  CoordinateUtils::DMSToDecimal(48, 51, 31.8388)
+     *      --> 48.8588443
+     *
+     * @param int   $degrees the degrees part of the DMS coordinate
+     * @param int   $minutes the minutes part of the DMS coordinate
+     * @param float $seconds the seconds part of the DMS coordinate
+     *
+     * @return float the decimal coordinate
+     */
+    public static function DMSToDecimal(int $degrees, int $minutes, float $seconds): float
+    {
+        $sign = $degrees < 0 ? -1 : 1;
+        $degrees = abs($degrees);
+        $decimal = $degrees + $minutes / 60 + $seconds / 3600;
+
+        return round($decimal * $sign, 6);
+    }
+}
diff --git a/src/Turbo/config/services.php b/src/Turbo/config/services.php
index 0344e95a..0cd1e6a1 100644
--- a/src/Turbo/config/services.php
+++ b/src/Turbo/config/services.php
@@ -11,13 +11,11 @@
 
 namespace Symfony\Component\DependencyInjection\Loader\Configurator;
 
-use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;
 use Symfony\UX\Turbo\Broadcaster\IdAccessor;
 use Symfony\UX\Turbo\Broadcaster\ImuxBroadcaster;
 use Symfony\UX\Turbo\Broadcaster\TwigBroadcaster;
 use Symfony\UX\Turbo\Doctrine\BroadcastListener;
-use Symfony\UX\Turbo\Request\RequestListener;
 use Symfony\UX\Turbo\Twig\TwigExtension;
 
 /*
@@ -57,8 +55,5 @@ return static function (ContainerConfigurator $container): void {
             ])
             ->tag('doctrine.event_listener', ['event' => 'onFlush'])
             ->tag('doctrine.event_listener', ['event' => 'postFlush'])
-
-        ->set('turbo.kernel.request_listener', RequestListener::class)
-            ->tag('kernel.event_listener', ['event' => KernelEvents::REQUEST, 'priority' => 256])
     ;
 };
diff --git a/src/Turbo/src/Request/RequestListener.php b/src/Turbo/src/Request/RequestListener.php
deleted file mode 100644
index a59172af..00000000
--- a/src/Turbo/src/Request/RequestListener.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Turbo\Request;
-
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\UX\Turbo\TurboBundle;
-
-/**
- * Registers the Turbo request format for all requests.
- *
- * @author Alexander Hofbauer <a.hofbauer@fify.at>
- */
-final class RequestListener
-{
-    public function __invoke(): void
-    {
-        (new Request())->setFormat(TurboBundle::STREAM_FORMAT, TurboBundle::STREAM_MEDIA_TYPE);
-    }
-}
diff --git a/src/Turbo/src/TurboBundle.php b/src/Turbo/src/TurboBundle.php
index e1524e4b..4c97d525 100644
--- a/src/Turbo/src/TurboBundle.php
+++ b/src/Turbo/src/TurboBundle.php
@@ -14,6 +14,7 @@ namespace Symfony\UX\Turbo;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\Compiler\PassConfig;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Bundle\Bundle;
 
 /**
@@ -24,6 +25,11 @@ final class TurboBundle extends Bundle
     public const STREAM_FORMAT = 'turbo_stream';
     public const STREAM_MEDIA_TYPE = 'text/vnd.turbo-stream.html';
 
+    public function boot(): void
+    {
+        (new Request())->setFormat(self::STREAM_FORMAT, self::STREAM_MEDIA_TYPE);
+    }
+
     public function build(ContainerBuilder $container): void
     {
         parent::build($container);


Test patch:
diff --git a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php
deleted file mode 100644
index 7f86431a..00000000
--- a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter;
-
-class CustomAttributesProductAutocompleter extends CustomProductAutocompleter
-{
-    public function getAttributes(object $entity): array
-    {
-        return [
-            'disabled' => true,
-            'value' => 'This value should be replaced with the result of getValue()',
-            'text' => 'This value should be replaced with the result of getText()',
-        ];
-    }
-}
diff --git a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php
index 09b5b96c..912a864c 100644
--- a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php
+++ b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php
@@ -19,9 +19,6 @@ use Symfony\UX\Autocomplete\Doctrine\EntitySearchUtil;
 use Symfony\UX\Autocomplete\EntityAutocompleterInterface;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Product;
 
-/**
- * @implements EntityAutocompleterInterface<Product>
- */
 class CustomProductAutocompleter implements EntityAutocompleterInterface
 {
     public function __construct(
@@ -61,11 +58,6 @@ class CustomProductAutocompleter implements EntityAutocompleterInterface
         return $entity->getId();
     }
 
-    public function getAttributes(object $entity): array
-    {
-        return [];
-    }
-
     public function isGranted(Security $security): bool
     {
         if ($this->requestStack->getCurrentRequest()?->query->get('enforce_test_security')) {
diff --git a/src/Autocomplete/tests/Fixtures/Kernel.php b/src/Autocomplete/tests/Fixtures/Kernel.php
index de2877b0..f1991a22 100644
--- a/src/Autocomplete/tests/Fixtures/Kernel.php
+++ b/src/Autocomplete/tests/Fixtures/Kernel.php
@@ -34,7 +34,6 @@ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
 use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
 use Symfony\UX\Autocomplete\AutocompleteBundle;
 use Symfony\UX\Autocomplete\DependencyInjection\AutocompleteFormTypePass;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomGroupByProductAutocompleter;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomProductAutocompleter;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Form\ProductType;
@@ -177,13 +176,6 @@ final class Kernel extends BaseKernel
                 'alias' => 'custom_group_by_product',
             ]);
 
-        $services->set(CustomAttributesProductAutocompleter::class)
-            ->public()
-            ->arg(1, new Reference('ux.autocomplete.entity_search_util'))
-            ->tag(AutocompleteFormTypePass::ENTITY_AUTOCOMPLETER_TAG, [
-                'alias' => 'custom_attributes_product',
-            ]);
-
         $services->alias('public.results_executor', 'ux.autocomplete.results_executor')
             ->public();
 
diff --git a/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php b/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php
deleted file mode 100644
index b804afd1..00000000
--- a/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Autocomplete\Tests\Integration;
-
-use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
-use Symfony\UX\Autocomplete\AutocompleteResultsExecutor;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Factory\ProductFactory;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Kernel;
-use Zenstruck\Foundry\Test\Factories;
-use Zenstruck\Foundry\Test\ResetDatabase;
-
-class AutocompleteResultsExecutorTest extends KernelTestCase
-{
-    use Factories;
-    use ResetDatabase;
-
-    public function testItReturnsExtraAttributes(): void
-    {
-        $kernel = new Kernel('test', true);
-        $kernel->disableForms();
-        $kernel->boot();
-
-        $product = ProductFactory::createOne(['name' => 'Foo']);
-
-        /** @var AutocompleteResultsExecutor $executor */
-        $executor = $kernel->getContainer()->get('public.results_executor');
-        $autocompleter = $kernel->getContainer()->get(CustomAttributesProductAutocompleter::class);
-        $data = $executor->fetchResults($autocompleter, '', 1);
-        $this->assertCount(1, $data->results);
-        $this->assertSame(['disabled' => true, 'value' => $product->getId(), 'text' => 'Foo'], $data->results[0]);
-    }
-}
diff --git a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php
index 097e4d77..6bae36f9 100644
--- a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php
+++ b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php
@@ -18,7 +18,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component2;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory;
-use Zenstruck\Foundry\Test\Factories;
 use Zenstruck\Foundry\Test\ResetDatabase;
 
 /**
@@ -26,7 +25,6 @@ use Zenstruck\Foundry\Test\ResetDatabase;
  */
 final class InteractsWithLiveComponentsTest extends KernelTestCase
 {
-    use Factories;
     use InteractsWithLiveComponents;
     use ResetDatabase;
 
diff --git a/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php b/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php
index 01941f10..a7062605 100644
--- a/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php
+++ b/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php
@@ -14,7 +14,6 @@ namespace Symfony\UX\LiveComponent\Tests\Unit\Form;
 use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Component\FormComponentWithManyDifferentFieldsType;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory;
-use Zenstruck\Foundry\Test\Factories;
 use Zenstruck\Foundry\Test\ResetDatabase;
 
 /**
@@ -22,7 +21,6 @@ use Zenstruck\Foundry\Test\ResetDatabase;
  */
 class ComponentWithFormTest extends KernelTestCase
 {
-    use Factories;
     use ResetDatabase;
 
     public function testFormValues(): void
diff --git a/src/Map/tests/Distance/DistanceCalculatorTest.php b/src/Map/tests/Distance/DistanceCalculatorTest.php
new file mode 100644
index 00000000..3f6c982a
--- /dev/null
+++ b/src/Map/tests/Distance/DistanceCalculatorTest.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Tests\Distance;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\UX\Map\Distance\DistanceCalculator;
+use Symfony\UX\Map\Distance\DistanceCalculatorInterface;
+use Symfony\UX\Map\Distance\HaversineDistanceCalculator;
+use Symfony\UX\Map\Distance\SphericalCosineDistanceCalculator;
+use Symfony\UX\Map\Distance\VincentyDistanceCalculator;
+use Symfony\UX\Map\Point;
+
+class DistanceCalculatorTest extends TestCase
+{
+    public function testCalculateDistanceUseCalculator(): void
+    {
+        $calculator = new class implements DistanceCalculatorInterface {
+            public function calculateDistance(Point $point1, Point $point2): float
+            {
+                return $point1->getLatitude() + $point2->getLongitude();
+            }
+        };
+
+        $distanceCalculator = new DistanceCalculator($calculator);
+        $this->assertSame(0.0, $distanceCalculator->calculateDistance(new Point(0.0, 0.0), new Point(0.0, 0.0)));
+        $this->assertSame(90.0, $distanceCalculator->calculateDistance(new Point(45.0, 0.0), new Point(0.0, 45.0)));
+    }
+
+    /**
+     * Test that the non-reference calculators (Haversine and Spherical Cosine)
+     * produce results close to the reference (Vincenty) within an acceptable margin.
+     *
+     * @dataProvider distanceAccuracyProvider
+     */
+    public function testAccuracyAgainstVincenty(Point $point1, Point $point2, float $tolerance): void
+    {
+        $vincenty = new VincentyDistanceCalculator();
+        $referenceDistance = $vincenty->calculateDistance($point1, $point2);
+
+        $calculators = [
+            new HaversineDistanceCalculator(),
+            new SphericalCosineDistanceCalculator(),
+        ];
+
+        foreach ($calculators as $calculator) {
+            $distance = $calculator->calculateDistance($point1, $point2);
+            $difference = abs($referenceDistance - $distance);
+
+            $this->assertLessThanOrEqual($tolerance, $difference, \sprintf('%s difference (%.2f m) exceeds tolerance (%.2f m).', $calculator::class, $difference, $tolerance));
+        }
+    }
+
+    /**
+     * @return array<array{Point, Point, float}>
+     */
+    public static function distanceAccuracyProvider(): array
+    {
+        return [
+            'Short distance: around the equator (111.32m)' => [
+                new Point(0.0, 0.0),
+                new Point(0.0, 0.001),
+                .25,
+            ],
+            'Small distance: Highbury to Emirates (445.61m)' => [
+                new Point(51.55703, -0.10280),
+                new Point(51.55509, -0.10844),
+                2.5,
+            ],
+            'Moderate distance: Rennes to Saint-Malo (64.537 km)' => [
+                new Point(48.1173, -1.6778),
+                new Point(48.6497, -2.0258),
+                50.0,
+            ],
+            'Moderate distance: Paris to London  (343.556 km)' => [
+                new Point(48.8566, 2.3522),
+                new Point(51.5074, -0.1278),
+                500.0,
+            ],
+            'Long distance: Metropolis to Gotham City (1,144.291 km)' => [
+                new Point(40.7128, -74.0060),
+                new Point(41.8781, -87.6298),
+                5000.0,
+            ],
+            'Long distance: New York to Los Angeles (3,935.746 km)' => [
+                new Point(40.7128, -74.0060),
+                new Point(34.0522, -118.2437),
+                10000.0,
+            ],
+        ];
+    }
+}
diff --git a/src/Map/tests/Distance/DistanceUnitTest.php b/src/Map/tests/Distance/DistanceUnitTest.php
new file mode 100644
index 00000000..f33b985d
--- /dev/null
+++ b/src/Map/tests/Distance/DistanceUnitTest.php
@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Tests\Distance;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\UX\Map\Distance\DistanceUnit;
+
+class DistanceUnitTest extends TestCase
+{
+    public function testConversionFactorIsPositive()
+    {
+        foreach (DistanceUnit::cases() as $unit) {
+            $this->assertGreaterThan(0, $unit->getConversionFactor());
+        }
+    }
+
+    public function testConversionFactorToMeterIsSameAsConversionFactor()
+    {
+        foreach (DistanceUnit::cases() as $unit) {
+            $this->assertEquals($unit->getConversionFactor(), $unit->getConversionFactorTo(DistanceUnit::Meter));
+        }
+    }
+
+    /**
+     * @dataProvider provideConvertedUnits
+     */
+    public function testConversionFactorFrom(DistanceUnit $unit, DistanceUnit $otherUnit, float $expected): void
+    {
+        $this->assertEqualsWithDelta($expected, $unit->getConversionFactorFrom($otherUnit), 0.001);
+    }
+
+    public static function provideConvertedUnits(): iterable
+    {
+        yield 'Kilometer to Kilometer' => [DistanceUnit::Kilometer, DistanceUnit::Kilometer, 1.0];
+        yield 'Kilometer to Meter' => [DistanceUnit::Kilometer, DistanceUnit::Meter, 1000.0];
+        yield 'Kilometer to Mile' => [DistanceUnit::Kilometer, DistanceUnit::Mile, 0.621371];
+        yield 'Kilometer to Nautical Mile' => [DistanceUnit::Kilometer, DistanceUnit::NauticalMile, 0.539957];
+
+        yield 'Meter to Kilometer' => [DistanceUnit::Meter, DistanceUnit::Kilometer, 0.001];
+        yield 'Meter to Meter' => [DistanceUnit::Meter, DistanceUnit::Meter, 1.0];
+        yield 'Meter to Mile' => [DistanceUnit::Meter, DistanceUnit::Mile, 0.000621371];
+        yield 'Meter to Nautical Mile' => [DistanceUnit::Meter, DistanceUnit::NauticalMile, 0.000539957];
+
+        yield 'Mile to Kilometer' => [DistanceUnit::Mile, DistanceUnit::Kilometer, 1.609344];
+        yield 'Mile to Meter' => [DistanceUnit::Mile, DistanceUnit::Meter, 1609.344];
+        yield 'Mile to Mile' => [DistanceUnit::Mile, DistanceUnit::Mile, 1.0];
+        yield 'Mile to Nautical Mile' => [DistanceUnit::Mile, DistanceUnit::NauticalMile, 0.868976];
+
+        yield 'Nautical Mile to Kilometer' => [DistanceUnit::NauticalMile, DistanceUnit::Kilometer, 1.852];
+        yield 'Nautical Mile to Meter' => [DistanceUnit::NauticalMile, DistanceUnit::Meter, 1852.0];
+        yield 'Nautical Mile to Mile' => [DistanceUnit::NauticalMile, DistanceUnit::Mile, 1.15078];
+        yield 'Nautical Mile to Nautical Mile' => [DistanceUnit::NauticalMile, DistanceUnit::NauticalMile, 1.0];
+    }
+}
diff --git a/src/Map/tests/PointTest.php b/src/Map/tests/PointTest.php
index 96108040..20326806 100644
--- a/src/Map/tests/PointTest.php
+++ b/src/Map/tests/PointTest.php
@@ -36,6 +36,20 @@ class PointTest extends TestCase
         new Point($latitude, $longitude);
     }
 
+    public function testGetLatitude(): void
+    {
+        $point = new Point(48.8566, 2.3533);
+
+        self::assertSame(48.8566, $point->getLatitude());
+    }
+
+    public function testGetLongitude(): void
+    {
+        $point = new Point(48.8566, 2.3533);
+
+        self::assertSame(2.3533, $point->getLongitude());
+    }
+
     public function testToArray(): void
     {
         $point = new Point(48.8566, 2.3533);
diff --git a/src/Map/tests/Utils/CoordinateUtilsTest.php b/src/Map/tests/Utils/CoordinateUtilsTest.php
new file mode 100644
index 00000000..e7ac82dc
--- /dev/null
+++ b/src/Map/tests/Utils/CoordinateUtilsTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Utils;
+
+use PHPUnit\Framework\TestCase;
+
+class CoordinateUtilsTest extends TestCase
+{
+    public function testDecimalToDMSConvertsCorrectly(): void
+    {
+        $result = CoordinateUtils::decimalToDMS(48.8588443);
+        $this->assertSame([48, 51, 31.83948], $result);
+    }
+
+    public function testDecimalToDMSHandlesNegativeValues(): void
+    {
+        $result = CoordinateUtils::decimalToDMS(-48.8588443);
+        $this->assertSame([-48, 51, 31.83948], $result);
+    }
+
+    public function testDMSToDecimalConvertsCorrectly(): void
+    {
+        $result = CoordinateUtils::DMSToDecimal(48, 51, 31.8388);
+        $this->assertSame(48.858844, $result);
+    }
+
+    public function testDMSToDecimalHandlesNegativeValues(): void
+    {
+        $result = CoordinateUtils::DMSToDecimal(-48, 51, 31.8388);
+        $this->assertSame(-48.858844, $result);
+    }
+
+    public function testDMSToDecimalHandlesZeroValues(): void
+    {
+        $result = CoordinateUtils::DMSToDecimal(0, 0, 0.0);
+        $this->assertSame(0.0, $result);
+    }
+}
diff --git a/src/Turbo/tests/Request/RequestListenerTest.php b/src/Turbo/tests/Request/RequestListenerTest.php
deleted file mode 100644
index 27b1c73f..00000000
--- a/src/Turbo/tests/Request/RequestListenerTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Turbo\Tests\Request;
-
-use Symfony\Bundle\FrameworkBundle\KernelBrowser;
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-use Symfony\UX\Turbo\TurboBundle;
-
-/**
- * Tests the Turbo request listener.
- *
- * @author Alexander Hofbauer <a.hofbauer@fify.at>
- */
-class RequestListenerTest extends WebTestCase
-{
-    public function testAddsTurboRequestFormat(): void
-    {
-        $client = static::createClient(server: [
-            'HTTP_ACCEPT' => 'text/vnd.turbo-stream.html, text/html, application/xhtml+xml',
-        ]);
-
-        // simulate worker mode
-        $client->disableReboot();
-
-        // request twice to test if listener is always called
-        $this->assertPreferredFormat();
-        $this->assertPreferredFormat();
-    }
-
-    private function assertPreferredFormat(): void
-    {
-        /** @var KernelBrowser $client */
-        $client = static::getClient();
-
-        $client->request('POST', '/turboRequest');
-
-        $response = $client->getResponse()->getContent();
-
-        $expectedJson = json_encode([
-            'preferred_format' => TurboBundle::STREAM_FORMAT,
-        ]);
-
-        $this->assertJsonStringEqualsJsonString($expectedJson ?: '', $response ?: '');
-    }
-}
diff --git a/src/Turbo/tests/app/Kernel.php b/src/Turbo/tests/app/Kernel.php
index 3f68994e..675a0f8b 100644
--- a/src/Turbo/tests/app/Kernel.php
+++ b/src/Turbo/tests/app/Kernel.php
@@ -29,7 +29,6 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Symfony\Component\Form\FormFactoryInterface;
-use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -145,7 +144,6 @@ class Kernel extends BaseKernel
         $routes->add('artists', '/artists')->controller('kernel::artists');
         $routes->add('artist', '/artists/{id}')->controller('kernel::artist');
         $routes->add('artist_from_song', '/artistFromSong')->controller('kernel::artistFromSong');
-        $routes->add('turbo_request', '/turboRequest')->controller('kernel::turboRequest');
     }
 
     public function getProjectDir(): string
@@ -314,11 +312,4 @@ class Kernel extends BaseKernel
             'song' => $song,
         ]));
     }
-
-    public function turboRequest(Request $request): Response
-    {
-        return new JsonResponse([
-            'preferred_format' => $request->getPreferredFormat(),
-        ]);
-    }
 }