vladx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SourceMapGenerator — Генерация source maps
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class SourceMapGenerator {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.file = options.file || '';
|
|
8
|
+
this.sourceRoot = options.sourceRoot || '';
|
|
9
|
+
this.mappings = [];
|
|
10
|
+
this.sources = new Set();
|
|
11
|
+
this.names = new Set();
|
|
12
|
+
this.generatedMappings = [];
|
|
13
|
+
this.generatedLine = 1;
|
|
14
|
+
this.generatedColumn = 0;
|
|
15
|
+
this.lastGeneratedLine = 0;
|
|
16
|
+
this.lastGeneratedColumn = 0;
|
|
17
|
+
this.lastSourceLine = 0;
|
|
18
|
+
this.lastSourceColumn = 0;
|
|
19
|
+
this.lastNameIndex = 0;
|
|
20
|
+
this.lastSourceIndex = 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Добавить mapping
|
|
25
|
+
*/
|
|
26
|
+
addMapping(mapping) {
|
|
27
|
+
if (!mapping.generated) {
|
|
28
|
+
throw new Error('Generated position is required');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!mapping.original && !mapping.name) {
|
|
32
|
+
this.mappings.push(mapping);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!mapping.source) {
|
|
37
|
+
throw new Error('Source is required for mapping with original or name');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.sources.add(mapping.source);
|
|
41
|
+
if (mapping.name) {
|
|
42
|
+
this.names.add(mapping.name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.mappings.push({
|
|
46
|
+
generated: {
|
|
47
|
+
line: mapping.generated.line,
|
|
48
|
+
column: mapping.generated.column
|
|
49
|
+
},
|
|
50
|
+
original: mapping.original ? {
|
|
51
|
+
line: mapping.original.line,
|
|
52
|
+
column: mapping.original.column
|
|
53
|
+
} : null,
|
|
54
|
+
source: mapping.source,
|
|
55
|
+
name: mapping.name || null
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Установить исходный контент
|
|
61
|
+
*/
|
|
62
|
+
setSourceContent(source, content) {
|
|
63
|
+
this.sourceContent = this.sourceContent || {};
|
|
64
|
+
this.sourceContent[source] = content;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Добавить source
|
|
69
|
+
*/
|
|
70
|
+
addSource(source) {
|
|
71
|
+
this.sources.add(source);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Добавить имя
|
|
76
|
+
*/
|
|
77
|
+
addName(name) {
|
|
78
|
+
this.names.add(name);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Сгенерировать source map
|
|
83
|
+
*/
|
|
84
|
+
toString() {
|
|
85
|
+
return JSON.stringify(this.toJSON());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Получить JSON
|
|
90
|
+
*/
|
|
91
|
+
toJSON() {
|
|
92
|
+
const sources = Array.from(this.sources);
|
|
93
|
+
const names = Array.from(this.names);
|
|
94
|
+
|
|
95
|
+
const map = {
|
|
96
|
+
version: 3,
|
|
97
|
+
file: this.file,
|
|
98
|
+
sourceRoot: this.sourceRoot,
|
|
99
|
+
sources,
|
|
100
|
+
names,
|
|
101
|
+
mappings: this.encodeMappings(this.mappings)
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (this.sourceContent) {
|
|
105
|
+
map.sourcesContent = sources.map(source =>
|
|
106
|
+
this.sourceContent[source] || null
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Кодировать mappings в base64 VLQ
|
|
115
|
+
*/
|
|
116
|
+
encodeMappings(mappings) {
|
|
117
|
+
let result = '';
|
|
118
|
+
let lastGeneratedLine = 0;
|
|
119
|
+
let lastGeneratedColumn = 0;
|
|
120
|
+
let lastSourceIndex = 0;
|
|
121
|
+
let lastSourceLine = 0;
|
|
122
|
+
let lastSourceColumn = 0;
|
|
123
|
+
let lastNameIndex = 0;
|
|
124
|
+
|
|
125
|
+
mappings.sort((a, b) => {
|
|
126
|
+
if (a.generated.line !== b.generated.line) {
|
|
127
|
+
return a.generated.line - b.generated.line;
|
|
128
|
+
}
|
|
129
|
+
return a.generated.column - b.generated.column;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
for (const mapping of mappings) {
|
|
133
|
+
if (mapping.generated.line !== lastGeneratedLine) {
|
|
134
|
+
const linesDiff = mapping.generated.line - lastGeneratedLine;
|
|
135
|
+
for (let i = 0; i < linesDiff; i++) {
|
|
136
|
+
result += ';';
|
|
137
|
+
}
|
|
138
|
+
lastGeneratedLine = mapping.generated.line;
|
|
139
|
+
lastGeneratedColumn = 0;
|
|
140
|
+
} else if (result.length > 0) {
|
|
141
|
+
result += ',';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Generated column
|
|
145
|
+
result += this.encodeVLQ(mapping.generated.column - lastGeneratedColumn);
|
|
146
|
+
lastGeneratedColumn = mapping.generated.column;
|
|
147
|
+
|
|
148
|
+
if (mapping.original) {
|
|
149
|
+
const sourceIndex = this.sources.indexOf(mapping.source);
|
|
150
|
+
result += this.encodeVLQ(sourceIndex - lastSourceIndex);
|
|
151
|
+
lastSourceIndex = sourceIndex;
|
|
152
|
+
|
|
153
|
+
result += this.encodeVLQ(mapping.original.line - 1 - lastSourceLine);
|
|
154
|
+
lastSourceLine = mapping.original.line - 1;
|
|
155
|
+
|
|
156
|
+
result += this.encodeVLQ(mapping.original.column - lastSourceColumn);
|
|
157
|
+
lastSourceColumn = mapping.original.column;
|
|
158
|
+
|
|
159
|
+
if (mapping.name !== null) {
|
|
160
|
+
const nameIndex = this.names.indexOf(mapping.name);
|
|
161
|
+
result += this.encodeVLQ(nameIndex - lastNameIndex);
|
|
162
|
+
lastNameIndex = nameIndex;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Кодировать число в VLQ
|
|
172
|
+
*/
|
|
173
|
+
encodeVLQ(value) {
|
|
174
|
+
let VLQ_BASE_SHIFT = 5;
|
|
175
|
+
let VLQ_BASE = 1 << VLQ_BASE_SHIFT;
|
|
176
|
+
let VLQ_BASE_MASK = VLQ_BASE - 1;
|
|
177
|
+
let VLQ_CONTINUATION_BIT = VLQ_BASE;
|
|
178
|
+
|
|
179
|
+
let encoded = '';
|
|
180
|
+
let sign = value < 0 ? 1 : 0;
|
|
181
|
+
value = Math.abs(value);
|
|
182
|
+
|
|
183
|
+
value = (value << 1) | sign;
|
|
184
|
+
|
|
185
|
+
do {
|
|
186
|
+
let digit = value & VLQ_BASE_MASK;
|
|
187
|
+
value >>>= VLQ_BASE_SHIFT;
|
|
188
|
+
|
|
189
|
+
if (value > 0) {
|
|
190
|
+
digit |= VLQ_CONTINUATION_BIT;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
encoded += this.base64Encode(digit);
|
|
194
|
+
} while (value > 0);
|
|
195
|
+
|
|
196
|
+
return encoded;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Base64 кодирование
|
|
201
|
+
*/
|
|
202
|
+
base64Encode(value) {
|
|
203
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
204
|
+
return chars[value];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default SourceMapGenerator;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestRunner — Фреймворк для тестирования
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { VladXObject } from './vladx-object.js';
|
|
6
|
+
|
|
7
|
+
export class TestRunner {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.suites = [];
|
|
10
|
+
this.currentSuite = null;
|
|
11
|
+
this.results = {
|
|
12
|
+
total: 0,
|
|
13
|
+
passed: 0,
|
|
14
|
+
failed: 0,
|
|
15
|
+
skipped: 0,
|
|
16
|
+
suites: []
|
|
17
|
+
};
|
|
18
|
+
this.assertions = this.createAssertions();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Создать группу тестов
|
|
23
|
+
*/
|
|
24
|
+
describe(name, callback) {
|
|
25
|
+
const suite = {
|
|
26
|
+
name,
|
|
27
|
+
tests: [],
|
|
28
|
+
beforeEach: [],
|
|
29
|
+
afterEach: []
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
this.suites.push(suite);
|
|
33
|
+
const parentSuite = this.currentSuite;
|
|
34
|
+
this.currentSuite = suite;
|
|
35
|
+
|
|
36
|
+
callback();
|
|
37
|
+
|
|
38
|
+
this.currentSuite = parentSuite;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Создать тест
|
|
43
|
+
*/
|
|
44
|
+
it(name, callback, options = {}) {
|
|
45
|
+
if (!this.currentSuite) {
|
|
46
|
+
throw new Error('Test must be inside a describe block');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const test = {
|
|
50
|
+
name,
|
|
51
|
+
callback,
|
|
52
|
+
skip: options.skip || false,
|
|
53
|
+
only: options.only || false,
|
|
54
|
+
timeout: options.timeout || 5000
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
this.currentSuite.tests.push(test);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Пропустить тест
|
|
62
|
+
*/
|
|
63
|
+
skip(name, callback) {
|
|
64
|
+
this.it(name, callback, { skip: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Запустить только этот тест
|
|
69
|
+
*/
|
|
70
|
+
only(name, callback) {
|
|
71
|
+
this.it(name, callback, { only: true });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Before each
|
|
76
|
+
*/
|
|
77
|
+
beforeEach(callback) {
|
|
78
|
+
if (!this.currentSuite) {
|
|
79
|
+
throw new Error('beforeEach must be inside a describe block');
|
|
80
|
+
}
|
|
81
|
+
this.currentSuite.beforeEach.push(callback);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* After each
|
|
86
|
+
*/
|
|
87
|
+
afterEach(callback) {
|
|
88
|
+
if (!this.currentSuite) {
|
|
89
|
+
throw new Error('afterEach must be inside a describe block');
|
|
90
|
+
}
|
|
91
|
+
this.currentSuite.afterEach.push(callback);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Запустить все тесты
|
|
96
|
+
*/
|
|
97
|
+
async run() {
|
|
98
|
+
this.results = {
|
|
99
|
+
total: 0,
|
|
100
|
+
passed: 0,
|
|
101
|
+
failed: 0,
|
|
102
|
+
skipped: 0,
|
|
103
|
+
suites: []
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Запустить только тесты с .only
|
|
107
|
+
const onlyTests = this.findOnlyTests();
|
|
108
|
+
const suitesToRun = onlyTests.length > 0 ? this.getSuitesWithOnly() : this.suites;
|
|
109
|
+
|
|
110
|
+
for (const suite of suitesToRun) {
|
|
111
|
+
await this.runSuite(suite);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return this.results;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Запустить suite
|
|
119
|
+
*/
|
|
120
|
+
async runSuite(suite) {
|
|
121
|
+
const suiteResult = {
|
|
122
|
+
name: suite.name,
|
|
123
|
+
tests: []
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
console.log(`\n${suite.name}`);
|
|
127
|
+
|
|
128
|
+
for (const test of suite.tests) {
|
|
129
|
+
const testResult = await this.runTest(suite, test);
|
|
130
|
+
suiteResult.tests.push(testResult);
|
|
131
|
+
|
|
132
|
+
if (testResult.skipped) {
|
|
133
|
+
this.results.skipped++;
|
|
134
|
+
console.log(` ⊘ ${test.name} (skipped)`);
|
|
135
|
+
} else if (testResult.passed) {
|
|
136
|
+
this.results.passed++;
|
|
137
|
+
console.log(` ✓ ${test.name}`);
|
|
138
|
+
} else {
|
|
139
|
+
this.results.failed++;
|
|
140
|
+
console.log(` ✗ ${test.name}`);
|
|
141
|
+
if (testResult.error) {
|
|
142
|
+
console.log(` ${testResult.error.message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.results.total++;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.results.suites.push(suiteResult);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Запустить тест
|
|
154
|
+
*/
|
|
155
|
+
async runTest(suite, test) {
|
|
156
|
+
const result = {
|
|
157
|
+
name: test.name,
|
|
158
|
+
passed: false,
|
|
159
|
+
failed: false,
|
|
160
|
+
skipped: test.skip,
|
|
161
|
+
error: null
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (test.skip) {
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Before each hooks
|
|
170
|
+
for (const hook of suite.beforeEach) {
|
|
171
|
+
await hook();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Run test with timeout
|
|
175
|
+
await Promise.race([
|
|
176
|
+
test.callback(this.assertions),
|
|
177
|
+
new Promise((_, reject) =>
|
|
178
|
+
setTimeout(() => reject(new Error(`Timeout: ${test.timeout}ms`)), test.timeout)
|
|
179
|
+
)
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
result.passed = true;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
result.failed = true;
|
|
185
|
+
result.error = error;
|
|
186
|
+
} finally {
|
|
187
|
+
// After each hooks
|
|
188
|
+
for (const hook of suite.afterEach) {
|
|
189
|
+
try {
|
|
190
|
+
await hook();
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.error('Error in afterEach:', e);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Создать assertions
|
|
202
|
+
*/
|
|
203
|
+
createAssertions() {
|
|
204
|
+
const assert = (condition, message) => {
|
|
205
|
+
if (!condition) {
|
|
206
|
+
throw new Error(message || 'Assertion failed');
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
assert,
|
|
212
|
+
|
|
213
|
+
equal: (actual, expected, message) => {
|
|
214
|
+
assert(actual === expected, message || `Expected ${actual} to equal ${expected}`);
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
deepEqual: (actual, expected, message) => {
|
|
218
|
+
assert(
|
|
219
|
+
JSON.stringify(actual) === JSON.stringify(expected),
|
|
220
|
+
message || `Expected ${JSON.stringify(actual)} to deeply equal ${JSON.stringify(expected)}`
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
notEqual: (actual, expected, message) => {
|
|
225
|
+
assert(actual !== expected, message || `Expected ${actual} to not equal ${expected}`);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
throws: async (fn, message) => {
|
|
229
|
+
let threw = false;
|
|
230
|
+
try {
|
|
231
|
+
await fn();
|
|
232
|
+
} catch (e) {
|
|
233
|
+
threw = true;
|
|
234
|
+
}
|
|
235
|
+
assert(threw, message || 'Expected function to throw');
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
doesNotThrow: async (fn, message) => {
|
|
239
|
+
let threw = false;
|
|
240
|
+
let error;
|
|
241
|
+
try {
|
|
242
|
+
await fn();
|
|
243
|
+
} catch (e) {
|
|
244
|
+
threw = true;
|
|
245
|
+
error = e;
|
|
246
|
+
}
|
|
247
|
+
assert(!threw, message || `Expected function not to throw, but threw: ${error?.message}`);
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
isTrue: (value, message) => {
|
|
251
|
+
assert(value === true, message || `Expected ${value} to be true`);
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
isFalse: (value, message) => {
|
|
255
|
+
assert(value === false, message || `Expected ${value} to be false`);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
isNull: (value, message) => {
|
|
259
|
+
assert(value === null, message || `Expected ${value} to be null`);
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
isNotNull: (value, message) => {
|
|
263
|
+
assert(value !== null, message || `Expected ${value} to not be null`);
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
isUndefined: (value, message) => {
|
|
267
|
+
assert(value === undefined, message || `Expected ${value} to be undefined`);
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
isDefined: (value, message) => {
|
|
271
|
+
assert(value !== undefined, message || `Expected ${value} to be defined`);
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
contains: (array, item, message) => {
|
|
275
|
+
assert(array.includes(item), message || `Expected ${array} to contain ${item}`);
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
notContains: (array, item, message) => {
|
|
279
|
+
assert(!array.includes(item), message || `Expected ${array} to not contain ${item}`);
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
hasProperty: (object, property, message) => {
|
|
283
|
+
assert(property in object, message || `Expected object to have property ${property}`);
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
typeOf: (value, type, message) => {
|
|
287
|
+
assert(
|
|
288
|
+
typeof value === type,
|
|
289
|
+
message || `Expected ${value} to be of type ${type}`
|
|
290
|
+
);
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
instanceOf: (value, constructor, message) => {
|
|
294
|
+
assert(
|
|
295
|
+
value instanceof constructor,
|
|
296
|
+
message || `Expected ${value} to be instance of ${constructor.name}`
|
|
297
|
+
);
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
lengthOf: (array, length, message) => {
|
|
301
|
+
assert(
|
|
302
|
+
array.length === length,
|
|
303
|
+
message || `Expected array to have length ${length}`
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
greaterThan: (value, expected, message) => {
|
|
308
|
+
assert(value > expected, message || `Expected ${value} to be greater than ${expected}`);
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
lessThan: (value, expected, message) => {
|
|
312
|
+
assert(value < expected, message || `Expected ${value} to be less than ${expected}`);
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
matches: (string, regex, message) => {
|
|
316
|
+
assert(regex.test(string), message || `Expected ${string} to match ${regex}`);
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
fail: (message) => {
|
|
320
|
+
throw new Error(message || 'Test failed');
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
snapshot: (value, name) => {
|
|
324
|
+
// Simplified snapshot testing
|
|
325
|
+
const snapshotKey = `${name}`;
|
|
326
|
+
console.log(`Snapshot: ${snapshotKey}`, JSON.stringify(value, null, 2));
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Найти тесты с .only
|
|
333
|
+
*/
|
|
334
|
+
findOnlyTests() {
|
|
335
|
+
const onlyTests = [];
|
|
336
|
+
for (const suite of this.suites) {
|
|
337
|
+
for (const test of suite.tests) {
|
|
338
|
+
if (test.only) {
|
|
339
|
+
onlyTests.push(test);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return onlyTests;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Получить suites с тестами .only
|
|
348
|
+
*/
|
|
349
|
+
getSuitesWithOnly() {
|
|
350
|
+
const suitesWithOnly = [];
|
|
351
|
+
for (const suite of this.suites) {
|
|
352
|
+
if (suite.tests.some(test => test.only)) {
|
|
353
|
+
suitesWithOnly.push(suite);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return suitesWithOnly;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Получить результаты в формате JSON
|
|
361
|
+
*/
|
|
362
|
+
getResultsJSON() {
|
|
363
|
+
return JSON.stringify(this.results, null, 2);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Получить результаты в формате JUnit XML
|
|
368
|
+
*/
|
|
369
|
+
getResultsJUnit() {
|
|
370
|
+
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<testsuites>\n';
|
|
371
|
+
|
|
372
|
+
for (const suite of this.results.suites) {
|
|
373
|
+
const suiteFailed = suite.tests.some(t => t.failed);
|
|
374
|
+
xml += ` <testsuite name="${suite.name}" tests="${suite.tests.length}" failures="${suite.tests.filter(t => t.failed).length}" skipped="${suite.tests.filter(t => t.skipped).length}">\n`;
|
|
375
|
+
|
|
376
|
+
for (const test of suite.tests) {
|
|
377
|
+
if (test.skipped) {
|
|
378
|
+
xml += ` <testcase name="${test.name}">\n <skipped/>\n </testcase>\n`;
|
|
379
|
+
} else if (test.failed) {
|
|
380
|
+
xml += ` <testcase name="${test.name}">\n <failure message="${test.error?.message || 'Test failed'}"/>\n </testcase>\n`;
|
|
381
|
+
} else {
|
|
382
|
+
xml += ` <testcase name="${test.name}"/>\n`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
xml += ' </testsuite>\n';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
xml += '</testsuites>';
|
|
390
|
+
return xml;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export default TestRunner;
|