mirror of
https://github.com/grafana/grafana.git
synced 2025-09-16 23:23:49 +08:00
Frontend: Move toEmit jest matchers to shared workspace (#108610)
* feat(test-utils): move the toEmitValue/s jest matchers to test-utils and add test script * chore(jest): update configs to use matchers from test-utils package * ci(frontend-tests): hook up packages tests * fix(test-utils): re-export matchers from index.ts so packages that include setupTests don't error * ci(pr-frontend-unit-tests): add frontend-packages-unit-tests to list of required unit tests * Update packages/grafana-test-utils/README.md Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --------- Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
This commit is contained in:
19
.github/workflows/pr-frontend-unit-tests.yml
vendored
19
.github/workflows/pr-frontend-unit-tests.yml
vendored
@ -107,6 +107,24 @@ jobs:
|
||||
- run: yarn install --immutable --check-cache
|
||||
- run: yarn run plugin:test:ci
|
||||
|
||||
frontend-packages-unit-tests:
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
name: "Packages unit tests"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- run: yarn install --immutable --check-cache
|
||||
- run: yarn run packages:test:ci
|
||||
|
||||
|
||||
# This is the job that is actually required by rulesets.
|
||||
# We need to require EITHER the OSS or the Enterprise job to pass.
|
||||
# However, if one is skipped, GitHub won't flat-map the shards,
|
||||
@ -116,6 +134,7 @@ jobs:
|
||||
- frontend-unit-tests
|
||||
- frontend-unit-tests-enterprise
|
||||
- frontend-decoupled-plugin-tests
|
||||
- frontend-packages-unit-tests
|
||||
# always() is the best function here.
|
||||
# success() || failure() will skip this function if any need is also skipped.
|
||||
# That means conditional test suites will fail the entire requirement check.
|
||||
|
@ -43,6 +43,7 @@
|
||||
"packages:prepare": "lerna version --no-push --no-git-tag-version --force-publish --exact",
|
||||
"packages:pack": "mkdir -p ./npm-artifacts && lerna exec --no-private -- yarn pack --out \"../../npm-artifacts/%s-%v.tgz\"",
|
||||
"packages:typecheck": "nx run-many -t typecheck --projects='tag:scope:package'",
|
||||
"packages:test:ci": "nx run-many -t test:ci --projects='tag:scope:package'",
|
||||
"prettier:check": "prettier --check --ignore-path .prettierignore --list-different=false --log-level=warn \"**/*.{ts,tsx,scss,md,mdx,json,js,cjs}\"",
|
||||
"prettier:checkDocs": "prettier --check --list-different=false --log-level=warn \"docs/**/*.md\" \"*.md\" \"packages/**/*.{ts,tsx,scss,md,mdx,json,js,cjs}\"",
|
||||
"prettier:write": "prettier --ignore-path .prettierignore --list-different \"**/*.{js,ts,tsx,scss,md,mdx,json,cjs}\" --write",
|
||||
|
@ -1,6 +1,10 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { TextEncoder, TextDecoder } from 'util';
|
||||
|
||||
import { matchers } from '@grafana/test-utils/matchers';
|
||||
|
||||
expect.extend(matchers);
|
||||
|
||||
Object.assign(global, { TextDecoder, TextEncoder });
|
||||
|
||||
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
||||
|
@ -1,3 +1,24 @@
|
||||
# Grafana test utils
|
||||
|
||||
This package is a collection of test utils and a mock API (using MSW) for use with core Grafana UI development.
|
||||
|
||||
## Matchers
|
||||
|
||||
To add the matchers to your Jest config, import them then extend `expect`. This should be done in the `setupFilesAfterEnv` file declared in `jest.config.{js,ts}`.
|
||||
|
||||
```ts
|
||||
// setupTests.ts
|
||||
import { matchers } from '@grafana/test-utils';
|
||||
|
||||
expect.extend(matchers);
|
||||
```
|
||||
|
||||
Included in this package are the following matchers:
|
||||
|
||||
### `toEmitValues`
|
||||
|
||||
Tests that an Observable emits the expected values in the correct order. This matcher collects all emitted values (including errors) and compares them against the expected array using deep equality.
|
||||
|
||||
### `toEmitValuesWith`
|
||||
|
||||
Tests that an Observable emits values that satisfy custom expectations. This matcher collects all emitted values and passes them to a callback function where you can perform custom assertions.
|
||||
|
3
packages/grafana-test-utils/jest-setup.js
Normal file
3
packages/grafana-test-utils/jest-setup.js
Normal file
@ -0,0 +1,3 @@
|
||||
import { matchers } from '@grafana/test-utils/matchers';
|
||||
|
||||
expect.extend(matchers);
|
29
packages/grafana-test-utils/jest.config.js
Normal file
29
packages/grafana-test-utils/jest.config.js
Normal file
@ -0,0 +1,29 @@
|
||||
process.env.TZ = 'Pacific/Easter'; // UTC-06:00 or UTC-05:00 depending on daylight savings
|
||||
|
||||
export default {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
|
||||
testMatch: ['<rootDir>/**/__tests__/**/*.{js,jsx,ts,tsx}', '<rootDir>/**/*.{spec,test,jest}.{js,jsx,ts,tsx}'],
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': [
|
||||
'@swc/jest',
|
||||
{
|
||||
sourceMaps: 'inline',
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
decorators: false,
|
||||
dynamicImport: true,
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
@ -40,17 +40,28 @@
|
||||
"./unstable": {
|
||||
"import": "./src/unstable.ts",
|
||||
"require": "./src/unstable.ts"
|
||||
},
|
||||
"./matchers": {
|
||||
"types": "./src/matchers/index.ts",
|
||||
"import": "./src/matchers/index.ts",
|
||||
"require": "./src/matchers/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"test": "jest --watch --onlyChanged",
|
||||
"test:ci": "jest --maxWorkers 4"
|
||||
},
|
||||
"dependencies": {
|
||||
"msw": "2.10.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/tsconfig": "^2.0.0",
|
||||
"@swc/core": "1.10.12",
|
||||
"@swc/jest": "^0.2.26",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "22.16.5",
|
||||
"jest": "29.7.0",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
@ -5,4 +5,6 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export {};
|
||||
// This is also exported as `@grafana/test-utils/matchers` but we cannot use that in places
|
||||
// where the tsconfig is not set to moduleResolution: bundler so we export it here also.
|
||||
export { matchers } from './matchers';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import { toEmitValues } from './toEmitValues';
|
||||
import { toEmitValuesWith } from './toEmitValuesWith';
|
28
packages/grafana-test-utils/src/matchers/types.ts
Normal file
28
packages/grafana-test-utils/src/matchers/types.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000;
|
||||
|
||||
export interface ObservableMatchers<R, T = {}> extends jest.ExpectExtendMap {
|
||||
toEmitValues<T>(received: Observable<T>, expected: T[]): Promise<jest.CustomMatcherResult>;
|
||||
toEmitValuesWith<T>(
|
||||
received: Observable<T>,
|
||||
expectations: (received: T[]) => void
|
||||
): Promise<jest.CustomMatcherResult>;
|
||||
}
|
||||
|
||||
type ObservableType<T> = T extends Observable<infer V> ? V : never;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace jest {
|
||||
interface Matchers<R, T = {}> {
|
||||
toEmitValues<E = ObservableType<T>>(expected: E[]): Promise<CustomMatcherResult>;
|
||||
/**
|
||||
* Collect all the values emitted by the observables (also errors) and pass them to the expectations functions after
|
||||
* the observable ended (or emitted error). If Observable does not complete within OBSERVABLE_TEST_TIMEOUT_IN_MS the
|
||||
* test fails.
|
||||
*/
|
||||
toEmitValuesWith<E = ObservableType<T>>(expectations: (received: E[]) => void): Promise<CustomMatcherResult>;
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1 +1 @@
|
||||
import '@grafana/plugin-configs/jest/jest-setup.js';
|
||||
import '@grafana/plugin-configs/jest/jest-setup';
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000;
|
||||
|
||||
export interface ObservableMatchers<R, T = {}> extends jest.ExpectExtendMap {
|
||||
toEmitValues<T>(received: Observable<T>, expected: T[]): Promise<jest.CustomMatcherResult>;
|
||||
toEmitValuesWith<T>(
|
||||
received: Observable<T>,
|
||||
expectations: (received: T[]) => void
|
||||
): Promise<jest.CustomMatcherResult>;
|
||||
}
|
@ -7,9 +7,9 @@ import i18next from 'i18next';
|
||||
import failOnConsole from 'jest-fail-on-console';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import getEnvConfig from '../../scripts/webpack/env-util';
|
||||
import { matchers } from '@grafana/test-utils';
|
||||
|
||||
import { matchers } from './matchers';
|
||||
import getEnvConfig from '../../scripts/webpack/env-util';
|
||||
|
||||
const config = getEnvConfig() as Record<string, string | boolean>;
|
||||
|
||||
|
@ -3714,7 +3714,11 @@ __metadata:
|
||||
resolution: "@grafana/test-utils@workspace:packages/grafana-test-utils"
|
||||
dependencies:
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@swc/core": "npm:1.10.12"
|
||||
"@swc/jest": "npm:^0.2.26"
|
||||
"@types/jest": "npm:29.5.14"
|
||||
"@types/node": "npm:22.16.5"
|
||||
jest: "npm:29.7.0"
|
||||
msw: "npm:2.10.4"
|
||||
typescript: "npm:5.8.3"
|
||||
languageName: unknown
|
||||
|
Reference in New Issue
Block a user