<![CDATA[Diogo Redin]]>https://diogoredin.github.io/https://diogoredin.github.io/favicon.pngDiogo Redinhttps://diogoredin.github.io/Ghost 5.26Fri, 13 Jan 2023 17:20:11 GMT60<![CDATA[Building a Vanilla JavaScript & CSS Tooltip]]>https://diogoredin.github.io/building-a-vanilla-javascript-css-tooltip/63b5f26d71ec4d3f0b47cb3bFri, 14 Oct 2022 11:57:27 GMTBuilding a Vanilla JavaScript & CSS Tooltip

In this article, we are going to implement a tooltip in plain JavaScript and CSS. A tooltip is a message which appears when a cursor is positioned over an icon, image, links, or other elements in order to give more information to the user. These are usually non-interactble and not essential in a page.

Defining the component API

The first step for building this tooltip will be to create a HTML, CSS and JavaScript file. Alternatively you can follow along by creating a CodePen.

Our base index.html file looks something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <link rel="stylesheet" href="./style.css" />
    <title>Vanilla JavaScript and CSS Tooltip</title>
  </head>

  <body>
    <main>
      <p>
        Geckos are a group of usually small, usually nocturnal lizards. They are
        found on every continent except Australia.
      </p>

      <p>
        Many species of gecko have adhesive toe pads which enable them to climb
        walls and even windows.
      </p>
    </main>

    <script src="./main.js"></script>
  </body>
</html>

It includes two paragraphs where we will eventually place some tooltips and links to our stylesheet and main JavaScript file, where will have the tooltip logic.

Whenever developing a component, my first question is which API I would like it to have. My assumption is that a tooltip should be lean (without much boilerplate code), easy to use (and wrap around content) and accessible. Given these conditions we can start with something like:

<span data-tooltip="Hello world!">Australia</span>.

