Frontend Testing
In mintter we have currently 3 types of testing:
- Static type testing with Typescript
- Unit testing functions with vitest
- UI testing with Cypress (DEPRECATED)
Run tests locally
pnpm test # will run all unit and ui testing (CI and `headless` mode)
pnpm app test:unit:run # run unit tests in CI mode (vitest)
pnpm app test:unit:watch # run unit tests in watch mode
pnpm app test:ui:run # run ui tests in `headless` mode
pnpm app test:ui:open # run ui tests opening the Cypress dashboard
Testing conventions
- test files should be colocated: you should add your tests files in the closest
__tests__
folder (if any, create one) - name convention for testing typescript:
*.test.ts**
- name convention for testing UI components:
*.cy.ts**
- use
data-testid
to target UI elements: This prevents tests from failing if the UI implementation changes slightly. - when writing Vitest tests, remember imports: Vitest intentionally does not add global tools like
describe
,test
orexpect
. you need to explicitly import them (checkout the example)
UI Testing setup
By default by calling cy.mount()
it will mock most of the initial backend calls (account, info, file lists...), but if you want to mock a specific call with a new value, you can call the createTestQueryClient()
function to get access to the client.
describe('awesome test', () => {
test('the test', () => {
let {client} = createTestQueryClient({
publication: { // ...
})
cy.mount(<Component />, {
client // make sure to pass the client as an option to the mount function
})
// all your assertions chained here
}
})
createTestQueryClient
can accept a certain number of parameters to mock and it will return them alongside with the client
, so you can assert tests with them.
checkout the test/utils
(frontend/app/src/test/utils.tsx) file and see how internally works.
We are using react-query's QueryClient
to mock the backend. this way we can test the app almost as if it was running alongisde with the backend and with minimal change from how our users will interact with it. We also use Cypress component testing for the same reason, because our app is running on the native webview, rendering to an actual webview makes more sense in terms of reliability. It's not perfect, but god enough.
Examples
Unit test that will run by vitest
// block-to-api.test.ts
import {describe, expect, test} from 'vitest'
import {Block} from '@app/client'
import {
paragraph,
Statement,
statement,
text,
} from '@app/mttast'
import {blockToApi} from '../block-to-api'
describe('Transform: blockToApi', () => {
test('should return an empty annotations list', () => {
let input: Statement = statement({id: 'blockId'}, [
paragraph([text('Hello world')]),
])
let output: Partial<Block> = {
id: 'blockId',
type: 'statement',
text: 'Hello world',
attributes: {
childrenType: 'group',
},
}
expect(blockToApi(input)).toEqual(output)
})
UI test that does not include any special mock
// draft-list-page.cy.tsx
import { DraftList } from "@app/pages/draft-list-page";
// TODO: FIXME
describe("DraftList", () => {
it("Should show an empty list", () => {
cy.mount(<DraftList />)
.get('[data-testid="filelist-title"]')
.contains("Drafts")
.get('[data-testid="filelist-empty-label"]')
.contains("You have no Drafts yet.");
});
});
UI test that needs a special mock
// draft-list-page.cy.tsx
import { DraftList } from "@app/pages/draft-list-page";
import { createTestQueryClient } from "@app/test/utils";
// TODO: FIXME
describe("DraftList", () => {
it("should render the draft list returned", () => {
let { client } = createTestQueryClient({
draftList: [
{
id: "1",
title: "document 1",
subtitle: "",
author: "testauthor",
createTime: new Date(),
updateTime: new Date(),
publishTime: new Date(),
children: [],
},
{
id: "2",
title: "document 2",
subtitle: "",
author: "testauthor",
createTime: new Date(),
updateTime: new Date(),
publishTime: new Date(),
children: [],
},
],
});
cy.mount(<DraftList />, {
client,
})
.get('[data-testid="filelist-list"]')
.children()
.should("have.length", 2);
});
});