Skip to content

Instantly share code, notes, and snippets.

@horacioh
Last active September 1, 2022 21:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save horacioh/a24dc9cd0afcc01e0da443159e876480 to your computer and use it in GitHub Desktop.
Save horacioh/a24dc9cd0afcc01e0da443159e876480 to your computer and use it in GitHub Desktop.
Mintter Frontend testing docs

Frontend Testing

In mintter we have currently 3 types of testing:

  1. Static type testing with Typescript
  2. Unit testing functions with vitest
  3. UI testing with Cypress

Run tests locally

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

Testing conventions

  1. test files should be colocated: you should add your tests files in the closest __tests__ folder (if any, create one)
  2. name convention for testing typescript: *.test.ts**
  3. name convention for testing UI components: *.cy.ts**
  4. use data-testid to target UI elements: This prevents tests from failing if the UI implementation changes slightly.
  5. when writing Vitest tests, remember imports: Vitest intentionally does not add global tools like describe, test or expect. 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 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 using cypress

// 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 using cypress

// 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);
  });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment