Merge pull request #660 from duckduckgo/mwm/sunrise_sunset

SunInfo: add new Instant Answer for sunrise/sunset.
master
Zaahir Moolla 2014-10-31 11:12:24 -04:00
commit 9386eb5c3d
8 changed files with 450 additions and 20 deletions

View File

@ -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

108
lib/DDG/Goodie/SunInfo.pm Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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&quot;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;

41
t/SunInfo.t Normal file
View File

@ -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;