@fgv/ts-utils-jest 5.0.0-15 → 5.0.0-17

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 (24) hide show
  1. package/README.md +11 -0
  2. package/config/jest-disable-colors.js +34 -0
  3. package/lib/matchers/toFailTestAndMatchSnapshot/index.js +2 -1
  4. package/lib/utils/colorHelpers.d.ts +54 -0
  5. package/lib/utils/colorHelpers.js +84 -0
  6. package/lib/utils/matcherHelpers.js +9 -5
  7. package/package.json +3 -3
  8. package/src/__snapshots__/index.test.ts.snap +3 -3
  9. package/src/matchers/toFail/__snapshots__/index.test.ts.snap +3 -3
  10. package/src/matchers/toFailTest/__snapshots__/index.test.ts.snap +2 -2
  11. package/src/matchers/toFailTestAndMatchSnapshot/__snapshots__/index.test.ts.snap +4 -4
  12. package/src/matchers/toFailTestAndMatchSnapshot/index.ts +2 -1
  13. package/src/matchers/toFailTestWith/__snapshots__/index.test.ts.snap +12 -12
  14. package/src/matchers/toFailWith/__snapshots__/index.test.ts.snap +7 -7
  15. package/src/matchers/toFailWithDetail/__snapshots__/index.test.ts.snap +17 -17
  16. package/src/matchers/toHaveBeenCalledWithArgumentsMatching/__snapshots__/index.test.ts.snap +35 -35
  17. package/src/matchers/toSucceed/__snapshots__/index.test.ts.snap +3 -3
  18. package/src/matchers/toSucceedAndMatchInlineSnapshot/__snapshots__/index.test.ts.snap +3 -3
  19. package/src/matchers/toSucceedAndMatchSnapshot/__snapshots__/index.test.ts.snap +3 -3
  20. package/src/matchers/toSucceedAndSatisfy/__snapshots__/index.test.ts.snap +11 -11
  21. package/src/matchers/toSucceedWith/__snapshots__/index.test.ts.snap +8 -8
  22. package/src/matchers/toSucceedWithDetail/__snapshots__/index.test.ts.snap +19 -19
  23. package/src/utils/colorHelpers.ts +82 -0
  24. package/src/utils/matcherHelpers.ts +12 -5
