Merge pull request #660 from duckduckgo/mwm/sunrise_sunset
SunInfo: add new Instant Answer for sunrise/sunset.master
commit
9386eb5c3d
2
dist.ini
2
dist.ini
|
@ -13,6 +13,7 @@ module = Dist::Zilla::Plugin::UploadToDuckPAN
|
|||
|
||||
[Prereqs]
|
||||
MIME::Base64 = 3.13
|
||||
MIME::Types = 0
|
||||
Roman = 1.23
|
||||
Fortune = 0.2
|
||||
Math::Int2Base = 1.00
|
||||
|
@ -71,6 +72,7 @@ Telephony::CountryDialingCodes = 1.04
|
|||
URI::Escape::XS = 0.12
|
||||
DateTime::Calendar::Chinese = 1.00
|
||||
DateTime::Event::Chinese = 1.00
|
||||
DateTime::Event::Sunrise = 0
|
||||
Geo::Coordinates::DecimalDegrees = 0.09
|
||||
Math::SigFigs = 1.09
|
||||
Bit::Vector = 7.3
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package DDG::Goodie::SunInfo;
|
||||
# ABSTRACT: sunrise and sunset information for the client location
|
||||
|
||||
use DDG::Goodie;
|
||||
with 'DDG::GoodieRole::Dates';
|
||||
with 'DDG::GoodieRole::ImageLoader';
|
||||
|
||||
use DateTime::Event::Sunrise;
|
||||
|
||||
zci answer_type => "sun_info";
|
||||
zci is_cached => 0;
|
||||
|
||||
triggers startend => 'sunrise', 'sunset', 'what time is sunset', 'what time is sunrise';
|
||||
|
||||
primary_example_queries 'sunrise', 'sunset';
|
||||
secondary_example_queries 'sunrise for aug 30', 'sunset on 2015-01-01';
|
||||
description 'Compute the sunrise and sunset for a given day';
|
||||
code_url 'https://github.com/duckduckgo/zeroclickinfo-goodies/blob/master/lib/DDG/Goodie/SunInfo.pm';
|
||||
category 'calculations';
|
||||
topics 'everyday';
|
||||
attribution github => ['https://github.com/duckduckgo', 'duckduckgo'];
|
||||
|
||||
my $time_format = '%l:%M %p';
|
||||
my $datestring_regex = datestring_regex();
|
||||
|
||||
my $sunrise_svg = goodie_img_tag({
|
||||
filename => 'sunrise.svg',
|
||||
height => 48,
|
||||
width => 48,
|
||||
});
|
||||
my $sunset_svg = goodie_img_tag({
|
||||
filename => 'sunset.svg',
|
||||
height => 48,
|
||||
width => 48,
|
||||
});
|
||||
|
||||
handle remainder => sub {
|
||||
|
||||
my $remainder = shift // '';
|
||||
$remainder =~ s/\?//g; # Strip question marks.
|
||||
my ($lat, $lon, $tz) = ($loc->latitude, $loc->longitude, $loc->time_zone);
|
||||
my $where = where_string();
|
||||
return unless (($lat || $lon) && $tz && $where); # We'll need a real location and time zone.
|
||||
my $dt;
|
||||
if (!$remainder) {
|
||||
$dt = DateTime->now;
|
||||
} elsif ($remainder =~ /^(?:on|for)?\s*(?<when>$datestring_regex)$/) {
|
||||
$dt = parse_datestring_to_date($+{'when'});
|
||||
}
|
||||
return unless $dt; # Also going to need to know which day.
|
||||
$dt->set_time_zone($tz);
|
||||
|
||||
my $sun_at_loc = DateTime::Event::Sunrise->new(
|
||||
longitude => $lon,
|
||||
latitude => $lat,
|
||||
precise => 1, # Slower but more precise.
|
||||
silent => 1, # Don't fill up STDERR with noise, if we have trouble.
|
||||
);
|
||||
|
||||
# We don't care for which one they asked, we compute both sunrise and sunset
|
||||
my $sunrise = $sun_at_loc->sunrise_datetime($dt)->strftime($time_format);
|
||||
my $sunset = $sun_at_loc->sunset_datetime($dt)->strftime($time_format);
|
||||
|
||||
return pretty_output($where, date_output_string($dt), $sunrise, $sunset);
|
||||
};
|
||||
|
||||
sub where_string {
|
||||
my @where_bits;
|
||||
if (my $city = $loc->city) {
|
||||
# If we have the city we can abbrev the region or country, if avail.
|
||||
# - Phoenixville, Pennsylvania
|
||||
@where_bits = ($city, $loc->region_name || $loc->country_code3);
|
||||
} elsif (my $region_name = $loc->region_name) {
|
||||
# No city, but a region name; abbreviate the country or continent
|
||||
# - Pennsylvania, USA
|
||||
@where_bits = ($region_name, $loc->country_code3 || $loc->continent_code);
|
||||
} elsif (my $country = $loc->country) {
|
||||
# Country and continent, then, I guess.
|
||||
# United States, NA
|
||||
@where_bits = ($loc->country, $loc->continent_code);
|
||||
}
|
||||
return join(', ', @where_bits);
|
||||
}
|
||||
|
||||
sub pretty_output {
|
||||
my ($where, $when, $rise, $set) = @_;
|
||||
|
||||
$rise =~ s/^\s+//g; # strftime puts a space in front for single-digits.
|
||||
$set =~ s/^\s+//g;
|
||||
|
||||
my $text = "On $when, sunrise in $where is at $rise; sunset at $set.";
|
||||
|
||||
my $html = "<div class='zci--suninfo'>";
|
||||
$html .= "<div class='suninfo--header text--secondary'><span class='ddgsi'>@</span>$where on $when</div>";
|
||||
$html .= "<div class='suninfo--row'>".
|
||||
"<span class='suninfo--risebox'>"
|
||||
. $sunrise_svg
|
||||
. "</span><span class='suninfo--timeboxes suninfo--border-right'><span class='text--primary suninfo--times'>$rise</span></span>";
|
||||
$html .=
|
||||
"<span class='suninfo--setbox'>"
|
||||
. $sunset_svg
|
||||
. "</span><span class='suninfo--timeboxes'><span class='text--primary suninfo--times'>$set</span></span>";
|
||||
$html .= "</div></div>";
|
||||
|
||||
return ($text, html => $html);
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,94 @@
|
|||
package DDG::GoodieRole::ImageLoader;
|
||||
# ABSTRACT: A role to allow Goodies to load images to pretty up their output.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Moo::Role;
|
||||
|
||||
use Devel::StackTrace;
|
||||
use List::MoreUtils qw( all );
|
||||
use MIME::Base64;
|
||||
use MIME::Types;
|
||||
use Package::Stash;
|
||||
use Scalar::Util qw( reftype );
|
||||
use Try::Tiny;
|
||||
|
||||
# Key and whether or not it is required
|
||||
my %known_keys = (
|
||||
filename => 1,
|
||||
alt => 0,
|
||||
height => 0,
|
||||
width => 0,
|
||||
class => 0,
|
||||
);
|
||||
|
||||
my $mt = MIME::Types->new(
|
||||
only_complete => 1,
|
||||
only_iana => 1
|
||||
);
|
||||
|
||||
# Returns an empty tag ('') in case of error.
|
||||
my $cannot = '';
|
||||
|
||||
sub goodie_img_tag {
|
||||
my $req_ref = shift;
|
||||
|
||||
# They must give us a hashref.
|
||||
my $ref_val = reftype($req_ref) || '';
|
||||
return $cannot unless ($ref_val eq 'HASH');
|
||||
my %request = %$req_ref;
|
||||
# And we must know what they all mean.
|
||||
return $cannot unless (all { exists $known_keys{$_} } (keys %request));
|
||||
# And all required ones must be present;
|
||||
return $cannot unless (all { exists $request{$_} } grep { $known_keys{$_} } (keys %known_keys));
|
||||
# We need to know the type of the file and that it's an image
|
||||
my $filename = $request{filename};
|
||||
my $type = $mt->mimeTypeOf($request{filename});
|
||||
return $cannot unless ($type && (split '/', $type)[0] eq 'image');
|
||||
# Now we need to hook into the role consumer's share dir, which we do in a ugly way here.
|
||||
my $their_share = _grab_package_function('share');
|
||||
return $cannot unless $their_share;
|
||||
# Now we need to be sure that we can get at the file
|
||||
my $file = $their_share->($filename);
|
||||
return $cannot unless $file;
|
||||
my $contents = scalar $file->slurp(iomode => '<:bytes');
|
||||
# Reckon it's possible they tried to trick us with an empty file
|
||||
return $cannot unless $contents;
|
||||
my $b64_contents = encode_base64($contents, '');
|
||||
return $cannot unless $b64_contents;
|
||||
my $their_enc = _grab_package_function('html_enc');
|
||||
return $cannot unless $their_enc;
|
||||
|
||||
my $goodie_tag = '<img src="data:' . $type . ';base64,' . $b64_contents . '"';
|
||||
foreach my $img_attr (grep { defined $request{$_} } qw(alt class height width)) {
|
||||
$goodie_tag .= ' ' . $img_attr . '="' . $their_enc->($request{$img_attr}) . '"';
|
||||
}
|
||||
$goodie_tag .= '/>';
|
||||
|
||||
return $goodie_tag;
|
||||
}
|
||||
|
||||
sub _grab_package_function {
|
||||
my $function_name = shift;
|
||||
|
||||
my $func = try {
|
||||
my $hit = 0;
|
||||
# We only care about the most recent caller who is some kinda goodie-looking thing.
|
||||
my $frame_filter = sub {
|
||||
my $frame_info = shift;
|
||||
if (!$hit && $frame_info->{caller}[0] =~ /^DDG::Goodie::/) { $hit++; return 1; }
|
||||
else { return 0; }
|
||||
};
|
||||
my $trace = Devel::StackTrace->new(
|
||||
frame_filter => $frame_filter,
|
||||
no_args => 1,
|
||||
);
|
||||
my $stash = Package::Stash->new($trace->frame(0)->package); # Get the package info for our caller.
|
||||
$stash->get_symbol('&' . $function_name);
|
||||
};
|
||||
|
||||
return $func;
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,55 @@
|
|||
.zci--answer .zci--suninfo {
|
||||
font-size: 1.75em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--header {
|
||||
font-size: 0.5em;
|
||||
margin-bottom: 0.25em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .ddgsi {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--row span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .ddgsi {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--timeboxes {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--border-right {
|
||||
border-right: 1px solid #ccc;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--times {
|
||||
margin: 0 0.5em;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--setbox,
|
||||
.zci--answer .zci--suninfo .suninfo--risebox {
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--setbox {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.zci--answer .zci--suninfo .suninfo--risebox {
|
||||
padding: 0;
|
||||
margin-left: -3px;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||
<path fill="#F0A630" d="M35.014,29c0.029-0.328,0.045-0.663,0.045-1c0-6.075-4.926-11-11-11c-6.075,0-11,4.925-11,11
|
||||
c0,0.337,0.015,0.672,0.044,1h2.011c-0.036-0.328-0.055-0.662-0.055-1c0-4.971,4.029-9,9-9c4.969,0,9,4.029,9,9
|
||||
c0,0.338-0.02,0.672-0.055,1H35.014L35.014,29L35.014,29z M24.059,9c-0.553,0-1,0.432-1,0.99v4.02c0,0.546,0.463,0.99,1,0.99
|
||||
c0.551,0,1-0.431,1-0.99V9.99C25.059,9.444,24.594,9,24.059,9L24.059,9L24.059,9z M37.535,14.582
|
||||
c-0.391-0.391-1.012-0.402-1.408-0.007l-2.842,2.842c-0.387,0.387-0.373,1.029,0.008,1.407c0.389,0.391,1.012,0.403,1.406,0.007
|
||||
l2.842-2.842C37.928,15.603,37.914,14.961,37.535,14.582L37.535,14.582L37.535,14.582z M43.117,28.059c0-0.553-0.434-1-0.992-1
|
||||
h-4.018c-0.549,0-0.99,0.463-0.99,1c0,0.552,0.43,1,0.99,1h4.018C42.672,29.059,43.117,28.595,43.117,28.059L43.117,28.059
|
||||
L43.117,28.059z M5,28.059c0,0.552,0.432,1,0.991,1h4.018c0.548,0,0.991-0.464,0.991-1c0-0.553-0.432-1-0.991-1H5.991
|
||||
C5.443,27.059,5,27.521,5,28.059L5,28.059L5,28.059z M10.582,14.582c-0.391,0.391-0.402,1.012-0.007,1.407l2.842,2.842
|
||||
c0.387,0.387,1.028,0.374,1.407-0.007c0.391-0.391,0.402-1.012,0.007-1.407l-2.842-2.842C11.603,14.188,10.961,14.204,10.582,14.582
|
||||
L10.582,14.582L10.582,14.582z M6.074,33H17.1l6.9-5.9l6.959,5.9h11.084c0.562,0,1.016,0.446,1.016,1c0,0.535-0.455,1-1.016,1H30.1
|
||||
L24,29.801L17.9,35H6.074c-0.562,0-1.016-0.447-1.016-1C5.059,33.464,5.513,33,6.074,33L6.074,33z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#F0624D" d="M34.941,29c0.029-0.328,0.045-0.663,0.045-1c0-6.075-4.926-11-11-11c-6.075,0-11,4.925-11,11
|
||||
c0,0.337,0.015,0.672,0.044,1h2.011c-0.036-0.328-0.055-0.662-0.055-1c0-4.971,4.029-9,9-9c4.969,0,9,4.029,9,9
|
||||
c0,0.338-0.02,0.672-0.055,1H34.941L34.941,29L34.941,29z M23.986,9c-0.553,0-1,0.432-1,0.99v4.02c0,0.546,0.463,0.99,1,0.99
|
||||
c0.551,0,1-0.431,1-0.99V9.99C24.986,9.444,24.521,9,23.986,9L23.986,9L23.986,9z M37.463,14.582
|
||||
c-0.391-0.391-1.012-0.402-1.408-0.007l-2.842,2.842c-0.387,0.387-0.373,1.029,0.008,1.407c0.389,0.391,1.012,0.403,1.406,0.007
|
||||
l2.842-2.842C37.855,15.603,37.842,14.961,37.463,14.582L37.463,14.582L37.463,14.582z M43.045,28.059c0-0.553-0.434-1-0.992-1
|
||||
h-4.018c-0.549,0-0.99,0.463-0.99,1c0,0.552,0.43,1,0.99,1h4.018C42.6,29.059,43.045,28.595,43.045,28.059L43.045,28.059
|
||||
L43.045,28.059z M4.928,28.059c0,0.552,0.432,1,0.991,1h4.018c0.548,0,0.991-0.464,0.991-1c0-0.553-0.432-1-0.991-1H5.919
|
||||
C5.371,27.059,4.928,27.521,4.928,28.059L4.928,28.059L4.928,28.059z M10.51,14.582c-0.391,0.391-0.402,1.012-0.007,1.407
|
||||
l2.842,2.842c0.387,0.387,1.028,0.374,1.407-0.007c0.391-0.391,0.402-1.012,0.007-1.407l-2.842-2.842
|
||||
C11.53,14.188,10.889,14.204,10.51,14.582L10.51,14.582L10.51,14.582z"/>
|
||||
</g>
|
||||
<path fill="#F0624D" d="M6.002,35c-0.562,0-1.016-0.464-1.016-1c0-0.553,0.453-1,1.016-1H17.9l6.1,5.199L30.1,33h11.871
|
||||
c0.561,0,1.016,0.465,1.016,1c0,0.554-0.453,1-0.916,1H30.887L24,40.9L17.1,35H6.002L6.002,35z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
131
t/00-roles.t
131
t/00-roles.t
|
@ -10,11 +10,11 @@ use DateTime;
|
|||
|
||||
subtest 'NumberStyler' => sub {
|
||||
|
||||
{ package RoleTester; use Moo; with 'DDG::GoodieRole::NumberStyler'; 1; }
|
||||
{ package NumberRoleTester; use Moo; with 'DDG::GoodieRole::NumberStyler'; 1; }
|
||||
|
||||
subtest 'Initialization' => sub {
|
||||
new_ok('RoleTester', [], 'Applied to a class');
|
||||
isa_ok(RoleTester::number_style_regex(), 'Regexp', 'number_style_regex()');
|
||||
new_ok('NumberRoleTester', [], 'Applied to a class');
|
||||
isa_ok(NumberRoleTester::number_style_regex(), 'Regexp', 'number_style_regex()');
|
||||
};
|
||||
|
||||
subtest 'Valid numbers' => sub {
|
||||
|
@ -31,7 +31,7 @@ subtest 'NumberStyler' => sub {
|
|||
foreach my $tc (@valid_test_cases) {
|
||||
my @numbers = @{$tc->[0]};
|
||||
my $expected_style_id = $tc->[1];
|
||||
is(RoleTester::number_style_for(@numbers)->id,
|
||||
is(NumberRoleTester::number_style_for(@numbers)->id,
|
||||
$expected_style_id, '"' . join(' ', @numbers) . '" yields a style of ' . $expected_style_id);
|
||||
}
|
||||
};
|
||||
|
@ -48,7 +48,7 @@ subtest 'NumberStyler' => sub {
|
|||
foreach my $tc (@invalid_test_cases) {
|
||||
my @numbers = @{$tc->[0]};
|
||||
my $why_not = $tc->[1];
|
||||
is(RoleTester::number_style_for(@numbers), undef, '"' . join(' ', @numbers) . '" fails because it ' . $why_not);
|
||||
is(NumberRoleTester::number_style_for(@numbers), undef, '"' . join(' ', @numbers) . '" fails because it ' . $why_not);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -56,19 +56,19 @@ subtest 'NumberStyler' => sub {
|
|||
|
||||
subtest 'Dates' => sub {
|
||||
|
||||
{ package RoleTester; use Moo; with 'DDG::GoodieRole::Dates'; 1; }
|
||||
{ package DatesRoleTester; use Moo; with 'DDG::GoodieRole::Dates'; 1; }
|
||||
|
||||
my $test_datestring_regex;
|
||||
my $test_formatted_datestring_regex;
|
||||
my $test_descriptive_datestring_regex;
|
||||
|
||||
subtest 'Initialization' => sub {
|
||||
new_ok('RoleTester', [], 'Applied to a class');
|
||||
$test_datestring_regex = RoleTester::datestring_regex();
|
||||
new_ok('DatesRoleTester', [], 'Applied to a class');
|
||||
$test_datestring_regex = DatesRoleTester::datestring_regex();
|
||||
isa_ok($test_datestring_regex, 'Regexp', 'datestring_regex()');
|
||||
$test_formatted_datestring_regex = RoleTester::formatted_datestring_regex();
|
||||
$test_formatted_datestring_regex = DatesRoleTester::formatted_datestring_regex();
|
||||
isa_ok($test_formatted_datestring_regex, 'Regexp', 'formatted_datestring_regex()');
|
||||
$test_descriptive_datestring_regex = RoleTester::descriptive_datestring_regex();
|
||||
$test_descriptive_datestring_regex = DatesRoleTester::descriptive_datestring_regex();
|
||||
isa_ok($test_descriptive_datestring_regex, 'Regexp', 'descriptive_datestring_regex()');
|
||||
};
|
||||
|
||||
|
@ -134,7 +134,7 @@ subtest 'Dates' => sub {
|
|||
$test_formatted_datestring_regex =~ qr/^$test_datestring_regex$/;
|
||||
ok(scalar @- == 1 && scalar @+ == 1, ' with no sub-captures.');
|
||||
|
||||
my $date_object = RoleTester::parse_formatted_datestring_to_date($test_date);
|
||||
my $date_object = DatesRoleTester::parse_formatted_datestring_to_date($test_date);
|
||||
isa_ok($date_object, 'DateTime', $test_date);
|
||||
is($date_object->epoch, $dates_to_match{$test_date}, '... which represents the correct time.');
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ subtest 'Dates' => sub {
|
|||
|
||||
foreach my $set (@date_sets) {
|
||||
my @source = @{$set->{src}};
|
||||
eq_or_diff([map { $_->epoch } (RoleTester::parse_all_datestrings_to_date(@source))],
|
||||
eq_or_diff([map { $_->epoch } (DatesRoleTester::parse_all_datestrings_to_date(@source))],
|
||||
$set->{output}, '"' . join(', ', @source) . '": dates parsed correctly');
|
||||
}
|
||||
};
|
||||
|
@ -210,7 +210,7 @@ subtest 'Dates' => sub {
|
|||
}
|
||||
|
||||
my $result;
|
||||
lives_ok { $result = RoleTester::parse_formatted_datestring_to_date($test_string) } '... and does not kill the parser.';
|
||||
lives_ok { $result = DatesRoleTester::parse_formatted_datestring_to_date($test_string) } '... and does not kill the parser.';
|
||||
is($result, undef, '... and returns undef to signal failure.');
|
||||
}
|
||||
};
|
||||
|
@ -227,7 +227,7 @@ subtest 'Dates' => sub {
|
|||
|
||||
foreach my $set (@invalid_date_sets) {
|
||||
my @source = @$set;
|
||||
my @date_results = RoleTester::parse_all_datestrings_to_date(@source);
|
||||
my @date_results = DatesRoleTester::parse_all_datestrings_to_date(@source);
|
||||
is(@date_results, 0, '"' . join(', ', @source) . '": cannot be parsed in combination.');
|
||||
}
|
||||
};
|
||||
|
@ -240,7 +240,7 @@ subtest 'Dates' => sub {
|
|||
|
||||
foreach my $result (sort keys %date_strings) {
|
||||
foreach my $test_string (@{$date_strings{$result}}) {
|
||||
is(RoleTester::date_output_string($test_string), $result, $test_string . ' normalizes for output as ' . $result);
|
||||
is(DatesRoleTester::date_output_string($test_string), $result, $test_string . ' normalizes for output as ' . $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -248,11 +248,11 @@ subtest 'Dates' => sub {
|
|||
my %bad_stuff = (
|
||||
'Empty string' => '',
|
||||
'Hashref' => {},
|
||||
'Object' => RoleTester->new,
|
||||
'Object' => DatesRoleTester->new,
|
||||
);
|
||||
foreach my $description (sort keys %bad_stuff) {
|
||||
my $result;
|
||||
lives_ok { $result = RoleTester::date_output_string($bad_stuff{$description}) } $description . ' does not kill the string output';
|
||||
lives_ok { $result = DatesRoleTester::date_output_string($bad_stuff{$description}) } $description . ' does not kill the string output';
|
||||
is($result, '', '... and yields an empty string as a result');
|
||||
}
|
||||
};
|
||||
|
@ -291,9 +291,9 @@ subtest 'Dates' => sub {
|
|||
set_fixed_time($query_time);
|
||||
my %strings = %{$time_strings{$query_time}};
|
||||
foreach my $test_date (sort keys %strings) {
|
||||
my $result = RoleTester::parse_descriptive_datestring_to_date($test_date);
|
||||
my $result = DatesRoleTester::parse_descriptive_datestring_to_date($test_date);
|
||||
isa_ok($result, 'DateTime', $test_date);
|
||||
is(RoleTester::date_output_string($result), $strings{$test_date}, $test_date . ' relative to ' . $query_time);
|
||||
is(DatesRoleTester::date_output_string($result), $strings{$test_date}, $test_date . ' relative to ' . $query_time);
|
||||
}
|
||||
}
|
||||
restore_time();
|
||||
|
@ -322,7 +322,7 @@ subtest 'Dates' => sub {
|
|||
);
|
||||
|
||||
foreach my $test_mixed_date (sort keys %mixed_dates_to_test) {
|
||||
my $parsed_date_object = RoleTester::parse_datestring_to_date($test_mixed_date);
|
||||
my $parsed_date_object = DatesRoleTester::parse_datestring_to_date($test_mixed_date);
|
||||
isa_ok($parsed_date_object, 'DateTime', $test_mixed_date);
|
||||
is($parsed_date_object->epoch, $mixed_dates_to_test{$test_mixed_date}, ' ... represents the correct time.');
|
||||
}
|
||||
|
@ -331,4 +331,95 @@ subtest 'Dates' => sub {
|
|||
}
|
||||
};
|
||||
|
||||
subtest 'ImageLoader' => sub {
|
||||
|
||||
subtest 'object with no share' => sub {
|
||||
# We have to wrap the function in a method in order to get the call-stack correct.
|
||||
{ package ImgRoleTester; use Moo; with 'DDG::GoodieRole::ImageLoader'; sub img_wrap { shift; goodie_img_tag(@_); } 1; }
|
||||
|
||||
my $no_share;
|
||||
subtest 'Initialization' => sub {
|
||||
$no_share = new_ok('ImgRoleTester', [], 'Applied to class');
|
||||
};
|
||||
|
||||
subtest 'non-share enabled object attempts' => sub {
|
||||
my %no_deaths = (
|
||||
'undef' => undef,
|
||||
'array ref' => [],
|
||||
'killer code ref' => sub { die },
|
||||
'with itself' => $no_share,
|
||||
'empty hash ref' => +{},
|
||||
'nonsense hash ref' => {ding => 'dong'},
|
||||
'proper' => {filename => 'hi.jpg'},
|
||||
);
|
||||
foreach my $desc (sort keys %no_deaths) {
|
||||
lives_ok { $no_share->goodie_img_tag($no_deaths{$desc}) } $desc . ': does not die.';
|
||||
}
|
||||
};
|
||||
};
|
||||
subtest 'object with a share' => sub {
|
||||
our $b64_gif =
|
||||
'R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7';
|
||||
our $final_src = 'src="data:image/gif;base64,' . $b64_gif;
|
||||
{
|
||||
|
||||
package DDG::Goodie::ImgShareTester;
|
||||
use Moo;
|
||||
use HTML::Entities;
|
||||
use Path::Class; # Hopefully the real share stays implemented this way.
|
||||
use MIME::Base64;
|
||||
with 'DDG::GoodieRole::ImageLoader';
|
||||
our $tmp_dir = Path::Class::tempdir(CLEANUP => 1);
|
||||
our $tmp_file = file(($tmp_dir->tempfile(TEMPLATE => 'img_XXXXXX', suffix => '.gif'))[1]);
|
||||
# Always return the same file for our purposes here.
|
||||
sub share { $tmp_file }
|
||||
sub html_enc { encode_entities(@_) } # Deal with silly symbol table twiddling.
|
||||
sub fill_temp { $tmp_file->spew(iomode => '>:bytes', decode_base64($b64_gif)) }
|
||||
sub kill_temp { undef $tmp_file }
|
||||
sub img_wrap { shift; goodie_img_tag(@_); }
|
||||
1;
|
||||
}
|
||||
|
||||
my $with_share;
|
||||
subtest 'Initialization' => sub {
|
||||
$with_share = new_ok('DDG::Goodie::ImgShareTester', [], 'Applied to class');
|
||||
};
|
||||
|
||||
subtest 'tag creation' => sub {
|
||||
my $filename = $with_share->share()->stringify;
|
||||
my $tag_content;
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename}) } 'Empty file does not die';
|
||||
is($tag_content, '', '... but returns empty tag.');
|
||||
$with_share->fill_temp;
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename}) } 'Newly filled file does not die';
|
||||
like($tag_content, qr/$final_src/, '... contains proper data');
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename, alt => 'Yo!'}) } 'With alt';
|
||||
like($tag_content, qr/$final_src/, '... contains proper data');
|
||||
like($tag_content, qr/alt=\"Yo!\"/, '... and proper alt attribute');
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename, alt => 'Yo!', height => 12}) } 'Plus height';
|
||||
like($tag_content, qr/$final_src/, '... contains proper data');
|
||||
like($tag_content, qr/alt="Yo!"/, '... and proper alt attribute');
|
||||
like($tag_content, qr/height="12"/, '... and proper height attribute');
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename, alt => 'Yo!', height => 12, width => 10}) } 'Plus width';
|
||||
like($tag_content, qr/$final_src/, '... contains proper data');
|
||||
like($tag_content, qr/alt="Yo!"/, '... and proper alt attribute');
|
||||
like($tag_content, qr/height="12"/, '... and proper height attribute');
|
||||
like($tag_content, qr/width="10"/, '... and proper width attribute');
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename, alt => 'hello"there!', height => 12, width => 10, class => 'smooth' }); } 'Plus class';
|
||||
like($tag_content, qr/$final_src/, '... contains proper data');
|
||||
like($tag_content, qr/alt="hello"there!"/, '... and proper alt attribute');
|
||||
like($tag_content, qr/height="12"/, '... and proper height attribute');
|
||||
like($tag_content, qr/width="10"/, '... and proper width attribute');
|
||||
like($tag_content, qr/class="smooth"/, '... and proper class attribute');
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename, atl => 'Yo!', height => 12, width => 10, class => 'smooth'}) }
|
||||
'Any mispelled does not die';
|
||||
is($tag_content, '', '... but yields an empty tag');
|
||||
$with_share->kill_temp;
|
||||
lives_ok { $tag_content = $with_share->img_wrap({filename => $filename, alt => 'Yo!', height => 12, width => 10, class => 'smooth'}) }
|
||||
'File disappeared does not die';
|
||||
is($tag_content, '', '... but yields an empty tag');
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use DDG::Test::Goodie;
|
||||
|
||||
zci answer_type => 'sun_info';
|
||||
zci is_cached => 0;
|
||||
|
||||
# Presume sun will rise in the morning and set at night year round in PA.
|
||||
my @now = (qr/^On.*Phoenixville, Pennsylvania.*AM.*PM\.$/, html => qr/Phoenixville.*AM.*PM/);
|
||||
my @aug = (qr/^On 30 Aug.*AM.*PM\.$/, html => qr/Phoenixville.*AM.*PM/);
|
||||
my @exact = (
|
||||
'On 01 Jan 2015, sunrise in Phoenixville, Pennsylvania is at 7:23 AM; sunset at 4:46 PM.',
|
||||
html =>
|
||||
qr{^<div class='zci--suninfo'><div class='suninfo--header text--secondary'><span class='ddgsi'>.*<img.*4:46 PM</span></span></div></div>$},
|
||||
);
|
||||
|
||||
ddg_goodie_test(
|
||||
[qw( DDG::Goodie::SunInfo )],
|
||||
'sunrise' => test_zci(@now),
|
||||
'what time is sunrise' => test_zci(@now),
|
||||
'sunset' => test_zci(@now),
|
||||
'what time is sunset' => test_zci(@now),
|
||||
'sunrise for aug 30' => test_zci(@aug),
|
||||
'sunrise 30 aug' => test_zci(@aug),
|
||||
'sunset for aug 30?' => test_zci(@aug),
|
||||
'sunset aug 30th' => test_zci(@aug),
|
||||
'sunset on 2015-01-01' => test_zci(@exact),
|
||||
'what time is sunrise on 2015-01-01?' => test_zci(@exact),
|
||||
'January 1st, 2015 sunrise' => test_zci(@exact),
|
||||
'sunset for philly' => undef,
|
||||
'sunrise on mars' => undef,
|
||||
'sunset boulevard' => undef,
|
||||
'tequila sunrise' => undef,
|
||||
'sunrise mall' => undef,
|
||||
'after the sunset' => undef,
|
||||
);
|
||||
|
||||
done_testing;
|
Loading…
Reference in New Issue