This snippet is not yet accessible (actually there isn't even a tooltip here yet), but we can do that with a bit of JavaScript.

Before we get to it, we can add some styling to the page, so we see the tooltips better:

main {
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.5rem;
  font-size: 16px;
  padding: 5%;
}

[data-tooltip] {
  text-decoration: underline;
}

And we can actually add some tooltips, with varying amounts of content. Since it should be possible to use tooltips inside paragraphs we opt for <span>, which is a valid child of <p> and displays itself as inline by default, only taking as much space as it's content requires. Using data-tooltip allow us to store extra information without other hacks such as non-standard attributes, or extra properties on the DOM.

<main>
  <p>
    Geckos are a group of usually small, usually nocturnal lizards. They are
    found on every continent except
    <span
      data-tooltip="Geckos are a group of usually small, usually nocturnal lizards."
      >Australia</span
    >.
  </p>

  <p>
    Many species of gecko have
    <span data-tooltip="Hi there!">adhesive</span> toe pads which enable them to
    climb walls and even windows.
  </p>
</main>

Adding tooltips to our HTML

So far we only have spans indicating where tooltips should be placed and with which content, so our next step should be to add them to our HTML. This will allow us to properly build the tooltip, but also expose them to search engine robots and make them accessible to screen readers.

const tooltips = document.querySelectorAll("[data-tooltip]");

querySelectorAll allows us to get all "tooltips" that should be placed in the document using a CSS selector. It gives us back an iterable HTML collection with all the elements found.

tooltips.forEach((trigger) => {
  let tooltip = document.createElement("div");

  tooltip.setAttribute("role", "tooltip");
  tooltip.setAttribute("inert", true);
  tooltip.textContent = trigger.dataset.tooltip;

  trigger.appendChild(tooltip);
});

We can take the elements and for each of them add an actual accessible tooltip:

  1. role=tooltip so screen readers know what the content is;
  2. inert because the user should not be able to interact with it (otherwise, it would be a popup);

After loading the page our HTML should have the following structure:

<main>
  <p>
    Geckos are a group of usually small, usually nocturnal lizards. They are
    found on every continent except
    <span
      data-tooltip="Geckos are a group of usually small, usually nocturnal lizards."
    >
      Australia
      <div role="tooltip" inert="true">
        Geckos are a group of usually small, usually nocturnal lizards.
      </div> </span
    >.
  </p>

  <p>
    Many species of gecko have
    <span data-tooltip="Hi there!">
      adhesive
      <div role="tooltip" inert="true">Hi there!</div>
    </span>
    toe pads which enable them to climb walls and even windows.
  </p>
</main>

Styling the Tooltip

In order to be able to see the tooltip in our page we can start by applying some width, height and positioning. The width: auto allows the tooltip to grow in size as it wants, while never exceeding the size of it's parent (this takes into account margins and padding). We however set max-width: 20% to ensure that a tooltip placed in the middle of the container doesn't become too large.

In terms of height, it's also set to height: auto so the tooltip can grow in height in case there is too much content. word-wrap is also left at it's default value (normal) so text breaks down into multiple lines. min-height: 25px ensures the tooltip always has an appropriate height, and line-height of the same size ensures a single line is displayed aligned in the middle. font-size: 1rem applies 1x the font size set in the root element of the page, which is good for accessibility because this is affected by what the font-size the user sets in their browser.

We apply a small margin at the top, so the tooltip is not too close to the content and padding as well, so there is space between the borders of the tooltip and the content. Most tooltips have a reduced opacity but only for the background, because the text should still have a good contrast. For this we can use a rgba value (red, green, blue, alpha), where the last value indicates the opacity of the color. This allows us to only apply opacity to the background and not the content. Lastly border-radius applies rounded corners to the element equally.

[role="tooltip"] {
  width: auto;
  max-width: 20%;
  height: auto;
  min-height: 25px;
  line-height: 25px;
  font-size: 1rem;
  background-color: rgba(0, 0, 0, 0.7);
  color: #ffffff;
  border-radius: 5px;
  margin-top: 10px;
  padding: 10px 15px;
}

Note: Right now the tooltip is always displayed, we will handle the displaying and hiding in a further section.

Building a Vanilla JavaScript & CSS Tooltip
Our styled tooltip at the end of this article

Positioning the Tooltip

Now that we have some basic styles we can actually position the tooltip correctly. Most of the times the desired position is in the middle of the hovered element, so that's what we are going to do.

First we attach an event handler to detect when the mouse enters an element that should display a tooltip. The mouseenter event is used because it's only triggered when the mouse pointer enters the div element, while the mouseover event is triggered when the mouse pointer enters the div element or its child elements (which would include the tooltip itself).

Once the event is triggered we try to find the tooltip nested inside using a querySelector again but now for a single element. getBoundingClientRect returns x, y referent to the position of the trigger element (e.g. a word) in the viewport and width and height of the trigger element.

Using this information and by also applying position: absolute in it's CSS, we firstly position the element at the start of the word. By summing half of the width of the word, we then position the start of the tooltip at the middle of the word. Because we actually want the tooltip to start before the middle, so their middles' coincide we apply a transformation of transform: translateX(-50%). The schema below should help you understand the positioning better.

const displayTooltip = (e) => {
  const trigger = e.target;
  const tooltip = trigger.querySelector("[role=tooltip]");

  const { x, y, width, height } = trigger.getBoundingClientRect();
  tooltip.style.left = `${Math.floor(x + width / 2)}px`;
  tooltip.style.top = `${Math.floor(y + height)}px`;
};

tooltips.forEach((trigger) => {
	...

  trigger.appendChild(tooltip);

	trigger.addEventListener("mouseenter", displayTooltip);
});
[role="tooltip"] {
	...
  position: absolute;
  transform: translateX(-50%);
}
Building a Vanilla JavaScript & CSS Tooltip

Fading in the Tooltip

Until now our tooltips are always displayed, and the mouseenter event is just applying the correct positioning. For tooltips the desired behavior is that they are hidden by default and only displayed after the user has spent some time with their mouse over the element. Tooltips also usually have a fade-in effect so they are more subtle in their appearance. In order to implement this the best property to manipulate is the opacity, while keeping the other display properties intact.

We will start by implementing the fade-in effect. The following CSS hides the tooltips by default, while keeping them present in the layout (so there are no jumps between elements on hover):

[role="tooltip"] {
  opacity: 0;
}

The transition property here indicates that whenever the opacity property changes it should be increased following a ease function (which is the default and specifies a transition effect with a slow start, then fast and then slow again). This animation takes place for 0.1s and gives an effect of fade-in to the tooltip.

[role="tooltip"].active {
  opacity: 1;
  transition: opacity 0.1s;
}

As you saw above, the opacity is only applied when the tooltip is considered active, so we need to add this class to our tooltip when it's hovered to actually apply the CSS:

const displayTooltip = (e) => {
  const trigger = e.target;
  const tooltip = trigger.querySelector("[role=tooltip]");

	...

  tooltip.classList.add("active");
};

const hideTooltip = (e) => {
  const tooltip = e.target.querySelector("[role=tooltip]");
  tooltip.classList.remove("active");
};

tooltips.forEach((trigger) => {
  ...

  trigger.appendChild(tooltip);

	trigger.addEventListener("mouseenter", displayTooltip);
  trigger.addEventListener("mouseleave", hideTooltip);
});

Now that the tooltips have a fade-in effect we want to change them so they are only displayed after the user has spent a certain amount with their mouse over the element. A good amount of time I have seen for these animations is usually 300ms but feel free to use any other you prefer.

Here we can opt for creating an anonymous function that wraps the displayTooltip and delays it's call. This allows us to keep the logic for displaying the tooltip decoupled from the fact we want to wait some time before displaying it.

const DELAY = 300;
let tooltipTimer = null;

tooltips.forEach((trigger) => {
	...

  trigger.addEventListener("mouseenter", (e) => {
    clearTimeout(tooltipTimer);

    tooltipTimer = setTimeout(() => {
      displayTooltip(e);
    }, DELAY);
  });

  trigger.addEventListener("mouseleave", (e) => {
    clearTimeout(tooltipTimer);
    hideTooltip(e);
  });
});

An important detail in this implementation is the cancellation of any previous display of a tooltip that might have been triggered. The reasoning is that we only want to display one tooltip at a time, so any previous tooltip scheduled animations should be cancelled.

Regarding the mouseleave we also cancel the animation in case the user has left the element (i.e. word) before the 300ms. Forgetting to do this usually leads to memory leaks and dangling events in your application (which can be tricky in your test suites).

Adding a triangular "tip" to the Tooltip

As a nice extra we can add a nice triangular tip to our tooltip (no pun intended)! To do this we can add a pseudo element to our tooltip that has a triangle shape. Let's start by adding a pesudo element to the tooltip and positioning it:

[role="tooltip"]::before {
  content: "";
  position: absolute;
  margin-top: -20px;
  transform: translateX(-50%);
  left: 50%;
}

Again we position the element absolutely, so it's positioned relative to its first positioned ancestor element, i.e. the tooltip. We apply margin-top: -20px because we want to display it at the top of the tooltip, and 20px is the padding of the tooltip plus the "height" of the triangle (it will also be 10px).

In order to center the triangle in the middle of the tooltip, first we make it's start be at the middle of the tooltip by using left: 50%. And after we apply translateX(-50%) to push it's start a little bit before so the middles coincide.

This works because the left property is based on the size of the parent element, i.e. the tooltip and the transform property is based on the size of the target element, i.e. the triangle,

[role="tooltip"]::before {
	...
  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-bottom: 10px solid rgba(0, 0, 0, 0.7);
}

In order to form the actual triangle, we use an old trick where by setting no width and height and just the bottom border we get a triangle. The actual width and height of the arrow is determined by the width of the border. For a great explanation on this check this article on CSS tricks.

See the Pen Animation to Explain CSS Triangles by Chris Coyier (@chriscoyier) on CodePen.

Final Result

See the Pen JavaScript & CSS Tooltip by Diogo Redin (@diogoredin) on CodePen.

Final notes

Some of the solutions here presented come from an amazing tutorial from the Chrome development team on how to develop a tooltip. The video includes some extra ideas on how to improve the accessibility of tooltips so I recommend you watch it:

Tips on Tooltips accessibility
]]>
<![CDATA[How I like to use Tailwind]]>https://diogoredin.github.io/how-i-like-to-use-tailwind/63b5f26d71ec4d3f0b47cb3aThu, 02 Dec 2021 22:08:46 GMT

