puty 0.0.4-rc1 → 0.0.4

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.
Files changed (3) hide show
  1. package/README.md +32 -10
  2. package/package.json +1 -1
  3. package/src/puty.js +71 -22
package/README.md CHANGED
@@ -188,12 +188,12 @@ executions:
188
188
  - `mode: 'class'` - Indicates this suite tests a class
189
189
  - `constructorArgs` - Arguments passed to the class constructor
190
190
  - `executions` - Array of method calls to execute in sequence
191
- - `method` - Name of the method to call
191
+ - `method` - Name of the method to call (supports nested: `user.api.getData`)
192
192
  - `in` - Arguments to pass to the method
193
193
  - `out` - Expected return value (optional)
194
194
  - `asserts` - Assertions to run after the method call
195
- - Property assertions: Check instance properties
196
- - Method assertions: Call methods and check their return values
195
+ - Property assertions: Check instance properties (supports nested: `user.profile.name`)
196
+ - Method assertions: Call methods and check their return values (supports nested: `settings.getTheme`)
197
197
 
198
198
  ### Error Testing
199
199
 
@@ -387,21 +387,43 @@ For class tests:
387
387
  ```yaml
388
388
  case: 'test description'
389
389
  executions:
390
- - method: 'methodName'
390
+ - method: 'methodName' # Supports nested: 'user.api.getData'
391
391
  in: [arg1]
392
- out: expectedValue # Optional
393
- throws: 'Error msg' # Optional
392
+ out: expectedValue # Optional
393
+ throws: 'Error msg' # Optional
394
394
  asserts:
395
- - property: 'prop'
396
- op: 'eq' # Currently only 'eq' is supported
395
+ - property: 'prop' # Supports nested: 'user.profile.name'
396
+ op: 'eq' # Currently only 'eq' is supported
397
397
  value: expected
398
- - method: 'getter'
398
+ - method: 'getter' # Supports nested: 'settings.ui.getTheme'
399
399
  in: []
400
400
  out: expected
401
- mocks: # Optional: Mocks for the entire test case
401
+ mocks: # Optional: Mocks for the entire test case
402
402
  mockName:
403
403
  calls:
404
404
  - in: [args]
405
405
  out: result
406
406
  ```
407
407
 
408
+ #### Nested Properties and Methods
409
+
410
+ Puty supports accessing nested properties and calling nested methods using dot notation:
411
+
412
+ ```yaml
413
+ case: 'test nested access'
414
+ executions:
415
+ - method: 'settings.ui.setTheme' # Call nested method
416
+ in: ['dark']
417
+ out: 'dark'
418
+ asserts:
419
+ - property: 'user.profile.name' # Access nested property
420
+ op: eq
421
+ value: 'John Doe'
422
+ - property: 'user.account.balance' # Deep nested property
423
+ op: eq
424
+ value: 100.50
425
+ - method: 'api.client.get' # Call nested method
426
+ in: ['/users/123']
427
+ out: 'GET /users/123'
428
+ ```
429
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puty",
3
- "version": "0.0.4-rc1",
3
+ "version": "0.0.4",
4
4
  "description": "A tooling function to test javascript functions and classes.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/puty.js CHANGED
@@ -11,6 +11,70 @@ import { expect, test, describe } from "vitest";
11
11
  import { traverseAllFiles, parseWithIncludes } from "./utils.js";
12
12
  import { resolveMocks, processMockReferences, createMockFunctions, validateMockCalls } from "./mockResolver.js";
13
13
 
