Skip to content

Commit 0bbcb8f

Browse files
authored
feat: frontend tests (#448)
1 parent f4fd13f commit 0bbcb8f

File tree

2 files changed

+214
-9
lines changed

2 files changed

+214
-9
lines changed

docs/extend/github-actions.md

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ In public repos, [GitHub Actions](https://github.com/features/actions) allow you
44

55
In this guide, you will learn how to add pre-defined workflows to your extension.
66

7-
## Setup
8-
97
:::tip [Flarum CLI](https://github.com/flarum/cli)
108

119
You can use the CLI to automatically add and update workflows to your code:
@@ -15,6 +13,9 @@ $ flarum-cli infra githubActions
1513

1614
:::
1715

16+
## Backend
17+
18+
1819
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:
1920

2021
```yaml
@@ -34,9 +35,7 @@ jobs:
3435
backend_directory: .
3536
```
3637
37-
## Backend
38-
39-
Flarum provides a pre-defined workflow for running certain jobs for the backend of your extension. These are the currently available jobs:
38+
These are the currently available jobs:
4039
4140
| Name | Key | Description |
4241
|-------------------------------------------------|--------------------------|----------------------------------------|
@@ -69,4 +68,52 @@ For more details on parameters, [checkout the full predefined reusable workflow
6968

7069
## Frontend
7170

72-
Soon..
71+
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:
72+
73+
```yaml
74+
name: Frontend
75+
76+
on: [workflow_dispatch, push, pull_request]
77+
78+
jobs:
79+
run:
80+
uses: flarum/framework/.github/workflows/REUSABLE_frontend.yml@main
81+
with:
82+
enable_bundlewatch: false
83+
enable_prettier: true
84+
enable_typescript: false
85+
86+
frontend_directory: ./js
87+
backend_directory: .
88+
js_package_manager: yarn
89+
main_git_branch: main
90+
91+
secrets:
92+
bundlewatch_github_token: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}
93+
```
94+
95+
Unlike the backend workflow, the frontend workflow runs everything in a single job. Here are the available parameters:
96+
97+
| Name | Key | Description | Format |
98+
|-----------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|--------|
99+
| Build Script | `build_script` | Script to run for production build. Empty value to disable. | string |
100+
| Build Typings Script | `build_typings_script` | Script to run for typings build. Empty value to disable. | string |
101+
| Format Script | `format_script` | Script to run for code formatting. Empty value to disable. | string |
102+
| Check Typings Script | `check_typings_script` | Script to run for tyiping check. Empty value to disable. | string |
103+
| Type Coverage Script | `type_coverage_script` | Script to run for type coverage. Empty value to disable. | string |
104+
| Test Script | `test_script` | Script to run for tests. Empty value to disable. | string |
105+
| Enable Bundlewatch | `enable_bundlewatch` | Enable Bundlewatch? | string |
106+
| Enable Prettier | `enable_prettier` | Enable Prettier? | string |
107+
| Enable Typescript | `enable_typescript` | Enable TypeScript? | string |
108+
| Enable Tests | `enable_tests` | Enable Tests? | string |
109+
| 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 |
110+
| Frontend Directory | `frontend_directory` | The directory of the project where frontend code is located. This should contain a `package.json` file. | string |
111+
| Main Git Branch | `main_git_branch` | The main git branch to use for the workflow. | string |
112+
| Node Version | `node_version` | The node version to use for the workflow. | string |
113+
| JS Package Manager | `js_package_manager` | The package manager to use (ex. yarn) | string |
114+
| Cache Dependency Path | `cache_dependency_path` | The path to the cache dependency file. | string |
115+
:::tip
116+
117+
For more details on parameters, [checkout the full predefined reusable workflow file](https://github.com/flarum/framework/blob/main/.github/workflows/REUSABLE_frontend.yml).
118+
119+
:::

docs/extend/testing.md

Lines changed: 161 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ $ flarum-cli infra backendTesting
2020

2121
:::
2222

23-
Firstly, you will need to require the `flarum/testing` composer package as a dev dependency for your extension:
24-
2523
```bash
2624
composer require --dev flarum/testing:^1.0
2725
```
@@ -405,7 +403,167 @@ NOTE: If you find your extension needs _lots and lots_ of mocks, or mocks that f
405403

406404
## Frontend Tests
407405

408-
Coming Soon!
406+
### Setup
407+
408+
:::tip [Flarum CLI](https://github.com/flarum/cli)
409+
410+
You can use the CLI to automatically add and update frontend testing infrastructure to your code:
411+
412+
```bash
413+
$ flarum-cli infra frontendTesting
414+
```
415+
416+
:::
417+
418+
First, you need to install the Jest config dev dependency:
419+
420+
```bash
421+
$ yarn add --dev @flarum/jest-config
422+
```
423+
424+
Then, add the following to your `package.json`:
425+
426+
```json
427+
{
428+
"type": "module",
429+
"scripts": {
430+
...,
431+
"test": "yarn node --experimental-vm-modules $(yarn bin jest)"
432+
}
433+
}
434+
```
435+
436+
Rename `webpack.config.js` to `webpack.config.cjs`. This is necessary because Jest doesn't support ESM yet.
437+
438+
Create a `jest.config.cjs` file in the root of your extension:
439+
440+
```js
441+
module.exports = require('@flarum/jest-config')();
442+
```
443+
444+
If you are using TypeScript, create tsconfig.test.json with the following content:
445+
446+
```json
447+
{
448+
"extends": "./tsconfig.json",
449+
"include": ["tests/**/*"],
450+
"files": ["../../../node_modules/@flarum/jest-config/shims.d.ts"]
451+
}
452+
```
453+
454+
Then, you will need to set up a file structure for tests:
455+
456+
```
457+
js
458+
├── dist
459+
├── src
460+
├── tests
461+
│ ├── unit
462+
│ │ └── functionTest.test.js
463+
│ ├── integration
464+
│ │ └── componentTest.test.js
465+
├── package.json
466+
├── tsconfig.json
467+
├── tsconfig.test.json
468+
├── jest.config.cjs
469+
└── webpack.config.cjs
470+
```
471+
472+
#### GitHub Testing Workflow
473+
474+
To run tests on every commit and pull request, check out the [GitHub Actions](github-actions.md) page.
475+
476+
### Using Unit Tests
477+
478+
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.
479+
480+
Here's a simple example of a unit test fo core's `abbreviateNumber` function:
481+
482+
```ts
483+
import abbreviateNumber from '../../../../src/common/utils/abbreviateNumber';
484+
485+
test('does not change small numbers', () => {
486+
expect(abbreviateNumber(1)).toBe('1');
487+
});
488+
489+
test('abbreviates large numbers', () => {
490+
expect(abbreviateNumber(1000000)).toBe('1M');
491+
expect(abbreviateNumber(100500)).toBe('100.5K');
492+
});
493+
494+
test('abbreviates large numbers with decimal places', () => {
495+
expect(abbreviateNumber(100500)).toBe('100.5K');
496+
expect(abbreviateNumber(13234)).toBe('13.2K');
497+
});
498+
```
499+
500+
### Using Integration Tests
501+
502+
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.
503+
504+
Here's a simple example of an integration test for core's `Alert` component:
505+
506+
```ts
507+
import Alert from '../../../../src/common/components/Alert';
508+
import m from 'mithril';
509+
import mq from 'mithril-query';
510+
import { jest } from '@jest/globals';
511+
512+
describe('Alert displays as expected', () => {
513+
it('should display alert messages with an icon', () => {
514+
const alert = mq(m(Alert, { type: 'error' }, 'Shoot!'));
515+
expect(alert).toContainRaw('Shoot!');
516+
expect(alert).toHaveElement('i.icon');
517+
});
518+
519+
it('should display alert messages with a custom icon when using a title', () => {
520+
const alert = mq(Alert, { type: 'error', icon: 'fas fa-users', title: 'Woops..' });
521+
expect(alert).toContainRaw('Woops..');
522+
expect(alert).toHaveElement('i.fas.fa-users');
523+
});
524+
525+
it('should display alert messages with a title', () => {
526+
const alert = mq(m(Alert, { type: 'error', title: 'Error Title' }, 'Shoot!'));
527+
expect(alert).toContainRaw('Shoot!');
528+
expect(alert).toContainRaw('Error Title');
529+
expect(alert).toHaveElement('.Alert-title');
530+
});
531+
532+
it('should display alert messages with custom controls', () => {
533+
const alert = mq(Alert, { type: 'error', controls: [m('button', { className: 'Button--test' }, 'Click me!')] });
534+
expect(alert).toHaveElement('button.Button--test');
535+
});
536+
});
537+
538+
describe('Alert is dismissible', () => {
539+
it('should show dismiss button', function () {
540+
const alert = mq(m(Alert, { dismissible: true }, 'Shoot!'));
541+
expect(alert).toHaveElement('button.Alert-dismiss');
542+
});
543+
544+
it('should call ondismiss when dismiss button is clicked', function () {
545+
const ondismiss = jest.fn();
546+
const alert = mq(Alert, { dismissible: true, ondismiss });
547+
alert.click('.Alert-dismiss');
548+
expect(ondismiss).toHaveBeenCalled();
549+
});
550+
551+
it('should not be dismissible if not chosen', function () {
552+
const alert = mq(Alert, { type: 'error', dismissible: false });
553+
expect(alert).not.toHaveElement('button.Alert-dismiss');
554+
});
555+
});
556+
```
557+
558+
#### Methods
559+
560+
These are the custom methods that are available for mithril component tests:
561+
* **`toHaveElement(selector)`** - Checks if the component has an element that matches the given selector.
562+
* **`toContainRaw(content)`** - Checks if the component HTML contains the given content.
563+
564+
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.
565+
566+
409567

410568
## E2E Tests
411569

0 commit comments

Comments
 (0)