The first time I visited Tailwind’s website I was really fascinated by its clean design and numerous code samples, ready to be used. My main interest in Tailwind wasn’t its value proposition to replace CSS, but the ability to build really quick prototypes for SaaS ideas.

I quickly setup a React project with Tailwind, which was fairly easy but I was immediately off put by the large amount of class names that now all my components needed to have. Most the business logic was now harder to read because of Tailwind, and its structure harder to follow.

This all changed when I discovered Tailwind Styled Components. If you are used to Styled Components or Emotion, the logic is very similar: You create an element, (outside of the render method), give it their styles and only afterwards you call it. The difference with this package is that instead of providing CSS, you give each element a list of Tailwind class names to apply the styling. All of this with the advantage of being able to also use variables to conditionally apply styles.

Before

<div className=`flex ${primary ? "bg-indigo-600" : "bg-indigo-300"} inline-flex items-center border border-transparent text-xs font-medium rounded shadow-sm text-white hover:bg-indigo-700 focus:outline-none`>
Tailwind without TW Styled Components

After

const Button = tw.div`
    ${(p) => (p.$primary ? "bg-indigo-600" : "bg-indigo-300")}

    flex
    inline-flex
    items-center
    border
    border-transparent
    text-xs
    font-medium
    rounded
    shadow-sm
    text-white

    hover:bg-indigo-700
    focus:outline-none
`
Using Tailwind Styled Components
<Button $primary={false}>
Using Tailwind Styled Components

