1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'package:file/file.dart' ; |
6 | import 'package:file/memory.dart' ; |
7 | import 'package:path/path.dart' as path; |
8 | import 'package:pub_semver/pub_semver.dart' ; |
9 | import 'package:snippets/snippets.dart' ; |
10 | import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; |
11 | |
12 | import 'filesystem_resource_provider.dart'; |
13 | |
14 | class FakeFlutterInformation extends FlutterInformation { |
15 | FakeFlutterInformation(this.flutterRoot); |
16 | |
17 | final Directory flutterRoot; |
18 | |
19 | @override |
20 | Directory getFlutterRoot() { |
21 | return flutterRoot; |
22 | } |
23 | |
24 | @override |
25 | Map<String, dynamic> getFlutterInformation() { |
26 | return <String, dynamic>{ |
27 | 'flutterRoot' : flutterRoot, |
28 | 'frameworkVersion' : Version(2, 10, 0), |
29 | 'dartSdkVersion' : Version(2, 12, 1), |
30 | }; |
31 | } |
32 | } |
33 | |
34 | void main() { |
35 | group('Parser' , () { |
36 | late MemoryFileSystem memoryFileSystem = MemoryFileSystem(); |
37 | late FlutterRepoSnippetConfiguration configuration; |
38 | late SnippetGenerator generator; |
39 | late Directory tmpDir; |
40 | late Directory flutterRoot; |
41 | |
42 | void writeSkeleton(String type) { |
43 | switch (type) { |
44 | case 'dartpad' : |
45 | configuration.getHtmlSkeletonFile('dartpad' ).writeAsStringSync(''' |
46 | <div>HTML Bits (DartPad-style)</div> |
47 | <iframe class="snippet-dartpad" src="https://dartpad.dev/embed-flutter.html?split=60&run=true&sample_id={{id}}&sample_channel={{channel}}"> |
48 | <div>More HTML Bits</div> |
49 | ''' ); |
50 | case 'sample' : |
51 | case 'snippet' : |
52 | configuration.getHtmlSkeletonFile(type).writeAsStringSync(''' |
53 | <div>HTML Bits</div> |
54 | {{description}} |
55 | <pre>{{code}}</pre> |
56 | <pre>{{app}}</pre> |
57 | <div>More HTML Bits</div> |
58 | ''' ); |
59 | } |
60 | } |
61 | |
62 | setUp(() { |
63 | // Create a new filesystem. |
64 | memoryFileSystem = MemoryFileSystem(); |
65 | tmpDir = memoryFileSystem.systemTempDirectory |
66 | .createTempSync('flutter_snippets_test.' ); |
67 | flutterRoot = memoryFileSystem |
68 | .directory(path.join(tmpDir.absolute.path, 'flutter' )); |
69 | configuration = FlutterRepoSnippetConfiguration( |
70 | flutterRoot: flutterRoot, filesystem: memoryFileSystem); |
71 | configuration.skeletonsDirectory.createSync(recursive: true); |
72 | <String>['dartpad' , 'sample' , 'snippet' ].forEach(writeSkeleton); |
73 | FlutterInformation.instance = FakeFlutterInformation(flutterRoot); |
74 | generator = SnippetGenerator( |
75 | configuration: configuration, |
76 | filesystem: memoryFileSystem, |
77 | flutterRoot: configuration.skeletonsDirectory.parent); |
78 | }); |
79 | |
80 | test('parses from comments' , () async { |
81 | final File inputFile = _createSnippetSourceFile(tmpDir, memoryFileSystem); |
82 | final Iterable<SourceElement> elements = getFileElements(inputFile, |
83 | resourceProvider: FileSystemResourceProvider(memoryFileSystem)); |
84 | expect(elements, isNotEmpty); |
85 | final SnippetDartdocParser sampleParser = |
86 | SnippetDartdocParser(memoryFileSystem); |
87 | sampleParser.parseFromComments(elements); |
88 | sampleParser.parseAndAddAssumptions(elements, inputFile); |
89 | expect(elements.length, equals(7)); |
90 | int sampleCount = 0; |
91 | for (final SourceElement element in elements) { |
92 | expect(element.samples.length, greaterThanOrEqualTo(1)); |
93 | sampleCount += element.samples.length; |
94 | final String code = generator.generateCode(element.samples.first); |
95 | expect(code, contains('// Description')); |
96 | expect( |
97 | code, |
98 | contains(RegExp( |
99 | '''^String elementName = ' ${element.elementName}';\$''' , |
100 | multiLine: true))); |
101 | final String html = generator.generateHtml(element.samples.first); |
102 | expect( |
103 | html, |
104 | contains(RegExp( |
105 | '''^<pre>String elementName = ' ${element.elementName}';.*\$''' , |
106 | multiLine: true))); |
107 | expect( |
108 | html, |
109 | contains( |
110 | '<div class="snippet-description">{@end-inject-html}Description{@inject-html}</div>\n' )); |
111 | } |
112 | expect(sampleCount, equals(8)); |
113 | }); |
114 | test('parses dartpad samples from linked file' , () async { |
115 | final File inputFile = _createDartpadSourceFile( |
116 | tmpDir, memoryFileSystem, flutterRoot, |
117 | linked: true); |
118 | final Iterable<SourceElement> elements = getFileElements(inputFile, |
119 | resourceProvider: FileSystemResourceProvider(memoryFileSystem)); |
120 | expect(elements, isNotEmpty); |
121 | final SnippetDartdocParser sampleParser = |
122 | SnippetDartdocParser(memoryFileSystem); |
123 | sampleParser.parseFromComments(elements); |
124 | expect(elements.length, equals(1)); |
125 | int sampleCount = 0; |
126 | for (final SourceElement element in elements) { |
127 | expect(element.samples.length, greaterThanOrEqualTo(1)); |
128 | sampleCount += element.samples.length; |
129 | final String code = |
130 | generator.generateCode(element.samples.first, formatOutput: false); |
131 | expect(code, contains('// Description')); |
132 | expect(
|
133 | code,
|
134 | contains(RegExp('^void ${element.name}Sample\\(\\) \\{.*\$' ,
|
135 | multiLine: true)));
|
136 | final String html = generator.generateHtml(element.samples.first);
|
137 | expect(
|
138 | html,
|
139 | contains(RegExp(
|
140 | '''^<iframe class="snippet-dartpad" src="https://dartpad.dev/.*sample_id=${element.name}.0.*>.*\$''',
|
141 | multiLine: true)));
|
142 | }
|
143 | expect(sampleCount, equals(1));
|
144 | });
|
145 | test('parses assumptions', () async {
|
146 | final File inputFile = _createSnippetSourceFile(tmpDir, memoryFileSystem);
|
147 | final SnippetDartdocParser sampleParser =
|
148 | SnippetDartdocParser(memoryFileSystem);
|
149 | final List<SourceLine> assumptions =
|
150 | sampleParser.parseAssumptions(inputFile);
|
151 | expect(assumptions.length, equals(1));
|
152 | expect(assumptions.first.text, equals('int integer = 3;'));
|
153 | });
|
154 | });
|
155 | }
|
156 |
|
157 | File _createSnippetSourceFile(Directory tmpDir, FileSystem filesystem) {
|
158 | return filesystem.file(path.join(tmpDir.absolute.path, 'snippet_in.dart'))
|
159 | ..createSync(recursive: true)
|
160 | ..writeAsStringSync(r'''
|
161 | // Copyright
|
162 |
|
163 | // @dart = 2.12
|
164 |
|
165 | import 'foo.dart';
|
166 |
|
167 | // Examples can assume:
|
168 | // int integer = 3;
|
169 |
|
170 | /// Top level variable comment
|
171 | ///
|
172 | /// {@tool snippet}
|
173 | /// Description
|
174 | /// ```dart
|
175 | /// String elementName = 'topLevelVariable';
|
176 | /// ```
|
177 | /// {@end-tool}
|
178 | int topLevelVariable = 4;
|
179 |
|
180 | /// Top level function comment
|
181 | ///
|
182 | /// {@tool snippet}
|
183 | /// Description
|
184 | /// ```dart
|
185 | /// String elementName = 'topLevelFunction';
|
186 | /// ```
|
187 | /// {@end-tool}
|
188 | int topLevelFunction() {
|
189 | return integer;
|
190 | }
|
191 |
|
192 | /// Class comment
|
193 | ///
|
194 | /// {@tool snippet}
|
195 | /// Description
|
196 | /// ```dart
|
197 | /// String elementName = 'DocumentedClass';
|
198 | /// ```
|
199 | /// {@end-tool}
|
200 | ///
|
201 | /// {@tool snippet}
|
202 | /// Description2
|
203 | /// ```dart
|
204 | /// String elementName = 'DocumentedClass';
|
205 | /// ```
|
206 | /// {@end-tool}
|
207 | class DocumentedClass {
|
208 | /// Constructor comment
|
209 | /// {@tool snippet}
|
210 | /// Description
|
211 | /// ```dart
|
212 | /// String elementName = 'DocumentedClass';
|
213 | /// ```
|
214 | /// {@end-tool}
|
215 | const DocumentedClass();
|
216 |
|
217 | /// Named constructor comment
|
218 | /// {@tool snippet}
|
219 | /// Description
|
220 | /// ```dart
|
221 | /// String elementName = 'DocumentedClass.name';
|
222 | /// ```
|
223 | /// {@end-tool}
|
224 | const DocumentedClass.name();
|
225 |
|
226 | /// Member variable comment
|
227 | /// {@tool snippet}
|
228 | /// Description
|
229 | /// ```dart
|
230 | /// String elementName = 'DocumentedClass.intMember';
|
231 | /// ```
|
232 | /// {@end-tool}
|
233 | int intMember;
|
234 |
|
235 | /// Member comment
|
236 | /// {@tool snippet}
|
237 | /// Description
|
238 | /// ```dart
|
239 | /// String elementName = 'DocumentedClass.member';
|
240 | /// ```
|
241 | /// {@end-tool}
|
242 | void member() {}
|
243 | }
|
244 | ''');
|
245 | }
|
246 |
|
247 | File _createDartpadSourceFile(
|
248 | Directory tmpDir, FileSystem filesystem, Directory flutterRoot,
|
249 | {bool linked = false}) {
|
250 | final File linkedFile =
|
251 | filesystem.file(path.join(flutterRoot.absolute.path, 'linked_file.dart'))
|
252 | ..createSync(recursive: true)
|
253 | ..writeAsStringSync('''
|
254 | // Copyright
|
255 |
|
256 | import 'foo.dart';
|
257 |
|
258 | // Description
|
259 |
|
260 | void DocumentedClassSample() {
|
261 | String elementName = 'DocumentedClass';
|
262 | }
|
263 | ''');
|
264 |
|
265 | final String source = linked
|
266 | ? '''
|
267 | /// ** See code in ${path.relative(linkedFile.path, from: flutterRoot.absolute.path)} **'''
|
268 | : '''
|
269 | /// ```dart
|
270 | /// void DocumentedClassSample() {
|
271 | /// String elementName = 'DocumentedClass';
|
272 | /// }
|
273 | /// ```''';
|
274 |
|
275 | return filesystem.file(path.join(tmpDir.absolute.path, 'snippet_in.dart'))
|
276 | ..createSync(recursive: true)
|
277 | ..writeAsStringSync('''
|
278 | // Copyright
|
279 |
|
280 | // @dart = 2.12
|
281 |
|
282 | import 'foo.dart';
|
283 |
|
284 | /// Class comment
|
285 | ///
|
286 | /// {@tool dartpad --template=template}
|
287 | /// Description
|
288 | $source
|
289 | /// {@end-tool}
|
290 | class DocumentedClass {}
|
291 | ''');
|
292 | }
|
293 |
|