diff --git a/README.md b/README.md index ca2b514..d12a109 100644 --- a/README.md +++ b/README.md @@ -362,11 +362,11 @@ void Promise.all([import("unit/controller/App.qunit")]).then(() => { will be converted to: ```js -"sap.ui.require([], function () { - "use strict"; +"use strict"; +QUnit.config.autostart = false; +"sap.ui.require([], function () { function __ui5_require_async(path) { /* ... */ } - QUnit.config.autostart = false; void Promise.all([__ui5_require_async("unit/controller/App.qunit")]).then(() => { QUnit.start(); }); @@ -375,6 +375,8 @@ will be converted to: > :warning: Although `sap.ui.define` and `sap.ui.require` may appear similar from an API perspective, they have different behaviors. To understand these differences, please read the section titled "Using sap.ui.require instead of sap.ui.define on the top level" in the [Troubleshooting for Loading Modules](https://ui5.sap.com/#/topic/4363b3fe3561414ca1b030afc8cd30ce). +> :bulb: The plugin detects the global usage of `QUnit.config.autostart` and moves this out of the `sap.ui.require` or `sap.ui.define` block automatically to ensure that the config is applied sychronously when loading the module. The move can be supressed with the configuration option `noWrapQUnitConfigAutostart`. If `QUnit` is imported, e.g. `import QUnit from "qunit";` then this is detected and the autostart config is not moved as it must apply locally. + ### Converting ES classes into Control.extend(..) syntax By default, the plugin converts ES classes to `Control.extend(..)` syntax if the class extends from a class which has been imported. @@ -795,6 +797,7 @@ In general, comments are preserved, but for each class property/method whose pos ### Wrapping - `noWrapBeforeImport` (Default: false) Does not wrap code before the first import (if there are imports). +- `noWrapQUnitConfigAutostart` (Default: true) Does not wrap the `QUnit.config.autostart` in the `sap.ui.require` or `sap.ui.define` block. ### Class Conversion diff --git a/packages/plugin/__test__/__snapshots__/test.js.snap b/packages/plugin/__test__/__snapshots__/test.js.snap index 54f8b47..8929288 100644 --- a/packages/plugin/__test__/__snapshots__/test.js.snap +++ b/packages/plugin/__test__/__snapshots__/test.js.snap @@ -1647,9 +1647,40 @@ exports[`sap-ui-require othermodule-noannotation.js 1`] = ` `; exports[`sap-ui-require testsuite-annotation.qunit.js 1`] = ` -"sap.ui.require([], function () { +""use strict"; + +QUnit.config.autostart = false; +sap.ui.require([], function () { + function __ui5_require_async(path) { + return new Promise(function (resolve, reject) { + sap.ui.require([path], function (module) { + if (!(module && module.__esModule)) { + module = module === null || !(typeof module === "object" && path.endsWith("/library")) ? { + default: module + } : module; + Object.defineProperty(module, "__esModule", { + value: true + }); + } + resolve(module); + }, function (err) { + reject(err); + }); + }); + } + void Promise.all([__ui5_require_async("unit/controller/App.qunit")]).then(() => { + QUnit.start(); + }); +});" +`; + +exports[`sap-ui-require testsuite-annotation-with-qunit-import.qunit.js 1`] = ` +"sap.ui.require(["qunit"], function (__QUnit) { "use strict"; + function _interopRequireDefault(obj) { + return obj && obj.__esModule && typeof obj.default !== "undefined" ? obj.default : obj; + } function __ui5_require_async(path) { return new Promise(function (resolve, reject) { sap.ui.require([path], function (module) { @@ -1667,6 +1698,7 @@ exports[`sap-ui-require testsuite-annotation.qunit.js 1`] = ` }); }); } + const QUnit = _interopRequireDefault(__QUnit); QUnit.config.autostart = false; void Promise.all([__ui5_require_async("unit/controller/App.qunit")]).then(() => { QUnit.start(); @@ -1675,9 +1707,10 @@ exports[`sap-ui-require testsuite-annotation.qunit.js 1`] = ` `; exports[`sap-ui-require testsuite-noannotation.qunit.js 1`] = ` -"sap.ui.define([], function () { - "use strict"; +""use strict"; +QUnit.config.autostart = false; +sap.ui.define([], function () { function __ui5_require_async(path) { return new Promise(function (resolve, reject) { sap.ui.require([path], function (module) { @@ -1695,7 +1728,6 @@ exports[`sap-ui-require testsuite-noannotation.qunit.js 1`] = ` }); }); } - QUnit.config.autostart = false; void Promise.all([__ui5_require_async("unit/controller/App.qunit")]).then(() => { QUnit.start(); }); diff --git a/packages/plugin/__test__/fixtures/sap-ui-require/testsuite-annotation-with-qunit-import.qunit.js b/packages/plugin/__test__/fixtures/sap-ui-require/testsuite-annotation-with-qunit-import.qunit.js new file mode 100644 index 0000000..c7aa790 --- /dev/null +++ b/packages/plugin/__test__/fixtures/sap-ui-require/testsuite-annotation-with-qunit-import.qunit.js @@ -0,0 +1,10 @@ +/* @sapUiRequire */ +import QUnit from "qunit"; + +// https://api.qunitjs.com/config/autostart/ +QUnit.config.autostart = false; + +// import all your QUnit tests here +void Promise.all([import("unit/controller/App.qunit")]).then(() => { + QUnit.start(); +}); diff --git a/packages/plugin/src/modules/helpers/wrapper.js b/packages/plugin/src/modules/helpers/wrapper.js index 8cd0a8b..c1b0481 100644 --- a/packages/plugin/src/modules/helpers/wrapper.js +++ b/packages/plugin/src/modules/helpers/wrapper.js @@ -83,6 +83,7 @@ export function wrap(visitor, programNode, opts) { } } + let moveUseStrictIfNeeded = false; const preDefine = [...ignoredImports]; // If the noWrapBeforeImport opt is set, split any code before the first import and afterwards into separate arrays. // This should be done before any interops or other vars are injected. @@ -101,6 +102,25 @@ export function wrap(visitor, programNode, opts) { reachedFirstImport = true; } } + moveUseStrictIfNeeded = true; + body = newBody; + } + + // If the QUnit.config.autostart is found it needs to be moved to the top of the program + if ( + opts.noWrapQUnitConfigAutostart === undefined || + opts.noWrapQUnitConfigAutostart + ) { + const qunitConfigAutostart = findQUnitConfigAutostart(body, imports); + if (qunitConfigAutostart) { + preDefine.push(qunitConfigAutostart); + body = body.filter((node) => node !== qunitConfigAutostart); + moveUseStrictIfNeeded = true; + } + } + + // if code has been moved to preDefine, we need to move the "use strict" directive + if (moveUseStrictIfNeeded) { if ( !opts.neverUseStrict && preDefine.length && @@ -111,7 +131,6 @@ export function wrap(visitor, programNode, opts) { ...(programNode.directives || []), ]; } - body = newBody; } if (injectDynamicImportHelper) { @@ -220,6 +239,32 @@ function hasUseSapUiRequire(comments, body, remove) { }); } +function findQUnitConfigAutostart(body, imports) { + // if one imports QUnit, we don't need to move the QUnit.config.autostart + // as the configuration should apply to the local QUnit module + if (imports?.some((imp) => imp.name === "QUnit")) { + return undefined; + } + // find the QUnit.config.autostart + return body?.find((node) => { + return ( + t.isExpressionStatement(node) && + t.isAssignmentExpression(node.expression) && + t.isMemberExpression(node.expression.left) && + t.isMemberExpression(node.expression.left.object) && + t.isIdentifier(node.expression.left.object.object) && + node.expression.left.object.object.name === "QUnit" && + t.isIdentifier(node.expression.left.object.property) && + node.expression.left.object.property.name === "config" && + t.isIdentifier(node.expression.left.property) && + node.expression.left.property.name === "autostart" && + node.expression.operator === "=" /* && + t.isBooleanLiteral(node.expression.right) && + node.expression.right.value === false */ + ); + }); +} + function generateDefineOrRequire(body, imports, exportGlobal, useRequire) { const defineOpts = { SOURCES: t.arrayExpression(imports.map((i) => t.stringLiteral(i.src))),