@@ -12,17 +12,21 @@ import 'package:build_modules/src/platform.dart';
1212import 'package:build_runner_core/build_runner_core.dart' as core;
1313import 'package:build_runner_core/src/generate/build_impl.dart' ;
1414import 'package:build_runner_core/src/generate/options.dart' ;
15+ import 'package:build_test/builder.dart' ;
16+ import 'package:build_test/src/debug_test_builder.dart' ;
1517import 'package:build_web_compilers/build_web_compilers.dart' ;
1618import 'package:build_web_compilers/builders.dart' ;
1719import 'package:build_web_compilers/src/dev_compiler_bootstrap.dart' ;
1820import 'package:logging/logging.dart' ;
1921import 'package:meta/meta.dart' ;
2022import 'package:path/path.dart' as path;
23+ import 'package:test_core/backend.dart' ;
2124import 'package:watcher/watcher.dart' ;
2225
2326import '../artifacts.dart' ;
2427import '../base/file_system.dart' ;
2528import '../base/logger.dart' ;
29+ import '../base/platform.dart' ;
2630import '../compile.dart' ;
2731import '../dart/package_map.dart' ;
2832import '../globals.dart' ;
@@ -65,6 +69,20 @@ final DartPlatform flutterWebPlatform =
6569
6670/// The build application to compile a flutter application to the web.
6771final List <core.BuilderApplication > builders = < core.BuilderApplication > [
72+ core.apply (
73+ 'flutter_tools|test_bootstrap' ,
74+ < BuilderFactory > [
75+ (BuilderOptions options) => const DebugTestBuilder (),
76+ (BuilderOptions options) => const FlutterWebTestBootstrapBuilder (),
77+ ],
78+ core.toRoot (),
79+ hideOutput: true ,
80+ defaultGenerateFor: const InputSet (
81+ include: < String > [
82+ 'test/**' ,
83+ ],
84+ ),
85+ ),
6886 core.apply (
6987 'flutter_tools|module_library' ,
7088 < Builder Function (BuilderOptions )> [moduleLibraryBuilder],
@@ -109,14 +127,15 @@ final List<core.BuilderApplication> builders = <core.BuilderApplication>[
109127 'flutter_tools|entrypoint' ,
110128 < BuilderFactory > [
111129 (BuilderOptions options) => FlutterWebEntrypointBuilder (
112- options.config['target ' ] ?? 'lib/main.dart' ),
130+ options.config['targets ' ] ?? < String > [ 'lib/main.dart' ] ),
113131 ],
114132 core.toRoot (),
115133 hideOutput: true ,
116134 defaultGenerateFor: const InputSet (
117135 include: < String > [
118136 'lib/**' ,
119137 'web/**' ,
138+ 'test/**_test.dart.browser_test.dart' ,
120139 ],
121140 ),
122141 ),
@@ -135,13 +154,14 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
135154 @override
136155 Future <void > initialize ({
137156 @required Directory projectDirectory,
138- @required String target,
157+ @required List <String > targets,
158+ String testOutputDir,
139159 }) async {
140160 // Override the generated output directory so this does not conflict with
141161 // other build_runner output.
142162 core.overrideGeneratedOutputDirectory ('flutter_web' );
143163 _packageUriMapper = PackageUriMapper (
144- path.absolute (target ), PackageMap .globalPackagesPath, null , null );
164+ path.absolute ('lib/main.dart' ), PackageMap .globalPackagesPath, null , null );
145165 _packageGraph = core.PackageGraph .forPath (projectDirectory.path);
146166 final core.BuildEnvironment buildEnvironment = core.OverrideableEnvironment (
147167 core.IOEnvironment (_packageGraph), onLog: (LogRecord record) {
@@ -163,21 +183,31 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
163183 trackPerformance: false ,
164184 deleteFilesByDefault: true ,
165185 );
186+ final Set <core.BuildDirectory > buildDirs = < core.BuildDirectory > {
187+ if (testOutputDir != null )
188+ core.BuildDirectory (
189+ 'test' ,
190+ outputLocation: core.OutputLocation (
191+ testOutputDir,
192+ useSymlinks: ! platform.isWindows,
193+ ),
194+ ),
195+ };
166196 final Status status =
167- logger.startProgress ('Compiling $target for the Web...' , timeout: null );
197+ logger.startProgress ('Compiling ${ targets . first } for the Web...' , timeout: null );
168198 try {
169199 _builder = await BuildImpl .create (
170200 buildOptions,
171201 buildEnvironment,
172202 builders,
173203 < String , Map <String , dynamic >> {
174204 'flutter_tools|entrypoint' : < String , dynamic > {
175- 'target ' : target ,
205+ 'targets ' : targets ,
176206 }
177207 },
178208 isReleaseBuild: false ,
179209 );
180- await _builder.run (const < AssetId , ChangeType > {});
210+ await _builder.run (const < AssetId , ChangeType > {}, buildDirs : buildDirs );
181211 } finally {
182212 status.stop ();
183213 }
@@ -205,9 +235,9 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
205235
206236/// A ddc-only entrypoint builder that respects the Flutter target flag.
207237class FlutterWebEntrypointBuilder implements Builder {
208- const FlutterWebEntrypointBuilder (this .target );
238+ const FlutterWebEntrypointBuilder (this .targets );
209239
210- final String target ;
240+ final List < String > targets ;
211241
212242 @override
213243 Map <String , List <String >> get buildExtensions => const < String , List <String >> {
@@ -222,10 +252,124 @@ class FlutterWebEntrypointBuilder implements Builder {
222252
223253 @override
224254 Future <void > build (BuildStep buildStep) async {
225- if (! buildStep.inputId.path.contains (target)) {
255+ bool matches = false ;
256+ for (String target in targets) {
257+ if (buildStep.inputId.path.contains (target)) {
258+ matches = true ;
259+ break ;
260+ }
261+ }
262+ if (! matches) {
226263 return ;
227264 }
228265 log.info ('building for target ${buildStep .inputId .path }' );
229266 await bootstrapDdc (buildStep, platform: flutterWebPlatform);
230267 }
231268}
269+
270+ class FlutterWebTestBootstrapBuilder implements Builder {
271+ const FlutterWebTestBootstrapBuilder ();
272+
273+ @override
274+ Map <String , List <String >> get buildExtensions => const < String , List <String >> {
275+ '_test.dart' : < String > [
276+ '_test.dart.browser_test.dart' ,
277+ ]
278+ };
279+
280+ @override
281+ Future <void > build (BuildStep buildStep) async {
282+ final AssetId id = buildStep.inputId;
283+ final String contents = await buildStep.readAsString (id);
284+ final String assetPath = id.pathSegments.first == 'lib'
285+ ? path.url.join ('packages' , id.package, id.path)
286+ : id.path;
287+ final Metadata metadata = parseMetadata (
288+ assetPath, contents, Runtime .builtIn.map ((Runtime runtime) => runtime.name).toSet ());
289+
290+ if (metadata.testOn.evaluate (SuitePlatform (Runtime .chrome))) {
291+ await buildStep.writeAsString (id.addExtension ('.browser_test.dart' ), '''
292+ import 'dart:ui' as ui;
293+ import 'dart:html';
294+ import 'dart:js';
295+
296+ import 'package:stream_channel/stream_channel.dart';
297+ import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports
298+ import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
299+ import 'package:test_api/src/remote_listener.dart'; // ignore: implementation_imports
300+ import 'package:test_api/src/suite_channel_manager.dart'; // ignore: implementation_imports
301+
302+ import "${path .url .basename (id .path )}" as test;
303+
304+ Future<void> main() async {
305+ // Extra initialization for flutter_web.
306+ // The following parameters are hard-coded in Flutter's test embedder. Since
307+ // we don't have an embedder yet this is the lowest-most layer we can put
308+ // this stuff in.
309+ await ui.webOnlyTestSetup();
310+ internalBootstrapBrowserTest(() => test.main);
311+ }
312+
313+ void internalBootstrapBrowserTest(Function getMain()) {
314+ var channel =
315+ serializeSuite(getMain, hidePrints: false, beforeLoad: () async {
316+ var serialized =
317+ await suiteChannel("test.browser.mapper").stream.first as Map;
318+ if (serialized == null) return;
319+ });
320+ postMessageChannel().pipe(channel);
321+ }
322+ StreamChannel serializeSuite(Function getMain(),
323+ {bool hidePrints = true, Future beforeLoad()}) =>
324+ RemoteListener.start(getMain,
325+ hidePrints: hidePrints, beforeLoad: beforeLoad);
326+
327+ StreamChannel suiteChannel(String name) {
328+ var manager = SuiteChannelManager.current;
329+ if (manager == null) {
330+ throw StateError('suiteChannel() may only be called within a test worker.');
331+ }
332+
333+ return manager.connectOut(name);
334+ }
335+
336+ StreamChannel postMessageChannel() {
337+ var controller = StreamChannelController(sync: true);
338+ window.onMessage.firstWhere((message) {
339+ return message.origin == window.location.origin && message.data == "port";
340+ }).then((message) {
341+ var port = message.ports.first;
342+ var portSubscription = port.onMessage.listen((message) {
343+ controller.local.sink.add(message.data);
344+ });
345+
346+ controller.local.stream.listen((data) {
347+ port.postMessage({"data": data});
348+ }, onDone: () {
349+ port.postMessage({"event": "done"});
350+ portSubscription.cancel();
351+ });
352+ });
353+
354+ context['parent'].callMethod('postMessage', [
355+ JsObject.jsify({"href": window.location.href, "ready": true}),
356+ window.location.origin,
357+ ]);
358+ return controller.foreign;
359+ }
360+
361+ void setStackTraceMap
362+ per(StackTraceMapper mapper) {
363+ var formatter = StackTraceFormatter.current;
364+ if (formatter == null) {
365+ throw StateError(
366+ 'setStackTraceMapper() may only be called within a test worker.');
367+ }
368+
369+ formatter.configure(mapper: mapper);
370+ }
371+ ''' );
372+ }
373+ }
374+ }
375+
0 commit comments