web_test.dart 19.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:file_testing/file_testing.dart';
6
import 'package:flutter_tools/src/artifacts.dart';
7
import 'package:flutter_tools/src/base/file_system.dart';
8
import 'package:flutter_tools/src/base/platform.dart';
9
import 'package:flutter_tools/src/build_info.dart';
10
import 'package:flutter_tools/src/build_system/build_system.dart';
11
import 'package:flutter_tools/src/build_system/depfile.dart';
12
import 'package:flutter_tools/src/build_system/targets/common.dart';
13
import 'package:flutter_tools/src/build_system/targets/web.dart';
14
import 'package:flutter_tools/src/globals.dart' as globals;
15 16 17 18
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';

import '../../../src/common.dart';
19
import '../../../src/context.dart';
20 21 22
import '../../../src/mocks.dart';
import '../../../src/testbed.dart';

23 24
const List<String> kDart2jsLinuxArgs = <String>[
  'bin/cache/dart-sdk/bin/dart',
25
   '--disable-dart-dev',
26 27 28 29
  'bin/cache/dart-sdk/bin/snapshots/dart2js.dart.snapshot',
  '--libraries-spec=bin/cache/flutter_web_sdk/libraries.json',
];

30 31 32
void main() {
  Testbed testbed;
  Environment environment;
33 34 35 36 37 38 39 40 41
  FakeProcessManager processManager;
  final Platform linux = FakePlatform(
    operatingSystem: 'linux',
    environment: <String, String>{},
  );
  final Platform windows = FakePlatform(
    operatingSystem: 'windows',
    environment: <String, String>{},
  );
42
  DepfileService depfileService;
43 44 45

  setUp(() {
    testbed = Testbed(setup: () {
46
      globals.fs.file('.packages')
47
        ..createSync(recursive: true)
48
        ..writeAsStringSync('foo:foo/lib/\n');
49
      globals.fs.currentDirectory.childDirectory('bar').createSync();
50
      processManager = FakeProcessManager.list(<FakeCommand>[]);
51

52 53
      environment = Environment.test(
        globals.fs.currentDirectory,
54
        projectDir: globals.fs.currentDirectory.childDirectory('foo'),
55
        outputDir: globals.fs.currentDirectory.childDirectory('bar'),
56
        defines: <String, String>{
57
          kTargetFile: globals.fs.path.join('foo', 'lib', 'main.dart'),
58 59 60 61 62
        },
        artifacts: MockArtifacts(),
        processManager: FakeProcessManager.any(),
        logger: globals.logger,
        fileSystem: globals.fs,
63
      );
64 65 66 67
      depfileService = DepfileService(
      fileSystem: globals.fs,
      logger: globals.logger,
    );
68 69
      environment.buildDir.createSync(recursive: true);
    }, overrides: <Type, Generator>{
70
      Platform: () => linux,
71 72 73 74 75 76 77 78 79 80 81
    });
  });

  test('WebEntrypointTarget generates an entrypoint with plugins and init platform', () => testbed.run(() async {
    environment.defines[kHasWebPlugins] = 'true';
    environment.defines[kInitializePlatform] = 'true';
    await const WebEntrypointTarget().build(environment);

    final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();

    // Plugins
82
    expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
83 84 85 86 87 88 89 90 91
    expect(generated, contains('registerPlugins(webPluginRegistry);'));

    // Platform
    expect(generated, contains('if (true) {'));

    // Main
    expect(generated, contains('entrypoint.main();'));

    // Import.
92
    expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
93 94
  }));

95
  test('WebReleaseBundle copies dart2js output and resource files to output directory', () => testbed.run(() async {
96
    environment.defines[kBuildMode] = 'release';
97 98
    final Directory webResources = environment.projectDir.childDirectory('web');
    webResources.childFile('index.html')
99 100 101 102 103 104 105 106 107
      ..createSync(recursive: true)
      ..writeAsStringSync('''
<html>
  <script src="main.dart.js" type="application/javascript"></script>
  <script>
    navigator.serviceWorker.register('flutter_service_worker.js');
  </script>
</html>
''');
108
    webResources.childFile('foo.txt')
109
      .writeAsStringSync('A');
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    environment.buildDir.childFile('main.dart.js').createSync();

    await const WebReleaseBundle().build(environment);

    expect(environment.outputDir.childFile('foo.txt')
      .readAsStringSync(), 'A');
    expect(environment.outputDir.childFile('main.dart.js')
      .existsSync(), true);
    expect(environment.outputDir.childDirectory('assets')
      .childFile('AssetManifest.json').existsSync(), true);

    // Update to arbitary resource file triggers rebuild.
    webResources.childFile('foo.txt').writeAsStringSync('B');

    await const WebReleaseBundle().build(environment);

    expect(environment.outputDir.childFile('foo.txt')
      .readAsStringSync(), 'B');
128 129 130 131 132
    // Appends number to requests for service worker and main.dart.js
    expect(environment.outputDir.childFile('index.html').readAsStringSync(), allOf(
      contains('<script src="main.dart.js?v='),
      contains('flutter_service_worker.js?v='),
    ));
133 134
  }));

135
  test('WebEntrypointTarget generates an entrypoint for a file outside of main', () => testbed.run(() async {
136
    environment.defines[kTargetFile] = globals.fs.path.join('other', 'lib', 'main.dart');
137 138 139 140 141 142 143 144
    await const WebEntrypointTarget().build(environment);

    final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();

    // Import.
    expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;"));
  }));

145 146 147 148 149 150 151 152 153
  test('WebEntrypointTarget generates a plugin registrant for a file outside of main', () => testbed.run(() async {
    environment.defines[kTargetFile] = globals.fs.path.join('other', 'lib', 'main.dart');
    environment.defines[kHasWebPlugins] = 'true';
    await const WebEntrypointTarget().build(environment);

    final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();

    // Import.
    expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;"));
154
    expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
155 156
  }));

157

158 159 160 161 162 163 164 165
  test('WebEntrypointTarget generates an entrypoint with plugins and init platform on windows', () => testbed.run(() async {
    environment.defines[kHasWebPlugins] = 'true';
    environment.defines[kInitializePlatform] = 'true';
    await const WebEntrypointTarget().build(environment);

    final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();

    // Plugins
166
    expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
167 168 169 170 171 172 173 174 175
    expect(generated, contains('registerPlugins(webPluginRegistry);'));

    // Platform
    expect(generated, contains('if (true) {'));

    // Main
    expect(generated, contains('entrypoint.main();'));

    // Import.
176
    expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
177
  }, overrides: <Type, Generator>{
178
    Platform: () => windows,
179 180 181 182 183 184 185 186 187 188
  }));

  test('WebEntrypointTarget generates an entrypoint without plugins and init platform', () => testbed.run(() async {
    environment.defines[kHasWebPlugins] = 'false';
    environment.defines[kInitializePlatform] = 'true';
    await const WebEntrypointTarget().build(environment);

    final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();

    // Plugins
189
    expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';")));
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    expect(generated, isNot(contains('registerPlugins(webPluginRegistry);')));

    // Platform
    expect(generated, contains('if (true) {'));

    // Main
    expect(generated, contains('entrypoint.main();'));
  }));

  test('WebEntrypointTarget generates an entrypoint with plugins and without init platform', () => testbed.run(() async {
    environment.defines[kHasWebPlugins] = 'true';
    environment.defines[kInitializePlatform] = 'false';
    await const WebEntrypointTarget().build(environment);

    final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();

    // Plugins
207
    expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    expect(generated, contains('registerPlugins(webPluginRegistry);'));

    // Platform
    expect(generated, contains('if (false) {'));

    // Main
    expect(generated, contains('entrypoint.main();'));
  }));

  test('WebEntrypointTarget generates an entrypoint without plugins and without init platform', () => testbed.run(() async {
    environment.defines[kHasWebPlugins] = 'false';
    environment.defines[kInitializePlatform] = 'false';
    await const WebEntrypointTarget().build(environment);

    final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();

    // Plugins
225
    expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';")));
226 227 228 229 230 231 232 233 234
    expect(generated, isNot(contains('registerPlugins(webPluginRegistry);')));

    // Platform
    expect(generated, contains('if (false) {'));

    // Main
    expect(generated, contains('entrypoint.main();'));
  }));

235 236 237
  test('Dart2JSTarget calls dart2js with expected args with csp', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'profile';
    environment.defines[kCspMode] = 'true';
238 239 240
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
241
        '-Ddart.vm.profile=true',
242 243
        '-o',
        environment.buildDir.childFile('app.dill').absolute.path,
244
        '--packages=.packages',
245 246 247 248 249 250 251 252
        '--cfe-only',
        environment.buildDir.childFile('main.dart').absolute.path,
      ]
    ));
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '-Ddart.vm.profile=true',
253
        '-O4',
