blob: 63ec9f42a1135dab3c160d3895753a14a33195e9 [file] [log] [blame]
// 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: &#39;foo&#39;));'''));
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(&#39;The actual $name.&#39;);'));
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 '';
}
}