Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve theme.json test failure messages by pretty printing css for a more accurate diff #64077

Merged
merged 3 commits into from
Jul 31, 2024

Conversation

talldan
Copy link
Contributor

@talldan talldan commented Jul 30, 2024

What?

Related #60842

One of the issues with the theme json tests (the ones that people hate) is that when they fail they output the css diff as a big one-line string, and it's pretty much impossible to spot the difference. I had an idea to improve this, which is to apply some basic pretty printing to the CSS used in the assertion so that it's like a traditional stylesheet - on multiple lines.

When a test fails it'll now show a much more useful diff with only the specific selectors or rules that are actually different rather than all the css. I think this will make the tests much more useful, when making changes to production you'll now be able to see the exact impact.

This has been achieved using a custom assertion function that pretty prints the css strings.

The raw CSS is still useful for updating the failed test. To handle this, a custom message is appended on to the test failure containing the raw css. It does this by taking advantage of the fact that PHP unit test failures are thrown as exceptions. The exception from an assertion can be caught and re-thrown but with some extra information appended.

Testing Instructions

  1. Introduce an error in the expected css in one of the updates tests (preferably one of the ones with a big blog of css)
  2. Run the tests
  3. Observe that the diff output is now much more specific

Screenshots or screencast

Before

There was 1 failure:

1) WP_Theme_JSON_Gutenberg_Test::test_get_stylesheet_generates_layout_styles_with_spacing_presets
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--wp--preset--spacing--60); }.is-layout-flow  > :first-child{margin-block-start: 0;}.is-layout-flow  > :last-child{margin-block-end: 0;}.is-layout-flow  > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-constrained  > :first-child{margin-block-start: 0;}.is-layout-constrained  > :last-child{margin-block-end: 0;}.is-layout-constrained  > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-flex {gap: var(--wp--preset--spacing--60);}.is-layout-grid {gap: var(--wp--preset--spacing--60);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}'
+':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--wp--preset--spacing--60); }.is-layout-flow  > :first-child{margin-block-start: 0;}.is-layout-flow  > :last-child{margin-block-end: 0;}.is-layout-flow  > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-constrained  > :first-child{margin-block-start: 0;}.is-layout-constrained  > :last-child{margin-block-end: 0;}.is-layout-constrained  > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-flex {gap: var(--wp--preset--spacing--60);}.is-layout-grid {gap: var(--wp--preset--spacing--60);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}'

/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:1122

After

2) WP_Theme_JSON_Gutenberg_Test::test_get_stylesheet_generates_layout_styles_with_spacing_presets
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
 margin-inline-end: 2em;
 }

-.is-layout-constrained .alignright {
+.is-layout-constrained > .alignright {
 float: right;
 margin-inline-start: 2em;
 margin-inline-end: 0;

/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:77
/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:1157


If the change is expected, update the test case to this CSS:
:root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--wp--preset--spacing--60); }.is-layout-flow  > :first-child{margin-block-start: 0;}.is-layout-flow  > :last-child{margin-block-end: 0;}.is-layout-flow  > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-constrained  > :first-child{margin-block-start: 0;}.is-layout-constrained  > :last-child{margin-block-end: 0;}.is-layout-constrained  > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-flex {gap: var(--wp--preset--spacing--60);}.is-layout-grid {gap: var(--wp--preset--spacing--60);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}

/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:84
/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:1157

@talldan talldan added the [Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests. label Jul 30, 2024
@talldan talldan self-assigned this Jul 30, 2024
@andrewserong
Copy link
Contributor

andrewserong commented Jul 30, 2024

Overall I love the idea of making these tests more readable and useful, by highlighting the actual CSS that has changed, I think that will help a lot in the developer experience of working with these 👍

In particular the 'expected' CSS in the tests is still technically one big single line string, so when updating it you can't simply copy/paste the test output, it has to be updated bit by bit. Though this might be a good thing, as I think it means less chance of missing something in the diff.

In my experience with these tests, I think this could be a negative. It can be quite time consuming to update the tests, and ideally it would be possible to copy + paste the actual string in the error message to quickly update a test.

Would it be possible to try including the original received raw string in the third param of the assertion (as the assertion message), to see if that can give folks a usable string to copy/paste to update the tests?

From my perspective in updating these tests, we want to try to achieve two things at once, if we can:

  • Improve readability and usefulness of test errors
  • Improve the ability to quickly update the test

@talldan
Copy link
Contributor Author

talldan commented Jul 30, 2024

From my perspective in updating these tests, we want to try to achieve two things at once, if we can:

  • Improve readability and usefulness of test errors
  • Improve the ability to quickly update the test

Ok, I think I've found a way in ce6a6a6 to have both the pretty printed failures and the raw css string for updating the test case.

It'll now output something like this:

There was 1 failure:

1) WP_Theme_JSON_Gutenberg_Test::test_get_stylesheet_preset_classes_work_with_compounded_selectors
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'.wp-block-heading.has-white-color {
+'.wp-bloc-heading.has-white-color {
 color: var(--wp--preset--color--white) !important;
 }