254 255 256 257 258 259 260
        '--no-minify',
        '--csp',
        '-o',
        environment.buildDir.childFile('main.dart.js').absolute.path,
        environment.buildDir.childFile('app.dill').absolute.path,
      ]
    ));
261

262
    await const Dart2JSTarget().build(environment);
263
  }, overrides: <Type, Generator>{
264
    ProcessManager: () => processManager,
265 266 267
  }));


268 269
  test('Dart2JSTarget calls dart2js with expected args with enabled experiment', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'profile';
270
    environment.defines[kExtraFrontEndOptions] = '--enable-experiment=non-nullable';
271 272 273 274
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '--enable-experiment=non-nullable',
275
        '-Ddart.vm.profile=true',
276 277
        '-o',
        environment.buildDir.childFile('app.dill').absolute.path,
278
        '--packages=.packages',
279 280 281 282 283 284 285 286 287
        '--cfe-only',
        environment.buildDir.childFile('main.dart').absolute.path,
      ]
    ));
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '--enable-experiment=non-nullable',
        '-Ddart.vm.profile=true',
288
        '-O4',
289 290 291 292 293 294 295 296 297 298 299 300
        '--no-minify',
        '-o',
        environment.buildDir.childFile('main.dart.js').absolute.path,
        environment.buildDir.childFile('app.dill').absolute.path,
      ]
    ));

    await const Dart2JSTarget().build(environment);
  }, overrides: <Type, Generator>{
    ProcessManager: () => processManager,
  }));

