#!perl
#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
#   file: t/text-templater.t
#
#   Copyright © 2015 Van de Bugger
#
#   This file is part of perl-Dist-Zilla-Role-TextTemplater.
#
#   perl-Dist-Zilla-Role-TextTemplater is free software: you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by the Free Software Foundation,
#   either version 3 of the License, or (at your option) any later version.
#
#   perl-Dist-Zilla-Role-TextTemplater is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
#   PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License along with
#   perl-Dist-Zilla-Role-TextTemplater. If not, see <http://www.gnu.org/licenses/>.
#
#   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

use strict;
use warnings;
use lib 't';        # Make ErrorLoggerTestPlugin accessible.

use Test::More;
use Test::Deep qw{ re };
use Test::Routine::Util;
use Dist::Zilla::File::InMemory;

plan tests => 25;

my $role     = 'TextTemplaterTester';
my $in_ctor  = 'Dist::Zilla died in construction: ';
my $aborting = 'Aborting...';

run_tests( 'nothing to substitute', $role, {
    text     => [ 'assa' ],         #   Nothing to expand.
    exp_text => [ 'assa' ],         #   Output is the same as output
    exp_messages => [],
} );

run_tests( 'simple substitution', $role, {
    text     => [ '2 + 2 = {{ 2 + 2 }}' ],
    #                      ^^^^^^^^^^^
    exp_text => [ '2 + 2 = 4'           ],
    #                     ^^^
    exp_messages => [],
} );

run_tests( 'var $dist', $role, {
    #   Check variable `$dist` can be used.
    text     => [ 'name = {{ $dist->name }}' ],
    #                        ^^^^^^^^^^^
    exp_text => [ 'name = Dummy'             ],
    #                     ^^^^^
    exp_messages => [],
} );

run_tests( 'var $plugin', $role, {
    #   Check variable `$plugin` can be used.
    text     => [ 'generated with {{ $plugin->plugin_name }}' ],
    #                                ^^^^^^^^^^^^^^^^^^^^
    exp_text => [ 'generated with =TextTemplaterTestPlugin'   ],
    #                             ^^^^^^^^^^^^^^^^^^^^^^^^
    exp_messages => [],
} );

{
    local $MY::assa;            # Avoid warning "Name "MY::assa" used only once".
    $MY::assa = 'qwerty';
    #^^^^^^^^^^^^^^^^^^^
    run_tests( 'global var full name', $role, {
        text     => [ '{{ $MY::assa }}' ],
        #                 ^^^^^^^^^
        exp_text => [ 'qwerty'          ],
        #              ^^^^^^
        exp_messages => [],
    } );
    run_tests( 'global var short name + package', $role, {
        package  => 'MY',
        #            ^^
        text     => [ '{{ $assa }}' ],
        #                 ^^^^^ not $MY::assa
        exp_text => [ 'qwerty'      ],
        #              ^^^^^^
        exp_messages => [],
    } );
}

run_tests( '$dist + $plugin + package', $role, {
    #   Check `package` does not break `$plugin` and `$dist` variables.
    package  => 'MY',
    text     => [
        '# Generated with {{ $plugin->plugin_name }}',
        '{{ $dist->name . " " . $dist->version }}',
    ],
    exp_text => [
        '# Generated with =TextTemplaterTestPlugin',
        'Dummy 0.001'
    ],
    exp_messages => [],
} );

run_tests( 'prepend', $role, {
    #   Make sure `prepend` works.
    prepend  => [ 'my $yep = "nope";' ],                # Define a variable in the fragment.
    text     => [ '{{ $yep = "yep"; }}/{{ $yep }}' ],   # Note: Variable is reset to its original
    exp_text => [ 'yep/nope'                       ],   # value in the beginning of each fragment.
    exp_messages => [],
} );

run_tests( 'no closing braces', $role, {
    #   Syntax error in template: no closing braces.
    text          => [ '{{ $assa } }' ],
    exp_exception => $in_ctor . $aborting,
    exp_messages  => [
        'End of data inside program text that began at line 1',
    ]
} );

run_tests( 'die', $role, {
    #   Code fragment dies. Check linenumbers are reported correctly.
    text => [
        # `Dist::Zilla` configuration parser strips leading and trailing spaces!
        '1        template line 1',
        '2        template line 2',
        '{{     # template line 3, code line 1',
        'die;   # template line 4, code line 2',
        '2 + 3; # template line 5, code line 3 }}',
        '6        template line 6'
    ],
    exp_exception => $in_ctor . $aborting,
    exp_messages  => [
        re( qr{^Died at template line 4\b} ),
        'Problem code fragment begins at template line 3.',
        'template:',
        '    1: 1        template line 1',
        '    2: 2        template line 2',
        '    3: {{     # template line 3, code line 1',
        '       ^^^ Code fragment begins in line above ^^^',
        '    4: die;   # template line 4, code line 2',
        '       ^^^ Code fragment died in line above ^^^',
        '    5: 2 + 3; # template line 5, code line 3 }}',
        '    6: 6        template line 6',
    ]
} );

run_tests( 'long template', $role, {
    #   Code fragment dies in long template. Error message must include beginning of the template
    #   and line where die occurs, as well as two lines above and below. Other lines should be
    #   skipped to keep error message reasonable small.
    text => [
        '1        template line  1',
        '2        template line  2',
        '3        template line  3',
        '4        template line  4',
        '5        template line  5',
        '{{     # template line  6, code line  1',
        '7;     # template line  7, code line  2',
        '8;     # template line  8, code line  3',
        '9;     # template line  9, code line  4',
        '10;    # template line 10, code line  5',
        '11;    # template line 11, code line  6',
        '12;    # template line 12, code line  7',
        '13;    # template line 13, code line  8',
        'die;   # template line 14, code line  9',
        '15;    # template line 15, code line 10',
        '16;    # template line 16, code line 11',
        '17;    # template line 17, code line 12',
        '18;    # template line 18, code line 13',
        '19;    # template line 19, code line 14 }}',
        '20       template line 20'
    ],
    exp_exception => $in_ctor . $aborting,
    exp_messages  => [
        re( qr{^Died at template line 14\b} ),
        'Problem code fragment begins at template line 6.',
        'template:',
        '        ...skipped 3 lines...',
        '    04: 4        template line  4',
        '    05: 5        template line  5',
        '    06: {{     # template line  6, code line  1',
        '        ^^^ Code fragment begins in line above ^^^',
        '    07: 7;     # template line  7, code line  2',
        '    08: 8;     # template line  8, code line  3',
        '        ...skipped 3 lines...',
        '    12: 12;    # template line 12, code line  7',
        '    13: 13;    # template line 13, code line  8',
        '    14: die;   # template line 14, code line  9',
        '        ^^^ Code fragment died in line above ^^^',
        '    15: 15;    # template line 15, code line 10',
        '    16: 16;    # template line 16, code line 11',
        '        ...skipped 4 lines...',
    ]
} );

run_tests( 'not so long template', $role, {
    #   The same as above, but single line should be never skipped: message "...skipped 1 line..."
    #   anyway occupies one line, so it is better to show template line rather than "skip" message.
    #   This template shortened so skips should not occur.
    text => [
        '1        template line  1',                    # Should not be skipped.
        '2        template line  2',
        '3        template line  3',
        '{{     # template line  4, code line  1',
        '5;     # template line  5, code line  2',
        '6;     # template line  6, code line  3',
        '7;     # template line  7, code line  4',      # Should not be skipped.
        '8;     # template line  8, code line  5',
        '9;     # template line  9, code line  6',
        'die;   # template line 10, code line  7',
        '11;    # template line 11, code line  8',
        '12;    # template line 12, code line  9 }}',
        '13       template line 13'                     # Should not be skipped.
    ],
    exp_exception => $in_ctor . $aborting,
    exp_messages  => [
        re( qr{^Died at template line 10\b} ),
        'Problem code fragment begins at template line 4.',
        'template:',
        '    01: 1        template line  1',
        '    02: 2        template line  2',
        '    03: 3        template line  3',
        '    04: {{     # template line  4, code line  1',
        '        ^^^ Code fragment begins in line above ^^^',
        '    05: 5;     # template line  5, code line  2',
        '    06: 6;     # template line  6, code line  3',
        '    07: 7;     # template line  7, code line  4',
        '    08: 8;     # template line  8, code line  5',
        '    09: 9;     # template line  9, code line  6',
        '    10: die;   # template line 10, code line  7',
        '        ^^^ Code fragment died in line above ^^^',
        '    11: 11;    # template line 11, code line  8',
        '    12: 12;    # template line 12, code line  9 }}',
        '    13: 13       template line 13',
    ]
} );

