diff --git a/packages/devextreme/build/gulp/vectormap.js b/packages/devextreme/build/gulp/vectormap.js index 85740d171cb5..fed2727464fa 100644 --- a/packages/devextreme/build/gulp/vectormap.js +++ b/packages/devextreme/build/gulp/vectormap.js @@ -17,6 +17,7 @@ const compressionPipes = require('./compression-pipes.js'); const VECTORMAP_UTILS_PATH = 'js/viz/vector_map.utils'; const VECTORMAP_UTILS_RESULT_PATH = path.join(context.RESULT_JS_PATH, 'vectormap-utils'); const VECTORMAP_DATA_RESULT_PATH = path.join(context.RESULT_JS_PATH, 'vectormap-data'); +const VECTORMAP_SOURCES_PATH = 'build/vectormap-sources'; function transformFileName(fileName) { return path.join(VECTORMAP_UTILS_PATH, fileName) + '.js'; @@ -24,38 +25,63 @@ function transformFileName(fileName) { gulp.task('vectormap-utils', function() { return merge( - createVectorMapUtilsStream('browser', '.debug', false), - createVectorMapUtilsStream('browser', '', true), - createVectorMapUtilsStream('node', '', false) + createBrowserVectorMapUtilsStream('.debug', false), + createBrowserVectorMapUtilsStream('', true), ); }); -gulp.task('vectormap-data', gulp.series('vectormap-utils', function() { - const stream = merge(); - const processFiles = require(path.join('../..', VECTORMAP_UTILS_RESULT_PATH, 'dx.vectormaputils.node.js')).processFiles; +function toArrayBuffer(buffer) { + return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); +} + +function collectShpFiles(dir) { + return fs.readdirSync(dir) + .filter(name => path.extname(name).toLowerCase() === '.shp') + .map(name => path.basename(name, '.shp')); +} + +gulp.task('vectormap-data', gulp.series('vectormap-utils', function generateData() { + const parse = require(path.join('../..', VECTORMAP_UTILS_RESULT_PATH, 'dx.vectormaputils.debug.js')).parse; + const settings = require(path.join('../..', VECTORMAP_SOURCES_PATH, '_settings.js')); + const precision = settings.precision >= 0 ? Math.round(settings.precision) : 4; if(!fs.existsSync(VECTORMAP_DATA_RESULT_PATH)) { fs.mkdirSync(VECTORMAP_DATA_RESULT_PATH); } - processFiles('build/vectormap-sources', { - output: VECTORMAP_DATA_RESULT_PATH, - settings: 'build/vectormap-sources/_settings.js' - }, function() { - const files = fs.readdirSync(VECTORMAP_DATA_RESULT_PATH); - - files.forEach(file => { - const data = fs.readFileSync(path.join(VECTORMAP_DATA_RESULT_PATH, file), 'utf8'); - - stream.add( - gulp.src('build/gulp/vectormapdata-template.jst') - .pipe(template({ data: data })) - .pipe(rename(file)) - .pipe(headerPipes.useStrict()) - .pipe(gulp.dest(VECTORMAP_DATA_RESULT_PATH)) - ); - }); + const sourceDir = path.resolve(VECTORMAP_SOURCES_PATH); + const files = collectShpFiles(sourceDir); + + files.forEach(name => { + const shpBuffer = fs.readFileSync(path.join(sourceDir, name + '.shp')); + const dbfBuffer = fs.readFileSync(path.join(sourceDir, name + '.dbf')); + + const shapeData = parse( + { shp: toArrayBuffer(shpBuffer), dbf: toArrayBuffer(dbfBuffer) }, + { precision }, + ); + + if(shapeData) { + const content = name + ' = ' + JSON.stringify(shapeData) + ';'; + fs.writeFileSync(path.join(VECTORMAP_DATA_RESULT_PATH, name + '.js'), content); + } }); + + const stream = merge(); + const dataFiles = fs.readdirSync(VECTORMAP_DATA_RESULT_PATH); + + dataFiles.forEach(file => { + const data = fs.readFileSync(path.join(VECTORMAP_DATA_RESULT_PATH, file), 'utf8'); + + stream.add( + gulp.src('build/gulp/vectormapdata-template.jst') + .pipe(template({ data: data })) + .pipe(rename(file)) + .pipe(headerPipes.useStrict()) + .pipe(gulp.dest(VECTORMAP_DATA_RESULT_PATH)) + ); + }); + return stream; })); @@ -67,20 +93,17 @@ function patchVectorMapUtilsStream(stream, isMinify) { .pipe(gulp.dest(VECTORMAP_UTILS_RESULT_PATH)); } -function createVectorMapUtilsStream(name, suffix, isMinify) { +function createBrowserVectorMapUtilsStream(suffix, isMinify) { const settings = require(path.join('../..', VECTORMAP_UTILS_PATH, '_settings.json')); - const part = settings[name]; + const part = settings['browser']; const stream = gulp.src(settings.commonFiles.concat(part.files).map(transformFileName)) .pipe(concat(part.fileName + suffix + '.js')); - if(name === 'browser') { - return stream.pipe(tap(file => { - patchVectorMapUtilsStream(gulp.src('build/gulp/vectormaputils-template.jst') - .pipe(template({ data: file.contents })) - .pipe(rename(path.basename(file.path))), isMinify); - })); - } - return patchVectorMapUtilsStream(stream, isMinify); + return stream.pipe(tap(file => { + patchVectorMapUtilsStream(gulp.src('build/gulp/vectormaputils-template.jst') + .pipe(template({ data: file.contents })) + .pipe(rename(path.basename(file.path))), isMinify); + })); } gulp.task('vectormap', gulp.series('vectormap-utils', 'vectormap-data')); diff --git a/packages/devextreme/build/gulp/vectormaputils-template.jst b/packages/devextreme/build/gulp/vectormaputils-template.jst index efe184e20eaa..d2592e1c9f19 100644 --- a/packages/devextreme/build/gulp/vectormaputils-template.jst +++ b/packages/devextreme/build/gulp/vectormaputils-template.jst @@ -6,10 +6,10 @@ } else if(typeof module === "object" && module.exports) { factory(exports); } else { - var exports = root.DevExpress = root.DevExpress || {}; - exports = exports.viz = exports.viz || {}; - exports = exports.vectormaputils = {}; - factory(exports); + var browserExports = root.DevExpress = root.DevExpress || {}; + browserExports = browserExports.viz = browserExports.viz || {}; + browserExports = browserExports.vectormaputils = {}; + factory(browserExports); } }(this, function(exports) { <%= data %> diff --git a/packages/devextreme/js/viz/vector_map.utils/_settings.json b/packages/devextreme/js/viz/vector_map.utils/_settings.json index b63c70ad9e7e..2de73c6fca8d 100644 --- a/packages/devextreme/js/viz/vector_map.utils/_settings.json +++ b/packages/devextreme/js/viz/vector_map.utils/_settings.json @@ -9,12 +9,5 @@ "files": [ "js-stream" ] - }, - "node": { - "fileName": "dx.vectormaputils.node", - "files": [ - "node-stream", - "node-cmd" - ] } } diff --git a/packages/devextreme/js/viz/vector_map.utils/node-cmd.js b/packages/devextreme/js/viz/vector_map.utils/node-cmd.js deleted file mode 100644 index 38e2835c09fd..000000000000 --- a/packages/devextreme/js/viz/vector_map.utils/node-cmd.js +++ /dev/null @@ -1,181 +0,0 @@ -/* eslint-disable no-console, no-undef, no-var, one-var, import/no-commonjs*/ - -var path = require('path'); - -function normalizeJsName(value) { - return value.trim().replace('-', '_').replace(' ', '_'); -} - -function processFile(file, options, callback) { - var name = path.basename(file, path.extname(file)); - options.info('%s: started', name); - parse(file, { precision: options.precision }, function(shapeData, errors) { - var content; - options.info('%s: finished', name); - errors && errors.forEach(function(e) { - options.error(' ' + e); - }); - if(shapeData) { - content = JSON.stringify(options.processData(shapeData), null, options.isDebug && 4); - if(!options.isJSON) { - content = options.processFileContent(content, normalizeJsName(name)); - } - fs.writeFile( - path.resolve(options.output || path.dirname(file), options.processFileName(name + (options.isJSON ? '.json' : '.js'))), - content, function(e) { - e && options.error(' ' + e.message); - callback(); - }); - } else { - callback(); - } - }); -} - -function collectFiles(dir, done) { - var input = path.resolve(dir || ''); - fs.stat(input, function(e, stat) { - if(e) { - done(e, []); - } else if(stat.isFile()) { - done(null, checkFile(input) ? [path.resolve(path.dirname(input), normalizeFile(input))] : []); - } else if(stat.isDirectory()) { - fs.readdir(input, function(e, dirItems) { - var list = []; - dirItems.forEach(function(dirItem) { - if(checkFile(dirItem)) { - list.push(path.resolve(input, normalizeFile(dirItem))); - } - }); - done(null, list); - }); - } else { - done(null, []); - } - }); - - function checkFile(name) { - return path.extname(name).toLowerCase() === '.shp'; - } - - function normalizeFile(name) { - return path.basename(name, '.shp'); - } -} - -function importFile(file) { - var content; - try { - content = require(path.resolve(String(file))); - } catch(_) { } - return content; -} - -function pickFunctionOption(value) { - return (isFunction(value) && value) || (value && importFile(String(value))) || null; -} - -function processFileContentByDefault(content, name) { - return name + ' = ' + content + ';'; -} - -function prepareSettings(source, options) { - options = Object.assign({}, options); - if(options.settings) { - options = Object.assign(importFile(options.settings) || {}, options); - } - return Object.assign(options, { - input: source ? String(source) : null, - output: options.output ? String(options.output) : null, - precision: options.precision >= 0 ? Math.round(options.precision) : 4, - processData: pickFunctionOption(options.processData) || eigen, - processFileName: pickFunctionOption(options.processFileName) || eigen, - processFileContent: pickFunctionOption(options.processFileContent) || processFileContentByDefault, - info: options.isQuiet ? noop : console.info.bind(console), - error: options.isQuiet ? noop : console.error.bind(console) - }); -} - -function processFiles(source, options, callback) { - var settings = prepareSettings(source, options && options.trim ? importFile(options) : options); - settings.info('Started'); - collectFiles(settings.input, function(e, files) { - e && settings.error(e.message); - settings.info(files.map(function(file) { - return ' ' + path.basename(file); - }).join('\n')); - when(files.map(function(file) { - return function(done) { - processFile(file, settings, done); - }; - }), function() { - settings.info('Finished'); - (isFunction(callback) ? callback : noop)(); - }); - }); -} - -exports.processFiles = processFiles; - -var COMMAND_LINE_ARG_KEYS = [ - { key: '--output', name: 'output', arg: true, desc: 'Destination directory' }, - { key: '--process-data', name: 'processData', arg: true, desc: 'Process parsed data' }, - { key: '--process-file-name', name: 'processFileName', arg: true, desc: 'Process output file name' }, - { key: '--process-file-content', name: 'processFileContent', arg: true, desc: 'Process output file content' }, - { key: '--precision', name: 'precision', arg: true, desc: 'Precision of shape coordinates' }, - { key: '--json', name: 'isJSON', desc: 'Generate as a .json file' }, - { key: '--debug', name: 'isDebug', desc: 'Generate non minified file' }, - { key: '--quiet', name: 'isQuiet', desc: 'Suppress console output' }, - { key: '--settings', name: 'settings', arg: true, desc: 'Path to settings file' }, - { key: '--help', name: 'isHelp', desc: 'Print help' } -]; - -function parseCommandLineArgs() { - var args = process.argv.slice(2); - var options = { isEmpty: !args.length }; - var map = {}; - args.forEach(function(arg, i) { - map[arg] = args[i + 1] || true; - }); - COMMAND_LINE_ARG_KEYS.forEach(function(info) { - var val = map[info.key]; - if(val) { - options[info.name] = info.arg ? val : true; - } - }); - if(options.isHelp || options.isEmpty) { - options = null; - printCommandLineHelp(); - } - return options; -} - -function printCommandLineHelp() { - var parts = ['node ', path.basename(process.argv[1]), ' Source ']; - var lines = []; - var maxLength = Math.max.apply(null, COMMAND_LINE_ARG_KEYS.map(function(info) { - return info.key.length; - })) + 2; - var message; - COMMAND_LINE_ARG_KEYS.forEach(function(info) { - var key = info.key; - parts.push(key, ' '); - if(info.arg) { - parts.push('<', key.slice(2), '>', ' '); - } - lines.push([' ', key, Array(maxLength - key.length).join(' '), info.desc].join('')); - }); - message = ['Generates dxVectorMap-compatible files from shapefiles.', '\n', parts.join('')].concat(lines).join('\n'); - console.log(message); -} - -function runFromConsole() { - var args = parseCommandLineArgs(); - if(args) { - processFiles(process.argv[2] || '', args); - } -} - -if(require.main === module) { - runFromConsole(); -} diff --git a/packages/devextreme/js/viz/vector_map.utils/node-stream.js b/packages/devextreme/js/viz/vector_map.utils/node-stream.js deleted file mode 100644 index 407112e16554..000000000000 --- a/packages/devextreme/js/viz/vector_map.utils/node-stream.js +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-disable no-unused-vars, no-var, one-var, import/no-commonjs*/ -function wrapBuffer(buffer) { - return buffer; -} - -function ui8(stream, position) { - return stream[position]; -} - -function ui16LE(stream, position) { - return stream.readUInt16LE(position); -} - -function ui32LE(stream, position) { - return stream.readUInt32LE(position); -} - -function ui32BE(stream, position) { - return stream.readUInt32BE(position); -} - -function f64LE(stream, position) { - return stream.readDoubleLE(position); -} - -var fs = require('fs'); -function sendRequest(path, callback) { - fs.readFile(path, callback); -} diff --git a/packages/devextreme/ports.json b/packages/devextreme/ports.json index 0a552a5bb3fc..524eb9999964 100644 --- a/packages/devextreme/ports.json +++ b/packages/devextreme/ports.json @@ -1,4 +1,3 @@ { - "qunit": "20060", - "vectormap-utils-tester": "20061" + "qunit": "20060" } diff --git a/packages/devextreme/project.json b/packages/devextreme/project.json index 650eb214b8f2..3d7c81a62fcf 100644 --- a/packages/devextreme/project.json +++ b/packages/devextreme/project.json @@ -685,6 +685,7 @@ "{projectRoot}/build/gulp/vectormap.js" ], "outputs": [ + "{projectRoot}/artifacts/js/vectormap-utils", "{projectRoot}/artifacts/js/vectormap-data" ] }, @@ -732,7 +733,6 @@ "{projectRoot}/build/gulp/vendor.js" ], "outputs": [ - "{projectRoot}/artifacts/js/vectormap-utils", "{projectRoot}/artifacts/js/cldr" ] }, diff --git a/packages/devextreme/testing/content/VectorMapData/_processFileContent.js b/packages/devextreme/testing/content/VectorMapData/_processFileContent.js deleted file mode 100644 index 56f10d9e59e2..000000000000 --- a/packages/devextreme/testing/content/VectorMapData/_processFileContent.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function(content, name) { - return 'test.namespace.' + name + ' = ' + content + ';'; -}; diff --git a/packages/devextreme/testing/content/VectorMapData/_settings.js b/packages/devextreme/testing/content/VectorMapData/_settings.js deleted file mode 100644 index 687814412275..000000000000 --- a/packages/devextreme/testing/content/VectorMapData/_settings.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - processFileName: function(name) { - return 'test_' + name; - } -}; diff --git a/packages/devextreme/testing/helpers/vectormaputils-tester.js b/packages/devextreme/testing/helpers/vectormaputils-tester.js deleted file mode 100644 index da90e9c094fe..000000000000 --- a/packages/devextreme/testing/helpers/vectormaputils-tester.js +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-env node */ -/* eslint-disable no-console */ - -process.on('uncaughtException', function(e) { - console.log('!!! Uncaught exception !!!'); - console.log(e.message); - console.log(e.stack); -}); - -const fs = require('fs'); -const path = require('path'); -const utils = require('../../artifacts/js/vectormap-utils/dx.vectormaputils.node.js'); -const PORTS = require('../../ports.json'); - -const dataDirectory = path.join(path.dirname(process.argv[1]), '../content/VectorMapData'); - -const actions = { - 'parse-buffer': function(arg, callback) { - let count = 2; - const obj = {}; - fs.readFile(path.join(dataDirectory, arg + '.shp'), function(e, buffer) { - obj['shp'] = buffer; - done(); - }); - fs.readFile(path.join(dataDirectory, arg + '.dbf'), function(e, buffer) { - obj['dbf'] = buffer; - done(); - }); - function done() { - let func; - let data; - let errors; - if(--count === 0) { - func = utils.parse(obj, function(data_, errors_) { - data = data_; - errors = errors_; - }); - callback({ - func: func === data, - data: data, - errors: errors - }); - } - } - }, - - 'read-and-parse': function(arg, callback) { - const func = utils.parse(path.join(dataDirectory, arg), function(data, errors) { - callback({ - func: func === undefined, - data: data, - errors: errors - }); - }); - } -}; - -function getRequestInfo(url) { - const parts = url.split('/'); - return { - action: parts[1], - arg: parts[2] - }; -} - -require('http').createServer(function(request, response) { - const info = getRequestInfo(request.url); - response.writeHead(200, { 'Content-Type': 'text/plain' }); - if(Object.hasOwn(actions, info.action)) { - const action = actions[info.action]; - if(typeof action === 'function') { - action(info.arg, callback); - } - } - function callback(data) { - response.end(JSON.stringify(data)); - } -}).listen(PORTS['vectormap-utils-tester'], '127.0.0.1'); diff --git a/packages/devextreme/testing/runner/index.ts b/packages/devextreme/testing/runner/index.ts index 29a4a0d6652f..4bc7c60da1ab 100644 --- a/packages/devextreme/testing/runner/index.ts +++ b/packages/devextreme/testing/runner/index.ts @@ -21,7 +21,6 @@ import { parseNumber, readBodyText, readFormBody, - resolveNodePath, safeDecodeURIComponent, safeReadFile, splitCommaList, @@ -35,7 +34,6 @@ import { createVectorMapService } from './lib/vectormap'; import { sendHtml, sendJson, - sendJsonText, sendNotFound, sendText, sendXml, @@ -77,9 +75,6 @@ const RUN_FLAGS = { const PORTS = loadPorts(path.join(PACKAGE_ROOT, 'ports.json')); const QUNIT_PORT = Number(PORTS.qunit); -const VECTOR_MAP_TESTER_PORT = Number(PORTS['vectormap-utils-tester']); - -const PATH_TO_NODE = resolveNodePath(); const logger = createRunnerLogger(RAW_LOG_FILENAME); const templates = createTemplateRenderer(TEMPLATES_ROOT, escapeHtml); @@ -100,10 +95,7 @@ const resultsReporter = createResultsReporter({ }); const vectorMapService = createVectorMapService({ packageRoot: PACKAGE_ROOT, - testingRoot: TESTING_ROOT, vectorDataDirectory: VECTOR_DATA_DIRECTORY, - vectorMapTesterPort: VECTOR_MAP_TESTER_PORT, - pathToNode: PATH_TO_NODE, }); const staticFiles = createStaticFileService({ escapeHtml, @@ -312,35 +304,6 @@ async function handleRequest(req: http.IncomingMessage, res: http.ServerResponse return; } - if (req.method === 'GET') { - const parseBufferMatch = /^\/TestVectorMapData\/ParseBuffer\/(.+)$/i.exec(pathname); - if (parseBufferMatch) { - const id = safeDecodeURIComponent(parseBufferMatch[1]); - const responseText = await vectorMapService.redirectRequestToVectorMapNodeServer('parse-buffer', id); - sendJsonText(res, responseText); - return; - } - } - - if (req.method === 'GET') { - const readAndParseMatch = /^\/TestVectorMapData\/ReadAndParse\/(.+)$/i.exec(pathname); - if (readAndParseMatch) { - const id = safeDecodeURIComponent(readAndParseMatch[1]); - const responseText = await vectorMapService.redirectRequestToVectorMapNodeServer('read-and-parse', id); - sendJsonText(res, responseText); - return; - } - } - - if (req.method === 'GET') { - const executeConsoleAppMatch = /^\/TestVectorMapData\/ExecuteConsoleApp(?:\/(.*))?$/i.exec(pathname); - if (executeConsoleAppMatch) { - const result = vectorMapService.executeVectorMapConsoleApp(requestUrl.searchParams); - sendJson(res, result); - return; - } - } - if (staticFiles.tryServeStatic(req, res, pathname, requestUrl.searchParams)) { return; } diff --git a/packages/devextreme/testing/runner/lib/http.ts b/packages/devextreme/testing/runner/lib/http.ts index 1d20d70838aa..d728ce87f884 100644 --- a/packages/devextreme/testing/runner/lib/http.ts +++ b/packages/devextreme/testing/runner/lib/http.ts @@ -28,13 +28,6 @@ export function sendJson(res: ServerResponse, payload: unknown): void { res.end(JSON.stringify(payload)); } -export function sendJsonText(res: ServerResponse, payloadText: string): void { - setNoCacheHeaders(res); - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.end(payloadText); -} - export function sendXml(res: ServerResponse, payload: string): void { setNoCacheHeaders(res); res.statusCode = 200; diff --git a/packages/devextreme/testing/runner/lib/types.ts b/packages/devextreme/testing/runner/lib/types.ts index 1034e6fec6c4..a533766b2572 100644 --- a/packages/devextreme/testing/runner/lib/types.ts +++ b/packages/devextreme/testing/runner/lib/types.ts @@ -100,16 +100,9 @@ export interface VectorMapDataItem { expected: string; } -export interface VectorMapOutputItem { - file: string; - variable: string | null; - content: JsonValue; -} - export interface PortsMap { [key: string]: number | string; qunit: number | string; - 'vectormap-utils-tester': number | string; } export type TemplateVarValue = JsonValue | bigint | undefined; diff --git a/packages/devextreme/testing/runner/lib/utils.ts b/packages/devextreme/testing/runner/lib/utils.ts index ec6e230f36a1..30a7393b1973 100644 --- a/packages/devextreme/testing/runner/lib/utils.ts +++ b/packages/devextreme/testing/runner/lib/utils.ts @@ -1,6 +1,5 @@ import * as fs from 'node:fs'; import { IncomingMessage } from 'node:http'; -import * as path from 'node:path'; import { PortsMap } from './types'; @@ -45,13 +44,12 @@ export function loadPorts(filePath: string): PortsMap { throw new Error(`Invalid ports definition: ${filePath}`); } - if (!isPortValue(parsed.qunit) || !isPortValue(parsed['vectormap-utils-tester'])) { + if (!isPortValue(parsed.qunit)) { throw new Error(`Required ports are missing in ${filePath}`); } const portsMap: PortsMap = { qunit: parsed.qunit, - 'vectormap-utils-tester': parsed['vectormap-utils-tester'], }; Object.entries(parsed).forEach(([key, value]) => { @@ -117,17 +115,6 @@ export function isContinuousIntegration(): boolean { return Boolean(process.env.CCNetWorkingDirectory || process.env.DEVEXTREME_TEST_CI); } -export function resolveNodePath(): string { - if (process.env.CCNetWorkingDirectory) { - const customPath = path.join(process.env.CCNetWorkingDirectory, 'node', 'node.exe'); - if (fs.existsSync(customPath)) { - return customPath; - } - } - - return 'node'; -} - export function readBodyText(req: IncomingMessage): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; diff --git a/packages/devextreme/testing/runner/lib/vectormap.ts b/packages/devextreme/testing/runner/lib/vectormap.ts index b2a2397b6699..8ffc86540758 100644 --- a/packages/devextreme/testing/runner/lib/vectormap.ts +++ b/packages/devextreme/testing/runner/lib/vectormap.ts @@ -1,147 +1,24 @@ -import { ChildProcess, spawn, spawnSync } from 'node:child_process'; import * as fs from 'node:fs'; -import * as http from 'node:http'; import * as path from 'node:path'; import { - JsonObject, - JsonValue, VectorMapDataItem, - VectorMapOutputItem, } from './types'; -const VECTOR_SERVER_RETRY_TIMEOUT_MS = 5000; -const VECTOR_SERVER_RETRY_DELAY_MS = 50; -const VECTOR_SERVER_KILL_DELAY_MS = 200; - interface VectorMapServiceOptions { packageRoot: string; - testingRoot: string; vectorDataDirectory: string; - vectorMapTesterPort: number; - pathToNode: string; } export interface VectorMapService { - executeVectorMapConsoleApp: (searchParams: URLSearchParams) => VectorMapOutputItem[]; readThemeCssFiles: () => string[]; readVectorMapTestData: () => VectorMapDataItem[]; - redirectRequestToVectorMapNodeServer: (action: string, arg: string) => Promise; -} - -interface VectorMapNodeServerState { - process: ChildProcess | null; - refs: number; - killTimer: NodeJS.Timeout | null; -} - -function isJsonObject(value: unknown): value is JsonObject { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function isJsonValue(value: unknown): value is JsonValue { - if ( - value === null - || typeof value === 'string' - || typeof value === 'number' - || typeof value === 'boolean' - ) { - return true; - } - - if (Array.isArray(value)) { - return value.every((item) => isJsonValue(item)); - } - - if (isJsonObject(value)) { - return Object.values(value).every((item) => isJsonValue(item)); - } - - return false; -} - -function getErrorCode(error: Error): string | null { - if ('code' in error && typeof error.code === 'string') { - return error.code; - } - - return null; -} - -function parseJsonContent(content: string, filePath: string): JsonValue { - const parsed = JSON.parse(content) as unknown; - - if (!isJsonValue(parsed)) { - throw new Error(`Unsupported JSON structure in ${filePath}`); - } - - return parsed; -} - -function wait(timeout: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, timeout); - }); -} - -function sanitizeVectorMapArg(arg: string): string { - const trimmed = arg.trim(); - - // Reject empty values - if (!trimmed) { - throw new Error('Vector map argument must not be empty.'); - } - - // Disallow characters and sequences that can alter the path structure - if (trimmed.includes('/') || trimmed.includes('\\') || trimmed.includes('?') || trimmed.includes('#')) { - throw new Error('Vector map argument contains invalid characters.'); - } - - // Prevent simple path traversal attempts - if (trimmed.includes('..')) { - throw new Error('Vector map argument must not contain path traversal sequences.'); - } - - // Optionally enforce a reasonable maximum length to avoid resource abuse - if (trimmed.length > 256) { - throw new Error('Vector map argument is too long.'); - } - - // Encode as a single safe URL path segment - return encodeURIComponent(trimmed); -} - -function httpGetText(targetUrl: string): Promise { - return new Promise((resolve, reject) => { - const request = http.get(targetUrl, (response) => { - const chunks: Buffer[] = []; - - response.on('data', (chunk: Buffer | string) => { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); - }); - - response.on('end', () => { - resolve(Buffer.concat(chunks).toString('utf8')); - }); - }); - - request.on('error', reject); - }); } export function createVectorMapService({ packageRoot, - testingRoot, vectorDataDirectory, - vectorMapTesterPort, - pathToNode, }: VectorMapServiceOptions): VectorMapService { - const vectorMapNodeServer: VectorMapNodeServerState = { - process: null, - refs: 0, - killTimer: null, - }; - function readThemeCssFiles(): string[] { const bundlesPath = path.join(packageRoot, 'scss', 'bundles'); const result: string[] = []; @@ -180,162 +57,8 @@ export function createVectorMapService({ }); } - function acquireVectorMapNodeServer(): void { - if (vectorMapNodeServer.killTimer !== null) { - clearTimeout(vectorMapNodeServer.killTimer); - vectorMapNodeServer.killTimer = null; - } - - if (vectorMapNodeServer.process === null || vectorMapNodeServer.process.killed) { - const scriptPath = path.join(testingRoot, 'helpers', 'vectormaputils-tester.js'); - - vectorMapNodeServer.process = spawn( - pathToNode, - [scriptPath, `${vectorDataDirectory}${path.sep}`], - { - stdio: 'ignore', - }, - ); - - vectorMapNodeServer.process.on('exit', () => { - if (vectorMapNodeServer.process !== null && vectorMapNodeServer.process.exitCode !== null) { - vectorMapNodeServer.process = null; - } - }); - } - - vectorMapNodeServer.refs += 1; - } - - function releaseVectorMapNodeServer(): void { - vectorMapNodeServer.refs -= 1; - - if (vectorMapNodeServer.refs <= 0) { - vectorMapNodeServer.refs = 0; - - vectorMapNodeServer.killTimer = setTimeout(() => { - if (vectorMapNodeServer.refs === 0 && vectorMapNodeServer.process !== null) { - try { - vectorMapNodeServer.process.kill(); - } catch { - // Ignore process kill failures. - } - vectorMapNodeServer.process = null; - } - vectorMapNodeServer.killTimer = null; - }, VECTOR_SERVER_KILL_DELAY_MS); - } - } - - async function requestWithRetryUntilReady( - action: string, - arg: string, - startTime: number, - ): Promise { - const safeArg = sanitizeVectorMapArg(arg); - - try { - return await httpGetText(`http://127.0.0.1:${vectorMapTesterPort}/${action}/${safeArg}`); - } catch (error) { - if (Date.now() - startTime > VECTOR_SERVER_RETRY_TIMEOUT_MS) { - throw error; - } - - await wait(VECTOR_SERVER_RETRY_DELAY_MS); - return requestWithRetryUntilReady(action, arg, startTime); - } - } - - async function redirectRequestToVectorMapNodeServer( - action: string, - arg: string, - ): Promise { - acquireVectorMapNodeServer(); - - try { - return await requestWithRetryUntilReady(action, arg, Date.now()); - } finally { - releaseVectorMapNodeServer(); - } - } - - function executeVectorMapConsoleApp( - searchParams: URLSearchParams, - ): VectorMapOutputItem[] { - const inputDirectory = `${path.join(packageRoot, 'testing', 'content', 'VectorMapData')}${path.sep}`; - const outputDirectory = path.join(inputDirectory, '__Output'); - const settingsPath = path.join(inputDirectory, '_settings.js'); - const processFileContentPath = path.join(inputDirectory, '_processFileContent.js'); - const vectorMapUtilsNodePath = path.resolve(path.join(packageRoot, 'artifacts/js/vectormap-utils/dx.vectormaputils.node.js')); - - const args = [vectorMapUtilsNodePath, inputDirectory]; - const fileArgument = searchParams.get('file'); - if (fileArgument !== null) { - args[1] += fileArgument; - } - - args.push('--quiet', '--output', outputDirectory, '--settings', settingsPath, '--process-file-content', processFileContentPath); - - const isJson = searchParams.has('json'); - if (isJson) { - args.push('--json'); - } - - fs.mkdirSync(outputDirectory, { recursive: true }); - - try { - const spawnResult = spawnSync(pathToNode, args, { - timeout: 15000, - stdio: 'ignore', - }); - - if (spawnResult.error !== undefined) { - const errorCode = getErrorCode(spawnResult.error); - if (errorCode !== 'ETIMEDOUT') { - throw spawnResult.error; - } - } - - const extension = isJson ? '.json' : '.js'; - - return fs.readdirSync(outputDirectory, { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.endsWith(extension)) - .map((entry) => { - const filePath = path.join(outputDirectory, entry.name); - let text = fs.readFileSync(filePath, 'utf8'); - let variable: string | null = null; - - if (!isJson) { - const index = text.indexOf('='); - if (index > 0) { - variable = text.substring(0, index).trim(); - text = text.substring(index + 1).trim(); - - if (text.endsWith(';')) { - text = text.slice(0, -1).trim(); - } - } - } - - return { - file: `${path.basename(entry.name, extension)}${extension}`, - variable, - content: parseJsonContent(text, filePath), - }; - }); - } finally { - try { - fs.rmSync(outputDirectory, { recursive: true, force: true }); - } catch { - // Ignore cleanup errors. - } - } - } - return { - executeVectorMapConsoleApp, readThemeCssFiles, readVectorMapTestData, - redirectRequestToVectorMapNodeServer, }; } diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap.utils/tests.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap.utils/tests.js index 66e317096f7f..51646bd94df0 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap.utils/tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap.utils/tests.js @@ -3,7 +3,6 @@ import { parse } from '../../../artifacts/js/vectormap-utils/dx.vectormaputils.js'; import $ from 'jquery'; -const CONTROLLER_URL = ROOT_URL + 'TestVectorMapData/'; const TEST_DATA_URL = ROOT_URL + 'packages/devextreme/testing/content/VectorMapData/'; let testData = []; @@ -14,14 +13,6 @@ function applyDatesPatch(obj, parser) { }); } -function applyNodeDatesPatch(obj) { - applyDatesPatch(obj, function(value) { - const offset = (new Date(value)).getTimezoneOffset(); - const vals = value.split('T')[0].split('-'); - return new Date(Number(vals[0]), Number(vals[1]) - 1, Number(vals[2]) + (offset < 0 ? 1 : 0)); - }); -} - function loadBinaryData(url) { const $deferred = $.Deferred(); const request = new XMLHttpRequest(); @@ -52,10 +43,6 @@ function getFailCallBack(assert) { }; } -function isPoint(obj) { - return obj.name === 'Point'; -} - QUnit.module('data loader', { before: async function() { const response = await fetch('/TestVectorMapData/GetTestData'); @@ -104,94 +91,6 @@ QUnit.module('data loader', { }); }); } - - QUnit.module('node - parse Buffer'); - - testData.forEach(function(testDataItem) { - QUnit.test(testDataItem.name, function(assert) { - assert.timeout(100000); - const done = assert.async(); - $.getJSON(CONTROLLER_URL + 'ParseBuffer/' + testDataItem.name).done(function(response) { - applyNodeDatesPatch(response.data); - assert.strictEqual(response.func, true, 'function result'); - assert.deepEqual(response.data, testDataItem.expected, 'parsing result'); - checkErrors(assert, response.errors, testDataItem.name); - }).fail(getFailCallBack(assert)) - .always(done); - }); - }); - - QUnit.module('node - read and parse'); - - testData.forEach(function(testDataItem) { - QUnit.test(testDataItem.name, function(assert) { - assert.timeout(100000); - const done = assert.async(); - $.getJSON(CONTROLLER_URL + 'ReadAndParse/' + testDataItem.name).done(function(response) { - applyNodeDatesPatch(response.data); - assert.strictEqual(response.func, true, 'function result'); - assert.deepEqual(response.data, testDataItem.expected, 'parsing result'); - checkErrors(assert, response.errors, testDataItem.name); - }).fail(getFailCallBack(assert)) - .always(done); - }); - }); - - QUnit.module('node-console'); - - QUnit.test('process single file', function(assert) { - const done = assert.async(); - $.getJSON(CONTROLLER_URL + 'ExecuteConsoleApp', { file: 'Point.shp' }, function(response) { - assert.strictEqual(response.length, 1, 'count'); - applyNodeDatesPatch(response[0].content); - assert.strictEqual(response[0].file, 'test_Point.js', 'file'); - assert.strictEqual(response[0].variable, 'test.namespace.Point', 'variable'); - assert.deepEqual(response[0].content, $.grep(testData, isPoint)[0].expected, 'content'); - }).fail(getFailCallBack(assert)) - .always(done); - }); - - QUnit.test('process directory', function(assert) { - const done = assert.async(); - $.getJSON(CONTROLLER_URL + 'ExecuteConsoleApp', function(response) { - assert.strictEqual(response.length, testData.length, 'count'); - response.forEach(function(responseItem) { - const testDataItem = $.grep(testData, function(obj) { - return obj.name === responseItem.file.substr(5).replace('.js', ''); - })[0]; - assert.strictEqual(responseItem.variable, 'test.namespace.' + testDataItem.name, 'variable /' + testDataItem.name); - applyNodeDatesPatch(responseItem.content); - assert.deepEqual(responseItem.content, testDataItem.expected, 'content / ' + testDataItem.name); - }); - }).fail(getFailCallBack(assert)) - .always(done); - }); - - QUnit.test('process single file / json', function(assert) { - const done = assert.async(); - $.getJSON(CONTROLLER_URL + 'ExecuteConsoleApp', { file: 'Point.shp', json: true }, function(response) { - assert.strictEqual(response.length, 1, 'count'); - applyNodeDatesPatch(response[0].content); - assert.strictEqual(response[0].file, 'test_Point.json', 'file'); - assert.deepEqual(response[0].content, $.grep(testData, isPoint)[0].expected, 'content'); - }).fail(getFailCallBack(assert)) - .always(done); - }); - - QUnit.test('process directory / json', function(assert) { - const done = assert.async(); - $.getJSON(CONTROLLER_URL + 'ExecuteConsoleApp', { json: 1 }, function(response) { - assert.strictEqual(response.length, testData.length, 'count'); - response.forEach(function(responseItem) { - const testDataItem = $.grep(testData, function(obj) { - return obj.name === responseItem.file.substr(5).replace('.json', ''); - })[0]; - applyNodeDatesPatch(responseItem.content); - assert.deepEqual(responseItem.content, testDataItem.expected, 'content / ' + testDataItem.name); - }); - }).fail(getFailCallBack(assert)) - .always(done); - }); }, }, function() { QUnit.test('trigger the "before" hook to load an array on which other test cases are based', function(assert) {