14
+ /**
15
+ * Resolves a nested property path on an object (e.g., "user.profile.name")
16
+ * @param {Object} obj - The object to traverse
17
+ * @param {string} path - The property path (e.g., "user.profile.name")
18
+ * @returns {any} The value at the path
19
+ * @throws {Error} If any part of the path doesn't exist
20
+ */
21
+ const getNestedProperty = (obj, path) => {
22
+ const parts = path.split('.');
23
+ let current = obj;
24
+
25
+ for (let i = 0; i < parts.length; i++) {
26
+ if (current == null) {
27
+ throw new Error(`Cannot access property '${parts[i]}' of ${current} in path '${path}'`);
28
+ }
29
+ if (!(parts[i] in current)) {
30
+ throw new Error(`Property '${parts[i]}' not found in path '${path}'`);
31
+ }
32
+ current = current[parts[i]];
33
+ }
34
+
35
+ return current;
36
+ };
37
+
38
+ /**
39
+ * Resolves a nested method path and calls it (e.g., "user.api.getData")
40
+ * @param {Object} obj - The object to traverse
41
+ * @param {string} path - The method path (e.g., "user.api.getData")
42
+ * @param {Array} args - Arguments to pass to the method
43
+ * @returns {any} The result of the method call
44
+ * @throws {Error} If any part of the path doesn't exist or final part is not a function
45
+ */
46
+ const callNestedMethod = (obj, path, args = []) => {
47
+ const parts = path.split('.');
48
+ const methodName = parts.pop();
49
+
50
+ let current = obj;
51
+ const parentPath = parts.join('.');
52
+
53
+ // Navigate to the parent object
54
+ for (const part of parts) {
55
+ if (current == null) {
56
+ throw new Error(`Cannot access property '${part}' of ${current} in path '${path}'`);
57
+ }
58
+ if (!(part in current)) {
59
+ throw new Error(`Property '${part}' not found in path '${path}'`);
60
+ }
61
+ current = current[part];
62
+ }
63
+
64
+ // Check if the method exists and is a function
65
+ if (current == null) {
66
+ throw new Error(`Cannot access method '${methodName}' of ${current} in path '${path}'`);
67
+ }
68
+ if (!(methodName in current)) {
69
+ throw new Error(`Method '${methodName}' not found in path '${path}'`);
70
+ }
71
+ if (typeof current[methodName] !== 'function') {
72
+ throw new Error(`'${methodName}' is not a function in path '${path}'`);
73
+ }
74
+
75
+ return current[methodName](...args);
76
+ };
77
+
14
78
  /**
15
79
  * File extensions that are recognized as YAML test files
16
80
  * @type {string[]}
@@ -215,16 +279,11 @@ const setupClassTests = (suite) => {
215
279
  asserts,
216
280
  } = execution;
217
281
 
218
- // Validate method exists
219
- if (!instance[method] || typeof instance[method] !== "function") {
220
- throw new Error(`Method '${method}' not found on class instance`);
221
- }
222
-
223
- // Execute the method and check its return value
282
+ // Execute the method and check its return value - supports nested methods
224
283
  if (throws) {
225
- expect(() => instance[method](...(inArg || []))).toThrow(throws);
284
+ expect(() => callNestedMethod(instance, method, inArg || [])).toThrow(throws);
226
285
  } else {
227
- const result = instance[method](...(inArg || []));
286
+ const result = callNestedMethod(instance, method, inArg || []);
228
287
  if (expectedOut !== undefined) {
229
288
  expect(result).toEqual(expectedOut);
230
289
  }
@@ -234,25 +293,15 @@ const setupClassTests = (suite) => {
234
293
  if (asserts) {
235
294
  for (const assertion of asserts) {
236
295
  if (assertion.property) {
237
- // Property assertion
238
- const actualValue = instance[assertion.property];
296
+ // Property assertion - supports nested properties like "user.profile.name"
297
+ const actualValue = getNestedProperty(instance, assertion.property);
239
298
  if (assertion.op === "eq") {
240
299
  expect(actualValue).toEqual(assertion.value);
241
300
  }
242
301
  // Add more operators as needed
243
302
  } else if (assertion.method) {
244
- // Method assertion
245
- if (
246
- !instance[assertion.method] ||
247
- typeof instance[assertion.method] !== "function"
248
- ) {
249
- throw new Error(
250
- `Method '${assertion.method}' not found on class instance for assertion`,
251
- );
252
- }
253
- const result = instance[assertion.method](
254
- ...(assertion.in || []),
255
- );
303
+ // Method assertion - supports nested methods like "user.api.getData"
304
+ const result = callNestedMethod(instance, assertion.method, assertion.in || []);
256
305
  expect(result).toEqual(assertion.out);
257
306
  }
258
307
  }