{
    package MY;
    sub oops {
        die;
    };
}

run_tests( 'deep die', $role, {
    #   Program fragment dies, but `die` is buried in the calling code.
    text => [ '{{ MY::oops(); }}' ],
    exp_exception => $in_ctor . $aborting,
    exp_messages  => [
        re( qr{^Died at t/text-templater\.t line \d+\b} ),
        'Problem code fragment begins at template line 1.',
        'template:',
        '    1: {{ MY::oops(); }}',
        '       ^^^ Code fragment begins in line above ^^^',
    ]
} );

run_tests( 'undefined subroutine', $role, {
    #   Call undefined subroutine. It is fatal error.
    text => [ '{{ MY::undefined(); }}' ],
    exp_exception => $in_ctor . $aborting,
    exp_messages  => [
        re( qr{^Undefined subroutine &MY::undefined called at template line 1\b} ),
        'Problem code fragment begins at template line 1.',
        'template:',
        '    1: {{ MY::undefined(); }}',
        '       ^^^ Code fragment begins in line above ^^^',
        '       ^^^ Code fragment died in line above ^^^',
    ]
} );

run_tests( 'prepend + warnings', $role, {
    #   Use undefined variable. It causes warning, wich should appear in the log, but build should
    #   complete successfuly.
    prepend  => [ 'use warnings;' ],
    text     => [ '{{ $MY::assa + 2 }}' ],
    exp_text => [ '2' ],
    exp_messages  => [
        re( qr{^Use of uninitialized value \$MY::assa in addition \(\+\) at template line 1\b} ),
    ]
} );

{
    local $TextTemplaterTestPlugin::Hook = sub {
        my ( $self, $string ) = @_;
        $self->fill_in_string( $string, { assa => 'Yahoo' } );
    };
    run_tests( 'custom variables', $role, {
        text         => [ '{{ $assa }}' ],
        exp_messages => [],
        exp_text     => [ 'Yahoo' ],
    } );
}

{
    #   Explicitly specified $plugin and $dist overrides default definition.
    local $TextTemplaterTestPlugin::Hook = sub {
        my ( $self, $string ) = @_;
        $self->fill_in_string( $string, { plugin => 'Mooo', dist => 'Yep' } );
        #                                 ^^^^^^            ^^^^
    };
    run_tests( 'explicit $plugin and $dist', $role, {
        text         => [ '{{ $plugin . " " . $dist }}' ],
        exp_text     => [ 'Mooo Yep' ],
        exp_messages => [],
    } );
}

{
    local ( $MY::assa, $ME::assa );
    $MY::assa = 'qwerty';
    $ME::assa = 'ytrewq';
    #   Explicitly specified lowercase option overrides the attribute.
    {
        local $TextTemplaterTestPlugin::Hook = sub {
            my ( $self, $string ) = @_;
            $self->fill_in_string( $string, undef, { package => 'ME' } );
        };
        run_tests( 'explicit $plugin and $dist', $role, {
            text         => [ '{{ $assa }}' ],
            package      => 'MY',
            exp_text     => [ 'ytrewq' ],
            exp_messages => [],
        } );
    }
    #   Explicitly specified uppercase option does not override the attribute.
    {
        local $TextTemplaterTestPlugin::Hook = sub {
            my ( $self, $string ) = @_;
            $self->fill_in_string( $string, undef, { PACKAGE => 'ME' } );
        };
        run_tests( 'explicit $plugin and $dist', $role, {
            text         => [ '{{ $assa }}' ],
            package      => 'MY',
            exp_text     => [ 'qwerty' ],
            exp_messages => [],
        } );
    }
}