package/README.md CHANGED
@@ -16,6 +16,7 @@ Also includes a handful of custom matchers to simplify the testing of other cust
16
16
  - [Summary](#summary)
17
17
  - [Installation](#installation)
18
18
  - [Setup](#setup)
19
+ - [ANSI Color Stripping](#ansi-color-stripping)
19
20
  - [API](#api)
20
21
  - [Testing Result\<T\>](#testing-resultt)
21
22
  - [.toFail()](#tofail)
@@ -43,6 +44,16 @@ npm install --save-dev @fgv/ts-utils-jest
43
44
 
44
45
  Note that snapshot testing for Jest itself can be tricky because different environments (e.g. CLI vs IDE vs CICD) might generate slightly different output due to e.g. differences in color display of diffs. To facilitate snapshot testing across multiple environments, this library also provides an extensible set of snapshot resolvers that can be used to capture environment-specific snapshots.
45
46
 
47
+ ### ANSI Color Stripping
48
+
49
+ **All matchers in this library automatically strip ANSI color codes** from Jest output to ensure consistent snapshots across different terminal environments. This includes:
50
+
51
+ - **Jest matcher utilities** (`printExpected`, `printReceived`) used in error messages
52
+ - **Snapshot content** passed to `toMatchSnapshot()` and `toFailTestAndMatchSnapshot()`
53
+ - **Test failure messages** captured by custom matcher testing utilities
54
+
55
+ This means you can run tests with any terminal color settings (`FORCE_COLOR=0`, `FORCE_COLOR=1`, or default) and get identical snapshot results. No special configuration is required - the color stripping happens automatically.
56
+
46
57
  ## API
47
58
 
48
59
  ### Testing Result\<T\>
@@ -0,0 +1,34 @@
1
+ // Disable colors in Jest to ensure consistent snapshot behavior
2
+ // This setup file ensures that Jest matchers don't include ANSI color codes
3
+ // in their output, which was causing snapshot test failures when running
4
+ // via different environments (direct Jest vs Heft/Rush)
5
+
6
+ // Force Jest to not use colors
7
+ process.env.FORCE_COLOR = '0';
8
+
9
+ // Monkey patch the jest-matcher-utils functions to remove colors
10
+ const matcherUtils = require('jest-matcher-utils');
11
+
12
+ // Store original functions
13
+ const originalPrintExpected = matcherUtils.printExpected;
14
+ const originalPrintReceived = matcherUtils.printReceived;
15
+ const originalMatcherHint = matcherUtils.matcherHint;
16
+
17
+ // Override with non-colored versions
18
+ matcherUtils.printExpected = function (value) {
19
+ // Remove ANSI color codes from the output
20
+ const result = originalPrintExpected.call(this, value);
21
+ return result.replace(/\x1b\[[0-9;]*m/g, '');
22
+ };
23
+
24
+ matcherUtils.printReceived = function (value) {
25
+ // Remove ANSI color codes from the output
26
+ const result = originalPrintReceived.call(this, value);
27
+ return result.replace(/\x1b\[[0-9;]*m/g, '');
28
+ };
29
+
30
+ matcherUtils.matcherHint = function (matcherName, received, expected, options) {
31
+ // Remove ANSI color codes from the output
32
+ const result = originalMatcherHint.call(this, matcherName, received, expected, options);
33
+ return result.replace(/\x1b\[[0-9;]*m/g, '');
34
+ };
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const jest_snapshot_1 = require("jest-snapshot");
5
5
  const predicate_1 = require("./predicate");
6
6
  const jest_matcher_utils_1 = require("jest-matcher-utils");
7
+ const colorHelpers_1 = require("../../utils/colorHelpers");
7
8
  exports.default = {
8
9
  toFailTestAndMatchSnapshot: function (cb) {
9
10
  const context = this;
@@ -20,7 +21,7 @@ exports.default = {
20
21
  }
21
22
  };
22
23
  }
23
- return jest_snapshot_1.toMatchSnapshot.call(context, cbResult.value, 'toFailTestAndMatchSnapshot');
24
+ return jest_snapshot_1.toMatchSnapshot.call(context, (0, colorHelpers_1.stripAnsiColors)(cbResult.value), 'toFailTestAndMatchSnapshot');
24
25
  }
25
26
  };
26
27
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Utility functions for handling ANSI color codes in test output.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ /**
7
+ * Strips ANSI color/style escape sequences from a string.
8
+ *
9
+ * This function removes all ANSI escape sequences commonly used for
10
+ * terminal colors and text styling, making the output suitable for
11
+ * color-agnostic snapshot testing.
12
+ *
13
+ * @param text - The text that may contain ANSI escape sequences
14
+ * @returns The text with all ANSI escape sequences removed
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const coloredText = '\u001b[31mError:\u001b[39m Something failed';
19
+ * const plainText = stripAnsiColors(coloredText);
20
+ * console.log(plainText); // "Error: Something failed"
21
+ * ```
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // Common usage in Jest matchers
26
+ * const formattedOutput = matcherUtils.printReceived(value);
27
+ * const cleanOutput = stripAnsiColors(formattedOutput);
28
+ * expect(cleanOutput).toMatchSnapshot();
29
+ * ```
30
+ *
31
+ * @public
32
+ */
33
+ export declare function stripAnsiColors(text: string): string;
34
+ /**
35
+ * Creates a wrapper function that strips ANSI colors from any string-returning function.
36
+ *
37
+ * This is useful for wrapping Jest matcher utility functions to make them
38
+ * color-agnostic for snapshot testing.
39
+ *
40
+ * @param fn - A function that returns a string (potentially with ANSI colors)
41
+ * @returns A wrapped function that returns the same string with colors stripped
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { printReceived } from 'jest-matcher-utils';
46
+ *
47
+ * const printReceivedClean = createColorStripWrapper(printReceived);
48
+ * const output = printReceivedClean(someValue); // No colors
49
+ * ```
50
+ *
51
+ * @public
52
+ */
53
+ export declare function createColorStripWrapper<T extends (...args: unknown[]) => string>(fn: T): T;
54
+ //# sourceMappingURL=colorHelpers.d.ts.map
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ /**
3
+ * Utility functions for handling ANSI color codes in test output.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.stripAnsiColors = stripAnsiColors;
9
+ exports.createColorStripWrapper = createColorStripWrapper;
10
+ /**
11
+ * Regular expression to match ANSI color/style escape sequences.
12
+ *
13
+ * This matches:
14
+ * - ESC character (ASCII 27) followed by `[`
15
+ * - `[0-9;]*` - Zero or more digits and semicolons (parameters)
16
+ * - `[a-zA-Z]` - Single letter command (m for SGR, K for erase, etc.)
17
+ *
18
+ * Examples of what this matches:
19
+ * - `\u001b[31m` - Red foreground
20
+ * - `\u001b[39m` - Default foreground
21
+ * - `\u001b[2m` - Dim/faint
22
+ * - `\u001b[22m` - Normal intensity
23
+ *
24
+ * Using a pre-compiled regex literal to satisfy ESLint security requirements.
25
+ * We use the unicode escape sequence \\u001b instead of \\x1b to avoid control-regex warnings.
26
+ */
27
+ // eslint-disable-next-line @rushstack/security/no-unsafe-regexp, no-control-regex
28
+ const ANSI_COLOR_REGEX = /\u001b\[[0-9;]*[a-zA-Z]/g;
29
+ /**
30
+ * Strips ANSI color/style escape sequences from a string.
31
+ *
32
+ * This function removes all ANSI escape sequences commonly used for
33
+ * terminal colors and text styling, making the output suitable for
34
+ * color-agnostic snapshot testing.
35
+ *
36
+ * @param text - The text that may contain ANSI escape sequences
37
+ * @returns The text with all ANSI escape sequences removed
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const coloredText = '\u001b[31mError:\u001b[39m Something failed';
42
+ * const plainText = stripAnsiColors(coloredText);
43
+ * console.log(plainText); // "Error: Something failed"
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * // Common usage in Jest matchers
49
+ * const formattedOutput = matcherUtils.printReceived(value);
50
+ * const cleanOutput = stripAnsiColors(formattedOutput);
51
+ * expect(cleanOutput).toMatchSnapshot();
52
+ * ```
53
+ *
54
+ * @public
55
+ */
56
+ function stripAnsiColors(text) {
57
+ return text.replace(ANSI_COLOR_REGEX, '');
58
+ }
59
+ /**
60
+ * Creates a wrapper function that strips ANSI colors from any string-returning function.
61
+ *
62
+ * This is useful for wrapping Jest matcher utility functions to make them
63
+ * color-agnostic for snapshot testing.
64
+ *
65
+ * @param fn - A function that returns a string (potentially with ANSI colors)
66
+ * @returns A wrapped function that returns the same string with colors stripped
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * import { printReceived } from 'jest-matcher-utils';
71
+ *
72
+ * const printReceivedClean = createColorStripWrapper(printReceived);
73
+ * const output = printReceivedClean(someValue); // No colors
74
+ * ```
75
+ *
76
+ * @public
77
+ */
78
+ function createColorStripWrapper(fn) {
79
+ return ((...args) => {
80
+ const result = fn(...args);
81
+ return stripAnsiColors(result);
82
+ });
83
+ }
84
+ //# sourceMappingURL=colorHelpers.js.map
@@ -5,8 +5,12 @@ exports.printExpectedDetailedResult = printExpectedDetailedResult;
5
5
  exports.printReceivedResult = printReceivedResult;
6
6
  exports.printReceivedDetailedResult = printReceivedDetailedResult;
7
7
  const jest_matcher_utils_1 = require("jest-matcher-utils");
8
+ const colorHelpers_1 = require("./colorHelpers");
9
+ // Create color-stripped versions of Jest matcher utilities
10
+ const printExpectedClean = (0, colorHelpers_1.createColorStripWrapper)(jest_matcher_utils_1.printExpected);
11
+ const printReceivedClean = (0, colorHelpers_1.createColorStripWrapper)(jest_matcher_utils_1.printReceived);
8
12
  function printExpectedValue(outcome, expected) {
9
- return expected !== undefined ? ` ${outcome} with ${(0, jest_matcher_utils_1.printExpected)(expected)}` : ` ${outcome}`;
13
+ return expected !== undefined ? ` ${outcome} with ${printExpectedClean(expected)}` : ` ${outcome}`;
10
14
  }
11
15
  function printExpectedResult(expect, isNot, expected) {
12
16
  return [
@@ -31,14 +35,14 @@ function printExpectedDetailedResult(expect, isNot, expectedMessage, expectedDet
31
35
  : expect === 'success'
32
36
  ? printExpectedValue('Not success', expectedMessage)
33
37
  : printExpectedValue('Not failure', expectedMessage),
34
- ` Detail: "${(0, jest_matcher_utils_1.printExpected)(expectedDetail)}"`
38
+ ` Detail: "${printExpectedClean(expectedDetail)}"`
35
39
  ].join('\n');
36
40
  }
37
41
  function printReceivedResult(received) {
38
42
  return [
39
43
  'Received:',
40
44
  received.isSuccess()
41
- ? ` Success with ${(0, jest_matcher_utils_1.printReceived)(received.value)}`
45
+ ? ` Success with ${printReceivedClean(received.value)}`
42
46
  : ` Failure with "${received.message}"`
43
47
  ].join('\n');
44
48
  }
@@ -46,8 +50,8 @@ function printReceivedDetailedResult(received) {
46
50
  return [
47
51
  'Received:',
48
52
  received.isSuccess()
49
- ? ` Success with "${(0, jest_matcher_utils_1.printReceived)(received.value)}"\n Detail: "${(0, jest_matcher_utils_1.printReceived)(received.detail)}"`
50
- : ` Failure with "${received.message}"\n Detail: "${(0, jest_matcher_utils_1.printReceived)(received.detail)}"`
53
+ ? ` Success with "${printReceivedClean(received.value)}"\n Detail: "${printReceivedClean(received.detail)}"`
54
+ : ` Failure with "${received.message}"\n Detail: "${printReceivedClean(received.detail)}"`
51
55
  ].join('\n');
52
56
  }
53
57
  //# sourceMappingURL=matcherHelpers.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-utils-jest",
3
- "version": "5.0.0-15",
3
+ "version": "5.0.0-17",
4
4
  "description": "Custom matchers for ts-utils result class",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [
@@ -42,10 +42,10 @@
42
42
  "@types/heft-jest": "1.0.6",
43
43
  "eslint-plugin-n": "^16.6.2",
44
44
  "eslint-plugin-tsdoc": "~0.4.0",
45
- "@fgv/ts-utils": "5.0.0-15"
45
+ "@fgv/ts-utils": "5.0.0-17"
46
46
  },
47
47
  "peerDependencies": {
48
- "@fgv/ts-utils": "5.0.0-15"
48
+ "@fgv/ts-utils": "5.0.0-17"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "heft build --clean",
@@ -1,10 +1,10 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`verify matchers accessibility toFailTestAndMatchSnapshot exists and works: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).toBe(expected) // Object.is equality
4
+ "expect(received).toBe(expected) // Object.is equality
5
5
 
6
- Expected: false
7
- Received: true"
6
+ Expected: false
7
+ Received: true"
8
8
  `;
9
9
 
10
10
  exports[`verify matchers accessibility toSucceedAndMatchSnapshot exists and works: toSucceedAndMatchSnapshot 1`] = `
@@ -1,7 +1,7 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toFail reports details for a failed test with .not: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).not.toFail(expected)
4
+ "expect(received).not.toFail(expected)
5
5
  Expected:
6
6
  Not failure
7
7
  Received:
@@ -9,9 +9,9 @@ Received:
9
9
  `;
10
10
 
11
11
  exports[`.toFail reports details for a failed test: toFailTestAndMatchSnapshot 1`] = `
12
- "expect(received).toFail(expected)
12
+ "expect(received).toFail(expected)
13
13
  Expected:
14
14
  Failure
15
15
  Received:
16
- Success with \\"hello\\""
16
+ Success with \\"hello\\""
17
17
  `;
@@ -1,14 +1,14 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toFailTest logs details correctly for a failed .not result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(callback).not.toFailTest(expected)
4
+ "expect(callback).not.toFailTest(expected)
5
5
  Expected:
6
6
  Not failure
7
7
  Received: Test failed"
8
8
  `;
9
9
 
10
10
  exports[`.toFailTest logs details correctly for a failed result: toFailTestAndMatchSnapshot 1`] = `
11
- "expect(callback).toFailTest(expected)
11
+ "expect(callback).toFailTest(expected)
12
12
  Expected:
13
13
  Failure
14
14
  Received: Test passed"
@@ -1,14 +1,14 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toFailTestAndMatchSnapshot logs details correctly for a failed result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(callback).toFailTestAndMatchSnapshot(expected)
4
+ "expect(callback).toFailTestAndMatchSnapshot(expected)
5
5
  Expected: Callback to fail with an error that matches snapshot
6
6
  Received: Callback succeeded"
7
7
  `;
8
8
 
9
9
  exports[`.toFailTestAndMatchSnapshot passes for a test that fails with a result matching the snapshot: toFailTestAndMatchSnapshot 1`] = `
10
- "expect(received).toBe(expected) // Object.is equality
10
+ "expect(received).toBe(expected) // Object.is equality
11
11
 
12
- Expected: false
13
- Received: true"
12
+ Expected: false
13
+ Received: true"
14
14
  `;
@@ -3,6 +3,7 @@ import { Context, toMatchSnapshot } from 'jest-snapshot';
3
3
  import { matcherName, predicate } from './predicate';
4
4
 
5
5
  import { matcherHint } from 'jest-matcher-utils';
6
+ import { stripAnsiColors } from '../../utils/colorHelpers';
6
7
 
7
8
  declare global {
8
9
  // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -40,7 +41,7 @@ export default {
40
41
  }
41
42
  return toMatchSnapshot.call(
42
43
  context,
43
- cbResult.value,
44
+ stripAnsiColors(cbResult.value),
44
45
  'toFailTestAndMatchSnapshot'
45
46
  ) as jest.CustomMatcherResult;
46
47
  }
@@ -1,34 +1,34 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toFailTestWith logs failure correctly when callback fails with .not: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(callback).not.toFailTestWith(expectedMessage)
4
+ "expect(callback).not.toFailTestWith(expectedMessage)
5
5
  Expected:
6
- Not failure with /expect/i
6
+ Not failure with /expect/i
7
7
  Received: Callback failed with:
8
8
  >>>>
9
- expect(received).toBe(expected) // Object.is equality
9
+ expect(received).toBe(expected) // Object.is equality
10
10
 
11
- Expected: false
12
- Received: true
11
+ Expected: false
12
+ Received: true
13
13
  <<<<"
14
14
  `;
15
15
 
16
16
  exports[`.toFailTestWith logs failure correctly when callback returns an unexpected value: toFailTestAndMatchSnapshot 1`] = `
17
- "expect(callback).toFailTestWith(expectedMessage)
17
+ "expect(callback).toFailTestWith(expectedMessage)
18
18
  Expected:
19
- Failure with /random text/i
19
+ Failure with /random text/i
20
20
  Received: Callback failed with:
21
21
  >>>>
22
- expect(received).toBe(expected) // Object.is equality
22
+ expect(received).toBe(expected) // Object.is equality
23
23
 
24
- Expected: \\"goodbye\\"
25
- Received: \\"hello\\"
24
+ Expected: \\"goodbye\\"
25
+ Received: \\"hello\\"
26
26
  <<<<"
27
27
  `;
28
28
 
29
29
  exports[`.toFailTestWith logs failure correctly when callback succeeds: toFailTestAndMatchSnapshot 1`] = `
30
- "expect(callback).toFailTestWith(expectedMessage)
30
+ "expect(callback).toFailTestWith(expectedMessage)
31
31
  Expected:
32
- Failure with \\"whatever\\"
32
+ Failure with \\"whatever\\"
33
33
  Received: Callback succeeded"
34
34
  `;
@@ -1,25 +1,25 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toFailWith reports details correctly on a failure due to a success result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).toFailWith(expected)
4
+ "expect(received).toFailWith(expected)
5
5
  Expected:
6
- Failure with \\"hello\\"
6
+ Failure with \\"hello\\"
7
7
  Received:
8
- Success with \\"hello\\""
8
+ Success with \\"hello\\""
9
9
  `;
10
10
 
11
11
  exports[`.toFailWith reports details correctly on a failure due to non-matching failure value: toFailTestAndMatchSnapshot 1`] = `
12
- "expect(received).toFailWith(expected)
12
+ "expect(received).toFailWith(expected)
13
13
  Expected:
14
- Failure with \\"error\\"
14
+ Failure with \\"error\\"
15
15
  Received:
16
16
  Failure with \\"oops\\""
17
17
  `;
18
18
 
19
19
  exports[`.toFailWith reports details correctly on a failure with a .not: toFailTestAndMatchSnapshot 1`] = `
20
- "expect(received).not.toFailWith(expected)
20
+ "expect(received).not.toFailWith(expected)
21
21
  Expected:
22
- Not failure with \\"oops\\"
22
+ Not failure with \\"oops\\"
23
23
  Received:
24
24
  Failure with \\"oops\\""
25
25
  `;
@@ -1,41 +1,41 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toFailWithDetail reports details correctly on a failure due to a success result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).toFailWith(expected)
4
+ "expect(received).toFailWith(expected)
5
5
  Expected:
6
- Failure with \\"hello\\"
7
- Detail: \\"\\"detail\\"\\"
6
+ Failure with \\"hello\\"
7
+ Detail: \\"\\"detail\\"\\"
8
8
  Received:
9
- Success with \\"\\"hello\\"\\"
10
- Detail: \\"undefined\\""
9
+ Success with \\"\\"hello\\"\\"
10
+ Detail: \\"undefined\\""
11
11
  `;
12
12
 
13
13
  exports[`.toFailWithDetail reports details correctly on a failure due to non-matching detail value: toFailTestAndMatchSnapshot 1`] = `
14
- "expect(received).toFailWith(expected)
14
+ "expect(received).toFailWith(expected)
15
15
  Expected:
16
- Failure with \\"error\\"
17
- Detail: \\"\\"detail\\"\\"
16
+ Failure with \\"error\\"
17
+ Detail: \\"\\"detail\\"\\"
18
18
  Received:
19
19
  Failure with \\"error\\"
20
- Detail: \\"\\"other detail\\"\\""
20
+ Detail: \\"\\"other detail\\"\\""
21
21
  `;
22
22
 
23
23
  exports[`.toFailWithDetail reports details correctly on a failure due to non-matching failure value: toFailTestAndMatchSnapshot 1`] = `
24
- "expect(received).toFailWith(expected)
24
+ "expect(received).toFailWith(expected)
25
25
  Expected:
26
- Failure with \\"error\\"
27
- Detail: \\"\\"detail\\"\\"
26
+ Failure with \\"error\\"
27
+ Detail: \\"\\"detail\\"\\"
28
28
  Received:
29
29
  Failure with \\"oops\\"
30
- Detail: \\"\\"detail\\"\\""
30
+ Detail: \\"\\"detail\\"\\""
31
31
  `;
32
32
 
33
33
  exports[`.toFailWithDetail reports details correctly on a failure with a .not: toFailTestAndMatchSnapshot 1`] = `
34
- "expect(received).not.toFailWith(expected)
34
+ "expect(received).not.toFailWith(expected)
35
35
  Expected:
36
- Not failure with \\"oops\\"
37
- Detail: \\"\\"detail\\"\\"
36
+ Not failure with \\"oops\\"
37
+ Detail: \\"\\"detail\\"\\"
38
38
  Received:
39
39
  Failure with \\"oops\\"
40
- Detail: \\"\\"detail\\"\\""
40
+ Detail: \\"\\"detail\\"\\""
41
41
  `;
@@ -1,77 +1,77 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if a .not test fails with few calls 1`] = `
4
- "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
4
+ "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
5
5
  Expected no call with arguments matching:
6
- [Any<String>]
6
+ [Any<String>]
7
7
  Received (1 total):
8
- *000: [\\"arg1\\"]"
8
+ *000: [\\"arg1\\"]"
9
9
  `;
10
10
 
11
11
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if a .not test fails with many calls "for a call in the middle" 1`] = `
12
- "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
12
+ "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
13
13
  Expected no call with arguments matching:
14
- [\\"arg3\\"]
14
+ [\\"arg3\\"]
15
15
  Received (7 total):
16
- 002: [\\"arg2\\"]
17
- *003: [\\"arg3\\"]
18
- 004: [\\"arg4\\"]"
16
+ 002: [\\"arg2\\"]
17
+ *003: [\\"arg3\\"]
18
+ 004: [\\"arg4\\"]"
19
19
  `;
20
20
 
21
21
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if a .not test fails with many calls "for the first call" 1`] = `
22
- "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
22
+ "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
23
23
  Expected no call with arguments matching:
24
- [\\"arg0\\"]
24
+ [\\"arg0\\"]
25
25
  Received (7 total):
26
- *000: [\\"arg0\\"]
27
- 001: [\\"arg1\\"]
28
- 002: [\\"arg2\\"]"
26
+ *000: [\\"arg0\\"]
27
+ 001: [\\"arg1\\"]
28
+ 002: [\\"arg2\\"]"
29
29
  `;
30
30
 
31
31
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if a .not test fails with many calls "for the last call" 1`] = `
32
- "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
32
+ "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
33
33
  Expected no call with arguments matching:
34
- [\\"arg6\\"]
34
+ [\\"arg6\\"]
35
35
  Received (7 total):
36
- 004: [\\"arg4\\"]
37
- 005: [\\"arg5\\"]
38
- *006: [\\"arg6\\"]"
36
+ 004: [\\"arg4\\"]
37
+ 005: [\\"arg5\\"]
38
+ *006: [\\"arg6\\"]"
39
39
  `;
40
40
 
41
41
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if a .not test fails with many calls "for the second call" 1`] = `
42
- "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
42
+ "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
43
43
  Expected no call with arguments matching:
44
- [StringMatching /1/]
44
+ [StringMatching /1/]
45
45
  Received (7 total):
46
- 000: [\\"arg0\\"]
47
- *001: [\\"arg1\\"]
48
- 002: [\\"arg2\\"]"
46
+ 000: [\\"arg0\\"]
47
+ *001: [\\"arg1\\"]
48
+ 002: [\\"arg2\\"]"
49
49
  `;
50
50
 
51
51
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if a .not test fails with many calls "for the second-to-last-call" 1`] = `
52
- "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
52
+ "expect(received).not.toHaveBeenCalledWithArgumentsMatching(expected)
53
53
  Expected no call with arguments matching:
54
- [\\"arg5\\"]
54
+ [\\"arg5\\"]
55
55
  Received (7 total):
56
- 004: [\\"arg4\\"]
57
- *005: [\\"arg5\\"]
58
- 006: [\\"arg6\\"]"
56
+ 004: [\\"arg4\\"]
57
+ *005: [\\"arg5\\"]
58
+ 006: [\\"arg6\\"]"
59
59
  `;
60
60
 
61
61
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if a call is not matched 1`] = `
62
- "expect(received).toHaveBeenCalledWithArgumentsMatching(expected)
62
+ "expect(received).toHaveBeenCalledWithArgumentsMatching(expected)
63
63
  Expected call with arguments matching:
64
- [\\"call7\\"]
64
+ [\\"call7\\"]
65
65
  Received (6 total):
66
- 003: [\\"call4\\"]
67
- 004: [\\"call5\\"]
68
- 005: [\\"call6\\"]"
66
+ 003: [\\"call4\\"]
67
+ 004: [\\"call5\\"]
68
+ 005: [\\"call6\\"]"
69
69
  `;
70
70
 
71
71
  exports[`.toHaveBeenCalledWithArgumentsMatching reports a helpful message if function was not called 1`] = `
72
- "expect(received).toHaveBeenCalledWithArgumentsMatching(expected)
72
+ "expect(received).toHaveBeenCalledWithArgumentsMatching(expected)
73
73
  Expected call with arguments matching:
74
- [\\"call7\\"]
74
+ [\\"call7\\"]
75
75
  Received (0 total):"
76
76
  `;
77
77
 
@@ -1,15 +1,15 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toSucceed reports details for a failed .not test: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).not.toSucceed(expected)
4
+ "expect(received).not.toSucceed(expected)
5
5
  Expected:
6
6
  Not success
7
7
  Received:
8
- Success with \\"hello\\""
8
+ Success with \\"hello\\""
9
9
  `;
10
10
 
11
11
  exports[`.toSucceed reports details for a failed test: toFailTestAndMatchSnapshot 1`] = `
12
- "expect(received).toSucceed(expected)
12
+ "expect(received).toSucceed(expected)
13
13
  Expected:
14
14
  Success
15
15
  Received:
@@ -1,7 +1,7 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toSucceedAndMatchSnapshot logs details correctly for a failed result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(callback).toSucceedAndMatchInlineSnapshot(expected)
4
+ "expect(callback).toSucceedAndMatchInlineSnapshot(expected)
5
5
  Expected:
6
6
  Callback to succeed with a result that matches the snapshot
7
7
  Received:
@@ -9,7 +9,7 @@ Received:
9
9
  `;
10
10
 
11
11
  exports[`.toSucceedAndMatchSnapshot logs details correctly for a failed result: toFailTestAndMatchSnapshot 2`] = `
12
- "expect(received).not.toMatchInlineSnapshot(properties, snapshot)
12
+ "expect(received).not.toMatchInlineSnapshot(properties, snapshot)
13
13
 
14
- Matcher error: Snapshot matchers cannot be used with not"
14
+ Matcher error: Snapshot matchers cannot be used with not"
15
15
  `;
@@ -1,7 +1,7 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toSucceedAndMatchSnapshot logs details correctly for a failed result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(callback).toSucceedAndMatchSnapshot(expected)
4
+ "expect(callback).toSucceedAndMatchSnapshot(expected)
5
5
  Expected:
6
6
  Callback to succeed with a result that matches the snapshot
7
7
  Received:
@@ -9,9 +9,9 @@ Received:
9
9
  `;
10
10
 
11
11
  exports[`.toSucceedAndMatchSnapshot logs details correctly for a failed result: toFailTestAndMatchSnapshot 2`] = `
12
- "expect(received).not.toMatchSnapshot(hint)
12
+ "expect(received).not.toMatchSnapshot(hint)
13
13
 
14
- Matcher error: Snapshot matchers cannot be used with not"
14
+ Matcher error: Snapshot matchers cannot be used with not"
15
15
  `;
16
16
 
17
17
  exports[`.toSucceedAndMatchSnapshot passes for a success result that matches the snapshot: toSucceedAndMatchSnapshot 1`] = `
@@ -1,37 +1,37 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toSucceedAndSatisfy reports details when callback fails an expectation: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).toBe(expected) // Object.is equality
4
+ "expect(received).toBe(expected) // Object.is equality
5
5
 
6
- Expected: \\"goodbye\\"
7
- Received: \\"hello\\""
6
+ Expected: \\"goodbye\\"
7
+ Received: \\"hello\\""
8
8
  `;
9
9
 
10
10
  exports[`.toSucceedAndSatisfy reports details when callback returns false: toFailTestAndMatchSnapshot 1`] = `
11
- "expect(result).toSucceedAndSatisfy(callback)
11
+ "expect(result).toSucceedAndSatisfy(callback)
12
12
  Expected:
13
- Success with \\"successful callback\\"
13
+ Success with \\"successful callback\\"
14
14
  Received:
15
- Success with \\"hello\\"
15
+ Success with \\"hello\\"
16
16
  Callback returned false"
17
17
  `;
18
18
 
19
19
  exports[`.toSucceedAndSatisfy reports details when callback throws an exception: toFailTestAndMatchSnapshot 1`] = `"UH OH AN ERROR"`;
20
20
 
21
21
  exports[`.toSucceedAndSatisfy reports details when received is a failure result: toFailTestAndMatchSnapshot 1`] = `
22
- "expect(result).toSucceedAndSatisfy(callback)
22
+ "expect(result).toSucceedAndSatisfy(callback)
23
23
  Expected:
24
- Success with \\"successful callback\\"
24
+ Success with \\"successful callback\\"
25
25
  Received:
26
26
  Failure with \\"oops\\"
27
27
  Callback was not invoked"
28
28
  `;
29
29
 
30
30
  exports[`.toSucceedAndSatisfy reports details with success and .not: toFailTestAndMatchSnapshot 1`] = `
31
- "expect(result).not.toSucceedAndSatisfy(callback)
31
+ "expect(result).not.toSucceedAndSatisfy(callback)
32
32
  Expected:
33
- Not success with \\"successful callback\\"
33
+ Not success with \\"successful callback\\"
34
34
  Received:
35
- Success with \\"hello\\"
35
+ Success with \\"hello\\"
36
36
  Callback returned true"
37
37
  `;
@@ -1,25 +1,25 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toSucceedWith reports details when it fails due to a failure result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).toSucceedWith(expected)
4
+ "expect(received).toSucceedWith(expected)
5
5
  Expected:
6
- Success with \\"oops\\"
6
+ Success with \\"oops\\"
7
7
  Received:
8
8
  Failure with \\"oops\\""
9
9
  `;
10
10
 
11
11
  exports[`.toSucceedWith reports details when it fails due to a success result with .not: toFailTestAndMatchSnapshot 1`] = `
12
- "expect(received).not.toSucceedWith(expected)
12
+ "expect(received).not.toSucceedWith(expected)
13
13
  Expected:
14
- Not success with \\"hello\\"
14
+ Not success with \\"hello\\"
15
15
  Received:
16
- Success with \\"hello\\""
16
+ Success with \\"hello\\""
17
17
  `;
18
18
 
19
19
  exports[`.toSucceedWith reports details when it fails due to success with a non-matching value: toFailTestAndMatchSnapshot 1`] = `
20
- "expect(received).toSucceedWith(expected)
20
+ "expect(received).toSucceedWith(expected)
21
21
  Expected:
22
- Success with \\"goodbye\\"
22
+ Success with \\"goodbye\\"
23
23
  Received:
24
- Success with \\"hello\\""
24
+ Success with \\"hello\\""
25
25
  `;
@@ -1,41 +1,41 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`.toSucceedWithDetail reports details when it fails due to a failure result: toFailTestAndMatchSnapshot 1`] = `
4
- "expect(received).toSucceedWithDetail(expected)
4
+ "expect(received).toSucceedWithDetail(expected)
5
5
  Expected:
6
- Success with \\"oops\\"
7
- Detail: \\"\\"detail\\"\\"
6
+ Success with \\"oops\\"
7
+ Detail: \\"\\"detail\\"\\"
8
8
  Received:
9
9
  Failure with \\"oops\\"
10
- Detail: \\"\\"detail\\"\\""
10
+ Detail: \\"\\"detail\\"\\""
11
11
  `;
12
12
 
13
13
  exports[`.toSucceedWithDetail reports details when it fails due to a success result with .not: toFailTestAndMatchSnapshot 1`] = `
14
- "expect(received).not.toSucceedWithDetail(expected)
14
+ "expect(received).not.toSucceedWithDetail(expected)
15
15
  Expected:
16
- Not success with \\"hello\\"
17
- Detail: \\"\\"detail\\"\\"
16
+ Not success with \\"hello\\"
17
+ Detail: \\"\\"detail\\"\\"
18
18
  Received:
19
- Success with \\"\\"hello\\"\\"
20
- Detail: \\"\\"detail\\"\\""
19
+ Success with \\"\\"hello\\"\\"
20
+ Detail: \\"\\"detail\\"\\""
21
21
  `;
22
22
 
23
23
  exports[`.toSucceedWithDetail reports details when it fails due to success with a non-matching detail: toFailTestAndMatchSnapshot 1`] = `
24
- "expect(received).toSucceedWithDetail(expected)
24
+ "expect(received).toSucceedWithDetail(expected)
25
25
  Expected:
26
- Success with \\"hello\\"
27
- Detail: \\"\\"other detail\\"\\"
26
+ Success with \\"hello\\"
27
+ Detail: \\"\\"other detail\\"\\"
28
28
  Received:
29
- Success with \\"\\"hello\\"\\"
30
- Detail: \\"\\"detail\\"\\""
29
+ Success with \\"\\"hello\\"\\"
30
+ Detail: \\"\\"detail\\"\\""
31
31
  `;
32
32
 
33
33
  exports[`.toSucceedWithDetail reports details when it fails due to success with a non-matching value: toFailTestAndMatchSnapshot 1`] = `
34
- "expect(received).toSucceedWithDetail(expected)
34
+ "expect(received).toSucceedWithDetail(expected)
35
35
  Expected:
36
- Success with \\"goodbye\\"
37
- Detail: \\"\\"detail\\"\\"
36
+ Success with \\"goodbye\\"
37
+ Detail: \\"\\"detail\\"\\"
38
38
  Received:
39
- Success with \\"\\"hello\\"\\"
40
- Detail: \\"\\"detail\\"\\""
39
+ Success with \\"\\"hello\\"\\"
40
+ Detail: \\"\\"detail\\"\\""
41
41
  `;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Utility functions for handling ANSI color codes in test output.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ /**
8
+ * Regular expression to match ANSI color/style escape sequences.
9
+ *
10
+ * This matches:
11
+ * - ESC character (ASCII 27) followed by `[`
12
+ * - `[0-9;]*` - Zero or more digits and semicolons (parameters)
13
+ * - `[a-zA-Z]` - Single letter command (m for SGR, K for erase, etc.)
14
+ *
15
+ * Examples of what this matches:
16
+ * - `\u001b[31m` - Red foreground
17
+ * - `\u001b[39m` - Default foreground
18
+ * - `\u001b[2m` - Dim/faint
19
+ * - `\u001b[22m` - Normal intensity
20
+ *
21
+ * Using a pre-compiled regex literal to satisfy ESLint security requirements.
22
+ * We use the unicode escape sequence \\u001b instead of \\x1b to avoid control-regex warnings.
23
+ */
24
+ // eslint-disable-next-line @rushstack/security/no-unsafe-regexp, no-control-regex
25
+ const ANSI_COLOR_REGEX: RegExp = /\u001b\[[0-9;]*[a-zA-Z]/g;
26
+
27
+ /**
28
+ * Strips ANSI color/style escape sequences from a string.
29
+ *
30
+ * This function removes all ANSI escape sequences commonly used for
31
+ * terminal colors and text styling, making the output suitable for
32
+ * color-agnostic snapshot testing.
33
+ *
34
+ * @param text - The text that may contain ANSI escape sequences
35
+ * @returns The text with all ANSI escape sequences removed
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const coloredText = '\u001b[31mError:\u001b[39m Something failed';
40
+ * const plainText = stripAnsiColors(coloredText);
41
+ * console.log(plainText); // "Error: Something failed"
42
+ * ```
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // Common usage in Jest matchers
47
+ * const formattedOutput = matcherUtils.printReceived(value);
48
+ * const cleanOutput = stripAnsiColors(formattedOutput);
49
+ * expect(cleanOutput).toMatchSnapshot();
50
+ * ```
51
+ *
52
+ * @public
53
+ */
54
+ export function stripAnsiColors(text: string): string {
55
+ return text.replace(ANSI_COLOR_REGEX, '');
56
+ }
57
+
58
+ /**
59
+ * Creates a wrapper function that strips ANSI colors from any string-returning function.
60
+ *
61
+ * This is useful for wrapping Jest matcher utility functions to make them
62
+ * color-agnostic for snapshot testing.
63
+ *
64
+ * @param fn - A function that returns a string (potentially with ANSI colors)
65
+ * @returns A wrapped function that returns the same string with colors stripped
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * import { printReceived } from 'jest-matcher-utils';
70
+ *
71
+ * const printReceivedClean = createColorStripWrapper(printReceived);
72
+ * const output = printReceivedClean(someValue); // No colors
73
+ * ```
74
+ *
75
+ * @public
76
+ */
77
+ export function createColorStripWrapper<T extends (...args: unknown[]) => string>(fn: T): T {
78
+ return ((...args: Parameters<T>) => {
79
+ const result = fn(...args);
80
+ return stripAnsiColors(result);
81
+ }) as T;
82
+ }
@@ -1,8 +1,13 @@
1
1
  import { printExpected, printReceived } from 'jest-matcher-utils';
2
2
  import { DetailedResult, Result } from '../ts-utils';
3
+ import { createColorStripWrapper } from './colorHelpers';
4
+
5
+ // Create color-stripped versions of Jest matcher utilities
6
+ const printExpectedClean: typeof printExpected = createColorStripWrapper(printExpected);
7
+ const printReceivedClean: typeof printReceived = createColorStripWrapper(printReceived);
3
8
 
4
9
  function printExpectedValue<T>(outcome: string, expected?: T): string {
5
- return expected !== undefined ? ` ${outcome} with ${printExpected(expected)}` : ` ${outcome}`;
10
+ return expected !== undefined ? ` ${outcome} with ${printExpectedClean(expected)}` : ` ${outcome}`;
6
11
  }
7
12
 
8
13
  export function printExpectedResult<T>(expect: 'success' | 'failure', isNot: boolean, expected?: T): string {
@@ -34,7 +39,7 @@ export function printExpectedDetailedResult<T, TD>(
34
39
  : expect === 'success'
35
40
  ? printExpectedValue('Not success', expectedMessage)
36
41
  : printExpectedValue('Not failure', expectedMessage),
37
- ` Detail: "${printExpected(expectedDetail)}"`
42
+ ` Detail: "${printExpectedClean(expectedDetail)}"`
38
43
  ].join('\n');
39
44
  }
40
45
 
@@ -42,7 +47,7 @@ export function printReceivedResult<T>(received: Result<T>): string {
42
47
  return [
43
48
  'Received:',
44
49
  received.isSuccess()
45
- ? ` Success with ${printReceived(received.value)}`
50
+ ? ` Success with ${printReceivedClean(received.value)}`
46
51
  : ` Failure with "${received.message}"`
47
52
  ].join('\n');
48
53
  }
@@ -51,7 +56,9 @@ export function printReceivedDetailedResult<T, TD>(received: DetailedResult<T, T
51
56
  return [
52
57
  'Received:',
53
58
  received.isSuccess()
54
- ? ` Success with "${printReceived(received.value)}"\n Detail: "${printReceived(received.detail)}"`
55
- : ` Failure with "${received.message}"\n Detail: "${printReceived(received.detail)}"`
59
+ ? ` Success with "${printReceivedClean(received.value)}"\n Detail: "${printReceivedClean(
60
+ received.detail
61
+ )}"`
62
+ : ` Failure with "${received.message}"\n Detail: "${printReceivedClean(received.detail)}"`
56
63
  ].join('\n');
57
64
  }