| // 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 'package:file/file.dart'; | |
| import 'package:file/memory.dart'; | |
| import 'package:path/path.dart' as path; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| import 'package:snippets/snippets.dart'; | |
| import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; | |
| import 'filesystem_resource_provider.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('Parser', () { | |
| late var memoryFileSystem = MemoryFileSystem(); | |
| late FlutterRepoSnippetConfiguration configuration; | |
| late SnippetGenerator generator; | |
| late Directory tmpDir; | |
| late Directory flutterRoot; | |
| 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.'); | |
| flutterRoot = memoryFileSystem.directory(path.join(tmpDir.absolute.path, 'flutter')); | |
| configuration = FlutterRepoSnippetConfiguration( | |
| flutterRoot: flutterRoot, | |
| filesystem: memoryFileSystem, | |
| ); | |
| configuration.skeletonsDirectory.createSync(recursive: true); | |
| <String>['dartpad', 'sample', 'snippet'].forEach(writeSkeleton); | |
| FlutterInformation.instance = FakeFlutterInformation(flutterRoot); | |
| generator = SnippetGenerator( | |
| configuration: configuration, | |
| filesystem: memoryFileSystem, | |
| flutterRoot: configuration.skeletonsDirectory.parent, | |
| ); | |
| }); | |
| test('parses from comments', () async { | |
| final File inputFile = _createSnippetSourceFile(tmpDir, memoryFileSystem); | |
| final Iterable<SourceElement> elements = getFileElements( | |
| inputFile, | |
| resourceProvider: FileSystemResourceProvider(memoryFileSystem), | |
| ); | |
| expect(elements, isNotEmpty); | |
| final sampleParser = SnippetDartdocParser(memoryFileSystem); | |
| sampleParser.parseFromComments(elements); | |
| sampleParser.parseAndAddAssumptions(elements, inputFile); | |
| expect(elements.length, equals(7)); | |
| var sampleCount = 0; | |
| for (final element in elements) { | |
| expect(element.samples.length, greaterThanOrEqualTo(1)); | |
| sampleCount += element.samples.length; | |
| final String code = generator.generateCode(element.samples.first); | |
| expect(code, contains('// Description')); | |
| expect( | |
| code, | |
| contains( | |
| RegExp('''^String elementName = '${element.elementName}';\$''', multiLine: true), | |
| ), | |
| ); | |
| final String html = generator.generateHtml(element.samples.first); | |
| expect( | |
| html, | |
| contains( | |
| RegExp( | |
| '''^<pre>String elementName = '${element.elementName}';.*\$''', | |
| multiLine: true, | |
| ), | |
| ), | |
| ); | |
| expect( | |
| html, | |
| contains( | |
| '<div class="snippet-description">{@end-inject-html}Description{@inject-html}</div>\n', | |
| ), | |
| ); | |
| } | |
| expect(sampleCount, equals(8)); | |
| }); | |
| test('parses dartpad samples from linked file', () async { | |
| final File inputFile = _createDartpadSourceFile( | |
| tmpDir, | |
| memoryFileSystem, | |
| flutterRoot, | |
| linked: true, | |
| ); | |
| final Iterable<SourceElement> elements = getFileElements( | |
| inputFile, | |
| resourceProvider: FileSystemResourceProvider(memoryFileSystem), | |
| ); | |
| expect(elements, isNotEmpty); | |
| final sampleParser = SnippetDartdocParser(memoryFileSystem); | |
| sampleParser.parseFromComments(elements); | |
| expect(elements.length, equals(1)); | |
| var sampleCount = 0; | |
| for (final element in elements) { | |
| expect(element.samples.length, greaterThanOrEqualTo(1)); | |
| sampleCount += element.samples.length; | |
| final String code = generator.generateCode(element.samples.first); | |
| expect(code, contains('// Description')); | |
| expect( | |
| code, | |
| contains(RegExp('^void ${element.name}Sample\\(\\) \\{.*\$', multiLine: true)), | |
| ); | |
| final String html = generator.generateHtml(element.samples.first); | |
| expect( | |
| html, | |
| contains( | |
| RegExp( | |
| '''^<iframe class="snippet-dartpad" src="https://dartpad.dev/.*sample_id=${element.name}.0.*></iframe>.*\$''', | |
| multiLine: true, | |
| ), | |
| ), | |
| ); | |
| } | |
| expect(sampleCount, equals(1)); | |
| }); | |
| test('parses assumptions', () async { | |
| final File inputFile = _createSnippetSourceFile(tmpDir, memoryFileSystem); | |
| final sampleParser = SnippetDartdocParser(memoryFileSystem); | |
| final List<SourceLine> assumptions = sampleParser.parseAssumptions(inputFile); | |
| expect(assumptions.length, equals(1)); | |
| expect(assumptions.first.text, equals('int integer = 3;')); | |
| }); | |
| }); | |
| } | |
| File _createSnippetSourceFile(Directory tmpDir, FileSystem filesystem) { | |
| return filesystem.file(path.join(tmpDir.absolute.path, 'snippet_in.dart')) | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(r''' | |
| // Copyright | |
| // @dart = 2.12 | |
| import 'foo.dart'; | |
| // Examples can assume: | |
| // int integer = 3; | |
| /// Top level variable comment | |
| /// | |
| /// {@tool snippet} | |
| /// Description | |
| /// ```dart | |
| /// String elementName = 'topLevelVariable'; | |
| /// ``` | |
| /// {@end-tool} | |
| int topLevelVariable = 4; | |
| /// Top level function comment | |
| /// | |
| /// {@tool snippet} | |
| /// Description | |
| /// ```dart | |
| /// String elementName = 'topLevelFunction'; | |
| /// ``` | |
| /// {@end-tool} | |
| int topLevelFunction() { | |
| return integer; | |
| } | |
| /// Class comment | |
| /// | |
| /// {@tool snippet} | |
| /// Description | |
| /// ```dart | |
| /// String elementName = 'DocumentedClass'; | |
| /// ``` | |
| /// {@end-tool} | |
| /// | |
| /// {@tool snippet} | |
| /// Description2 | |
| /// ```dart | |
| /// String elementName = 'DocumentedClass'; | |
| /// ``` | |
| /// {@end-tool} | |
| class DocumentedClass { | |
| /// Constructor comment | |
| /// {@tool snippet} | |
| /// Description | |
| /// ```dart | |
| /// String elementName = 'DocumentedClass'; | |
| /// ``` | |
| /// {@end-tool} | |
| const DocumentedClass(); | |
| /// Named constructor comment | |
| /// {@tool snippet} | |
| /// Description | |
| /// ```dart | |
| /// String elementName = 'DocumentedClass.name'; | |
| /// ``` | |
| /// {@end-tool} | |
| const DocumentedClass.name(); | |
| /// Member variable comment | |
| /// {@tool snippet} | |
| /// Description | |
| /// ```dart | |
| /// String elementName = 'DocumentedClass.intMember'; | |
| /// ``` | |
| /// {@end-tool} | |
| int intMember; | |
| /// Member comment | |
| /// {@tool snippet} | |
| /// Description | |
| /// ```dart | |
| /// String elementName = 'DocumentedClass.member'; | |
| /// ``` | |
| /// {@end-tool} | |
| void member() {} | |
| } | |
| '''); | |
| } | |
| File _createDartpadSourceFile( | |
| Directory tmpDir, | |
| FileSystem filesystem, | |
| Directory flutterRoot, { | |
| bool linked = false, | |
| }) { | |
| final Directory flutterPackageDir = filesystem.directory( | |
| path.join(flutterRoot.absolute.path, 'packages', 'flutter'), | |
| ); | |
| final File linkedFile = | |
| filesystem.file(path.join(flutterPackageDir.absolute.path, 'linked_file.dart')) | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| // Copyright | |
| import 'foo.dart'; | |
| // Description | |
| void DocumentedClassSample() { | |
| String elementName = 'DocumentedClass'; | |
| } | |
| '''); | |
| final source = linked | |
| ? ''' | |
| /// ** See code in ${path.relative(linkedFile.path, from: flutterPackageDir.absolute.path)} **''' | |
| : ''' | |
| /// ```dart | |
| /// void DocumentedClassSample() { | |
| /// String elementName = 'DocumentedClass'; | |
| /// } | |
| /// ```'''; | |
| return filesystem.file(path.join(tmpDir.absolute.path, 'snippet_in.dart')) | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| // Copyright | |
| // @dart = 2.12 | |
| import 'foo.dart'; | |
| /// Class comment | |
| /// | |
| /// {@tool dartpad --template=template} | |
| /// Description | |
| $source | |
| /// {@end-tool} | |
| class DocumentedClass {} | |
| '''); | |
| } |