301 302
  test('Dart2JSTarget calls dart2js with expected args in profile mode', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'profile';
303 304 305
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
306
        '-Ddart.vm.profile=true',
307 308
        '-o',
        environment.buildDir.childFile('app.dill').absolute.path,
309
        '--packages=.packages',
310 311 312 313 314 315 316 317
        '--cfe-only',
        environment.buildDir.childFile('main.dart').absolute.path,
      ]
    ));
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '-Ddart.vm.profile=true',
318
        '-O4',
319 320 321 322 323 324
        '--no-minify',
        '-o',
        environment.buildDir.childFile('main.dart.js').absolute.path,
        environment.buildDir.childFile('app.dill').absolute.path,
      ]
    ));
325

326
    await const Dart2JSTarget().build(environment);
327
  }, overrides: <Type, Generator>{
328
    ProcessManager: () => processManager,
329 330 331 332
  }));

  test('Dart2JSTarget calls dart2js with expected args in release mode', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'release';
333 334 335
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
336
        '-Ddart.vm.product=true',
337 338
        '-o',
        environment.buildDir.childFile('app.dill').absolute.path,
339
        '--packages=.packages',
340 341 342 343 344 345 346 347
        '--cfe-only',
        environment.buildDir.childFile('main.dart').absolute.path,
      ]
    ));
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '-Ddart.vm.product=true',
348
        '-O4',
349 350 351 352 353
        '-o',
        environment.buildDir.childFile('main.dart.js').absolute.path,
        environment.buildDir.childFile('app.dill').absolute.path,
      ]
    ));
354

355
    await const Dart2JSTarget().build(environment);
356
  }, overrides: <Type, Generator>{
357
    ProcessManager: () => processManager,
358 359 360 361 362
  }));

  test('Dart2JSTarget calls dart2js with expected args in release with dart2js optimization override', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'release';
    environment.defines[kDart2jsOptimization] = 'O3';
363 364 365
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
366
        '-Ddart.vm.product=true',
367 368
        '-o',
        environment.buildDir.childFile('app.dill').absolute.path,
369
        '--packages=.packages',
370 371 372 373 374 375 376 377
        '--cfe-only',
        environment.buildDir.childFile('main.dart').absolute.path,
      ]
    ));
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '-Ddart.vm.product=true',
378
        '-O3',
379 380 381 382 383
        '-o',
        environment.buildDir.childFile('main.dart.js').absolute.path,
        environment.buildDir.childFile('app.dill').absolute.path,
      ]
    ));
384

385
    await const Dart2JSTarget().build(environment);
386
  }, overrides: <Type, Generator>{
387
    ProcessManager: () => processManager,
388
  }));
389 390 391

  test('Dart2JSTarget produces expected depfile', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'release';
392
    when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async {
393
      environment.buildDir.childFile('app.dill.deps')
394
        .writeAsStringSync('file:///a.dart');
395 396 397 398
      return FakeProcessResult(exitCode: 0);
    });
    await const Dart2JSTarget().build(environment);

399
    expect(environment.buildDir.childFile('dart2js.d'), exists);
400
    final Depfile depfile = depfileService.parse(environment.buildDir.childFile('dart2js.d'));
401

402
    expect(depfile.inputs.single.path, globals.fs.path.absolute('a.dart'));
403 404 405 406 407
    expect(depfile.outputs.single.path,
      environment.buildDir.childFile('main.dart.js').absolute.path);
  }, overrides: <Type, Generator>{
    ProcessManager: () => MockProcessManager(),
  }));
