| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // TODO(davidmartos96): Remove this tag once this test's state leaks/test |
| // dependencies have been fixed. |
| // https://github.com/flutter/flutter/issues/142716 |
| // Fails with "flutter test --test-randomize-ordering-seed=20240201" |
| @Tags(<String>['no-shuffle']) |
| library; |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:gen_defaults/template.dart'; |
| import 'package:gen_defaults/token_logger.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| |
| void main() { |
| final TokenLogger logger = tokenLogger; |
| // Required init with empty at least once to init late fields. |
| // Then we can use the `clear` method. |
| logger.init(allTokens: <String, dynamic>{}, versionMap: <String, List<String>>{}); |
| |
| setUp(() { |
| // Cleanup the global token logger before each test, to not be tied to a particular |
| // test order. |
| logger.clear(); |
| }); |
| |
| test('Templates will append to the end of a file', () { |
| final Directory tempDir = Directory.systemTemp.createTempSync('gen_defaults'); |
| try { |
| // Create a temporary file with some content. |
| final File tempFile = File(path.join(tempDir.path, 'test_template.txt')); |
| tempFile.createSync(); |
| tempFile.writeAsStringSync(''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| '''); |
| |
| // Have a test template append new parameterized content to the end of |
| // the file. |
| final Map<String, dynamic> tokens = <String, dynamic>{'version': '0.0', 'foo': 'Foobar', 'bar': 'Barfoo'}; |
| TestTemplate('Test', tempFile.path, tokens).updateFile(); |
| |
| expect(tempFile.readAsStringSync(), ''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Test |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'Foobar'; |
| static final String tokenBar = 'Barfoo'; |
| |
| // END GENERATED TOKEN PROPERTIES - Test |
| '''); |
| |
| } finally { |
| tempDir.deleteSync(recursive: true); |
| } |
| }); |
| |
| test('Templates will update over previously generated code at the end of a file', () { |
| final Directory tempDir = Directory.systemTemp.createTempSync('gen_defaults'); |
| try { |
| // Create a temporary file with some content. |
| final File tempFile = File(path.join(tempDir.path, 'test_template.txt')); |
| tempFile.createSync(); |
| tempFile.writeAsStringSync(''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Test |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'Foobar'; |
| static final String tokenBar = 'Barfoo'; |
| |
| // END GENERATED TOKEN PROPERTIES - Test |
| '''); |
| |
| // Have a test template append new parameterized content to the end of |
| // the file. |
| final Map<String, dynamic> tokens = <String, dynamic>{'version': '0.0', 'foo': 'foo', 'bar': 'bar'}; |
| TestTemplate('Test', tempFile.path, tokens).updateFile(); |
| |
| expect(tempFile.readAsStringSync(), ''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Test |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'foo'; |
| static final String tokenBar = 'bar'; |
| |
| // END GENERATED TOKEN PROPERTIES - Test |
| '''); |
| |
| } finally { |
| tempDir.deleteSync(recursive: true); |
| } |
| }); |
| |
| test('Multiple templates can modify different code blocks in the same file', () { |
| final Directory tempDir = Directory.systemTemp.createTempSync('gen_defaults'); |
| try { |
| // Create a temporary file with some content. |
| final File tempFile = File(path.join(tempDir.path, 'test_template.txt')); |
| tempFile.createSync(); |
| tempFile.writeAsStringSync(''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| '''); |
| |
| // Update file with a template for 'Block 1' |
| { |
| final Map<String, dynamic> tokens = <String, dynamic>{'version': '0.0', 'foo': 'foo', 'bar': 'bar'}; |
| TestTemplate('Block 1', tempFile.path, tokens).updateFile(); |
| } |
| expect(tempFile.readAsStringSync(), ''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Block 1 |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'foo'; |
| static final String tokenBar = 'bar'; |
| |
| // END GENERATED TOKEN PROPERTIES - Block 1 |
| '''); |
| |
| // Update file with a template for 'Block 2', which should append but not |
| // disturb the code in 'Block 1'. |
| { |
| final Map<String, dynamic> tokens = <String, dynamic>{'version': '0.0', 'foo': 'bar', 'bar': 'foo'}; |
| TestTemplate('Block 2', tempFile.path, tokens).updateFile(); |
| } |
| expect(tempFile.readAsStringSync(), ''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Block 1 |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'foo'; |
| static final String tokenBar = 'bar'; |
| |
| // END GENERATED TOKEN PROPERTIES - Block 1 |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Block 2 |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'bar'; |
| static final String tokenBar = 'foo'; |
| |
| // END GENERATED TOKEN PROPERTIES - Block 2 |
| '''); |
| |
| // Update 'Block 1' again which should just update that block, |
| // leaving 'Block 2' undisturbed. |
| { |
| final Map<String, dynamic> tokens = <String, dynamic>{'version': '0.0', 'foo': 'FOO', 'bar': 'BAR'}; |
| TestTemplate('Block 1', tempFile.path, tokens).updateFile(); |
| } |
| expect(tempFile.readAsStringSync(), ''' |
| // This is a file with stuff in it. |
| // This part shouldn't be changed by |
| // the template. |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Block 1 |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'FOO'; |
| static final String tokenBar = 'BAR'; |
| |
| // END GENERATED TOKEN PROPERTIES - Block 1 |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - Block 2 |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| static final String tokenFoo = 'bar'; |
| static final String tokenBar = 'foo'; |
| |
| // END GENERATED TOKEN PROPERTIES - Block 2 |
| '''); |
| |
| } finally { |
| tempDir.deleteSync(recursive: true); |
| } |
| }); |
| |
| test('Templates can get proper shapes from given data', () { |
| const Map<String, dynamic> tokens = <String, dynamic>{ |
| 'foo.shape': 'shape.large', |
| 'bar.shape': 'shape.full', |
| 'shape.large': <String, dynamic>{ |
| 'family': 'SHAPE_FAMILY_ROUNDED_CORNERS', |
| 'topLeft': 1.0, |
| 'topRight': 2.0, |
| 'bottomLeft': 3.0, |
| 'bottomRight': 4.0, |
| }, |
| 'shape.full': <String, dynamic>{ |
| 'family': 'SHAPE_FAMILY_CIRCULAR', |
| }, |
| }; |
| final TestTemplate template = TestTemplate('Test', 'foobar.dart', tokens); |
| expect(template.shape('foo'), 'const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(1.0), topRight: Radius.circular(2.0), bottomLeft: Radius.circular(3.0), bottomRight: Radius.circular(4.0)))'); |
| expect(template.shape('bar'), 'const StadiumBorder()'); |
| }); |
| |
| group('Tokens logger', () { |
| final List<String> printLog = List<String>.empty(growable: true); |
| final Map<String, List<String>> versionMap = <String, List<String>>{}; |
| final Map<String, dynamic> allTokens = <String, dynamic>{}; |
| |
| // Add to printLog instead of printing to stdout |
| void Function() overridePrint(void Function() testFn) => () { |
| final ZoneSpecification spec = ZoneSpecification( |
| print: (_, __, ___, String msg) { |
| printLog.add(msg); |
| } |
| ); |
| return Zone.current.fork(specification: spec).run<void>(testFn); |
| }; |
| |
| setUp(() { |
| logger.init(allTokens: allTokens, versionMap: versionMap); |
| }); |
| |
| tearDown(() { |
| logger.clear(); |
| printLog.clear(); |
| versionMap.clear(); |
| allTokens.clear(); |
| }); |
| |
| String errorColoredString(String str) => '\x1B[31m$str\x1B[0m'; |
| |
| const Map<String, List<String>> testVersions = <String, List<String>>{ |
| 'v1.0.0': <String>['file_1.json'], |
| 'v2.0.0': <String>['file_2.json, file_3.json'], |
| }; |
| |
| test('can print empty usage', overridePrint(() { |
| logger.printVersionUsage(verbose: true); |
| expect(printLog, contains('Versions used: ')); |
| |
| logger.printTokensUsage(verbose: true); |
| expect(printLog, contains('Tokens used: 0/0')); |
| })); |
| |
| test('can print version usage', overridePrint(() { |
| versionMap.addAll(testVersions); |
| |
| logger.printVersionUsage(verbose: false); |
| |
| expect(printLog, contains('Versions used: v1.0.0, v2.0.0')); |
| })); |
| |
| test('can print version usage (verbose)', overridePrint(() { |
| versionMap.addAll(testVersions); |
| |
| logger.printVersionUsage(verbose: true); |
| |
| expect(printLog, contains('Versions used: v1.0.0, v2.0.0')); |
| expect(printLog, contains(' v1.0.0:')); |
| expect(printLog, contains(' file_1.json')); |
| expect(printLog, contains(' v2.0.0:')); |
| expect(printLog, contains(' file_2.json, file_3.json')); |
| })); |
| |
| test('can log and print tokens usage', overridePrint(() { |
| allTokens['foo'] = 'value'; |
| |
| logger.log('foo'); |
| logger.printTokensUsage(verbose: false); |
| |
| expect(printLog, contains('Tokens used: 1/1')); |
| })); |
| |
| test('can log and print tokens usage (verbose)', overridePrint(() { |
| allTokens['foo'] = 'value'; |
| |
| logger.log('foo'); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('✅ foo')); |
| expect(printLog, contains('Tokens used: 1/1')); |
| })); |
| |
| test('detects invalid logs', overridePrint(() { |
| allTokens['foo'] = 'value'; |
| |
| logger.log('baz'); |
| logger.log('foobar'); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('❌ foo')); |
| expect(printLog, contains('Tokens used: 0/1')); |
| expect(printLog, contains(errorColoredString('Some referenced tokens do not exist: 2'))); |
| expect(printLog, contains(' baz')); |
| expect(printLog, contains(' foobar')); |
| })); |
| |
| test("color function doesn't log when providing a default", overridePrint(() { |
| allTokens['color_foo_req'] = 'value'; |
| |
| // color_foo_opt is not available, but because it has a default value, it won't warn about it |
| |
| TestColorTemplate('block', 'filename', allTokens).generate(); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('✅ color_foo_req')); |
| expect(printLog, contains('Tokens used: 1/1')); |
| })); |
| |
| test('color function logs when not providing a default', overridePrint(() { |
| // Nor color_foo_req or color_foo_opt are available, but only color_foo_req will be logged. |
| // This mimics a token being removed, but expected to exist. |
| |
| TestColorTemplate('block', 'filename', allTokens).generate(); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('Tokens used: 0/0')); |
| expect(printLog, contains(errorColoredString('Some referenced tokens do not exist: 1'))); |
| expect(printLog, contains(' color_foo_req')); |
| })); |
| |
| test('border function logs width token when available', overridePrint(() { |
| allTokens['border_foo.color'] = 'red'; |
| allTokens['border_foo.width'] = 3.0; |
| |
| TestBorderTemplate('block', 'filename', allTokens).generate(); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('✅ border_foo.color')); |
| expect(printLog, contains('✅ border_foo.width')); |
| expect(printLog, contains('Tokens used: 2/2')); |
| })); |
| |
| test('border function logs height token when width token not available', overridePrint(() { |
| allTokens['border_foo.color'] = 'red'; |
| allTokens['border_foo.height'] = 3.0; |
| |
| TestBorderTemplate('block', 'filename', allTokens).generate(); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('✅ border_foo.color')); |
| expect(printLog, contains('✅ border_foo.height')); |
| expect(printLog, contains('Tokens used: 2/2')); |
| })); |
| |
| test("border function doesn't log when width or height tokens not available", overridePrint(() { |
| allTokens['border_foo.color'] = 'red'; |
| |
| TestBorderTemplate('block', 'filename', allTokens).generate(); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('✅ border_foo.color')); |
| expect(printLog, contains('Tokens used: 1/1')); |
| })); |
| |
| test('can log and dump versions & tokens to a file', overridePrint(() { |
| versionMap.addAll(testVersions); |
| allTokens['foo'] = 'value'; |
| allTokens['bar'] = 'value'; |
| |
| logger.log('foo'); |
| logger.log('bar'); |
| logger.dumpToFile('test.json'); |
| |
| final String fileContent = File('test.json').readAsStringSync(); |
| expect(fileContent, contains('Versions used, v1.0.0, v2.0.0')); |
| expect(fileContent, contains('bar,')); |
| expect(fileContent, contains('foo')); |
| })); |
| |
| test('integration test', overridePrint(() { |
| allTokens['foo'] = 'value'; |
| allTokens['bar'] = 'value'; |
| |
| TestTemplate('block', 'filename', allTokens).generate(); |
| logger.printTokensUsage(verbose: true); |
| |
| expect(printLog, contains('✅ foo')); |
| expect(printLog, contains('✅ bar')); |
| expect(printLog, contains('Tokens used: 2/2')); |
| })); |
| }); |
| } |
| |
| class TestTemplate extends TokenTemplate { |
| TestTemplate(super.blockName, super.fileName, super.tokens); |
| |
| @override |
| String generate() => ''' |
| static final String tokenFoo = '${getToken('foo')}'; |
| static final String tokenBar = '${getToken('bar')}'; |
| '''; |
| } |
| |
| class TestColorTemplate extends TokenTemplate { |
| TestColorTemplate(super.blockName, super.fileName, super.tokens); |
| |
| @override |
| String generate() => ''' |
| static final Color color_1 = '${color('color_foo_req')}'; |
| static final Color color_2 = '${color('color_foo_opt', 'Colors.red')}'; |
| '''; |
| } |
| |
| class TestBorderTemplate extends TokenTemplate { |
| TestBorderTemplate(super.blockName, super.fileName, super.tokens); |
| |
| @override |
| String generate() => ''' |
| static final BorderSide border = '${border('border_foo')}'; |
| '''; |
| } |