mirror of
https://github.com/grafana/grafana.git
synced 2025-12-24 13:54:25 +08:00
Compare commits
63 Commits
dynamicall
...
ash/react-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4752b45f81 | ||
|
|
43ce2acafc | ||
|
|
f2cb70b1e0 | ||
|
|
7a1938c23b | ||
|
|
03de5f59e6 | ||
|
|
494a663449 | ||
|
|
d9059ca7b2 | ||
|
|
c254cf1387 | ||
|
|
ebf6ba442b | ||
|
|
462b6354d0 | ||
|
|
7f1f3c6ba6 | ||
|
|
de6d2700b7 | ||
|
|
481dc3e630 | ||
|
|
0f46f38b77 | ||
|
|
2f3a4d0358 | ||
|
|
d0aec88ca8 | ||
|
|
4a0e9204b3 | ||
|
|
12c6e9615f | ||
|
|
7a0d7c5dec | ||
|
|
b035732a85 | ||
|
|
05ef468b41 | ||
|
|
730f10597a | ||
|
|
caff0e2d1e | ||
|
|
f10a494369 | ||
|
|
8d42d4a079 | ||
|
|
720f038981 | ||
|
|
b380ce2bfd | ||
|
|
68a83b73c9 | ||
|
|
3808ddf948 | ||
|
|
f1d654d2e3 | ||
|
|
b0798f24c5 | ||
|
|
4c90d10281 | ||
|
|
96614c4eca | ||
|
|
fd4a97e49e | ||
|
|
68e0ed782c | ||
|
|
5fbbf2ac4a | ||
|
|
e97d48d86b | ||
|
|
74c656713a | ||
|
|
470cd869f3 | ||
|
|
3ef28b727f | ||
|
|
afe54f6739 | ||
|
|
f7d8fd4986 | ||
|
|
c73db56467 | ||
|
|
37bd5ded3a | ||
|
|
418c1a4d5a | ||
|
|
d3e807d6e2 | ||
|
|
03a044a9a0 | ||
|
|
e861318c2d | ||
|
|
35633b756d | ||
|
|
eb77bf89df | ||
|
|
636c62862d | ||
|
|
737ee7c7bd | ||
|
|
13b5c3f974 | ||
|
|
1915a92eb2 | ||
|
|
94cad60654 | ||
|
|
9d9085075b | ||
|
|
efeac25952 | ||
|
|
0da94b11ee | ||
|
|
9aa86eb056 | ||
|
|
f6107150e0 | ||
|
|
cb90eddf84 | ||
|
|
141ed7bdbf | ||
|
|
d2bf550499 |
24
.github/workflows/pr-e2e-tests.yml
vendored
24
.github/workflows/pr-e2e-tests.yml
vendored
@@ -192,6 +192,30 @@ jobs:
|
||||
-f "output[summary]=${IMAGE}" \
|
||||
-f "output[text]=${IMAGE}"
|
||||
|
||||
# TODO remove this when delivering
|
||||
# This will push the temporary docker image to dockerhub
|
||||
push-docker-image-to-dockerhub:
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-grafana
|
||||
steps:
|
||||
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
||||
with:
|
||||
name: grafana-docker-tar-gz
|
||||
path: .
|
||||
- uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2
|
||||
- name: Load & Push Docker image
|
||||
run: |
|
||||
set -euo pipefail
|
||||
LOADED_IMAGE_NAME=$(docker load -i grafana.docker.tar.gz | sed 's/Loaded image: //g')
|
||||
DOCKER_IMAGE="grafana/grafana:12.4.0-react19"
|
||||
docker tag "${LOADED_IMAGE_NAME}" "${DOCKER_IMAGE}"
|
||||
docker push "${DOCKER_IMAGE}"
|
||||
|
||||
run-e2e-tests:
|
||||
needs:
|
||||
- build-grafana
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"@types/lodash": "4.17.7",
|
||||
"@types/node": "24.9.2",
|
||||
"@types/prismjs": "1.26.4",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/uuid": "9.0.8",
|
||||
"glob": "10.5.0",
|
||||
@@ -37,8 +37,8 @@
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.6.3"
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"@types/lodash": "4.17.7",
|
||||
"@types/node": "24.9.2",
|
||||
"@types/prismjs": "1.26.4",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/uuid": "9.0.8",
|
||||
"glob": "10.4.1",
|
||||
@@ -37,8 +37,8 @@
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.6.3"
|
||||
|
||||
15
package.json
15
package.json
@@ -140,8 +140,8 @@
|
||||
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/prismjs": "1.26.5",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/react-grid-layout": "1.3.5",
|
||||
"@types/react-highlight-words": "0.20.0",
|
||||
"@types/react-resizable": "3.0.8",
|
||||
@@ -239,7 +239,7 @@
|
||||
"prettier": "3.6.2",
|
||||
"prom-client": "^15.1.3",
|
||||
"publint": "^0.3.12",
|
||||
"react-refresh": "0.14.0",
|
||||
"react-refresh": "0.18.0",
|
||||
"react-select-event": "5.5.1",
|
||||
"redux-mock-store": "1.5.5",
|
||||
"rimraf": "6.0.1",
|
||||
@@ -388,9 +388,9 @@
|
||||
"pluralize": "^8.0.0",
|
||||
"prismjs": "1.30.0",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "18.3.1",
|
||||
"react": "19.2.1",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-dom": "19.2.1",
|
||||
"react-draggable": "4.5.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-grid-layout": "patch:react-grid-layout@npm%3A1.4.4#~/.yarn/patches/react-grid-layout-npm-1.4.4-4024c5395b.patch",
|
||||
@@ -460,7 +460,10 @@
|
||||
"tmp@npm:^0.0.33": "~0.2.1",
|
||||
"js-yaml@npm:4.1.0": "^4.1.0",
|
||||
"js-yaml@npm:=4.1.0": "^4.1.0",
|
||||
"nodemailer": "7.0.7"
|
||||
"nodemailer": "7.0.7",
|
||||
"pretty-format/react-is": "19.0.0",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
||||
@@ -68,13 +68,13 @@
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/lodash": "^4",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/tinycolor2": "^1",
|
||||
"i18next": "^25.5.2",
|
||||
"i18next-cli": "^1.24.22",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.22.4",
|
||||
|
||||
@@ -92,12 +92,12 @@
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/papaparse": "5.3.16",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"esbuild": "0.25.8",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"rimraf": "6.0.1",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-esbuild": "6.2.1",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"@leeoniya/ufuzzy": "1.0.19",
|
||||
"d3": "^7.8.5",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-use": "17.6.0",
|
||||
"react-virtualized-auto-sizer": "1.0.26",
|
||||
"tinycolor2": "1.6.0",
|
||||
@@ -73,7 +73,7 @@
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-virtualized-auto-sizer": "1.0.8",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"babel-jest": "29.7.0",
|
||||
|
||||
@@ -22,7 +22,7 @@ import { getBarColorByDiff, getBarColorByPackage, getBarColorByValue } from './c
|
||||
import { CollapseConfig, CollapsedMap, FlameGraphDataContainer, LevelItem } from './dataTransform';
|
||||
|
||||
type RenderOptions = {
|
||||
canvasRef: RefObject<HTMLCanvasElement>;
|
||||
canvasRef: RefObject<HTMLCanvasElement | null>;
|
||||
data: FlameGraphDataContainer;
|
||||
root: LevelItem;
|
||||
direction: 'children' | 'parents';
|
||||
@@ -373,7 +373,7 @@ function useColorFunction(
|
||||
);
|
||||
}
|
||||
|
||||
function useSetupCanvas(canvasRef: RefObject<HTMLCanvasElement>, wrapperWidth: number, numberOfLevels: number) {
|
||||
function useSetupCanvas(canvasRef: RefObject<HTMLCanvasElement | null>, wrapperWidth: number, numberOfLevels: number) {
|
||||
const [ctx, setCtx] = useState<CanvasRenderingContext2D>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
"react-i18next": "^15.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react": "19.2.7",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-copy": "3.5.0",
|
||||
"typescript": "5.9.2"
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/systemjs": "6.15.3",
|
||||
"jest": "^29.6.4",
|
||||
"react": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"ts-jest": "29.4.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.2"
|
||||
|
||||
@@ -84,3 +84,19 @@ global.ResizeObserver = class ResizeObserver {
|
||||
this.#isObserving = false;
|
||||
}
|
||||
};
|
||||
|
||||
global.MessageChannel = jest.fn().mockImplementation(() => {
|
||||
let onmessage;
|
||||
return {
|
||||
port1: {
|
||||
set onmessage(cb) {
|
||||
onmessage = cb;
|
||||
},
|
||||
},
|
||||
port2: {
|
||||
postMessage: (data) => {
|
||||
onmessage?.({ data });
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"version": "12.4.0-pre",
|
||||
"dependencies": {
|
||||
"react": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
@@ -15,7 +15,7 @@
|
||||
"@swc/helpers": "^0.5.0",
|
||||
"@swc/jest": "^0.2.26",
|
||||
"@types/eslint": "9.6.1",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/webpack-bundle-analyzer": "^4.7.0",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"eslint": "9.32.0",
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
"@reduxjs/toolkit": "2.10.1",
|
||||
"@types/debounce-promise": "3.1.9",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/react-highlight-words": "0.20.0",
|
||||
"@types/react-window": "1.8.8",
|
||||
"@types/semver": "7.7.1",
|
||||
@@ -94,8 +94,8 @@
|
||||
"i18next-cli": "^1.24.22",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-select-event": "5.5.1",
|
||||
"rimraf": "6.0.1",
|
||||
"rollup": "^4.22.4",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useDebounce } from 'react-use';
|
||||
|
||||
import { TimeRange } from '@grafana/data';
|
||||
@@ -225,7 +226,10 @@ export const useMetricsLabelsValues = (timeRange: TimeRange, languageProvider: P
|
||||
newSelectedMetric === '' ? undefined : selector
|
||||
);
|
||||
|
||||
setMetrics(fetchedMetrics);
|
||||
// TODO why?!
|
||||
flushSync(() => {
|
||||
setMetrics(fetchedMetrics);
|
||||
});
|
||||
setSelectedMetric(newSelectedMetric);
|
||||
setLabelKeys(fetchedLabelKeys);
|
||||
setIsLoadingLabelKeys(false);
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('MetricsLabelsSection', () => {
|
||||
onBlur: onBlur,
|
||||
variableEditor: undefined,
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ describe('MetricsLabelsSection', () => {
|
||||
labelsFilters: defaultQuery.labels,
|
||||
variableEditor: undefined,
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -78,12 +78,12 @@
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"esbuild": "0.25.8",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"rimraf": "6.0.1",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-esbuild": "6.2.1",
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
"@react-awesome-query-builder/ui": "6.6.15",
|
||||
"immutable": "5.1.4",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-select": "5.10.2",
|
||||
"react-use": "17.6.0",
|
||||
"react-virtualized-auto-sizer": "1.0.26",
|
||||
@@ -43,8 +43,8 @@
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/react-virtualized-auto-sizer": "1.0.8",
|
||||
"@types/systemjs": "6.15.3",
|
||||
"@types/uuid": "10.0.0",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"@hello-pangea/dnd": "18.0.1",
|
||||
"@monaco-editor/react": "4.7.0",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@rc-component/cascader": "1.9.0",
|
||||
"@rc-component/drawer": "1.3.0",
|
||||
"@rc-component/picker": "1.7.1",
|
||||
"@rc-component/slider": "1.0.1",
|
||||
@@ -105,7 +106,6 @@
|
||||
"monaco-editor": "0.34.1",
|
||||
"ol": "10.7.0",
|
||||
"prismjs": "1.30.0",
|
||||
"rc-cascader": "3.34.0",
|
||||
"react-calendar": "^6.0.0",
|
||||
"react-colorful": "5.6.1",
|
||||
"react-custom-scrollbars-2": "4.5.0",
|
||||
@@ -167,9 +167,9 @@
|
||||
"@types/mock-raf": "1.0.6",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/prismjs": "1.26.5",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-color": "3.0.13",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/react-highlight-words": "0.20.0",
|
||||
"@types/react-transition-group": "4.4.12",
|
||||
"@types/react-window": "1.8.8",
|
||||
@@ -190,8 +190,8 @@
|
||||
"msw": "^2.10.2",
|
||||
"msw-storybook-addon": "^2.0.5",
|
||||
"process": "^0.11.10",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-select-event": "^5.1.0",
|
||||
"rimraf": "6.0.1",
|
||||
"rollup": "^4.22.4",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import RCCascader, { FieldNames } from 'rc-cascader';
|
||||
import RCCascader, { FieldNames } from '@rc-component/cascader';
|
||||
import * as React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@@ -47,7 +47,7 @@ export const ButtonCascader = (props: ButtonCascaderProps) => {
|
||||
<RCCascader
|
||||
onChange={onChangeCascader(onChange)}
|
||||
loadData={onLoadDataCascader(loadData)}
|
||||
dropdownClassName={cx(cascaderStyles.dropdown, styles.popup)}
|
||||
popupClassName={cx(cascaderStyles.dropdown, styles.popup)}
|
||||
{...rest}
|
||||
expandIcon={null}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import RCCascader from '@rc-component/cascader';
|
||||
import memoize from 'micro-memoize';
|
||||
import RCCascader from 'rc-cascader';
|
||||
import { PureComponent } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -279,7 +279,7 @@ class UnthemedCascader extends PureComponent<CascaderProps, CascaderState> {
|
||||
expandIcon={null}
|
||||
open={this.props.alwaysOpen}
|
||||
disabled={disabled}
|
||||
dropdownClassName={styles.dropdown}
|
||||
popupClassName={styles.dropdown}
|
||||
>
|
||||
<div className={disableDivFocus}>
|
||||
<Input
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseOptionType as RCCascaderOption, CascaderProps } from 'rc-cascader';
|
||||
import { BaseOptionType as RCCascaderOption, CascaderProps } from '@rc-component/cascader';
|
||||
|
||||
import { CascaderOption } from './Cascader';
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ interface ComboboxListProps<T extends string | number> {
|
||||
options: Array<ComboboxOption<T>>;
|
||||
highlightedIndex: number | null;
|
||||
selectedItems?: Array<ComboboxOption<T>>;
|
||||
scrollRef: React.RefObject<HTMLDivElement>;
|
||||
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
getItemProps: UseComboboxPropGetters<ComboboxOption<T>>['getItemProps'];
|
||||
enableAllOption?: boolean;
|
||||
isMultiSelect?: boolean;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { autoUpdate, autoPlacement, size, useFloating } from '@floating-ui/react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { CSSProperties, type RefObject, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { BOUNDARY_ELEMENT_ID } from '../../utils/floating';
|
||||
import { measureText } from '../../utils/measureText';
|
||||
@@ -21,7 +21,21 @@ const POPOVER_PADDING = 16;
|
||||
|
||||
const SCROLL_CONTAINER_PADDING = 8;
|
||||
|
||||
export const useComboboxFloat = (items: Array<ComboboxOption<string | number>>, isOpen: boolean) => {
|
||||
interface UseComboboxFloatReturn {
|
||||
inputRef: RefObject<HTMLInputElement | null>;
|
||||
floatingRef: RefObject<HTMLDivElement | null>;
|
||||
scrollRef: RefObject<HTMLDivElement | null>;
|
||||
floatStyles: CSSProperties & {
|
||||
width: number;
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const useComboboxFloat = (
|
||||
items: Array<ComboboxOption<string | number>>,
|
||||
isOpen: boolean
|
||||
): UseComboboxFloatReturn => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const floatingRef = useRef<HTMLDivElement>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useStyles2 } from '../../themes/ThemeContext';
|
||||
import { List } from '../List/List';
|
||||
|
||||
interface DataLinkSuggestionsProps {
|
||||
activeRef?: React.RefObject<HTMLDivElement>;
|
||||
activeRef?: React.RefObject<HTMLDivElement | null>;
|
||||
suggestions: VariableSuggestion[];
|
||||
activeIndex: number;
|
||||
onSuggestionSelect: (suggestion: VariableSuggestion) => void;
|
||||
@@ -103,7 +103,7 @@ DataLinkSuggestions.displayName = 'DataLinkSuggestions';
|
||||
interface DataLinkSuggestionsListProps extends DataLinkSuggestionsProps {
|
||||
label: string;
|
||||
activeIndexOffset: number;
|
||||
activeRef?: React.RefObject<HTMLDivElement>;
|
||||
activeRef?: React.RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
const DataLinkSuggestionsList = React.memo(
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('useListFocus', () => {
|
||||
|
||||
const testid = 'test';
|
||||
const getListElement = (
|
||||
ref: RefObject<HTMLUListElement>,
|
||||
ref: RefObject<HTMLUListElement | null>,
|
||||
handleKeys?: (event: KeyboardEvent) => void,
|
||||
onClick?: () => void
|
||||
) => (
|
||||
|
||||
@@ -7,7 +7,7 @@ const CAUGHT_KEYS = ['ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter', 'Tab'];
|
||||
|
||||
/** @internal */
|
||||
export interface UseListFocusProps {
|
||||
localRef: RefObject<HTMLUListElement>;
|
||||
localRef: RefObject<HTMLUListElement | null>;
|
||||
options: TimeOption[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RefObject, useRef } from 'react';
|
||||
|
||||
export function useFocus(): [RefObject<HTMLInputElement>, () => void] {
|
||||
export function useFocus(): [RefObject<HTMLInputElement | null>, () => void] {
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const setFocus = () => {
|
||||
ref.current && ref.current.focus();
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('useMenuFocus', () => {
|
||||
|
||||
const testid = 'test';
|
||||
const getMenuElement = (
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
ref: RefObject<HTMLDivElement | null>,
|
||||
handleKeys?: (event: KeyboardEvent) => void,
|
||||
handleFocus?: () => void,
|
||||
onClick?: () => void
|
||||
|
||||
@@ -6,7 +6,7 @@ const UNFOCUSED = -1;
|
||||
|
||||
/** @internal */
|
||||
export interface UseMenuFocusProps {
|
||||
localRef: RefObject<HTMLDivElement>;
|
||||
localRef: RefObject<HTMLDivElement | null>;
|
||||
isMenuOpen?: boolean;
|
||||
close?: () => void;
|
||||
onOpen?: (focusOnItem: (itemId: number) => void) => void;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { HTMLAttributes, PropsWithChildren, type JSX } from 'react';
|
||||
import * as React from 'react';
|
||||
import { createElement, type HTMLAttributes, type PropsWithChildren, type HTMLElementType, type JSX } from 'react';
|
||||
|
||||
import { textUtil } from '@grafana/data';
|
||||
|
||||
export interface RenderUserContentAsHTMLProps<T = HTMLSpanElement>
|
||||
extends Omit<HTMLAttributes<T>, 'dangerouslySetInnerHTML'> {
|
||||
component?: keyof React.ReactHTML;
|
||||
component?: HTMLElementType;
|
||||
content: string;
|
||||
}
|
||||
|
||||
@@ -19,7 +18,7 @@ export function RenderUserContentAsHTML<T>({
|
||||
content,
|
||||
...rest
|
||||
}: PropsWithChildren<RenderUserContentAsHTMLProps<T>>): JSX.Element {
|
||||
return React.createElement(component || 'span', {
|
||||
return createElement(component || 'span', {
|
||||
dangerouslySetInnerHTML: { __html: textUtil.sanitize(content) },
|
||||
...rest,
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface TableCellTooltipProps {
|
||||
field: Field;
|
||||
getActions: (field: Field, rowIdx: number) => ActionModel[];
|
||||
getTextColorForBackground: (bgColor: string) => string;
|
||||
gridRef: RefObject<DataGridHandle>;
|
||||
gridRef: RefObject<DataGridHandle | null>;
|
||||
height: number;
|
||||
placement?: TableCellTooltipPlacement;
|
||||
renderer: TableCellRenderer;
|
||||
|
||||
@@ -463,7 +463,7 @@ export function useColumnResize(
|
||||
return dataGridResizeHandler;
|
||||
}
|
||||
|
||||
export function useScrollbarWidth(ref: RefObject<DataGridHandle>, height: number) {
|
||||
export function useScrollbarWidth(ref: RefObject<DataGridHandle | null>, height: number) {
|
||||
const [scrollbarWidth, setScrollbarWidth] = useState(0);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
||||
@@ -49,7 +49,7 @@ interface RowsListProps {
|
||||
listHeight: number;
|
||||
width: number;
|
||||
cellHeight?: TableCellHeight;
|
||||
listRef: React.RefObject<VariableSizeList>;
|
||||
listRef: React.RefObject<VariableSizeList | null>;
|
||||
tableState: TableState;
|
||||
tableStyles: TableStyles;
|
||||
nestedDataField?: Field;
|
||||
|
||||
@@ -135,7 +135,7 @@ export const Table = memo((props: Props) => {
|
||||
// `useTableStateReducer`, which is needed to construct options for `useTable` (the hook that returns
|
||||
// `toggleAllRowsExpanded`), and if we used a variable, that variable would be undefined at the time
|
||||
// we initialize `useTableStateReducer`.
|
||||
const toggleAllRowsExpandedRef = useRef<(value?: boolean) => void>();
|
||||
const toggleAllRowsExpandedRef = useRef<(value?: boolean) => void>(null);
|
||||
|
||||
// Internal react table state reducer
|
||||
const stateReducer = useTableStateReducer({
|
||||
|
||||
@@ -14,8 +14,8 @@ import { GrafanaTableState } from './types';
|
||||
Select the scrollbar element from the VariableSizeList scope
|
||||
*/
|
||||
export function useFixScrollbarContainer(
|
||||
variableSizeListScrollbarRef: React.RefObject<HTMLDivElement>,
|
||||
tableDivRef: React.RefObject<HTMLDivElement>
|
||||
variableSizeListScrollbarRef: React.RefObject<HTMLDivElement | null>,
|
||||
tableDivRef: React.RefObject<HTMLDivElement | null>
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (variableSizeListScrollbarRef.current && tableDivRef.current) {
|
||||
@@ -43,7 +43,7 @@ export function useFixScrollbarContainer(
|
||||
*/
|
||||
export function useResetVariableListSizeCache(
|
||||
extendedState: GrafanaTableState,
|
||||
listRef: React.RefObject<VariableSizeList>,
|
||||
listRef: React.RefObject<VariableSizeList | null>,
|
||||
data: DataFrame,
|
||||
hasUniqueId: boolean
|
||||
) {
|
||||
|
||||
@@ -19,7 +19,7 @@ interface EventsCanvasProps {
|
||||
}
|
||||
|
||||
export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords, config }: EventsCanvasProps) {
|
||||
const plotInstance = useRef<uPlot>();
|
||||
const plotInstance = useRef<uPlot>(null);
|
||||
// render token required to re-render annotation markers. Rendering lines happens in uPlot and the props do not change
|
||||
// so we need to force the re-render when the draw hook was performed by uPlot
|
||||
const [renderToken, setRenderToken] = useState(0);
|
||||
|
||||
@@ -140,7 +140,7 @@ export const TooltipPlugin2 = ({
|
||||
|
||||
const [{ plot, isHovering, isPinned, contents, style, dismiss }, setState] = useReducer(mergeState, null, initState);
|
||||
|
||||
const sizeRef = useRef<TooltipContainerSize>();
|
||||
const sizeRef = useRef<TooltipContainerSize>(null);
|
||||
const styles = useStyles2(getStyles, maxWidth);
|
||||
|
||||
const renderRef = useRef(render);
|
||||
|
||||
@@ -96,7 +96,7 @@ export interface GraphNGState {
|
||||
export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
||||
static contextType = PanelContextRoot;
|
||||
panelContext: PanelContext = {} as PanelContext;
|
||||
private plotInstance: React.RefObject<uPlot>;
|
||||
private plotInstance: React.RefObject<uPlot | null>;
|
||||
|
||||
private subscription = new Subscription();
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ export const TooltipPlugin = ({
|
||||
renderTooltip,
|
||||
...otherProps
|
||||
}: TooltipPluginProps) => {
|
||||
const plotInstance = useRef<uPlot>();
|
||||
const plotInstance = useRef<uPlot>(null);
|
||||
const theme = useTheme2();
|
||||
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
|
||||
const [focusedPointIdx, setFocusedPointIdx] = useState<number | null>(null);
|
||||
|
||||
@@ -19,7 +19,7 @@ export function useDelayedSwitch(value: boolean, options: DelayOptions = {}): bo
|
||||
const { duration = 250, delay = 250 } = options;
|
||||
|
||||
const [delayedValue, setDelayedValue] = useState(value);
|
||||
const onStartTime = useRef<Date | undefined>();
|
||||
const onStartTime = useRef<Date | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@@ -34,7 +35,10 @@ export const ForgottenPassword = () => {
|
||||
const sendEmail = async (formModel: EmailDTO) => {
|
||||
const res = await getBackendSrv().post('/api/user/password/send-reset-email', formModel);
|
||||
if (res) {
|
||||
setEmailSent(true);
|
||||
// TODO why?
|
||||
flushSync(() => {
|
||||
setEmailSent(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ const defaultMatchers = {
|
||||
* "Time as X" core component, expects ascending x
|
||||
*/
|
||||
export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
||||
private plotInstance: React.RefObject<uPlot>;
|
||||
private plotInstance: React.RefObject<uPlot | null>;
|
||||
|
||||
constructor(props: GraphNGProps) {
|
||||
super(props);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { act } from '@testing-library/react';
|
||||
import { comboboxTestSetup } from 'test/helpers/comboboxTestSetup';
|
||||
import { getSelectParent, selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||
import { render, screen, userEvent, waitFor, within } from 'test/test-utils';
|
||||
@@ -29,7 +30,9 @@ const selectComboboxOptionInTest = async (input: HTMLElement, optionOrOptions: s
|
||||
};
|
||||
|
||||
const setup = async () => {
|
||||
const view = render(<SharedPreferences resourceUri="user" preferenceType="user" />);
|
||||
// TODO investigate why we need act
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
const view = await act(() => render(<SharedPreferences resourceUri="user" preferenceType="user" />));
|
||||
const themeSelect = await screen.findByRole('combobox', { name: 'Interface theme' });
|
||||
await waitFor(() => expect(themeSelect).not.toBeDisabled());
|
||||
return view;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
@@ -25,7 +26,10 @@ export const VerifyEmail = () => {
|
||||
getBackendSrv()
|
||||
.post('/api/user/signup', formModel)
|
||||
.then(() => {
|
||||
setEmailSent(true);
|
||||
// TODO why?
|
||||
flushSync(() => {
|
||||
setEmailSent(true);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
const msg = err.data?.message || err;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import { InitialEntry } from 'history';
|
||||
import { last } from 'lodash';
|
||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||
@@ -144,8 +145,11 @@ const fillOutForm = async ({
|
||||
};
|
||||
|
||||
const saveMuteTiming = async () => {
|
||||
const user = userEvent.setup();
|
||||
await user.click(await screen.findByText(/save time interval/i));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
const button = await screen.findByText(/save time interval/i);
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(button));
|
||||
};
|
||||
|
||||
setupMswServer();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { HTMLAttributes } from 'react';
|
||||
|
||||
import { Button, IconSize } from '@grafana/ui';
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLButtonElement> {
|
||||
interface Props extends Omit<HTMLAttributes<HTMLButtonElement>, 'onToggle'> {
|
||||
isCollapsed: boolean;
|
||||
onToggle: (isCollapsed: boolean) => void;
|
||||
// Todo: this should be made compulsory for a11y purposes
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||
import { Props } from 'react-virtualized-auto-sizer';
|
||||
import { render, userEvent, waitFor, waitForElementToBeRemoved } from 'test/test-utils';
|
||||
import { render, waitFor, waitForElementToBeRemoved } from 'test/test-utils';
|
||||
import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||
|
||||
import { mockExportApi, setupMswServer } from '../../mockApi';
|
||||
@@ -72,14 +73,15 @@ describe('GrafanaModifyExport', () => {
|
||||
json: 'Json Export Content',
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderModifyExport(grafanaRulerRule.grafana_alert.uid);
|
||||
|
||||
await waitForElementToBeRemoved(() => ui.loading.get());
|
||||
expect(await ui.form.nameInput.find()).toHaveValue('Grafana-rule');
|
||||
|
||||
await user.click(ui.exportButton.get());
|
||||
// TODO investigate why we need act
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(ui.exportButton.get()));
|
||||
|
||||
const drawer = await ui.exportDrawer.dialog.find();
|
||||
expect(drawer).toBeInTheDocument();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import { render, testWithFeatureToggles, waitFor } from 'test/test-utils';
|
||||
import { byLabelText, byRole } from 'testing-library-selector';
|
||||
|
||||
@@ -156,7 +157,10 @@ groups:
|
||||
await user.click(await ui.dsImport.mimirDsOption.find());
|
||||
|
||||
// Click the import button
|
||||
await user.click(ui.importButton.get());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(ui.importButton.get()));
|
||||
|
||||
// Verify confirmation dialog appears
|
||||
expect(await ui.confirmationModal.find()).toBeInTheDocument();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||
import { render, screen } from 'test/test-utils';
|
||||
import { byPlaceholderText, byRole, byTestId } from 'testing-library-selector';
|
||||
@@ -65,7 +66,10 @@ describe('new receiver', () => {
|
||||
await user.type(email, 'tester@grafana.com');
|
||||
|
||||
// try to test the contact point
|
||||
await user.click(await ui.testContactPointButton.find());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(async () => fireEvent.click(await ui.testContactPointButton.find()));
|
||||
|
||||
expect(await ui.testContactPointModal.find()).toBeInTheDocument();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { act } from '@testing-library/react';
|
||||
import type { JSX } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { render, screen, within } from 'test/test-utils';
|
||||
@@ -301,15 +302,20 @@ describe('AnnotationsField', function () {
|
||||
})
|
||||
);
|
||||
|
||||
render(
|
||||
<FormWrapper
|
||||
formValues={{
|
||||
annotations: [
|
||||
{ key: Annotation.dashboardUID, value: 'dash-test-uid' },
|
||||
{ key: Annotation.panelID, value: '1' },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
// TODO investigate why we need act
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act
|
||||
await act(() =>
|
||||
render(
|
||||
<FormWrapper
|
||||
formValues={{
|
||||
annotations: [
|
||||
{ key: Annotation.dashboardUID, value: 'dash-test-uid' },
|
||||
{ key: Annotation.panelID, value: '1' },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
expect(await ui.dashboardAnnotation.find()).toBeInTheDocument();
|
||||
|
||||
@@ -479,7 +479,7 @@ describe('RuleViewer', () => {
|
||||
expect.objectContaining({
|
||||
ruleUid: 'test-rule-uid',
|
||||
}),
|
||||
expect.any(Object)
|
||||
undefined
|
||||
);
|
||||
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
||||
});
|
||||
@@ -500,7 +500,7 @@ describe('RuleViewer', () => {
|
||||
expect.objectContaining({
|
||||
ruleUid: 'test-rule-uid',
|
||||
}),
|
||||
expect.any(Object)
|
||||
undefined
|
||||
);
|
||||
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import { produce } from 'immer';
|
||||
import { render } from 'test/test-utils';
|
||||
import { byRole, byText } from 'testing-library-selector';
|
||||
@@ -58,7 +59,7 @@ describe('Moving a Grafana managed rule', () => {
|
||||
|
||||
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
||||
|
||||
const { user } = render(
|
||||
render(
|
||||
<MoveRuleTestComponent
|
||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||
@@ -66,7 +67,10 @@ describe('Moving a Grafana managed rule', () => {
|
||||
rule={ruleToMove}
|
||||
/>
|
||||
);
|
||||
await user.click(byRole('button').get());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(byRole('button').get()));
|
||||
|
||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||
|
||||
@@ -87,7 +91,7 @@ describe('Moving a Grafana managed rule', () => {
|
||||
uid: 'does-not-exist',
|
||||
};
|
||||
|
||||
const { user } = render(
|
||||
render(
|
||||
<MoveRuleTestComponent
|
||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||
targetRuleGroupIdentifier={currentRuleGroupID}
|
||||
@@ -95,7 +99,10 @@ describe('Moving a Grafana managed rule', () => {
|
||||
rule={grafanaRulerRule}
|
||||
/>
|
||||
);
|
||||
await user.click(byRole('button').get());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(byRole('button').get()));
|
||||
|
||||
expect(await byText(/error/i).find()).toBeInTheDocument();
|
||||
});
|
||||
@@ -130,7 +137,7 @@ describe('Moving a Data source managed rule', () => {
|
||||
draft.grafana_alert.title = 'updated rule title';
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
render(
|
||||
<MoveRuleTestComponent
|
||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||
@@ -138,7 +145,10 @@ describe('Moving a Data source managed rule', () => {
|
||||
rule={newRule}
|
||||
/>
|
||||
);
|
||||
await user.click(byRole('button').get());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(byRole('button').get()));
|
||||
|
||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||
|
||||
@@ -167,7 +177,7 @@ describe('Moving a Data source managed rule', () => {
|
||||
|
||||
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
||||
|
||||
const { user } = render(
|
||||
render(
|
||||
<MoveRuleTestComponent
|
||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||
@@ -175,7 +185,10 @@ describe('Moving a Data source managed rule', () => {
|
||||
rule={ruleToMove}
|
||||
/>
|
||||
);
|
||||
await user.click(byRole('button').get());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(byRole('button').get()));
|
||||
|
||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||
|
||||
@@ -206,7 +219,7 @@ describe('Moving a Data source managed rule', () => {
|
||||
|
||||
const ruleID = fromRulerRuleAndRuleGroupIdentifier(currentRuleGroupID, ruleToMove);
|
||||
|
||||
const { user } = render(
|
||||
render(
|
||||
<MoveRuleTestComponent
|
||||
currentRuleGroupIdentifier={currentRuleGroupID}
|
||||
targetRuleGroupIdentifier={targetRuleGroupID}
|
||||
@@ -214,7 +227,10 @@ describe('Moving a Data source managed rule', () => {
|
||||
rule={ruleToMove}
|
||||
/>
|
||||
);
|
||||
await user.click(byRole('button').get());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(byRole('button').get()));
|
||||
|
||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||
|
||||
@@ -239,7 +255,7 @@ describe('Moving a Data source managed rule', () => {
|
||||
draft.grafana_alert.title = 'updated rule title';
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
render(
|
||||
<MoveRuleTestComponent
|
||||
currentRuleGroupIdentifier={curentRuleGroupID}
|
||||
targetRuleGroupIdentifier={curentRuleGroupID}
|
||||
@@ -247,7 +263,10 @@ describe('Moving a Data source managed rule', () => {
|
||||
rule={newRule}
|
||||
/>
|
||||
);
|
||||
await user.click(byRole('button').get());
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(byRole('button').get()));
|
||||
|
||||
expect(await byText(/error/i).find()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -31,7 +31,6 @@ describe('pause rule', () => {
|
||||
expect(byText(/uninitialized/i).get()).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(byRole('button').get());
|
||||
expect(await byText(/loading/i).find()).toBeInTheDocument();
|
||||
|
||||
expect(await byText(/success/i).find()).toBeInTheDocument();
|
||||
expect(await byText(/result/i).find()).toBeInTheDocument();
|
||||
@@ -68,7 +67,6 @@ describe('pause rule', () => {
|
||||
expect(await byText(/uninitialized/i).find()).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(byRole('button').get());
|
||||
expect(await byText(/loading/i).find()).toBeInTheDocument();
|
||||
expect(byText(/success/i).query()).not.toBeInTheDocument();
|
||||
expect(await byText(/error:(.+)oops/i).find()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -84,8 +84,8 @@ export function useAsync<Result, Args extends unknown[] = unknown[]>(
|
||||
error: undefined,
|
||||
result: initialValue,
|
||||
});
|
||||
const promiseRef = useRef<Promise<Result>>();
|
||||
const argsRef = useRef<Args>();
|
||||
const promiseRef = useRef<Promise<Result>>(undefined);
|
||||
const argsRef = useRef<Args>(undefined);
|
||||
|
||||
const methods = useSyncedRef({
|
||||
execute(...params: Args) {
|
||||
|
||||
@@ -155,8 +155,8 @@ export function useRuleGroupConsistencyCheck() {
|
||||
const { isGroupInSync } = useRuleGroupIsInSync();
|
||||
const [groupConsistent, setGroupConsistent] = useState<boolean | undefined>();
|
||||
|
||||
const apiCheckInterval = useRef<ReturnType<typeof setTimeout> | undefined>();
|
||||
const timeoutInterval = useRef<ReturnType<typeof setTimeout> | undefined>();
|
||||
const apiCheckInterval = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const timeoutInterval = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -245,8 +245,8 @@ export function useRuleGroupConsistencyCheck() {
|
||||
export function usePrometheusConsistencyCheck() {
|
||||
const { matchingPromRuleExists } = useMatchingPromRuleExists();
|
||||
|
||||
const removalConsistencyInterval = useRef<number | undefined>();
|
||||
const creationConsistencyInterval = useRef<number | undefined>();
|
||||
const removalConsistencyInterval = useRef<number | undefined>(undefined);
|
||||
const creationConsistencyInterval = useRef<number | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from 'test/test-utils';
|
||||
import { byRole } from 'testing-library-selector';
|
||||
|
||||
@@ -58,7 +59,10 @@ describe('RuleList - GroupedView', () => {
|
||||
});
|
||||
|
||||
it('should paginate through groups', async () => {
|
||||
const { user } = render(<GroupedView />);
|
||||
// TODO investigate why we need act
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act
|
||||
await act(() => render(<GroupedView />));
|
||||
|
||||
const mimirSection = await ui.dsSection(/Mimir/).find();
|
||||
|
||||
@@ -73,7 +77,10 @@ describe('RuleList - GroupedView', () => {
|
||||
expect(firstPageGroups[39]).toHaveTextContent('test-group-40');
|
||||
|
||||
const loadMoreButton = await within(mimirSection).findByRole('button', { name: /Show more/i });
|
||||
await user.click(loadMoreButton);
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(() => fireEvent.click(loadMoreButton));
|
||||
|
||||
await waitFor(() => expect(loadMoreButton).toBeEnabled());
|
||||
|
||||
@@ -86,7 +93,10 @@ describe('RuleList - GroupedView', () => {
|
||||
});
|
||||
|
||||
it('should disable next button when there is no more data', async () => {
|
||||
const { user } = render(<GroupedView />);
|
||||
// TODO investigate why we need act
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act
|
||||
await act(() => render(<GroupedView />));
|
||||
|
||||
const prometheusSection = await ui.dsSection(/Prometheus/).find();
|
||||
const promNamespace = await ui.namespace(/test-prometheus-namespace/).find(prometheusSection);
|
||||
@@ -96,17 +106,27 @@ describe('RuleList - GroupedView', () => {
|
||||
await ui.group('test-group-40').find(promNamespace);
|
||||
|
||||
// fetch page 2
|
||||
await user.click(await loadMoreButton.find(prometheusSection));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(async () => fireEvent.click(await loadMoreButton.find(prometheusSection)));
|
||||
// await user.click(await loadMoreButton.find(prometheusSection));
|
||||
// we should now have all groups 1-80
|
||||
await ui.group('test-group-80').find(promNamespace);
|
||||
|
||||
// fetch page 3
|
||||
await user.click(await loadMoreButton.find(prometheusSection));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(async () => fireEvent.click(await loadMoreButton.find(prometheusSection)));
|
||||
// we should now have all groups 1-120
|
||||
await ui.group('test-group-120').find(promNamespace);
|
||||
|
||||
// fetch page 4
|
||||
await user.click(await loadMoreButton.find(prometheusSection));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// eslint-disable-next-line testing-library/no-unnecessary-act, testing-library/prefer-user-event
|
||||
await act(async () => fireEvent.click(await loadMoreButton.find(prometheusSection)));
|
||||
// we should now have all groups 1-130
|
||||
await ui.group('test-group-130').find(promNamespace);
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ type Props = {
|
||||
|
||||
function LoadMoreHelper({ handleLoad }: Props) {
|
||||
const intersectionRef = useRef<HTMLDivElement>(null);
|
||||
// TODO remove when react-use is fixed
|
||||
// see https://github.com/streamich/react-use/issues/2612
|
||||
// @ts-expect-error
|
||||
const intersection = useIntersection(intersectionRef, {
|
||||
root: null,
|
||||
threshold: 1,
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ queryType: 'defaultAnnotationsQuery', refId: 'initialAnnotationRef' }),
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({ refId: 'initialAnnotationRef' }),
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
@@ -204,7 +204,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
||||
refId: 'A',
|
||||
}),
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
@@ -242,7 +242,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
||||
legendFormat: '{{method}} {{endpoint}}',
|
||||
}),
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
@@ -284,7 +284,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
||||
refId: 'AnnoTarget',
|
||||
}),
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
@@ -320,7 +320,7 @@ describe('StandardAnnotationQueryEditor', () => {
|
||||
expr: 'up',
|
||||
}),
|
||||
}),
|
||||
expect.anything()
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ function useScopesRow(onApply: () => void) {
|
||||
function useGlobalScopesSearch(searchQuery: string, parentId?: string | null) {
|
||||
const { selectScope, searchAllNodes, getScopeNodes } = useScopeServicesState();
|
||||
const [actions, setActions] = useState<CommandPaletteAction[] | undefined>(undefined);
|
||||
const searchQueryRef = useRef<string>();
|
||||
const searchQueryRef = useRef<string>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if ((!parentId || parentId === 'scopes') && searchQuery && config.featureToggles.scopeSearchAllLevels) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, waitFor, screen, within, Matcher, getByRole } from '@testing-library/react';
|
||||
import { render, waitFor, screen, within, Matcher, getByRole, act, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { merge, uniqueId } from 'lodash';
|
||||
import { openMenu } from 'react-select-event';
|
||||
@@ -300,7 +300,9 @@ describe('CorrelationsPage', () => {
|
||||
await userEvent.click(screen.getByText('Regular expression'));
|
||||
await userEvent.type(screen.getByLabelText(/expression/i), 'test expression');
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: /add$/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /add$/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_added');
|
||||
@@ -451,7 +453,9 @@ describe('CorrelationsPage', () => {
|
||||
await userEvent.clear(screen.getByRole('textbox', { name: /results field/i }));
|
||||
await userEvent.type(screen.getByRole('textbox', { name: /results field/i }), 'Line');
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /add$/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /add$/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_added');
|
||||
@@ -518,7 +522,9 @@ describe('CorrelationsPage', () => {
|
||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /save$/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /save$/i })));
|
||||
|
||||
expect(await screen.findByRole('cell', { name: /edited label$/i }, { timeout: 5000 })).toBeInTheDocument();
|
||||
|
||||
@@ -536,7 +542,9 @@ describe('CorrelationsPage', () => {
|
||||
const rowExpanderButton = within(tableRows[0]).getByRole('button', { name: /toggle row expanded/i });
|
||||
await userEvent.click(rowExpanderButton);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /next$/i })));
|
||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||
|
||||
// select Logfmt, be sure expression field is disabled
|
||||
@@ -575,7 +583,9 @@ describe('CorrelationsPage', () => {
|
||||
await userEvent.click(screen.getByRole('button', { name: /save$/i }));
|
||||
expect(screen.getByText('Please define an expression')).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByLabelText(/expression/i), 'test expression');
|
||||
await userEvent.click(screen.getByRole('button', { name: /save$/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /save$/i })));
|
||||
await waitFor(() => {
|
||||
expect(mocks.reportInteraction).toHaveBeenCalledWith('grafana_correlations_edited');
|
||||
});
|
||||
@@ -726,7 +736,9 @@ describe('CorrelationsPage', () => {
|
||||
expect(descriptionInput).toBeInTheDocument();
|
||||
expect(descriptionInput).toHaveAttribute('readonly');
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /next$/i })));
|
||||
await userEvent.click(screen.getByRole('button', { name: /next$/i }));
|
||||
|
||||
// expect the transformation to exist but be read only
|
||||
|
||||
@@ -58,7 +58,7 @@ jest.mock('app/features/plugins/extensions/getPluginExtensions', () => ({
|
||||
createPluginExtensionsGetter: () => getPluginExtensionsMock,
|
||||
}));
|
||||
|
||||
function setup({ routeProps }: { routeProps?: Partial<GrafanaRouteComponentProps> } = {}) {
|
||||
async function setup({ routeProps }: { routeProps?: Partial<GrafanaRouteComponentProps> } = {}) {
|
||||
const context = getGrafanaContextMock();
|
||||
const defaultRouteProps = getRouteComponentProps();
|
||||
const props: Props = {
|
||||
@@ -66,21 +66,29 @@ function setup({ routeProps }: { routeProps?: Partial<GrafanaRouteComponentProps
|
||||
...routeProps,
|
||||
};
|
||||
|
||||
const renderResult = render(
|
||||
<TestProvider grafanaContext={context}>
|
||||
<LocationServiceProvider service={locationService}>
|
||||
<DashboardScenePage {...props} />
|
||||
</LocationServiceProvider>
|
||||
</TestProvider>
|
||||
);
|
||||
|
||||
const rerender = (newProps: Props) => {
|
||||
renderResult.rerender(
|
||||
// react 19 changed how suspense rendering works
|
||||
// RTL hasn't caught up yet
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
// TODO remove this hack when RTL is updated. probably `render` itself will become async
|
||||
const renderResult = await act(async () =>
|
||||
render(
|
||||
<TestProvider grafanaContext={context}>
|
||||
<LocationServiceProvider service={locationService}>
|
||||
<DashboardScenePage {...newProps} />
|
||||
<DashboardScenePage {...props} />
|
||||
</LocationServiceProvider>
|
||||
</TestProvider>
|
||||
)
|
||||
);
|
||||
|
||||
const rerender = async (newProps: Props) => {
|
||||
await act(async () =>
|
||||
renderResult.rerender(
|
||||
<TestProvider grafanaContext={context}>
|
||||
<LocationServiceProvider service={locationService}>
|
||||
<DashboardScenePage {...newProps} />
|
||||
</LocationServiceProvider>
|
||||
</TestProvider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -152,6 +160,8 @@ describe('DashboardScenePage', () => {
|
||||
beforeEach(() => {
|
||||
locationService.push('/d/my-dash-uid');
|
||||
getDashboardScenePageStateManager().clearDashboardCache();
|
||||
getDashboardScenePageStateManager().clearSceneCache();
|
||||
getDashboardScenePageStateManager().clearState();
|
||||
loadDashboardMock.mockClear();
|
||||
loadDashboardMock.mockResolvedValue({ dashboard: simpleDashboard, meta: { slug: '123' } });
|
||||
// hacky way because mocking autosizer does not work
|
||||
@@ -163,7 +173,7 @@ describe('DashboardScenePage', () => {
|
||||
});
|
||||
|
||||
it('Can render dashboard', async () => {
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
await waitForDashboardToRender();
|
||||
|
||||
@@ -175,7 +185,7 @@ describe('DashboardScenePage', () => {
|
||||
});
|
||||
|
||||
it('routeReloadCounter should trigger reload', async () => {
|
||||
const { rerender, props } = setup();
|
||||
const { rerender, props } = await setup();
|
||||
|
||||
await waitForDashboardToRender();
|
||||
|
||||
@@ -190,13 +200,13 @@ describe('DashboardScenePage', () => {
|
||||
|
||||
props.location.state = { routeReloadCounter: 1 };
|
||||
|
||||
rerender(props);
|
||||
await rerender(props);
|
||||
|
||||
expect(await screen.findByTitle('Updated title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Can inspect panel', async () => {
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
await waitForDashboardToRender();
|
||||
|
||||
@@ -218,7 +228,7 @@ describe('DashboardScenePage', () => {
|
||||
});
|
||||
|
||||
it('Can view panel in fullscreen', async () => {
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
await waitForDashboardToRender();
|
||||
|
||||
@@ -238,7 +248,7 @@ describe('DashboardScenePage', () => {
|
||||
interval: {} as SystemDateFormatsState['interval'],
|
||||
useBrowserLocale: true,
|
||||
});
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
await waitForDashboardToRenderWithTimeRange({
|
||||
from: '03/11/2025, 02:09:37 AM',
|
||||
@@ -257,7 +267,7 @@ describe('DashboardScenePage', () => {
|
||||
interval: {} as SystemDateFormatsState['interval'],
|
||||
useBrowserLocale: true,
|
||||
});
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
await waitForDashboardToRenderWithTimeRange({
|
||||
from: '11.03.2025, 02:09:37',
|
||||
@@ -269,17 +279,17 @@ describe('DashboardScenePage', () => {
|
||||
describe('empty state', () => {
|
||||
it('Shows empty state when dashboard is empty', async () => {
|
||||
loadDashboardMock.mockResolvedValue({ dashboard: { uid: 'my-dash-uid', panels: [] }, meta: {} });
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
expect(await screen.findByText('Start your new dashboard by adding a visualization')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows and hides empty state when panels are added and removed', async () => {
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
await waitForDashboardToRender();
|
||||
|
||||
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||
|
||||
// Hacking a bit, accessing private cache property to get access to the underlying DashboardScene object
|
||||
const dashboardScenesCache = getDashboardScenePageStateManager().getCache();
|
||||
@@ -289,7 +299,7 @@ describe('DashboardScenePage', () => {
|
||||
act(() => {
|
||||
dashboard.removePanel(panels[0]);
|
||||
});
|
||||
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
dashboard.removePanel(panels[1]);
|
||||
@@ -301,14 +311,14 @@ describe('DashboardScenePage', () => {
|
||||
});
|
||||
|
||||
expect(await screen.findByTitle('Panel Added')).toBeInTheDocument();
|
||||
expect(await screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Start your new dashboard by adding a visualization')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('home page', () => {
|
||||
it('should render the dashboard when the route is home', async () => {
|
||||
(useParams as jest.Mock).mockReturnValue({});
|
||||
setup({
|
||||
await setup({
|
||||
routeProps: {
|
||||
route: {
|
||||
...getRouteComponentProps().route,
|
||||
@@ -331,7 +341,7 @@ describe('DashboardScenePage', () => {
|
||||
loadDashboardMock.mockClear();
|
||||
loadDashboardMock.mockResolvedValue({ dashboard: { uid: 'my-dash-uid', panels: [] }, meta: {} });
|
||||
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
await waitFor(() => expect(screen.queryByText('Refresh')).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.queryByText('Last 6 hours')).toBeInTheDocument());
|
||||
@@ -363,7 +373,7 @@ describe('DashboardScenePage', () => {
|
||||
isHandled: true,
|
||||
});
|
||||
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
expect(await screen.findByTestId(selectors.components.EntityNotFound.container)).toBeInTheDocument();
|
||||
});
|
||||
@@ -387,7 +397,7 @@ describe('DashboardScenePage', () => {
|
||||
isHandled: true,
|
||||
});
|
||||
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
expect(await screen.findByTestId('dashboard-page-error')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('dashboard-page-error')).toHaveTextContent('Internal server error');
|
||||
@@ -396,7 +406,7 @@ describe('DashboardScenePage', () => {
|
||||
it('should render error alert for runtime errors', async () => {
|
||||
setupLoadDashboardRuntimeErrorMock();
|
||||
|
||||
setup();
|
||||
await setup();
|
||||
|
||||
expect(await screen.findByTestId('dashboard-page-error')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('dashboard-page-error')).toHaveTextContent('Runtime error');
|
||||
@@ -411,7 +421,7 @@ describe('DashboardScenePage', () => {
|
||||
const manager = getDashboardScenePageStateManager();
|
||||
manager.setActiveManager('v2');
|
||||
|
||||
const { unmount } = setup();
|
||||
const { unmount } = await setup();
|
||||
|
||||
expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2);
|
||||
unmount();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { screen, waitForElementToBeRemoved } from '@testing-library/react';
|
||||
import { act, screen } from '@testing-library/react';
|
||||
import { Route, Routes } from 'react-router-dom-v5-compat';
|
||||
import { of } from 'rxjs';
|
||||
import { render } from 'test/test-utils';
|
||||
@@ -26,7 +26,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
function setup(token = 'an-access-token') {
|
||||
async function setup(token = 'an-access-token') {
|
||||
const pubdashProps: PublicDashboardSceneProps = {
|
||||
...getRouteComponentProps({
|
||||
route: {
|
||||
@@ -37,11 +37,19 @@ function setup(token = 'an-access-token') {
|
||||
}),
|
||||
};
|
||||
|
||||
return render(
|
||||
<Routes>
|
||||
<Route path="/public-dashboards/:accessToken" element={<PublicDashboardScenePage {...pubdashProps} />} />
|
||||
</Routes>,
|
||||
{ historyOptions: { initialEntries: [`/public-dashboards/${token}`] } }
|
||||
// TODO investigate why act is needed here
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
return await act(() =>
|
||||
render(
|
||||
<Routes>
|
||||
<Route
|
||||
path="/public-dashboards/:accessToken"
|
||||
element={<PublicDashboardScenePage {...pubdashProps} />}
|
||||
key={token}
|
||||
/>
|
||||
</Routes>,
|
||||
{ historyOptions: { initialEntries: [`/public-dashboards/${token}`] } }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,7 +123,6 @@ const publicDashboardSceneSelector = e2eSelectors.pages.PublicDashboardScene;
|
||||
|
||||
describe('PublicDashboardScenePage', () => {
|
||||
beforeEach(() => {
|
||||
config.publicDashboardAccessToken = 'an-access-token';
|
||||
getDashboardScenePageStateManager().clearDashboardCache();
|
||||
setupLoadDashboardMock({ dashboard: simpleDashboard, meta: {} });
|
||||
|
||||
@@ -125,7 +132,9 @@ describe('PublicDashboardScenePage', () => {
|
||||
});
|
||||
|
||||
it('can render public dashboard', async () => {
|
||||
setup();
|
||||
const accessToken = 'an-access-token';
|
||||
config.publicDashboardAccessToken = accessToken;
|
||||
await setup(accessToken);
|
||||
|
||||
await waitForDashboardGridToRender();
|
||||
|
||||
@@ -139,7 +148,9 @@ describe('PublicDashboardScenePage', () => {
|
||||
});
|
||||
|
||||
it('cannot see menu panel', async () => {
|
||||
setup();
|
||||
const accessToken = 'cannot-see-menu-panel';
|
||||
config.publicDashboardAccessToken = accessToken;
|
||||
await setup(accessToken);
|
||||
|
||||
await waitForDashboardGridToRender();
|
||||
|
||||
@@ -148,7 +159,9 @@ describe('PublicDashboardScenePage', () => {
|
||||
});
|
||||
|
||||
it('shows time controls when it is not hidden', async () => {
|
||||
setup();
|
||||
const accessToken = 'shows-time-controls';
|
||||
config.publicDashboardAccessToken = accessToken;
|
||||
await setup(accessToken);
|
||||
|
||||
await waitForDashboardGridToRender();
|
||||
|
||||
@@ -158,7 +171,9 @@ describe('PublicDashboardScenePage', () => {
|
||||
});
|
||||
|
||||
it('does not render paused or deleted screen', async () => {
|
||||
setup();
|
||||
const accessToken = 'does-not-render-paused-or-deleted-screen';
|
||||
config.publicDashboardAccessToken = accessToken;
|
||||
await setup(accessToken);
|
||||
|
||||
await waitForDashboardGridToRender();
|
||||
|
||||
@@ -172,7 +187,7 @@ describe('PublicDashboardScenePage', () => {
|
||||
dashboard: { ...simpleDashboard, timepicker: { hidden: true } },
|
||||
meta: {},
|
||||
});
|
||||
setup(accessToken);
|
||||
await setup(accessToken);
|
||||
|
||||
await waitForDashboardGridToRender();
|
||||
|
||||
@@ -207,9 +222,7 @@ describe('given unavailable public dashboard', () => {
|
||||
},
|
||||
});
|
||||
|
||||
setup(accessToken);
|
||||
|
||||
await waitForElementToBeRemoved(screen.getByTestId(publicDashboardSceneSelector.loadingPage));
|
||||
await setup(accessToken);
|
||||
|
||||
expect(screen.queryByTestId(publicDashboardSceneSelector.page)).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
|
||||
@@ -239,9 +252,7 @@ describe('given unavailable public dashboard', () => {
|
||||
},
|
||||
});
|
||||
|
||||
setup(accessToken);
|
||||
|
||||
await waitForElementToBeRemoved(screen.getByTestId(publicDashboardSceneSelector.loadingPage));
|
||||
await setup(accessToken);
|
||||
|
||||
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).not.toBeInTheDocument();
|
||||
|
||||
@@ -49,7 +49,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
|
||||
const [contentSent, setContentSent] = useState<{ title?: string; folderUid?: string }>({});
|
||||
|
||||
const validationTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
const validationTimeoutRef = useRef<NodeJS.Timeout>(null);
|
||||
|
||||
// Validate title on form mount to catch invalid default values
|
||||
useEffect(() => {
|
||||
@@ -59,14 +59,18 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
// Cleanup timeout on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
if (validationTimeoutRef.current) {
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleTitleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue('title', e.target.value, { shouldDirty: true });
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
if (validationTimeoutRef.current) {
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
}
|
||||
validationTimeoutRef.current = setTimeout(() => {
|
||||
trigger('title');
|
||||
}, 400);
|
||||
@@ -75,7 +79,9 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
);
|
||||
|
||||
const onSave = async (overwrite: boolean) => {
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
if (validationTimeoutRef.current) {
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
}
|
||||
|
||||
const isTitleValid = await trigger('title');
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { TabsLayoutManager } from '../layout-tabs/TabsLayoutManager';
|
||||
* Will scroll element into view. If element is not connected yet, it will try to expand rows
|
||||
* and switch tabs to make it visible.
|
||||
*/
|
||||
export function scrollCanvasElementIntoView(sceneObject: SceneObject, ref: React.RefObject<HTMLElement>) {
|
||||
export function scrollCanvasElementIntoView(sceneObject: SceneObject, ref: React.RefObject<HTMLElement | null>) {
|
||||
if (ref.current?.isConnected) {
|
||||
scrollIntoView(ref.current);
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { screen, waitFor, within } from '@testing-library/react';
|
||||
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
@@ -154,7 +154,9 @@ describe('VersionSettings', () => {
|
||||
expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(VERSIONS_FETCH_LIMIT);
|
||||
|
||||
const showMoreButton = screen.getByRole('button', { name: /show more versions/i });
|
||||
await user.click(showMoreButton);
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
act(() => fireEvent.click(showMoreButton));
|
||||
|
||||
expect(historySrv.getHistoryList).toBeCalledTimes(2);
|
||||
expect(screen.getByText(/Fetching more entries/i)).toBeInTheDocument();
|
||||
|
||||
@@ -21,7 +21,7 @@ export const usePanelLatestData = (
|
||||
options: GetDataOptions,
|
||||
checkSchema?: boolean
|
||||
): UsePanelLatestData => {
|
||||
const querySubscription = useRef<Unsubscribable>();
|
||||
const querySubscription = useRef<Unsubscribable>(null);
|
||||
const [latestData, setLatestData] = useState<PanelData>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -61,7 +61,7 @@ interface State {
|
||||
|
||||
class UnThemedTransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
|
||||
subscription?: Unsubscribable;
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
ref: RefObject<HTMLDivElement | null>;
|
||||
|
||||
constructor(props: TransformationsEditorProps) {
|
||||
super(props);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import { Routes, Route } from 'react-router-dom-v5-compat';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
@@ -62,7 +62,9 @@ describe('PublicDashboardPageProxy', () => {
|
||||
describe('when scene feature enabled', () => {
|
||||
it('should render PublicDashboardScenePage if publicDashboardsScene is enabled', async () => {
|
||||
config.featureToggles.publicDashboardsScene = true;
|
||||
setup({});
|
||||
// TODO investigate why we need act
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => setup({}));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId(PublicDashboardScene.page)).toBeInTheDocument();
|
||||
|
||||
@@ -64,7 +64,7 @@ export function useDatasource(dataSource: string | DataSourceRef | DataSourceIns
|
||||
|
||||
export interface KeybaordNavigatableListProps {
|
||||
keyboardEvents?: Observable<React.KeyboardEvent>;
|
||||
containerRef: React.RefObject<HTMLElement>;
|
||||
containerRef: React.RefObject<HTMLElement | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,7 @@ interface State {
|
||||
}
|
||||
|
||||
export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
private latestThresholdInputRef: React.RefObject<HTMLInputElement>;
|
||||
private latestThresholdInputRef: React.RefObject<HTMLInputElement | null>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@@ -46,6 +46,9 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
||||
);
|
||||
const styles = useStyles2(getStyles, contentOutlineExpanded);
|
||||
const scrollerRef = useRef(scroller || null);
|
||||
// TODO remove when react-use is fixed
|
||||
// see https://github.com/streamich/react-use/issues/2612
|
||||
// @ts-expect-error
|
||||
const { y: verticalScroll } = useScroll(scrollerRef);
|
||||
const { outlineItems } = useContentOutlineContext() ?? { outlineItems: [] };
|
||||
const [activeSectionId, setActiveSectionId] = useState(outlineItems[0]?.id);
|
||||
|
||||
@@ -74,7 +74,7 @@ export const ExplorePaneContainer = connector(ExplorePaneContainerUnconnected);
|
||||
|
||||
function useStopQueries(exploreId: string) {
|
||||
const paneSelector = useMemo(() => getExploreItemSelector(exploreId), [exploreId]);
|
||||
const paneRef = useRef<ReturnType<typeof paneSelector>>();
|
||||
const paneRef = useRef<ReturnType<typeof paneSelector>>(null);
|
||||
paneRef.current = useSelector(paneSelector);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -63,7 +63,7 @@ type Props = {
|
||||
scrollElementClass?: string;
|
||||
traceProp: Trace;
|
||||
datasource: DataSourceApi<DataQuery, DataSourceJsonData, {}> | undefined;
|
||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
||||
topOfViewRef?: RefObject<HTMLDivElement | null>;
|
||||
createSpanLink?: SpanLinkFunc;
|
||||
focusedSpanId?: string;
|
||||
createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel<Field>;
|
||||
|
||||
@@ -57,7 +57,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
||||
const [focusedSpanIndexForSearch, setFocusedSpanIndexForSearch] = useState(-1);
|
||||
const [tagKeys, setTagKeys] = useState<Array<SelectableValue<string>>>();
|
||||
const [tagValues, setTagValues] = useState<{ [key: string]: Array<SelectableValue<string>> }>({});
|
||||
const prevTraceIdRef = useRef<string>();
|
||||
const prevTraceIdRef = useRef<string>(null);
|
||||
|
||||
const durationRegex = /^\d+(?:\.\d)?\d*(?:ns|us|µs|ms|s|m|h)$/;
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ const timeRange = {
|
||||
to: new Date(1000),
|
||||
} as unknown as TimeRange;
|
||||
|
||||
function getContent(result: React.ReactElement) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function getContent(result: React.ReactElement<any>) {
|
||||
return result.props.children.props.children[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -561,7 +561,7 @@ const CardsContainer = ({
|
||||
mainContainerRef,
|
||||
}: {
|
||||
listOfContentCards: React.ReactNode[];
|
||||
mainContainerRef?: React.RefObject<HTMLDivElement>;
|
||||
mainContainerRef?: React.RefObject<HTMLDivElement | null>;
|
||||
}) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ type TVirtualizedTraceViewOwnProps = {
|
||||
focusedSpanIdForSearch: string;
|
||||
showSpanFilterMatchesOnly: boolean;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
||||
topOfViewRef?: RefObject<HTMLDivElement | null>;
|
||||
datasourceType: string;
|
||||
datasourceUid: string;
|
||||
headerHeight: number;
|
||||
|
||||
@@ -104,7 +104,7 @@ export type TProps = {
|
||||
focusedSpanIdForSearch: string;
|
||||
showSpanFilterMatchesOnly: boolean;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
||||
topOfViewRef?: RefObject<HTMLDivElement | null>;
|
||||
headerHeight: number;
|
||||
criticalPath: CriticalPathSection[];
|
||||
traceFlameGraphs: TraceFlameGraphs;
|
||||
@@ -197,8 +197,10 @@ export class UnthemedTraceTimelineViewer extends PureComponent<TProps, State> {
|
||||
return (
|
||||
<div
|
||||
className={styles.TraceTimelineViewer}
|
||||
ref={(ref: HTMLDivElement | null) => {
|
||||
ref && this.setState({ height: ref.getBoundingClientRect().height });
|
||||
ref={(ref) => {
|
||||
if (ref) {
|
||||
this.setState({ height: ref.getBoundingClientRect().height });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TimelineHeaderRow
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { screen, waitFor, within } from '@testing-library/react';
|
||||
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
@@ -103,7 +103,9 @@ describe('SignupInvitedPage', () => {
|
||||
get: { email: '', invitedBy: '', name: '', username: '', orgName: '' },
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /sign up/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /sign up/i })));
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/email is required/i)).toBeInTheDocument());
|
||||
expect(screen.getByText(/username is required/i)).toBeInTheDocument();
|
||||
|
||||
@@ -43,7 +43,7 @@ export const LibraryPanelsView = ({
|
||||
}
|
||||
);
|
||||
const asyncDispatch = useMemo(() => asyncDispatcher(dispatch), [dispatch]);
|
||||
const abortControllerRef = useRef<AbortController>();
|
||||
const abortControllerRef = useRef<AbortController>(null);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface Props {}
|
||||
|
||||
export const LiveConnectionWarning = memo(function LiveConnectionWarning() {
|
||||
const [show, setShow] = useState<boolean | undefined>(undefined);
|
||||
const subscriptionRef = useRef<Unsubscribable>();
|
||||
const subscriptionRef = useRef<Unsubscribable>(null);
|
||||
const styles = useStyles2(getStyle);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { isObservable, lastValueFrom } from 'rxjs';
|
||||
|
||||
import { DataFrame, DataQueryRequest, DataSourceApi, GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||
@@ -31,7 +32,10 @@ export const LogLineDetailsTrace = ({ timeRange, timeZone, traceRef }: Props) =>
|
||||
.get(traceRef.dsUID)
|
||||
.then((dataSource) => {
|
||||
if (dataSource) {
|
||||
setDataSource(dataSource);
|
||||
// TODO why?
|
||||
flushSync(() => {
|
||||
setDataSource(dataSource);
|
||||
});
|
||||
} else {
|
||||
setDataFrames(null);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { QueryStatus } from '@reduxjs/toolkit/query';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { UserEvent } from '@testing-library/user-event';
|
||||
import type { JSX } from 'react';
|
||||
import { act, type JSX } from 'react';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import {
|
||||
@@ -237,7 +237,9 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
||||
@@ -245,7 +247,9 @@ describe('ProvisioningWizard', () => {
|
||||
|
||||
expect(mockUseCreateOrUpdateRepository).toHaveBeenCalled();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Synchronize with external storage/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Synchronize with external storage/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: /3\. Synchronize with external storage/i })).toBeInTheDocument();
|
||||
@@ -281,7 +285,9 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
||||
@@ -339,7 +345,9 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Branch "invalid-branch" not found')).toBeInTheDocument();
|
||||
@@ -373,7 +381,9 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
@@ -414,7 +424,9 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('alert')).toBeInTheDocument();
|
||||
@@ -442,13 +454,17 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: /2\. Choose what to synchronize/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Previous/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Previous/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: /1\. Connect to external storage/i })).toBeInTheDocument();
|
||||
@@ -485,7 +501,9 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(() => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
expect(screen.getByRole('button', { name: /Submitting.../i })).toBeDisabled();
|
||||
});
|
||||
@@ -522,7 +540,9 @@ describe('ProvisioningWizard', () => {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Choose what to synchronize/i }));
|
||||
// TODO investigate why we need act/fireEvent
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(async () => fireEvent.click(screen.getByRole('button', { name: /Choose what to synchronize/i })));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: /Synchronize with external storage/i })).toBeInTheDocument();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { AppEvents } from '@grafana/data';
|
||||
@@ -43,7 +44,10 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions
|
||||
const workflow = watch('workflow');
|
||||
|
||||
const handleSubmitForm = async (data: BulkActionFormData) => {
|
||||
setHasSubmitted(true);
|
||||
// TODO why?
|
||||
flushSync(() => {
|
||||
setHasSubmitted(true);
|
||||
});
|
||||
|
||||
const resources = collectSelectedItems(selectedItems);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export function useSearchKeyboardNavigation(
|
||||
): ItemSelection {
|
||||
const highlightIndexRef = useRef<ItemSelection>({ x: 0, y: -1 });
|
||||
const [highlightIndex, setHighlightIndex] = useState<ItemSelection>({ x: 0, y: -1 });
|
||||
const urlsRef = useRef<Field>();
|
||||
const urlsRef = useRef<Field>(null);
|
||||
|
||||
// Clear selection when the search results change
|
||||
useEffect(() => {
|
||||
|
||||
@@ -77,7 +77,7 @@ export const SuggestionsInput = ({
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme, inputHeight);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>();
|
||||
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
scrollRef.current?.scrollTo(0, scrollTop);
|
||||
|
||||
@@ -11,7 +11,7 @@ import checkboxWhitePng from 'img/checkbox_white.png';
|
||||
|
||||
import { ALL_VARIABLE_VALUE } from '../../constants';
|
||||
|
||||
export interface Props extends React.HTMLProps<HTMLUListElement>, Themeable2 {
|
||||
export interface Props extends Omit<React.HTMLProps<HTMLUListElement>, 'onToggle'>, Themeable2 {
|
||||
multi: boolean;
|
||||
values: VariableOption[];
|
||||
selectedValues: VariableOption[];
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
"lodash": "4.17.21",
|
||||
"monaco-editor": "0.34.1",
|
||||
"prismjs": "1.30.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-select": "5.10.2",
|
||||
"react-use": "17.6.0",
|
||||
"rxjs": "7.8.2",
|
||||
@@ -36,8 +36,8 @@
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/prismjs": "1.26.5",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"i18next-cli": "^1.24.22",
|
||||
"jest": "29.7.0",
|
||||
"react-select-event": "5.5.1",
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
"lodash": "4.17.21",
|
||||
"monaco-editor": "0.34.1",
|
||||
"prismjs": "1.30.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-select": "5.10.2",
|
||||
"react-use": "17.6.0",
|
||||
"rxjs": "7.8.2",
|
||||
@@ -37,8 +37,8 @@
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/prismjs": "1.26.5",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"jest": "29.7.0",
|
||||
"react-select-event": "5.5.1",
|
||||
"ts-node": "10.9.2",
|
||||
|
||||
@@ -20,8 +20,8 @@ interface CodeEditorProps {
|
||||
export const LogsQLCodeEditor = (props: CodeEditorProps) => {
|
||||
const { query, datasource, onChange } = props;
|
||||
|
||||
const monacoRef = useRef<Monaco>();
|
||||
const disposalRef = useRef<monacoType.IDisposable>();
|
||||
const monacoRef = useRef<Monaco>(null);
|
||||
const disposalRef = useRef<monacoType.IDisposable>(undefined);
|
||||
|
||||
const onFocus = useCallback(async () => {
|
||||
disposalRef.current = await reRegisterCompletionProvider(
|
||||
|
||||
@@ -42,8 +42,8 @@ interface LogsCodeEditorProps {
|
||||
export const PPLQueryEditor = (props: LogsCodeEditorProps) => {
|
||||
const { query, datasource, onChange } = props;
|
||||
|
||||
const monacoRef = useRef<Monaco>();
|
||||
const disposalRef = useRef<monacoType.IDisposable>();
|
||||
const monacoRef = useRef<Monaco>(null);
|
||||
const disposalRef = useRef<monacoType.IDisposable>(undefined);
|
||||
|
||||
const onFocus = useCallback(async () => {
|
||||
disposalRef.current = await reRegisterCompletionProvider(
|
||||
|
||||
@@ -20,8 +20,8 @@ interface SQLCodeEditorProps {
|
||||
export const SQLQueryEditor = (props: SQLCodeEditorProps) => {
|
||||
const { query, datasource, onChange } = props;
|
||||
|
||||
const monacoRef = useRef<Monaco>();
|
||||
const disposalRef = useRef<monacoType.IDisposable>();
|
||||
const monacoRef = useRef<Monaco>(null);
|
||||
const disposalRef = useRef<monacoType.IDisposable>(undefined);
|
||||
|
||||
const onFocus = useCallback(async () => {
|
||||
disposalRef.current = await reRegisterCompletionProvider(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||
|
||||
@@ -347,7 +347,9 @@ describe('QueryEditor should render right editor', () => {
|
||||
it('should have an account selector when the feature is enabled', async () => {
|
||||
config.featureToggles.cloudWatchCrossAccountQuerying = true;
|
||||
props.datasource.resources.getAccounts = jest.fn().mockResolvedValue(['account123']);
|
||||
render(<QueryEditor {...props} query={validMetricQueryBuilderQuery} />);
|
||||
// TODO investigate why we need act
|
||||
// see https://github.com/testing-library/react-testing-library/issues/1375
|
||||
await act(async () => render(<QueryEditor {...props} query={validMetricQueryBuilderQuery} />));
|
||||
await screen.findByText('Metric Insights');
|
||||
expect(await screen.findByText('Account')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -46,11 +46,9 @@ describe('ElasticsearchQueryContext', () => {
|
||||
// the following applies to all hooks in ElasticsearchQueryContext as they all share the same code.
|
||||
describe('useQuery Hook', () => {
|
||||
it('Should throw when used outside of ElasticsearchQueryContext', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
renderHook(() => useQuery());
|
||||
}).toThrow();
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should return the current query object', () => {
|
||||
|
||||
@@ -33,11 +33,9 @@ describe('useStatelessReducer Hook', () => {
|
||||
|
||||
describe('useDispatch Hook', () => {
|
||||
it('Should throw when used outside of DispatchContext', () => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
renderHook(() => useDispatch());
|
||||
}).toThrow();
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should return a dispatch function', () => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"@grafana/sql": "12.4.0-pre",
|
||||
"@grafana/ui": "12.4.0-pre",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"rxjs": "7.8.2",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
@@ -24,7 +24,7 @@
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react": "19.2.7",
|
||||
"jest": "29.7.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.2",
|
||||
|
||||
@@ -94,7 +94,7 @@ const EDITOR_HEIGHT_OFFSET = 2;
|
||||
* Hook that returns function that will set up monaco autocomplete for the label selector
|
||||
*/
|
||||
function useAutocomplete(getLabelValues: (label: string) => Promise<string[]>, labels?: string[]) {
|
||||
const providerRef = useRef<CompletionProvider>();
|
||||
const providerRef = useRef<CompletionProvider>(null);
|
||||
if (providerRef.current === undefined) {
|
||||
providerRef.current = new CompletionProvider();
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"lodash": "4.17.21",
|
||||
"monaco-editor": "0.34.1",
|
||||
"prismjs": "1.30.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-use": "17.6.0",
|
||||
"rxjs": "7.8.2",
|
||||
"tslib": "2.8.1"
|
||||
@@ -29,8 +29,8 @@
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/prismjs": "1.26.5",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"css-loader": "7.1.2",
|
||||
"jest": "29.7.0",
|
||||
"style-loader": "4.0.0",
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"d3-random": "^3.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"micro-memoize": "^4.1.2",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-select": "5.10.2",
|
||||
"react-use": "17.6.0",
|
||||
"rxjs": "7.8.2",
|
||||
@@ -31,8 +31,8 @@
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/uuid": "10.0.0",
|
||||
"jest": "29.7.0",
|
||||
"ts-node": "10.9.2",
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"@reduxjs/toolkit": "2.10.1",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.30.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-use": "17.6.0",
|
||||
"redux": "5.0.1",
|
||||
"rxjs": "7.8.2",
|
||||
@@ -32,8 +32,8 @@
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash": "4.17.20",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "18.3.5",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/uuid": "10.0.0",
|
||||
"jest": "29.7.0",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user