As of v13, we bundle our specs with ESBuild to an AMD bundle so that the ESM output can continue to work with Karma in the browser. (There are more reasons on why this is done; not relevant here)
This means that for our development experience we need to heavily rely on source maps. e.g. for assertion errors. Without source maps it would be extremely difficult backtracking where an error ocurred as everything is in a single large AMD bundle file.
Errors initially look like this without source mappings:
Expected false to be true.
<Jasmine>
runHarnessTests/</<@http://localhost:9877/base/angular_material/src/material/select/testing/unit_tests_bundle_spec.js:72191:40
fulfilled@http://localhost:9877/base/angular_material/src/material/select/testing/unit_tests_bundle_spec.js:26:26
invoke@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:372:26
ProxyZoneSpec.prototype.onInvoke@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-testing.js:301:43
invoke@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:371:52
run@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:134:43
scheduleResolveOrReject/<@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:1276:36
invokeTask@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:406:31
ProxyZoneSpec.prototype.onInvokeTask@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-testing.js:332:43
invokeTask@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:405:60
runTask@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:178:47
drainMicroTaskQueue@http://localhost:9877/base/npm/node_modules/zone.js/dist/zone-evergreen.js:582:35
Karma processes the error using a custom reporter where it looks for files in the trace, seqentually visiting every file mentioned in the trace. It first comes across:
<Jasmine>
angular_material/src/material/select/testing/unit_tests_bundle_spec.js:72191:40
- and so on.
If it discovers an actual file when iterating through the trace, it will load the file and its source map, trying to determine the original file + original position. All of this is reasonable so far.
There is one little issue though. Since we emit ES2020 output but downlevel to ES2016 for tests
and the dev-app, async/await
is transformed by ESBuild to use generators + a __async
helper.
This helper function now also shows up in the stacktrace but has no original mapping as it is purely generated by ESBuild. Karma generates a warning for this (unexecptedly IMO), since it came across a file in the trace which could not be mapped. The problem is that the generated helper is part of a file which has a corresponding source-map.
SourceMap position not found for trace: http://localhost:9877/base/angular_material/src/material/select/testing/unit_tests_bundle_spec.js:26:26
Still, the resolved stacktrace looks like this after the transformation:
Expected false to be true.
<Jasmine>
runHarnessTests/</<@../../src/material/select/testing/shared.spec.ts:123:38 <- angular_material/src/material/select/testing/unit_tests_bundle_spec.js:72191:40
fulfilled@angular_material/src/material/select/testing/unit_tests_bundle_spec.js:26:26
invoke@npm/node_modules/zone.js/dist/zone-evergreen.js:372:26
ProxyZoneSpec.prototype.onInvoke@npm/node_modules/zone.js/dist/zone-testing.js:301:43
invoke@npm/node_modules/zone.js/dist/zone-evergreen.js:371:52
run@npm/node_modules/zone.js/dist/zone-evergreen.js:134:43
scheduleResolveOrReject/<@npm/node_modules/zone.js/dist/zone-evergreen.js:1276:36
invokeTask@npm/node_modules/zone.js/dist/zone-evergreen.js:406:31
ProxyZoneSpec.prototype.onInvokeTask@npm/node_modules/zone.js/dist/zone-testing.js:332:43
invokeTask@npm/node_modules/zone.js/dist/zone-evergreen.js:405:60
runTask@npm/node_modules/zone.js/dist/zone-evergreen.js:178:47
drainMicroTaskQueue@npm/node_modules/zone.js/dist/zone-evergreen.js:582:35
The unnecessary warnings by Karma in the case of downleveled async/await is polluting the Karma test output currently, so it's something we should try to hide. It's unclear yet how t
This does not surface there because we always build TypeScript with importHelpers: true
, so
that the __async
helper will actually come from node_modules/tslib
which has proper source
mappings then/or no mappings at all (so there is no warning). ESBuild does not have such functionality.
The CLI seems to rely on Babel here to downlevel async/await. The rest remains ES2020.
Simply using the downlevel async/await Babel plugin is not sufficient though as it results
in the same problem of generated functions. The CLI solves this though by relying on similar
helpers to tslib
, called the @babel/runtime
, so that the async generator wrapper is actually
no longer part of a file for which source maps are not available (hence no attemp/warning):
Chrome 95.0.4638.69 (Mac OS 10.15.7) a b FAILED
Error: Expected true to be false.
at <Jasmine>
at UserContext.<anonymous> (src/app/test.spec.ts:4:22)
at Generator.next (<anonymous>)
at asyncGeneratorStep (node_modules/@babel/runtime/helpers/asyncToGenerator.js
It looks like we can fix this by following what the CLI internally does. This would allow us to also run the tests with ES2020, while we'd just donwlevel the necessary async/await portion. We'd need to setup the babel runtime transform as well, so that the async helper is coming from an external file in "node_modules/@babel/runtime" (solving the source map warning issue from Karma since the helper comes from a file without any source maps)
Expected false to be true.
<Jasmine>
runHarnessTests/<@../../src/material/select/testing/shared.spec.ts:123:38 <- angular_material/src/material/select/testing/unit_tests_bundle_spec.js:73855:40
asyncGeneratorStep@../../node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:3:20 <- angular_material/src/material/select/testing/unit_tests_bundle_spec.js:72432:26
_next@../../node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js:25:9 <- angular_material/src/material/select/testing/unit_tests_bundle_spec.js:72450:29
invoke@npm/node_modules/zone.js/dist/zone-evergreen.js:372:26
ProxyZoneSpec.prototype.onInvoke@npm/node_modules/zone.js/dist/zone-testing.js:301:43
invoke@npm/node_modules/zone.js/dist/zone-evergreen.js:371:52
run@npm/node_modules/zone.js/dist/zone-evergreen.js:134:43
scheduleResolveOrReject/<@npm/node_modules/zone.js/dist/zone-evergreen.js:1276:36