Recently, I played with ArrowJS - a tiny reactive web framework that uses JavaScript for everything. Here is a typical "counter" application code.
1 | import { reactive, html } from '@arrow-js/core' |
How would you run this example in your project? Let's find out.
- Zero build tools
- Build using Vite
- End-to-end test
- Component test
- Continuous integration
- Components with CSS
- A pyramid of components
- See also
Zero build tools
First, let's try the above example without any build tools, just using a modern browser. In an empty folder, I will create index.html
and will import the ArrowJS from CDN:
1 | <body> |
Wow, I can open this local index.html
in my browser and it works.
Beautiful. You can find this code in the branch zero of the repo bahmutov/arrowjs-test-examples.
Build using Vite
What if we want to split the source code? Trying to move some of the source code does not work. For example, moving the "counter" into its own module and importing from the index.html
breaks when using the local file in the browser.
1 | import { reactive, html } from 'https://cdn.skypack.dev/@arrow-js/core' |
1 | <body> |
Ok, we probably do need to bundle things to run them locally. Let's use Vite, and while we are at it, let's use a local ArrowJS NPM module.
1 | $ npm init --yes |
In my package.json
I use dev
script to run vite
1 | { |
Let's start the development server with npm start
1 | $ npm run dev |
The application works again
The best thing - the page reloads almost instantly if we change any of the code. For example, let's change the initial data object in the src/counter.js
You can find this code in the branch vite of the repo bahmutov/arrowjs-test-examples.
End-to-end test
Now let's test the counter using Cypress end-to-end tests. We need to install Cypress and scaffold the config file and the first spec. We can use @bahmutov/cly
1 | $ npm i -D cypress |
Let's put settings for our E2E tests into cypress.config.js
file
1 | const { defineConfig } = require('cypress') |
Our test should check if clicking increments the count
1 | /// <reference types="cypress" /> |
In the first terminal I will run Vite dev server. In the second terminal, I open Cypress
1 | # first terminal |
You can find this code in the branch e2e of the repo bahmutov/arrowjs-test-examples.
Component test
Do we need to run Vite dev server? Do we need to load the full application page just to confirm the counter works? We can work and test individual ArrowJS "components" (which are really just the exported HTML template functions). Stop Vite dev server and open Cypress again. Pick "Component Testing" and pick the "React.js" front-end framework and "Vite" bundler
Cypress will ask you to install React NPM dependencies, but you skip this step.
Then Cypress will freak out about missing Vite config.
Create an empty file vite.config.js
and add the following code:
1 | import { defineConfig } from 'vite' |
Super. Now open in the code editor cypress/support/component.js
. Since we picked React, this file tries to import it. Remove everything from that file and replace the code with our own cy.mount
custom command. This command will take the HTML template function produced by ArrowJS an will apply it to an element. The element comes from cypress/support/component-index.html
1 |
|
1 | function mount(template) { |
Let's write a component test. I like placing the component tests right next to the components in the src
folder. Let's tell Cypress about component spec files and where to find them:
1 | const { defineConfig } = require('cypress') |
Cypress Component Testing shows a missing React dependency warning, but ignore it.
Let's write component spec
1 | import { counter } from './counter' |
The test looks so much like the end-to-end spec cypress/e2e/click.cy.js
, only instead of cy.visit('/')
command, we are importing the component and mounting it ourselves using the cy.mount(...)
command. The component runs like a mini web application, just like the end-to-end page.
The E2E test takes longer - because it waits for the cy.visit
to finish. In this example, both tests are fast, but in a realistic application, loading and testing each component can bring significant speed savings.
You can find this code in the branch component of the repo bahmutov/arrowjs-test-examples.
Continuous integration
Let's run our tests on each commit. We want to run both end-to-end and component tests. The simplest way is to use the GitHub Actions via my reusable Cypress workflows. Here is the workflow file
1 | name: ci |
Push the code and observe the results.
Once the test jobs finish, they show their summaries
You can find this code in the branch ci of the repo bahmutov/arrowjs-test-examples.
Components with CSS
Let's grab another ArrowJS example. Let's take the Dropdown component, it looks really nice in its demo
1 | import { reactive, html } from '@arrow-js/core' |
Let's make a sample test.
1 | import { dropdown } from './dropdown' |
Ughh, the test fails. And our dropdown looks nothing like its demo!
Hmm, if our component does not look like the real app, then we probably forgot to include its CSS styles. The demo site has dropdown.css
link.
Let's grab this CSS file and simply import it from our spec file src/dropdown.cy.js
- Vite should be able to bundle it correctly. It is pretty fun to watch. The component suddenly becomes "real", and the test passes.
For full experience, let's include the demo page index.css
too. It will make the next test even better.
A pyramid of components
Cypress component testing can deal with the smallest components (and even unit tests), and large components that include the entire pyramid of sub components. There is no shallow rendering - it is all running live in the browser. Let's test the entire demo page showing those three dropdowns.
1 | import { dropdown } from './dropdown' |
We can run the test "shows three dropdowns" which is pretty much tests the full demo page https://www.arrow-js.com/demos/dropdowns.html but without the actual page.
Nice.
You can find all source in the main branch of the repo bahmutov/arrowjs-test-examples.
See also
- I played with component tests for Lit web components in the repo bahmutov/lit-component1