Turn off your browser and try red, green & refactor pattern (part 3)

ยท 3 mins read ยท ย Testingย JavaScript

Since we added the home page let's add some extra routes for About and Contact pages. Don't forget, tests always come first:

describe('a user who visits about page under url "/about"', () => {
  it('should be able to see About headline and the url should equal with "/about"', () => {
    cy.visit('/about');

    cy.contains('h1', 'About');
    cy.url().should('include', '/about');
  });
});

describe('a user who visits contact page under url "/contact"', () => {
  it('should be able to see About headline and the url should equal with "/contact"', () => {
    cy.visit('/contact');

    cy.contains('h1', 'Contact');
    cy.url().should('include', '/contact');
  });
});

As you guessed, both failed:

Turn off your browser and try red, green & refactor pattern

Time to make them green by adding some extra routes and views accordingly:

const express = require('express');
const router = express.Router();

router.get('/', function(req, res, next) {
  res.render('index', { title: 'Home' });
});

router.get('/about', function(req, res, next) {
  res.render('about', { title: 'About' });
});

router.get('/contact', function(req, res, next) {
  res.render('contact', { title: 'Contact' });
});

module.exports = router;

So they pass:

Turn off your browser and try red, green & refactor pattern

One more for our example. Can we navigate from home page to about and contact page? Time for some extra test cases:

describe('a user who is in home page', () => {
  it('should be able to navigate to about page after clicking the about link in navbar', () => {
    cy.visit('/');

    cy.get('nav a').contains('About').click();

    cy.contains('h1', 'About');
    cy.url().should('include', '/about');
  });

  it('should be able to navigate to contact page after clicking the contact link in navbar', () => {
    cy.visit('/');

    cy.get('nav a').contains('Contact').click();

    cy.contains('h1', 'Contact');
    cy.url().should('include', '/contact');
  });
});

Both fail of course since we haven't created the navigation menu yet:

Turn off your browser and try red, green & refactor pattern

Time to add the navigation menu in the header:

<ul class="hidden sm:flex items-center justify-between">
    <li class="ml-4">
        <a href="/about" class="inline-block p-4 hover:bg-gray-100">About</a>
    </li>
    <li class="ml-4">
        <a href="/contact" class="inline-block p-4 hover:bg-gray-100">Contact</a>
    </li>
</ul>

These help our test cases to become green:

Turn off your browser and try red, green & refactor pattern

A last one. Can we navigate to About and Contact pages from Home page while using a mobile device? Do we have a mobile menu in place? No we don't. Let's create it then but first we need to create the appropriate test cases:

describe('a user who is in home page and uses a mobile device', () => {
  beforeEach(() => {
    cy.viewport('iphone-x');
  });

  it('should be able to navigate to about page after clicking the about link in mobile menu', () => {
    cy.visit('/');

    cy.get('[aria-label="mobile-toggle"]').click();
    cy.get('[data-test="mobile-menu"] a').contains('About').click();

    cy.contains('h1', 'About');
    cy.url().should('include', '/about');
  });

  it('should be able to navigate to contact page after clicking the about link in mobile menu', () => {
    cy.visit('/');

    cy.get('[aria-label="mobile-toggle"]').click();
    cy.get('[data-test="mobile-menu"] a').contains('Contact').click();

    cy.contains('h1', 'Contact');
    cy.url().should('include', '/contact');
  });
});

These will fail of course:

Turn off your browser and try red, green & refactor pattern

Notice that we asked Cypress to use an iPhoneX viewport, so that these test cases will use the mobile version of our application. This gives us enormous flexibility obviously.

Time to add that mobile menu in place:

<div data-test="mobile-menu" x-data="{ open: false }" class="block sm:hidden">
    <button
        @click="open = true"
        aria-label="mobile-toggle"
        class="flex items-center py-4 text-gray-500 hover:text-gray-800 hover:border-teal-500 appearance-none focus:outline-none"
    >
        <svg
            class="fill-current h-4 w-4"
            viewBox="0 0 20 20"
            xmlns="http://www.w3.org/2000/svg"
        >
            <title>Menu</title>
            <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" />
        </svg>
    </button>

    <ul
        x-show="open"
        @click.away="open = false"
        class="absolute top-auto left-0 w-full shadow py-2 z-10 bg-white"
    >
        <li>
            <a href="/about" class="block px-4 py-2 hover:bg-gray-100">About</a>
        </li>
        <li>
            <a href="/contact" class="block px-4 py-2 hover:bg-gray-100">Contact</a>
        </li>
    </ul>
</div>

If you are wondering what are these x-something directives, these are directives provided by AlpineJS. I highly recommend to use this framework, because of its simplicity especially when it comes about such simple webpages and applications like the one we are building here.

In order to bring AlpineJS into the game, we need to add a script tag in head section from a CDN. Nothing more. It actually takes care of the rest since it initializes on its own. Pretty cool, right?

As expected, our tests now are passing:

Turn off your browser and try red, green & refactor pattern

Pretty awesome, right? ๐ŸŽ‰

You can find the actual codebase of this application here

You can read more about Cypress here

You can read more about AlpineJS here

Move on with part 4 here

Did you miss part 2? You can find it here

In case you want to take it from the very start, check part 1 here

Newsletter

Get notified about latest posts and updates once a week!!