my $template = shift;
my ($options, %locals) = (@_ && ref($_[0])) ? @_ : ({ }, @_);
- $options->{type} = lc($options->{type} || 'html');
- $options->{no_layout} = 1 if $options->{type} eq 'js';
+ # Set defaults for all available options.
+ my %defaults = (
+ type => 'html',
+ output => 1,
+ header => 1,
+ layout => 1,
+ process => 1,
+ );
+ $options->{$_} //= $defaults{$_} for keys %defaults;
+ $options->{type} = lc $options->{type};
+
+ # Check supplied options for validity.
+ foreach (keys %{ $options }) {
+ croak "Unsupported option: $_" unless $defaults{$_};
+ }
+
+ # Only certain types are supported.
+ croak "Unsupported type: " . $options->{type} unless $options->{type} =~ m/^(?:html|js|json)$/;
+
+ # The "template" argument must be a string or a reference to one.
+ croak "Unsupported 'template' reference type: " . ref($template) if ref($template) && (ref($template) !~ m/^(?:SCALAR|SL::Presenter::EscapedText)$/);
+
+ # If all output is turned off then don't output the header either.
+ if (!$options->{output}) {
+ $options->{header} = 0;
+ $options->{layout} = 0;
+
+ } else {
+ # Layout only makes sense if we're outputting HTML.
+ $options->{layout} = 0 if $options->{type} ne 'html';
+ }
+
+ if ($options->{header}) {
+ # Output the HTTP response and the layout in case of HTML output.
- if (!$options->{partial} && !$options->{inline} && !$::form->{header}) {
- if ($options->{no_layout}) {
+ if ($options->{layout}) {
+ $::form->{title} = $locals{title} if $locals{title};
+ $::form->header;
+
+ } else {
+ # No layout: just the standard HTTP response. Also notify
+ # $::form that the header has already been output so that
+ # $::form->header() won't output it again.
$::form->{header} = 1;
- my $content_type = $options->{type} eq 'js' ? 'text/javascript' : 'text/html';
+ my $content_type = $options->{type} eq 'html' ? 'text/html'
+ : $options->{type} eq 'js' ? 'text/javascript'
+ : 'application/json';
print $::form->create_http_response(content_type => $content_type,
charset => $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET());
-
- } else {
- $::form->{title} = $locals{title} if $locals{title};
- $::form->header(no_menu => $options->{no_menu});
}
}
- my $output;
- if ($options->{raw}) {
- $output = $$template;
- } else {
- $output = $self->presenter->render(
- $template, $options,
- %locals,
- SELF => $self,
- );
- }
+ # Let the presenter do the rest of the work.
+ my $output = $self->presenter->render(
+ $template,
+ { type => $options->{type}, process => $options->{process} },
+ %locals,
+ SELF => $self,
+ );
- print $output unless $options->{inline} || $options->{no_output};
+ # Print the output if wanted.
+ print $output if $options->{output};
return $output;
}
C<$options>, if present, must be a hash reference. All remaining
parameters are slurped into C<%locals>.
-What is rendered and how C<$template> is interpreted is determined by
-the options I<type>, I<inline>, I<partial> and I<no_layout>. The
+What is rendered and how C<$template> is interpreted is determined
+both by C<$template>'s reference type and by the supplied options. The
actual rendering is handled by L<SL::Presenter/render>.
-If C<< $options->{inline} >> is trueish then C<$template> is a string
-containing the template code to interprete. Additionally the output
-will not be sent to the browser. Instead it is only returned to the
-caller.
+If C<$template> is a normal scalar (not a reference) then it is meant
+to be a template file name relative to the C<templates/webpages>
+directory. The file name to use is determined by the C<type> option.
+
+If C<$template> is a reference to a scalar then the referenced
+scalar's content is used as the content to process. The C<type> option
+is not considered in this case.
+
+Other reference types, unknown options and unknown arguments to the
+C<type> option cause the function to L<croak>.
+
+The following options are available (defaults: C<type> = 'html',
+C<process> = 1, C<output> = 1, C<header> = 1, C<layout> = 1):
+
+=over 2
+
+=item C<type>
-If C<< $options->{raw} >> is trueish, the function will treat the
-input as already parsed, and will not filter the input through
-Template. This also means that L<SL::Presenter/render> is not
-called either. Unlike C<inline>, the input is taken as a reference.
+The template type. Can be C<html> (the default), C<js> for JavaScript
+or C<json> for JSON content. Affects the extension that's added to the
+file name given with a non-reference C<$template> argument, the
+content type HTTP header that is output and whether or not the layout
+will be output as well (see description of C<layout> below).
-If C<< $options->{inline} >> is falsish then C<$template> is
-interpreted as the name of a template file. It is prefixed with
-"templates/webpages/" and postfixed with a file extension based on
-C<< $options->{type} >>. C<< $options->{type} >> can be either C<html>
-or C<js> and defaults to C<html>. An exception will be thrown if that
-file does not exist.
+=item C<process>
-If C<< $options->{partial} >> or C<< $options->{inline} >> is trueish
-then neither the HTTP response header nor the standard HTML header is
-generated.
+If trueish (which is also the default) it causes the template/content
+to be processed by the Template toolkit. Otherwise the
+template/content is output as-is.
-Otherwise at least the HTTP response header will be generated based on
-the template type (C<< $options->{type} >>).
+=item C<output>
-If the template type is C<html> then the standard HTML header will be
-output via C<< $::form->header >> with C<< $::form->{title} >> set to
-C<$locals{title}> (the latter only if C<$locals{title}> is
-trueish). Setting C<< $options->{no_layout} >> to trueish will prevent
-this.
+If trueish (the default) then the generated output will be sent to the
+browser in addition to being returned. If falsish then the options
+C<header> and C<layout> are set to 0 as well.
+
+=item C<header>
+
+Determines whether or not to output the HTTP response
+headers. Defaults to the same value that C<output> is set to. If set
+to falsish then the layout is not output either.
+
+=item C<layout>
+
+Determines whether or not the basic HTML layout structure should be
+output (HTML header, common JavaScript and stylesheet inclusions, menu
+etc.). Defaults to 0 if C<type> is not C<html> and to the same value
+C<header> is set to otherwise.
+
+=back
The template itself has access to several variables. These are listed
in the documentation to L<SL::Presenter/render>.
-Unless C<< $options->{inline} >> is trueish the function will send the
-output to the browser.
-
The function will always return the output.
Example: Render a HTML template with a certain title and a few locals
TODO_ITEMS => SL::DB::Manager::Todo->get_all_sorted);
Example: Render a string and return its content for further processing
-by the calling function. No header is generated due to C<inline>.
+by the calling function. No header is generated due to C<output>.
- my $content = $self->render('[% USE JavaScript %][% JavaScript.replace_with("#someid", "js/something") %]',
- { type => 'js', inline => 1 });
+ my $content = $self->render(\'[% USE JavaScript %][% JavaScript.replace_with("#someid", "js/something") %]',
+ { output => 0 });
-Example: Render a JavaScript template and send it to the
+Example: Render a JavaScript template
+"templates/webpages/todo/single_item.js" and send it to the
browser. Typical use for actions called via AJAX:
$self->render('todo/single_item', { type => 'js' },
SL::DB::CustomVariableConfig->reorder_list(@{ $::form->{cvarcfg_id} || [] });
- $self->render('1;', { type => 'js', inline => 1 });
+ $self->render(\'', { type => 'json' });
}
#
$self->{customers} = SL::DB::Manager::Customer->get_all(query => [ @filter ], limit => $limit);
$self->{value} = $::form->{column} || 'name';
- $self->render('ct/ajax_autocomplete2', { no_layout => 1 });
+ $self->render('ct/ajax_autocomplete2', { layout => 0, type => 'json' });
}
-
controller_class => 'DeliveryPlan',
output_format => 'HTML',
top_info_text => $::locale->text('Delivery Plan for currently outstanding sales orders'),
- raw_top_info_text => $self->render('delivery_plan/report_top', { no_output => 1, partial => 1 }),
- raw_bottom_info_text => $self->render('delivery_plan/report_bottom', { no_output => 1, partial => 1 }),
+ raw_top_info_text => $self->render('delivery_plan/report_top', { output => 0 }),
+ raw_bottom_info_text => $self->render('delivery_plan/report_bottom', { output => 0 }),
title => $::locale->text('Delivery Plan'),
allow_pdf_export => 1,
allow_csv_export => 1,
use strict;
use parent qw(SL::Controller::Base);
-use JSON ();
+use SL::JSON ();
sub action_empty {
my ($self) = @_;
stylesheets_inline => [ $::request->{layout}->stylesheets_inline ],
};
- $self->render(\ JSON::to_json($layout), { type => 'js', raw => 1 });
+ $self->render(\ SL::JSON::to_json($layout), { type => 'json', process => 0 });
}
}
return if $self->_redirect_to_main_script_if_already_logged_in;
# Otherwise show the login form.
- $self->render('login_screen/user_login', { no_menu => 1 }, error => error_state($::form->{error}));
+ $self->render('login_screen/user_login', error => error_state($::form->{error}));
}
sub action_logout {
$::auth->destroy_session;
$::auth->create_or_refresh_session;
- $self->render('login_screen/user_login', { no_menu => 1 }, error => $::locale->text('You are logged out!'));
+ $self->render('login_screen/user_login', error => $::locale->text('You are logged out!'));
}
sub action_login {
# Other login errors.
if (0 > $result) {
$::auth->punish_wrong_login;
- return $self->render('login_screen/user_login', { no_menu => 1 }, error => $::locale->text('Incorrect username or password!'));
+ return $self->render('login_screen/user_login', error => $::locale->text('Incorrect username or password!'));
}
# Everything is fine.
$self->{parts} = SL::DB::Manager::Part->get_all(query => [ @filter ], limit => $limit);
$self->{value} = $::form->{column} || 'description';
- $self->render('part/ajax_autocomplete', { no_layout => 1 });
+ $self->render('part/ajax_autocomplete', { layout => 0, type => 'json' });
}
SL::DB::PaymentTerm->reorder_list(@{ $::form->{payment_term_id} || [] });
- $self->render('1;', { type => 'js', inline => 1 });
+ $self->render(\'', { type => 'json' });
}
#
SL::DB::PriceFactor->reorder_list(@{ $::form->{price_factor_id} || [] });
- $self->render('1;', { type => 'js', inline => 1 });
+ $self->render(\'', { type => 'json' });
}
#
controller_class => 'Project',
output_format => 'HTML',
top_info_text => $::locale->text('Projects'),
- raw_bottom_info_text => $self->render('project/report_bottom', { no_output => 1, partial => 1 }),
+ raw_bottom_info_text => $self->render('project/report_bottom', { output => 0 }),
title => $::locale->text('Projects'),
allow_pdf_export => 1,
allow_csv_export => 1,
SL::DB::Unit->reorder_list(@{ $::form->{unit_id} || [] });
- $self->render('1;', { type => 'js', inline => 1 });
+ $self->render(\'', { type => 'json' });
}
#
SL::DB::Warehouse->reorder_list(@{ $::form->{warehouse_id} || [] });
- $self->render('1;', { type => 'js', inline => 1 });
+ $self->render(\'', { type => 'json' });
}
#
$::form->{error} = $::locale->text('The session is invalid or has expired.') if ($error_type eq 'session');
$::form->{error} = $::locale->text('Incorrect password!') if ($error_type eq 'password');
- $::form->header(no_menu => 1);
+ $::form->header;
print $::form->parse_html_template($template, \%params);
$::lxdebug->leave_sub;
--- /dev/null
+package SL::JSON;
+
+use strict;
+
+use JSON ();
+
+use parent qw(Exporter);
+our @EXPORT = qw(encode_json decode_json to_json from_json);
+
+sub new {
+ shift;
+ return JSON->new(@_)->convert_blessed(1);
+}
+
+sub encode_json {
+ return JSON->new->convert_blessed(1)->encode(@_);
+}
+
+sub decode_json {
+ goto &JSON::decode_json;
+}
+
+sub to_json {
+ my ($object, $options) = @_;
+ $options ||= {};
+ $options->{convert_blessed} = 1;
+ return JSON::to_json($object, $options);
+}
+
+sub from_json {
+ goto &JSON::decode_json;
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::JSON - Thin wrapper around the JSON module that provides default options
+
+=head1 SYNOPSIS
+
+ use SL::JSON;
+
+ my $escaped_text_object = SL::Presenter->get->render('some/template');
+ my $json = encode_json($escaped_text_object);
+
+=head1 OVERVIEW
+
+JSON by default does not dump or stringify blessed
+objects. kivitendo's rendering infrastructure always returns thin
+proxy objects as instances of L<SL::Presenter::EscapedText>. This
+module provides the same functions that L<JSON> does but changes their
+default regarding converting blessed arguments.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<decode_json $json>
+
+Same as L<JSON/decode_json>.
+
+=item C<encode_json $object>
+
+Same as L<JSON/encode_json> but sets C<convert_blessed> first.
+
+=item C<from_json $object [, $options]>
+
+Same as L<JSON/from_json>.
+
+=item C<to_json $object [, $options ]>
+
+Same as L<JSON/to_json> but sets C<convert_blessed> first.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
$callback = URI->new($callback)->rel($callback) if $callback;
$callback = "login.pl?action=company_logo" if $callback =~ /^(\.\/)?$/;
- $self->render("menu/menunew", { partial => 1, no_output => 1 },
+ $self->render("menu/menunew", { output => 0 },
force_ul_width => 1,
date => $self->clock_line,
menu_items => $self->acc_menu,
sub javascripts_inline {
my $self = shift;
my $sections = [ section_menu($self->menu) ];
- $self->render('menu/menu', { partial => 1, no_output => 1 },
+ $self->render('menu/menu', { output => 0 },
sections => $sections,
)
}
sub pre_content {
my ($self) = @_;
- $self->SUPER::render('menu/header', { partial => 1, no_output => 1 },
+ $self->SUPER::render('menu/header', { output => 0 },
now => DateTime->now_local,
is_fastcgi => scalar($::dispatcher->interface_type =~ /fastcgi/i),
is_links => scalar($ENV{HTTP_USER_AGENT} =~ /links/i));
$callback = URI->new($callback)->rel($callback) if $callback;
$callback = "login.pl?action=company_logo" if $callback =~ /^(\.\/)?$/;
- $self->SUPER::render('menu/menuv3', { no_menu => 1, no_output => 1 },
+ $self->SUPER::render('menu/menuv3', { output => 0 },
force_ul_width => 1,
date => $self->clock_line,
menu => $self->print_menu,
my $template = shift;
my ($options, %locals) = (@_ && ref($_[0])) ? @_ : ({ }, @_);
- $options->{type} = lc($options->{type} || 'html');
+ # Set defaults for all available options.
+ my %defaults = (
+ type => 'html',
+ process => 1,
+ );
+ $options->{$_} //= $defaults{$_} for keys %defaults;
+ $options->{type} = lc $options->{type};
- my $source;
- if ($options->{inline}) {
- $source = \$template;
+ # Check supplied options for validity.
+ foreach (keys %{ $options }) {
+ croak "Unsupported option: $_" unless $defaults{$_};
+ }
- } else {
+ # Only certain types are supported.
+ croak "Unsupported type: " . $options->{type} unless $options->{type} =~ m/^(?:html|js|json)$/;
+
+ # The "template" argument must be a string or a reference to one.
+ croak "Unsupported 'template' reference type: " . ref($template) if ref($template) && (ref($template) !~ m/^(?:SCALAR|SL::Presenter::EscapedText)$/);
+
+ # Look for the file given by $template if $template is not a reference.
+ my $source;
+ if (!ref $template) {
$source = "templates/webpages/${template}." . $options->{type};
croak "Template file ${source} not found" unless -f $source;
+
+ } elsif (ref($template) eq 'SCALAR') {
+ # Normal scalar reference: hand over to Template
+ $source = $template;
+
+ } else {
+ # Instance of SL::Presenter::EscapedText. Get reference to its content.
+ $source = \$template->{text};
+ }
+
+ # If no processing is requested then return the content.
+ if (!$options->{process}) {
+ # If $template is a reference then don't try to read a file.
+ return SL::Presenter::EscapedText->new(text => ${ $template }, is_escaped => 1) if ref $template;
+
+ # Otherwise return the file's content.
+ my $file = IO::File->new($source, "r") || croak("Template file ${source} could not be read");
+ my $content = do { local $/ = ''; <$file> };
+ $file->close;
+
+ return SL::Presenter::EscapedText->new(text => $content, is_escaped => 1);
}
+ # Processing was requested. Set up all variables.
my %params = ( %locals,
AUTH => $::auth,
FLASH => $::form->{FLASH},
all of the parameters for controlling the output that the controller's
function does.
-What is rendered and how C<$template> is interpreted is determined by
-the options I<type> and I<inline>.
+What is rendered and how C<$template> is interpreted is determined
+both by C<$template>'s reference type and by the supplied options.
+
+If C<$template> is a normal scalar (not a reference) then it is meant
+to be a template file name relative to the C<templates/webpages>
+directory. The file name to use is determined by the C<type> option.
-If C<< $options->{inline} >> is trueish then C<$template> is a string
-containing the template code to interprete.
+If C<$template> is a reference to a scalar then the referenced
+scalar's content is used as the content to process. The C<type> option
+is not considered in this case.
-If C<< $options->{inline} >> is falsish then C<$template> is
-interpreted as the name of a template file. It is prefixed with
-"templates/webpages/" and postfixed with a file extension based on
-C<< $options->{type} >>. C<< $options->{type} >> can be either C<html>
-or C<js> and defaults to C<html>. An exception will be thrown if that
-file does not exist.
+Other reference types, unknown options and unknown arguments to the
+C<type> option cause the function to L<croak>.
-The template itself has access to the following variables:
+The following options are available:
+
+=over 2
+
+=item C<type>
+
+The template type. Can be C<html> (the default), C<js> for JavaScript
+or C<json> for JSON content. Affects only the extension that's added
+to the file name given with a non-reference C<$template> argument.
+
+=item C<process>
+
+If trueish (which is also the default) it causes the template/content
+to be processed by the Template toolkit. Otherwise the
+template/content is returned as-is.
+
+=back
+
+If template processing is requested then the template has access to
+the following variables:
=over 2
Example: Render a string and return its content for further processing
by the calling function.
- my $content = $presenter->render(
- '[% USE JavaScript %][% JavaScript.replace_with("#someid", "js/something") %]',
- { type => 'js', inline => 1 }
+ my $content = $presenter->render(\'[% USE JavaScript %][% JavaScript.replace_with("#someid", "js/something") %]');
+
+Example: Return the content of a JSON template file without processing
+it at all:
+
+ my $template_content = $presenter->render(
+ 'customer/contact',
+ { type => 'json', process => 0 }
);
=item C<escape $text>
use strict;
+use JSON ();
+
use overload '""' => \&escaped;
sub new {
return $self->{text};
}
+sub TO_JSON {
+ goto &escaped;
+}
+
1;
__END__
my ($self, %params) = @_;
if (!$self->{json}) {
- $self->{json} = JSON->new->allow_nonref(1);
+ $self->{json} = JSON->new->allow_nonref(1)->convert_blessed(1);
my $args = $self->{json_args};
--- /dev/null
+use strict;
+use Test::Exception;
+use Test::More;
+use Test::Output;
+
+use lib 't';
+use Support::TestSetup;
+
+use SL::Presenter;
+
+no warnings 'uninitialized';
+
+Support::TestSetup::login();
+
+sub reset_test_env {
+ $ENV{HTTP_USER_AGENT} = 'Perl Tests';
+
+ $::request = {
+ cgi => CGI->new({}),
+ layout => SL::Layout::Javascript->new,
+ };
+
+ $::myconfig{stylesheet} = 'javascript';
+
+ delete @{ $::form }{qw(header footer)};
+}
+
+my $ctrl = SL::Controller::Base->new;
+
+# Passing invalid parameters:
+throws_ok { $ctrl->render(\'dummy', { unknown => 1 }) } qr/unsupported option/i, 'string ref, unknown parameter';
+throws_ok { $ctrl->render(\'dummy', { type => "excel" }) } qr/unsupported type/i, 'string ref, unsupported "type"';
+throws_ok { $ctrl->render({}) } qr/unsupported.*template.*reference.*type/i, 'string ref, unsupported template argument reference type';
+throws_ok { $ctrl->render('does/not/exist') } qr/template.*file.*not.*found/i, 'non-existing template file name';
+
+# No output:
+stdout_is { $ctrl->render(\'Hallo', { output => 0 }) } '', 'no output';
+
+# Type of return value:
+is(ref($ctrl->render(\'Hallo', { output => 0 })), 'SL::Presenter::EscapedText', 'render returns SL::Presenter::EscapedText');
+
+# Actual return value for string ref parameters (enforce stringification from SL::Presenter::EscapedText before comparison):
+is("" . $ctrl->render(\'Hallo [% world %]', { output => 0 }, world => 'Welt'), 'Hallo Welt', 'render string ref, no output');
+is("" . $ctrl->render(\'Hallo [% world %]', { output => 0, process => 0 }, world => 'Welt'), 'Hallo [% world %]', 'render string ref, no output, no processing');
+is("" . $ctrl->render(\'Hallo [% world %]', { output => 0, type => 'js' }, world => 'Welt'), 'Hallo Welt', 'render string ref, no output, different type');
+
+# Actual return value for template file name parameters (enforce stringification from SL::Presenter::EscapedText before comparison):
+is("" . $ctrl->render('t/render', { output => 0 }, world => 'Welt'), "Hallo Welt\n", 'render template file, no args');
+is("" . $ctrl->render('t/render', { output => 0, process => 0 }, world => 'Welt'), "[\% USE HTML \%]Hallo [\% HTML.escape(world) \%]\n", 'render template file, no processing');
+is("" . $ctrl->render('t/render', { output => 0, type => 'js' }, thingy => 'jungle'), "Welcome to the jungle\n", 'render template file, different type');
+
+# No HTTP header in screen output:
+reset_test_env();
+stdout_unlike { $ctrl->render(\'Hallo [% world %]', { header => 0 }, world => 'Welt') } qr/content-type/i, 'no HTTP header with header=0';
+
+reset_test_env();
+stdout_unlike { $ctrl->render(\'Hallo [% world %]', { header => 0 }, world => 'Welt') } qr/<html>/i, 'no HTML header with header=0';
+
+# With HTTP header in screen output:
+reset_test_env();
+stdout_like { $ctrl->render(\'Hallo [% world %]', world => 'Welt') } qr/content-type/i, 'HTTP header with header=1';
+
+reset_test_env();
+stdout_like { $ctrl->render(\'Hallo [% world %]', world => 'Welt') } qr/<html>/i, 'HTML header with header=1';
+
+# Menu yes/no:
+reset_test_env();
+stdout_like { $ctrl->render(\'Hallo [% world %]', world => 'Welt') } qr/<table.*class=.*menunew/i, 'HTML header & menu with header=1';
+
+reset_test_env();
+stdout_unlike { $ctrl->render(\'Hallo [% world %]', { header => 0 }, world => 'Welt') } qr/<table.*class=.*menunew/i, 'HTML header & menu with header=0';
+
+reset_test_env();
+stdout_unlike { $ctrl->render(\'Hallo [% world %]', { layout => 0 }, world => 'Welt') } qr/<table.*class=.*menunew/i, 'HTML header & menu with layout=0';
+
+done_testing;
+
+1;
--- /dev/null
+use strict;
+use Test::Exception;
+use Test::More;
+
+use lib 't';
+use Support::TestSetup;
+
+use SL::Presenter;
+
+Support::TestSetup::login();
+
+my $pr = SL::Presenter->get;
+
+# Passing invalid parameters:
+throws_ok { $pr->render(\'dummy', { unknown => 1 }) } qr/unsupported option/i, 'string ref, unknown parameter';
+throws_ok { $pr->render(\'dummy', { type => "excel" }) } qr/unsupported type/i, 'string ref, unsupported "type"';
+throws_ok { $pr->render({}) } qr/unsupported.*template.*reference.*type/i, 'string ref, unsupported template argument reference type';
+throws_ok { $pr->render('does/not/exist') } qr/template.*file.*not.*found/i, 'non-existing template file name';
+
+# Type of return value:
+is(ref($pr->render(\'Hallo')), 'SL::Presenter::EscapedText', 'render returns SL::Presenter::EscapedText');
+
+# Actual return value for string ref parameters (enforce stringification from SL::Presenter::EscapedText before comparison):
+is("" . $pr->render(\'Hallo [% world %]', world => 'Welt'), 'Hallo Welt', 'render string ref, no args');
+is("" . $pr->render(\'Hallo [% world %]', { process => 0 }, world => 'Welt'), 'Hallo [% world %]', 'render string ref, no processing');
+is("" . $pr->render(\'Hallo [% world %]', { type => 'js' }, world => 'Welt'), 'Hallo Welt', 'render string ref, different type');
+
+# Actual return value for template file name parameters (enforce stringification from SL::Presenter::EscapedText before comparison):
+is("" . $pr->render('t/render', world => 'Welt'), "Hallo Welt\n", 'render template file, no args');
+is("" . $pr->render('t/render', { process => 0 }, world => 'Welt'), "[\% USE HTML \%]Hallo [\% HTML.escape(world) \%]\n", 'render template file, no processing');
+is("" . $pr->render('t/render', { type => 'js' }, thingy => 'jungle'), "Welcome to the jungle\n", 'render template file, different type');
+
+done_testing;
+
+1;
else
echo -- "$@"
fi
-} | HARNESS_OPTIONS=j:c xargs perl -Imodules/override -MTest::Harness -e 'BEGIN { push @INC, "modules/fallback" } runtests(@ARGV)'
+} | HARNESS_OPTIONS=j:c xargs perl -X -Imodules/override -MTest::Harness -e 'BEGIN { push @INC, "modules/fallback" } runtests(@ARGV)'
+++ /dev/null
-[%- USE HTML %][% USE JSON %][
-[%- FOREACH customer = SELF.customers %]
- {
- "value": [% customer.${SELF.value}.json %],
- "label": [% customer.displayable_name.json %],
- "id": [% customer.id.json %],
- "customernumber": [% customer.customernumber.json %],
- "name": [% customer.name.json %]
- }[% ',' UNLESS loop.last %]
-[%- END %]
-]
--- /dev/null
+[%- USE HTML %][% USE JSON %][
+[%- FOREACH customer = SELF.customers %]
+ {
+ "value": [% customer.${SELF.value}.json %],
+ "label": [% customer.displayable_name.json %],
+ "id": [% customer.id.json %],
+ "customernumber": [% customer.customernumber.json %],
+ "name": [% customer.name.json %]
+ }[% ',' UNLESS loop.last %]
+[%- END %]
+]
+++ /dev/null
-[%- USE HTML %][% USE JSON %][
-[%- FOREACH part = SELF.parts %]
-[%- ajax_autocomplete__label = part.partnumber _ " " _ part.description %]
- {
- "value": [% part.${SELF.value}.json %],
- "label": [% ajax_autocomplete__label.json %],
- "id": [% part.id.json %],
- "partnumber": [% part.partnumber.json %],
- "description": [% part.description.json %],
- "type": [% part.type.json %]
- }[% ',' UNLESS loop.last %]
-[%- END %]
-]
--- /dev/null
+[%- USE HTML %][% USE JSON %][
+[%- FOREACH part = SELF.parts %]
+[%- ajax_autocomplete__label = part.partnumber _ " " _ part.description %]
+ {
+ "value": [% part.${SELF.value}.json %],
+ "label": [% ajax_autocomplete__label.json %],
+ "id": [% part.id.json %],
+ "partnumber": [% part.partnumber.json %],
+ "description": [% part.description.json %],
+ "type": [% part.type.json %]
+ }[% ',' UNLESS loop.last %]
+[%- END %]
+]
--- /dev/null
+[% USE HTML %]Hallo [% HTML.escape(world) %]
--- /dev/null
+Welcome to the [% thingy %]