In mintter we have currently 3 types of testing:
yarn test # will run all unit and ui testing (CI and `headless` mode)
yarn app test:unit:run # run unit tests in CI mode (vitest)
yarn app test:unit:watch # run unit tests in watch mode
yarn app test:ui:run # run ui tests in `headless` mode
yarn app test:ui:open # run ui tests opening the Cypress dashboard
- 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)
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
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.
// 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)
})
// 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.");
});
});
// 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);
});
});