HTML-OSM
view release on metacpan or search on metacpan
t/function.t view on Meta::CPAN
is($m->center([999, 0]), 0, 'out-of-range returns 0');
ok(!$m->{center}, 'center not set on failure');
};
subtest 'center: geo object with latitude()/longitude() methods' => sub {
my $m = HTML::OSM->new();
mock 'MockCtrObj::latitude' => sub { $CFG{LAT_PARIS} };
mock 'MockCtrObj::longitude' => sub { $CFG{LON_PARIS} };
is($m->center(bless {}, 'MockCtrObj'), 1, 'geo object center succeeds');
is_deeply($m->{center}, [$CFG{LAT_PARIS}, $CFG{LON_PARIS}], 'stored');
};
subtest 'center: string address resolved via geocoder' => sub {
my $m = HTML::OSM->new();
mock 'MockCtrGeo::geocode' => sub { { lat => $CFG{LAT_NYC}, lon => $CFG{LON_NYC} } };
$m->{geocoder} = bless {}, 'MockCtrGeo';
is($m->center('New York'), 1, 'string center geocoded');
is_deeply($m->{center}, [$CFG{LAT_NYC}, $CFG{LON_NYC}], 'stored');
};
subtest 'center: geocode failure returns 0' => sub {
my $m = HTML::OSM->new();
mock 'MockCtrFail::geocode' => sub { undef };
$m->{geocoder} = bless {}, 'MockCtrFail';
is($m->center('Nowhere'), 0, 'geocode failure -> 0');
};
subtest 'center: unknown ref type croaks with message' => sub {
my $m = HTML::OSM->new();
my $bad = bless {}, 'UnknownCenter';
throws_ok { $m->center($bad) }
qr/center\(\): unknown point type: UnknownCenter/,
'exact croak message';
};
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# add_geojson()
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
subtest 'add_geojson: pre-parsed hashref stored, returns 1' => sub {
my $m = HTML::OSM->new();
my $data = { type => 'FeatureCollection', features => [] };
is($m->add_geojson($data), 1, 'returns 1');
is(scalar @{$m->{geojson}}, 1, 'one layer');
is($m->{geojson}[0]{data}, $data, 'same ref stored');
returns_ok(1, { type => 'integer' }, 'add_geojson returns integer');
};
subtest 'add_geojson: JSON string is decoded in-place' => sub {
my $m = HTML::OSM->new();
$m->add_geojson('{"type":"FeatureCollection","features":[]}');
is(ref($m->{geojson}[0]{data}), 'HASH', 'decoded to hashref');
is($m->{geojson}[0]{data}{type}, 'FeatureCollection', 'content correct');
};
subtest 'add_geojson: invalid JSON dies' => sub {
my $m = HTML::OSM->new();
dies_ok { $m->add_geojson('not json at all') } 'invalid JSON dies';
};
subtest 'add_geojson: style and popup opts stored' => sub {
my $m = HTML::OSM->new();
$m->add_geojson({ type => 'FeatureCollection', features => [] },
style => { color => '#ff0000' }, popup => 'name');
is_deeply($m->{geojson}[0]{opts}{style}, { color => '#ff0000' }, 'style stored');
is($m->{geojson}[0]{opts}{popup}, 'name', 'popup stored');
};
subtest 'add_geojson: multiple calls accumulate layers' => sub {
my $m = HTML::OSM->new();
$m->add_geojson({ type => 'FeatureCollection', features => [] });
$m->add_geojson({ type => 'FeatureCollection', features => [] });
is(scalar @{$m->{geojson}}, 2, 'two layers');
memory_cycle_ok($m->{geojson}, 'no cycles in geojson');
};
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# add_heatmap()
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
subtest 'add_heatmap: valid arrayref stored, returns 1' => sub {
my $m = HTML::OSM->new();
my $points = [[$CFG{LAT_LONDON}, $CFG{LON_LONDON}, 0.8]];
is($m->add_heatmap($points), 1, 'returns 1');
is(scalar @{$m->{heatmap_layers}}, 1, 'one layer');
is($m->{heatmap_layers}[0]{points}, $points, 'same ref stored');
returns_ok(1, { type => 'integer' }, 'returns integer');
};
subtest 'add_heatmap: radius and blur opts stored' => sub {
my $m = HTML::OSM->new();
$m->add_heatmap([[0, 0]], radius => 30, blur => 20);
is($m->{heatmap_layers}[0]{opts}{radius}, 30, 'radius stored');
is($m->{heatmap_layers}[0]{opts}{blur}, 20, 'blur stored');
};
subtest 'add_heatmap: non-arrayref croaks with exact message' => sub {
my $m = HTML::OSM->new();
throws_ok { $m->add_heatmap('string') }
qr/add_heatmap: points must be an arrayref/,
'string croaks';
throws_ok { $m->add_heatmap({ lat => 1 }) }
qr/add_heatmap: points must be an arrayref/,
'hashref croaks';
throws_ok { $m->add_heatmap(undef) }
qr/add_heatmap: points must be an arrayref/,
'undef croaks';
};
subtest 'add_heatmap: multiple layers accumulate' => sub {
my $m = HTML::OSM->new();
$m->add_heatmap([[0, 0]]);
$m->add_heatmap([[1, 1]]);
is(scalar @{$m->{heatmap_layers}}, 2, 'two layers');
memory_cycle_ok($m->{heatmap_layers}, 'no cycles in heatmap_layers');
};
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# add_gpx()
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
subtest 'add_gpx: URL stored, returns 1' => sub {
my $m = HTML::OSM->new();
my $url = 'https://example.com/track.gpx';
is($m->add_gpx($url), 1, 'returns 1');
is(scalar @{$m->{gpx_tracks}}, 1, 'one track');
t/function.t view on Meta::CPAN
my (undef, $body) = $m->onload_render();
like($body, qr/search-box/, 'search box present');
like($body, qr/reset-button/, 'reset button present');
like($body, qr/clear-search-button/, 'clear-search button present');
like($body, qr/searchMarkers/, 'searchMarkers array present');
};
subtest 'onload_render: heatmap plugin injected only when needed' => sub {
my $with = HTML::OSM->new();
$with->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
$with->add_heatmap([[$CFG{LAT_LONDON}, $CFG{LON_LONDON}, 0.9]]);
like(($with->onload_render())[0], qr/leaflet.heat/i, 'plugin in head when used');
my $without = HTML::OSM->new();
$without->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
unlike(($without->onload_render())[0], qr/leaflet.heat/i, 'plugin absent when unused');
};
subtest 'onload_render: GPX plugin injected only when needed' => sub {
my $with = HTML::OSM->new();
$with->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
$with->add_gpx('https://example.com/track.gpx');
like(($with->onload_render())[0], qr/gpx/i, 'GPX plugin in head when used');
my $without = HTML::OSM->new();
$without->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
unlike(($without->onload_render())[0], qr/leaflet-gpx/i, 'GPX plugin absent when unused');
};
subtest 'onload_render: cluster => 1 injects all three cluster assets' => sub {
my $m = HTML::OSM->new(cluster => 1);
$m->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
$m->add_marker([$CFG{LAT_PARIS}, $CFG{LON_PARIS}]);
my ($head, $body) = $m->onload_render();
like($head, qr/markercluster.*\.js/i, 'cluster JS');
like($head, qr/MarkerCluster\.css/, 'cluster CSS');
like($head, qr/MarkerCluster\.Default\.css/, 'cluster default CSS');
like($body, qr/markerClusterGroup/, 'clusterGroup created');
like($body, qr/clusterGroup\.addLayer/, 'markers via addLayer');
like($body, qr/map\.addLayer\(clusterGroup\)/, 'clusterGroup added to map');
};
subtest 'onload_render: without cluster, markers added directly' => sub {
my $m = HTML::OSM->new();
$m->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
my ($head, $body) = $m->onload_render();
unlike($head, qr/markercluster/, 'no cluster assets');
unlike($body, qr/clusterGroup/, 'no clusterGroup');
like($body, qr/addTo\(map\)/, 'marker direct to map');
};
subtest 'onload_render: icon URL uses L.icon in emitted JS' => sub {
my $m = HTML::OSM->new();
$m->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}],
icon => 'https://example.com/pin.png');
my (undef, $body) = $m->onload_render();
like($body, qr/L\.icon/, 'L.icon call present');
like($body, qr|example\.com/pin\.png|, 'icon URL present');
};
subtest 'onload_render: popup label with single quote is JS-escaped' => sub {
# A raw single quote in a label would break the JS string â _js_string must fire.
my $m = HTML::OSM->new();
$m->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}], html => "O'Brien's Pub");
my (undef, $body) = $m->onload_render();
like($body, qr/O\\'Brien/, 'single quote JS-escaped in popup label');
};
subtest 'onload_render: GPX URL with special chars is JS-escaped' => sub {
my $m = HTML::OSM->new();
$m->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
$m->add_gpx("https://example.com/track's.gpx");
my (undef, $body) = $m->onload_render();
like($body, qr/track\\'s\.gpx/, 'GPX URL JS-escaped');
};
subtest 'onload_render: GeoJSON style colour appears in body' => sub {
my $m = HTML::OSM->new();
$m->add_marker([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
$m->add_geojson({ type => 'FeatureCollection', features => [] },
style => { color => '#abcdef' });
my (undef, $body) = $m->onload_render();
like($body, qr/L\.geoJSON/, 'L.geoJSON call present');
like($body, qr/#abcdef/, 'style colour present');
};
subtest 'onload_render: choropleth emits colour lookup and fillColor' => sub {
my $m = HTML::OSM->new();
$m->center([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
$m->add_choropleth(\@FEATURES, { England => 100, Scotland => 50 }, key => 'name');
my (undef, $body) = $m->onload_render();
like($body, qr/choroplethColors/, 'colour lookup emitted');
like($body, qr/fillColor/, 'fillColor style emitted');
like($body, qr/choroplethValues/, 'value lookup emitted');
};
subtest 'onload_render: non-marker-only render with center succeeds' => sub {
my $m = HTML::OSM->new();
$m->center([$CFG{LAT_LONDON}, $CFG{LON_LONDON}]);
$m->add_geojson({ type => 'FeatureCollection', features => [] });
my @r;
lives_ok { @r = $m->onload_render() } 'lives with center + GeoJSON only';
is(scalar @r, 2, 'two-element result');
};
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# _fetch_coordinates()
# White-box via direct method call; all HTTP is intercepted by the global mock.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
subtest '_fetch_coordinates: empty string croaks' => sub {
my $m = HTML::OSM->new();
throws_ok { $m->_fetch_coordinates('') }
qr/address not given to _fetch_coordinates/,
'empty string croaks';
};
subtest '_fetch_coordinates: undef croaks' => sub {
my $m = HTML::OSM->new();
throws_ok { $m->_fetch_coordinates(undef) }
qr/address not given to _fetch_coordinates/,
'undef croaks';
};
subtest '_fetch_coordinates: geocoder returns blessed object with lat/lon methods' => sub {
my $m = HTML::OSM->new();
( run in 0.631 second using v1.01-cache-2.11-cpan-364913b4093 )