The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

use strict;
use warnings;

use Test::More;
use Test::Exception;
use File::Path qw(remove_tree);
use Path::Tiny;

my $TEMPLATE_OUT = 't/out';

# clear the test template output directory
sub clear_output_directory {
	remove_tree($TEMPLATE_OUT, { keep_root => 1 });
}

BEGIN {
	use_ok 'App::DuckPAN::TemplateDefinitions';
}

################################
# Parsing template definitions #
################################

my $template_defs =
	new_ok 'App::DuckPAN::TemplateDefinitions',
		[ templates_yml => 't/template/templates.yml' ];

# check templates
my @templates = $template_defs->get_templates;
my %template_map = map { $_->name => $_ } @templates;

# check if templates are instantiated
isa_ok $_, 'App::DuckPAN::Template'
	for @templates;

# check if all the templates have been read
is_deeply [ sort map { $_->name } @templates ],
	[qw(complex_out_dir css js no_input no_write_perm pm test)],
	'template defs: read all templates';

# check template sets
my @template_sets = $template_defs->get_template_sets;
my %template_set_map = map { $_->name => $_ } @template_sets;

# check if template sets are instantiated
isa_ok $_, 'App::DuckPAN::TemplateSet'
	for @template_sets;

# check if all the template sets have been read
is_deeply [ sort map { $_->name } @template_sets ],
	[qw(all_optional errors required_and_optional subdir_support_not_defined subdir_support_specified)],
	'template defs: read all template sets';

###############################
# Template definitions errors #
###############################

throws_ok {
		my $templates_defs = App::DuckPAN::TemplateDefinitions->new(
			templates_yml => 't/template/templates-nonexistent.yml'
		);
	} qr/Error loading/,
	'template defs: non-existent template definitions file throws error';

########################
# Individual Templates #
########################

# check if all fields of a template are read
is $template_map{pm}->label, 'Perl Module',
	'template defs: set template label field';
is $template_map{pm}->input_file, 't/template/lib/DDG/Default.pm',
	'template defs: set template input field';
is $template_map{pm}->output_file, 't/out/lib/DDG/<:$package_name:>.pm',
	'template defs: set template output field';

# output directory computation
is $template_map{pm}->output_directory, 't/out/lib/DDG',
	'template: output directory generated';
is $template_map{complex_out_dir}->output_directory, 't/out/share/text',
	'template: output directory generated (complex)';

#################
# Template Sets #
#################

# check if all fields of a template set are read
my $template_set_ro = $template_set_map{required_and_optional};

is $template_set_ro->description, 'Template set with required and optional templates',
	'template defs: set template_set description';
is_deeply $template_set_ro->required_templates, [@template_map{qw(pm test)}],
	'template defs: set template_set required templates';
is_deeply $template_set_ro->optional_templates, [@template_map{qw(js css)}],
	'template defs: set template_set optional templates';

ok !$template_set_map{subdir_support_specified}->subdir_support, 'Template set with subdir support explicitly disabled';
ok $template_set_map{subdir_support_not_defined}->subdir_support, 'Template set with subdir support implicitly enabled';

{
	# check template combinations that we should show to the user
	my $combinations = $template_set_map{all_optional}->optional_template_combinations;

	# for neater code
	my %tm = %template_map;

	# combinations must be in a particular order
	is_deeply $combinations,
		[
			[ $tm{js}   ],
			[ $tm{pm}   ],
			[ $tm{test} ],
			[ $tm{js},  $tm{pm}   ],
			[ $tm{js},  $tm{test} ],
			[ $tm{pm},  $tm{test} ],
			[ $tm{js},  $tm{pm},  $tm{test} ],
		],
		'template set: all optional template combinations generated correctly';
}

##################################
# File generation from templates #
##################################

my $package_name = 'MyInstantAnswer';
my $ia_id = 'my_instant_answer';
my %vars = (
	package_name => $package_name,
	ia_id	 => $ia_id,
);
my $pm_out_file   = "$TEMPLATE_OUT/lib/DDG/$package_name.pm";
my $test_out_file = "$TEMPLATE_OUT/t/$package_name.test";
my $js_out_file   = "$TEMPLATE_OUT/share/javascript/$ia_id.js";
my $css_out_file  = "$TEMPLATE_OUT/share/css/$ia_id.css";

clear_output_directory();

$template_map{pm}->generate(\%vars);

# check if output file is correct
ok -f "$pm_out_file", 'template: file generated from template';

my $pm_file_content = path($pm_out_file)->slurp;

# check the content of the output file using all variables
is $pm_file_content, <<EOT, 'template: generated file content is as expected';
package $package_name;
my \$id = '$ia_id';
EOT

#########################################
# File generation errors from templates #
#########################################

# overwriting file
throws_ok {
		$template_map{pm}->generate(\%vars);
	} qr/already exists/,
	'template: overwriting generated file throws error';

# input file not present
throws_ok { $template_map{no_input}->generate(\%vars) }
	qr/not found/,
	'template: non-existent template file throws error';


# create a directory with no write access
mkdir "$TEMPLATE_OUT/readonly", 0500;
throws_ok {
		$template_map{no_write_perm}->generate(\%vars)
	}
	qr/Error creating output/,
	'template: failure creating template output file throws error';

{
	my $got_warning;
	local $SIG{__WARN__} = sub { $got_warning = 1 };

	clear_output_directory();

	# all variables were passed to template; no warnings should be shown
	$template_map{pm}->generate(\%vars);
	ok !$got_warning, 'template: no warning when all variables are passed to template';

	clear_output_directory();

	# some or all variables were missing; show warnings
	$template_map{pm}->generate({});
	ok $got_warning, 'template: show warning when not all variables are passed to template';
}

######################################
# File generation from template sets #
######################################
my %generate_res;

clear_output_directory();
%generate_res = $template_set_map{required_and_optional}->generate(\%vars, [ $template_map{js} ]);

is_deeply [ sort @{$generate_res{created_files}} ],
	[ $pm_out_file, $js_out_file, $test_out_file ],
	'template set: return value has all the created files';

ok !$generate_res{error}, 'template set: no errors when succesfully generating files';

# verify all required files have been generated
ok -f $_, "template set: required file '$_' generated from template set"
	for ($pm_out_file, $test_out_file);

ok  -f $js_out_file,  "template set: selected optional file '$js_out_file' generated from template set";
ok !-f $css_out_file, "template set: unselected optional file '$css_out_file' not generated from template set";

#############################################
# File generation errors from template sets #
#############################################

clear_output_directory();
throws_ok {
		$template_set_map{required_and_optional}->generate(\%vars, [ $template_map{pm} ])
	}
	qr/Unknown template/,
	'template set: die when invalid templates passed in optional templates list';

# individual template errors while generating output
%generate_res = $template_set_map{errors}->generate(\%vars, [ $template_map{no_input} ]);

# required files are generated before optional files, thus we get the .pm file
# in the created files list. Feel free to change this behaviour and update the
# test if necessary.
is_deeply $generate_res{created_files}, [ $pm_out_file ],
	'template set: successfully generated file added to "created_files" list';

# the optional template failed to generate the output
ok scalar($generate_res{error} =~ /Template input file.*not found/),
	'template set: error message set for failed template';

#############################################

clear_output_directory();

done_testing;