I usually only use Tailwind to make quick prototypes, so I use it conjunction with Tailwind UI, which has a large amount of components readily available to be used.

My process when converting these samples to my project involves:

  • Creating the new component folder and respective index.ts
  • Creating a file named after the component which has it’s RxJS structure without any classnames (I like this because it eases searches and code identification in VSCode)
  • Creating a styles.ts file which includes Tailwind Styled Components and creates and exports all necessary elements to build the sample component.

Coming up with names for the elements is a bit hard, but the rule I use with Styled Components usually helps me here as well. If it’s a div to apply some spacing / alignment styles around an object, I usually call it SomethingContainer (e.g. MenuContainer). If it’s more specific or about styling I call it SomethingStyled (e.g. StyledMenuItem).

Below you can see the example of an header created with this structure:

export { default } from "./Header";
header/index.tsx
import { useOktaAuth } from "@okta/okta-react";
import { Link, useHistory } from "react-router-dom";

import Button from "../Button";
import {
  ButtonsContainer,
  LogoContainer,
  StyledHeader,
  StyledLogo,
} from "./styles";

const Header = () => {
  const { authState, oktaAuth } = useOktaAuth();
  const history = useHistory();

  return (
    <StyledHeader>
      <LogoContainer>
        <Link to="/">
          <StyledLogo src={`${process.env.PUBLIC_URL}/icon.svg`} />
        </Link>
      </LogoContainer>

      <ButtonsContainer>
        {authState.isAuthenticated ? (
          <Button light onClick={() => oktaAuth.signOut()}>
            Sign Out
          </Button>
        ) : (
          <Button light onClick={() => history.push("/signin")}>
            Sign In
          </Button>
        )}
        <Button to="/signup">Sign Up</Button>
      </ButtonsContainer>
    </StyledHeader>
  );
};

export default Header;
header/Header.tsx
import tw from "tailwind-styled-components";

export const StyledHeader = tw.header`
    flex
    justify-between
    items-center
    max-w-7xl
    mx-auto
    px-4 py-6
    sm:px-6
    md:justify-start
    md:space-x-10
    lg:px-8
`;

export const LogoContainer = tw.header`
    flex
    justify-start
    lg:w-0
    lg:flex-1
`;

export const StyledLogo = tw.img`
    h-8
    w-auto
    sm:h-10
`;

