// Specification files can be downloaded here https://github.com/mustache/spec // Test implemented by Georgios Valotasios. // See: https://github.com/valotas/mustache4dart import 'dart:convert'; import 'dart:io'; import 'package:mustache_template/mustache.dart'; import 'package:test/test.dart'; String render( String source, dynamic values, { required String? Function(String) partial, }) { late Template? Function(String) resolver; resolver = (String name) { final String? source = partial(name); if (source == null) { return null; } return Template(source, partialResolver: resolver, lenient: true); }; final Template t = Template(source, partialResolver: resolver, lenient: true); return t.renderString(values); } void main() { defineTests(); } void defineTests() { final Directory specsDir = Directory('test/spec/specs'); specsDir.listSync().forEach((FileSystemEntity f) { if (f is File) { final String filename = f.path; if (shouldRun(filename)) { final String text = f.readAsStringSync(); _defineGroupFromFile(filename, text); } } }); } void _defineGroupFromFile(String filename, String text) { final Map jsondata = (json.decode(text) as Map).cast(); final List> tests = (jsondata['tests']! as List).cast>(); filename = filename.substring(filename.lastIndexOf('/') + 1); group('Specs of $filename', () { for (final Map t in tests) { final StringBuffer testDescription = StringBuffer(t['name']! as String); testDescription.write(': '); testDescription.write(t['desc']); final String template = t['template']! as String; final Object? data = t['data']; final String templateOneline = template .replaceAll('\n', r'\n') .replaceAll('\r', r'\r'); final StringBuffer reason = StringBuffer( "Could not render right '''$templateOneline'''", ); final Object? expected = t['expected']; final Map? partials = t['partials'] as Map?; String? partial(String name) { if (partials == null) { return null; } return partials[name] as String?; } //swap the data.lambda with a dart real function if (data is Map && data['lambda'] != null) { data['lambda'] = lambdas[t['name']]; } reason.write(" with '$data'"); if (partials != null) { reason.write(' and partial: $partials'); } test( testDescription.toString(), () => expect( render(template, data, partial: partial), expected, reason: reason.toString(), ), ); } }); } bool shouldRun(String filename) { // filter out only .json files if (!filename.endsWith('.json')) { return false; } return true; } String Function(Object?) _dummyCallableWithState() { int callCounter = 0; return (Object? arg) { callCounter++; return callCounter.toString(); }; } String Function(LambdaContext) wrapLambda(Object? Function(Object?) f) => (LambdaContext ctx) => ctx.renderSource(f(ctx.source).toString()); Map lambdas = { 'Interpolation': wrapLambda((Object? t) => 'world'), 'Interpolation - Expansion': wrapLambda((Object? t) => '{{planet}}'), 'Interpolation - Alternate Delimiters': wrapLambda( (Object? t) => '|planet| => {{planet}}', ), 'Interpolation - Multiple Calls': wrapLambda( _dummyCallableWithState(), ), //function() { return (g=(function(){return this})()).calls=(g.calls||0)+1 } 'Escaping': wrapLambda((Object? t) => '>'), 'Section': wrapLambda((Object? txt) => txt == '{{x}}' ? 'yes' : 'no'), 'Section - Expansion': wrapLambda((Object? txt) => '$txt{{planet}}$txt'), 'Section - Alternate Delimiters': wrapLambda( (Object? txt) => '$txt{{planet}} => |planet|$txt', ), 'Section - Multiple Calls': wrapLambda((Object? t) => '__${t}__'), 'Inverted Section': wrapLambda((Object? txt) => false), };