408 409 410

  test('Dart2JSTarget calls dart2js with Dart defines in release mode', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'release';
411
    environment.defines[kDartDefines] = 'FOO=bar,BAZ=qux';
412 413 414
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
415
        '-Ddart.vm.product=true',
416 417
        '-DFOO=bar',
        '-DBAZ=qux',
418 419 420
        '-o',
        environment.buildDir.childFile('app.dill').absolute.path,
        '--packages=.packages',
421
       '--cfe-only',
422 423 424 425 426 427 428 429 430
        environment.buildDir.childFile('main.dart').absolute.path,
      ]
    ));
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '-Ddart.vm.product=true',
        '-DFOO=bar',
        '-DBAZ=qux',
431
        '-O4',
432 433 434 435 436
        '-o',
        environment.buildDir.childFile('main.dart.js').absolute.path,
        environment.buildDir.childFile('app.dill').absolute.path,
      ]
    ));
437

438
    await const Dart2JSTarget().build(environment);
439
  }, overrides: <Type, Generator>{
440
    ProcessManager: () => processManager,
441 442 443 444
  }));

  test('Dart2JSTarget calls dart2js with Dart defines in profile mode', () => testbed.run(() async {
    environment.defines[kBuildMode] = 'profile';
445
    environment.defines[kDartDefines] = 'FOO=bar,BAZ=qux';
446 447 448
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
449
        '-Ddart.vm.profile=true',
450 451
        '-DFOO=bar',
        '-DBAZ=qux',
452 453 454
        '-o',
        environment.buildDir.childFile('app.dill').absolute.path,
        '--packages=.packages',
455 456 457 458 459 460 461 462 463 464
        '--cfe-only',
        environment.buildDir.childFile('main.dart').absolute.path,
      ]
    ));
    processManager.addCommand(FakeCommand(
      command: <String>[
        ...kDart2jsLinuxArgs,
        '-Ddart.vm.profile=true',
        '-DFOO=bar',
        '-DBAZ=qux',
465
        '-O4',
466 467 468 469 470 471
        '--no-minify',
        '-o',
        environment.buildDir.childFile('main.dart.js').absolute.path,
        environment.buildDir.childFile('app.dill').absolute.path,
      ]
    ));
472

473
    await const Dart2JSTarget().build(environment);
474
  }, overrides: <Type, Generator>{
475
    ProcessManager: () => processManager,
476 477
  }));

478
  test('Generated service worker correctly inlines file hashes', () {
479
    final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>[]);
480 481 482 483

    expect(result, contains('{\n  "/foo": "abcd"\n};'));
  });

484 485 486 487 488 489
  test('Generated service worker includes core files', () {
    final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>['foo', 'bar']);

    expect(result, contains('"foo",\n"bar"'));
  });

490
  test('WebServiceWorker generates a service_worker for a web resource folder', () => testbed.run(() async {
491
    environment.outputDir.childDirectory('a').childFile('a.txt')
492 493 494 495 496 497 498
      ..createSync(recursive: true)
      ..writeAsStringSync('A');
    await const WebServiceWorker().build(environment);

    expect(environment.outputDir.childFile('flutter_service_worker.js'), exists);
    // Contains file hash.
    expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
499
      contains('"a/a.txt": "7fc56270e7a70fa81a5935b72eacbe29"'));
500 501
    expect(environment.buildDir.childFile('service_worker.d'), exists);
    // Depends on resource file.
502 503 504 505 506
    expect(environment.buildDir.childFile('service_worker.d').readAsStringSync(),
      contains('a/a.txt'));
    // Contains NOTICES
    expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
      contains('NOTICES'));
507
  }));
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522

  test('WebServiceWorker contains baseUrl cache', () => testbed.run(() async {
    environment.outputDir
      .childFile('index.html')
      .createSync(recursive: true);
    await const WebServiceWorker().build(environment);

    expect(environment.outputDir.childFile('flutter_service_worker.js'), exists);
    // Contains file hash for both `/` and index.html.
    expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
      contains('"/": "d41d8cd98f00b204e9800998ecf8427e"'));
    expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
      contains('"index.html": "d41d8cd98f00b204e9800998ecf8427e"'));
    expect(environment.buildDir.childFile('service_worker.d'), exists);
  }));
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539

  test('WebServiceWorker does not cache source maps', () => testbed.run(() async {
    environment.outputDir
      .childFile('main.dart.js')
      .createSync(recursive: true);
    environment.outputDir
      .childFile('main.dart.js.map')
      .createSync(recursive: true);
    await const WebServiceWorker().build(environment);

    // No caching of source maps.
    expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
      isNot(contains('"main.dart.js.map"')));
    // Expected twice, once for RESOURCES and once for CORE.
    expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
      contains('"main.dart.js"'));
  }));
540 541 542
}

class MockProcessManager extends Mock implements ProcessManager {}
543
class MockArtifacts extends Mock implements Artifacts {}