flutter_flutter/dev/snippets/test/snippet_parser_test.dart
Kate Lovett a04fb324be
Bump Dart to 3.8 and reformat (#171703)
Bumps the Dart version to 3.8 across the repo (excluding
engine/src/flutter/third_party) and applies formatting updates from Dart
3.8.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-07-07 17:58:32 +00:00

309 lines
8.6 KiB
Dart

// 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 MemoryFileSystem 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 SnippetDartdocParser sampleParser = SnippetDartdocParser(memoryFileSystem);
sampleParser.parseFromComments(elements);
sampleParser.parseAndAddAssumptions(elements, inputFile);
expect(elements.length, equals(7));
int sampleCount = 0;
for (final SourceElement 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 = &#39;${element.elementName}&#39;;.*\$''',
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 SnippetDartdocParser sampleParser = SnippetDartdocParser(memoryFileSystem);
sampleParser.parseFromComments(elements);
expect(elements.length, equals(1));
int sampleCount = 0;
for (final SourceElement 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 SnippetDartdocParser 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 File linkedFile = filesystem.file(path.join(flutterRoot.absolute.path, 'linked_file.dart'))
..createSync(recursive: true)
..writeAsStringSync('''
// Copyright
import 'foo.dart';
// Description
void DocumentedClassSample() {
String elementName = 'DocumentedClass';
}
''');
final String source = linked
? '''
/// ** See code in ${path.relative(linkedFile.path, from: flutterRoot.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 {}
''');
}