export const ButtonsContainer = tw.div`
    flex
    items-center
    justify-end
    md:flex-1
    lg:w-0
    gap-x-8
`;
header/styles.ts

Another useful thing that you can do with Tailwind Styled Components is creating Typescript interfaces when they receive props to apply different styles. Take as an example this Link component which receives two arguments:

import { Link } from "react-router-dom";
import tw from "tailwind-styled-components";

interface LinkProps {
  $light: boolean;
  $full: boolean;
}

export const StyledLink = tw(Link)<LinkProps>`
    whitespace-nowrap
    text-base
    font-medium
    disabled:opacity-50
    ${(p) =>
        p.$light
            ? "text-gray-500 hover:text-gray-900"
                : `
                    items-center
                    justify-center
                    px-4
                    py-2
                    rounded-md
                    text-white
                    bg-gradient-to-r
                    hover:from-purple-600
                    hover:to-green-300
                    from-yellow-500
                    to-pink-500
    `}
	${(p) => (p.$full ? "flex w-full" : "inline-flex")}
`;

Hope you enjoyed the article and have fun with Tailwind!

]]>
<![CDATA[How to do good code reviews]]>https://diogoredin.github.io/how-to-do-good-code-reviews/63b5f26d71ec4d3f0b47cb38Mon, 24 May 2021 00:33:07 GMT

Code reviews can be daunting to make, especially if you are new to the technology or the team you are in. However, code reviews are one of the best opportunities you have to grow as an engineer and help your team move forward. Code reviews should help your team:

  • Be aligned on technical solutions
  • Improve the architecture of the application
  • Improve performance
  • Minimise technical debt
  • Provide teaching opportunities for all engineers involved

Code reviews can also help you catch bugs the author didn't, but that responsibility should be mainly on your tests and the use of typed languages. Remember, no bug should reach the quality environment and at the end of the day you are equally responsible for the code you are reviewing.

What makes good reviews

Timely

Your priority number one as a team member should be to unblock any colleagues with code reviews pending and increase your overall team's productivity.

This means that if a colleague is struggling with any feedback you have left in their review you should prioritise helping them.

Being timely also means you should promptly review it again once the author has addressed all of your comments. Pair-programming is a great way to pass on any feedback faster and more effectively too.

If you have pull requests awaiting for too long, either:

The pull requests are too long and are not being appropriately broken apart - Remember a feature can be composed of various smaller pull requests. Tools like feature flags or multiple feature branches based upon each other can help with this.

The team is not experienced enough with the technology at hand - In this case the first point is even more important, because any novice to a technology might feel overwhelmed with a large diff. Besides this, you can start pair-programming with other engineers on the team, so they also become familiar with the technology and are more comfortable reviewing the code in the future.

The pull request is not seen as a priority by everyone on the team - Sometimes the priorities of the team might not be clear for everyone. This can be addressed by making sure PRs have tasks associated which are visible, discussed and agreed upon before being started.

Professional and light-hearted

It's important that you approach every code review with an open mind. This includes:

  • Being ready to be challenged on your suggestions and opinions;
  • Being able to change your mind when presented with good counter arguments and facts;
  • Being able to negotiate trade-offs given various constraints.

You should also try to keep a positive tone at all times. By maintaining a positive tone I mean:

  • Always critique the code instead of the person;
  • Use positive vocabulary;
  • Formulate the suggestions always as proposals, up for discussion;
  • Give praise on the effort or any outstanding solutions;
  • Use emojis to make the review more light-hearted.

You should also avoid using any cursing or slang language. The author might take it badly or feel you are unfriendly or aggressive.

Few nit-picks

Unless the other author is new and is still getting used to the team, it's a bad sign if your review contains too many nit-picks. If you find yourself nitpicking too much maybe it's time you introduce some kind of linter on your project or write down some guidelines for future authors.

You should however ensure your review does not consist of many nit-picks, since they might deviate the author from focusing on more important suggestions. If you find yourself nitpicking too much on someone's code schedule a meeting with them to help them find patterns and strategies they can use to improve.

Empathetic

Software Engineering is one big act of empathy: Empathy for the user, empathy for other engineers, empathy for the product.

