From a05b9db1318dcb8ccdce8bd79e1e5de259764c20 Mon Sep 17 00:00:00 2001 From: Sami Mazouz Date: Thu, 16 Feb 2023 09:55:32 +0100 Subject: [PATCH] feat: frontend tests --- docs/extend/github-actions.md | 59 ++++++++++-- docs/extend/testing.md | 164 +++++++++++++++++++++++++++++++++- 2 files changed, 214 insertions(+), 9 deletions(-) diff --git a/docs/extend/github-actions.md b/docs/extend/github-actions.md index 3179a4ec7..1051a2b13 100644 --- a/docs/extend/github-actions.md +++ b/docs/extend/github-actions.md @@ -4,8 +4,6 @@ In public repos, [GitHub Actions](https://github.com/features/actions) allow you In this guide, you will learn how to add pre-defined workflows to your extension. -## Setup - :::tip [Flarum CLI](https://github.com/flarum/cli) You can use the CLI to automatically add and update workflows to your code: @@ -15,6 +13,9 @@ $ flarum-cli infra githubActions ::: +## Backend + + All you need to do is create a `.github/workflows/backend.yml` file in your extension, it will reuse a predefined workflow by the core development team which can be found [here](https://github.com/flarum/framework/blob/main/.github/workflows/REUSABLE_backend.yml). You need to specify the configuration as follows: ```yaml @@ -34,9 +35,7 @@ jobs: backend_directory: . ``` -## Backend - -Flarum provides a pre-defined workflow for running certain jobs for the backend of your extension. These are the currently available jobs: +These are the currently available jobs: | Name | Key | Description | |-------------------------------------------------|--------------------------|----------------------------------------| @@ -69,4 +68,52 @@ For more details on parameters, [checkout the full predefined reusable workflow ## Frontend -Soon.. +All you need to do is create a `.github/workflows/frontend.yml` file in your extension, it will reuse a predefined workflow by the core development team which can be found [here](https://github.com/flarum/framework/blob/main/.github/workflows/REUSABLE_frontend.yml). You need to specify the configuration as follows: + +```yaml +name: Frontend + +on: [workflow_dispatch, push, pull_request] + +jobs: + run: + uses: flarum/framework/.github/workflows/REUSABLE_frontend.yml@main + with: + enable_bundlewatch: false + enable_prettier: true + enable_typescript: false + + frontend_directory: ./js + backend_directory: . + js_package_manager: yarn + main_git_branch: main + + secrets: + bundlewatch_github_token: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }} +``` + +Unlike the backend workflow, the frontend workflow runs everything in a single job. Here are the available parameters: + +| Name | Key | Description | Format | +|-----------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|--------| +| Build Script | `build_script` | Script to run for production build. Empty value to disable. | string | +| Build Typings Script | `build_typings_script` | Script to run for typings build. Empty value to disable. | string | +| Format Script | `format_script` | Script to run for code formatting. Empty value to disable. | string | +| Check Typings Script | `check_typings_script` | Script to run for tyiping check. Empty value to disable. | string | +| Type Coverage Script | `type_coverage_script` | Script to run for type coverage. Empty value to disable. | string | +| Test Script | `test_script` | Script to run for tests. Empty value to disable. | string | +| Enable Bundlewatch | `enable_bundlewatch` | Enable Bundlewatch? | string | +| Enable Prettier | `enable_prettier` | Enable Prettier? | string | +| Enable Typescript | `enable_typescript` | Enable TypeScript? | string | +| Enable Tests | `enable_tests` | Enable Tests? | string | +| Backend Directory | `backend_directory` | The directory of the project where backend code is located. This should contain a `composer.json` file, and is generally the root directory of the repo. | string | +| Frontend Directory | `frontend_directory` | The directory of the project where frontend code is located. This should contain a `package.json` file. | string | +| Main Git Branch | `main_git_branch` | The main git branch to use for the workflow. | string | +| Node Version | `node_version` | The node version to use for the workflow. | string | +| JS Package Manager | `js_package_manager` | The package manager to use (ex. yarn) | string | +| Cache Dependency Path | `cache_dependency_path` | The path to the cache dependency file. | string | +:::tip + +For more details on parameters, [checkout the full predefined reusable workflow file](https://github.com/flarum/framework/blob/main/.github/workflows/REUSABLE_frontend.yml). + +::: diff --git a/docs/extend/testing.md b/docs/extend/testing.md index f38b90fbf..6a9b32402 100644 --- a/docs/extend/testing.md +++ b/docs/extend/testing.md @@ -20,8 +20,6 @@ $ flarum-cli infra backendTesting ::: -Firstly, you will need to require the `flarum/testing` composer package as a dev dependency for your extension: - ```bash composer require --dev flarum/testing:^1.0 ``` @@ -405,7 +403,167 @@ NOTE: If you find your extension needs _lots and lots_ of mocks, or mocks that f ## Frontend Tests -Coming Soon! +### Setup + +:::tip [Flarum CLI](https://github.com/flarum/cli) + +You can use the CLI to automatically add and update frontend testing infrastructure to your code: + +```bash +$ flarum-cli infra frontendTesting +``` + +::: + +First, you need to install the Jest config dev dependency: + +```bash +$ yarn add --dev @flarum/jest-config +``` + +Then, add the following to your `package.json`: + +```json +{ + "type": "module", + "scripts": { + ..., + "test": "yarn node --experimental-vm-modules $(yarn bin jest)" + } +} +``` + +Rename `webpack.config.js` to `webpack.config.cjs`. This is necessary because Jest doesn't support ESM yet. + +Create a `jest.config.cjs` file in the root of your extension: + +```js +module.exports = require('@flarum/jest-config')(); +``` + +If you are using TypeScript, create tsconfig.test.json with the following content: + +```json +{ + "extends": "./tsconfig.json", + "include": ["tests/**/*"], + "files": ["../../../node_modules/@flarum/jest-config/shims.d.ts"] +} +``` + +Then, you will need to set up a file structure for tests: + +``` +js +├── dist +├── src +├── tests +│ ├── unit +│ │ └── functionTest.test.js +│ ├── integration +│ │ └── componentTest.test.js +├── package.json +├── tsconfig.json +├── tsconfig.test.json +├── jest.config.cjs +└── webpack.config.cjs +``` + +#### GitHub Testing Workflow + +To run tests on every commit and pull request, check out the [GitHub Actions](github-actions.md) page. + +### Using Unit Tests + +Like any other JS project, you can use Jest to write unit tests for your frontend code. Checkout the [Jest docs](https://jestjs.io/docs/using-matchers) for more information on how to write tests. + +Here's a simple example of a unit test fo core's `abbreviateNumber` function: + +```ts +import abbreviateNumber from '../../../../src/common/utils/abbreviateNumber'; + +test('does not change small numbers', () => { + expect(abbreviateNumber(1)).toBe('1'); +}); + +test('abbreviates large numbers', () => { + expect(abbreviateNumber(1000000)).toBe('1M'); + expect(abbreviateNumber(100500)).toBe('100.5K'); +}); + +test('abbreviates large numbers with decimal places', () => { + expect(abbreviateNumber(100500)).toBe('100.5K'); + expect(abbreviateNumber(13234)).toBe('13.2K'); +}); +``` + +### Using Integration Tests + +Integration tests are used to test the components of your frontend code and the interaction between different components. For example, you might test that a page component renders the correct content based on certain parameters. + +Here's a simple example of an integration test for core's `Alert` component: + +```ts +import Alert from '../../../../src/common/components/Alert'; +import m from 'mithril'; +import mq from 'mithril-query'; +import { jest } from '@jest/globals'; + +describe('Alert displays as expected', () => { + it('should display alert messages with an icon', () => { + const alert = mq(m(Alert, { type: 'error' }, 'Shoot!')); + expect(alert).toContainRaw('Shoot!'); + expect(alert).toHaveElement('i.icon'); + }); + + it('should display alert messages with a custom icon when using a title', () => { + const alert = mq(Alert, { type: 'error', icon: 'fas fa-users', title: 'Woops..' }); + expect(alert).toContainRaw('Woops..'); + expect(alert).toHaveElement('i.fas.fa-users'); + }); + + it('should display alert messages with a title', () => { + const alert = mq(m(Alert, { type: 'error', title: 'Error Title' }, 'Shoot!')); + expect(alert).toContainRaw('Shoot!'); + expect(alert).toContainRaw('Error Title'); + expect(alert).toHaveElement('.Alert-title'); + }); + + it('should display alert messages with custom controls', () => { + const alert = mq(Alert, { type: 'error', controls: [m('button', { className: 'Button--test' }, 'Click me!')] }); + expect(alert).toHaveElement('button.Button--test'); + }); +}); + +describe('Alert is dismissible', () => { + it('should show dismiss button', function () { + const alert = mq(m(Alert, { dismissible: true }, 'Shoot!')); + expect(alert).toHaveElement('button.Alert-dismiss'); + }); + + it('should call ondismiss when dismiss button is clicked', function () { + const ondismiss = jest.fn(); + const alert = mq(Alert, { dismissible: true, ondismiss }); + alert.click('.Alert-dismiss'); + expect(ondismiss).toHaveBeenCalled(); + }); + + it('should not be dismissible if not chosen', function () { + const alert = mq(Alert, { type: 'error', dismissible: false }); + expect(alert).not.toHaveElement('button.Alert-dismiss'); + }); +}); +``` + +#### Methods + +These are the custom methods that are available for mithril component tests: +* **`toHaveElement(selector)`** - Checks if the component has an element that matches the given selector. +* **`toContainRaw(content)`** - Checks if the component HTML contains the given content. + +To negate any of these methods, simply prefix them with `not.`. For example, `expect(alert).not.toHaveElement('button.Alert-dismiss');`. For more information, check out the [Jest docs](https://jestjs.io/docs/using-matchers). For example you may need to check how to [mock functions](https://jestjs.io/docs/mock-functions), or how to use `beforeEach` and `afterEach` to set up and tear down tests. + + ## E2E Tests