mirror of
https://github.com/grafana/grafana.git
synced 2025-09-17 02:52:51 +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 install --immutable --check-cache
|
||||||
- run: yarn run plugin:test:ci
|
- 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.
|
# This is the job that is actually required by rulesets.
|
||||||
# We need to require EITHER the OSS or the Enterprise job to pass.
|
# 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,
|
# However, if one is skipped, GitHub won't flat-map the shards,
|
||||||
@ -116,6 +134,7 @@ jobs:
|
|||||||
- frontend-unit-tests
|
- frontend-unit-tests
|
||||||
- frontend-unit-tests-enterprise
|
- frontend-unit-tests-enterprise
|
||||||
- frontend-decoupled-plugin-tests
|
- frontend-decoupled-plugin-tests
|
||||||
|
- frontend-packages-unit-tests
|
||||||
# always() is the best function here.
|
# always() is the best function here.
|
||||||
# success() || failure() will skip this function if any need is also skipped.
|
# success() || failure() will skip this function if any need is also skipped.
|
||||||
# That means conditional test suites will fail the entire requirement check.
|
# 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: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: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: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: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: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",
|
"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 '@testing-library/jest-dom';
|
||||||
import { TextEncoder, TextDecoder } from 'util';
|
import { TextEncoder, TextDecoder } from 'util';
|
||||||
|
|
||||||
|
import { matchers } from '@grafana/test-utils/matchers';
|
||||||
|
|
||||||
|
expect.extend(matchers);
|
||||||
|
|
||||||
Object.assign(global, { TextDecoder, TextEncoder });
|
Object.assign(global, { TextDecoder, TextEncoder });
|
||||||
|
|
||||||
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
# Grafana test utils
|
# Grafana test utils
|
||||||
|
|
||||||
This package is a collection of test utils and a mock API (using MSW) for use with core Grafana UI development.
|
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": {
|
"./unstable": {
|
||||||
"import": "./src/unstable.ts",
|
"import": "./src/unstable.ts",
|
||||||
"require": "./src/unstable.ts"
|
"require": "./src/unstable.ts"
|
||||||
|
},
|
||||||
|
"./matchers": {
|
||||||
|
"types": "./src/matchers/index.ts",
|
||||||
|
"import": "./src/matchers/index.ts",
|
||||||
|
"require": "./src/matchers/index.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
|
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||||
|
"test": "jest --watch --onlyChanged",
|
||||||
|
"test:ci": "jest --maxWorkers 4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"msw": "2.10.4"
|
"msw": "2.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@grafana/tsconfig": "^2.0.0",
|
"@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",
|
"@types/node": "22.16.5",
|
||||||
|
"jest": "29.7.0",
|
||||||
"typescript": "5.8.3"
|
"typescript": "5.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,6 @@
|
|||||||
* @packageDocumentation
|
* @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 { toEmitValues } from './toEmitValues';
|
||||||
import { toEmitValuesWith } from './toEmitValuesWith';
|
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 failOnConsole from 'jest-fail-on-console';
|
||||||
import { initReactI18next } from 'react-i18next';
|
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>;
|
const config = getEnvConfig() as Record<string, string | boolean>;
|
||||||
|
|
||||||
|
@ -3714,7 +3714,11 @@ __metadata:
|
|||||||
resolution: "@grafana/test-utils@workspace:packages/grafana-test-utils"
|
resolution: "@grafana/test-utils@workspace:packages/grafana-test-utils"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@grafana/tsconfig": "npm:^2.0.0"
|
"@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"
|
"@types/node": "npm:22.16.5"
|
||||||
|
jest: "npm:29.7.0"
|
||||||
msw: "npm:2.10.4"
|
msw: "npm:2.10.4"
|
||||||
typescript: "npm:5.8.3"
|
typescript: "npm:5.8.3"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
|
Reference in New Issue
Block a user