puty 0.0.1 → 0.0.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 +59 -0
- package/package.json +4 -1
- package/src/puty.js +139 -25
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@ It should run all the tests in the file.
|
|
|
26
26
|
|
|
27
27
|
## Usage
|
|
28
28
|
|
|
29
|
+
### Testing Functions
|
|
30
|
+
|
|
29
31
|
```yaml
|
|
30
32
|
file: './math.js'
|
|
31
33
|
group: math
|
|
@@ -61,3 +63,60 @@ in:
|
|
|
61
63
|
- 2
|
|
62
64
|
out: 3
|
|
63
65
|
```
|
|
66
|
+
|
|
67
|
+
### Testing Classes
|
|
68
|
+
|
|
69
|
+
Puty also supports testing classes with method calls and state assertions:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
file: './calculator.js'
|
|
73
|
+
group: Calculator
|
|
74
|
+
suites: [basic-operations]
|
|
75
|
+
---
|
|
76
|
+
suite: basic-operations
|
|
77
|
+
mode: 'class'
|
|
78
|
+
exportName: default
|
|
79
|
+
constructorArgs: [10] # Initial value
|
|
80
|
+
---
|
|
81
|
+
case: add and multiply operations
|
|
82
|
+
executions:
|
|
83
|
+
- method: add
|
|
84
|
+
in: [5]
|
|
85
|
+
out: 15
|
|
86
|
+
asserts:
|
|
87
|
+
- property: value
|
|
88
|
+
op: eq
|
|
89
|
+
value: 15
|
|
90
|
+
- method: multiply
|
|
91
|
+
in: [2]
|
|
92
|
+
out: 30
|
|
93
|
+
asserts:
|
|
94
|
+
- property: value
|
|
95
|
+
op: eq
|
|
96
|
+
value: 30
|
|
97
|
+
- method: getValue
|
|
98
|
+
in: []
|
|
99
|
+
out: 30
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Class Test Structure
|
|
103
|
+
|
|
104
|
+
- `mode: 'class'` - Indicates this suite tests a class
|
|
105
|
+
- `constructorArgs` - Arguments passed to the class constructor
|
|
106
|
+
- `executions` - Array of method calls to execute in sequence
|
|
107
|
+
- `method` - Name of the method to call
|
|
108
|
+
- `in` - Arguments to pass to the method
|
|
109
|
+
- `out` - Expected return value (optional)
|
|
110
|
+
- `asserts` - Assertions to run after the method call
|
|
111
|
+
- Property assertions: Check instance properties
|
|
112
|
+
- Method assertions: Call methods and check their return values
|
|
113
|
+
|
|
114
|
+
### Error Testing
|
|
115
|
+
|
|
116
|
+
You can test that functions or methods throw expected errors:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
case: divide by zero
|
|
120
|
+
in: [10, 0]
|
|
121
|
+
throws: "Division by zero"
|
|
122
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puty",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "A tooling function to test javascript functions and classes.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,5 +19,8 @@
|
|
|
19
19
|
"lint": "bunx prettier src tests -c",
|
|
20
20
|
"lint:fix": "bunx prettier src tests -w",
|
|
21
21
|
"build": "bun run esbuild.js"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"vitest": "^3.2.1"
|
|
22
25
|
}
|
|
23
26
|
}
|
package/src/puty.js
CHANGED
|
@@ -36,12 +36,27 @@ export const parseYamlDocuments = (yamlContent) => {
|
|
|
36
36
|
exportName: doc.exportName || doc.suite,
|
|
37
37
|
cases: [],
|
|
38
38
|
};
|
|
39
|
+
// Only add mode and constructorArgs if mode is explicitly 'class'
|
|
40
|
+
if (doc.mode === "class") {
|
|
41
|
+
currentSuite.mode = "class";
|
|
42
|
+
currentSuite.constructorArgs = doc.constructorArgs || [];
|
|
43
|
+
}
|
|
39
44
|
} else if (doc.case && currentSuite) {
|
|
40
|
-
|
|
45
|
+
const testCase = {
|
|
41
46
|
name: doc.case,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (currentSuite.mode === "class") {
|
|
50
|
+
testCase.executions = doc.executions || [];
|
|
51
|
+
} else {
|
|
52
|
+
testCase.in = doc.in || [];
|
|
53
|
+
testCase.out = doc.out;
|
|
54
|
+
if (doc.throws) {
|
|
55
|
+
testCase.throws = doc.throws;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
currentSuite.cases.push(testCase);
|
|
45
60
|
}
|
|
46
61
|
}
|
|
47
62
|
|
|
@@ -64,28 +79,111 @@ const setupTestSuite = (testConfig) => {
|
|
|
64
79
|
describe(group, () => {
|
|
65
80
|
for (const suite of suites) {
|
|
66
81
|
describe(suite.name, () => {
|
|
67
|
-
const { cases } = suite;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
functionUnderTest,
|
|
74
|
-
} = testCase;
|
|
75
|
-
test(name, () => {
|
|
76
|
-
const out = functionUnderTest(...(inArg || []));
|
|
77
|
-
if (out instanceof Error) {
|
|
78
|
-
expect(out.message).toEqual(out.message);
|
|
79
|
-
} else {
|
|
80
|
-
expect(out).toEqual(expectedOut);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
82
|
+
const { cases, mode } = suite;
|
|
83
|
+
|
|
84
|
+
if (mode === "class") {
|
|
85
|
+
setupClassTests(suite);
|
|
86
|
+
} else {
|
|
87
|
+
setupFunctionTests(suite);
|
|
83
88
|
}
|
|
84
89
|
});
|
|
85
90
|
}
|
|
86
91
|
});
|
|
87
92
|
};
|
|
88
93
|
|
|
94
|
+
const setupFunctionTests = (suite) => {
|
|
95
|
+
const { cases } = suite;
|
|
96
|
+
for (const testCase of cases) {
|
|
97
|
+
const {
|
|
98
|
+
name,
|
|
99
|
+
in: inArg,
|
|
100
|
+
out: expectedOut,
|
|
101
|
+
functionUnderTest,
|
|
102
|
+
throws,
|
|
103
|
+
} = testCase;
|
|
104
|
+
test(name, () => {
|
|
105
|
+
if (!functionUnderTest) {
|
|
106
|
+
throw new Error(`Function not found for test case: ${name}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (throws) {
|
|
110
|
+
// Test expects an error to be thrown
|
|
111
|
+
expect(() => functionUnderTest(...(inArg || []))).toThrow(throws);
|
|
112
|
+
} else {
|
|
113
|
+
const out = functionUnderTest(...(inArg || []));
|
|
114
|
+
expect(out).toEqual(expectedOut);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const setupClassTests = (suite) => {
|
|
121
|
+
const { cases, ClassUnderTest, constructorArgs } = suite;
|
|
122
|
+
for (const testCase of cases) {
|
|
123
|
+
const { name, executions } = testCase;
|
|
124
|
+
test(name, () => {
|
|
125
|
+
if (!ClassUnderTest) {
|
|
126
|
+
throw new Error(`Class not found for test suite: ${suite.name}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const instance = new ClassUnderTest(...constructorArgs);
|
|
130
|
+
|
|
131
|
+
for (const execution of executions) {
|
|
132
|
+
const {
|
|
133
|
+
method,
|
|
134
|
+
in: inArg,
|
|
135
|
+
out: expectedOut,
|
|
136
|
+
throws,
|
|
137
|
+
asserts,
|
|
138
|
+
} = execution;
|
|
139
|
+
|
|
140
|
+
// Validate method exists
|
|
141
|
+
if (!instance[method] || typeof instance[method] !== "function") {
|
|
142
|
+
throw new Error(`Method '${method}' not found on class instance`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Execute the method and check its return value
|
|
146
|
+
if (throws) {
|
|
147
|
+
expect(() => instance[method](...(inArg || []))).toThrow(throws);
|
|
148
|
+
} else {
|
|
149
|
+
const result = instance[method](...(inArg || []));
|
|
150
|
+
if (expectedOut !== undefined) {
|
|
151
|
+
expect(result).toEqual(expectedOut);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Run assertions
|
|
156
|
+
if (asserts) {
|
|
157
|
+
for (const assertion of asserts) {
|
|
158
|
+
if (assertion.property) {
|
|
159
|
+
// Property assertion
|
|
160
|
+
const actualValue = instance[assertion.property];
|
|
161
|
+
if (assertion.op === "eq") {
|
|
162
|
+
expect(actualValue).toEqual(assertion.value);
|
|
163
|
+
}
|
|
164
|
+
// Add more operators as needed
|
|
165
|
+
} else if (assertion.method) {
|
|
166
|
+
// Method assertion
|
|
167
|
+
if (
|
|
168
|
+
!instance[assertion.method] ||
|
|
169
|
+
typeof instance[assertion.method] !== "function"
|
|
170
|
+
) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Method '${assertion.method}' not found on class instance for assertion`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const result = instance[assertion.method](
|
|
176
|
+
...(assertion.in || []),
|
|
177
|
+
);
|
|
178
|
+
expect(result).toEqual(assertion.out);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
89
187
|
/**
|
|
90
188
|
*
|
|
91
189
|
* @param {*} module
|
|
@@ -97,11 +195,27 @@ export const injectFunctions = (module, originalTestConfig) => {
|
|
|
97
195
|
let functionUnderTest = module[testConfig.exportName || "default"];
|
|
98
196
|
|
|
99
197
|
for (const suite of testConfig.suites) {
|
|
100
|
-
if (suite.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
198
|
+
if (suite.mode === "class") {
|
|
199
|
+
const exportName = suite.exportName || "default";
|
|
200
|
+
const exported = module[exportName];
|
|
201
|
+
if (!exported) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Export '${exportName}' not found in module for class suite '${suite.name}'`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
suite.ClassUnderTest = exported;
|
|
207
|
+
} else {
|
|
208
|
+
if (suite.exportName) {
|
|
209
|
+
functionUnderTest = module[suite.exportName];
|
|
210
|
+
if (!functionUnderTest) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Export '${suite.exportName}' not found in module for suite '${suite.name}'`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const testCase of suite.cases) {
|
|
217
|
+
testCase.functionUnderTest = functionUnderTest;
|
|
218
|
+
}
|
|
105
219
|
}
|
|
106
220
|
}
|
|
107
221
|
return testConfig;
|