puty 0.0.2 → 0.0.3

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 CHANGED
@@ -1,6 +1,27 @@
1
- # Puty: Pure Unit Test in Yaml
1
+ # Puty: Pure Unit Test in YAML
2
2
 
3
- Write unit test specifications using YAML files, powered by vitest as the test runner.
3
+ Puty is a declarative testing framework that allows you to write unit tests using YAML files instead of JavaScript code. It's built on top of [Vitest](https://vitest.dev/) and designed to make testing more accessible and maintainable by separating test data from test logic.
4
+
5
+ Puty is ideal for testing pure functions - functions that always return the same output for the same input and have no side effects. The declarative YAML format perfectly captures the essence of pure function testing: given these inputs, expect this output.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Installation](#installation)
11
+ - [Quick Start](#quick-start)
12
+ - [Usage](#usage)
13
+ - [Testing Functions](#testing-functions)
14
+ - [Testing Classes](#testing-classes)
15
+ - [Error Testing](#error-testing)
16
+ - [Using !include Directive](#using-include-directive)
17
+ - [YAML Structure](#yaml-structure)
18
+
19
+ ## Features
20
+
21
+ - 📝 Write tests in simple YAML format
22
+ - 📦 Modular test organization with `!include` directive
23
+ - 🎯 Clear separation of test data and test logic
24
+ - ⚡ Powered by Vitest for fast test execution
4
25
 
5
26
  ## Installation
6
27
 
@@ -8,26 +29,71 @@ Write unit test specifications using YAML files, powered by vitest as the test r
8
29
  npm install puty
9
30
  ```
10
31
 
11
- create a file in your project `puty.test.js`
32
+ ## Quick Start
33
+
34
+
35
+ 1. Create a test runner file `puty.test.js` in your project:
36
+
12
37
  ```js
13
- import { setupTestSuiteFromYaml } from "./puty.js";
38
+ import { setupTestSuiteFromYaml } from "puty";
39
+
40
+ // Search for test files in the current directory
14
41
  await setupTestSuiteFromYaml();
42
+
43
+ // Or specify a different directory
44
+ // await setupTestSuiteFromYaml("./tests");
15
45
  ```
16
46
 
17
- This function will read for all `.spec.yaml` and `.test.yaml` files in the current directory and run the tests.
47
+ **Note:** Puty uses ES module imports and requires your project to support ES modules. If you're using Node.js, make sure to add `"type": "module"` to your `package.json` or use `.mjs` file extensions.
18
48
 
19
- run
20
49
 
50
+ 2. Create your first test file `math.test.yaml`:
51
+
52
+ ```yaml
53
+ file: './math.js'
54
+ group: math
55
+ suites: [add]
56
+ ---
57
+ suite: add
58
+ exportName: add
59
+ ---
60
+ case: add two numbers
61
+ in: [2, 3]
62
+ out: 5
21
63
  ```
22
- vitest
64
+
65
+ 3. Run your tests:
66
+
67
+ ```bash
68
+ npx vitest
69
+ ```
70
+
71
+ ### Recommended Vitest Configuration
72
+
73
+ To enable automatic test reruns when YAML test files change, create a `vitest.config.js` file in your project root:
74
+
75
+ ```js
76
+ import { defineConfig } from 'vitest/config'
77
+
78
+ export default defineConfig({
79
+ test: {
80
+ forceRerunTriggers: [
81
+ '**/*.js',
82
+ '**/*.{test,spec}.yaml',
83
+ '**/*.{test,spec}.yml'
84
+ ],
85
+ },
86
+ });
23
87
  ```
24
88
 
25
- It should run all the tests in the file.
89
+ This configuration ensures that Vitest will re-run your tests whenever you modify either your JavaScript source files or your YAML test files.
26
90
 
27
91
  ## Usage
28
92
 
29
93
  ### Testing Functions
30
94
 
95
+ Here's a complete example of testing JavaScript functions with Puty:
96
+
31
97
  ```yaml
32
98
  file: './math.js'
33
99
  group: math
@@ -64,6 +130,22 @@ in:
64
130
  out: 3
65
131
  ```
66
132
 
133
+ This under the hood creates a test structure in Vitest like:
134
+ ```js
135
+ describe('math', () => {
136
+ describe('add', () => {
137
+ it('add 1 and 2', () => { ... })
138
+ it('add 2 and 2', () => { ... })
139
+ })
140
+ describe('increment', () => {
141
+ it('increment 1', () => { ... })
142
+ it('increment 2', () => { ... })
143
+ })
144
+ })
145
+ ```
146
+
147
+ See the [YAML Structure](#yaml-structure) section for detailed documentation of all available fields.
148
+
67
149
  ### Testing Classes
68
150
 
69
151
  Puty also supports testing classes with method calls and state assertions:
@@ -120,3 +202,110 @@ case: divide by zero
120
202
  in: [10, 0]
121
203
  throws: "Division by zero"
122
204
  ```
205
+
206
+ ### Using !include Directive
207
+
208
+ Puty supports the `!include` directive to modularize and reuse YAML test files. This is useful for:
209
+ - Sharing common test data across multiple test files
210
+ - Organizing large test suites into smaller, manageable files
211
+ - Reusing test cases for different modules
212
+
213
+ #### Basic Usage
214
+
215
+ You can include entire YAML documents:
216
+
217
+ ```yaml
218
+ file: "./math.js"
219
+ group: math-tests
220
+ suites: [add]
221
+ ---
222
+ !include ./suite-definition.yaml
223
+ ---
224
+ !include ./test-cases.yaml
225
+ ```
226
+
227
+ #### Including Values
228
+
229
+ You can also include specific values within a YAML document:
230
+
231
+ ```yaml
232
+ case: test with shared data
233
+ in: !include ./test-data/input.yaml
234
+ out: !include ./test-data/expected-output.yaml
235
+ ```
236
+
237
+ #### Recursive Includes
238
+
239
+ The `!include` directive supports recursive includes, allowing included files to include other files:
240
+
241
+ ```yaml
242
+ # main.yaml
243
+ !include ./level1.yaml
244
+
245
+ # level1.yaml
246
+ suite: test
247
+ ---
248
+ !include ./level2.yaml
249
+
250
+ # level2.yaml
251
+ case: nested test
252
+ in: []
253
+ out: "success"
254
+ ```
255
+
256
+ #### Important Notes
257
+
258
+ - File paths in `!include` are relative to the YAML file containing the directive
259
+ - Circular dependencies are detected and will cause an error
260
+ - Missing include files will result in a clear error message
261
+ - Both single documents and multi-document YAML files can be included
262
+
263
+
264
+ ## YAML Structure
265
+
266
+ Puty test files use multi-document YAML format with three types of documents:
267
+
268
+ ### 1. Configuration Document (First document)
269
+
270
+ ```yaml
271
+ file: './module.js' # Required: Path to JS file (relative to YAML file)
272
+ group: 'test-group' # Required: Test group name (or use 'name')
273
+ suites: ['suite1', 'suite2'] # Optional: List of suites to define
274
+ ```
275
+
276
+ ### 2. Suite Definition Documents
277
+
278
+ ```yaml
279
+ suite: 'suiteName' # Required: Suite name
280
+ exportName: 'functionName' # Optional: Export to test (defaults to suite name or 'default')
281
+ mode: 'class' # Optional: Set to 'class' for class testing
282
+ constructorArgs: [arg1] # Optional: Arguments for class constructor (class mode only)
283
+ ```
284
+
285
+ ### 3. Test Case Documents
286
+
287
+ For function tests:
288
+ ```yaml
289
+ case: 'test description' # Required: Test case name
290
+ in: [arg1, arg2] # Required: Input arguments
291
+ out: expectedValue # Optional: Expected output (omit if testing for errors)
292
+ throws: 'Error message' # Optional: Expected error message
293
+ ```
294
+
295
+ For class tests:
296
+ ```yaml
297
+ case: 'test description'
298
+ executions:
299
+ - method: 'methodName'
300
+ in: [arg1]
301
+ out: expectedValue # Optional
302
+ throws: 'Error msg' # Optional
303
+ asserts:
304
+ - property: 'prop'
305
+ op: 'eq' # Currently only 'eq' is supported
306
+ value: expected
307
+ - method: 'getter'
308
+ in: []
309
+ out: expected
310
+ ```
311
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puty",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "A tooling function to test javascript functions and classes.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * @fileoverview Main entry point for the puty testing framework
3
+ * Exports the primary function for setting up YAML-driven test suites.
4
+ */
5
+
1
6
  import { setupTestSuiteFromYaml } from "./puty.js";
2
7
 
8
+ /**
9
+ * Main entry point function for discovering and setting up test suites from YAML files
10
+ * @see {@link setupTestSuiteFromYaml} for detailed documentation
11
+ */
3
12
  export { setupTestSuiteFromYaml };
package/src/puty.js CHANGED
@@ -1,14 +1,42 @@
1
+ /**
2
+ * @fileoverview Main test framework functionality for YAML-driven testing
3
+ * This module provides functions for setting up test suites from YAML configurations,
4
+ * injecting functions/classes from modules, and executing tests using vitest.
5
+ */
6
+
1
7
  import path from "node:path";
2
- import fs from "node:fs";
3
8
  import yaml from "js-yaml";
4
9
  import { expect, test, describe } from "vitest";
5
10
 
6
- import { traverseAllFiles } from "./utils.js";
11
+ import { traverseAllFiles, parseWithIncludes } from "./utils.js";
7
12
 
13
+ /**
14
+ * File extensions that are recognized as YAML test files
15
+ * @type {string[]}
16
+ */
8
17
  const extensions = [".test.yaml", ".test.yml", ".spec.yaml", ".spec.yml"];
9
18
 
10
19
  /**
11
- * Parse YAML content with document separators into structured test config
20
+ * Parses YAML content containing multiple documents separated by '---' into a structured test configuration
21
+ * @param {string} yamlContent - Raw YAML content string to parse
22
+ * @returns {Object} Structured test configuration object
23
+ * @returns {string|null} returns.file - Path to the JavaScript file being tested
24
+ * @returns {string|null} returns.group - Test group name
25
+ * @returns {string[]} [returns.suiteNames] - Array of suite names defined in config
26
+ * @returns {Object[]} returns.suites - Array of test suite objects
27
+ * @example
28
+ * const yamlContent = `
29
+ * file: './math.js'
30
+ * group: math
31
+ * ---
32
+ * suite: add
33
+ * exportName: add
34
+ * ---
35
+ * case: add 1 and 2
36
+ * in: [1, 2]
37
+ * out: 3
38
+ * `;
39
+ * const config = parseYamlDocuments(yamlContent);
12
40
  */
13
41
  export const parseYamlDocuments = (yamlContent) => {
14
42
  const docs = yaml.loadAll(yamlContent);
@@ -68,8 +96,19 @@ export const parseYamlDocuments = (yamlContent) => {
68
96
  };
69
97
 
70
98
  /**
71
- *
72
- * @param {*} testConfig
99
+ * Sets up and registers test suites with the testing framework (vitest)
100
+ * @param {Object} testConfig - Test configuration object containing suites and cases
101
+ * @param {string} testConfig.group - Name of the test group (used as describe block name)
102
+ * @param {Object[]} testConfig.suites - Array of test suite objects
103
+ * @param {boolean} [testConfig.skip] - Whether to skip this entire test suite
104
+ * @example
105
+ * setupTestSuite({
106
+ * group: 'math',
107
+ * suites: [{
108
+ * name: 'add',
109
+ * cases: [{ name: 'add 1+2', functionUnderTest: addFn, in: [1,2], out: 3 }]
110
+ * }]
111
+ * });
73
112
  */
74
113
  const setupTestSuite = (testConfig) => {
75
114
  const { group, suites, skip } = testConfig;
@@ -91,6 +130,16 @@ const setupTestSuite = (testConfig) => {
91
130
  });
92
131
  };
93
132
 
133
+ /**
134
+ * Sets up individual test cases for function-based testing
135
+ * @param {Object} suite - Test suite configuration
136
+ * @param {Object[]} suite.cases - Array of test case objects
137
+ * @param {string} suite.cases[].name - Test case name
138
+ * @param {any[]} suite.cases[].in - Input arguments for the function
139
+ * @param {any} suite.cases[].out - Expected output value
140
+ * @param {Function} suite.cases[].functionUnderTest - The function to test
141
+ * @param {string|RegExp} [suite.cases[].throws] - Expected error message/pattern if function should throw
142
+ */
94
143
  const setupFunctionTests = (suite) => {
95
144
  const { cases } = suite;
96
145
  for (const testCase of cases) {
@@ -117,6 +166,15 @@ const setupFunctionTests = (suite) => {
117
166
  }
118
167
  };
119
168
 
169
+ /**
170
+ * Sets up individual test cases for class-based testing
171
+ * @param {Object} suite - Test suite configuration for class testing
172
+ * @param {Object[]} suite.cases - Array of test case objects
173
+ * @param {string} suite.cases[].name - Test case name
174
+ * @param {Object[]} suite.cases[].executions - Array of method executions to perform
175
+ * @param {Function} suite.ClassUnderTest - The class constructor to test
176
+ * @param {any[]} suite.constructorArgs - Arguments to pass to class constructor
177
+ */
120
178
  const setupClassTests = (suite) => {
121
179
  const { cases, ClassUnderTest, constructorArgs } = suite;
122
180
  for (const testCase of cases) {
@@ -185,10 +243,19 @@ const setupClassTests = (suite) => {
185
243
  };
186
244
 
187
245
  /**
188
- *
189
- * @param {*} module
190
- * @param {*} originalTestConfig
191
- * @returns
246
+ * Injects functions and classes from an imported module into test configuration objects
247
+ * @param {Object} module - The imported JavaScript module containing functions/classes to test
248
+ * @param {Object} originalTestConfig - Original test configuration object
249
+ * @returns {Object} Test configuration with injected functions/classes ready for testing
250
+ * @throws {Error} When required exports are not found in the module
251
+ * @example
252
+ * // Import module and inject functions
253
+ * const module = await import('./math.js');
254
+ * const testConfig = {
255
+ * suites: [{ name: 'add', exportName: 'add', cases: [...] }]
256
+ * };
257
+ * const ready = injectFunctions(module, testConfig);
258
+ * // ready.suites[0].cases[0].functionUnderTest === module.add
192
259
  */
193
260
  export const injectFunctions = (module, originalTestConfig) => {
194
261
  const testConfig = structuredClone(originalTestConfig);
@@ -222,13 +289,23 @@ export const injectFunctions = (module, originalTestConfig) => {
222
289
  };
223
290
 
224
291
  /**
225
- * Setup test suites from all yaml files in the current directory and its subdirectories
292
+ * Discovers and sets up test suites from all YAML test files in a directory and its subdirectories
293
+ * @param {string} dirname - Directory path to search for YAML test files
294
+ * @returns {Promise<void>} Promise that resolves when all test suites are set up
295
+ * @throws {Error} When YAML files cannot be parsed or modules cannot be imported
296
+ * @example
297
+ * // Set up all test suites from YAML files in the current directory
298
+ * await setupTestSuiteFromYaml('.');
299
+ *
300
+ * // Set up tests from a specific directory
301
+ * await setupTestSuiteFromYaml('./tests');
302
+ *
303
+ * // This will find all files matching: *.test.yaml, *.test.yml, *.spec.yaml, *.spec.yml
226
304
  */
227
305
  export const setupTestSuiteFromYaml = async (dirname) => {
228
306
  const testYamlFiles = traverseAllFiles(dirname, extensions);
229
307
  for (const file of testYamlFiles) {
230
- const yamlContent = fs.readFileSync(file, "utf8");
231
- const testConfig = parseYamlDocuments(yamlContent);
308
+ const testConfig = parseWithIncludes(file);
232
309
  const filepathRelativeToSpecFile = path.join(
233
310
  path.dirname(file),
234
311
  testConfig.file,
package/src/utils.js CHANGED
@@ -1,23 +1,88 @@
1
+ /**
2
+ * @fileoverview Utility functions for YAML processing with !include directive support
3
+ * This module provides functions for loading YAML files with include capabilities,
4
+ * directory traversal, and parsing YAML test configurations.
5
+ */
6
+
1
7
  import fs from "node:fs";
2
8
  import path, { join } from "node:path";
3
9
 
4
10
  import yaml from "js-yaml";
5
11
 
6
- export const loadYamlWithPath = (path) => {
12
+ /**
13
+ * Loads a YAML file with support for !include directives and circular dependency detection
14
+ * @param {string} filePath - Absolute or relative path to the YAML file to load
15
+ * @param {Set<string>} [visitedFiles=new Set()] - Set of already visited file paths for circular dependency detection
16
+ * @returns {any|any[]} The parsed YAML content. Returns single object for single documents, array for multi-document YAML
17
+ * @throws {Error} When circular dependencies are detected, files are not found, or YAML parsing fails
18
+ * @example
19
+ * // Load a simple YAML file
20
+ * const config = loadYamlWithPath('./config.yaml');
21
+ *
22
+ * // Load YAML with includes
23
+ * const data = loadYamlWithPath('./main.yaml'); // main.yaml contains: data: !include ./data.yaml
24
+ */
25
+ export const loadYamlWithPath = (filePath, visitedFiles = new Set()) => {
26
+ const absolutePath = path.resolve(filePath);
27
+
28
+ // Check for circular dependencies
29
+ if (visitedFiles.has(absolutePath)) {
30
+ throw new Error(
31
+ `Circular dependency detected: ${absolutePath} is already being processed`,
32
+ );
33
+ }
34
+
35
+ // Add current file to visited set
36
+ visitedFiles.add(absolutePath);
37
+
7
38
  const includeType = new yaml.Type("!include", {
8
39
  kind: "scalar",
9
- construct: function (filePath) {
10
- const content = fs.readFileSync(join(path, "..", filePath), "utf8");
11
- return yaml.load(content); // you could recurse here
40
+ construct: function (includePath) {
41
+ // Resolve include path relative to the current file's directory
42
+ const currentDir = path.dirname(absolutePath);
43
+ const resolvedIncludePath = path.resolve(currentDir, includePath);
44
+
45
+ // Check if included file exists
46
+ if (!fs.existsSync(resolvedIncludePath)) {
47
+ throw new Error(
48
+ `Include file not found: ${resolvedIncludePath} (included from ${absolutePath})`,
49
+ );
50
+ }
51
+
52
+ // Recursively load the included file with the same visited files set
53
+ return loadYamlWithPath(resolvedIncludePath, new Set(visitedFiles));
12
54
  },
13
55
  });
56
+
14
57
  const schema = yaml.DEFAULT_SCHEMA.extend([includeType]);
15
- return yaml.load(fs.readFileSync(path, "utf8"), { schema });
58
+
59
+ try {
60
+ const content = fs.readFileSync(absolutePath, "utf8");
61
+ // Always use loadAll - it handles both single and multi-document YAML
62
+ const docs = yaml.loadAll(content, { schema });
63
+ // If only one document, return it directly (not as array)
64
+ return docs.length === 1 ? docs[0] : docs;
65
+ } catch (error) {
66
+ throw new Error(
67
+ `Error loading YAML file ${absolutePath}: ${error.message}`,
68
+ );
69
+ } finally {
70
+ // Remove current file from visited set when done processing
71
+ visitedFiles.delete(absolutePath);
72
+ }
16
73
  };
17
74
 
18
75
  /**
19
- * Traverse all files in the current directory and its subdirectories
20
- * Return an array of full path of files
76
+ * Recursively traverses a directory and returns all files matching the specified extensions
77
+ * @param {string} startPath - The directory path to start traversing from
78
+ * @param {string[]} extensions - Array of file extensions to match (e.g., ['.yaml', '.yml'])
79
+ * @returns {string[]} Array of absolute file paths for all matching files found
80
+ * @example
81
+ * // Find all YAML test files
82
+ * const testFiles = traverseAllFiles('./tests', ['.test.yaml', '.spec.yml']);
83
+ *
84
+ * // Find all JavaScript files
85
+ * const jsFiles = traverseAllFiles('./src', ['.js', '.ts']);
21
86
  */
22
87
  export const traverseAllFiles = (startPath, extensions) => {
23
88
  const results = [];
@@ -33,3 +98,131 @@ export const traverseAllFiles = (startPath, extensions) => {
33
98
  }
34
99
  return results;
35
100
  };
101
+
102
+ /**
103
+ * Recursively flattens nested arrays of YAML documents that result from multi-document includes
104
+ * @param {any|any[]} data - The data to flatten, can be a single document or nested arrays
105
+ * @returns {any[]} Flattened array of documents
106
+ * @example
107
+ * // Flatten nested document arrays
108
+ * const nested = [doc1, [doc2, doc3], doc4];
109
+ * const flat = flattenDocuments(nested); // [doc1, doc2, doc3, doc4]
110
+ */
111
+ const flattenDocuments = (data) => {
112
+ if (!Array.isArray(data)) {
113
+ return [data];
114
+ }
115
+
116
+ const result = [];
117
+ for (const item of data) {
118
+ if (Array.isArray(item)) {
119
+ result.push(...flattenDocuments(item));
120
+ } else {
121
+ result.push(item);
122
+ }
123
+ }
124
+ return result;
125
+ };
126
+
127
+ /**
128
+ * Processes an array of YAML documents and converts them into a structured test configuration
129
+ * @param {any[]} docs - Array of YAML document objects to process
130
+ * @returns {Object} Structured test configuration object
131
+ * @returns {string|null} returns.file - Path to the JavaScript file being tested
132
+ * @returns {string|null} returns.group - Test group name
133
+ * @returns {string[]} [returns.suiteNames] - Array of suite names defined in config
134
+ * @returns {Object[]} returns.suites - Array of test suite objects
135
+ * @returns {string} returns.suites[].name - Suite name
136
+ * @returns {string} returns.suites[].exportName - Function/class export name to test
137
+ * @returns {Object[]} returns.suites[].cases - Array of test cases
138
+ * @returns {string} [returns.suites[].mode] - Test mode ('class' for class testing)
139
+ * @returns {any[]} [returns.suites[].constructorArgs] - Constructor arguments for class mode
140
+ * @example
141
+ * const docs = [
142
+ * { file: './math.js', group: 'math', suites: ['add'] },
143
+ * { suite: 'add', exportName: 'add' },
144
+ * { case: 'add 1+2', in: [1, 2], out: 3 }
145
+ * ];
146
+ * const config = processDocuments(docs);
147
+ */
148
+ const processDocuments = (docs) => {
149
+ const config = {
150
+ file: null,
151
+ group: null,
152
+ suites: [],
153
+ };
154
+
155
+ let currentSuite = null;
156
+
157
+ for (const doc of docs) {
158
+ if (doc.file) {
159
+ config.file = doc.file;
160
+ config.group = doc.group || doc.name;
161
+ if (doc.suites) {
162
+ config.suiteNames = doc.suites;
163
+ }
164
+ } else if (doc.suite) {
165
+ if (currentSuite) {
166
+ config.suites.push(currentSuite);
167
+ }
168
+ currentSuite = {
169
+ name: doc.suite,
170
+ exportName: doc.exportName || doc.suite,
171
+ cases: [],
172
+ };
173
+ // Only add mode and constructorArgs if mode is explicitly 'class'
174
+ if (doc.mode === "class") {
175
+ currentSuite.mode = "class";
176
+ currentSuite.constructorArgs = doc.constructorArgs || [];
177
+ }
178
+ } else if (doc.case && currentSuite) {
179
+ const testCase = {
180
+ name: doc.case,
181
+ };
182
+
183
+ if (currentSuite.mode === "class") {
184
+ testCase.executions = doc.executions || [];
185
+ } else {
186
+ testCase.in = doc.in || [];
187
+ testCase.out = doc.out;
188
+ if (doc.throws) {
189
+ testCase.throws = doc.throws;
190
+ }
191
+ }
192
+
193
+ currentSuite.cases.push(testCase);
194
+ }
195
+ }
196
+
197
+ if (currentSuite) {
198
+ config.suites.push(currentSuite);
199
+ }
200
+
201
+ return config;
202
+ };
203
+
204
+ /**
205
+ * Parses a YAML file with !include directive support and converts it into a structured test configuration
206
+ * @param {string} filePath - Path to the YAML file to parse
207
+ * @returns {Object} Structured test configuration object ready for test execution
208
+ * @throws {Error} When file cannot be found, YAML is invalid, or circular dependencies are detected
209
+ * @example
210
+ * // Parse a test configuration file with includes
211
+ * const config = parseWithIncludes('./tests/math.spec.yaml');
212
+ * // Returns: { file: './math.js', group: 'math', suites: [...] }
213
+ *
214
+ * // Works with files containing !include directives
215
+ * // main.yaml:
216
+ * // file: './lib.js'
217
+ * // ---
218
+ * // !include ./test-cases.yaml
219
+ * const config = parseWithIncludes('./main.yaml');
220
+ */
221
+ export const parseWithIncludes = (filePath) => {
222
+ const yamlData = loadYamlWithPath(filePath);
223
+
224
+ // Flatten any nested arrays that come from multi-document includes
225
+ const flattenedDocs = flattenDocuments(yamlData);
226
+
227
+ return processDocuments(flattenedDocs);
228
+ };