();
+ const definePropertyRe = /Object\.defineProperty\(\s*exports\s*,\s*["']([^"']+)["']/g;
+ let match = definePropertyRe.exec(moduleSource);
+ while (match) {
+ const name = match[1];
+ if (name !== '__esModule' && name !== 'default' && /^[A-Za-z_$][\w$]*$/.test(name)) {
+ names.add(name);
+ }
+ match = definePropertyRe.exec(moduleSource);
+ }
+
+ return [...names].sort();
+}
+
+function wrapWebpackVendorAsEsm(source: string): string {
+ const namedExports = collectWebpackEntryExportNames(source)
+ .map((name) => `export const ${name} = module.exports.${name};`)
+ .join('\n');
+
+ return wrapVendorCjsBranch(source, {
+ rewriteThis: false,
+ trailing: `${ESM_DEFAULT_FROM_CJS}${namedExports ? `${namedExports}\n` : ''}`,
+ });
+}
+
+/** globalize / cldrjs ship as UMD; native ESM needs a CJS-branch + require shim. */
+function wrapGlobalizeOrCldrAsEsm(source: string, relativeUrlPath: string): string {
+ const normalized = normalizeUrlPath(relativeUrlPath);
+ const isCldrMain = normalized.endsWith('/cldrjs/dist/cldr.js');
+ const isCldrPlugin = /\/cldrjs\/dist\/cldr\/[^/]+\.js$/i.test(normalized);
+ const isGlobalizeMain = normalized.endsWith('/globalize/dist/globalize.js');
+ const isGlobalizePlugin = normalized.includes('/globalize/dist/globalize/');
+ const baseName = path.basename(normalized, '.js');
+ const needsNumber = isGlobalizePlugin && (baseName === 'currency' || baseName === 'date');
+
+ const preamble: string[] = [];
+ if (isCldrPlugin) {
+ preamble.push('import __dxCldr from \'cldr\';');
+ } else if (isGlobalizeMain || isGlobalizePlugin) {
+ preamble.push('import __dxCldr from \'cldr\';');
+ preamble.push('import \'cldr/event\';');
+ if (isGlobalizePlugin) {
+ preamble.push('import \'cldr/supplemental\';');
+ preamble.push('import __dxGlobalize from \'globalize\';');
+ if (needsNumber) {
+ // CJS factory skips `./number`; AMD/DevExtreme always load it first.
+ preamble.push('import \'./number.js\';');
+ }
+ }
+ }
+
+ const requireShim = isCldrMain
+ ? 'function require(id) { throw new Error(\'Unexpected require in cldr: \' + id); }\n'
+ : [
+ 'function require(id) {\n',
+ ' if (id === \'cldrjs\' || id === \'cldr\' || id === \'../cldr\') {\n',
+ ' return __dxCldr;\n',
+ ' }\n',
+ ' if (id === \'../globalize\' || id === \'globalize\') {\n',
+ ' return __dxGlobalize;\n',
+ ' }\n',
+ ' throw new Error(\'Unhandled require in globalize/cldr UMD: \' + id);\n',
+ '}\n',
+ ].join('');
+
+ return wrapVendorCjsBranch(source, {
+ preamble: preamble.join('\n'),
+ requireShim,
+ });
+}
+
+/**
+ * Vector map geo data UMD: CJS writes into `exports`, browser branch expects
+ * bare `DevExpress`. Under ESM imports hoist above suite setup, so create the
+ * global sources bag and point `module.exports` at the same object.
+ */
+function wrapVectorMapDataAsEsm(source: string): string {
+ return wrapVendorCjsBranch(source, {
+ preamble: [
+ 'globalThis.DevExpress = globalThis.DevExpress || {};',
+ 'globalThis.DevExpress.viz = globalThis.DevExpress.viz || {};',
+ 'globalThis.DevExpress.viz.map = globalThis.DevExpress.viz.map || {};',
+ 'globalThis.DevExpress.viz.map.sources = globalThis.DevExpress.viz.map.sources || {};',
+ ].join('\n'),
+ exportsInit: 'globalThis.DevExpress.viz.map.sources',
+ trailing: 'export default module.exports;\n',
+ });
+}
+
+/** `dx.vectormaputils.js` is UMD (`exports.parse = …`); tests do `import { parse }`. */
+function wrapVectorMapUtilsAsEsm(source: string): string {
+ return wrapVendorCjsBranch(source, {
+ rewriteThis: false,
+ trailing: 'export default module.exports;\nexport const parse = module.exports.parse;\n',
+ });
+}
+
+type VendorWrapper = (source: string, relativeUrlPath: string) => string;
+
+function resolveVendorEsmWrapper(relativeUrlPath: string): VendorWrapper | null {
+ const normalized = normalizeUrlPath(relativeUrlPath);
+
+ if (
+ normalized.endsWith('/intl/dist/Intl.complete.js')
+ || normalized.endsWith('/intl/dist/Intl.js')
+ ) {
+ return (source) => wrapIntlVendorAsEsm(source);
+ }
+
+ if (
+ normalized.endsWith('/globalize/dist/globalize.js')
+ || normalized.includes('/globalize/dist/globalize/')
+ || normalized.endsWith('/cldrjs/dist/cldr.js')
+ || /\/cldrjs\/dist\/cldr\/[^/]+\.js$/i.test(normalized)
+ ) {
+ return wrapGlobalizeOrCldrAsEsm;
+ }
+
+ if (/\/artifacts\/js\/vectormap-data\/[^/]+\.js$/i.test(normalized)) {
+ return (source) => wrapVectorMapDataAsEsm(source);
+ }
+
+ if (/\/artifacts\/js\/vectormap-utils\/dx\.vectormaputils\.js$/i.test(normalized)) {
+ return (source) => wrapVectorMapUtilsAsEsm(source);
+ }
+
+ if (
+ normalized.endsWith('/devextreme-quill/dist/dx-quill.js')
+ || normalized.endsWith('/artifacts/js/dx-diagram.js')
+ || normalized.endsWith('/artifacts/js/dx-gantt.js')
+ || normalized.endsWith('/artifacts/js/dx-exceljs-fork.js')
+ || normalized.endsWith('/artifacts/js/jszip.js')
+ ) {
+ return (source) => wrapWebpackVendorAsEsm(source);
+ }
+
+ return null;
+}
+
+/** Serve JSON as `export default …` for native ESM (`*.json!` replacement). */
+function sendJsonAsEsmModule(res: ServerResponse, filePath: string): boolean {
+ return sendTransformedJs(
+ res,
+ filePath,
+ (raw) => {
+ JSON.parse(raw);
+ return `export default ${raw};\n`;
+ },
+ 'Failed to export JSON as ESM module',
+ );
+}
+
+// --- Mutable artifact facades ---------------------------------------------------
+
+function sendMutableFacadeModule(res: ServerResponse, shimUrl: string): boolean {
+ // Serve a re-export at the artifact URL so relative library imports and
+ // bare import-map entries share the same shim module graph.
+ return sendJsModuleBody(
+ res,
+ `export * from '${shimUrl}';\nexport { default } from '${shimUrl}';\n`,
+ );
+}
+
+// --- ESM artifact tweaks --------------------------------------------------------
+
+/**
+ * Convert leftover `exports.foo = …` (from #DEBUG / dual CJS-ESM sources)
+ * into native ESM exports so the browser does not throw "exports is not defined".
+ */
+function rewriteLegacyCjsExportsInEsmArtifact(source: string): string {
+ if (!/\bexports\./.test(source)) {
+ return source;
+ }
+
+ return source
+ .replace(
+ /^exports\.([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*);?\s*$/gm,
+ (_match, exportName: string, valueName: string) => (exportName === valueName
+ ? `export { ${exportName} };`
+ : `export { ${valueName} as ${exportName} };`),
+ )
+ .replace(
+ /^exports\.([A-Za-z_$][\w$]*)\s*=\s*function\s*\(/gm,
+ 'export function $1(',
+ )
+ .replace(
+ /^exports\.([A-Za-z_$][\w$]*)\s*=\s*async\s+function\s*\(/gm,
+ 'export async function $1(',
+ );
+}
+
+/**
+ * ESM npm artifacts are built with removeDebug:true, which strips QUnit-only
+ * hooks. Re-attach the ones still present as locals in the compiled module.
+ *
+ * When debug is kept (`-c qunit`), some sources still emit CJS `exports.*`
+ * assignments that throw under native ESM — rewrite those to ESM exports.
+ */
+function restoreEsmDebugTestHooks(relativeUrlPath: string, source: string): string {
+ const normalized = normalizeUrlPath(relativeUrlPath);
+ if (!normalized.includes(ESM_ARTIFACT_MARKER)) {
+ return source;
+ }
+
+ let next = source;
+
+ if (normalized.endsWith('/__internal/events/core/m_events_engine.js')
+ && !next.includes('eventsEngine.detectPassiveEventHandlersSupport')) {
+ next = next.replace(
+ /eventsEngine\.passiveEventHandlersSupported\s*=\s*passiveEventHandlersSupported;/,
+ 'eventsEngine.passiveEventHandlersSupported = passiveEventHandlersSupported;\n'
+ + 'eventsEngine.elementDataMap = elementDataMap;\n'
+ + 'eventsEngine.detectPassiveEventHandlersSupport = detectPassiveEventHandlersSupport;',
+ );
+ }
+
+ return rewriteLegacyCjsExportsInEsmArtifact(next);
+}
+
+function sendEsmArtifactJs(
+ res: ServerResponse,
+ filePath: string,
+ relativeUrlPath: string,
+): boolean {
+ try {
+ const raw = fs.readFileSync(filePath, 'utf8');
+ let body = restoreEsmDebugTestHooks(relativeUrlPath, raw);
+ body = rewriteAspnetArtifactToEsm(body, relativeUrlPath);
+ if (body === raw) {
+ return sendStaticFile(res, filePath, fs.statSync(filePath).size);
+ }
+ return sendJsModuleBody(res, body);
+ } catch {
+ return sendError(res, 500, 'Failed to serve ESM artifact');
+ }
+}
+
function sendDirectoryListing(
res: ServerResponse,
requestPath: string,
@@ -141,7 +514,6 @@ ${items.join('\n')}
export function createStaticFileService({
escapeHtml,
rootDirectory,
- setNoCacheHeaders,
setStaticCacheHeaders,
}: StaticFileServiceDeps): StaticFileService {
function tryServeStatic(
@@ -156,30 +528,88 @@ export function createStaticFileService({
const relativeToRoot = path.relative(rootDirectory, filePath);
if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
- setNoCacheHeaders(res);
- res.statusCode = 403;
- res.setHeader('Content-Type', 'text/plain; charset=utf-8');
- res.end('Forbidden');
- return true;
+ return sendError(res, 403, 'Forbidden');
}
- if (!fs.existsSync(filePath)) {
+ const resolvedFilePath = resolveStaticFilePath(filePath);
+ if (!resolvedFilePath) {
return false;
}
setStaticCacheHeaders(res, searchParams);
- const stat = fs.statSync(filePath);
+ const stat = fs.statSync(resolvedFilePath);
if (stat.isDirectory()) {
- return sendDirectoryListing(res, pathname, filePath, escapeHtml);
+ return sendDirectoryListing(res, pathname, resolvedFilePath, escapeHtml);
}
- if (stat.isFile()) {
- return sendStaticFile(res, filePath, stat.size);
+ if (!stat.isFile()) {
+ return false;
+ }
+
+ if (searchParams.has('esm-export') && path.extname(resolvedFilePath).toLowerCase() === '.json') {
+ return sendJsonAsEsmModule(res, resolvedFilePath);
+ }
+
+ // Native ESM resolves relative imports against the request URL, not the
+ // on-disk file. Redirect extensionless URLs to the canonical file URL.
+ const resolvedUrlPath = `/${normalizeUrlPath(path.relative(rootDirectory, resolvedFilePath))}`;
+ if (resolvedUrlPath !== normalizedPath) {
+ const query = searchParams.toString();
+ res.statusCode = 302;
+ res.setHeader('Location', query ? `${resolvedUrlPath}?${query}` : resolvedUrlPath);
+ res.end();
+ return true;
+ }
+
+ const relativeUrlPath = normalizeUrlPath(relativeToRoot);
+ const isJs = path.extname(resolvedFilePath).toLowerCase() === '.js';
+
+ if (isJs && isQunitTestOrHelperPath(relativeUrlPath)) {
+ return sendTransformedJs(
+ res,
+ resolvedFilePath,
+ (raw) => rewriteQunitTestHelperSource(raw, relativeUrlPath),
+ 'Failed to rewrite CJS-style test/helper module',
+ );
+ }
+
+ // Relative library imports bypass import maps — serve mutable facades
+ // at the artifact URL unless ?dx-original=1 (used by the facade itself).
+ // Hand-written shims (themes, …) win; otherwise auto-generate.
+ if (!searchParams.has('dx-original')) {
+ const shimUrl = findHandWrittenMutableFacade(relativeUrlPath);
+ if (shimUrl) {
+ return sendMutableFacadeModule(res, shimUrl);
+ }
+ const autoFacade = tryBuildAutoMutableFacade(
+ relativeUrlPath,
+ resolvedFilePath,
+ rootDirectory,
+ );
+ if (autoFacade) {
+ return sendJsModuleBody(res, autoFacade);
+ }
+ }
+
+ if (isJs) {
+ const vendorWrapper = resolveVendorEsmWrapper(relativeUrlPath);
+ if (vendorWrapper) {
+ return sendTransformedJs(
+ res,
+ resolvedFilePath,
+ (raw) => vendorWrapper(raw, relativeUrlPath),
+ 'Failed to wrap vendor bundle as ESM',
+ );
+ }
+
+ if (relativeUrlPath.includes(ESM_ARTIFACT_MARKER)) {
+ return sendEsmArtifactJs(res, resolvedFilePath, relativeUrlPath);
+ }
}
- return false;
+ return sendStaticFile(res, resolvedFilePath, stat.size);
}
return {
diff --git a/packages/devextreme/testing/runner/templates/run-suite.template.html b/packages/devextreme/testing/runner/templates/run-suite.template.html
index a7660dce96c2..d206af4ba123 100644
--- a/packages/devextreme/testing/runner/templates/run-suite.template.html
+++ b/packages/devextreme/testing/runner/templates/run-suite.template.html
@@ -99,9 +99,9 @@
-
+
-
+
+
+ {{{IMPORT_MAP_SCRIPT}}}
+
-
+
diff --git a/packages/devextreme/testing/systemjs-builder.js b/packages/devextreme/testing/systemjs-builder.js
deleted file mode 100644
index 940b87d249de..000000000000
--- a/packages/devextreme/testing/systemjs-builder.js
+++ /dev/null
@@ -1,272 +0,0 @@
-const path = require('path');
-const fs = require('fs');
-const babel = require('@babel/core');
-const parseArguments = require('minimist');
-
-
-const root = path.join(__dirname, '..');
-const transpilePath = path.join(root, '/artifacts/transpiled');
-
-const getFileList = (dirName) => {
- let files = [];
- const items = fs.readdirSync(dirName, { withFileTypes: true });
-
- // eslint-disable-next-line no-restricted-syntax
- for(const item of items) {
- if(item.isDirectory()) {
- files = [...files, ...getFileList(path.join(dirName, item.name))];
- } else if(
- item.name.endsWith('.js') ||
- (item.name.endsWith('.json') && !item.name.includes('tsconfig') && !item.name.includes('__meta'))
- ) {
- files.push(path.join(dirName, item.name));
- }
- }
-
- return files;
-};
-
-const writeFileSync = (destPath, file) => {
- const destDir = path.dirname(destPath);
- if(!fs.existsSync(destDir)) {
- fs.mkdirSync(destDir, { recursive: true });
- }
-
- fs.writeFileSync(destPath, file);
-};
-
-const buildAmdModule = (body) => `
-define(function(require, exports, module) {
- ${body}
-});
-`;
-
-const transpileCommonJSFile = (source, pathToFile) => {
- const [pre, post] = path.extname(pathToFile) === '.json'
- ? ['module.exports = ', ';']
- : ['', ''];
-
- writeFileSync(
- pathToFile,
- buildAmdModule(
- `${pre}${source}${post}`.replace(/(\n|\r)/g, '$1 ')
- )
- );
-};
-
-const buildJsonModule = (body) => `
-define(function(require, exports, module) {
- module.exports = ${body};
-});
-`;
-
-const buildSystemJSModule = (body, pre = '') => `
-SystemJS.register([], function(exports) {
- ${pre}
-
- return {
- setters: [],
- execute: function() {
- ${body}
- }
- };
-});
-`;
-
-const transpileFile = async(sourcePath, targetPath) => {
- const code = fs.readFileSync(sourcePath)
- .toString()
- .replaceAll('/packages/devextreme/testing/helpers/wrapRenovatedWidget.js', '/packages/devextreme/artifacts/transpiled-testing/helpers/wrapRenovatedWidget.js')
- .replaceAll(path.normalize('/testing/helpers/'), path.normalize('/artifacts/transpiled-testing/helpers/'))
- // TODO see packages/devextreme/testing/tests/DevExpress.viz.vectorMap.utils/tests.js
- // import { parse } from '../../../artifacts/js/vectormap-utils/dx.vectormaputils.js';
- // This used to work because the runner cwd was the same as the devextreme root folder
- // remove next 3 lines after fix
- .replaceAll(
- path.normalize('../../../artifacts/js/vectormap-utils/dx.vectormaputils.js'),
- path.normalize('../../../../artifacts/js/vectormap-utils/dx.vectormaputils.js'));
-
- if(sourcePath.includes('testing/helpers/includeThemesLinks.js')) {
- writeFileSync(targetPath, buildSystemJSModule('', code.replaceAll('\n', ' ')));
- return;
- }
-
- if(
- /(^|\s)System(JS)?\.register/gm.test(code) ||
- /(^|\s)define\(/gm.test(code) ||
- sourcePath.includes('helpers/forMap')
- ) {
- writeFileSync(targetPath, code);
- } else if(/(\(|\s|^)require\(/.test(code) || /(module\.)?exports(\.\w+)?\s?=/.test(code)) {
- transpileCommonJSFile(code, targetPath);
- } else if(sourcePath.endsWith('.json')) {
- writeFileSync(targetPath, buildJsonModule(code));
- } else {
- await transpileWithBabel(code, targetPath);
- }
-};
-
-const transpileModules = async() => {
- await Promise.all(
- getFileList(transpilePath).map((filePath) => {
- return transpileFile(
- filePath,
- filePath.replace(path.normalize('/transpiled'), path.normalize('/transpiled-systemjs')),
- );
- })
- );
-};
-
-const buildCssAsSystemModule = (name, filePath) => `
-System.register('${filePath}', [], false, function() {});
-(function() {
- if (typeof document == 'undefined') return;
- var link = document.createElement('link');
- link.rel = 'stylesheet';
- link.href = '/packages/devextreme/${filePath}';
- link.setAttribute('data-theme', '${name}');
- document.getElementsByTagName('head')[0].appendChild(link);
-})();
-`;
-
-const transpileCss = async() => {
- const cssList = [
- ['artifacts/css/dx.light.css', 'generic.light'],
- ['artifacts/css/dx.material.blue.light.css', 'material.blue.light'],
- ['artifacts/css/dx.fluent.blue.light.css', 'fluent.blue.light'],
- ['artifacts/css/dx-gantt.css', 'gantt'],
- ];
-
- // eslint-disable-next-line no-restricted-syntax
- for(const [cssFile, styleName] of cssList) {
- const destPath = path.join(root, cssFile.replace('css', 'css-systemjs'));
-
- writeFileSync(destPath, buildCssAsSystemModule(styleName, cssFile));
- }
-};
-
-const transpileWithBabel = async(sourceCode, destPath) => {
- const { code } = await babel.transform(sourceCode, {
- compact: false,
- plugins: ['@babel/plugin-transform-modules-systemjs'],
- sourceMaps: true,
- });
-
- writeFileSync(destPath, code);
-};
-
-const transpileIntl = async() => {
- const listIntlFiles = [
- {
- filePath: require.resolve('intl/lib/core.js'),
- destPath: path.join(root, 'artifacts/js-systemjs/intl/intl.js'),
- },
- {
- filePath: require.resolve('intl/locale-data/complete.js'),
- destPath: path.join(root, 'artifacts/js-systemjs/intl/intl.complete.js'),
- },
- ];
-
- await Promise.all(listIntlFiles.map(({ filePath, destPath }) => {
- const code = fs.readFileSync(filePath).toString();
-
- writeFileSync(
- destPath,
- buildAmdModule(
- code.replace('IntlPolyfill', 'require("./intl.js")')
- )
- );
- }));
-
- const intlIndex = `
- define(function(require, exports, module) {
- window.IntlPolyfill = require('./intl.js');
-
- require('./intl.complete.js');
-
- if (!window.Intl) {
- window.Intl = window.IntlPolyfill;
- window.IntlPolyfill.__applyLocaleSensitivePrototypes();
- }
-
- module.exports = window.IntlPolyfill;
- });
- `;
-
- writeFileSync(path.join(root, 'artifacts/js-systemjs/intl/index.js'), intlIndex);
-};
-
-const transpileJsVendors = async() => {
- const pluginsList = [
- {
- filePath: require.resolve('systemjs-plugin-css/css.js'),
- destPath: path.join(root, 'artifacts/js-systemjs/css.js'),
- },
- {
- filePath: require.resolve('systemjs-plugin-json/json.js'),
- destPath: path.join(root, 'artifacts/js-systemjs/json.js'),
- },
- ];
-
- await Promise.all(
- pluginsList.map(({ filePath, destPath }) => {
- const code = fs.readFileSync(filePath).toString();
-
- return writeFileSync(
- destPath,
- buildSystemJSModule(
- '',
- code.replaceAll('module.exports', 'exports')
- )
- );
- }),
- );
-
- await transpileIntl();
-
- await transpileFile(
- require.resolve('knockout/build/output/knockout-latest.debug.js'),
- path.join(root, 'artifacts/js-systemjs/knockout.js')
- );
- await transpileFile(
- path.join(root, 'node_modules/@preact/signals-core/dist/signals-core.js'),
- path.join(root, 'artifacts/js-systemjs/preact-signals.js')
- );
-
-
- [].concat(
- getFileList(path.join(root, 'node_modules/devextreme-cldr-data')),
- getFileList(path.join(root, 'node_modules/cldr-core/supplemental'))
- )
- .filter(filePath => filePath.endsWith('.json'))
- .forEach((filePath) => {
- transpileFile(filePath, filePath.replace(path.normalize('/node_modules'), path.normalize('/artifacts/js-systemjs')));
- });
-};
-
-const transpileTesting = async() => {
- const contentList = getFileList(path.join(root, 'testing/content'));
- const helpersList = getFileList(path.join(root, 'testing/helpers'));
- const testsList = getFileList(path.join(root, 'testing/tests'));
-
- [].concat(contentList, helpersList, testsList)
- .forEach((filePath) => {
- transpileFile(filePath, filePath.replace(path.normalize('/testing/'), path.normalize('/artifacts/transpiled-testing/')));
- });
-};
-
-(async() => {
-
- const { transpile } = parseArguments(process.argv);
-
- switch(transpile) {
- case 'modules':
- return await transpileModules();
- case 'testing':
- return await transpileTesting();
- case 'css':
- return await transpileCss();
- case 'js-vendors':
- return await transpileJsVendors();
- }
-})();
diff --git a/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js b/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js
index c61b8e313b3c..6d2fdbc87a94 100644
--- a/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js
@@ -1,39 +1,28 @@
-(function(factory) {
- if(typeof define === 'function' && define.amd) {
- define(function(require, exports, module) {
- require('integration/jquery'),
- require('ui/button');
- require('ui/check_box');
- require('ui/drop_down_button');
- require('ui/form');
- require('ui/popup');
- require('ui/select_box');
- require('ui/text_box');
- require('ui/toolbar');
- require('ui/validator');
- require('ui/validation_summary');
-
- const aspnet = require('aspnet');
- window.DevExpress = { aspnet: aspnet }; // for DevExpress.aspnet.createComponent in templates
-
- module.exports = factory(
- require('jquery'),
- require('core/templates/template_engine_registry').setTemplateEngine,
- aspnet,
- function() { return require('ui/widget/ui.errors'); },
- function() { return require('../../helpers/ajaxMock.js'); }
- );
- });
- } else {
- factory(
- window.jQuery,
- DevExpress.setTemplateEngine,
- DevExpress.aspnet,
- function() { return window.DevExpress_ui_widget_errors; },
- function() { return window.ajaxMock; }
- );
- }
-}(function($, setTemplateEngine, aspnet, errorsAccessor, ajaxMockAccessor) {
+import 'integration/jquery';
+import 'ui/button';
+import 'ui/check_box';
+import 'ui/drop_down_button';
+import 'ui/form';
+import 'ui/popup';
+import 'ui/select_box';
+import 'ui/text_box';
+import 'ui/toolbar';
+import 'ui/validator';
+import 'ui/validation_summary';
+
+import $ from 'jquery';
+import { setTemplateEngine } from 'core/templates/template_engine_registry';
+import aspnetModule from 'aspnet';
+import errorsModule from 'ui/widget/ui.errors';
+import ajaxMock from '../../helpers/ajaxMock.js';
+
+// Templates call DevExpress.aspnet.createComponent / renderComponent.
+// MVC-style templates also expect global `$` (runner calls jQuery.noConflict()).
+window.DevExpress = window.DevExpress || {};
+window.DevExpress.aspnet = aspnetModule;
+window.$ = $;
+
+(function($, setTemplateEngine, aspnet, errorsAccessor, ajaxMockAccessor) {
if(QUnit.urlParams['nojquery']) {
return;
@@ -696,4 +685,4 @@
});
});
-}));
+})($, setTemplateEngine, aspnetModule, function() { return errorsModule; }, function() { return ajaxMock; });
diff --git a/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js b/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js
index 6c7ff287bb62..e1d72136d9ca 100644
--- a/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js
@@ -1,16 +1,19 @@
import { registerPattern, registerGradient } from 'common/charts';
import graphicObjects from '__internal/common/m_charts';
-import utils from 'viz/core/utils_default';
+function clearGraphicObjects() {
+ const objects = graphicObjects.getGraphicObjects();
+ Object.keys(objects).forEach((key) => {
+ delete objects[key];
+ });
+}
QUnit.module('Graphic objects', {
beforeEach: function() {
- this.getNextDefsStub = sinon.stub(utils, 'getNextDefsSvgId');
- this.getNextDefsStub.onCall(0).returns('DevExpressId_1');
- this.getNextDefsStub.onCall(1).returns('DevExpressId_2');
+ clearGraphicObjects();
},
afterEach: function() {
- this.getNextDefsStub.restore();
+ clearGraphicObjects();
}
});
@@ -18,12 +21,11 @@ QUnit.test('should register pattern', function(assert) {
const id_1 = registerPattern({ key: 'test_key_1' });
const id_2 = registerPattern({ key: 'test_key_2' });
- assert.equal(this.getNextDefsStub.callCount, 2);
- assert.equal(id_1, 'DevExpressId_1');
- assert.equal(id_2, 'DevExpressId_2');
+ assert.ok(/^DevExpress_\d+$/.test(id_1), 'id has expected format');
+ assert.notEqual(id_1, id_2, 'ids are unique');
assert.deepEqual(graphicObjects.getGraphicObjects(), {
- 'DevExpressId_1': { key: 'test_key_1', type: 'pattern' },
- 'DevExpressId_2': { key: 'test_key_2', type: 'pattern' }
+ [id_1]: { key: 'test_key_1', type: 'pattern' },
+ [id_2]: { key: 'test_key_2', type: 'pattern' }
});
});
@@ -31,11 +33,10 @@ QUnit.test('should register gradient', function(assert) {
const id_1 = registerGradient('gradient_type', { key: 'test_key_1' });
const id_2 = registerGradient('gradient_type', { key: 'test_key_2' });
- assert.equal(this.getNextDefsStub.callCount, 2);
- assert.equal(id_1, 'DevExpressId_1');
- assert.equal(id_2, 'DevExpressId_2');
+ assert.ok(/^DevExpress_\d+$/.test(id_1), 'id has expected format');
+ assert.notEqual(id_1, id_2, 'ids are unique');
assert.deepEqual(graphicObjects.getGraphicObjects(), {
- 'DevExpressId_1': { key: 'test_key_1', type: 'gradient_type' },
- 'DevExpressId_2': { key: 'test_key_2', type: 'gradient_type' }
+ [id_1]: { key: 'test_key_1', type: 'gradient_type' },
+ [id_2]: { key: 'test_key_2', type: 'gradient_type' }
});
});
diff --git a/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js b/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js
index 7800023e6c96..e7cdd80db419 100644
--- a/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js
@@ -3,14 +3,14 @@ const useJQuery = !QUnit.urlParams['nojquery'];
window.DevExpress = window.DevExpress || {};
window.DevExpress.config = { useJQuery: useJQuery };
-define(function(require) {
- QUnit.test = QUnit.urlParams['nocsp'] ? QUnit.test : QUnit.skip;
- require('bundles/dx.all.js');
+// Must stay dynamic: static imports hoist above the config assignment.
+await import('bundles/dx.all.js');
- QUnit.module('config.useJQuery');
+QUnit.test = QUnit.urlParams['nocsp'] ? QUnit.test : QUnit.skip;
- QUnit.test('config value useJQuery with jQuery in window', function(assert) {
- const config = DevExpress.config;
- assert.equal(config().useJQuery, useJQuery);
- });
+QUnit.module('config.useJQuery');
+
+QUnit.test('config value useJQuery with jQuery in window', function(assert) {
+ const config = DevExpress.config;
+ assert.equal(config().useJQuery, useJQuery);
});
diff --git a/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js b/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js
index 0fc86f98a8e9..26ba47daecfb 100644
--- a/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js
@@ -1,18 +1,13 @@
-define(function(require) {
- if(QUnit.urlParams['nojquery']) {
- return;
- }
-
- const $ = require('jquery');
-
- require('bundles/dx.all.js');
-
+import $ from 'jquery';
+import 'integration/jquery';
+import dxButton from 'ui/button';
+if(!QUnit.urlParams['nojquery']) {
QUnit.module('jquery integration');
QUnit.test('renderer uses correct strategy', function(assert) {
const node = document.createElement('div');
- const element = new DevExpress.ui.dxButton(node).element();
+ const element = new dxButton(node).element();
assert.ok(element instanceof window.jQuery);
});
@@ -22,4 +17,4 @@ define(function(require) {
assert.equal(typeof $element.dxButton, 'function');
});
-});
+}
diff --git a/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js b/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js
index c072d222b479..06020db1cc88 100644
--- a/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js
@@ -1,9 +1,3 @@
-SystemJS.config({
- map: {
- 'jqueryify': SystemJS.map.jquery
- }
-});
-
define(function(require) {
const $ = require('jquery');
const Template = require('core/templates/template').Template;
diff --git a/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js b/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js
index d028a628baf6..b1131dcb7c4d 100644
--- a/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js
@@ -1,26 +1,3 @@
-SystemJS.config({
- meta: {
- './localization.base.tests.js': {
- deps: [
- 'common/core/localization/globalize/core',
- 'common/core/localization/globalize/number',
- 'common/core/localization/globalize/currency',
- 'common/core/localization/globalize/date',
- 'common/core/localization/globalize/message'
- ]
- }
- },
- packages: {
- 'globalize': {
- meta: {
- '../globalize.js': {
- deps: ['cldr/unresolved']
- }
- }
- }
- }
-});
-
define(function(require, exports, module) {
const cldrData = [
require('devextreme-cldr-data/ar.json!json'),
@@ -547,7 +524,7 @@ define(function(require, exports, module) {
});
QUnit.module('Exceljs format', () => {
- ExcelJSLocalizationFormatTests.default.runCurrencyTests([
+ ExcelJSLocalizationFormatTests.runCurrencyTests([
{ value: 'USD', expected: '$#,##0_);\\($#,##0\\)' },
{ value: 'RUB', expected: '\\R\\U\\B#,##0_);\\(\\R\\U\\B#,##0\\)' },
{ value: 'JPY', expected: '\\¥#,##0_);\\(\\¥#,##0\\)' },
@@ -556,7 +533,7 @@ define(function(require, exports, module) {
{ value: 'SEK', expected: '\\S\\E\\K#,##0_);\\(\\S\\E\\K#,##0\\)' }
]);
- ExcelJSLocalizationFormatTests.default.runPivotGridCurrencyTests([
+ ExcelJSLocalizationFormatTests.runPivotGridCurrencyTests([
{ value: 'USD', expected: '$#,##0_);\\($#,##0\\)' },
{ value: 'RUB', expected: '\\R\\U\\B#,##0_);\\(\\R\\U\\B#,##0\\)' },
{ value: 'JPY', expected: '\\¥#,##0_);\\(\\¥#,##0\\)' },
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js
index 62f7c4e1a833..ec8e84cf6f59 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js
@@ -105,19 +105,22 @@ QUnit.module('AdaptiveColumns', {
if(name === 'width' || name === 'height') {
++cssInvokeCounter;
}
+ return cssFunc.apply(this, arguments);
};
- // arrange, act
- $('.dx-datagrid').width(200);
- setupDataGrid(this);
- this.rowsView.render($('#container'));
- this.resizingController.updateDimensions();
- this.clock.tick(10);
-
- // assert
- assert.equal(cssInvokeCounter, 0, 'no $.css() invokes for width/height CSS properties');
-
- renderer.fn.css = cssFunc;
+ try {
+ // arrange, act
+ $('.dx-datagrid').width(200);
+ setupDataGrid(this);
+ this.rowsView.render($('#container'));
+ this.resizingController.updateDimensions();
+ this.clock.tick(10);
+
+ // assert
+ assert.equal(cssInvokeCounter, 0, 'no $.css() invokes for width/height CSS properties');
+ } finally {
+ renderer.fn.css = cssFunc;
+ }
});
// T516888
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js
index 8ba22c6b1e98..954d86128ce6 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js
@@ -1,22 +1,13 @@
+import quillImporter from 'ui/html_editor/quill_importer';
-SystemJS.config({
- map: {
- 'devextreme-quill': '/packages/devextreme/testing/helpers/quillDependencies/noQuill.js'
- }
-});
-
-define(function(require) {
- const getQuill = require('ui/html_editor/quill_importer').getQuill;
-
- QUnit.module('Import 3rd party', function() {
- QUnit.test('it throw an error if the quill script isn\'t referenced', function(assert) {
- assert.throws(
- function() { getQuill(); },
- function(e) {
- return /(E1041)[\s\S]*(Quill)/.test(e.message);
- },
- 'The Quill script isn\'t referenced'
- );
- });
+QUnit.module('Import 3rd party', function() {
+ QUnit.test('it throw an error if the quill script is not referenced', function(assert) {
+ assert.throws(
+ function() { quillImporter.getQuill(); },
+ function(e) {
+ return /(E1041)[\s\S]*(Quill)/.test(e.message);
+ },
+ 'The Quill script is not referenced'
+ );
});
});
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js
index 939672ded248..cf29a6de403a 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js
@@ -72,6 +72,8 @@ const moduleConfig = {
},
afterEach: function() {
+ this.clock.tick(1000);
+ resizeCallbacks.empty();
this.clock.restore();
}
};
@@ -176,7 +178,7 @@ module('Table resizing module', moduleConfig, () => {
resizeCallbacks.fire();
- assert.strictEqual(typeof resizingInstance._resizeHandlerWithContext, 'object', '_resizeHandler is an object');
+ assert.ok(resizingInstance._resizeHandlerWithContext, '_resizeHandlerWithContext is registered');
});
test('Window resize callback should be cleaned after the widget dispose', function(assert) {
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js
index 3afd5c78e87e..bcc686b7c3b1 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js
@@ -1883,6 +1883,7 @@ testModule('Toolbar items state update', {
test('state of the items in menu should be synchronized after toolbar repaint (t1117604)', function(assert) {
this.options.items = this.mapToMenuItems(TABLE_OPERATIONS);
+ resizeCallbacks.empty();
const toolbar = new Toolbar(this.quillMock, this.options);
this.quillMock.getFormat = () => ({ table: true });
toolbar.updateTableWidgets();
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js
index 9205430c2753..55a3f263fb84 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { DesktopTooltipStrategy } from '__internal/scheduler/tooltip_strategies/desktop_tooltip_strategy';
import { FunctionTemplate } from 'core/templates/function_template';
import { extend } from 'core/utils/extend';
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js
index ecc19597f49f..11113aa6afb5 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js
@@ -1,20 +1,12 @@
-SystemJS.config({
- map: {
- 'devexpress-diagram': '/packages/devextreme/testing/helpers/noDiagram.js'
- }
-});
-
-define(function(require) {
- const getDiagram = require('__internal/ui/diagram/diagram.importer').getDiagram;
+import { getDiagram } from '__internal/ui/diagram/diagram.importer';
- QUnit.module('Import devexpress-diagram', function() {
- QUnit.test('throw an error if the devexpress-diagram script isn\'t referenced', function(assert) {
- assert.throws(
- function() { getDiagram(); },
- function(e) {
- return /(E1041)[\s\S]*(devexpress-diagram)/.test(e.message);
- }
- );
- });
+QUnit.module('Import devexpress-diagram', function() {
+ QUnit.test('throw an error if the devexpress-diagram script isn\'t referenced', function(assert) {
+ assert.throws(
+ function() { getDiagram(); },
+ function(e) {
+ return /(E1041)[\s\S]*(devexpress-diagram)/.test(e.message);
+ }
+ );
});
});
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js
index 66e095bfe854..58162b185b4c 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js
@@ -1,21 +1,12 @@
+import { getGanttViewCore as getGantt } from '__internal/ui/gantt/gantt_importer';
-SystemJS.config({
- map: {
- 'devexpress-gantt': '/packages/devextreme/testing/helpers/noGantt.js'
- }
-});
-
-define(function(require) {
- const getGantt = require('__internal/ui/gantt/gantt_importer').getGanttViewCore;
-
- QUnit.module('Import devexpress-gantt', function() {
- QUnit.test('throw an error if the devexpress-gantt script isn\'t referenced', function(assert) {
- assert.throws(
- function() { getGantt(); },
- function(e) {
- return /(E1041)[\s\S]*(devexpress-gantt)/.test(e.message);
- }
- );
- });
+QUnit.module('Import devexpress-gantt', function() {
+ QUnit.test('throw an error if the devexpress-gantt script isn\'t referenced', function(assert) {
+ assert.throws(
+ function() { getGantt(); },
+ function(e) {
+ return /(E1041)[\s\S]*(devexpress-gantt)/.test(e.message);
+ }
+ );
});
});
diff --git a/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js b/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js
index 3e98fd22e59e..dff805a7fd8e 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js
@@ -770,7 +770,7 @@ QUnit.module('initialized method', (hooks) => {
test('initialized fires for ordinary link (init before link addition - should wait theme loading)', function(assert) {
const done = assert.async();
- const url = ROOT_URL + 'packages/devextreme/testing' + '/helpers/themeMarker.css'; // WA for systemjs builder
+ const url = ROOT_URL + 'packages/devextreme/testing' + '/helpers/themeMarker.css';
const $frame = createFrame();
themes.setDefaultTimeout(30000);
diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js
index 953ea43bdf2c..ee10eb4db407 100644
--- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js
@@ -12,7 +12,9 @@ import seriesFamilyModule from 'viz/core/series_family';
import { setupSeriesFamily } from '../../helpers/chartMocks.js';
import pointerMock from '../../helpers/pointerMock.js';
-const seriesFamilyNativeConstructor = { ...seriesFamilyModule }.SeriesFamily;
+const mutableRendererModule = rendererModule.default ?? rendererModule;
+const mutableSeriesFamilyModule = seriesFamilyModule.default ?? seriesFamilyModule;
+const seriesFamilyNativeConstructor = mutableSeriesFamilyModule.SeriesFamily;
setupSeriesFamily();
QUnit.testStart(function() {
const markup =
@@ -2325,7 +2327,7 @@ QUnit.test('check horizontal alignment === center', function(assert) {
QUnit.module('Auto hide point markers', $.extend({}, moduleSetup, {
beforeEach: function() {
moduleSetup.beforeEach.call(this);
- seriesFamilyModule.SeriesFamily = seriesFamilyNativeConstructor;
+ mutableSeriesFamilyModule.SeriesFamily = seriesFamilyNativeConstructor;
const dataSource = [];
for(let i = 0; i < 500000; i += 250) {
const y1 = Math.sin(i);
@@ -3300,14 +3302,14 @@ QUnit.module('Option changing in onDrawn after zooming', {
beforeEach: function() {
this.legendShiftSpy = sinon.spy(legendModule.Legend.prototype, 'move');
this.titleShiftSpy = sinon.spy(titleModule.Title.prototype, 'move');
- sinon.stub(rendererModule, 'Renderer').callsFake(function() {
+ sinon.stub(mutableRendererModule, 'Renderer').callsFake(function() {
return new Renderer();
});
},
afterEach: function() {
legendModule.Legend.prototype.move.restore();
titleModule.Title.prototype.move.restore();
- rendererModule.Renderer.restore();
+ mutableRendererModule.Renderer.restore();
}
});
@@ -4901,7 +4903,7 @@ QUnit.test('Reset axes animation before adjusting position of vertical axes (fix
QUnit.module('SeriesFamily', $.extend({}, moduleSetup, {
beforeEach: function() {
moduleSetup.beforeEach.call(this);
- seriesFamilyModule.SeriesFamily = seriesFamilyNativeConstructor;
+ mutableSeriesFamilyModule.SeriesFamily = seriesFamilyNativeConstructor;
}
}));
diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js
index 358b3781265e..510f626adffa 100644
--- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js
@@ -10,12 +10,14 @@ import {
createChartInstance,
LabelCtor,
} from './chartParts/commons.js';
-import { ERROR_MESSAGES as dxErrors } from 'viz/core/errors_warnings';
+import errorsWarnings from 'viz/core/errors_warnings';
import seriesModule from 'viz/series/base_series';
import dataValidatorModule from 'viz/components/data_validator';
import { MockSeries, categories, seriesMockData, MockTranslator } from '../../helpers/chartMocks.js';
import graphicObjects from '__internal/common/m_charts';
+const dxErrors = errorsWarnings.ERROR_MESSAGES;
+
$('').appendTo('#qunit-fixture');
(function seriesCreationTests() {
diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js
index 5a818a865b72..a1cc51c986d2 100644
--- a/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import { ERROR_MESSAGES as dxErrors } from 'viz/core/errors_warnings';
+import errorsWarnings from 'viz/core/errors_warnings';
import translator2DModule from 'viz/translators/translator2d';
import { Range } from 'viz/translators/range';
import tickGeneratorModule from 'viz/axes/tick_generator';
@@ -9,6 +9,8 @@ import {
stubClass,
} from '../../helpers/vizMocks.js';
+const dxErrors = errorsWarnings.ERROR_MESSAGES;
+
const StubTranslator = stubClass(translator2DModule.Translator2D, {
updateBusinessRange: function(range) {
this.getBusinessRange.returns(range);
diff --git a/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js
index 6f4bb5c49201..9b44a0a266e2 100644
--- a/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js
@@ -61,12 +61,21 @@ QUnit.begin(function() {
step(1);
that.animationStep && that.animationStep(1);
complete();
- that.animationComplete && that.animationComplete();
+ // Drop the hook before calling it — overlapping animate()
+ // chains (or a second completion after done()) must not
+ // re-enter assert.async() under native ESM scheduling.
+ const onGroupComplete = that.animationComplete;
+ that.animationComplete = null;
+ onGroupComplete && onGroupComplete();
test.renderer.animationCompleted && test.renderer.animationCompleted();
}
}
if(arguments[1] && typeof arguments[1].step === 'function') {
+ // Real renderer replaces the in-flight animation; without this,
+ // a second animate() leaves an orphan setTimeout chain and
+ // animationComplete / assert.async() fire twice.
+ this.stopAnimation();
that = this;
step = arguments[1].step;
complete = arguments[1].complete || noop;
@@ -77,6 +86,7 @@ QUnit.begin(function() {
};
group.stopAnimation = function() {
clearTimeout(this.__animation);
+ this.__animation = null;
return this;
};
return group;
diff --git a/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js b/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js
index 4799c4cef71f..56edcb6d686e 100644
--- a/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js
+++ b/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js
@@ -1,4 +1,4 @@
-window.DevExpress = { viz: { map: { sources: {} } } };
+window.DevExpress = window.DevExpress || { viz: { map: { sources: {} } } };
import $ from 'core/renderer';
import { getWidth, getHeight, setWidth, setHeight } from 'core/utils/size';
@@ -18,6 +18,8 @@ import 'viz/tree_map';
import '/packages/devextreme/artifacts/js/vectormap-data/world.js';
import '/packages/devextreme/artifacts/js/vectormap-data/usa.js';
+const DevExpress = window.DevExpress;
+
const chartTestsSignature = {
getInitOptions() {
return {
diff --git a/packages/nx-infra-plugin/AGENTS.md b/packages/nx-infra-plugin/AGENTS.md
index a54a48137d65..c0f41b0ac880 100644
--- a/packages/nx-infra-plugin/AGENTS.md
+++ b/packages/nx-infra-plugin/AGENTS.md
@@ -65,3 +65,24 @@ Each behavior is owned by exactly ONE executor's canonical tests; consumers must
3. Preserve exact functional parity. Verify with the executor's e2e spec before and after.
4. Update consumer imports in one batch.
5. Run the full validation pipeline.
+
+## Former gulp tasks (gulp fully removed)
+
+`gulpfile.js`, `build/gulp/`, and all gulp dependencies have been deleted outright — devextreme no longer depends on gulp or ships a gulp CLI. The Nx-consumed build assets that used to live under `build/gulp/` (`transpile-config.js`, `modules_metadata.json`, the `*.jst` templates) were relocated to purpose-named folders directly under `build/` (`build/transpile-config.js`, `build/modules_metadata.json`, `build/localization-templates/`, `build/vectormap-templates/`). The table below is a historical reference mapping each removed gulp task to its Nx replacement:
+
+| Former gulp task | Nx target | Notes |
+| --------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `clean` | `clean:artifacts` | Uses `devextreme-nx-infra-plugin:clean` with `excludePatterns` to preserve `artifacts/css`, `artifacts/npm/devextreme/package.json`, and `artifacts/npm/devextreme-dist`. Run directly via `pnpm nx clean:artifacts devextreme`. |
+| `bundler-config-watch` | `build:devextreme-bundler-config:watch` | Uses `devextreme-nx-infra-plugin:concatenate-files` in `watch` mode (chokidar over the `build/bundle-templates/modules/parts` sources) with `additionalPasses` so a change rebuilds both `dx.custom.js` and the derived `dx.custom.config.js`, matching the old gulp `bundler-config` chain. `cache: false`. |
+| `bundler-config` (non-watch) | `build:devextreme-bundler-config` | Run via `pnpm nx build:devextreme-bundler-config devextreme` (add `-c prod` for parity with the old uglified variant). |
+| `generate-community-locales` | `build:community-localization` | Uses `devextreme-nx-infra-plugin:generate-community-locales` to normalize `js/localization/messages/*.json` against `en.json` in place (fills translations, English fallback for missing/`TODO` values, escapes quotes, inherits en's key order/formatting). Target is `cache: false` with no `outputs` (input dir == output dir — a source-normalization task, not a cached build artifact). Run via `pnpm nx build:community-localization devextreme`. |
+| `test-env` | `test-env` | Uses `nx:run-commands` to wrap the existing `node ./testing/launch` script (compiles the QUnit runner, starts the test server on port 20060, opens the browser). `cache: false`, no outputs (long-running server). `cwd` is `{projectRoot}`. |
+| `transpile-watch` | `build:transpile:watch` | `nx:run-commands` (parallel, `cache: false`) fanning out to the incremental watch targets `build:ts:internal:watch` (TypeScript watch program emitting `dist_ts`), `build:cjs:watch` (+ `-c production`), and `build:cjs:internal:watch` (+ `-c production`). Together these keep `artifacts/transpiled` and `artifacts/transpiled-renovation-npm` fresh, matching the old gulp JS + TS watch pipes. Watch capability lives in the `babel-transform` (per-file, chokidar + debounce, `watch` option) and `build-typescript` (`ts.createWatchProgram`, `watch` option) executors via the shared `src/utils/watch.ts` helper. `babel-transform` watch is intentionally file-level incremental and does **not** do an initial full transform — it relies on a preceding build having already populated its source directory (mirroring gulp-watch, which fed babel from a single in-memory TS-compiler stream with no such gap). Because `build:transpile`'s last step (`clean:dist-ts`) deletes `artifacts/dist_ts` — the exact directory `build:cjs:internal:watch` reads from and that runs right before `dev-watch` starts — `build:transpile:watch` itself (not the leaf `build:cjs:internal:watch` target, to avoid two parallel invocations each re-running the compile) declares `dependsOn: ["build:ts:internal"]`, so a real (or Nx-cache-restored) TS compile always repopulates `dist_ts/__internal` once, up front, before any of the fanned-out watch processes start; without it, whichever of the parallel TS-watch/babel-watch processes started first would decide — nondeterministically — whether the initial compile burst is picked up. |
+| `transpile-tests` | `transpile:tests` | Uses `devextreme-nx-infra-plugin:babel-transform` with the flat (keyless) `./testing/tests.babelrc.json` config to transpile `testing/**/*.js` in place. `dependsOn: ["build:devextreme-bundler-config"]` reproduces the old gulp `series('bundler-config', …)` prerequisite. `cache: false` (in-place source transform, not a cached artifact). Run via `pnpm nx transpile:tests devextreme`. |
+| `transpile-systemjs` | _(removed)_ | QUnit uses native ESM + import maps only. `build:systemjs` and `testing/systemjs-builder.js` are gone. ESM for QUnit is `artifacts/transpiled-esm-npm` from `build:transpile -c ci` (via `build:dev` / CI) or the dedicated `build:qunit-esm` target. |
+| `js-bundles-watch` | `bundle:watch` | Webpack watch via `devextreme-nx-infra-plugin:bundle` with `watch: true`. Does not run `compress:bundles`. |
+| `js-bundles-prod` | `bundle:prod` | Use `pnpm nx bundle:prod devextreme` (add `-c production` for uglify/dist parity). |
+| `js-bundles-debug` | `bundle:debug` | Use `pnpm nx bundle:debug devextreme` (add `-c production` for uglify/dist parity). |
+| `dev-watch` | `dev-watch` | `nx:run-commands` (parallel, `cache: false`, `cwd: {projectRoot}`) fanning out to the four already-migrated watch targets — `build:transpile:watch`, `build:devextreme-bundler-config:watch`, `bundle:watch`, `test-env` — matching the old `gulp.parallel('transpile-watch', 'bundler-config-watch', 'js-bundles-watch', 'test-env')`. |
+| `dev` | `dev` | `nx:run-commands` with `dependsOn: ["build:dev"]`; its own command then runs `pnpm nx dev-watch devextreme`, reproducing the old `gulp.series('default-dev', 'dev-watch')` build-then-watch sequencing. Reuses `build:dev` as the initial-build step rather than replicating gulp's lighter `default-dev` variant (which skipped `clean` and the one-shot `js-bundles-debug` build) — an accepted small startup-cost tradeoff. |
+| `default` / `main-batch` / `misc-batch` | `build`, `build-dist`, `build:dev` | Gulp orchestration (`gulp.series` / `gulp-multi-process`) is gone. Native Nx `build` covers the non-uglify default batch; `build -c production` (+ `build:npm -c production`) matches the old `gulp default --uglify`; `build -c production-internal` matches uglify + `BUILD_INTERNAL_PACKAGE`; `build -c testing` matches `BUILD_TEST_INTERNAL_PACKAGE`. `build-dist` is a thin wrapper (`pnpm nx run devextreme:build -c production`, `-c internal` → `production-internal`). `build:dev` is the former `DEVEXTREME_TEST_CI` path: clean → localization → `build:transpile -c ci` → parallel `bundle:debug,build:vectormap,copy:vendor` (skips prod bundles, aspnet, declarations, npm, license checks). npm scripts `build:dev` / `build-dist` / `clean` / `transpile-tests` call Nx directly. |
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9582741b143d..ea330bec5fca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -362,7 +362,7 @@ importers:
dependencies:
'@angular-devkit/build-angular':
specifier: ^22.0.9
- version: 22.1.2(258b0df82f598bf0cbd063448ecf32b5)
+ version: 22.1.2(9ab32ed578a68caf92174a202d695664)
'@angular/animations':
specifier: ^22.0.8
version: 22.1.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.2))
@@ -1584,21 +1584,6 @@ importers:
sinon:
specifier: 18.0.1
version: 18.0.1
- systemjs:
- specifier: 0.19.41
- version: 0.19.41
- systemjs-plugin-babel:
- specifier: 0.0.25
- version: 0.0.25
- systemjs-plugin-css:
- specifier: 0.1.37
- version: 0.1.37
- systemjs-plugin-json:
- specifier: 0.3.0
- version: 0.3.0
- systemjs-plugin-text:
- specifier: 0.0.11
- version: 0.0.11
terser-webpack-plugin:
specifier: 5.3.17
version: 5.3.17(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(webpack@5.105.4(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1))
@@ -16243,18 +16228,9 @@ packages:
systemjs-builder@0.16.15:
resolution: {integrity: sha512-C18G//KWWwQpstAVBUDt0YbbqvSFVVtr0MFqtf2zB4U/cePOA00Btcja++mzlFLMnepVpDv0GdtfE/6A8lrxeA==}
- systemjs-plugin-babel@0.0.25:
- resolution: {integrity: sha512-RMKSizWWlw4+IpDB385ugxn7Owd9W+HEtjYDQ6yO1FpsnER/vk6FbXRweUF+mvRi6EHgk8vDdUdtui7ReDwX3w==}
-
- systemjs-plugin-css@0.1.37:
- resolution: {integrity: sha512-wCGG62zYXuOlNji5FlBjeMFAnLeAO/HQmFg+8UBX/mlHoAKLHlGFYRstlhGKibRU2oxk/BH9DaihOuhhNLi7Kg==}
-
systemjs-plugin-json@0.3.0:
resolution: {integrity: sha512-GPHZgc6bGIDIQsoNAkhthddApy4ErFhy30rMBrEepkoDidhs0JeSk821htUOSrtqJjnUPBf2gge325B5GfsW0w==}
- systemjs-plugin-text@0.0.11:
- resolution: {integrity: sha512-buWE27P6iM3WZYXcsiy6+fiulQ/x+Puux4ni5ejTlcUgqUg3/sUvoAUZ4GGPACC0acjxmnaCt3kHb0+uNs1ekw==}
-
systemjs@0.19.41:
resolution: {integrity: sha512-8E9CmZ01dIr52po2LNhc3QuKyeSTyvQfshHMi3lekSbEOdR9OAUOX2X+wPKunZX3CpudM6w3r8eTCjGQrK79Wg==}
@@ -17944,13 +17920,13 @@ snapshots:
- webpack-cli
- yaml
- '@angular-devkit/build-angular@22.1.2(258b0df82f598bf0cbd063448ecf32b5)':
+ '@angular-devkit/build-angular@22.1.2(9ab32ed578a68caf92174a202d695664)':
dependencies:
'@ampproject/remapping': 2.3.0
'@angular-devkit/architect': 0.2201.2(chokidar@5.0.0)
'@angular-devkit/build-webpack': 0.2201.2(chokidar@5.0.0)(webpack-dev-server@5.2.6(tslib@2.8.1)(webpack@5.109.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(postcss@8.5.23)))(webpack@5.109.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(postcss@8.5.23))
'@angular-devkit/core': 22.1.2(chokidar@5.0.0)
- '@angular/build': 22.1.2(f076a2752836734f19dd852b068a7651)
+ '@angular/build': 22.1.2(9f8834ba6c8fdff652d4ac7537fafa50)
'@angular/compiler-cli': 22.1.0(@angular/compiler@22.1.0)(typescript@6.0.3)
'@babel/core': 8.0.1
'@babel/generator': 8.0.0
@@ -17970,7 +17946,7 @@ snapshots:
copy-webpack-plugin: 14.0.0(webpack@5.109.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(postcss@8.5.23))
css-loader: 7.1.4(webpack@5.109.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(postcss@8.5.23))
esbuild-wasm: 0.28.1
- http-proxy-middleware: 4.2.0
+ http-proxy-middleware: 4.2.0(supports-color@7.2.0)
istanbul-lib-instrument: 6.0.3
jsonc-parser: 3.3.1
karma-source-map-support: 1.4.0
@@ -18287,7 +18263,7 @@ snapshots:
- tsx
- yaml
- '@angular/build@22.1.2(f076a2752836734f19dd852b068a7651)':
+ '@angular/build@22.1.2(9f8834ba6c8fdff652d4ac7537fafa50)':
dependencies:
'@ampproject/remapping': 2.3.0
'@angular-devkit/architect': 0.2201.2(chokidar@5.0.0)
@@ -18301,7 +18277,7 @@ snapshots:
beasties: 0.4.3
browserslist: 4.28.7
esbuild: 0.28.1
- https-proxy-agent: 9.1.0
+ https-proxy-agent: 9.1.0(supports-color@7.2.0)
jsonc-parser: 3.3.1
listr2: 10.2.2
magic-string: 1.0.0
@@ -31174,7 +31150,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- http-proxy-middleware@4.2.0:
+ http-proxy-middleware@4.2.0(supports-color@7.2.0):
dependencies:
debug: 4.4.3(supports-color@7.2.0)
httpxy: 0.5.5
@@ -31229,7 +31205,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- https-proxy-agent@9.1.0:
+ https-proxy-agent@9.1.0(supports-color@7.2.0):
dependencies:
agent-base: 9.0.0
debug: 4.4.3(supports-color@7.2.0)
@@ -34730,7 +34706,7 @@ snapshots:
postcss: 8.5.23
rollup-plugin-dts: 6.4.1(rollup@4.59.0)(typescript@5.8.3)
rxjs: 7.8.2
- sass: 1.99.0
+ sass: 1.101.0
tinyglobby: 0.2.17
tslib: 2.8.1
typescript: 5.8.3
@@ -37795,14 +37771,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
- systemjs-plugin-babel@0.0.25: {}
-
- systemjs-plugin-css@0.1.37: {}
-
systemjs-plugin-json@0.3.0: {}
- systemjs-plugin-text@0.0.11: {}
-
systemjs@0.19.41:
dependencies:
when: 3.7.8