| import 'package:mustache_template/mustache.dart'; |
| import 'package:test/test.dart'; |
| |
| const String UNEXPECTED_EOF = 'Unexpected end of input'; |
| const String BAD_VALUE_INV_SECTION = 'Invalid value type for inverse section'; |
| const String BAD_TAG_NAME = 'Unless in lenient mode, tags may only contain'; |
| const String VALUE_MISSING = 'Value was missing'; |
| const String UNCLOSED_TAG = 'Unclosed tag'; |
| |
| Template parse(String source, {bool lenient = false}) => |
| Template(source, lenient: lenient); |
| |
| void main() { |
| group('Basic', () { |
| test('Variable', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': 'bob'}); |
| expect(output, equals('_bob_')); |
| }); |
| test('Comment', () { |
| final String output = parse( |
| '_{{! i am a\n comment ! }}_', |
| ).renderString(<dynamic, dynamic>{}); |
| expect(output, equals('__')); |
| }); |
| }); |
| group('Section', () { |
| test('Map', () { |
| final String output = parse( |
| '{{#section}}_{{var}}_{{/section}}', |
| ).renderString(<String, Map<String, String>>{ |
| 'section': <String, String>{'var': 'bob'}, |
| }); |
| expect(output, equals('_bob_')); |
| }); |
| test('List', () { |
| final String output = parse( |
| '{{#section}}_{{var}}_{{/section}}', |
| ).renderString(<String, List<Map<String, String>>>{ |
| 'section': <Map<String, String>>[ |
| <String, String>{'var': 'bob'}, |
| <String, String>{'var': 'jim'}, |
| ], |
| }); |
| expect(output, equals('_bob__jim_')); |
| }); |
| test('Empty List', () { |
| final String output = parse( |
| '{{#section}}_{{var}}_{{/section}}', |
| ).renderString(<String, List<dynamic>>{'section': <dynamic>[]}); |
| expect(output, equals('')); |
| }); |
| test('False', () { |
| final String output = parse( |
| '{{#section}}_{{var}}_{{/section}}', |
| ).renderString(<String, bool>{'section': false}); |
| expect(output, equals('')); |
| }); |
| test('Invalid value', () { |
| final Exception? ex = renderFail( |
| '{{#section}}_{{var}}_{{/section}}', |
| <String, int>{'section': 42}, |
| ); |
| if (ex is TemplateException) { |
| expect(ex.message, startsWith(VALUE_MISSING)); |
| } else { |
| fail('Unexpected type: $ex'); |
| } |
| }); |
| test('Invalid value - lenient mode', () { |
| final String output = parse( |
| '{{#var}}_{{var}}_{{/var}}', |
| lenient: true, |
| ).renderString(<String, int>{'var': 42}); |
| expect(output, equals('_42_')); |
| }); |
| |
| test('True', () { |
| final String output = parse( |
| '{{#section}}_ok_{{/section}}', |
| ).renderString(<String, bool>{'section': true}); |
| expect(output, equals('_ok_')); |
| }); |
| |
| test('Nested', () { |
| final String output = parse( |
| '{{#section}}.{{var}}.{{#nested}}_{{nestedvar}}_{{/nested}}.{{/section}}', |
| ).renderString(<String, Map<String, Object>>{ |
| 'section': <String, Object>{ |
| 'var': 'bob', |
| 'nested': <Map<String, String>>[ |
| <String, String>{'nestedvar': 'jim'}, |
| <String, String>{'nestedvar': 'sally'}, |
| ], |
| }, |
| }); |
| expect(output, equals('.bob._jim__sally_.')); |
| }); |
| |
| test('Whitespace in section tags', () { |
| expect( |
| parse('{{#foo.bar}}oi{{/foo.bar}}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| expect( |
| parse('{{# foo.bar}}oi{{/foo.bar}}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| expect( |
| parse('{{#foo.bar }}oi{{/foo.bar}}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| expect( |
| parse('{{# foo.bar }}oi{{/foo.bar}}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| expect( |
| parse('{{#foo.bar}}oi{{/ foo.bar}}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| expect( |
| parse('{{#foo.bar}}oi{{/foo.bar }}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| expect( |
| parse('{{#foo.bar}}oi{{/ foo.bar }}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| expect( |
| parse('{{# foo.bar }}oi{{/ foo.bar }}').renderString( |
| <String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }, |
| ), |
| equals('oi'), |
| ); |
| }); |
| |
| test('Whitespace in variable tags', () { |
| expect( |
| parse('{{foo.bar}}').renderString(<String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }), |
| equals('true'), |
| ); |
| expect( |
| parse('{{ foo.bar}}').renderString(<String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }), |
| equals('true'), |
| ); |
| expect( |
| parse('{{foo.bar }}').renderString(<String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }), |
| equals('true'), |
| ); |
| expect( |
| parse('{{ foo.bar }}').renderString(<String, Map<String, bool>>{ |
| 'foo': <String, bool>{'bar': true}, |
| }), |
| equals('true'), |
| ); |
| }); |
| |
| test('Odd whitespace in tags', () { |
| void render(String source, dynamic values, dynamic output) => expect( |
| parse(source, lenient: true).renderString(values), |
| equals(output), |
| ); |
| |
| render('{{\t# foo}}oi{{\n/foo}}', <String, bool>{'foo': true}, 'oi'); |
| |
| render( |
| '{{ # # foo }} {{ oi }} {{ / # foo }}', |
| <String, List<Map<String, String>>>{ |
| '# foo': <Map<String, String>>[ |
| <String, String>{'oi': 'OI!'}, |
| ], |
| }, |
| ' OI! ', |
| ); |
| |
| render( |
| '{{ #foo }} {{ oi }} {{ /foo }}', |
| <String, List<Map<String, String>>>{ |
| 'foo': <Map<String, String>>[ |
| <String, String>{'oi': 'OI!'}, |
| ], |
| }, |
| ' OI! ', |
| ); |
| |
| render( |
| '{{\t#foo }} {{ oi }} {{ /foo }}', |
| <String, List<Map<String, String>>>{ |
| 'foo': <Map<String, String>>[ |
| <String, String>{'oi': 'OI!'}, |
| ], |
| }, |
| ' OI! ', |
| ); |
| |
| render('{{{ #foo }}} {{{ /foo }}}', <String, int>{ |
| '#foo': 1, |
| '/foo': 2, |
| }, '1 2'); |
| |
| // Invalid - I'm ok with that for now. |
| // render( |
| // "{{{ { }}}", |
| // {'{': 1}, |
| // '1'); |
| |
| render('{{\nfoo}}', <String, String>{'foo': 'bar'}, 'bar'); |
| |
| render('{{\tfoo}}', <String, String>{'foo': 'bar'}, 'bar'); |
| |
| render('{{\t# foo}}oi{{\n/foo}}', <String, bool>{'foo': true}, 'oi'); |
| |
| render('{{{\tfoo\t}}}', <String, bool>{'foo': true}, 'true'); |
| |
| // TODO(stuartmorgan): Fix and enable this test, which was commented out |
| // when the source was first imported. |
| // empty, or error in strict mode. |
| // render( |
| // "{{ > }}", |
| // {'>': 'oi'}, |
| // ''); |
| }); |
| |
| test('Empty source', () { |
| final Template t = Template(''); |
| expect(t.renderString(<dynamic, dynamic>{}), equals('')); |
| }); |
| |
| test('Template name', () { |
| final Template t = Template('', name: 'foo'); |
| expect(t.name, equals('foo')); |
| }); |
| |
| test('Bad tag', () { |
| expect(() => Template('{{{ foo }|'), throwsException); |
| }); |
| }); |
| |
| group('Inverse Section', () { |
| test('Map', () { |
| final String output = parse( |
| '{{^section}}_{{var}}_{{/section}}', |
| ).renderString(<String, Map<String, String>>{ |
| 'section': <String, String>{'var': 'bob'}, |
| }); |
| expect(output, equals('')); |
| }); |
| test('List', () { |
| final String output = parse( |
| '{{^section}}_{{var}}_{{/section}}', |
| ).renderString(<String, List<Map<String, String>>>{ |
| 'section': <Map<String, String>>[ |
| <String, String>{'var': 'bob'}, |
| <String, String>{'var': 'jim'}, |
| ], |
| }); |
| expect(output, equals('')); |
| }); |
| test('Empty List', () { |
| final String output = parse( |
| '{{^section}}_ok_{{/section}}', |
| ).renderString(<String, List<dynamic>>{'section': <dynamic>[]}); |
| expect(output, equals('_ok_')); |
| }); |
| test('False', () { |
| final String output = parse( |
| '{{^section}}_ok_{{/section}}', |
| ).renderString(<String, bool>{'section': false}); |
| expect(output, equals('_ok_')); |
| }); |
| test('Invalid value', () { |
| final Exception? ex = renderFail( |
| '{{^section}}_{{var}}_{{/section}}', |
| <String, int>{'section': 42}, |
| ); |
| expect(ex is TemplateException, isTrue); |
| expect( |
| (ex! as TemplateException).message, |
| startsWith(BAD_VALUE_INV_SECTION), |
| ); |
| }); |
| test('Invalid value - lenient mode', () { |
| final String output = parse( |
| '{{^var}}_ok_{{/var}}', |
| lenient: true, |
| ).renderString(<String, int>{'var': 42}); |
| expect(output, equals('')); |
| }); |
| test('True', () { |
| final String output = parse( |
| '{{^section}}_ok_{{/section}}', |
| ).renderString(<String, bool>{'section': true}); |
| expect(output, equals('')); |
| }); |
| }); |
| |
| group('Html escape', () { |
| test('Escape at start', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': '&.'}); |
| expect(output, equals('_&._')); |
| }); |
| |
| test('Escape at end', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': '.&'}); |
| expect(output, equals('_.&_')); |
| }); |
| |
| test('&', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': '&'}); |
| expect(output, equals('_&_')); |
| }); |
| |
| test('<', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': '<'}); |
| expect(output, equals('_<_')); |
| }); |
| |
| test('>', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': '>'}); |
| expect(output, equals('_>_')); |
| }); |
| |
| test('"', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': '"'}); |
| expect(output, equals('_"_')); |
| }); |
| |
| test("'", () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': "'"}); |
| expect(output, equals('_'_')); |
| }); |
| |
| test('/', () { |
| final String output = parse( |
| '_{{var}}_', |
| ).renderString(<String, String>{'var': '/'}); |
| expect(output, equals('_/_')); |
| }); |
| }); |
| |
| group('Invalid format', () { |
| test('Mismatched tag', () { |
| const String source = '{{#section}}_{{var}}_{{/notsection}}'; |
| final Exception? ex = renderFail(source, <String, Map<String, String>>{ |
| 'section': <String, String>{'var': 'bob'}, |
| }); |
| expectFail(ex, 1, 22, 'Mismatched tag'); |
| }); |
| |
| test('Unexpected EOF', () { |
| const String source = '{{#section}}_{{var}}_{{/section'; |
| final Exception? ex = renderFail(source, <String, Map<String, String>>{ |
| 'section': <String, String>{'var': 'bob'}, |
| }); |
| expectFail(ex, 1, 31, UNEXPECTED_EOF); |
| }); |
| |
| test('Bad tag name, open section', () { |
| const String source = r'{{#section$%$^%}}_{{var}}_{{/section}}'; |
| final Exception? ex = renderFail(source, <String, Map<String, String>>{ |
| 'section': <String, String>{'var': 'bob'}, |
| }); |
| expectFail(ex, null, null, BAD_TAG_NAME); |
| }); |
| |
| test('Bad tag name, close section', () { |
| const String source = r'{{#section}}_{{var}}_{{/section$%$^%}}'; |
| final Exception? ex = renderFail(source, <String, Map<String, String>>{ |
| 'section': <String, String>{'var': 'bob'}, |
| }); |
| expectFail(ex, null, null, BAD_TAG_NAME); |
| }); |
| |
| test('Bad tag name, variable', () { |
| const String source = r'{{#section}}_{{var$%$^%}}_{{/section}}'; |
| final Exception? ex = renderFail(source, <String, Map<String, String>>{ |
| 'section': <String, String>{'var': 'bob'}, |
| }); |
| expectFail(ex, null, null, BAD_TAG_NAME); |
| }); |
| |
| test('Missing variable', () { |
| const String source = r'{{#section}}_{{var}}_{{/section}}'; |
| final Exception? ex = renderFail(source, <String, Map<dynamic, dynamic>>{ |
| 'section': <dynamic, dynamic>{}, |
| }); |
| expectFail(ex, null, null, VALUE_MISSING); |
| }); |
| |
| // Null variables shouldn't be a problem. |
| test('Null variable', () { |
| final Template t = Template('{{#section}}_{{var}}_{{/section}}'); |
| final String output = t.renderString(<String, Map<String, void>>{ |
| 'section': <String, void>{'var': null}, |
| }); |
| expect(output, equals('__')); |
| }); |
| |
| test('Unclosed section', () { |
| const String source = r'{{#section}}foo'; |
| final Exception? ex = renderFail(source, <String, Map<dynamic, dynamic>>{ |
| 'section': <dynamic, dynamic>{}, |
| }); |
| expectFail(ex, null, null, UNCLOSED_TAG); |
| }); |
| }); |
| |
| group('Lenient', () { |
| test('Odd section name', () { |
| final String output = parse( |
| r'{{#section$%$^%}}_{{var}}_{{/section$%$^%}}', |
| lenient: true, |
| ).renderString(<String, Map<String, String>>{ |
| r'section$%$^%': <String, String>{'var': 'bob'}, |
| }); |
| expect(output, equals('_bob_')); |
| }); |
| |
| test('Odd variable name', () { |
| final String output = parse( |
| r'{{#section}}_{{var$%$^%}}_{{/section}}', |
| lenient: true, |
| ).renderString(<String, Map<String, String>>{ |
| 'section': <String, String>{r'var$%$^%': 'bob'}, |
| }); |
| expect(output, equals('_bob_')); |
| }); |
| |
| test('Null variable', () { |
| final String output = parse( |
| r'{{#section}}_{{var}}_{{/section}}', |
| lenient: true, |
| ).renderString(<String, Map<String, void>>{ |
| 'section': <String, void>{'var': null}, |
| }); |
| expect(output, equals('__')); |
| }); |
| |
| test('Null section', () { |
| final String output = parse( |
| '{{#section}}_{{var}}_{{/section}}', |
| lenient: true, |
| ).renderString(<String, void>{'section': null}); |
| expect(output, equals('')); |
| }); |
| |
| // Known failure |
| // test('Null inverse section', () { |
| // var output = parse('{{^section}}_{{var}}_{{/section}}', lenient: true) |
| // .renderString({"section": null}, lenient: true); |
| // expect(output, equals('')); |
| // }); |
| }); |
| |
| group('Escape tags', () { |
| test('{{{ ... }}}', () { |
| final String output = parse( |
| '{{{blah}}}', |
| ).renderString(<String, String>{'blah': '&'}); |
| expect(output, equals('&')); |
| }); |
| test('{{& ... }}', () { |
| final String output = parse( |
| '{{{blah}}}', |
| ).renderString(<String, String>{'blah': '&'}); |
| expect(output, equals('&')); |
| }); |
| }); |
| |
| group('Partial tag', () { |
| String partialTest( |
| Map<String, Object> values, |
| Map<String, Object> sources, |
| String renderTemplate, { |
| bool lenient = false, |
| }) { |
| final Map<String, Template> templates = <String, Template>{}; |
| Template? resolver(String name) => templates[name]; |
| for (final String k in sources.keys) { |
| templates[k] = Template( |
| sources[k]! as String, |
| name: k, |
| lenient: lenient, |
| partialResolver: resolver, |
| ); |
| } |
| final Template? t = resolver(renderTemplate); |
| return t!.renderString(values); |
| } |
| |
| test('basic', () { |
| final String output = partialTest( |
| <String, Object>{'foo': 'bar'}, |
| <String, Object>{'root': '{{>partial}}', 'partial': '{{foo}}'}, |
| 'root', |
| ); |
| expect(output, 'bar'); |
| }); |
| |
| test('missing partial strict', () { |
| bool threw = false; |
| try { |
| partialTest( |
| <String, Object>{'foo': 'bar'}, |
| <String, Object>{'root': '{{>partial}}'}, |
| 'root', |
| ); |
| } on Exception catch (e) { |
| expect(e is TemplateException, isTrue); |
| threw = true; |
| } |
| expect(threw, isTrue); |
| }); |
| |
| test('missing partial lenient', () { |
| final String output = partialTest( |
| <String, Object>{'foo': 'bar'}, |
| <String, Object>{'root': '{{>partial}}'}, |
| 'root', |
| lenient: true, |
| ); |
| expect(output, equals('')); |
| }); |
| |
| test('context', () { |
| final String output = partialTest( |
| <String, Object>{'text': 'content'}, |
| <String, Object>{'root': '"{{>partial}}"', 'partial': '*{{text}}*'}, |
| 'root', |
| lenient: true, |
| ); |
| expect(output, equals('"*content*"')); |
| }); |
| |
| test('recursion', () { |
| final String output = partialTest( |
| <String, Object>{ |
| 'content': 'X', |
| 'nodes': <Map<String, Object>>[ |
| <String, Object>{'content': 'Y', 'nodes': <dynamic>[]}, |
| ], |
| }, |
| <String, Object>{ |
| 'root': '{{>node}}', |
| 'node': '{{content}}<{{#nodes}}{{>node}}{{/nodes}}>', |
| }, |
| 'root', |
| lenient: true, |
| ); |
| expect(output, equals('X<Y<>>')); |
| }); |
| |
| test('standalone without previous', () { |
| final String output = partialTest( |
| <String, Object>{}, |
| <String, Object>{'root': ' {{>partial}}\n>', 'partial': '>\n>'}, |
| 'root', |
| lenient: true, |
| ); |
| expect(output, equals(' >\n >>')); |
| }); |
| |
| test('standalone indentation', () { |
| final String output = partialTest( |
| <String, Object>{'content': '<\n->'}, |
| <String, Object>{ |
| 'root': '\\\n {{>partial}}\n/\n', |
| 'partial': '|\n{{{content}}}\n|\n', |
| }, |
| 'root', |
| lenient: true, |
| ); |
| expect(output, equals('\\\n |\n <\n->\n |\n/\n')); |
| }); |
| }); |
| |
| group('Lambdas', () { |
| void lambdaTest({ |
| required String template, |
| dynamic lambda, |
| dynamic output, |
| }) => expect( |
| parse(template).renderString(<String, dynamic>{'lambda': lambda}), |
| equals(output), |
| ); |
| |
| test('basic', () { |
| lambdaTest( |
| template: 'Hello, {{lambda}}!', |
| lambda: (_) => 'world', |
| output: 'Hello, world!', |
| ); |
| }); |
| |
| test('escaping', () { |
| lambdaTest( |
| template: '<{{lambda}}{{{lambda}}}', |
| lambda: (_) => '>', |
| output: '<>>', |
| ); |
| }); |
| |
| test('sections', () { |
| lambdaTest( |
| template: '{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}', |
| lambda: (LambdaContext ctx) => '__${ctx.renderString()}__', |
| output: '__FILE__ != __LINE__', |
| ); |
| }); |
| |
| // TODO(stuartmorgan): Fix and re-enable this test, which was skipped when |
| // the package was first imported. |
| test('inverted sections truthy', () { |
| const String template = '<{{^lambda}}{{static}}{{/lambda}}>'; |
| final Map<String, Object> values = <String, Object>{ |
| 'lambda': (_) => false, |
| 'static': 'static', |
| }; |
| const String output = '<>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }, skip: 'skip test'); |
| |
| test("seth's use case", () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, Object> values = <String, Object>{ |
| 'markdown': (LambdaContext ctx) => ctx.renderString().toLowerCase(), |
| 'content': 'OI YOU!', |
| }; |
| const String output = '<oi you!>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('Lambda v2', () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, Object> values = <String, Object>{ |
| 'markdown': (LambdaContext ctx) => ctx.source, |
| 'content': 'OI YOU!', |
| }; |
| const String output = '<{{content}}>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('Lambda v2...', () { |
| const String template = |
| '<{{#markdown}}dsfsf dsfsdf dfsdfsd{{/markdown}}>'; |
| final Map<String, dynamic Function(dynamic ctx)> values = |
| <String, dynamic Function(dynamic ctx)>{ |
| // ignore: avoid_dynamic_calls |
| 'markdown': (dynamic ctx) => ctx.source, |
| }; |
| const String output = '<dsfsf dsfsdf dfsdfsd>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('Alternate Delimiters', () { |
| // A lambda's return value should parse with the default delimiters. |
| |
| const String template = '{{= | | =}}\nHello, (|&lambda|)!'; |
| |
| //function() { return "|planet| => {{planet}}" } |
| final Map<String, Object> values = <String, Object>{ |
| 'planet': 'world', |
| 'lambda': |
| (LambdaContext ctx) => ctx.renderSource('|planet| => {{planet}}'), |
| }; |
| |
| const String output = 'Hello, (|planet| => world)!'; |
| |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('Alternate Delimiters 2', () { |
| // Lambdas used for sections should parse with the current delimiters. |
| |
| const String template = '{{= | | =}}<|#lambda|-|/lambda|>'; |
| |
| //function() { return "|planet| => {{planet}}" } |
| final Map<String, Object> values = <String, Object>{ |
| 'planet': 'Earth', |
| 'lambda': (LambdaContext ctx) { |
| final String txt = ctx.source; |
| return ctx.renderSource('$txt{{planet}} => |planet|$txt'); |
| }, |
| }; |
| |
| const String output = '<-{{planet}} => Earth->'; |
| |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('LambdaContext.lookup', () { |
| final Template t = Template('{{ foo }}'); |
| final String s = t.renderString(<String, Object>{ |
| 'foo': (LambdaContext lc) => lc.lookup('bar'), |
| 'bar': 'jim', |
| }); |
| expect(s, equals('jim')); |
| }); |
| |
| test('LambdaContext.lookup closed', () { |
| final Template t = Template('{{ foo }}'); |
| LambdaContext? lc2; |
| t.renderString(<String, Object>{ |
| 'foo': (LambdaContext lc) => lc2 = lc, |
| 'bar': 'jim', |
| }); |
| expect(lc2, isNotNull); |
| expect(() => lc2?.lookup('foo'), throwsException); |
| }); |
| }); |
| |
| group('Other', () { |
| test('Standalone line', () { |
| final String val = parse( |
| '|\n{{#bob}}\n{{/bob}}\n|', |
| ).renderString(<String, List<dynamic>>{'bob': <dynamic>[]}); |
| expect(val, equals('|\n|')); |
| }); |
| }); |
| |
| group('Array indexing', () { |
| test('Basic', () { |
| final String val = parse('{{array.1}}').renderString(<String, List<int>>{ |
| 'array': <int>[1, 2, 3], |
| }); |
| expect(val, equals('2')); |
| }); |
| test('RangeError', () { |
| final Exception? error = renderFail('{{array.5}}', <String, List<int>>{ |
| 'array': <int>[1, 2, 3], |
| }); |
| expect(error, isA<TemplateException>()); |
| }); |
| }); |
| |
| group('Delimiters', () { |
| test('Basic', () { |
| final String val = parse( |
| '{{=<% %>=}}(<%text%>)', |
| ).renderString(<String, String>{'text': 'Hey!'}); |
| expect(val, equals('(Hey!)')); |
| }); |
| |
| test('Single delimiters', () { |
| final String val = parse( |
| '({{=[ ]=}}[text])', |
| ).renderString(<String, String>{'text': 'It worked!'}); |
| expect(val, equals('(It worked!)')); |
| }); |
| }); |
| |
| group('Template with custom delimiters', () { |
| test('Basic', () { |
| final Template t = Template('(<%text%>)', delimiters: '<% %>'); |
| final String val = t.renderString(<String, String>{'text': 'Hey!'}); |
| expect(val, equals('(Hey!)')); |
| }); |
| }); |
| |
| group('Lambda context', () { |
| test('LambdaContext write', () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, Null Function(LambdaContext ctx)> values = |
| <String, Null Function(LambdaContext ctx)>{ |
| 'markdown': (LambdaContext ctx) { |
| ctx.write('foo'); |
| }, |
| }; |
| const String output = '<foo>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('LambdaContext render', () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, Object> values = <String, Object>{ |
| 'content': 'bar', |
| 'markdown': (LambdaContext ctx) { |
| ctx.render(); |
| }, |
| }; |
| const String output = '<bar>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('LambdaContext render with value', () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, Null Function(LambdaContext ctx)> values = |
| <String, Null Function(LambdaContext ctx)>{ |
| 'markdown': (LambdaContext ctx) { |
| ctx.render(value: <String, String>{'content': 'oi!'}); |
| }, |
| }; |
| const String output = '<oi!>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('LambdaContext renderString with value', () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, String Function(LambdaContext ctx)> values = |
| <String, String Function(LambdaContext ctx)>{ |
| 'markdown': (LambdaContext ctx) { |
| return ctx.renderString( |
| value: <String, String>{'content': 'oi!'}, |
| ); |
| }, |
| }; |
| const String output = '<oi!>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('LambdaContext write and return', () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, String Function(LambdaContext ctx)> values = |
| <String, String Function(LambdaContext ctx)>{ |
| 'markdown': (LambdaContext ctx) { |
| ctx.write('foo'); |
| return 'bar'; |
| }, |
| }; |
| const String output = '<foobar>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| |
| test('LambdaContext renderSource with value', () { |
| const String template = '<{{#markdown}}{{content}}{{/markdown}}>'; |
| final Map<String, String Function(LambdaContext ctx)> values = |
| <String, String Function(LambdaContext ctx)>{ |
| 'markdown': (LambdaContext ctx) { |
| return ctx.renderSource( |
| ctx.source, |
| value: <String, String>{'content': 'oi!'}, |
| ); |
| }, |
| }; |
| const String output = '<oi!>'; |
| expect(parse(template).renderString(values), equals(output)); |
| }); |
| }); |
| } |
| |
| Exception? renderFail(String source, Object values) { |
| try { |
| parse(source).renderString(values); |
| return null; |
| } on Exception catch (e) { |
| return e; |
| } |
| } |
| |
| void expectFail( |
| Exception? ex, |
| int? line, |
| int? column, [ |
| String? msgStartsWith, |
| ]) { |
| if (ex is! TemplateException) { |
| fail('Unexpected type: $ex'); |
| } |
| if (line != null) { |
| expect(ex.line, equals(line)); |
| } |
| if (column != null) { |
| expect(ex.column, equals(column)); |
| } |
| if (msgStartsWith != null) { |
| expect(ex.message, startsWith(msgStartsWith)); |
| } |
| } |