mirror of
https://github.com/symfony/ux-google-map.git
synced 2026-03-23 23:42:13 +01:00
[Map] Create Map component
This commit is contained in:
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/Tests export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# CHANGELOG
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Bridge added
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2024-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
87
README.md
Normal file
87
README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Symfony UX Map: Google Maps
|
||||
|
||||
[Google Maps](https://developers.google.com/maps/documentation/javascript/overview) integration for Symfony UX Map.
|
||||
|
||||
## DSN example
|
||||
|
||||
```dotenv
|
||||
UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default
|
||||
|
||||
# With options
|
||||
UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?version=weekly
|
||||
UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?language=fr®ion=FR
|
||||
```
|
||||
|
||||
Available options:
|
||||
|
||||
| Option | Description | Default |
|
||||
|------------|------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|
|
||||
| `id` | The id of the script tag | `__googleMapsScriptId` |
|
||||
| `language` | Force language, see [list of supported languages](https://developers.google.com/maps/faq#languagesupport) specified in the browser | The user's preferred language |
|
||||
| `region` | Unicode region subtag identifiers compatible with [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) | |
|
||||
| `nonce` | Use a cryptographic nonce attribute | |
|
||||
| `retries` | The number of script load retries | 3 |
|
||||
| `url` | Custom url to load the Google Maps API script | `https://maps.googleapis.com/maps/api/js` |
|
||||
| `version` | The release channels or version numbers | `weekly` |
|
||||
|
||||
## Map options
|
||||
|
||||
You can use the `GoogleOptions` class to configure your `Map`::
|
||||
|
||||
```php
|
||||
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\FullscreenControlOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\StreetViewControlOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ZoomControlOptions;
|
||||
use Symfony\UX\Map\Point;
|
||||
use Symfony\UX\Map\Map;
|
||||
|
||||
$map = (new Map())
|
||||
->center(new Point(48.8566, 2.3522))
|
||||
->zoom(6);
|
||||
|
||||
// To configure controls options, and some other options:
|
||||
$googleOptions = (new GoogleOptions())
|
||||
->mapId('YOUR_MAP_ID')
|
||||
->gestureHandling(GestureHandling::GREEDY)
|
||||
->backgroundColor('#f00')
|
||||
->doubleClickZoom(true)
|
||||
->zoomControlOptions(new ZoomControlOptions(
|
||||
position: ControlPosition::BLOCK_START_INLINE_END,
|
||||
))
|
||||
->mapTypeControlOptions(new MapTypeControlOptions(
|
||||
mapTypeIds: ['roadmap'],
|
||||
position: ControlPosition::INLINE_END_BLOCK_START,
|
||||
style: MapTypeControlStyle::DROPDOWN_MENU,
|
||||
))
|
||||
->streetViewControlOptions(new StreetViewControlOptions(
|
||||
position: ControlPosition::BLOCK_END_INLINE_START,
|
||||
))
|
||||
->fullscreenControlOptions(new FullscreenControlOptions(
|
||||
position: ControlPosition::INLINE_START_BLOCK_END,
|
||||
))
|
||||
;
|
||||
|
||||
// To disable controls:
|
||||
$googleOptions = (new GoogleOptions())
|
||||
->mapId('YOUR_MAP_ID')
|
||||
->zoomControl(false)
|
||||
->mapTypeControl(false)
|
||||
->streetViewControl(false)
|
||||
->fullscreenControl(false)
|
||||
;
|
||||
|
||||
// Add the custom options to the map
|
||||
$map->options($googleOptions);
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
|
||||
- [Report issues](https://github.com/symfony/ux/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/ux/pulls)
|
||||
in the [main Symfony UX repository](https://github.com/symfony/ux)
|
||||
26
assets/dist/map_controller.d.ts
vendored
Normal file
26
assets/dist/map_controller.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/// <reference types="google.maps" />
|
||||
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
|
||||
import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller';
|
||||
import type { LoaderOptions } from '@googlemaps/js-api-loader';
|
||||
type MapOptions = Pick<google.maps.MapOptions, 'mapId' | 'gestureHandling' | 'backgroundColor' | 'disableDoubleClickZoom' | 'zoomControl' | 'zoomControlOptions' | 'mapTypeControl' | 'mapTypeControlOptions' | 'streetViewControl' | 'streetViewControlOptions' | 'fullscreenControl' | 'fullscreenControlOptions'>;
|
||||
export default class extends AbstractMapController<MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindow> {
|
||||
static values: {
|
||||
providerOptions: ObjectConstructor;
|
||||
};
|
||||
providerOptionsValue: Pick<LoaderOptions, 'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version'>;
|
||||
connect(): Promise<void>;
|
||||
protected doCreateMap({ center, zoom, options, }: {
|
||||
center: Point;
|
||||
zoom: number;
|
||||
options: MapOptions;
|
||||
}): google.maps.Map;
|
||||
protected doCreateMarker(definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>): google.maps.marker.AdvancedMarkerElement;
|
||||
protected doCreateInfoWindow({ definition, marker, }: {
|
||||
definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>['infoWindow'];
|
||||
marker: google.maps.marker.AdvancedMarkerElement;
|
||||
}): google.maps.InfoWindow;
|
||||
private createTextOrElement;
|
||||
private closeInfoWindowsExcept;
|
||||
protected doFitBoundsToMarkers(): void;
|
||||
}
|
||||
export {};
|
||||
103
assets/dist/map_controller.js
vendored
Normal file
103
assets/dist/map_controller.js
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
|
||||
import { Loader } from '@googlemaps/js-api-loader';
|
||||
|
||||
let loader;
|
||||
let library;
|
||||
class default_1 extends AbstractMapController {
|
||||
async connect() {
|
||||
if (!loader) {
|
||||
loader = new Loader(this.providerOptionsValue);
|
||||
}
|
||||
const { Map: _Map, InfoWindow } = await loader.importLibrary('maps');
|
||||
const { AdvancedMarkerElement } = await loader.importLibrary('marker');
|
||||
library = { _Map, AdvancedMarkerElement, InfoWindow };
|
||||
super.connect();
|
||||
}
|
||||
doCreateMap({ center, zoom, options, }) {
|
||||
options.zoomControl = typeof options.zoomControlOptions !== 'undefined';
|
||||
options.mapTypeControl = typeof options.mapTypeControlOptions !== 'undefined';
|
||||
options.streetViewControl = typeof options.streetViewControlOptions !== 'undefined';
|
||||
options.fullscreenControl = typeof options.fullscreenControlOptions !== 'undefined';
|
||||
return new library._Map(this.element, {
|
||||
...options,
|
||||
center,
|
||||
zoom,
|
||||
});
|
||||
}
|
||||
doCreateMarker(definition) {
|
||||
const { position, title, infoWindow, rawOptions = {}, ...otherOptions } = definition;
|
||||
const marker = new library.AdvancedMarkerElement({
|
||||
position,
|
||||
title,
|
||||
...otherOptions,
|
||||
...rawOptions,
|
||||
map: this.map,
|
||||
});
|
||||
if (infoWindow) {
|
||||
this.createInfoWindow({ definition: infoWindow, marker });
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
doCreateInfoWindow({ definition, marker, }) {
|
||||
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
|
||||
const infoWindow = new library.InfoWindow({
|
||||
headerContent: this.createTextOrElement(headerContent),
|
||||
content: this.createTextOrElement(content),
|
||||
...otherOptions,
|
||||
...rawOptions,
|
||||
});
|
||||
if (definition.opened) {
|
||||
infoWindow.open({
|
||||
map: this.map,
|
||||
shouldFocus: false,
|
||||
anchor: marker,
|
||||
});
|
||||
}
|
||||
marker.addListener('click', () => {
|
||||
if (definition.autoClose) {
|
||||
this.closeInfoWindowsExcept(infoWindow);
|
||||
}
|
||||
infoWindow.open({
|
||||
map: this.map,
|
||||
anchor: marker,
|
||||
});
|
||||
});
|
||||
return infoWindow;
|
||||
}
|
||||
createTextOrElement(content) {
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
if (content.includes('<')) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = content;
|
||||
return div;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
closeInfoWindowsExcept(infoWindow) {
|
||||
this.infoWindows.forEach((otherInfoWindow) => {
|
||||
if (otherInfoWindow !== infoWindow) {
|
||||
otherInfoWindow.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
doFitBoundsToMarkers() {
|
||||
if (this.markers.length === 0) {
|
||||
return;
|
||||
}
|
||||
const bounds = new google.maps.LatLngBounds();
|
||||
this.markers.forEach((marker) => {
|
||||
if (!marker.position) {
|
||||
return;
|
||||
}
|
||||
bounds.extend(marker.position);
|
||||
});
|
||||
this.map.fitBounds(bounds);
|
||||
}
|
||||
}
|
||||
default_1.values = {
|
||||
providerOptions: Object,
|
||||
};
|
||||
|
||||
export { default_1 as default };
|
||||
39
assets/package.json
Normal file
39
assets/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@symfony/ux-map-google",
|
||||
"description": "GoogleMaps bridge for Symfony UX Map, integrate interactive maps in your Symfony applications",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "dist/map_controller.js",
|
||||
"types": "dist/map_controller.d.ts",
|
||||
"symfony": {
|
||||
"controllers": {
|
||||
"map": {
|
||||
"main": "dist/map_controller.js",
|
||||
"webpackMode": "lazy",
|
||||
"fetch": "lazy",
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"importmap": {
|
||||
"@hotwired/stimulus": "^3.0.0",
|
||||
"@googlemaps/js-api-loader": "^1.16.6",
|
||||
"@symfony/ux-map-google/map-controller": "path:%PACKAGE%/dist/map_controller.js"
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@googlemaps/js-api-loader": "^1.16.6",
|
||||
"@hotwired/stimulus": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@googlemaps/js-api-loader": {
|
||||
"optional": false
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@googlemaps/js-api-loader": "^1.16.6",
|
||||
"@hotwired/stimulus": "^3.0.0",
|
||||
"@types/google.maps": "^3.55.9",
|
||||
"happy-dom": "^14.12.3"
|
||||
}
|
||||
}
|
||||
187
assets/src/map_controller.ts
Normal file
187
assets/src/map_controller.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
|
||||
import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller';
|
||||
import type { LoaderOptions } from '@googlemaps/js-api-loader';
|
||||
import { Loader } from '@googlemaps/js-api-loader';
|
||||
|
||||
type MapOptions = Pick<
|
||||
google.maps.MapOptions,
|
||||
| 'mapId'
|
||||
| 'gestureHandling'
|
||||
| 'backgroundColor'
|
||||
| 'disableDoubleClickZoom'
|
||||
| 'zoomControl'
|
||||
| 'zoomControlOptions'
|
||||
| 'mapTypeControl'
|
||||
| 'mapTypeControlOptions'
|
||||
| 'streetViewControl'
|
||||
| 'streetViewControlOptions'
|
||||
| 'fullscreenControl'
|
||||
| 'fullscreenControlOptions'
|
||||
>;
|
||||
|
||||
let loader: Loader;
|
||||
let library: {
|
||||
_Map: typeof google.maps.Map;
|
||||
AdvancedMarkerElement: typeof google.maps.marker.AdvancedMarkerElement;
|
||||
InfoWindow: typeof google.maps.InfoWindow;
|
||||
};
|
||||
|
||||
export default class extends AbstractMapController<
|
||||
MapOptions,
|
||||
google.maps.Map,
|
||||
google.maps.marker.AdvancedMarkerElement,
|
||||
google.maps.InfoWindow
|
||||
> {
|
||||
static values = {
|
||||
providerOptions: Object,
|
||||
};
|
||||
|
||||
declare providerOptionsValue: Pick<
|
||||
LoaderOptions,
|
||||
'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version'
|
||||
>;
|
||||
|
||||
async connect() {
|
||||
if (!loader) {
|
||||
loader = new Loader(this.providerOptionsValue);
|
||||
}
|
||||
|
||||
const { Map: _Map, InfoWindow } = await loader.importLibrary('maps');
|
||||
const { AdvancedMarkerElement } = await loader.importLibrary('marker');
|
||||
library = { _Map, AdvancedMarkerElement, InfoWindow };
|
||||
|
||||
super.connect();
|
||||
}
|
||||
|
||||
protected doCreateMap({
|
||||
center,
|
||||
zoom,
|
||||
options,
|
||||
}: {
|
||||
center: Point;
|
||||
zoom: number;
|
||||
options: MapOptions;
|
||||
}): google.maps.Map {
|
||||
// We assume the following control options are enabled if their options are set
|
||||
options.zoomControl = typeof options.zoomControlOptions !== 'undefined';
|
||||
options.mapTypeControl = typeof options.mapTypeControlOptions !== 'undefined';
|
||||
options.streetViewControl = typeof options.streetViewControlOptions !== 'undefined';
|
||||
options.fullscreenControl = typeof options.fullscreenControlOptions !== 'undefined';
|
||||
|
||||
return new library._Map(this.element, {
|
||||
...options,
|
||||
center,
|
||||
zoom,
|
||||
});
|
||||
}
|
||||
|
||||
protected doCreateMarker(
|
||||
definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>
|
||||
): google.maps.marker.AdvancedMarkerElement {
|
||||
const { position, title, infoWindow, rawOptions = {}, ...otherOptions } = definition;
|
||||
|
||||
const marker = new library.AdvancedMarkerElement({
|
||||
position,
|
||||
title,
|
||||
...otherOptions,
|
||||
...rawOptions,
|
||||
map: this.map,
|
||||
});
|
||||
|
||||
if (infoWindow) {
|
||||
this.createInfoWindow({ definition: infoWindow, marker });
|
||||
}
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
protected doCreateInfoWindow({
|
||||
definition,
|
||||
marker,
|
||||
}: {
|
||||
definition: MarkerDefinition<
|
||||
google.maps.marker.AdvancedMarkerElementOptions,
|
||||
google.maps.InfoWindowOptions
|
||||
>['infoWindow'];
|
||||
marker: google.maps.marker.AdvancedMarkerElement;
|
||||
}): google.maps.InfoWindow {
|
||||
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
|
||||
|
||||
const infoWindow = new library.InfoWindow({
|
||||
headerContent: this.createTextOrElement(headerContent),
|
||||
content: this.createTextOrElement(content),
|
||||
...otherOptions,
|
||||
...rawOptions,
|
||||
});
|
||||
|
||||
if (definition.opened) {
|
||||
infoWindow.open({
|
||||
map: this.map,
|
||||
shouldFocus: false,
|
||||
anchor: marker,
|
||||
});
|
||||
}
|
||||
|
||||
marker.addListener('click', () => {
|
||||
if (definition.autoClose) {
|
||||
this.closeInfoWindowsExcept(infoWindow);
|
||||
}
|
||||
|
||||
infoWindow.open({
|
||||
map: this.map,
|
||||
anchor: marker,
|
||||
});
|
||||
});
|
||||
|
||||
return infoWindow;
|
||||
}
|
||||
|
||||
private createTextOrElement(content: string | null): string | HTMLElement | null {
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we assume it's HTML if it includes "<"
|
||||
if (content.includes('<')) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = content;
|
||||
return div;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private closeInfoWindowsExcept(infoWindow: google.maps.InfoWindow) {
|
||||
this.infoWindows.forEach((otherInfoWindow) => {
|
||||
if (otherInfoWindow !== infoWindow) {
|
||||
otherInfoWindow.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected doFitBoundsToMarkers(): void {
|
||||
if (this.markers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = new google.maps.LatLngBounds();
|
||||
this.markers.forEach((marker) => {
|
||||
if (!marker.position) {
|
||||
return;
|
||||
}
|
||||
|
||||
bounds.extend(marker.position);
|
||||
});
|
||||
|
||||
this.map.fitBounds(bounds);
|
||||
}
|
||||
}
|
||||
62
assets/test/map_controller.test.ts
Normal file
62
assets/test/map_controller.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { Application, Controller } from '@hotwired/stimulus';
|
||||
import { getByTestId, waitFor } from '@testing-library/dom';
|
||||
import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
|
||||
import GoogleController from '../src/map_controller';
|
||||
|
||||
// Controller used to check the actual controller was properly booted
|
||||
class CheckController extends Controller {
|
||||
connect() {
|
||||
this.element.addEventListener('ux:map:pre-connect', (event) => {
|
||||
this.element.classList.add('pre-connected');
|
||||
});
|
||||
|
||||
this.element.addEventListener('ux:map:connect', (event) => {
|
||||
this.element.classList.add('connected');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const startStimulus = () => {
|
||||
const application = Application.start();
|
||||
application.register('check', CheckController);
|
||||
application.register('google', GoogleController);
|
||||
};
|
||||
|
||||
describe('GoogleMapsController', () => {
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = mountDOM(`
|
||||
<div
|
||||
data-testid="map"
|
||||
data-controller="check google"
|
||||
style="height: 700px; margin: 10px"
|
||||
data-google-provider-options-value="{"language":"fr","region":"FR","retries":10,"version":"weekly","apiKey":""}"
|
||||
data-google-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":4,"fitBoundsToMarkers":true,"options":{"mapId":"YOUR_MAP_ID","gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControl":true,"zoomControlOptions":{"position":22},"mapTypeControl":true,"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControl":true,"streetViewControlOptions":{"position":22},"fullscreenControl":true,"fullscreenControlOptions":{"position":20}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":45.764,"lng":4.8357},"title":"Lyon","infoWindow":{"headerContent":"<b>Lyon<\/b>","content":"The French town in the historic Rh\u00f4ne-Alpes region, located at the junction of the Rh\u00f4ne and Sa\u00f4ne rivers.","position":null,"opened":false,"autoClose":true}}]}"
|
||||
></div>
|
||||
`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearDOM();
|
||||
});
|
||||
|
||||
it('connect', async () => {
|
||||
const div = getByTestId(container, 'map');
|
||||
expect(div).not.toHaveClass('pre-connected');
|
||||
expect(div).not.toHaveClass('connected');
|
||||
|
||||
startStimulus();
|
||||
await waitFor(() => expect(div).toHaveClass('pre-connected'));
|
||||
await waitFor(() => expect(div).toHaveClass('connected'));
|
||||
});
|
||||
});
|
||||
17
assets/vitest.config.js
Normal file
17
assets/vitest.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig, mergeConfig } from 'vitest/config';
|
||||
import configShared from '../../../../../../vitest.config.js'
|
||||
|
||||
export default mergeConfig(
|
||||
configShared,
|
||||
defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@symfony/ux-map/abstract-map-controller': __dirname + '/../../../../assets/src/abstract_map_controller.ts',
|
||||
},
|
||||
},
|
||||
test: {
|
||||
// We need a browser(-like) environment to run the tests
|
||||
environment: 'happy-dom',
|
||||
},
|
||||
})
|
||||
);
|
||||
33
composer.json
Normal file
33
composer.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "symfony/ux-map-google",
|
||||
"type": "symfony-ux-map-bridge",
|
||||
"description": "Symfony UX Map GoogleMaps Bridge",
|
||||
"keywords": ["google-maps", "map", "symfony", "ux"],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Hugo Alliaume",
|
||||
"email": "hugo@alliau.me"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"symfony/ux-map": "^2.19"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^6.4|^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\UX\\Map\\Bridge\\Google\\": "src/" },
|
||||
"exclude-from-classmap": []
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Symfony\\UX\\Map\\Bridge\\Google\\Tests\\": "tests/" }
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
26
phpunit.xml.dist
Normal file
26
phpunit.xml.dist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/bin/.phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true"
|
||||
>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1"/>
|
||||
<server name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&max[direct]=0"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Test Suite">
|
||||
<directory>./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
157
src/GoogleOptions.php
Normal file
157
src/GoogleOptions.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?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\Bridge\Google;
|
||||
|
||||
use Symfony\UX\Map\Bridge\Google\Option\FullscreenControlOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\StreetViewControlOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ZoomControlOptions;
|
||||
use Symfony\UX\Map\MapOptionsInterface;
|
||||
|
||||
/**
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
final class GoogleOptions implements MapOptionsInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ?string $mapId = null,
|
||||
private GestureHandling $gestureHandling = GestureHandling::AUTO,
|
||||
private ?string $backgroundColor = null,
|
||||
private bool $disableDoubleClickZoom = false,
|
||||
private bool $zoomControl = true,
|
||||
private ZoomControlOptions $zoomControlOptions = new ZoomControlOptions(),
|
||||
private bool $mapTypeControl = true,
|
||||
private MapTypeControlOptions $mapTypeControlOptions = new MapTypeControlOptions(),
|
||||
private bool $streetViewControl = true,
|
||||
private StreetViewControlOptions $streetViewControlOptions = new StreetViewControlOptions(),
|
||||
private bool $fullscreenControl = true,
|
||||
private FullscreenControlOptions $fullscreenControlOptions = new FullscreenControlOptions(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function mapId(?string $mapId): self
|
||||
{
|
||||
$this->mapId = $mapId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function gestureHandling(GestureHandling $gestureHandling): self
|
||||
{
|
||||
$this->gestureHandling = $gestureHandling;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function backgroundColor(?string $backgroundColor): self
|
||||
{
|
||||
$this->backgroundColor = $backgroundColor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function doubleClickZoom(bool $enable = true): self
|
||||
{
|
||||
$this->disableDoubleClickZoom = !$enable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function zoomControl(bool $enable = true): self
|
||||
{
|
||||
$this->zoomControl = $enable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function zoomControlOptions(ZoomControlOptions $zoomControlOptions): self
|
||||
{
|
||||
$this->zoomControl = true;
|
||||
$this->zoomControlOptions = $zoomControlOptions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function mapTypeControl(bool $enable = true): self
|
||||
{
|
||||
$this->mapTypeControl = $enable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function mapTypeControlOptions(MapTypeControlOptions $mapTypeControlOptions): self
|
||||
{
|
||||
$this->mapTypeControl = true;
|
||||
$this->mapTypeControlOptions = $mapTypeControlOptions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function streetViewControl(bool $enable = true): self
|
||||
{
|
||||
$this->streetViewControl = $enable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function streetViewControlOptions(StreetViewControlOptions $streetViewControlOptions): self
|
||||
{
|
||||
$this->streetViewControl = true;
|
||||
$this->streetViewControlOptions = $streetViewControlOptions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fullscreenControl(bool $enable = true): self
|
||||
{
|
||||
$this->fullscreenControl = $enable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fullscreenControlOptions(FullscreenControlOptions $fullscreenControlOptions): self
|
||||
{
|
||||
$this->fullscreenControl = true;
|
||||
$this->fullscreenControlOptions = $fullscreenControlOptions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = [
|
||||
'mapId' => $this->mapId,
|
||||
'gestureHandling' => $this->gestureHandling->value,
|
||||
'backgroundColor' => $this->backgroundColor,
|
||||
'disableDoubleClickZoom' => $this->disableDoubleClickZoom,
|
||||
];
|
||||
|
||||
if ($this->zoomControl) {
|
||||
$array['zoomControlOptions'] = $this->zoomControlOptions->toArray();
|
||||
}
|
||||
|
||||
if ($this->mapTypeControl) {
|
||||
$array['mapTypeControlOptions'] = $this->mapTypeControlOptions->toArray();
|
||||
}
|
||||
|
||||
if ($this->streetViewControl) {
|
||||
$array['streetViewControlOptions'] = $this->streetViewControlOptions->toArray();
|
||||
}
|
||||
|
||||
if ($this->fullscreenControl) {
|
||||
$array['fullscreenControlOptions'] = $this->fullscreenControlOptions->toArray();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
80
src/Option/ControlPosition.php
Normal file
80
src/Option/ControlPosition.php
Normal file
@@ -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\Bridge\Google\Option;
|
||||
|
||||
/**
|
||||
* @see https://developers.google.com/maps/documentation/javascript/reference/control#ControlPosition
|
||||
*
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
enum ControlPosition: int
|
||||
{
|
||||
/**
|
||||
* Equivalent to bottom-center in both LTR and RTL.
|
||||
*/
|
||||
case BLOCK_END_INLINE_CENTER = 24;
|
||||
|
||||
/**
|
||||
* Equivalent to bottom-right in LTR, or bottom-left in RTL.
|
||||
*/
|
||||
case BLOCK_END_INLINE_END = 25;
|
||||
|
||||
/**
|
||||
* Equivalent to bottom-left in LTR, or bottom-right in RTL.
|
||||
*/
|
||||
case BLOCK_END_INLINE_START = 23;
|
||||
|
||||
/**
|
||||
* Equivalent to top-center in both LTR and RTL.
|
||||
*/
|
||||
case BLOCK_START_INLINE_CENTER = 15;
|
||||
|
||||
/**
|
||||
* Equivalent to top-right in LTR, or top-left in RTL.
|
||||
*/
|
||||
case BLOCK_START_INLINE_END = 16;
|
||||
|
||||
/**
|
||||
* Equivalent to top-left in LTR, or top-right in RTL.
|
||||
*/
|
||||
case BLOCK_START_INLINE_START = 14;
|
||||
|
||||
/**
|
||||
* Equivalent to right-center in LTR, or left-center in RTL.
|
||||
*/
|
||||
case INLINE_END_BLOCK_CENTER = 21;
|
||||
|
||||
/**
|
||||
* Equivalent to right-bottom in LTR, or left-bottom in RTL.
|
||||
*/
|
||||
case INLINE_END_BLOCK_END = 22;
|
||||
|
||||
/**
|
||||
* Equivalent to right-top in LTR, or left-top in RTL.
|
||||
*/
|
||||
case INLINE_END_BLOCK_START = 20;
|
||||
|
||||
/**
|
||||
* Equivalent to left-center in LTR, or right-center in RTL.
|
||||
*/
|
||||
case INLINE_START_BLOCK_CENTER = 17;
|
||||
|
||||
/**
|
||||
* Equivalent to left-bottom in LTR, or right-bottom in RTL.
|
||||
*/
|
||||
case INLINE_START_BLOCK_END = 19;
|
||||
|
||||
/**
|
||||
* Equivalent to left-top in LTR, or right-top in RTL.
|
||||
*/
|
||||
case INLINE_START_BLOCK_START = 18;
|
||||
}
|
||||
34
src/Option/FullscreenControlOptions.php
Normal file
34
src/Option/FullscreenControlOptions.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\Bridge\Google\Option;
|
||||
|
||||
/**
|
||||
* Options for the rendering of the fullscreen control.
|
||||
*
|
||||
* @see https://developers.google.com/maps/documentation/javascript/reference/control#FullscreenControlOptions
|
||||
*
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
final readonly class FullscreenControlOptions
|
||||
{
|
||||
public function __construct(
|
||||
private ControlPosition $position = ControlPosition::INLINE_END_BLOCK_START,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'position' => $this->position->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
45
src/Option/GestureHandling.php
Normal file
45
src/Option/GestureHandling.php
Normal file
@@ -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\Bridge\Google\Option;
|
||||
|
||||
/**
|
||||
* This setting controls how the API handles gestures on the map.
|
||||
*
|
||||
* @see https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.gestureHandling
|
||||
*
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
enum GestureHandling: string
|
||||
{
|
||||
/**
|
||||
* Scroll events and one-finger touch gestures scroll the page, and do not zoom or pan the map.
|
||||
* Two-finger touch gestures pan and zoom the map.
|
||||
* Scroll events with a ctrl key or ⌘ key pressed zoom the map.
|
||||
* In this mode the map cooperates with the page.
|
||||
*/
|
||||
case COOPERATIVE = 'cooperative';
|
||||
|
||||
/**
|
||||
* All touch gestures and scroll events pan or zoom the map.
|
||||
*/
|
||||
case GREEDY = 'greedy';
|
||||
|
||||
/**
|
||||
* The map cannot be panned or zoomed by user gestures.
|
||||
*/
|
||||
case NONE = 'none';
|
||||
|
||||
/**
|
||||
* Gesture handling is either cooperative or greedy, depending on whether the page is scrollable or in an iframe.
|
||||
*/
|
||||
case AUTO = 'auto';
|
||||
}
|
||||
41
src/Option/MapTypeControlOptions.php
Normal file
41
src/Option/MapTypeControlOptions.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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\Bridge\Google\Option;
|
||||
|
||||
/**
|
||||
* Options for the rendering of the map type control.
|
||||
*
|
||||
* @see https://developers.google.com/maps/documentation/javascript/reference/control#MapTypeControlOptions
|
||||
*
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
final readonly class MapTypeControlOptions
|
||||
{
|
||||
/**
|
||||
* @param array<'hybrid'|'roadmap'|'satellite'|'terrain'|string> $mapTypeIds
|
||||
*/
|
||||
public function __construct(
|
||||
private array $mapTypeIds = [],
|
||||
private ControlPosition $position = ControlPosition::BLOCK_START_INLINE_START,
|
||||
private MapTypeControlStyle $style = MapTypeControlStyle::DEFAULT,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'mapTypeIds' => $this->mapTypeIds,
|
||||
'position' => $this->position->value,
|
||||
'style' => $this->style->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
38
src/Option/MapTypeControlStyle.php
Normal file
38
src/Option/MapTypeControlStyle.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\Bridge\Google\Option;
|
||||
|
||||
/**
|
||||
* Identifiers for common MapTypesControls.
|
||||
*
|
||||
* @see https://developers.google.com/maps/documentation/javascript/reference/control#MapTypeControlStyle
|
||||
*
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
enum MapTypeControlStyle: int
|
||||
{
|
||||
/**
|
||||
* Uses the default map type control. When the DEFAULT control is shown, it will vary according to window size and other factors.
|
||||
* The DEFAULT control may change in future versions of the API.
|
||||
*/
|
||||
case DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* A dropdown menu for the screen realestate conscious.
|
||||
*/
|
||||
case DROPDOWN_MENU = 2;
|
||||
|
||||
/**
|
||||
* The standard horizontal radio buttons bar.
|
||||
*/
|
||||
case HORIZONTAL_BAR = 1;
|
||||
}
|
||||
34
src/Option/StreetViewControlOptions.php
Normal file
34
src/Option/StreetViewControlOptions.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\Bridge\Google\Option;
|
||||
|
||||
/**
|
||||
* Options for the rendering of the Street View pegman control on the map.
|
||||
*
|
||||
* @see https://developers.google.com/maps/documentation/javascript/reference/control#StreetViewControlOptions
|
||||
*
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
final readonly class StreetViewControlOptions
|
||||
{
|
||||
public function __construct(
|
||||
private ControlPosition $position = ControlPosition::INLINE_END_BLOCK_END,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'position' => $this->position->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
34
src/Option/ZoomControlOptions.php
Normal file
34
src/Option/ZoomControlOptions.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\Bridge\Google\Option;
|
||||
|
||||
/**
|
||||
* Options for the rendering of the zoom control.
|
||||
*
|
||||
* @see https://developers.google.com/maps/documentation/javascript/reference/control#ZoomControlOptions
|
||||
*
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
final readonly class ZoomControlOptions
|
||||
{
|
||||
public function __construct(
|
||||
private ControlPosition $position = ControlPosition::INLINE_END_BLOCK_END,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'position' => $this->position->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
83
src/Renderer/GoogleRenderer.php
Normal file
83
src/Renderer/GoogleRenderer.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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\Bridge\Google\Renderer;
|
||||
|
||||
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
|
||||
use Symfony\UX\Map\MapOptionsInterface;
|
||||
use Symfony\UX\Map\Renderer\AbstractRenderer;
|
||||
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
|
||||
|
||||
/**
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class GoogleRenderer extends AbstractRenderer
|
||||
{
|
||||
/**
|
||||
* Parameters are based from https://googlemaps.github.io/js-api-loader/interfaces/LoaderOptions.html documentation.
|
||||
*/
|
||||
public function __construct(
|
||||
StimulusHelper $stimulusHelper,
|
||||
#[\SensitiveParameter]
|
||||
private string $apiKey,
|
||||
private ?string $id = null,
|
||||
private ?string $language = null,
|
||||
private ?string $region = null,
|
||||
private ?string $nonce = null,
|
||||
private ?int $retries = null,
|
||||
private ?string $url = null,
|
||||
private ?string $version = null,
|
||||
) {
|
||||
parent::__construct($stimulusHelper);
|
||||
}
|
||||
|
||||
protected function getName(): string
|
||||
{
|
||||
return 'google';
|
||||
}
|
||||
|
||||
protected function getProviderOptions(): array
|
||||
{
|
||||
return array_filter([
|
||||
'id' => $this->id,
|
||||
'language' => $this->language,
|
||||
'region' => $this->region,
|
||||
'nonce' => $this->nonce,
|
||||
'retries' => $this->retries,
|
||||
'url' => $this->url,
|
||||
'version' => $this->version,
|
||||
]) + ['apiKey' => $this->apiKey];
|
||||
}
|
||||
|
||||
protected function getDefaultMapOptions(): MapOptionsInterface
|
||||
{
|
||||
return new GoogleOptions();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return \sprintf(
|
||||
'google://%s@default/?%s',
|
||||
str_repeat('*', \strlen($this->apiKey)),
|
||||
http_build_query(array_filter([
|
||||
'id' => $this->id,
|
||||
'language' => $this->language,
|
||||
'region' => $this->region,
|
||||
'nonce' => $this->nonce,
|
||||
'retries' => $this->retries,
|
||||
'url' => $this->url,
|
||||
'version' => $this->version,
|
||||
]))
|
||||
);
|
||||
}
|
||||
}
|
||||
51
src/Renderer/GoogleRendererFactory.php
Normal file
51
src/Renderer/GoogleRendererFactory.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Bridge\Google\Renderer;
|
||||
|
||||
use Symfony\UX\Map\Exception\InvalidArgumentException;
|
||||
use Symfony\UX\Map\Exception\UnsupportedSchemeException;
|
||||
use Symfony\UX\Map\Renderer\AbstractRendererFactory;
|
||||
use Symfony\UX\Map\Renderer\Dsn;
|
||||
use Symfony\UX\Map\Renderer\RendererFactoryInterface;
|
||||
use Symfony\UX\Map\Renderer\RendererInterface;
|
||||
|
||||
/**
|
||||
* @author Hugo Alliaume <hugo@alliau.me>
|
||||
*/
|
||||
final class GoogleRendererFactory extends AbstractRendererFactory implements RendererFactoryInterface
|
||||
{
|
||||
public function create(Dsn $dsn): RendererInterface
|
||||
{
|
||||
if (!$this->supports($dsn)) {
|
||||
throw new UnsupportedSchemeException($dsn);
|
||||
}
|
||||
|
||||
$apiKey = $dsn->getUser() ?: throw new InvalidArgumentException('The Google Maps renderer requires an API key as the user part of the DSN.');
|
||||
|
||||
return new GoogleRenderer(
|
||||
$this->stimulus,
|
||||
$apiKey,
|
||||
id: $dsn->getOption('id'),
|
||||
language: $dsn->getOption('language'),
|
||||
region: $dsn->getOption('region'),
|
||||
nonce: $dsn->getOption('nonce'),
|
||||
retries: $dsn->getOption('retries'),
|
||||
url: $dsn->getOption('url'),
|
||||
version: $dsn->getOption('version', 'weekly'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getSupportedSchemes(): array
|
||||
{
|
||||
return ['google'];
|
||||
}
|
||||
}
|
||||
68
tests/GoogleOptionsTest.php
Normal file
68
tests/GoogleOptionsTest.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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\Bridge\Google\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
|
||||
|
||||
class GoogleOptionsTest extends TestCase
|
||||
{
|
||||
public function testWithMinimalConfiguration(): void
|
||||
{
|
||||
$options = new GoogleOptions();
|
||||
|
||||
self::assertSame([
|
||||
'mapId' => null,
|
||||
'gestureHandling' => 'auto',
|
||||
'backgroundColor' => null,
|
||||
'disableDoubleClickZoom' => false,
|
||||
'zoomControlOptions' => [
|
||||
'position' => ControlPosition::INLINE_END_BLOCK_END->value,
|
||||
],
|
||||
'mapTypeControlOptions' => [
|
||||
'mapTypeIds' => [],
|
||||
'position' => ControlPosition::BLOCK_START_INLINE_START->value,
|
||||
'style' => MapTypeControlStyle::DEFAULT->value,
|
||||
],
|
||||
'streetViewControlOptions' => [
|
||||
'position' => ControlPosition::INLINE_END_BLOCK_END->value,
|
||||
],
|
||||
'fullscreenControlOptions' => [
|
||||
'position' => ControlPosition::INLINE_END_BLOCK_START->value,
|
||||
],
|
||||
], $options->toArray());
|
||||
}
|
||||
|
||||
public function testWithMinimalConfigurationAndWithoutControls(): void
|
||||
{
|
||||
$options = new GoogleOptions(
|
||||
mapId: '2b2d73ba4b8c7b41',
|
||||
gestureHandling: GestureHandling::GREEDY,
|
||||
backgroundColor: '#f00',
|
||||
disableDoubleClickZoom: true,
|
||||
zoomControl: false,
|
||||
mapTypeControl: false,
|
||||
streetViewControl: false,
|
||||
fullscreenControl: false,
|
||||
);
|
||||
|
||||
self::assertSame([
|
||||
'mapId' => '2b2d73ba4b8c7b41',
|
||||
'gestureHandling' => GestureHandling::GREEDY->value,
|
||||
'backgroundColor' => '#f00',
|
||||
'disableDoubleClickZoom' => true,
|
||||
], $options->toArray());
|
||||
}
|
||||
}
|
||||
49
tests/GoogleRendererFactoryTest.php
Normal file
49
tests/GoogleRendererFactoryTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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\Bridge\Google\Tests;
|
||||
|
||||
use Symfony\UX\Map\Bridge\Google\Renderer\GoogleRendererFactory;
|
||||
use Symfony\UX\Map\Renderer\RendererFactoryInterface;
|
||||
use Symfony\UX\Map\Test\RendererFactoryTestCase;
|
||||
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
|
||||
|
||||
final class GoogleRendererFactoryTest extends RendererFactoryTestCase
|
||||
{
|
||||
public function createRendererFactory(): RendererFactoryInterface
|
||||
{
|
||||
return new GoogleRendererFactory(new StimulusHelper(null));
|
||||
}
|
||||
|
||||
public static function supportsRenderer(): iterable
|
||||
{
|
||||
yield [true, 'google://GOOGLE_MAPS_API_KEY@default'];
|
||||
yield [false, 'somethingElse://login:apiKey@default'];
|
||||
}
|
||||
|
||||
public static function createRenderer(): iterable
|
||||
{
|
||||
yield [
|
||||
'google://*******************@default/?version=weekly',
|
||||
'google://GOOGLE_MAPS_API_KEY@default',
|
||||
];
|
||||
|
||||
yield [
|
||||
'google://*******************@default/?version=quartly',
|
||||
'google://GOOGLE_MAPS_API_KEY@default?version=quartly',
|
||||
];
|
||||
}
|
||||
|
||||
public static function unsupportedSchemeRenderer(): iterable
|
||||
{
|
||||
yield ['somethingElse://foo@default'];
|
||||
}
|
||||
}
|
||||
75
tests/GoogleRendererTest.php
Normal file
75
tests/GoogleRendererTest.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?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\Bridge\Google\Tests;
|
||||
|
||||
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Renderer\GoogleRenderer;
|
||||
use Symfony\UX\Map\InfoWindow;
|
||||
use Symfony\UX\Map\Map;
|
||||
use Symfony\UX\Map\Marker;
|
||||
use Symfony\UX\Map\Point;
|
||||
use Symfony\UX\Map\Test\RendererTestCase;
|
||||
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
|
||||
|
||||
class GoogleRendererTest extends RendererTestCase
|
||||
{
|
||||
public function provideTestRenderMap(): iterable
|
||||
{
|
||||
$map = (new Map())
|
||||
->center(new Point(48.8566, 2.3522))
|
||||
->zoom(12);
|
||||
|
||||
yield 'simple map, with minimum options' => [
|
||||
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="{"apiKey":"api_key"}" data-symfony--ux-map-google--map-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":12,"fitBoundsToMarkers":false,"options":{"mapId":null,"gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControlOptions":{"position":22},"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControlOptions":{"position":22},"fullscreenControlOptions":{"position":20}},"markers":[]}"></div>',
|
||||
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
|
||||
'map' => $map,
|
||||
];
|
||||
|
||||
yield 'with every options' => [
|
||||
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="{"id":"gmap","language":"fr","region":"FR","nonce":"abcd","retries":10,"url":"https:\/\/maps.googleapis.com\/maps\/api\/js","version":"quarterly","apiKey":"api_key"}" data-symfony--ux-map-google--map-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":12,"fitBoundsToMarkers":false,"options":{"mapId":null,"gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControlOptions":{"position":22},"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControlOptions":{"position":22},"fullscreenControlOptions":{"position":20}},"markers":[]}"></div>',
|
||||
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key', id: 'gmap', language: 'fr', region: 'FR', nonce: 'abcd', retries: 10, url: 'https://maps.googleapis.com/maps/api/js', version: 'quarterly'),
|
||||
'map' => $map,
|
||||
];
|
||||
|
||||
yield 'with markers and infoWindows' => [
|
||||
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="{"apiKey":"api_key"}" data-symfony--ux-map-google--map-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":12,"fitBoundsToMarkers":false,"options":{"mapId":null,"gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControlOptions":{"position":22},"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControlOptions":{"position":22},"fullscreenControlOptions":{"position":20}},"markers":[{"position":{"lat":48.8566,"lng":2.3522},"title":"Paris","infoWindow":null},{"position":{"lat":48.8566,"lng":2.3522},"title":"Lyon","infoWindow":{"headerContent":null,"content":"Lyon","position":null,"opened":false,"autoClose":true}}]}"></div>',
|
||||
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
|
||||
'map' => (clone $map)
|
||||
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
|
||||
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Lyon', infoWindow: new InfoWindow(content: 'Lyon'))),
|
||||
];
|
||||
|
||||
yield 'with controls enabled' => [
|
||||
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="{"apiKey":"api_key"}" data-symfony--ux-map-google--map-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":12,"fitBoundsToMarkers":false,"options":{"mapId":null,"gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false,"zoomControlOptions":{"position":22},"mapTypeControlOptions":{"mapTypeIds":[],"position":14,"style":0},"streetViewControlOptions":{"position":22},"fullscreenControlOptions":{"position":20}},"markers":[]}"></div>',
|
||||
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
|
||||
'map' => (clone $map)
|
||||
->options(new GoogleOptions(
|
||||
zoomControl: true,
|
||||
mapTypeControl: true,
|
||||
streetViewControl: true,
|
||||
fullscreenControl: true,
|
||||
)),
|
||||
];
|
||||
|
||||
yield 'without controls enabled' => [
|
||||
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="{"apiKey":"api_key"}" data-symfony--ux-map-google--map-view-value="{"center":{"lat":48.8566,"lng":2.3522},"zoom":12,"fitBoundsToMarkers":false,"options":{"mapId":null,"gestureHandling":"auto","backgroundColor":null,"disableDoubleClickZoom":false},"markers":[]}"></div>',
|
||||
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
|
||||
'map' => (clone $map)
|
||||
->options(new GoogleOptions(
|
||||
zoomControl: false,
|
||||
mapTypeControl: false,
|
||||
streetViewControl: false,
|
||||
fullscreenControl: false,
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
34
tests/Option/ControlPositionTest.php
Normal file
34
tests/Option/ControlPositionTest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\Bridge\Google\Tests\Option;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
|
||||
|
||||
class ControlPositionTest extends TestCase
|
||||
{
|
||||
public function testEnumValues(): void
|
||||
{
|
||||
self::assertSame(24, ControlPosition::BLOCK_END_INLINE_CENTER->value);
|
||||
self::assertSame(25, ControlPosition::BLOCK_END_INLINE_END->value);
|
||||
self::assertSame(23, ControlPosition::BLOCK_END_INLINE_START->value);
|
||||
self::assertSame(15, ControlPosition::BLOCK_START_INLINE_CENTER->value);
|
||||
self::assertSame(16, ControlPosition::BLOCK_START_INLINE_END->value);
|
||||
self::assertSame(14, ControlPosition::BLOCK_START_INLINE_START->value);
|
||||
self::assertSame(21, ControlPosition::INLINE_END_BLOCK_CENTER->value);
|
||||
self::assertSame(22, ControlPosition::INLINE_END_BLOCK_END->value);
|
||||
self::assertSame(20, ControlPosition::INLINE_END_BLOCK_START->value);
|
||||
self::assertSame(17, ControlPosition::INLINE_START_BLOCK_CENTER->value);
|
||||
self::assertSame(19, ControlPosition::INLINE_START_BLOCK_END->value);
|
||||
self::assertSame(18, ControlPosition::INLINE_START_BLOCK_START->value);
|
||||
}
|
||||
}
|
||||
30
tests/Option/FullscreenControlOptionsTest.php
Normal file
30
tests/Option/FullscreenControlOptionsTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Bridge\Google\Tests\Option;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\FullscreenControlOptions;
|
||||
|
||||
class FullscreenControlOptionsTest extends TestCase
|
||||
{
|
||||
public function testToArray(): void
|
||||
{
|
||||
$options = new FullscreenControlOptions(
|
||||
position: ControlPosition::BLOCK_END_INLINE_CENTER
|
||||
);
|
||||
|
||||
self::assertSame([
|
||||
'position' => ControlPosition::BLOCK_END_INLINE_CENTER->value,
|
||||
], $options->toArray());
|
||||
}
|
||||
}
|
||||
26
tests/Option/GestureHandlingTest.php
Normal file
26
tests/Option/GestureHandlingTest.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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\Bridge\Google\Tests\Option;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
|
||||
|
||||
class GestureHandlingTest extends TestCase
|
||||
{
|
||||
public function testEnumValues(): void
|
||||
{
|
||||
self::assertSame('cooperative', GestureHandling::COOPERATIVE->value);
|
||||
self::assertSame('greedy', GestureHandling::GREEDY->value);
|
||||
self::assertSame('none', GestureHandling::NONE->value);
|
||||
self::assertSame('auto', GestureHandling::AUTO->value);
|
||||
}
|
||||
}
|
||||
35
tests/Option/MapTypeControlOptionsTest.php
Normal file
35
tests/Option/MapTypeControlOptionsTest.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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\Bridge\Google\Tests\Option;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlOptions;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
|
||||
|
||||
class MapTypeControlOptionsTest extends TestCase
|
||||
{
|
||||
public function testToArray(): void
|
||||
{
|
||||
$options = new MapTypeControlOptions(
|
||||
mapTypeIds: ['satellite', 'hybrid'],
|
||||
position: ControlPosition::BLOCK_END_INLINE_END,
|
||||
style: MapTypeControlStyle::HORIZONTAL_BAR,
|
||||
);
|
||||
|
||||
self::assertSame([
|
||||
'mapTypeIds' => ['satellite', 'hybrid'],
|
||||
'position' => ControlPosition::BLOCK_END_INLINE_END->value,
|
||||
'style' => MapTypeControlStyle::HORIZONTAL_BAR->value,
|
||||
], $options->toArray());
|
||||
}
|
||||
}
|
||||
25
tests/Option/MapTypeControlStyleTest.php
Normal file
25
tests/Option/MapTypeControlStyleTest.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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\Bridge\Google\Tests\Option;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
|
||||
|
||||
class MapTypeControlStyleTest extends TestCase
|
||||
{
|
||||
public function testEnumValues(): void
|
||||
{
|
||||
self::assertSame(0, MapTypeControlStyle::DEFAULT->value);
|
||||
self::assertSame(2, MapTypeControlStyle::DROPDOWN_MENU->value);
|
||||
self::assertSame(1, MapTypeControlStyle::HORIZONTAL_BAR->value);
|
||||
}
|
||||
}
|
||||
30
tests/Option/StreetViewControlOptionsTest.php
Normal file
30
tests/Option/StreetViewControlOptionsTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Bridge\Google\Tests\Option;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\StreetViewControlOptions;
|
||||
|
||||
class StreetViewControlOptionsTest extends TestCase
|
||||
{
|
||||
public function testToArray(): void
|
||||
{
|
||||
$options = new StreetViewControlOptions(
|
||||
position: ControlPosition::INLINE_END_BLOCK_CENTER
|
||||
);
|
||||
|
||||
self::assertSame([
|
||||
'position' => ControlPosition::INLINE_END_BLOCK_CENTER->value,
|
||||
], $options->toArray());
|
||||
}
|
||||
}
|
||||
30
tests/Option/ZoomControlOptionsTest.php
Normal file
30
tests/Option/ZoomControlOptionsTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Bridge\Google\Tests\Option;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
|
||||
use Symfony\UX\Map\Bridge\Google\Option\ZoomControlOptions;
|
||||
|
||||
class ZoomControlOptionsTest extends TestCase
|
||||
{
|
||||
public function testToArray(): void
|
||||
{
|
||||
$options = new ZoomControlOptions(
|
||||
position: ControlPosition::BLOCK_START_INLINE_END,
|
||||
);
|
||||
|
||||
self::assertSame([
|
||||
'position' => ControlPosition::BLOCK_START_INLINE_END->value,
|
||||
], $options->toArray());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user