The top questions I regularly make myself when reviewing code are the following:

If I were to implement this, would I do it this way? If not, in which way? Would it bring any benefits or is this approach okay as well?
Would other engineers understand this code? Can I grasp what the code is doing without looking at any documentation?
Does this feature have any performance issues? Should there be monitoring tools in place here?
If I onboarded a new engineer in the team could they extend this feature without much effort?
Does this code satisfy the product requirements?
Would a user like this experience? Is it accessible? Is there anything we overlooked in the user experience?
Does this code follow our guidelines?
Are we tracking or saving anything we shouldn't? Is the code ethic?

Feasible and over-explanatory

When I give feedback on a pull request I always ponder three variables: time, effort, and return on investment. I only leave a given suggestion if I believe it can be done without preventing the team from delivering the feature in time.

The only exception to this rule is if there is a major bug or flaw in the code. In cases I opt for not suggesting something I either leave it as optional in the pull request or discuss it with the author and create a new issue in the backlog for us to tackle later.

Another good practice is to have the code open and running locally and play around with the code to try out any solution you might want to suggest. This way you can fully reflect upon the repercussions of the suggestion and think twice if they really improve the code or product

If I face any hardship when implementing a suggestion I'm giving I leave it documented for the author, so they have an easier time implementing it.

Ask questions

If there is something you don't understand in the code you should clarify it right away. If you don't understand something it's possible other engineers won't understand it either and the code should either be improved or documented.

Conventional Comments

This is a best practice I have adopted since the last year. It's a simple syntax for formatting your review comments that indicates clearly to the author the scope, type, and importance of your comments. I have the website bookmarked and open it when reviewing if I don't remember any of the syntax options.

Conventional Comments
Comments that are easy to grok and grep
How to do good code reviews

Platform features

You should take full advantage of the platform you are using for reviewing code. For instance, I always:

  • Have key take ways in bold,
  • Have code samples with appropriate syntax highlighting,
  • Mark code suggestions marked as suggestions to produce nice code diffs,
  • Mark the comments as resolved / unresolved to keep the pull request organised.
Creating and highlighting code blocks - GitHub Docs
Share samples of code with fenced code blocks and enabling syntax highlighting.
How to do good code reviews
Multi-line code suggestions beta | GitHub Changelog
Multi-line code suggestions beta
How to do good code reviews

What steps to take when reviewing

Read the pull request and associated task

When doing a code review ensure the task accomplishes everything it set out to do and ensure the author didn't forget to implement anything.

This is probably the last time you can have a say on how a feature is built or its specifications, so take the chance to analyse it and suggest improvements you believe are essential (without going out-scope).

Run the code

