import 'package:code_buffer/code_buffer.dart'; import 'package:jael/jael.dart' as jael; import 'package:symbol_table/symbol_table.dart'; import 'package:test/test.dart'; void main() { test('attribute binding', () { const template = ''' <html> <body> <h1>Hello</h1> <img ready="always" data-img-src=profile['avatar'] /> <input name="csrf_token" type="hidden" value=csrf_token> </body> </html> '''; var buf = CodeBuffer(); jael.Document? document; late SymbolTable scope; try { document = jael.parseDocument(template, sourceUrl: 'test.jael'); scope = SymbolTable<dynamic>(values: { 'csrf_token': 'foo', 'profile': { 'avatar': 'thosakwe.png', } }); } on jael.JaelError catch (e) { print(e); print(e.stackTrace); } expect(document, isNotNull); const jael.Renderer().render(document!, buf, scope); print(buf); expect( buf.toString(), ''' <html> <body> <h1> Hello </h1> <img ready="always" data-img-src="thosakwe.png"> <input name="csrf_token" type="hidden" value="foo"> </body> </html> ''' .trim()); }); test('interpolation', () { const template = ''' <!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <body> <h1>Pokémon</h1> {{ pokemon.name }} - {{ pokemon.type }} <img> </body> </html> '''; var buf = CodeBuffer(); //jael.scan(template, sourceUrl: 'test.jael').tokens.forEach(print); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable<dynamic>(values: { 'pokemon': const _Pokemon('Darkrai', 'Dark'), }); const jael.Renderer().render(document, buf, scope); print(buf); expect( buf.toString().replaceAll('\n', '').replaceAll(' ', '').trim(), ''' <!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <body> <h1> Pokémon </h1> Darkrai - Dark <img/> </body> </html> ''' .replaceAll('\n', '') .replaceAll(' ', '') .trim()); }); test('for loop', () { const template = ''' <html> <body> <h1>Pokémon</h1> <ul> <li for-each=starters as="starter" index-as="idx">#{{ idx }} {{ starter.name }} - {{ starter.type }}</li> </ul> </body> </html> '''; var buf = CodeBuffer(); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable<dynamic>(values: { 'starters': starters, }); const jael.Renderer().render(document, buf, scope); print(buf); expect( buf.toString(), ''' <html> <body> <h1> Pokémon </h1> <ul> <li> #0 Bulbasaur - Grass </li> <li> #1 Charmander - Fire </li> <li> #2 Squirtle - Water </li> </ul> </body> </html> ''' .trim()); }); test('conditional', () { const template = ''' <html> <body> <h1>Conditional</h1> <b if=starters.isEmpty>Empty</b> <b if=starters.isNotEmpty>Not empty</b> </body> </html> '''; var buf = CodeBuffer(); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable<dynamic>(values: { 'starters': starters, }); const jael.Renderer().render(document, buf, scope); print(buf); expect( buf.toString(), ''' <html> <body> <h1> Conditional </h1> <b> Not empty </b> </body> </html> ''' .trim()); }); test('declare', () { const template = ''' <div> <declare one=1 two=2 three=3> <ul> <li>{{one}}</li> <li>{{two}}</li> <li>{{three}}</li> </ul> <ul> <declare three=4> <li>{{one}}</li> <li>{{two}}</li> <li>{{three}}</li> </declare> </ul> </declare> </div> '''; var buf = CodeBuffer(); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable(); const jael.Renderer().render(document, buf, scope); print(buf); expect( buf.toString(), ''' <div> <ul> <li> 1 </li> <li> 2 </li> <li> 3 </li> </ul> <ul> <li> 1 </li> <li> 2 </li> <li> 4 </li> </ul> </div> ''' .trim()); }); test('unescaped attr/interp', () { const template = ''' <div> <img src!="<SCARY XSS>" /> {{- "<MORE SCARY XSS>" }} </div> '''; var buf = CodeBuffer(); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable(); const jael.Renderer().render(document, buf, scope); print(buf); expect( buf.toString().replaceAll('\n', '').replaceAll(' ', '').trim(), ''' <div> <img src="<SCARY XSS>"> <MORE SCARY XSS> </div> ''' .replaceAll('\n', '') .replaceAll(' ', '') .trim()); }); test('quoted attribute name', () { const template = ''' <button '(click)'="myEventHandler(\$event)"></button> '''; var buf = CodeBuffer(); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable(); const jael.Renderer().render(document, buf, scope); print(buf); expect( buf.toString(), ''' <button (click)="myEventHandler(\$event)"> </button> ''' .trim()); }); test('switch', () { const template = ''' <switch value=account.isDisabled> <case value=true> BAN HAMMER LOLOL </case> <case value=false> You are in good standing. </case> <default> Weird... </default> </switch> '''; var buf = CodeBuffer(); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable<dynamic>(values: { 'account': _Account(isDisabled: true), }); const jael.Renderer().render(document, buf, scope); print(buf); expect(buf.toString().trim(), 'BAN HAMMER LOLOL'); }); test('default', () { const template = ''' <switch value=account.isDisabled> <case value=true> BAN HAMMER LOLOL </case> <case value=false> You are in good standing. </case> <default> Weird... </default> </switch> '''; var buf = CodeBuffer(); var document = jael.parseDocument(template, sourceUrl: 'test.jael')!; var scope = SymbolTable<dynamic>(values: { 'account': _Account(isDisabled: null), }); const jael.Renderer().render(document, buf, scope); print(buf); expect(buf.toString().trim(), 'Weird...'); }); } const List<_Pokemon> starters = [ _Pokemon('Bulbasaur', 'Grass'), _Pokemon('Charmander', 'Fire'), _Pokemon('Squirtle', 'Water'), ]; class _Pokemon { final String name, type; const _Pokemon(this.name, this.type); } class _Account { final bool? isDisabled; _Account({this.isDisabled}); }