From 6f09e43dfbbcf8de0a4aa936cf52ce6a651f7408 Mon Sep 17 00:00:00 2001
From: Bryan English <bryan@bryanenglish.com>
Date: Mon, 10 Oct 2022 15:26:29 -0400
Subject: [PATCH] test_runner: add extra fields in AssertionError YAML

Many TAP reporters offer special-case handling of YAML objects
containing `expected`, `actual`, and `operator` fields, as produced by
`AssertionError` and similar userland Error classes.
---
 lib/internal/test_runner/tap_stream.js   | 25 ++++++++++++++++++++++++
 test/message/test_runner_describe_it.out |  3 +++
 test/message/test_runner_output.out      |  3 +++
 3 files changed, 31 insertions(+)

diff --git a/lib/internal/test_runner/tap_stream.js b/lib/internal/test_runner/tap_stream.js
index ebd825a3dc9236..efd73e1e945995 100644
--- a/lib/internal/test_runner/tap_stream.js
+++ b/lib/internal/test_runner/tap_stream.js
@@ -179,17 +179,30 @@ function jsToYaml(indent, name, value) {
       code,
       failureType,
       message,
+      expected,
+      actual,
+      operator,
       stack,
     } = value;
     let errMsg = message ?? '<unknown error>';
     let errStack = stack;
     let errCode = code;
+    let errExpected = expected;
+    let errActual = actual;
+    let errOperator = operator;
+    let errIsAssertion = isAssertionLike(value);
 
     // If the ERR_TEST_FAILURE came from an error provided by user code,
     // then try to unwrap the original error message and stack.
     if (code === 'ERR_TEST_FAILURE' && kUnwrapErrors.has(failureType)) {
       errStack = cause?.stack ?? errStack;
       errCode = cause?.code ?? errCode;
+      if (isAssertionLike(cause)) {
+        errExpected = cause.expected;
+        errActual = cause.actual;
+        errOperator = cause.operator ?? errOperator;
+        errIsAssertion = true;
+      }
       if (failureType === kTestCodeFailure) {
         errMsg = cause?.message ?? errMsg;
       }
@@ -201,6 +214,14 @@ function jsToYaml(indent, name, value) {
       result += jsToYaml(indent, 'code', errCode);
     }
 
+    if (errIsAssertion) {
+      result += jsToYaml(indent, 'expected', errExpected);
+      result += jsToYaml(indent, 'actual', errActual);
+      if (errOperator) {
+        result += jsToYaml(indent, 'operator', errOperator);
+      }
+    }
+
     if (typeof errStack === 'string') {
       const frames = [];
 
@@ -229,4 +250,8 @@ function jsToYaml(indent, name, value) {
   return result;
 }
 
+function isAssertionLike(value) {
+  return value && typeof value === 'object' && 'expected' in value && 'actual' in value;
+}
+
 module.exports = { TapStream };
diff --git a/test/message/test_runner_describe_it.out b/test/message/test_runner_describe_it.out
index a1fa5fc1faaf04..7ca922397a52b3 100644
--- a/test/message/test_runner_describe_it.out
+++ b/test/message/test_runner_describe_it.out
@@ -123,6 +123,9 @@ not ok 13 - async assertion fail
     true !== false
     
   code: 'ERR_ASSERTION'
+  expected: false
+  actual: true
+  operator: 'strictEqual'
   stack: |-
     *
     *
diff --git a/test/message/test_runner_output.out b/test/message/test_runner_output.out
index fe5698e9d031f0..938989b1b34146 100644
--- a/test/message/test_runner_output.out
+++ b/test/message/test_runner_output.out
@@ -133,6 +133,9 @@ not ok 13 - async assertion fail
     true !== false
     
   code: 'ERR_ASSERTION'
+  expected: false
+  actual: true
+  operator: 'strictEqual'
   stack: |-
     *
     *