As mentioned above, when reviewing the code I usually checkout the branch and run the code locally. This helps me:

  • Ensure the code runs as expected on my machine too (the author didn't forget to document any change to the build phase and dependencies are well specified),
  • I can explore and get more familiar with the feature,
  • I can do some tests for common pitfalls I might remember,
  • I can try out suggestions before giving them to ensure they are feasible,
  • If it's a UI implementation I can checkout all spacings and colors are according to the wireframes, same for the user experience requirements.

Analyse the tests

Tests are a form of documentation and should verify the behaviour of the feature. Analysing the tests first guarantees you gain more context of the feature before diving into the implementation. You might come back here afterward to check for any missing tests.

When reviewing tests I usually check:

  • If they are well grouped;
  • If their description makes sense;
  • If they are isolated and independent;
  • If they are reasonably fast;
  • If they are repeatable;
  • If their phases are clear: preparation, execution and assertion;
  • If the expectations are within the scope of the test description;
  • If the scope of each test is well delimited and does not try to test too much;
  • If the tests fail when the code is broken;
  • If there is the possibility they start producing false positives;
  • If they are not too coupled with implementation;
  • If they are easy to understand.

I'm an advocate that every line you write should be tested. Sometimes it won't be possible, but you should aim high and have a good effort to coverage ratio, enough to give you confidence in any code changes you might make in the future. When setting up a project it's useful to introduce tools to track the code coverage of your tests.

Analyse the implementation

When reviewing the implementation I usually review the commits one by one. I usually have both the commit diff and the code open in the IDE. Having the code open helps me make sense of the broader context and ensure the changes actually make sense within the current architecture.

Regarding the commits ideally, they all focus on the why instead of the how and give out information it would have been hard for the reader to get otherwise. I ensure these are well written and linked to the corresponding task for easier tracking in the future.

I'm always on the lookout for convoluted code and ensure to leave suggestions when I believe it can be simplified. Naming is hard, but as a rule of thumb, names should be as short as possible.

If the code is implementing a feature very similar to something that already exists, I ensure the implementation is similar and that we take advantage of the appropriate abstractions. I also ask myself if I would be able to debug the code. If the answer is not, chances are it should be refactored.

When reviewing the implementation I also keep in mind algorithmic concerns, error handling, monitoring, and evaluate if the core software principles are being followed (within reason), namely:

KISS - Keep it short and simple.

DRY - Don't repeat yourself.

Single-responsibility principle - Every module, class or function in an application should have responsibility over a single part of that application functionality, and it should encapsulate that part.

Open–closed principle - Software entities should be open for extension, but closed for modification.

Liskov substitution principle - Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.

Interface segregation principle - Many client-specific interfaces are better than one general-purpose interface.

Dependency inversion principle - One should depend upon abstractions, not concretions.

Besides these, which are universal, you will also want to check specific best practices for the technology you are reviewing (and check the corresponding documentation to clarify any doubts or raise pertinent questions). Sometimes, when I see some code that I suspect might be missing some edge case, I open up the application and test through the scenario.

In the end, I also check if test descriptions, commit messages, documentation, and comments (if you do them) are written in understandable English since they are all part of the code being developed too.

Conclusion

I hope this article has given you a few ideas of practices you can adopt when reviewing pull requests. They mostly reflect what I have learned from both giving and receiving feedback on pull requests.

I'm constantly learning and evolving, so my opinion on some of these practices might change in the future, in which case I will edit this article or create a follow-up. If you have any comments or suggestions leave them down below. In the future I intend to do an article too on how to open great pull requests, so keep tuned!

Recommended reads

If you would like to learn more I suggest you read the following articles:

]]>
<![CDATA[Getting started with React Testing Library]]>https://diogoredin.github.io/getting-started-with-react-testing-library/63b5f26d71ec4d3f0b47cb33Sun, 25 Apr 2021 17:20:00 GMT

React Testing Library is a test library that helps us write integration and unit tests for our UI applications by allowing us to:

  • Render components
  • Perform actions upon them (click, type, check ...)
  • Retrieve any element rendered through accessible and semantic queries

It's built on top of testing-library, which has various integrations besides React, namely Angular, Vue, puppeteer, webDriverIO, Cypress, etc.

The React Testing Library philosophy can be sum up in the following statements:

  • Focus on user behavior
  • Don't test internal state or properties directly
  • Only test inputs and expected outputs

As general advice, the best mindset to adopt when writing tests with this library is to act as your application user.

For reference, our application can have the following levels of tests:

  • E2E - Spin up the application and simulate user behavior. Similar to a robot performing a task in the application
  • Integration - Verify that several units work together in harmony
  • Unit - Verify the functionality of a single function/component
  • Static - Catch type errors as you write code

Queries to use

Query Type Returns When
queryBy* Synchronous First matching node or null if there is no match. Good for asserting if an element is not present in the UI
getBy* Synchronous First matching node or throws an error if there is no match. Good for assertions of elements we know are already in the UI
findBy* Asynchronous A promise that resolves when a matching node is found or rejects if there is no match. Good to wait for elements to be rendered in the UI
Type Examples Characteristics
Accessible getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue Reflect the experience of visual/mouse users as well as those that use assistive technology
Semantic getByAltText, getByTitle HTML5 and ARIA compliant selectors
Test IDs getByTestId Reflects implementation details and the user can’t interact with them
Manual querySelector Same as above and more prone to changes

All these queries support an `AllBy` variant for getting an array of all matching nodes.

Awaits and assertions

