@dannysir/js-te 0.0.2 → 0.1.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 +94 -31
- package/babelTransformImport.js +28 -21
- package/bin/cli.js +8 -8
- package/constants.js +22 -0
- package/index.js +5 -0
- package/package.json +1 -1
- package/src/mock/store.js +5 -5
- package/src/tests.js +43 -1
package/README.md
CHANGED
|
@@ -27,10 +27,12 @@ describe('[단순 연산 테스트]', () => {
|
|
|
27
27
|
|
|
28
28
|
### 2. 테스트 실행
|
|
29
29
|
|
|
30
|
-
package.json에
|
|
30
|
+
package.json에 추가.
|
|
31
31
|
|
|
32
|
+
- type을 module로 설정해주세요.
|
|
32
33
|
```json
|
|
33
34
|
{
|
|
35
|
+
"type": "module",
|
|
34
36
|
"scripts": {
|
|
35
37
|
"test": "js-te"
|
|
36
38
|
}
|
|
@@ -127,41 +129,40 @@ Babel을 사용해서 import 구문을 변환하여 mock 함수를 가져오도
|
|
|
127
129
|
2. `path`를 key로 이용해 Map에 저장
|
|
128
130
|
2. Babel로 코드 변환
|
|
129
131
|
1. 전체 파일의 import문 확인
|
|
130
|
-
2.
|
|
131
|
-
|
|
132
|
+
2. (0.0.3 버전 추가) import 경로를 **절대 경로**로 변환
|
|
133
|
+
2. import 경로(절대 경로)가 Map에 존재하면 mock 객체로 변환
|
|
134
|
+
3. import 경로(절대 경로)가 Map에 없다면 그대로 import
|
|
132
135
|
3. 테스트 실행
|
|
133
136
|
4. 원본 파일 복구
|
|
134
137
|
|
|
135
|
-
###
|
|
138
|
+
### `mock(모듈 절대 경로), mock객체)`
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
반드시 **절대 경로**로 표현한 후 `mock` 함수에 **절대 경로**로 등록을 해주세요.
|
|
140
|
-
|
|
141
|
-
> 만약 모듈이 사용되는 모든 위치의 path가 동일하다면 상대 경로도 정삭 작동합니다.
|
|
140
|
+
모듈을 모킹합니다. import 하기 **전에** 호출해야 합니다.
|
|
142
141
|
|
|
143
|
-
|
|
142
|
+
**🚨 주의사항**
|
|
144
143
|
|
|
145
|
-
|
|
144
|
+
1. 반드시 경로는 절대 경로로 입력해주세요.
|
|
145
|
+
- babel이 import문에서 절대 경로로 변환하여 확인을 하기 때문에 반드시 절대 경로로 등록해주세요.
|
|
146
|
+
2. import문을 반드시 mocking 이후에 선언해주세요.
|
|
147
|
+
- mocking 전에 import를 하게 되면 mocking되기 전의 모듈을 가져오게 됩니다.
|
|
146
148
|
|
|
147
149
|
```javascript
|
|
148
150
|
// random.js
|
|
149
151
|
export const random = () => Math.random();
|
|
150
152
|
|
|
151
153
|
// game.js
|
|
152
|
-
import { random } from './random.js';
|
|
154
|
+
import { random } from './random.js'; // 자유롭게 import하면 babel에서 절대 경로로 변환하여 판단합니다.
|
|
153
155
|
export const play = () => random() * 10;
|
|
154
156
|
|
|
155
157
|
// game.test.js
|
|
156
|
-
import { mock, test, expect } from 'js-te';
|
|
157
|
-
|
|
158
158
|
test('랜덤 함수 모킹', async () => {
|
|
159
159
|
// 1. 먼저 모킹
|
|
160
|
-
mock('
|
|
160
|
+
mock('/Users/san/Js-Te/test-helper/random.js', { // 반드시 절대 경로로 등록
|
|
161
161
|
random: () => 0.5
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
// 2. 그 다음 import
|
|
165
|
+
// 상단에 import문을 입력할 경우
|
|
165
166
|
const { play } = await import('./game.js');
|
|
166
167
|
|
|
167
168
|
// 3. 모킹된 값 사용
|
|
@@ -181,6 +182,82 @@ test('랜덤 함수 모킹', async () => {
|
|
|
181
182
|
|
|
182
183
|
mock이 등록되어 있는지 확인합니다.
|
|
183
184
|
|
|
185
|
+
## `each(cases)`
|
|
186
|
+
|
|
187
|
+
`cases`를 배열로 받아 순차적으로 테스트 진행
|
|
188
|
+
|
|
189
|
+
#### 🚨 주의 사항
|
|
190
|
+
|
|
191
|
+
`cases`는 반드시 `Array` 타입으로 받아야 합니다.
|
|
192
|
+
|
|
193
|
+
### 플레이스 홀더
|
|
194
|
+
|
|
195
|
+
- %s - 문자열/숫자
|
|
196
|
+
- %o - 객체 (JSON.stringify)
|
|
197
|
+
|
|
198
|
+
```jsx
|
|
199
|
+
test.each([
|
|
200
|
+
[1, 2, 3, 6],
|
|
201
|
+
[3, 4, 5, 12],
|
|
202
|
+
[10, 20, 13, 43],
|
|
203
|
+
[10, 12, 13, 35],
|
|
204
|
+
])('[each test] - input : %s, %s, %s, %s', (a, b, c, result) => {
|
|
205
|
+
expect(a + b + c).toBe(result);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/* 출력 결과
|
|
209
|
+
✓ [each test] - input : 1, 2, 3, 6
|
|
210
|
+
✓ [each test] - input : 3, 4, 5, 12
|
|
211
|
+
✓ [each test] - input : 10, 20, 13, 43
|
|
212
|
+
✓ [each test] - input : 10, 12, 13, 35
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
test.each([
|
|
216
|
+
[{ name : 'dannysir', age : null}],
|
|
217
|
+
])('[each test placeholder] - input : %o', (arg) => {
|
|
218
|
+
expect(arg.name).toBe('dannysir');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
/* 출력 결과
|
|
222
|
+
✓ [each test placeholder] - input : {"name":"dannysir","age":null}
|
|
223
|
+
*/
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## `beforeEach(함수)`
|
|
227
|
+
|
|
228
|
+
각 테스트가 진행되기 전에 실행할 함수를 선언합니다.
|
|
229
|
+
|
|
230
|
+
중첩된 describe에서의 `beforeEach`는 상위 describe의 `beforeEach`를 모두 실행한 후, 자신의 `beforeEach`를 실행합니다.
|
|
231
|
+
|
|
232
|
+
```jsx
|
|
233
|
+
describe('카운터 테스트', () => {
|
|
234
|
+
let counter;
|
|
235
|
+
|
|
236
|
+
beforeEach(() => {
|
|
237
|
+
counter = 0;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('카운터 증가', () => {
|
|
241
|
+
counter++;
|
|
242
|
+
expect(counter).toBe(1);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('카운터는 0부터 시작', () => {
|
|
246
|
+
expect(counter).toBe(0);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('중첩된 describe', () => {
|
|
250
|
+
beforeEach(() => {
|
|
251
|
+
counter = 10;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('카운터는 10', () => {
|
|
255
|
+
expect(counter).toBe(10);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
184
261
|
## 테스트 파일 찾기 규칙
|
|
185
262
|
|
|
186
263
|
자동으로 다음 파일들을 찾아서 실행합니다:
|
|
@@ -204,8 +281,6 @@ mock이 등록되어 있는지 확인합니다.
|
|
|
204
281
|
### 기본 테스트
|
|
205
282
|
|
|
206
283
|
```javascript
|
|
207
|
-
import { describe, test, expect } from 'js-te';
|
|
208
|
-
|
|
209
284
|
describe('문자열 테스트', () => {
|
|
210
285
|
test('문자열 합치기', () => {
|
|
211
286
|
const result = 'hello' + ' ' + 'world';
|
|
@@ -222,10 +297,8 @@ describe('문자열 테스트', () => {
|
|
|
222
297
|
|
|
223
298
|
```javascript
|
|
224
299
|
// mocking.test.js
|
|
225
|
-
import { mock, test, expect } from 'js-te';
|
|
226
|
-
|
|
227
300
|
test('[mocking] - mocking random function', async () => {
|
|
228
|
-
mock('/
|
|
301
|
+
mock('/Users/san/Js-Te/test-helper/random.js', {
|
|
229
302
|
random: () => 3,
|
|
230
303
|
});
|
|
231
304
|
const {play} = await import('../src/test-helper/game.js');
|
|
@@ -234,7 +307,7 @@ test('[mocking] - mocking random function', async () => {
|
|
|
234
307
|
|
|
235
308
|
|
|
236
309
|
// game.js
|
|
237
|
-
import {random} from '/
|
|
310
|
+
import {random} from '/test-helper/random.js'
|
|
238
311
|
|
|
239
312
|
export const play = () => {
|
|
240
313
|
return random() * 10;
|
|
@@ -244,16 +317,6 @@ export const play = () => {
|
|
|
244
317
|
export const random = () => Math.random();
|
|
245
318
|
```
|
|
246
319
|
|
|
247
|
-
## 설정
|
|
248
|
-
|
|
249
|
-
`package.json`에 해당 설정을 하셔야 정상 작동합니다.
|
|
250
|
-
|
|
251
|
-
```json
|
|
252
|
-
{
|
|
253
|
-
"type": "module"
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
320
|
## 링크
|
|
258
321
|
|
|
259
322
|
- [GitHub](https://github.com/dannysir/Js-Te)
|
package/babelTransformImport.js
CHANGED
|
@@ -1,44 +1,53 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import {BABEL, MOCK} from "./constants.js";
|
|
3
|
+
|
|
1
4
|
export const babelTransformImport = ({types: t}) => {
|
|
2
5
|
return {
|
|
3
6
|
visitor: {
|
|
4
7
|
Program(path) {
|
|
5
8
|
const importStatement = t.ImportDeclaration(
|
|
6
9
|
[t.importSpecifier(
|
|
7
|
-
t.identifier(
|
|
8
|
-
t.identifier(
|
|
10
|
+
t.identifier(MOCK.STORE_NAME),
|
|
11
|
+
t.identifier(MOCK.STORE_NAME)
|
|
9
12
|
)],
|
|
10
|
-
t.stringLiteral(
|
|
13
|
+
t.stringLiteral(MOCK.STORE_PATH)
|
|
11
14
|
);
|
|
12
15
|
path.node.body.unshift(importStatement);
|
|
13
16
|
},
|
|
14
17
|
|
|
15
|
-
ImportDeclaration(
|
|
16
|
-
const source =
|
|
18
|
+
ImportDeclaration(nodePath, state) {
|
|
19
|
+
const source = nodePath.node.source.value;
|
|
17
20
|
|
|
18
|
-
if (source ===
|
|
21
|
+
if (source === MOCK.STORE_PATH) {
|
|
19
22
|
return;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
const
|
|
25
|
+
const currentFilePath = state.filename || process.cwd();
|
|
26
|
+
const currentDir = path.dirname(currentFilePath);
|
|
27
|
+
|
|
28
|
+
let absolutePath;
|
|
29
|
+
if (source.startsWith(BABEL.PERIOD)) {
|
|
30
|
+
absolutePath = path.resolve(currentDir, source);
|
|
31
|
+
} else {
|
|
32
|
+
absolutePath = source;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const specifiers = nodePath.node.specifiers;
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
const moduleVarName = path.scope.generateUidIdentifier('module');
|
|
37
|
+
const moduleVarName = nodePath.scope.generateUidIdentifier(BABEL.MODULE);
|
|
26
38
|
|
|
27
|
-
const moduleDeclaration = t.variableDeclaration(
|
|
39
|
+
const moduleDeclaration = t.variableDeclaration(BABEL.CONST, [
|
|
28
40
|
t.variableDeclarator(
|
|
29
41
|
moduleVarName,
|
|
30
42
|
t.conditionalExpression(
|
|
31
|
-
// 조건: __mockRegistry__.has(source)
|
|
32
43
|
t.callExpression(
|
|
33
|
-
t.memberExpression(t.identifier(
|
|
34
|
-
[t.stringLiteral(
|
|
44
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.HAS)),
|
|
45
|
+
[t.stringLiteral(absolutePath)]
|
|
35
46
|
),
|
|
36
|
-
// true: mock 반환
|
|
37
47
|
t.callExpression(
|
|
38
|
-
t.memberExpression(t.identifier(
|
|
39
|
-
[t.stringLiteral(
|
|
48
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.GET)),
|
|
49
|
+
[t.stringLiteral(absolutePath)]
|
|
40
50
|
),
|
|
41
|
-
// false: 실제 import
|
|
42
51
|
t.awaitExpression(
|
|
43
52
|
t.importExpression(t.stringLiteral(source))
|
|
44
53
|
)
|
|
@@ -46,7 +55,6 @@ export const babelTransformImport = ({types: t}) => {
|
|
|
46
55
|
)
|
|
47
56
|
]);
|
|
48
57
|
|
|
49
|
-
// 2. 각 specifier를 moduleVarName에서 추출
|
|
50
58
|
const extractDeclarations = specifiers.map(spec => {
|
|
51
59
|
let importedName, localName;
|
|
52
60
|
|
|
@@ -55,7 +63,6 @@ export const babelTransformImport = ({types: t}) => {
|
|
|
55
63
|
localName = spec.local.name;
|
|
56
64
|
} else if (t.isImportNamespaceSpecifier(spec)) {
|
|
57
65
|
localName = spec.local.name;
|
|
58
|
-
// namespace import는 전체 모듈
|
|
59
66
|
return t.variableDeclarator(
|
|
60
67
|
t.identifier(localName),
|
|
61
68
|
moduleVarName
|
|
@@ -71,9 +78,9 @@ export const babelTransformImport = ({types: t}) => {
|
|
|
71
78
|
);
|
|
72
79
|
});
|
|
73
80
|
|
|
74
|
-
const extractDeclaration = t.variableDeclaration(
|
|
81
|
+
const extractDeclaration = t.variableDeclaration(BABEL.CONST, extractDeclarations);
|
|
75
82
|
|
|
76
|
-
|
|
83
|
+
nodePath.replaceWithMultiple([moduleDeclaration, extractDeclaration]);
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
};
|
package/bin/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {transformSync} from '@babel/core';
|
|
|
6
6
|
import * as jsTe from '../index.js';
|
|
7
7
|
import {green, yellow} from "../utils/consoleColor.js";
|
|
8
8
|
import {getTestResultMsg} from "../utils/makeMessage.js";
|
|
9
|
-
import {RESULT_TITLE} from "../constants.js";
|
|
9
|
+
import {BABEL, PATH, RESULT_TITLE} from "../constants.js";
|
|
10
10
|
import { babelTransformImport } from '../babelTransformImport.js';
|
|
11
11
|
|
|
12
12
|
let totalPassed = 0;
|
|
@@ -25,7 +25,7 @@ const transformFile = (filePath) => {
|
|
|
25
25
|
filename: filePath,
|
|
26
26
|
plugins: [babelTransformImport],
|
|
27
27
|
parserOpts: {
|
|
28
|
-
sourceType:
|
|
28
|
+
sourceType: BABEL.MODULE,
|
|
29
29
|
plugins: ['dynamicImport']
|
|
30
30
|
}
|
|
31
31
|
});
|
|
@@ -46,18 +46,18 @@ const findTestFiles = (dir) => {
|
|
|
46
46
|
const items = fs.readdirSync(directory);
|
|
47
47
|
const dirName = path.basename(directory);
|
|
48
48
|
|
|
49
|
-
const isTestDir = dirName ===
|
|
49
|
+
const isTestDir = dirName === PATH.TEST_DIRECTORY || inTestDir;
|
|
50
50
|
|
|
51
51
|
for (const item of items) {
|
|
52
|
-
if (item ===
|
|
52
|
+
if (item === PATH.NODE_MODULES) continue;
|
|
53
53
|
|
|
54
54
|
const fullPath = path.join(directory, item);
|
|
55
55
|
const stat = fs.statSync(fullPath);
|
|
56
56
|
|
|
57
57
|
if (stat.isDirectory()) {
|
|
58
58
|
walk(fullPath, isTestDir);
|
|
59
|
-
} else if (item.endsWith(
|
|
60
|
-
if (item.endsWith(
|
|
59
|
+
} else if (item.endsWith(PATH.TEST_FILE) || isTestDir) {
|
|
60
|
+
if (item.endsWith(PATH.JAVASCRIPT_FILE)) {
|
|
61
61
|
files.push(fullPath);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -75,14 +75,14 @@ const findAllSourceFiles = (dir) => {
|
|
|
75
75
|
const items = fs.readdirSync(directory);
|
|
76
76
|
|
|
77
77
|
for (const item of items) {
|
|
78
|
-
if (item ===
|
|
78
|
+
if (item === PATH.NODE_MODULES || item === PATH.BIN || item === PATH.TEST_DIRECTORY) continue;
|
|
79
79
|
|
|
80
80
|
const fullPath = path.join(directory, item);
|
|
81
81
|
const stat = fs.statSync(fullPath);
|
|
82
82
|
|
|
83
83
|
if (stat.isDirectory()) {
|
|
84
84
|
walk(fullPath);
|
|
85
|
-
} else if (item.endsWith(
|
|
85
|
+
} else if (item.endsWith(PATH.JAVASCRIPT_FILE) && !item.endsWith(PATH.TEST_FILE)) {
|
|
86
86
|
files.push(fullPath);
|
|
87
87
|
}
|
|
88
88
|
}
|
package/constants.js
CHANGED
|
@@ -25,4 +25,26 @@ export const COLORS = {
|
|
|
25
25
|
cyan: '\x1b[36m',
|
|
26
26
|
gray: '\x1b[90m',
|
|
27
27
|
bold: '\x1b[1m'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const MOCK = {
|
|
31
|
+
STORE_NAME: 'mockStore',
|
|
32
|
+
STORE_PATH : '@dannysir/js-te/src/mock/store.js'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const BABEL = {
|
|
36
|
+
MODULE: 'module',
|
|
37
|
+
CONST: 'const',
|
|
38
|
+
HAS: 'has',
|
|
39
|
+
GET: 'get',
|
|
40
|
+
PERIOD: '.',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const PATH = {
|
|
44
|
+
NODE_MODULES: 'node_modules',
|
|
45
|
+
TEST_DIRECTORY: 'test',
|
|
46
|
+
TEST_FILE: '.test.js',
|
|
47
|
+
JAVASCRIPT_FILE: '.js',
|
|
48
|
+
BIN: 'bin',
|
|
49
|
+
|
|
28
50
|
};
|
package/index.js
CHANGED
|
@@ -6,15 +6,19 @@ import {
|
|
|
6
6
|
import {Tests} from "./src/tests.js";
|
|
7
7
|
import {green, red} from "./utils/consoleColor.js";
|
|
8
8
|
import {getTestResultMsg} from "./utils/makeMessage.js";
|
|
9
|
+
import {clearAllMocks} from "./src/mock/store.js";
|
|
9
10
|
|
|
10
11
|
const tests = new Tests();
|
|
11
12
|
|
|
12
13
|
export { mock, clearAllMocks, unmock, isMocked } from './src/mock/store.js';
|
|
13
14
|
|
|
14
15
|
export const test = (description, fn) => tests.test(description, fn);
|
|
16
|
+
test.each = (cases) => tests.testEach(cases);
|
|
15
17
|
|
|
16
18
|
export const describe = (suiteName, fn) => tests.describe(suiteName, fn);
|
|
17
19
|
|
|
20
|
+
export const beforeEach = (fn) => tests.beforeEach(fn);
|
|
21
|
+
|
|
18
22
|
export const expect = (actual) => {
|
|
19
23
|
let value = actual;
|
|
20
24
|
|
|
@@ -73,6 +77,7 @@ export const run = async () => {
|
|
|
73
77
|
const directoryString = green(CHECK) + (test.path === '' ? EMPTY : test.path + DIRECTORY_DELIMITER) + test.description
|
|
74
78
|
console.log(directoryString);
|
|
75
79
|
passed++;
|
|
80
|
+
clearAllMocks();
|
|
76
81
|
} catch (error) {
|
|
77
82
|
const errorDirectory = red(CROSS) + test.path + test.description
|
|
78
83
|
console.log(errorDirectory);
|
package/package.json
CHANGED
package/src/mock/store.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const mockStore = new Map();
|
|
2
2
|
|
|
3
3
|
export function clearAllMocks() {
|
|
4
|
-
|
|
4
|
+
mockStore.clear();
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function mock(modulePath, mockExports) {
|
|
8
|
-
|
|
8
|
+
mockStore.set(modulePath, mockExports);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function unmock(modulePath) {
|
|
12
|
-
|
|
12
|
+
mockStore.delete(modulePath);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function isMocked(modulePath) {
|
|
16
|
-
return
|
|
16
|
+
return mockStore.has(modulePath);
|
|
17
17
|
}
|
package/src/tests.js
CHANGED
|
@@ -3,22 +3,45 @@ import {DIRECTORY_DELIMITER} from "../constants.js";
|
|
|
3
3
|
export class Tests {
|
|
4
4
|
#tests = [];
|
|
5
5
|
#testDepth = [];
|
|
6
|
+
#beforeEachArr = [];
|
|
6
7
|
|
|
7
8
|
describe(str, fn) {
|
|
8
9
|
this.#testDepth.push(str);
|
|
10
|
+
const prevLength = this.#beforeEachArr.length;
|
|
9
11
|
fn();
|
|
12
|
+
this.#beforeEachArr.length = prevLength;
|
|
10
13
|
this.#testDepth.pop();
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
test(description, fn) {
|
|
17
|
+
const beforeEachHooks = [...this.#beforeEachArr];
|
|
18
|
+
|
|
14
19
|
const testObj = {
|
|
15
20
|
description,
|
|
16
|
-
fn
|
|
21
|
+
fn: async () => {
|
|
22
|
+
for (const hook of beforeEachHooks) {
|
|
23
|
+
await hook();
|
|
24
|
+
}
|
|
25
|
+
await fn();
|
|
26
|
+
},
|
|
17
27
|
path: this.#testDepth.join(DIRECTORY_DELIMITER),
|
|
18
28
|
}
|
|
19
29
|
this.#tests.push(testObj);
|
|
20
30
|
}
|
|
21
31
|
|
|
32
|
+
testEach(cases) {
|
|
33
|
+
return (description, fn) => {
|
|
34
|
+
cases.forEach(testCase => {
|
|
35
|
+
const args = Array.isArray(testCase) ? testCase : [testCase];
|
|
36
|
+
this.test(this.#formatDescription(args, description), () => fn(...args));
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeEach(fn) {
|
|
42
|
+
this.#beforeEachArr.push(fn);
|
|
43
|
+
}
|
|
44
|
+
|
|
22
45
|
getTests() {
|
|
23
46
|
return [...this.#tests];
|
|
24
47
|
}
|
|
@@ -26,5 +49,24 @@ export class Tests {
|
|
|
26
49
|
clearTests() {
|
|
27
50
|
this.#tests = [];
|
|
28
51
|
this.#testDepth = [];
|
|
52
|
+
this.#beforeEachArr = [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#formatDescription(args, description) {
|
|
56
|
+
let argIndex = 0;
|
|
57
|
+
return description.replace(/%([so])/g, (match, type) => {
|
|
58
|
+
if (argIndex >= args.length) return match;
|
|
59
|
+
|
|
60
|
+
const arg = args[argIndex++];
|
|
61
|
+
|
|
62
|
+
switch (type) {
|
|
63
|
+
case 's':
|
|
64
|
+
return arg;
|
|
65
|
+
case 'o':
|
|
66
|
+
return JSON.stringify(arg);
|
|
67
|
+
default:
|
|
68
|
+
return match;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
29
71
|
}
|
|
30
72
|
}
|