{
  "$type": "site.standard.document",
  "content": {
    "$type": "site.standard.content.markdown",
    "text": "In my day job I code React components. React is a good solution to allow large teams to make significant applications fast and under a standardized set of rules. Without a library like React, the Javascript ecosystem would allow for many different ways of achieving a result. This makes it challenging for teams to gain traction.\n\nHowever, it doesn’t make it great for doing very specific things. Even things that you might think should be easy. This is often my frustration with testing library components, along with using a library to test that library. Yo dawg, I heard you like libraries.\n\nI’m using [Cypress](https://www.cypress.io/) for testing as it has a solid API and makes assertions in a true browser environment. I’m also using [Chai’s expect API](https://www.chaijs.com/api/bdd/) to help with the assertion.\n\n## Asserting ref placement\n\nOne of the biggest annoyances of learning React is not manipulating the DOM like I used to in the jQuery days. The DOM is very powerful, and we’ve got lots of awesome APIs in the browser that do all sorts of things. But React doesn’t want you to use any of those. One of my biggest pain points was getting an element to programmatically focus. To do that in React, you may have [seen something like this](https://react.dev/learn/manipulating-the-dom-with-refs#example-focusing-a-text-input):\n\n```\nfunction MyInputWithButton(props) {\n    const inputRef = React.useRef(null);\n\n    function handleClick() {\n        inputRef.current.focus();\n    }\n\n    return (\n        <>\n            <input { ...props } ref={ inputRef }/>\n            <button onClick={ handleClick }>Focus the input</button>\n        </>\n    );\n}\n```\n\nHowever, someone may want to supply their own `ref`. That’s where `React.forwardRef()` comes in. When we include this, things get more complicated.\n\n```\nconst MyInputWithButton = React.forwardRef((props, ref) => {\n    const inputRef = React.useRef(null);\n    React.useImperativeHandle(ref, () => inputRef.current);\n\n    function handleClick() {\n        inputRef.current.focus();\n    }\n\n    return (\n        <>\n            <input { ...props } ref={ inputRef } />\n            <button onClick={ handleClick }>\n                Focus the input\n            </button>\n        </>\n    );\n});\n```\n\nWe’ll need to merge the `ref` that we made, with the one that is potentially incoming from outside. There’s [other ways of merging refs](https://www.jameskerr.blog/posts/react-useref-and-forward-ref/) but the point is that you’ll need to support this possibility if you allow the user to supply a `ref`.\n\nThat’s all well and good. Now that we have this component in a library, how do we test it? Specifically, how can we be sure that the `ref` is assigned to the `<input/>` and not the `<button/>`?\n\nThis is exactly the problem I came across recently. A regression came in that caused that `ref` to be assigned to more than one element by accident. So we want to be sure that wherever the `ref` is meant to be assigned, it stays there.\n\nIf you’re familiar with testing suites, you may think that this would a test that works:\n\n```\ndescribe('ref assignment', function () {\n    it('should assign ref to <input/>', function () {\n        const ref = React.createRef();\n        mount(<MyInputWithButton ref={ ref } />);\n        expect(ref.current.tagName).to.equal('INPUT'); // FAILURE\n    });\n});\n```\n\nUnfortunately, `ref.current` is `null` when written this way. In all my searching online, I couldn’t find any examples of folks testing if a `ref` is assigned to the proper element. Luckily, [ChatGPT](https://chatgpt.com/) saved the day with the following test that will do what we want.\n\n```\ndescribe('ref assignment', function () {\n    it('should assign ref to <input/>', function () {\n        const ref = React.createRef();\n        mount(<MyInputWithButton ref={ ref } />);\n        cy.get('input').then(([$input]) => expect(ref.current).to.equal($input)); // SUCCESS\n    });\n});\n```\n\nIt seems that the secret is to first select the DOM element within the environment and after selection to assert that value against the `ref.current`. Hopefully, this helps anyone else trying to assure that `ref`s are assigned properly. I’m going to be adding similar tests in places where we expose the `ref`.\n\n## Hover styles\n\nIf you thought that was fun, you’re going to love this next one. How might we test that styles appear on hover?\n\nYou might think it’s just a matter of triggering a mouseover on the element. However, that only works if we’re triggering some Javascript execution. If we’re trying to test that styles are applied on `:hover` using pure CSS, it’s not straightforward. There’s a plugin for Cypress called [`cypress-real-events`](https://github.com/dmtrKovalenko/cypress-real-events) that tries to help get closer to true events. This is done by hooking into the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) to trigger events, but even this isn’t helpful enough for what we want. From the project’s `README.md`:\n\n> Unfortunately, neither visual regression services like Happo and Percy nor plain cy.screenshot do not allow to test the hovering state. The hovering state is very different from any kind of js and css so it is not possible to capture it using dom snapshotting (like visual regression services do) and the screenshooting as well because cypress core itself is preventing hovering state in the screenshots.\n\nThe root cause of this problem is because hover is called a “Trusted Event”. This means *no technology can directly trigger a native CSS `:hover`*. The following is an excerpt from another post [Simulating hovers in Cypress](https://reflect.run/articles/simulating-hovers-in-cypress/):\n\n> Events that are generated by the user agent, either as a result of user interaction, or as a direct result of changes to the DOM, are trusted by the user agent with privileges that are not afforded to events generated by script through the `createEvent()` method, modified using the `initEvent()` method, or dispatched via the `dispatchEvent()` method. The isTrusted attribute of trusted events has a value of `true`, while untrusted events have an `isTrusted` attribute value of `false`.\n> \n> Most untrusted events will not trigger default actions, with the exception of the `click` event. This event always triggers the default action, even if the `isTrusted` attribute is `false` (this behavior is retained for backward-compatibility). All other untrusted events behave as if the `preventDefault()` method had been called on that event.”\n\nThe post has some workarounds, but none of them truly solve the problem as everything is still Javascript-centric. What we really need is to get that CSS style tied to `:hover` to show visually and be included in visual regression testing.\n\nIn my search for a solution, I came across a project for [Storybook](https://storybook.js.org/) called [Storybook Pseudo States](https://storybook.js.org/addons/storybook-addon-pseudo-states) and it claims to “force” your components to display pseudo states like hover. While I am currently using Storybook for local development, it’s not part of the testing suite. So I wanted to know what they technique was under the hood.\n\n[The file that is doing the magic](https://github.com/chromaui/storybook-addon-pseudo-states/blob/main/src/preview/rewriteStyleSheet.ts) is fairly overwhelming but there’s a simple way to think of what they are doing. You can even take a good guess at the file name (`rewriteStyleSheet.ts`). Here’s the basic steps:\n\n-   Get a reference to the element we want to assert hover on.\n-   Find the CSS that currently matches that element.\n-   Rewrite the `:hover` selector to a non-pseudo selector.\n-   Apply the non-psuedo selector to the element.\n-   Profit.\n\nThere’s a lot that this addon is trying to cover past those steps. Seems like half the file is managing the `:host` selector found in Shadow DOM contexts. This may also depend on how you have styles applied to your components. For my environment, the styles are written into `<style/>` tags and there’s no Shadow DOM to worry about. This is what I did in my Cypress test:\n\n```\nfunction forceHoverSnapshot($target, ...screenshotArgs) {\n    const HOVER_ATTRIBUTE_SELECTOR = '__data-cypress-hover__';\n\n    // If our replacement stylesheet doesn't exist\n    if (!document.getElementById(HOVER_ATTRIBUTE_SELECTOR)) {\n        // Find the stylesheets that associate with the target\n        const sheets = [...document.styleSheets].filter(({ cssRules }) => \n            [...cssRules].some({ selectorText }) => $target.matches(selectorText));\n        \n        // Concat all the cssText from associated styles\n        const cssText = sheets.reduce((css, { ownerNode }) => css + (ownerNode?.textContent || ''), '');\n\n        // Create and append new sheet\n        const $sheet = document.createElement('style');\n        $sheet.id = HOVER_ATTRIBUTE_SELECTOR;\n        sheets.at(-1).ownerNode.after($sheet);\n\n        // Append to replace instances of `:hover` with attribute\n        $sheet.textContent = cssText.replaceAll(':hover', `[${HOVER_ATTRIBUTE_SELECTOR}]`);\n    }\n\n    // Add the attribute to the target element\n    $target.setAttribute(HOVER_ATTRIBUTE_SELECTOR, '');\n\n    // Screenshot\n    screenshotArgs.length && cy.screenshot(...screenshotArgs);\n}\n```\n\nI imagine this would be helpful as a plugin in the future if it hasn’t been done yet. Knowing this, you *could* opt to simply include a `[data-hover]` selector to the same declaration as `:hover` so it can be manually triggered. In fact, it might even make sense if you wish to present a “visually focused” state, typically used when keyboard navigating. For me, I don’t want to include code meant exclusively for testing in my production environment (👀 on `data-cy` on public sites) so the extraneous selector is out of the question.\n\nSo, that’s been the trouble with testing components recently. I know there’s been so many attempts at making testing our components easier over the years but sometimes it is an uphill battle for what seems like the smallest ideas that should have been tackled many times before. If I come across others, I’ll update this post. Good luck testing out there, it’s a battlefield!",
    "version": "1.0"
  },
  "description": "A case of libraries getting in the way, and how to find peace with it.",
  "path": "/posts/testing-is-hard",
  "publishedAt": "2024-07-19T00:00:00.000Z",
  "site": "https://blog.damato.design",
  "tags": [
    "interactions"
  ],
  "textContent": "In my day job I code React components. React is a good solution to allow large teams to make significant applications fast and under a standardized set of rules. Without a library like React, the Javascript ecosystem would allow for many different ways of achieving a result. This makes it challenging for teams to gain traction.\n\nHowever, it doesn’t make it great for doing very specific things. Even things that you might think should be easy. This is often my frustration with testing library components, along with using a library to test that library. Yo dawg, I heard you like libraries.\n\nI’m using Cypress for testing as it has a solid API and makes assertions in a true browser environment. I’m also using Chai’s expect API to help with the assertion.\n\nAsserting ref placement\n\nOne of the biggest annoyances of learning React is not manipulating the DOM like I used to in the jQuery days. The DOM is very powerful, and we’ve got lots of awesome APIs in the browser that do all sorts of things. But React doesn’t want you to use any of those. One of my biggest pain points was getting an element to programmatically focus. To do that in React, you may have seen something like this:\n\nHowever, someone may want to supply their own . That’s where  comes in. When we include this, things get more complicated.\n\nWe’ll need to merge the  that we made, with the one that is potentially incoming from outside. There’s other ways of merging refs but the point is that you’ll need to support this possibility if you allow the user to supply a .\n\nThat’s all well and good. Now that we have this component in a library, how do we test it? Specifically, how can we be sure that the  is assigned to the  and not the ?\n\nThis is exactly the problem I came across recently. A regression came in that caused that  to be assigned to more than one element by accident. So we want to be sure that wherever the  is meant to be assigned, it stays there.\n\nIf you’re familiar with testing suites, you may think that this would a test that works:\n\nUnfortunately,  is  when written this way. In all my searching online, I couldn’t find any examples of folks testing if a  is assigned to the proper element. Luckily, ChatGPT saved the day with the following test that will do what we want.\n\nIt seems that the secret is to first select the DOM element within the environment and after selection to assert that value against the . Hopefully, this helps anyone else trying to assure that s are assigned properly. I’m going to be adding similar tests in places where we expose the .\n\nHover styles\n\nIf you thought that was fun, you’re going to love this next one. How might we test that styles appear on hover?\n\nYou might think it’s just a matter of triggering a mouseover on the element. However, that only works if we’re triggering some Javascript execution. If we’re trying to test that styles are applied on  using pure CSS, it’s not straightforward. There’s a plugin for Cypress called [](https://github.com/dmtrKovalenko/cypress-real-events) that tries to help get closer to true events. This is done by hooking into the Chrome DevTools Protocol to trigger events, but even this isn’t helpful enough for what we want. From the project’s :\n\nUnfortunately, neither visual regression services like Happo and Percy nor plain cy.screenshot do not allow to test the hovering state. The hovering state is very different from any kind of js and css so it is not possible to capture it using dom snapshotting (like visual regression services do) and the screenshooting as well because cypress core itself is preventing hovering state in the screenshots.\n\nThe root cause of this problem is because hover is called a “Trusted Event”. This means no technology can directly trigger a native CSS . The following is an excerpt from another post Simulating hovers in Cypress:\n\nEvents that are generated by the user agent, either as a result of user interaction, or as a direct result of changes to the DOM, are trusted by the user agent with privileges that are not afforded to events generated by script through the  method, modified using the  method, or dispatched via the  method. The isTrusted attribute of trusted events has a value of , while untrusted events have an  attribute value of .\nMost untrusted events will not trigger default actions, with the exception of the  event. This event always triggers the default action, even if the  attribute is  (this behavior is retained for backward-compatibility). All other untrusted events behave as if the  method had been called on that event.”\n\nThe post has some workarounds, but none of them truly solve the problem as everything is still Javascript-centric. What we really need is to get that CSS style tied to  to show visually and be included in visual regression testing.\n\nIn my search for a solution, I came across a project for Storybook called Storybook Pseudo States and it claims to “force” your components to display pseudo states like hover. While I am currently using Storybook for local development, it’s not part of the testing suite. So I wanted to know what they technique was under the hood.\n\nThe file that is doing the magic is fairly overwhelming but there’s a simple way to think of what they are doing. You can even take a good guess at the file name (). Here’s the basic steps:\nGet a reference to the element we want to assert hover on.\nFind the CSS that currently matches that element.\nRewrite the  selector to a non-pseudo selector.\nApply the non-psuedo selector to the element.\nProfit.\n\nThere’s a lot that this addon is trying to cover past those steps. Seems like half the file is managing the  selector found in Shadow DOM contexts. This may also depend on how you have styles applied to your components. For my environment, the styles are written into  tags and there’s no Shadow DOM to worry about. This is what I did in my Cypress test:\n\nI imagine this would be helpful as a plugin in the future if it hasn’t been done yet. Knowing this, you could opt to simply include a  selector to the same declaration as  so it can be manually triggered. In fact, it might even make sense if you wish to present a “visually focused” state, typically used when keyboard navigating. For me, I don’t want to include code meant exclusively for testing in my production environment (👀 on  on public sites) so the extraneous selector is out of the question.\n\nSo, that’s been the trouble with testing components recently. I know there’s been so many attempts at making testing our components easier over the years but sometimes it is an uphill battle for what seems like the smallest ideas that should have been tackled many times before. If I come across others, I’ll update this post. Good luck testing out there, it’s a battlefield!",
  "title": "Testing is hard",
  "updatedAt": "2026-06-13T16:19:08.361Z"
}