Do I need an expectation after a find? No, but:

  • It will make what you are testing clearer for other programmers
  • What you are waiting for might not include all assertions you need to make
Getting started with React Testing Library

Firing Events

UserEvent is a companion library for Testing Library that provides more advanced simulation of browser interactions than the built-in fireEvent method.

For instance, when typing it will first click on the input and then type the text, firing multiple events, closer to what the user will do.

Getting started with React Testing Library
Getting started with React Testing Library

The "not wrapped in act(...)" warning

  • Happens when something in the component changes and we didn’t test it

Don’t just wrap the test in act(() => {})

  • Functions from React Testing Library are all wrapped in act by default, ensuring that if you test the component change the warning will disappear
Getting started with React Testing Library

Using the debugger

Sometimes it might be useful to see what elements are present in the DOM. To do so we can use the screen.debug() instruction inside our test. If the elements printed are too many and get cut you can set the env var DEBUG_PRINT_LIMIT with a large print limit, e.g. DEBUG_PRINT_LIMIT=30000 yarn test.

  • To print the entire document: screen.debug()
  • To print a single element: screen.debug(screen.getByText('test'))
  • To print multiple elements: screen.debug(screen.getAllByText('test'))

Using the testing-playground.com

  • To open the entire document in the playground: screen.logTestingPlayground()
  • To open a single element in the playground: screen.logTestingPlayground(screen.getByText('test'))
Getting started with React Testing Library
Getting started with React Testing Library

Utilities

  • waitFor waits for expectations to pass, which can be for example a function or API call;
  • waitForElementToBeRemove waits for an element present in the page to be removed;
  • within(screen.getByRole('form')).getByText('test') searches for an element inside another;

Common mistakes

Not using Testing Library ESLint plugins

There are two plugins that can help a lot in avoiding most of the mistakes I'm about to mention.

Using act()

The act warning usually means that something in your test is happening which is not tested. Most utilities from React Testing Library are already wrapped in act to mark the behaviors you assert as tested, so there is usually no need to wrap your tests in act.

Getting started with React Testing Library

Forgetting to await queries

Asynchronous queries (findBy*, waitFor, waitForElementToBeRemoved, etc.) returns promises and need to have await in front of their invocation, otherwise, the next instruction will be executed before the queries return.

Getting started with React Testing Library

Adding aria-, role, and other accessibility attributes incorrectly

With React Testing Library influencing us towards improving our components accessibility it might be tempting to throw attributes to just make the test work.

In the example below, for instance, getting a form <form></form> with getByRole("form") would not work until we add an aria-label. One could force the role manually <form role="role"></form> and although it would work (if you don't have any a11y eslint plugin in place) the correct form would be <form aria-label="Sign In"></form>.

My recommendation is to always read the MDN document regarding Roles to better understand what best suits your use cases. There is also a very good website (a11ymatters) which contains the most common UI patterns and the accessibility attributes to use.

Getting started with React Testing Library

Using getBy* with expect().not.toBeInTheDocument()

The issue with using getBy with expect().not.toBeInTheDocument() is that if the element does not exist the query will throw an error before it even gets to evaluate if the element is not on the screen. The query queryBy was created specifically for these use cases, to assert if an element is not in the document without it throwing an error.

Getting started with React Testing Library

Using query* variants for anything except checking for non-existence

The query* variants should only be used to verify the non-existence of elements, which was the reason they were initially created. For checking the existence of elements, getBy should be used instead.

Getting started with React Testing Library

Using await with fireEvent, userEvent or non async queries

fireEvent and userEvent are synchronous functions which means we don't need to mark them with await.

Getting started with React Testing Library

waitFor to wait for elements that can be queried with find*

find queries already return a promise, so it's straightforward to await them to resolve in our tests.

If we end up repeating ourselves with these elements we need to wait for we can even extract them to some nice one-line helpers.

Getting started with React Testing Library

Using side effects inside waitFor

waitFor should be used exclusively to assert if our expectations have been completed, since the waitFor retries multiple times until it passes (or fails after a certain time threshold).

Getting started with React Testing Library

Credits

]]>