| // 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. |
| |
| import 'dart:convert'; |
| |
| import 'package:file/file.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:platform/platform.dart'; |
| import 'package:pub_semver/pub_semver.dart'; |
| import 'package:snippets/snippets.dart'; |
| import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; |
| |
| import '../bin/snippets.dart' as snippets_main; |
| import 'fake_process_manager.dart'; |
| |
| class FakeFlutterInformation extends FlutterInformation { |
| FakeFlutterInformation(this.flutterRoot); |
| |
| final Directory flutterRoot; |
| |
| @override |
| Directory getFlutterRoot() { |
| return flutterRoot; |
| } |
| |
| @override |
| Map<String, dynamic> getFlutterInformation() { |
| return <String, dynamic>{ |
| 'flutterRoot': flutterRoot, |
| 'frameworkVersion': Version(2, 10, 0), |
| 'dartSdkVersion': Version(2, 12, 1), |
| }; |
| } |
| } |
| |
| void main() { |
| group('Generator', () { |
| late MemoryFileSystem memoryFileSystem = MemoryFileSystem(); |
| late FlutterRepoSnippetConfiguration configuration; |
| late SnippetGenerator generator; |
| late Directory tmpDir; |
| |
| void writeSkeleton(String type) { |
| switch (type) { |
| case 'dartpad': |
| configuration.getHtmlSkeletonFile('dartpad').writeAsStringSync(''' |
| <div>HTML Bits (DartPad-style)</div> |
| <iframe class="snippet-dartpad" src="https://dartpad.dev/embed-flutter.html?split=60&run=true&sample_id={{id}}&sample_channel={{channel}}"></iframe> |
| <div>More HTML Bits</div> |
| '''); |
| case 'sample': |
| case 'snippet': |
| configuration.getHtmlSkeletonFile(type).writeAsStringSync(''' |
| <div>HTML Bits</div> |
| {{description}} |
| <pre>{{code}}</pre> |
| <pre>{{app}}</pre> |
| <div>More HTML Bits</div> |
| '''); |
| } |
| } |
| |
| setUp(() { |
| // Create a new filesystem. |
| memoryFileSystem = MemoryFileSystem(); |
| tmpDir = memoryFileSystem.systemTempDirectory |
| .createTempSync('flutter_snippets_test.'); |
| configuration = FlutterRepoSnippetConfiguration( |
| flutterRoot: memoryFileSystem |
| .directory(path.join(tmpDir.absolute.path, 'flutter')), |
| filesystem: memoryFileSystem); |
| configuration.skeletonsDirectory.createSync(recursive: true); |
| <String>['dartpad', 'sample', 'snippet'].forEach(writeSkeleton); |
| FlutterInformation.instance = |
| FakeFlutterInformation(configuration.flutterRoot); |
| generator = SnippetGenerator( |
| configuration: configuration, |
| filesystem: memoryFileSystem, |
| flutterRoot: configuration.skeletonsDirectory.parent); |
| }); |
| |
| test('generates samples', () async { |
| final File inputFile = memoryFileSystem |
| .file(path.join(tmpDir.absolute.path, 'snippet_in.txt')) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(r''' |
| A description of the sample. |
| |
| On several lines. |
| |
| ** See code in examples/api/widgets/foo/foo_example.0.dart ** |
| '''); |
| final String examplePath = path.join(configuration.flutterRoot.path, 'examples/api/widgets/foo/foo_example.0.dart'); |
| memoryFileSystem.file(examplePath) |
| ..create(recursive: true) |
| ..writeAsStringSync(''' |
| // Copyright |
| |
| // Flutter code sample for [MyElement]. |
| |
| void main() { |
| runApp(MaterialApp(title: 'foo')); |
| }\n''' |
| ); |
| final File outputFile = memoryFileSystem |
| .file(path.join(tmpDir.absolute.path, 'snippet_out.txt')); |
| final SnippetDartdocParser sampleParser = |
| SnippetDartdocParser(memoryFileSystem); |
| const String sourcePath = 'packages/flutter/lib/src/widgets/foo.dart'; |
| const int sourceLine = 222; |
| final SourceElement element = sampleParser.parseFromDartdocToolFile( |
| inputFile, |
| element: 'MyElement', |
| startLine: sourceLine, |
| sourceFile: memoryFileSystem.file(sourcePath), |
| type: 'sample', |
| ); |
| |
| expect(element.samples, isNotEmpty); |
| element.samples.first.metadata.addAll(<String, Object?>{ |
| 'channel': 'stable', |
| }); |
| final String code = generator.generateCode( |
| element.samples.first, |
| output: outputFile, |
| ); |
| expect(code, contains("runApp(MaterialApp(title: 'foo'));")); |
| final String html = generator.generateHtml( |
| element.samples.first, |
| ); |
| expect(html, contains('<div>HTML Bits</div>')); |
| expect(html, contains('<div>More HTML Bits</div>')); |
| expect(html, contains(r'''runApp(MaterialApp(title: 'foo'));''')); |
| expect(html, isNot(contains('sample_channel=stable'))); |
| expect( |
| html, |
| contains('A description of the sample.\n' |
| '\n' |
| 'On several lines.{@inject-html}</div>')); |
| expect(html, contains('void main() {')); |
| |
| final String outputContents = outputFile.readAsStringSync(); |
| expect(outputContents, contains('void main() {')); |
| }); |
| |
| test('generates snippets', () async { |
| final File inputFile = memoryFileSystem |
| .file(path.join(tmpDir.absolute.path, 'snippet_in.txt')) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(r''' |
| A description of the snippet. |
| |
| On several lines. |
| |
| ```code |
| void main() { |
| print('The actual $name.'); |
| } |
| ``` |
| '''); |
| |
| final SnippetDartdocParser sampleParser = |
| SnippetDartdocParser(memoryFileSystem); |
| const String sourcePath = 'packages/flutter/lib/src/widgets/foo.dart'; |
| const int sourceLine = 222; |
| final SourceElement element = sampleParser.parseFromDartdocToolFile( |
| inputFile, |
| element: 'MyElement', |
| startLine: sourceLine, |
| sourceFile: memoryFileSystem.file(sourcePath), |
| type: 'snippet', |
| ); |
| expect(element.samples, isNotEmpty); |
| element.samples.first.metadata.addAll(<String, Object>{ |
| 'channel': 'stable', |
| }); |
| final String code = generator.generateCode(element.samples.first); |
| expect(code, contains('// A description of the snippet.')); |
| final String html = generator.generateHtml(element.samples.first); |
| expect(html, contains('<div>HTML Bits</div>')); |
| expect(html, contains('<div>More HTML Bits</div>')); |
| expect(html, contains(r' print('The actual $name.');')); |
| expect( |
| html, |
| contains( |
| '<div class="snippet-description">{@end-inject-html}A description of the snippet.\n\n' |
| 'On several lines.{@inject-html}</div>\n')); |
| expect(html, contains('main() {')); |
| }); |
| |
| test('generates dartpad samples', () async { |
| final File inputFile = memoryFileSystem |
| .file(path.join(tmpDir.absolute.path, 'snippet_in.txt')) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(r''' |
| A description of the snippet. |
| |
| On several lines. |
| |
| ** See code in examples/api/widgets/foo/foo_example.0.dart ** |
| '''); |
| final String examplePath = path.join(configuration.flutterRoot.path, 'examples/api/widgets/foo/foo_example.0.dart'); |
| memoryFileSystem.file(examplePath) |
| ..create(recursive: true) |
| ..writeAsStringSync(''' |
| // Copyright |
| |
| // Flutter code sample for [MyElement]. |
| |
| void main() { |
| runApp(MaterialApp(title: 'foo')); |
| }\n''' |
| ); |
| |
| final SnippetDartdocParser sampleParser = |
| SnippetDartdocParser(memoryFileSystem); |
| const String sourcePath = 'packages/flutter/lib/src/widgets/foo.dart'; |
| const int sourceLine = 222; |
| final SourceElement element = sampleParser.parseFromDartdocToolFile( |
| inputFile, |
| element: 'MyElement', |
| startLine: sourceLine, |
| sourceFile: memoryFileSystem.file(sourcePath), |
| type: 'dartpad', |
| ); |
| expect(element.samples, isNotEmpty); |
| element.samples.first.metadata.addAll(<String, Object>{ |
| 'channel': 'stable', |
| }); |
| final String code = generator.generateCode(element.samples.first); |
| expect(code, contains("runApp(MaterialApp(title: 'foo'));")); |
| final String html = generator.generateHtml(element.samples.first); |
| expect(html, contains('<div>HTML Bits (DartPad-style)</div>')); |
| expect(html, contains('<div>More HTML Bits</div>')); |
| expect( |
| html, |
| contains( |
| '<iframe class="snippet-dartpad" src="https://dartpad.dev/embed-flutter.html?split=60&run=true&sample_id=MyElement.0&sample_channel=stable"></iframe>\n')); |
| }); |
| |
| test('generates sample metadata', () async { |
| final File inputFile = memoryFileSystem |
| .file(path.join(tmpDir.absolute.path, 'snippet_in.txt')) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(r''' |
| A description of the snippet. |
| |
| On several lines. |
| |
| ```dart |
| void main() { |
| print('The actual $name.'); |
| } |
| ``` |
| '''); |
| |
| final File outputFile = memoryFileSystem |
| .file(path.join(tmpDir.absolute.path, 'snippet_out.dart')); |
| final File expectedMetadataFile = memoryFileSystem |
| .file(path.join(tmpDir.absolute.path, 'snippet_out.json')); |
| |
| final SnippetDartdocParser sampleParser = |
| SnippetDartdocParser(memoryFileSystem); |
| const String sourcePath = 'packages/flutter/lib/src/widgets/foo.dart'; |
| const int sourceLine = 222; |
| final SourceElement element = sampleParser.parseFromDartdocToolFile( |
| inputFile, |
| element: 'MyElement', |
| startLine: sourceLine, |
| sourceFile: memoryFileSystem.file(sourcePath), |
| type: 'sample', |
| ); |
| expect(element.samples, isNotEmpty); |
| element.samples.first.metadata |
| .addAll(<String, Object>{'channel': 'stable'}); |
| generator.generateCode(element.samples.first, output: outputFile); |
| expect(expectedMetadataFile.existsSync(), isTrue); |
| final Map<String, dynamic> json = |
| jsonDecode(expectedMetadataFile.readAsStringSync()) |
| as Map<String, dynamic>; |
| expect(json['id'], equals('MyElement.0')); |
| expect(json['channel'], equals('stable')); |
| expect(json['file'], equals('snippet_out.dart')); |
| expect(json['description'], |
| equals('A description of the snippet.\n\nOn several lines.')); |
| expect(json['sourcePath'], |
| equals('packages/flutter/lib/src/widgets/foo.dart')); |
| }); |
| }); |
| |
| group('snippets command line argument test', () { |
| late MemoryFileSystem memoryFileSystem = MemoryFileSystem(); |
| late Directory tmpDir; |
| late Directory flutterRoot; |
| late FakeProcessManager fakeProcessManager; |
| |
| setUp(() { |
| fakeProcessManager = FakeProcessManager(); |
| memoryFileSystem = MemoryFileSystem(); |
| tmpDir = memoryFileSystem.systemTempDirectory |
| .createTempSync('flutter_snippets_test.'); |
| flutterRoot = memoryFileSystem |
| .directory(path.join(tmpDir.absolute.path, 'flutter')) |
| ..createSync(recursive: true); |
| }); |
| |
| test('command line arguments are parsed and passed to generator', () { |
| final FakePlatform platform = FakePlatform(environment: <String, String>{ |
| 'PACKAGE_NAME': 'dart:ui', |
| 'LIBRARY_NAME': 'library', |
| 'ELEMENT_NAME': 'element', |
| 'FLUTTER_ROOT': flutterRoot.absolute.path, |
| // The details here don't really matter other than the flutter root. |
| 'FLUTTER_VERSION': ''' |
| { |
| "frameworkVersion": "2.5.0-6.0.pre.55", |
| "channel": "use_snippets_pkg", |
| "repositoryUrl": "git@github.com:flutter/flutter.git", |
| "frameworkRevision": "fec4641e1c88923ecd6c969e2ff8a0dd12dc0875", |
| "frameworkCommitDate": "2021-08-11 15:19:48 -0700", |
| "engineRevision": "d8bbebed60a77b3d4fe9c840dc94dfbce159d951", |
| "dartSdkVersion": "2.14.0 (build 2.14.0-393.0.dev)", |
| "flutterRoot": "${flutterRoot.absolute.path}" |
| }''', |
| }); |
| final FlutterInformation flutterInformation = FlutterInformation( |
| filesystem: memoryFileSystem, |
| processManager: fakeProcessManager, |
| platform: platform, |
| ); |
| FlutterInformation.instance = flutterInformation; |
| MockSnippetGenerator mockSnippetGenerator = MockSnippetGenerator(); |
| snippets_main.snippetGenerator = mockSnippetGenerator; |
| String errorMessage = ''; |
| errorExit = (String message) { |
| errorMessage = message; |
| }; |
| |
| snippets_main.platform = platform; |
| snippets_main.filesystem = memoryFileSystem; |
| snippets_main.processManager = fakeProcessManager; |
| final File input = memoryFileSystem |
| .file(tmpDir.childFile('input.snippet')) |
| ..writeAsString('/// Test file'); |
| snippets_main.main( |
| <String>['--input=${input.absolute.path}']); |
| |
| final Map<String, dynamic> metadata = |
| mockSnippetGenerator.sample.metadata; |
| // Ignore the channel, because channel is really just the branch, and will be |
| // different on development workstations. |
| metadata.remove('channel'); |
| expect( |
| metadata, |
| equals(<String, dynamic>{ |
| 'id': 'dart_ui.library.element', |
| 'element': 'element', |
| 'sourcePath': 'unknown.dart', |
| 'sourceLine': 1, |
| 'serial': '', |
| 'package': 'dart:ui', |
| 'library': 'library', |
| })); |
| |
| snippets_main.main(<String>[]); |
| expect( |
| errorMessage, |
| equals( |
| 'The --input option must be specified, either on the command line, or in the INPUT environment variable.')); |
| errorMessage = ''; |
| |
| snippets_main |
| .main(<String>['--input=${input.absolute.path}', '--type=snippet']); |
| expect(errorMessage, equals('')); |
| errorMessage = ''; |
| |
| mockSnippetGenerator = MockSnippetGenerator(); |
| snippets_main.snippetGenerator = mockSnippetGenerator; |
| snippets_main.main(<String>[ |
| '--input=${input.absolute.path}', |
| '--type=snippet', |
| '--no-format-output' |
| ]); |
| expect(mockSnippetGenerator.formatOutput, equals(false)); |
| errorMessage = ''; |
| |
| input.deleteSync(); |
| snippets_main.main( |
| <String>['--input=${input.absolute.path}']); |
| expect(errorMessage, |
| equals('The input file ${input.absolute.path} does not exist.')); |
| errorMessage = ''; |
| }); |
| }); |
| } |
| |
| class MockSnippetGenerator extends SnippetGenerator { |
| late CodeSample sample; |
| File? output; |
| String? copyright; |
| String? description; |
| late bool formatOutput; |
| late bool addSectionMarkers; |
| late bool includeAssumptions; |
| |
| @override |
| String generateCode( |
| CodeSample sample, { |
| File? output, |
| String? copyright, |
| String? description, |
| bool formatOutput = true, |
| bool addSectionMarkers = false, |
| bool includeAssumptions = false, |
| }) { |
| this.sample = sample; |
| this.output = output; |
| this.copyright = copyright; |
| this.description = description; |
| this.formatOutput = formatOutput; |
| this.addSectionMarkers = addSectionMarkers; |
| this.includeAssumptions = includeAssumptions; |
| |
| return ''; |
| } |
| |
| @override |
| String generateHtml(CodeSample sample) { |
| return ''; |
| } |
| } |