Keep Examples Up To Date

Use tests and automatic dependency updates to keep examples up to date with the latest versions of the relevant library. Make it visible with dependency version badges.

Examples, examples, examples

I love building tools and I love having many examples for each tool. Lots of examples make it simpler to understand how the tool works. For example cypress-react-unit-test for testing React components has LOTS of both internal and external examples as you can see in the screenshot below:

Long list of examples

Tip: I give external example repos their own GitHub topic, in this case the topic cypress-react-unit-test-example has 23 GitHub repositories.

It is a challenge to keep external example repos up to date with the latest version of cypress-react-unit-test as this user calls out:

Tweet asking to update try-cra-app-typescript

So how do we keep try-cra-app-typescript up-to-date?

Concentrate on the most important dependencies

Each example might have multiple dependencies. The above project has

package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"typescript": "~3.7.2"
},
"devDependencies": {
"cypress": "4.3.0",
"cypress-react-unit-test": "3.0.1"
}
}

Because this is an example meant to demo cypress-react-unit-test + Cypress, we will ignore production dependencies and will only update cypress and cypress-react-unit-test dev dependencies. I will use Renovate Bot to automatically open pull requests when new versions of these two dependencies are published on NPM. Following the advice I gave in How To Update Only Some Dependencies Using Renovate App here is renovate.json file

renovate.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"extends": [
"config:base"
],
"automerge": true,
"schedule": [
"every weekend"
],
"updateNotScheduled": false,
"timezone": "America/New_York",
"masterIssue": true,
"enabledManagers": [
"npm"
],
"packageRules": [
{
"packagePatterns": [
"*"
],
"excludePackagePatterns": [
"cypress",
"cypress-react-unit-test"
],
"enabled": false
}
]
}

We only need to update the dependencies we care about, and we can update them once a week during the weekend hours. When I enable Renovate GitHub Application for this repository, the app immediately opens a master issue showing the possible updates.

Renovate master issue

Set up CI

To safely update any dependency, we must have tests and continuous integration service to run them. Following Example CI configs I will use GitHub Actions with cypress-io/github-action.

.github/workflows/ci.yml
1
2
3
4
5
6
7
8
9
10
11
12
name: ci
on: [push, pull_request]
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v1

Tip: I always have the CI badge in the README file pointing at the master branch.

1
2
3
[![ci status][ci image]][ci url]
[ci image]: https://github.com/bahmutov/try-cra-app-typescript/workflows/ci/badge.svg?branch=master
[ci url]: https://github.com/bahmutov/try-cra-app-typescript/actions

Now I can click on cypress or cypress-react-unit-test checkbox in the Renovate's master issue to open a pull request to update the dependency.

Click checkbox next to the dependency to update

A few seconds later a new pull request appears: Cypress dependency has been updated to v4.6.0 in package.json file; now it is being tested by GitHub Actions workflow.

Update Cypress pull request

Once the CI finishes successfully we can manually merge the pull request or let Renovate Bot auto-merge it after an hour or so.

Cypress can be updated since the tests are green

Tip: if you use semantic release and automate the changelog writing, Renovate pull requests will show a very useful changelog. This makes it easier for the reviewer to decide how to proceed.

Renovate includes the release changelog from the dependency

Sometimes a Renovate pull request shows a failed attempt to update. For example, the API for mounting component has changed between v3 and v4 (this was a breaking change).

Failed pull request to update cypress-react-unit-test

We can inspect the CI output to see the error message.

Mount method has changed between v3 and v4

I will still merge this pull request, but then I will update the tests to use cypress-react-unit-test using the new API. You can see the changes in commit aed44a.

Add version badges

I want to make it obvious to anyone looking at the cypress-react-unit-test what the compatible Cypress and cypress-react-unit-test versions are right now. The simplest way in my opinion is to put the current dependency versions into the README as badges. I have a little utility for this dependency-version-badge. We can simply run the tool using npx and insert two badges into the README at the first line

1
2
3
4
5
6
7
8
$ npx -p dependency-version-badge update-badge cypress cypress-react-unit-test
npx: installed 3 in 2.091s
⚠️ Could not find version badge for dependency "cypress"
Insert new badge on the first line
saving updated readme with [email protected]
⚠️ Could not find version badge for dependency "cypress-react-unit-test"
Insert new badge on the first line
saving updated readme with [email protected]

The badges use shields.io to pull SVG of the badge and set the text to the dependency's name and version.

The first line of the README.md

The rendered README file looks nice

Rendered README file with CI and version badges

Auto-updating badges

We have manually set the badges with dependencies, but keeping them up to date manually is too much work. Let's update them automatically using GitHub Action. I will add another workflow file that should only run when pushing new code to master branch. We can further limit this action to only execute when the commit includes changes to README, package.json or the workflow file itself.

.github/workflows/badges.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
name: badges
# update README badge only if the README file changes
# or if the package.json file changes, or this file changes
on:
push:
branches:
- master
paths:
- README.md
- package.json
- .github/workflows/badges.yml

jobs:
build:
name: Badges
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎
uses: actions/checkout@v1

- name: Update version badges 🏷
run: |
npx -p dependency-version-badge update-badge cypress cypress-react-unit-test

# commit any changed files
# https://github.com/mikeal/publish-to-github-action
- name: Push any changes to repo 📤
uses: mikeal/publish-to-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Usually there are no changes and the workflow finishes without pushing any new code

Badges workflow when the versions stay the same

But let's say we change the cypress-react-unit-test version in package.json. We can do it manually to simulate the version change, but normally Renovate would merge a pull request with version bump.

package.json
1
2
- "cypress-react-unit-test": "4.2.3"
+ "cypress-react-unit-test": "4.2.2"

The "badges" workflow runs and updates the markdown in the README file. Notice the changed Markdown file in Git status before pushing the change from the GitHub Action back to the repository.

Badges action has changed the README file

You can see the automatic commit in the list of commits.

Commits show README update

README update commit shows the updated badge

Now the example repository will stay up to date, and make it obvious to our users if the example shown is still applicable.