/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:77
/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:755


If the change is expected, update the test case to this CSS:
.wp-bloc-heading.has-white-color{color: var(--wp--preset--color--white) !important;}.wp-block-heading.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.wp-block-heading.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}

/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:85
/var/www/html/wp-content/plugins/gutenberg/phpunit/class-wp-theme-json-test.php:755

The only downside is that it outputs the line of code for the custom assertion at the bottom (85) as well as the actual line of code of the failure (755). Also the code might be considered sketchy/hacky, but I think it's ok.

@talldan talldan changed the title Try improving theme.json test failure messages by formatting css used in assertions Try improving theme.json test failure messages by pretty printing css used in assertions while still outputting the raw unformatted css Jul 30, 2024
@talldan talldan marked this pull request as ready for review July 30, 2024 10:01
Copy link

github-actions bot commented Jul 30, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: talldan <[email protected]>
Co-authored-by: andrewserong <[email protected]>
Co-authored-by: ramonjd <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@talldan talldan added the No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core label Jul 30, 2024
…re along with raw css for updating the test

Update accidentally committed test failure

Fix expected,actual order and minimize diff

Fix - make sure the actual is output in addition to the pretty printed failure, not the expected

Update test expectation so test passes

Revert "Update test expectation so test passes"

This reverts commit c9f655a.

Update test expectation so test passes
@talldan talldan force-pushed the try/theme-json-test-css-formatting branch from 97f668d to ce6a6a6 Compare July 30, 2024 10:19
Comment on lines +600 to +603
$this->assertSameCSS( $variables, $theme_json->get_stylesheet( array( 'variables' ) ) );
$this->assertSameCSS( $styles, $theme_json->get_stylesheet( array( 'styles' ) ) );
$this->assertSameCSS( $presets, $theme_json->get_stylesheet( array( 'presets' ) ) );
$this->assertSameCSS( $all, $theme_json->get_stylesheet() );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice, I love how this abstraction means that we don't add any lines to individual tests 👍

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @talldan! The pretty printing in test failures is going to make a huge difference to anyone working on updating these tests, and the new assertSameCSS method still allows for quick copy/pasting to update the affected tests. Also the inclusion of the error message is a win as it helps nudge folks toward how to fix a failing test when the changes are expected 👍

Looking good while testing locally running npm run test:unit:php:base -- --filter WP_Theme_JSON_Gutenberg_Test:

image

✅ Changes in CSS are now pretty printed correctly
✅ Copy + pasting the expected string into the test gets the test passing again

The only downside is that it outputs the line of code for the custom assertion at the bottom (85) as well as the actual line of code of the failure (755). Also the code might be considered sketchy/hacky, but I think it's ok.

The double output of lines here seems quite minor to me, and at least it flags where the error message is generated. Also, I see what you mean in terms of the code possibly being seen as hacky, but given the clear developer experience wins and that it'll help debug failing tests, I think it's worth it 👍

LGTM! 🚀

Comment on lines 49 to 51
// Pretty print CSS in test assertions so that it provides a better diff when a test fails.
// Without this: the failing test outputs the entire css string as being different.
// With this: the failing test only highlights the specific CSS rule that is different.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny formatting nit: should this be a doc comment instead?

* @param string $expected The expected CSS.
* @param string $actual The actual CSS.
*/
private function assertSameCSS( $expected, $actual, $message = '' ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! I like!

@talldan
Copy link
Contributor Author

talldan commented Jul 31, 2024

Thanks for the reviews, I'll merge this and then follow up with the same change in core.

@talldan talldan merged commit de89af5 into trunk Jul 31, 2024
64 checks passed
@talldan talldan deleted the try/theme-json-test-css-formatting branch July 31, 2024 09:41
@talldan talldan changed the title Try improving theme.json test failure messages by pretty printing css used in assertions while still outputting the raw unformatted css Improve theme.json test failure messages by pretty printing css for a more accurate diff Jul 31, 2024
@github-actions github-actions bot added this to the Gutenberg 19.0 milestone Jul 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core [Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants