Skip to content
34 changes: 32 additions & 2 deletions src/Context/Support.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use Behat\Behat\Exception\PendingException;
use Exception;
use Mustangostang\Spyc;
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;

trait Support {

Expand Down Expand Up @@ -120,6 +122,12 @@ protected function check_string( $output, $expected, $action, $message = false,
if ( false === $message ) {
$message = $output;
}

$diff = $this->generate_diff( $expected, rtrim( $output, "\n" ) );
if ( ! empty( $diff ) ) {
$message .= "\n\n" . $diff;
}

throw new Exception( $message );
}
}
Expand All @@ -133,15 +141,21 @@ protected function check_string( $output, $expected, $action, $message = false,
protected function compare_tables( $expected_rows, $actual_rows, $output ): void {
// The first row is the header and must be present.
if ( $expected_rows[0] !== $actual_rows[0] ) {
throw new Exception( $output );
$expected_table = implode( "\n", $expected_rows );
$actual_table = implode( "\n", $actual_rows );
$diff = $this->generate_diff( $expected_table, $actual_table );
throw new Exception( $output . "\n\n" . $diff );
}

unset( $actual_rows[0] );
unset( $expected_rows[0] );

$missing_rows = array_diff( $expected_rows, $actual_rows );
if ( ! empty( $missing_rows ) ) {
throw new Exception( $output );
$expected_table = implode( "\n", $expected_rows );
$actual_table = implode( "\n", $actual_rows );
$diff = $this->generate_diff( $expected_table, $actual_table );
throw new Exception( $output . "\n\n" . $diff );
}
}

Expand Down Expand Up @@ -290,4 +304,20 @@ protected function check_that_yaml_string_contains_yaml_string( $actual_yaml, $e

return $this->compare_contents( $expected_value, $actual_value );
}

/**
* Generate a unified diff between two strings.
*
* @param string $expected The expected string.
* @param string $actual The actual string.
* @return string The unified diff output.
*/
protected function generate_diff( string $expected, string $actual ): string {
$builder = new UnifiedDiffOutputBuilder(
"--- Expected\n+++ Actual\n",
false
);
$differ = new Differ( $builder );
return $differ->diff( $expected, $actual );
}
}
53 changes: 47 additions & 6 deletions src/Context/ThenStepDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,21 @@ public function then_stdout_should_be_json_containing( PyStringNode $expected ):
$expected = $this->replace_variables( (string) $expected );

if ( ! $this->check_that_json_string_contains_json_string( $output, $expected ) ) {
throw new Exception( $this->result );
$message = (string) $this->result;
// Pretty print JSON for better diff readability.
$expected_decoded = json_decode( $expected );
$actual_decoded = json_decode( $output );
if ( null !== $expected_decoded && null !== $actual_decoded ) {
$expected_json = json_encode( $expected_decoded, JSON_PRETTY_PRINT );
$actual_json = json_encode( $actual_decoded, JSON_PRETTY_PRINT );
if ( false !== $expected_json && false !== $actual_json ) {
$diff = $this->generate_diff( $expected_json, $actual_json );
if ( ! empty( $diff ) ) {
$message .= "\n\n" . $diff;
}
}
}
throw new Exception( $message );
}
}

Expand Down Expand Up @@ -246,7 +260,19 @@ public function then_stdout_should_be_a_json_array_containing( PyStringNode $exp

$missing = array_diff( $expected_values, $actual_values );
if ( ! empty( $missing ) ) {
throw new Exception( $this->result );
$message = (string) $this->result;
// Pretty print JSON arrays for better diff readability.
if ( null !== $expected_values && null !== $actual_values ) {
$expected_json = json_encode( $expected_values, JSON_PRETTY_PRINT );
$actual_json = json_encode( $actual_values, JSON_PRETTY_PRINT );
if ( false !== $expected_json && false !== $actual_json ) {
$diff = $this->generate_diff( $expected_json, $actual_json );
if ( ! empty( $diff ) ) {
$message .= "\n\n" . $diff;
}
}
}
throw new Exception( $message );
}
}

Expand All @@ -269,19 +295,29 @@ public function then_stdout_should_be_csv_containing( TableNode $expected ): voi
$output = $this->result->stdout;

$expected_rows = $expected->getRows();
foreach ( $expected as &$row ) {
foreach ( $expected_rows as &$row ) {
foreach ( $row as &$value ) {
$value = $this->replace_variables( $value );
}
}

if ( ! $this->check_that_csv_string_contains_values( $output, $expected_rows ) ) {
throw new Exception( $this->result );
$message = (string) $this->result;
// Convert expected rows to CSV format for diff.
$expected_csv = '';
foreach ( $expected_rows as $row ) {
$expected_csv .= implode( ',', array_map( 'trim', $row ) ) . "\n";
}
$diff = $this->generate_diff( trim( $expected_csv ), trim( $output ) );
if ( ! empty( $diff ) ) {
$message .= "\n\n" . $diff;
}
throw new Exception( $message );
}
}

/**
* Expect STDOUT to be YAML containig certain content.
* Expect STDOUT to be YAML containing certain content.
*
* ```
* Scenario: My example scenario
Expand All @@ -303,7 +339,12 @@ public function then_stdout_should_be_yaml_containing( PyStringNode $expected ):
$expected = $this->replace_variables( (string) $expected );

if ( ! $this->check_that_yaml_string_contains_yaml_string( $output, $expected ) ) {
throw new Exception( $this->result );
$message = (string) $this->result;
$diff = $this->generate_diff( $expected, $output );
if ( ! empty( $diff ) ) {
$message .= "\n\n" . $diff;
}
throw new Exception( $message );
}
}

Expand Down
Loading