Wire dart2js through flutter tool, add compilation test (#27668)
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 41dd11e..947d0d02 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -167,10 +167,22 @@
await _flutterBuildApk(path);
await _flutterBuildIpa(path);
}
+ await _flutterBuildDart2js(path.join('dev', 'integration_tests', 'web'));
print('${bold}DONE: All build tests successful.$reset');
}
+Future<void> _flutterBuildDart2js(String relativePathToApplication) async {
+ print('Running Dart2JS build tests...');
+ await runCommand(flutter,
+ <String>['build', 'web', '-v'],
+ workingDirectory: path.join(flutterRoot, relativePathToApplication),
+ expectNonZeroExit: false,
+ timeout: _kShortTimeout,
+ );
+ print('Done.');
+}
+
Future<void> _flutterBuildAot(String relativePathToApplication) async {
print('Running AOT build tests...');
await runCommand(flutter,
diff --git a/dev/integration_tests/web/lib/main.dart b/dev/integration_tests/web/lib/main.dart
new file mode 100644
index 0000000..49c544c
--- /dev/null
+++ b/dev/integration_tests/web/lib/main.dart
@@ -0,0 +1,13 @@
+// Copyright 2019 The Chromium 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:flutter/widgets.dart';
+
+void main() {
+ runApp(Center(
+ // Can remove when https://github.com/dart-lang/sdk/issues/35801 is fixed.
+ // ignore: prefer_const_constructors
+ child: Text('Hello, World', textDirection: TextDirection.ltr),
+ ));
+}
diff --git a/dev/integration_tests/web/pubspec.yaml b/dev/integration_tests/web/pubspec.yaml
new file mode 100644
index 0000000..8749907
--- /dev/null
+++ b/dev/integration_tests/web/pubspec.yaml
@@ -0,0 +1,17 @@
+name: web_integration
+description: Integration test for web compilation.
+
+environment:
+ # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
+ sdk: ">=2.0.0-dev.68.0 <3.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ meta: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+# PUBSPEC CHECKSUM: d53c
diff --git a/packages/flutter/lib/src/foundation/basic_types.dart b/packages/flutter/lib/src/foundation/basic_types.dart
index 73637cd..2c6c875 100644
--- a/packages/flutter/lib/src/foundation/basic_types.dart
+++ b/packages/flutter/lib/src/foundation/basic_types.dart
@@ -8,6 +8,7 @@
// COMMON SIGNATURES
export 'dart:ui' show VoidCallback;
+export 'bitfield.dart' if (dart.library.html) 'bitfield_unsupported.dart';
/// Signature for callbacks that report that an underlying value has changed.
///
@@ -66,69 +67,6 @@
/// * [AsyncValueSetter], the setter equivalent of this signature.
typedef AsyncValueGetter<T> = Future<T> Function();
-
-// BITFIELD
-
-/// The largest SMI value.
-///
-/// See <https://www.dartlang.org/articles/numeric-computation/#smis-and-mints>
-const int kMaxUnsignedSMI = 0x3FFFFFFFFFFFFFFF;
-
-/// A BitField over an enum (or other class whose values implement "index").
-/// Only the first 62 values of the enum can be used as indices.
-class BitField<T extends dynamic> {
- /// Creates a bit field of all zeros.
- ///
- /// The given length must be at most 62.
- BitField(this._length)
- : assert(_length <= _smiBits),
- _bits = _allZeros;
-
- /// Creates a bit field filled with a particular value.
- ///
- /// If the value argument is true, the bits are filled with ones. Otherwise,
- /// the bits are filled with zeros.
- ///
- /// The given length must be at most 62.
- BitField.filled(this._length, bool value)
- : assert(_length <= _smiBits),
- _bits = value ? _allOnes : _allZeros;
-
- final int _length;
- int _bits;
-
- static const int _smiBits = 62; // see https://www.dartlang.org/articles/numeric-computation/#smis-and-mints
- static const int _allZeros = 0;
- static const int _allOnes = kMaxUnsignedSMI; // 2^(_kSMIBits+1)-1
-
- /// Returns whether the bit with the given index is set to one.
- bool operator [](T index) {
- assert(index.index < _length);
- return (_bits & 1 << index.index) > 0;
- }
-
- /// Sets the bit with the given index to the given value.
- ///
- /// If value is true, the bit with the given index is set to one. Otherwise,
- /// the bit is set to zero.
- void operator []=(T index, bool value) {
- assert(index.index < _length);
- if (value)
- _bits = _bits | (1 << index.index);
- else
- _bits = _bits & ~(1 << index.index);
- }
-
- /// Sets all the bits to the given value.
- ///
- /// If the value is true, the bits are all set to one. Otherwise, the bits are
- /// all set to zero. Defaults to setting all the bits to zero.
- void reset([ bool value = false ]) {
- _bits = value ? _allOnes : _allZeros;
- }
-}
-
-
// LAZY CACHING ITERATOR
/// A lazy caching version of [Iterable].
diff --git a/packages/flutter/lib/src/foundation/bitfield.dart b/packages/flutter/lib/src/foundation/bitfield.dart
new file mode 100644
index 0000000..5240bc0
--- /dev/null
+++ b/packages/flutter/lib/src/foundation/bitfield.dart
@@ -0,0 +1,67 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// The largest SMI value.
+///
+/// See <https://www.dartlang.org/articles/numeric-computation/#smis-and-mints>
+///
+/// When compiling to JavaScript, this value is not supported since it is
+/// larger than the maximum safe 32bit integer.
+const int kMaxUnsignedSMI = 0x3FFFFFFFFFFFFFFF;
+
+/// A BitField over an enum (or other class whose values implement "index").
+/// Only the first 62 values of the enum can be used as indices.
+///
+/// When compiling to JavaScript, this class is not supported.
+class BitField<T extends dynamic> {
+ /// Creates a bit field of all zeros.
+ ///
+ /// The given length must be at most 62.
+ BitField(this._length)
+ : assert(_length <= _smiBits),
+ _bits = _allZeros;
+
+ /// Creates a bit field filled with a particular value.
+ ///
+ /// If the value argument is true, the bits are filled with ones. Otherwise,
+ /// the bits are filled with zeros.
+ ///
+ /// The given length must be at most 62.
+ BitField.filled(this._length, bool value)
+ : assert(_length <= _smiBits),
+ _bits = value ? _allOnes : _allZeros;
+
+ final int _length;
+ int _bits;
+
+ static const int _smiBits = 62; // see https://www.dartlang.org/articles/numeric-computation/#smis-and-mints
+ static const int _allZeros = 0;
+ static const int _allOnes = kMaxUnsignedSMI; // 2^(_kSMIBits+1)-1
+
+ /// Returns whether the bit with the given index is set to one.
+ bool operator [](T index) {
+ assert(index.index < _length);
+ return (_bits & 1 << index.index) > 0;
+ }
+
+ /// Sets the bit with the given index to the given value.
+ ///
+ /// If value is true, the bit with the given index is set to one. Otherwise,
+ /// the bit is set to zero.
+ void operator []=(T index, bool value) {
+ assert(index.index < _length);
+ if (value)
+ _bits = _bits | (1 << index.index);
+ else
+ _bits = _bits & ~(1 << index.index);
+ }
+
+ /// Sets all the bits to the given value.
+ ///
+ /// If the value is true, the bits are all set to one. Otherwise, the bits are
+ /// all set to zero. Defaults to setting all the bits to zero.
+ void reset([ bool value = false ]) {
+ _bits = value ? _allOnes : _allZeros;
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter/lib/src/foundation/bitfield_unsupported.dart b/packages/flutter/lib/src/foundation/bitfield_unsupported.dart
new file mode 100644
index 0000000..c7baf47
--- /dev/null
+++ b/packages/flutter/lib/src/foundation/bitfield_unsupported.dart
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Unsupported.
+const int kMaxUnsignedSMI = 0;
+
+/// Unsupported.
+class BitField<T extends dynamic> {
+ /// Unsupported.
+ // Ignored so that both bitfield implementations have the same API.
+ // ignore: avoid_unused_constructor_parameters
+ BitField(int length);
+
+ /// Unsupported.
+ // Ignored so that both bitfield implementations have the same API.
+ // ignore: avoid_unused_constructor_parameters
+ BitField.filled(int length, bool value);
+
+ /// Unsupported.
+ bool operator [](T index) {
+ throw UnsupportedError('Not supported when compiling to JavaScript');
+ }
+
+ /// Unsupported.
+ void operator []=(T index, bool value) {
+ throw UnsupportedError('Not supported when compiling to JavaScript');
+ }
+
+ /// Unsupported.
+ void reset([ bool value = false ]) {
+ throw UnsupportedError('Not supported when compiling to JavaScript');
+ }
+}
diff --git a/packages/flutter/lib/src/foundation/unsupported.dart b/packages/flutter/lib/src/foundation/unsupported.dart
new file mode 100644
index 0000000..7ad4207f
--- /dev/null
+++ b/packages/flutter/lib/src/foundation/unsupported.dart
@@ -0,0 +1,7 @@
+/// The largest SMI value.
+///
+/// See <https://www.dartlang.org/articles/numeric-computation/#smis-and-mints>
+///
+/// When compiling to JavaScript, this value is not supported since it is
+/// larger than the maximum safe 32bit integer.
+const int kMaxUnsignedSMI = 0x3FFFFFFFFFFFFFFF;
\ No newline at end of file
diff --git a/packages/flutter/lib/src/foundation/unsupported_web.dart b/packages/flutter/lib/src/foundation/unsupported_web.dart
new file mode 100644
index 0000000..8a23100
--- /dev/null
+++ b/packages/flutter/lib/src/foundation/unsupported_web.dart
@@ -0,0 +1,7 @@
+/// The largest SMI value.
+///
+/// See <https://www.dartlang.org/articles/numeric-computation/#smis-and-mints>
+///
+/// When compiling to JavaScript, this value is not supported since it is
+/// larger than the maximum safe 32bit integer.
+const int kMaxUnsignedSMI = 0;
\ No newline at end of file
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index fe8f968..989ca6f 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -49,6 +49,7 @@
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
+ case TargetPlatform.web:
return null;
}
assert(platform != null);
@@ -352,6 +353,7 @@
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
+ case TargetPlatform.web:
return null;
}
return null;
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 6cfb8bc..9bf9280 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -26,6 +26,8 @@
frontendServerSnapshotForEngineDartSdk,
engineDartSdkPath,
engineDartBinary,
+ dart2jsSnapshot,
+ kernelWorkerSnapshot,
}
String _artifactToFileName(Artifact artifact, [TargetPlatform platform, BuildMode mode]) {
@@ -70,6 +72,10 @@
return 'frontend_server.dart.snapshot';
case Artifact.engineDartBinary:
return 'dart';
+ case Artifact.dart2jsSnapshot:
+ return 'flutter_dart2js.dart.snapshot';
+ case Artifact.kernelWorkerSnapshot:
+ return 'flutter_kernel_worker.dart.snapshot';
}
assert(false, 'Invalid artifact $artifact.');
return null;
@@ -121,6 +127,7 @@
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
+ case TargetPlatform.web:
return _getHostArtifactPath(artifact, platform, mode);
}
assert(false, 'Invalid platform $platform.');
@@ -183,13 +190,17 @@
case Artifact.engineDartSdkPath:
return dartSdkPath;
case Artifact.engineDartBinary:
- return fs.path.join(dartSdkPath,'bin', _artifactToFileName(artifact));
+ return fs.path.join(dartSdkPath, 'bin', _artifactToFileName(artifact));
case Artifact.platformKernelDill:
return fs.path.join(_getFlutterPatchedSdkPath(), _artifactToFileName(artifact));
case Artifact.platformLibrariesJson:
return fs.path.join(_getFlutterPatchedSdkPath(), 'lib', _artifactToFileName(artifact));
case Artifact.flutterPatchedSdkPath:
return _getFlutterPatchedSdkPath();
+ case Artifact.dart2jsSnapshot:
+ return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact));
+ case Artifact.kernelWorkerSnapshot:
+ return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact));
default:
assert(false, 'Artifact $artifact not available for platform $platform.');
return null;
@@ -205,6 +216,7 @@
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
+ case TargetPlatform.web:
assert(mode == null, 'Platform $platform does not support different build modes.');
return fs.path.join(engineDir, platformName);
case TargetPlatform.ios:
@@ -265,6 +277,10 @@
return fs.path.join(_hostEngineOutPath, 'dart-sdk');
case Artifact.engineDartBinary:
return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', _artifactToFileName(artifact));
+ case Artifact.dart2jsSnapshot:
+ return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', _artifactToFileName(artifact));
+ case Artifact.kernelWorkerSnapshot:
+ return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', _artifactToFileName(artifact));
}
assert(false, 'Invalid artifact $artifact.');
return null;
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index cd8ce05..2e3322b 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -266,6 +266,7 @@
windows_x64,
fuchsia,
tester,
+ web,
}
/// iOS target device architecture.
@@ -325,6 +326,8 @@
return 'fuchsia';
case TargetPlatform.tester:
return 'flutter-tester';
+ case TargetPlatform.web:
+ return 'web';
}
assert(false);
return null;
@@ -346,6 +349,8 @@
return TargetPlatform.darwin_x64;
case 'linux-x64':
return TargetPlatform.linux_x64;
+ case 'web':
+ return TargetPlatform.web;
}
assert(platform != null);
return null;
@@ -400,6 +405,11 @@
return fs.path.join(getBuildDirectory(), 'ios');
}
+/// Returns the web build output directory.
+String getWebBuildDirectory() {
+ return fs.path.join(getBuildDirectory(), 'web');
+}
+
/// Returns directory used by incremental compiler (IKG - incremental kernel
/// generator) to store cached intermediate state.
String getIncrementalCompilerByteStoreDirectory() {
diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart
index c31f741..64100e2 100644
--- a/packages/flutter_tools/lib/src/commands/build.dart
+++ b/packages/flutter_tools/lib/src/commands/build.dart
@@ -11,6 +11,7 @@
import 'build_bundle.dart';
import 'build_flx.dart';
import 'build_ios.dart';
+import 'build_web.dart';
class BuildCommand extends FlutterCommand {
BuildCommand({bool verboseHelp = false}) {
@@ -20,6 +21,7 @@
addSubcommand(BuildIOSCommand());
addSubcommand(BuildFlxCommand());
addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));
+ addSubcommand(BuildWebCommand());
}
@override
diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart
new file mode 100644
index 0000000..a1a4645
--- /dev/null
+++ b/packages/flutter_tools/lib/src/commands/build_web.dart
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium 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:async';
+
+import '../base/logger.dart';
+import '../build_info.dart';
+import '../globals.dart';
+import '../runner/flutter_command.dart' show ExitStatus, FlutterCommandResult;
+import '../web/compile.dart';
+import 'build.dart';
+
+class BuildWebCommand extends BuildSubCommand {
+ BuildWebCommand() {
+ usesTargetOption();
+ usesPubOption();
+ defaultBuildMode = BuildMode.release;
+ }
+
+ @override
+ final String name = 'web';
+
+ @override
+ bool get hidden => true;
+
+ @override
+ final String description = '(EXPERIMENTAL) build a web application bundle.';
+
+ @override
+ Future<FlutterCommandResult> runCommand() async {
+ final String target = argResults['target'];
+ final Status status = logger.startProgress('Compiling $target to JavaScript...', timeout: null);
+ final int result = await webCompiler.compile(target: target);
+ status.stop();
+ return FlutterCommandResult(result == 0 ? ExitStatus.success : ExitStatus.fail);
+ }
+}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index ba6a2b1..7a4490b 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -39,6 +39,7 @@
import 'run_hot.dart';
import 'usage.dart';
import 'version.dart';
+import 'web/compile.dart';
import 'windows/windows_workflow.dart';
Future<T> runInContext<T>(
@@ -91,6 +92,7 @@
Usage: () => Usage(),
UserMessages: () => UserMessages(),
WindowsWorkflow: () => const WindowsWorkflow(),
+ WebCompiler: () => const WebCompiler(),
Xcode: () => Xcode(),
XcodeProjectInterpreter: () => XcodeProjectInterpreter(),
},
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
new file mode 100644
index 0000000..4dab748
--- /dev/null
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -0,0 +1,70 @@
+// Copyright 2019 The Chromium 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:meta/meta.dart';
+
+import '../artifacts.dart';
+import '../base/common.dart';
+import '../base/context.dart';
+import '../base/file_system.dart';
+import '../base/io.dart';
+import '../base/process_manager.dart';
+import '../build_info.dart';
+import '../convert.dart';
+import '../globals.dart';
+
+/// The [WebCompiler] instance.
+WebCompiler get webCompiler => context[WebCompiler];
+
+/// A wrapper around dart2js for web compilation.
+class WebCompiler {
+ const WebCompiler();
+
+ /// Compile `target` using dart2js.
+ ///
+ /// `minify` controls whether minifaction of the source is enabled. Defaults to `true`.
+ /// `enabledAssertions` controls whether assertions are enabled. Defaults to `false`.
+ Future<int> compile({@required String target, bool minify = true, bool enabledAssertions = false}) async {
+ final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
+ final String dart2jsPath = artifacts.getArtifactPath(Artifact.dart2jsSnapshot);
+ final String flutterPatchedSdkPath = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
+ final String librariesPath = fs.path.join(flutterPatchedSdkPath, 'libraries.json');
+ final Directory outputDir = fs.directory(getWebBuildDirectory());
+ if (!outputDir.existsSync()) {
+ outputDir.createSync(recursive: true);
+ }
+ final String outputPath = fs.path.join(outputDir.path, 'main.dart.js');
+ if (!processManager.canRun(engineDartPath)) {
+ throwToolExit('Unable to find Dart binary at $engineDartPath');
+ }
+ final List<String> command = <String>[
+ engineDartPath,
+ dart2jsPath,
+ target,
+ '-o',
+ '$outputPath',
+ '--libraries-spec=$librariesPath',
+ '--platform-binaries=$flutterPatchedSdkPath',
+ ];
+ if (minify) {
+ command.add('-m');
+ }
+ if (enabledAssertions) {
+ command.add('--enable-asserts');
+ }
+ printTrace(command.join(' '));
+ final Process result = await processManager.start(command);
+ result
+ .stdout
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen(printStatus);
+ result
+ .stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen(printError);
+ return result.exitCode;
+ }
+}
diff --git a/packages/flutter_tools/test/web/compile_test.dart b/packages/flutter_tools/test/web/compile_test.dart
new file mode 100644
index 0000000..0316a33
--- /dev/null
+++ b/packages/flutter_tools/test/web/compile_test.dart
@@ -0,0 +1,54 @@
+// Copyright 2019 The Chromium 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:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/globals.dart';
+import 'package:flutter_tools/src/web/compile.dart';
+import 'package:mockito/mockito.dart';
+import 'package:process/process.dart';
+
+import '../src/context.dart';
+
+void main() {
+ final MockProcessManager mockProcessManager = MockProcessManager();
+ final MockProcess mockProcess = MockProcess();
+ final BufferLogger mockLogger = BufferLogger();
+
+ testUsingContext('invokes dart2js with correct arguments', () async {
+ const WebCompiler webCompiler = WebCompiler();
+ final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
+ final String dart2jsPath = artifacts.getArtifactPath(Artifact.dart2jsSnapshot);
+ final String flutterPatchedSdkPath = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
+ final String librariesPath = fs.path.join(flutterPatchedSdkPath, 'libraries.json');
+
+ when(mockProcess.stdout).thenAnswer((Invocation invocation) => const Stream<List<int>>.empty());
+ when(mockProcess.stderr).thenAnswer((Invocation invocation) => const Stream<List<int>>.empty());
+ when(mockProcess.exitCode).thenAnswer((Invocation invocation) async => 0);
+ when(mockProcessManager.start(any)).thenAnswer((Invocation invocation) async => mockProcess);
+ when(mockProcessManager.canRun(engineDartPath)).thenReturn(true);
+
+ await webCompiler.compile(target: 'lib/main.dart');
+
+ final String outputPath = fs.path.join('build', 'web', 'main.dart.js');
+ verify(mockProcessManager.start(<String>[
+ engineDartPath,
+ dart2jsPath,
+ 'lib/main.dart',
+ '-o',
+ outputPath,
+ '--libraries-spec=$librariesPath',
+ '--platform-binaries=$flutterPatchedSdkPath',
+ '-m',
+ ])).called(1);
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Logger: () => mockLogger,
+ });
+}
+
+class MockProcessManager extends Mock implements ProcessManager {}
+class MockProcess extends Mock implements Process {}
\ No newline at end of file