@dannysir/js-te 0.3.0 → 0.3.2
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 +13 -7
- package/bin/cli.js +14 -62
- package/dist/index.cjs +166 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +166 -102
- package/dist/index.mjs.map +1 -1
- package/index.js +90 -0
- package/package.json +4 -6
- package/src/babelPlugins/babelCollectMocks.js +35 -0
- package/src/babelPlugins/babelTransformImport.js +184 -0
- package/src/babelPlugins/utils/getModuleInfo.js +21 -0
- package/src/babelPlugins/utils/pathHelper.js +56 -0
- package/src/babelPlugins/utils/wrapperCreator.js +121 -0
- package/src/cli/runTests.js +22 -0
- package/src/cli/setupEnvironment.js +32 -0
- package/src/cli/setupFiles.js +15 -0
- package/src/constants/babel.js +12 -0
- package/src/constants/index.js +29 -0
- package/src/constants/view.js +9 -0
- package/src/matchers.js +1 -2
- package/src/mock/collectMocks.js +2 -3
- package/src/mock/store.js +4 -4
- package/src/testManager.js +31 -12
- package/{utils → src/utils}/consoleColor.js +2 -2
- package/{bin → src}/utils/findFiles.js +16 -5
- package/{utils → src/utils}/formatString.js +15 -5
- package/src/utils/messages.js +26 -0
- package/{bin → src}/utils/transformFiles.js +9 -4
- package/babelTransformImport.js +0 -317
- package/constants.js +0 -46
- package/src/testRunner.js +0 -28
- package/utils/makeMessage.js +0 -14
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
|
|
2
|
+
import {findAbsolutePath, shouldTransform} from "./utils/pathHelper.js";
|
|
3
|
+
import {getModuleInfo} from "./utils/getModuleInfo.js";
|
|
4
|
+
import {createNamespaceWrapper, createOriginalDeclaration, createWrapperFunction} from "./utils/wrapperCreator.js";
|
|
5
|
+
import {BABEL, MOCK} from "../constants/babel.js";
|
|
6
|
+
|
|
7
|
+
export const babelTransformImport = (mockedPaths = null) => {
|
|
8
|
+
return ({types: t}) => {
|
|
9
|
+
return {
|
|
10
|
+
visitor: {
|
|
11
|
+
Program(path) {
|
|
12
|
+
const mockStoreDeclaration = t.VariableDeclaration('const', [
|
|
13
|
+
t.VariableDeclarator(
|
|
14
|
+
t.Identifier(MOCK.STORE_NAME),
|
|
15
|
+
t.MemberExpression(
|
|
16
|
+
t.Identifier('global'),
|
|
17
|
+
t.Identifier(MOCK.STORE_NAME)
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
]);
|
|
21
|
+
path.node.body.unshift(mockStoreDeclaration);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
ImportDeclaration(nodePath, state) {
|
|
25
|
+
const source = nodePath.node.source.value;
|
|
26
|
+
|
|
27
|
+
if (source === MOCK.STORE_PATH) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const currentFilePath = state.filename || process.cwd();
|
|
32
|
+
const absolutePath = findAbsolutePath(source, currentFilePath);
|
|
33
|
+
|
|
34
|
+
if (!shouldTransform(absolutePath, mockedPaths)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const specifiers = nodePath.node.specifiers;
|
|
39
|
+
const originalVarName = nodePath.scope.generateUidIdentifier('original');
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
|
|
43
|
+
const _original = await import('./random.js');
|
|
44
|
+
const random = (...args) => {
|
|
45
|
+
const module = mockStore.has('/path/to/random.js')
|
|
46
|
+
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
47
|
+
: _original;
|
|
48
|
+
return module.random(...args);
|
|
49
|
+
};
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
const originalDeclaration = createOriginalDeclaration(
|
|
53
|
+
t,
|
|
54
|
+
originalVarName,
|
|
55
|
+
source,
|
|
56
|
+
false
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const wrapperDeclarations = specifiers.map(spec => {
|
|
60
|
+
if (t.isImportDefaultSpecifier(spec)) {
|
|
61
|
+
return createWrapperFunction(
|
|
62
|
+
t,
|
|
63
|
+
'default',
|
|
64
|
+
spec.local.name,
|
|
65
|
+
absolutePath,
|
|
66
|
+
originalVarName
|
|
67
|
+
);
|
|
68
|
+
} else if (t.isImportNamespaceSpecifier(spec)) {
|
|
69
|
+
return createNamespaceWrapper(
|
|
70
|
+
t,
|
|
71
|
+
spec.local.name,
|
|
72
|
+
absolutePath,
|
|
73
|
+
originalVarName
|
|
74
|
+
);
|
|
75
|
+
} else {
|
|
76
|
+
return createWrapperFunction(
|
|
77
|
+
t,
|
|
78
|
+
spec.imported.name,
|
|
79
|
+
spec.local.name,
|
|
80
|
+
absolutePath,
|
|
81
|
+
originalVarName
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const wrapperDeclaration = t.variableDeclaration(BABEL.CONST, wrapperDeclarations);
|
|
87
|
+
|
|
88
|
+
nodePath.replaceWithMultiple([originalDeclaration, wrapperDeclaration]);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
VariableDeclaration(nodePath, state) {
|
|
92
|
+
const declarations = nodePath.node.declarations;
|
|
93
|
+
|
|
94
|
+
if (!declarations || declarations.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (nodePath.node._transformed) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const newDeclarations = [];
|
|
103
|
+
let hasTransformation = false;
|
|
104
|
+
|
|
105
|
+
for (const declarator of declarations) {
|
|
106
|
+
const requireInfo = getModuleInfo(t, declarator);
|
|
107
|
+
|
|
108
|
+
if (!requireInfo) {
|
|
109
|
+
newDeclarations.push(declarator);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const {source} = requireInfo;
|
|
114
|
+
const currentFilePath = state.filename || process.cwd();
|
|
115
|
+
const absolutePath = findAbsolutePath(source, currentFilePath);
|
|
116
|
+
|
|
117
|
+
if (!shouldTransform(absolutePath, mockedPaths)) {
|
|
118
|
+
newDeclarations.push(declarator);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
hasTransformation = true;
|
|
123
|
+
const originalVar = nodePath.scope.generateUidIdentifier('original');
|
|
124
|
+
|
|
125
|
+
/*
|
|
126
|
+
|
|
127
|
+
const _original = require('./random');
|
|
128
|
+
const random = (...args) => {
|
|
129
|
+
const module = mockStore.has('/path/to/random.js')
|
|
130
|
+
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
131
|
+
: _original;
|
|
132
|
+
return module.random(...args);
|
|
133
|
+
};
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
newDeclarations.push(
|
|
137
|
+
t.variableDeclarator(
|
|
138
|
+
originalVar,
|
|
139
|
+
t.callExpression(
|
|
140
|
+
t.identifier('require'),
|
|
141
|
+
[t.stringLiteral(source)]
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (t.isObjectPattern(declarator.id)) {
|
|
147
|
+
const properties = declarator.id.properties;
|
|
148
|
+
|
|
149
|
+
properties.forEach(prop => {
|
|
150
|
+
const key = prop.key.name;
|
|
151
|
+
const localName = prop.value.name;
|
|
152
|
+
|
|
153
|
+
newDeclarations.push(
|
|
154
|
+
createWrapperFunction(
|
|
155
|
+
t,
|
|
156
|
+
key,
|
|
157
|
+
localName,
|
|
158
|
+
absolutePath,
|
|
159
|
+
originalVar
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
newDeclarations.push(
|
|
165
|
+
createWrapperFunction(
|
|
166
|
+
t,
|
|
167
|
+
'default',
|
|
168
|
+
declarator.id.name,
|
|
169
|
+
absolutePath,
|
|
170
|
+
originalVar
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (hasTransformation) {
|
|
177
|
+
nodePath.node.declarations = newDeclarations;
|
|
178
|
+
nodePath.node._transformed = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* require 호출 정보 추출
|
|
3
|
+
* @param {Object} t - Babel types
|
|
4
|
+
* @param {Object} declarator - VariableDeclarator 노드
|
|
5
|
+
* @returns {Object|null} require 정보 또는 null
|
|
6
|
+
*/
|
|
7
|
+
export const getModuleInfo = (t, declarator) => {
|
|
8
|
+
const init = declarator.init;
|
|
9
|
+
|
|
10
|
+
if (!init ||
|
|
11
|
+
!t.isCallExpression(init) ||
|
|
12
|
+
!t.isIdentifier(init.callee, {name: 'require'}) ||
|
|
13
|
+
!init.arguments[0] ||
|
|
14
|
+
!t.isStringLiteral(init.arguments[0])) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
source: init.arguments[0].value
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
import {BABEL} from "../../constants/babel.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 상대/절대 경로를 절대 경로로 변환
|
|
7
|
+
* @param {string} source - import/require 경로
|
|
8
|
+
* @param {string} currentFilePath - 현재 파일의 경로
|
|
9
|
+
* @returns {string} 절대 경로
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // 상대 경로인 경우:
|
|
13
|
+
* resolveAbsolutePath('./random.js', '/Users/san/project/test.js')
|
|
14
|
+
* // 반환: '/Users/san/project/random.js'
|
|
15
|
+
*
|
|
16
|
+
* // 절대 경로인 경우:
|
|
17
|
+
* resolveAbsolutePath('/Users/san/lib/utils.js', '/Users/san/project/test.js')
|
|
18
|
+
* // 반환: '/Users/san/lib/utils.js'
|
|
19
|
+
*/
|
|
20
|
+
export const findAbsolutePath = (source, currentFilePath) => {
|
|
21
|
+
const currentDir = path.dirname(currentFilePath || process.cwd());
|
|
22
|
+
|
|
23
|
+
if (source.startsWith(BABEL.PERIOD)) {
|
|
24
|
+
return path.resolve(currentDir, source);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return source;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 해당 경로가 변환 대상인지 확인
|
|
32
|
+
* @param {string} absolutePath - 절대 경로
|
|
33
|
+
* @param {Set<string>|null} mockedPaths - mock된 경로들
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // mockedPaths가 null인 경우:
|
|
38
|
+
* shouldTransform('/Users/san/project/random.js', null)
|
|
39
|
+
* // 반환: true (모든 파일 변환)
|
|
40
|
+
*
|
|
41
|
+
* // mockedPaths에 포함된 경우:
|
|
42
|
+
* const mocked = new Set(['/Users/san/project/random.js']);
|
|
43
|
+
* shouldTransform('/Users/san/project/random.js', mocked)
|
|
44
|
+
* // 반환: true (변환 대상)
|
|
45
|
+
*
|
|
46
|
+
* // mockedPaths에 없는 경우:
|
|
47
|
+
* shouldTransform('/Users/san/project/game.js', mocked)
|
|
48
|
+
* // 반환: false (변환하지 않음)
|
|
49
|
+
*/
|
|
50
|
+
export const shouldTransform = (absolutePath, mockedPaths) => {
|
|
51
|
+
if (!mockedPaths) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return mockedPaths.has(absolutePath);
|
|
56
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
|
|
2
|
+
import {BABEL, MOCK} from "../../constants/babel.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mock wrapper 함수를 생성하는 헬퍼
|
|
6
|
+
* @param {Object} t - Babel types
|
|
7
|
+
* @param {string} importedName - import된 함수명
|
|
8
|
+
* @param {string} localName - 로컬 변수명
|
|
9
|
+
* @param {string} absolutePath - 모듈의 절대 경로
|
|
10
|
+
* @param {Object} originalVarName - 원본 모듈을 가리키는 변수
|
|
11
|
+
* @returns {Object} 바벨 변수 선언
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // 반환 결과 (코드로 표현하면):
|
|
15
|
+
* const random = (...args) => {
|
|
16
|
+
* const module = mockStore.has('/path/to/random.js')
|
|
17
|
+
* ? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
18
|
+
* : _original;
|
|
19
|
+
* return module.random(...args);
|
|
20
|
+
* };
|
|
21
|
+
*/
|
|
22
|
+
export const createWrapperFunction = (t, importedName, localName, absolutePath, originalVarName) => {
|
|
23
|
+
return t.variableDeclarator(
|
|
24
|
+
t.identifier(localName),
|
|
25
|
+
t.arrowFunctionExpression(
|
|
26
|
+
[t.restElement(t.identifier('args'))],
|
|
27
|
+
t.blockStatement([
|
|
28
|
+
t.variableDeclaration(BABEL.CONST, [
|
|
29
|
+
t.variableDeclarator(
|
|
30
|
+
t.identifier(BABEL.MODULE),
|
|
31
|
+
createConditionalModule(t, absolutePath, originalVarName)
|
|
32
|
+
)
|
|
33
|
+
]),
|
|
34
|
+
t.returnStatement(
|
|
35
|
+
t.callExpression(
|
|
36
|
+
t.memberExpression(t.identifier(BABEL.MODULE), t.identifier(importedName)),
|
|
37
|
+
[t.spreadElement(t.identifier('args'))]
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
])
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Namespace import용 wrapper 생성
|
|
47
|
+
* @param {Object} t - Babel types
|
|
48
|
+
* @param {string} localName - 로컬 변수명
|
|
49
|
+
* @param {string} absolutePath - 모듈의 절대 경로
|
|
50
|
+
* @param {Object} originalVarName - 원본 모듈 변수
|
|
51
|
+
* @returns {Object} 바벨 변수 선언
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* // 반환 결과 (코드로 표현하면):
|
|
55
|
+
* const utils = mockStore.has('/path/to/utils.js')
|
|
56
|
+
* ? { ..._original, ...mockStore.get('/path/to/utils.js') }
|
|
57
|
+
* : _original;
|
|
58
|
+
*/
|
|
59
|
+
export const createNamespaceWrapper = (t, localName, absolutePath, originalVarName) => {
|
|
60
|
+
return t.variableDeclarator(
|
|
61
|
+
t.identifier(localName),
|
|
62
|
+
createConditionalModule(t, absolutePath, originalVarName)
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 원본 모듈 선언문 생성
|
|
68
|
+
* @param {Object} t - Babel types
|
|
69
|
+
* @param {Object} originalVarName - 원본 모듈 변수
|
|
70
|
+
* @param {string} source - import 경로
|
|
71
|
+
* @param {boolean} isRequire - require 방식인지 여부
|
|
72
|
+
* @returns {Object} 바벨 변수 선언
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* // isRequire = false인 경우 (코드로 표현하면):
|
|
76
|
+
* const _original = await import('./random.js');
|
|
77
|
+
*
|
|
78
|
+
* // isRequire = true인 경우 (코드로 표현하면):
|
|
79
|
+
* const _original = require('./random.js');
|
|
80
|
+
*/
|
|
81
|
+
export const createOriginalDeclaration = (t, originalVarName, source, isRequire = false) => {
|
|
82
|
+
const init = isRequire
|
|
83
|
+
? t.callExpression(t.identifier('require'), [t.stringLiteral(source)])
|
|
84
|
+
: t.awaitExpression(t.importExpression(t.stringLiteral(source)));
|
|
85
|
+
|
|
86
|
+
return t.variableDeclaration(BABEL.CONST, [
|
|
87
|
+
t.variableDeclarator(originalVarName, init)
|
|
88
|
+
]);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 조건부 모듈 표현식 생성 (mockStore 체크)
|
|
93
|
+
* @param {Object} t - Babel types
|
|
94
|
+
* @param {string} absolutePath - 모듈의 절대 경로
|
|
95
|
+
* @param {Object} originalVarName - 원본 모듈 변수
|
|
96
|
+
* @returns {Object} 바벨 조건문
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // 반환 결과 (코드로 표현하면):
|
|
100
|
+
* mockStore.has('/path/to/random.js')
|
|
101
|
+
* ? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
102
|
+
* : _original
|
|
103
|
+
*/
|
|
104
|
+
const createConditionalModule = (t, absolutePath, originalVarName) => {
|
|
105
|
+
return t.conditionalExpression(
|
|
106
|
+
t.callExpression(
|
|
107
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.HAS)),
|
|
108
|
+
[t.stringLiteral(absolutePath)]
|
|
109
|
+
),
|
|
110
|
+
t.objectExpression([
|
|
111
|
+
t.spreadElement(originalVarName),
|
|
112
|
+
t.spreadElement(
|
|
113
|
+
t.callExpression(
|
|
114
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.GET)),
|
|
115
|
+
[t.stringLiteral(absolutePath)]
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
]),
|
|
119
|
+
originalVarName
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import {getFilePath} from "../utils/messages.js";
|
|
3
|
+
import {transformFiles} from "../utils/transformFiles.js";
|
|
4
|
+
import {NUM} from "../constants/index.js";
|
|
5
|
+
|
|
6
|
+
export const runTests = async (jsTe, mockedPaths, testFiles) => {
|
|
7
|
+
let totalPassed = NUM.ZERO;
|
|
8
|
+
let totalFailed = NUM.ZERO;
|
|
9
|
+
|
|
10
|
+
for (const file of testFiles) {
|
|
11
|
+
console.log(getFilePath(file));
|
|
12
|
+
transformFiles(file, mockedPaths);
|
|
13
|
+
|
|
14
|
+
await import(path.resolve(file));
|
|
15
|
+
|
|
16
|
+
const {passed, failed} = await jsTe.run();
|
|
17
|
+
totalPassed += passed;
|
|
18
|
+
totalFailed += failed;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {totalPassed, totalFailed}
|
|
22
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import {MODULE_TYPE, NUM, PATH} from "../constants/index.js";
|
|
4
|
+
|
|
5
|
+
const getUserModuleType = () => {
|
|
6
|
+
try {
|
|
7
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
8
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
9
|
+
return pkg.type === MODULE_TYPE.MODULE ? MODULE_TYPE.ESM : MODULE_TYPE.CJS;
|
|
10
|
+
} catch {
|
|
11
|
+
return MODULE_TYPE.CJS;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const setupEnvironment = async () => {
|
|
16
|
+
const moduleType = getUserModuleType();
|
|
17
|
+
|
|
18
|
+
let jsTe;
|
|
19
|
+
if (moduleType === MODULE_TYPE.ESM) {
|
|
20
|
+
jsTe = await import(PATH.DANNYSIR_JS_TE);
|
|
21
|
+
} else {
|
|
22
|
+
const {createRequire} = await import(MODULE_TYPE.MODULE);
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
jsTe = require(PATH.DANNYSIR_JS_TE);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Object.keys(jsTe).forEach(key => {
|
|
28
|
+
global[key] = jsTe[key];
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return jsTe
|
|
32
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {findAllSourceFiles, findTestFiles} from "../utils/findFiles.js";
|
|
2
|
+
import {getFileCountString} from "../utils/messages.js";
|
|
3
|
+
import {collectMockedPaths} from "../mock/collectMocks.js";
|
|
4
|
+
import {transformFiles} from "../utils/transformFiles.js";
|
|
5
|
+
|
|
6
|
+
export const setupFiles = () => {
|
|
7
|
+
const testFiles = findTestFiles(process.cwd());
|
|
8
|
+
const mockedPaths = collectMockedPaths(testFiles);
|
|
9
|
+
const sourceFiles = findAllSourceFiles(process.cwd());
|
|
10
|
+
|
|
11
|
+
for (const file of sourceFiles) {
|
|
12
|
+
transformFiles(file, mockedPaths);
|
|
13
|
+
}
|
|
14
|
+
return {mockedPaths, testFiles}
|
|
15
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const PATH = {
|
|
2
|
+
NODE_MODULES: 'node_modules',
|
|
3
|
+
TEST_DIRECTORY: 'test',
|
|
4
|
+
TEST_FILE: '.test.js',
|
|
5
|
+
JAVASCRIPT_FILE: '.js',
|
|
6
|
+
BIN: 'bin',
|
|
7
|
+
DIST: 'dist',
|
|
8
|
+
DANNYSIR_JS_TE: '@dannysir/js-te',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const RESULT_MSG = {
|
|
12
|
+
TESTS: 'Tests: ',
|
|
13
|
+
TOTAL: 'Total Result: ',
|
|
14
|
+
CHECK: '✓ ',
|
|
15
|
+
CROSS: '✗ ',
|
|
16
|
+
DIRECTORY_DELIMITER: ' > ',
|
|
17
|
+
EMPTY: '',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const NUM = {
|
|
21
|
+
ZERO: 0,
|
|
22
|
+
ONE: 1,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const MODULE_TYPE = {
|
|
26
|
+
MODULE: 'module',
|
|
27
|
+
ESM: 'esm',
|
|
28
|
+
CJS: 'cjs',
|
|
29
|
+
};
|
package/src/matchers.js
CHANGED
package/src/mock/collectMocks.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import {transformSync} from "@babel/core";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import {createMockCollectorPlugin} from "../babelPlugins/babelCollectMocks.js";
|
|
4
|
+
import {BABEL} from "../constants/babel.js";
|
|
5
5
|
|
|
6
6
|
export const collectMockedPaths = (testFiles) => {
|
|
7
7
|
const mockedPaths = new Set();
|
|
@@ -19,7 +19,6 @@ export const collectMockedPaths = (testFiles) => {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
} catch (error) {
|
|
22
|
-
// 파싱 에러는 무시 (어차피 테스트 실행 시 에러 발생)
|
|
23
22
|
console.warn(`Warning: Failed to scan ${testFile} for mocks`);
|
|
24
23
|
}
|
|
25
24
|
}
|
package/src/mock/store.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
export const mockStore = new Map();
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export const clearAllMocks = () => {
|
|
4
4
|
mockStore.clear();
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export const mock = (modulePath, mockExports) => {
|
|
8
8
|
mockStore.set(modulePath, mockExports);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export const unmock = (modulePath) => {
|
|
12
12
|
mockStore.delete(modulePath);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export
|
|
15
|
+
export const isMocked = (modulePath) => {
|
|
16
16
|
return mockStore.has(modulePath);
|
|
17
17
|
}
|
package/src/testManager.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {formatFailureMessage, formatSuccessMessage, getMatcherForReplace, placeHolder} from "./utils/formatString.js";
|
|
2
|
+
import {clearAllMocks} from "./mock/store.js";
|
|
3
|
+
import {NUM, RESULT_MSG} from "./constants/index.js";
|
|
4
|
+
import {getTestResultMsg} from "./utils/messages.js";
|
|
2
5
|
|
|
3
6
|
class TestManager {
|
|
4
7
|
#tests = [];
|
|
@@ -24,7 +27,7 @@ class TestManager {
|
|
|
24
27
|
}
|
|
25
28
|
await fn();
|
|
26
29
|
},
|
|
27
|
-
path: this.#testDepth.join(DIRECTORY_DELIMITER),
|
|
30
|
+
path: this.#testDepth.join(RESULT_MSG.DIRECTORY_DELIMITER),
|
|
28
31
|
}
|
|
29
32
|
this.#tests.push(testObj);
|
|
30
33
|
}
|
|
@@ -52,21 +55,37 @@ class TestManager {
|
|
|
52
55
|
this.#beforeEachArr = [];
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
async run() {
|
|
59
|
+
let passed = NUM.ZERO;
|
|
60
|
+
let failed = NUM.ZERO;
|
|
61
|
+
|
|
62
|
+
for (const test of testManager.getTests()) {
|
|
63
|
+
try {
|
|
64
|
+
await test.fn();
|
|
65
|
+
console.log(formatSuccessMessage(test));
|
|
66
|
+
passed++;
|
|
67
|
+
clearAllMocks();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.log(formatFailureMessage(test, error));
|
|
70
|
+
failed++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(getTestResultMsg(RESULT_MSG.TESTS, passed, failed));
|
|
75
|
+
|
|
76
|
+
testManager.clearTests();
|
|
77
|
+
|
|
78
|
+
return {passed, failed};
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
#formatDescription(args, description) {
|
|
56
82
|
let argIndex = 0;
|
|
57
|
-
return description.replace(
|
|
83
|
+
return description.replace(getMatcherForReplace(), (match, type) => {
|
|
58
84
|
if (argIndex >= args.length) return match;
|
|
59
85
|
|
|
60
|
-
const
|
|
86
|
+
const formatter = placeHolder[type];
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
case 's':
|
|
64
|
-
return arg;
|
|
65
|
-
case 'o':
|
|
66
|
-
return JSON.stringify(arg);
|
|
67
|
-
default:
|
|
68
|
-
return match;
|
|
69
|
-
}
|
|
88
|
+
return formatter ? formatter(args[argIndex++]) : match;
|
|
70
89
|
});
|
|
71
90
|
}
|
|
72
91
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {COLORS} from "../constants.js";
|
|
1
|
+
import {COLORS} from "../constants/view.js";
|
|
2
2
|
|
|
3
3
|
export const colorize = (text, color) => `${color}${text}${COLORS.reset}`;
|
|
4
4
|
|
|
5
5
|
export const green = (text) => colorize(text, COLORS.green + COLORS.bold);
|
|
6
6
|
export const red = (text) => colorize(text, COLORS.red + COLORS.bold);
|
|
7
7
|
export const bold = (text) => colorize(text, COLORS.bold);
|
|
8
|
-
export const yellow = (text) => colorize(text, COLORS.yellow + COLORS.bold);
|
|
8
|
+
export const yellow = (text) => colorize(text, COLORS.yellow + COLORS.bold);
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {PATH} from "../../constants.js";
|
|
4
3
|
|
|
4
|
+
|
|
5
|
+
import {PATH} from "../constants/index.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 테스트 파일을 찾는 로직입니다.
|
|
9
|
+
* @param {string} dir
|
|
10
|
+
* @returns {string[]}
|
|
11
|
+
*/
|
|
5
12
|
export const findTestFiles = (dir) => {
|
|
6
13
|
const files = [];
|
|
7
14
|
|
|
8
|
-
const walk = (directory
|
|
15
|
+
const walk = (directory) => {
|
|
9
16
|
const items = fs.readdirSync(directory);
|
|
10
17
|
const dirName = path.basename(directory);
|
|
11
|
-
|
|
12
|
-
const isTestDir = dirName === PATH.TEST_DIRECTORY || inTestDir;
|
|
18
|
+
const isTestDir = dirName === PATH.TEST_DIRECTORY;
|
|
13
19
|
|
|
14
20
|
for (const item of items) {
|
|
15
21
|
if (item === PATH.NODE_MODULES) continue;
|
|
@@ -31,6 +37,11 @@ export const findTestFiles = (dir) => {
|
|
|
31
37
|
return files;
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
/**
|
|
41
|
+
* 테스트 파일을 포함한 전체 파일을 찾는 함수입니다.
|
|
42
|
+
* @param {string} dir
|
|
43
|
+
* @returns {string[]}
|
|
44
|
+
*/
|
|
34
45
|
export const findAllSourceFiles = (dir) => {
|
|
35
46
|
const files = [];
|
|
36
47
|
|
|
@@ -53,4 +64,4 @@ export const findAllSourceFiles = (dir) => {
|
|
|
53
64
|
|
|
54
65
|
walk(dir);
|
|
55
66
|
return files;
|
|
56
|
-
}
|
|
67
|
+
};
|