run_tests( 'non-standard delimiters', $role, {
    delimiters    => '(* *)',
    text          => [ '(* 3 * 3 *)' ],
    exp_text      => [ '9' ],
    exp_messages  => [],
} );

run_tests( 'bad delimiters', $role, {
    delimiters    => '(**)',
    text          => [ '(* 3 * 3 *)' ],
    exp_exception => re( qr{^$in_ctor"delimiters" value must be Str of \*two\* whitespace-separated words} ),
    exp_messages  => [],
} );

{
    local $TextTemplaterTestPlugin::Hook = sub {
        my ( $self, $string ) = @_;
        $self->fill_in_string( $string, undef, { filename => 'Assa.txt' } );
        #                                        ^^^^^^^^^^^^^^^^^^^^^^
    };
    run_tests( 'filename', $role, {
        text => [
            '{{ die; }}',
        ],
        exp_exception => $in_ctor . $aborting,
        exp_messages  => [
            re( qr{^Died at Assa.txt line 1\b} ),
            #               ^^^^^^^^
            'Problem code fragment begins at Assa.txt line 1.',
            #                                ^^^^^^^^
            'Assa.txt:',
            '    1: {{ die; }}',
            '       ^^^ Code fragment begins in line above ^^^',
            '       ^^^ Code fragment died in line above ^^^',
        ]
    } );
}

{
    local $TextTemplaterTestPlugin::Hook = sub {
        my ( $self, $string ) = @_;
        my $file = Dist::Zilla::File::InMemory->new( {
            name    => 'Assa.3d',
            content => $string,
        } );
        $self->fill_in_file( $file );
        return $file->content;
    };
    run_tests( 'file', $role, {
        text     => [ '2 + 3 = {{ 2 + 3 }};' ],
        exp_text => [ '2 + 3 = 5;' ],
    } );
}

{
    local $TextTemplaterTestPlugin::Hook = sub {
        my ( $self, $string ) = @_;
        my $file = Dist::Zilla::File::InMemory->new( {
            name    => 'Assa.3e',
            content => $string,
        } );
        return $self->fill_in_file( $file );
    };
    run_tests( 'fill_in_file return value', $role, {
        text     => [ '2 * 3 = {{ 2 * 3 }};' ],
        exp_text => [ '2 * 3 = 6;' ],
    } );
}

{
    local $TextTemplaterTestPlugin::Hook = sub {
        my ( $self, $string ) = @_;
        my $file = Dist::Zilla::File::InMemory->new( {
            name => 'Assa.3f',
            content => $string,
        } );
        return $self->fill_in_file( $file );
    };
    run_tests( 'fill_in_file error message', $role, {
        text          => [
            'line   1',
            '{{   # 2',
            'die; # 3',
            '}}     4',
            'line   5',
            'line   6',
            'line   7',
        ],
        exp_exception => $in_ctor . $aborting,
        exp_messages  => [
            re( qr{^Died at Assa\.3f line 3\b} ),
            'Problem code fragment begins at Assa.3f line 2.',
            'Assa.3f:',
            '    1: line   1',
            '    2: {{   # 2',
            '       ^^^ Code fragment begins in line above ^^^',
            '    3: die; # 3',
            '       ^^^ Code fragment died in line above ^^^',
            '    4: }}     4',
            '    5: line   5',
            '       ...skipped 2 lines...',
        ],
    } );
}

done_testing;

exit( 0 );

# end of file #
