Fix frontend unit tests (#4045)

# What this PR does

- bring back and fix frontend unit tests


## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Dominik Broj 2024-03-12 13:21:53 +01:00 committed by GitHub
parent a601ad506c
commit a14716551c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 146 additions and 257 deletions

View file

@ -69,9 +69,9 @@ jobs:
if: steps.cache-frontend-dependencies.outputs.cache-hit != 'true'
working-directory: grafana-plugin
run: yarn install --frozen-lockfile --prefer-offline --network-timeout 500000
- name: Build frontend (will run linter and tests)
- name: Build, lint and test frontend
working-directory: grafana-plugin
run: yarn build
run: yarn lint && yarn test && yarn build
test-technical-documentation:
name: "Test technical documentation"

View file

@ -18,3 +18,7 @@ grafana-plugin.yml
/playwright-report*
/playwright/.cache/
/e2e-tests/storageState.json
# Jest test report
jest_html_reporters.html
jest-html-reporters*

View file

@ -26,6 +26,18 @@ module.exports = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
reporters: process.env.HTML_REPORT_ENABLED
? [
'default',
[
'jest-html-reporters',
{
openReport: process.env.NODE_ENV !== 'production',
},
],
]
: ['default'],
testTimeout: 10000,
testPathIgnorePatterns: ['/node_modules/', '/e2e-tests/'],
transform: {

View file

@ -8,6 +8,25 @@ import 'plugin/dayjs';
import { TextEncoder, TextDecoder } from 'util';
jest.mock('@grafana/runtime', () => ({
__esModule: true,
config: {
featureToggles: {
topNav: false,
},
bootData: {
user: {
timezone: 'UTC',
},
},
},
getBackendSrv: jest.fn().mockImplementation(() => ({
get: jest.fn(),
post: jest.fn(),
})),
getLocationSrv: jest.fn(),
}));
Object.assign(global, { TextDecoder, TextEncoder });
// https://stackoverflow.com/a/66055672

View file

@ -12,6 +12,7 @@
"labels:link": "yarn --cwd ../../gops-labels/frontend link && yarn link \"@grafana/labels\" && yarn --cwd ../../gops-labels/frontend watch",
"labels:unlink": "yarn --cwd ../../gops-labels/frontend unlink",
"test": "jest --verbose",
"test:report": "HTML_REPORT_ENABLED=true jest",
"test:silent": "jest --silent",
"test:e2e": "yarn playwright test --grep-invert @expensive",
"test:e2e-expensive": "yarn playwright test --grep @expensive",
@ -90,6 +91,7 @@
"identity-obj-proxy": "3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"jest-html-reporters": "^3.1.7",
"knip": "^5.0.3",
"lint-staged": "^10.2.11",
"lodash-es": "^4.17.21",

View file

@ -5,7 +5,7 @@ import { render, fireEvent, screen } from '@testing-library/react';
import { Collapse, CollapseProps } from 'components/Collapse/Collapse';
describe.skip('Collapse', () => {
describe('Collapse', () => {
function getProps(isOpen: boolean, onClick: jest.Mock = jest.fn()) {
return {
label: 'Toggle',

View file

@ -3,6 +3,8 @@ import React from 'react';
import { render } from '@testing-library/react';
import { Provider } from 'mobx-react';
import { UserHelper } from 'models/user/user.helpers';
import { AddRespondersPopup } from './AddRespondersPopup';
describe('AddRespondersPopup', () => {
@ -30,11 +32,10 @@ describe('AddRespondersPopup', () => {
getSearchResult: jest.fn().mockReturnValue(teams),
updateItems: jest.fn(),
},
userStore: {
search: jest.fn().mockReturnValue({ results: [] }),
},
};
UserHelper.search = jest.fn().mockReturnValue({ results: [] });
const component = render(
<Provider store={mockStoreValue}>
<AddRespondersPopup

View file

@ -14,23 +14,6 @@ jest.mock('plugin/GrafanaPluginRootPage.helpers', () => ({
isTopNavbar: () => false,
}));
jest.mock('@grafana/runtime', () => ({
__esModule: true,
config: {
featureToggles: {
topNav: false,
},
},
getBackendSrv: jest.fn().mockImplementation(() => ({
get: jest.fn(),
post: jest.fn(),
})),
getLocationSrv: jest.fn(),
}));
jest.mock('utils/authorization/authorization', () => ({
...jest.requireActual('utils/authorization/authorization'),
isUserActionAllowed: jest.fn().mockReturnValue(true),
@ -44,7 +27,7 @@ jest.mock('state/rootStore', () => ({
const mockRootStore = (rest?: any, connected = false, cloud_connected = true) => {
rootStore.userStore = {
loadUser: loadUserMock,
fetchItemById: loadUserMock,
currentUser: {
messaging_backends: {
MOBILE_APP: { connected },
@ -76,13 +59,11 @@ describe('MobileAppConnection', () => {
beforeEach(() => {
loadUserMock.mockClear();
(rootStore as any).mockClear();
mockRootStore();
UserHelper.fetchBackendConfirmationCode = jest.fn().mockResolvedValueOnce('dfd');
});
test('it shows a loading message if it is currently fetching the QR code', async () => {
mockRootStore({
fetchBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'),
});
const component = render(<MobileAppConnection userPk={USER_PK} />);
expect(component.container).toMatchSnapshot();
@ -93,10 +74,7 @@ describe('MobileAppConnection', () => {
});
test('it shows an error message if there was an error fetching the QR code', async () => {
mockRootStore({
fetchBackendConfirmationCode: jest.fn().mockRejectedValueOnce('dfd'),
});
UserHelper.fetchBackendConfirmationCode = jest.fn().mockRejectedValueOnce('dfd');
const component = render(<MobileAppConnection userPk={USER_PK} />);
await screen.findByText(/.*error fetching your QR code.*/);
@ -109,10 +87,6 @@ describe('MobileAppConnection', () => {
});
test("it shows a QR code if the app isn't already connected", async () => {
mockRootStore({
fetchBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'),
});
const component = render(<MobileAppConnection userPk={USER_PK} />);
expect(component.container).toMatchSnapshot();
@ -125,7 +99,6 @@ describe('MobileAppConnection', () => {
test('if we disconnect the app, it disconnects and fetches a new QR code', async () => {
mockRootStore(
{
fetchBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'),
unlinkBackend: jest.fn().mockResolvedValueOnce('asdfadsfafds'),
},
true
@ -153,7 +126,6 @@ describe('MobileAppConnection', () => {
test('it shows a loading message if it is currently disconnecting', async () => {
mockRootStore(
{
fetchBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'),
unlinkBackend: jest.fn().mockResolvedValueOnce(new Promise((resolve) => setTimeout(resolve, 500))),
},
true
@ -184,7 +156,6 @@ describe('MobileAppConnection', () => {
test('it shows an error message if there was an error disconnecting the mobile app', async () => {
mockRootStore(
{
fetchBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'),
unlinkBackend: jest.fn().mockRejectedValueOnce('asdfadsfafds'),
},
true
@ -213,7 +184,6 @@ describe('MobileAppConnection', () => {
test('it polls loadUser on first render if not connected', async () => {
mockRootStore(
{
fetchBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dfd'),
unlinkBackend: jest.fn().mockRejectedValueOnce('asdfadsfafds'),
},
false
@ -232,7 +202,6 @@ describe('MobileAppConnection', () => {
test('it polls loadUser after disconnect', async () => {
mockRootStore(
{
fetchBackendConfirmationCode: jest.fn().mockResolvedValueOnce('dff'),
unlinkBackend: jest.fn().mockRejectedValueOnce('asdff'),
},
true

View file

@ -30,7 +30,6 @@ enum License {
CLOUD = 'some-other-license',
}
const SELF_HOSTED_INSTALL_PLUGIN_ERROR_MESSAGE = 'ohhh nooo an error msg from self hosted install plugin';
const CHECK_IF_PLUGIN_IS_CONNECTED_ERROR_MESSAGE = 'ohhh nooo a plugin connection error';
const UPDATE_PLUGIN_STATUS_ERROR_MESSAGE = 'ohhh noooo a sync issue';
const PLUGIN_CONFIGURATION_FORM_DATA_ID = 'plugin-configuration-form';
@ -197,39 +196,6 @@ describe('PluginConfigPage', () => {
expect(component.container).toMatchSnapshot();
});
test("If onCallApiUrl is not set in the plugin's meta jsonData, and ONCALL_API_URL is passed in process.env, it calls selfHostedInstallPlugin", async () => {
// mocks
const processEnvOnCallApiUrl = 'onCallApiUrlFromProcessEnv';
process.env.ONCALL_API_URL = processEnvOnCallApiUrl;
PluginState.selfHostedInstallPlugin = jest.fn();
mockCheckTokenAndIfPluginIsConnected();
// test setup
render(<PluginConfigPage {...generateComponentProps()} />);
// assertions
expect(PluginState.selfHostedInstallPlugin).toHaveBeenCalledTimes(1);
expect(PluginState.selfHostedInstallPlugin).toHaveBeenCalledWith(processEnvOnCallApiUrl, true);
});
test("If onCallApiUrl is not set in the plugin's meta jsonData, and ONCALL_API_URL is passed in process.env, and there is an error calling selfHostedInstallPlugin, it sets an error message", async () => {
// mocks
const processEnvOnCallApiUrl = 'onCallApiUrlFromProcessEnv';
process.env.ONCALL_API_URL = processEnvOnCallApiUrl;
PluginState.selfHostedInstallPlugin = jest.fn().mockResolvedValueOnce(SELF_HOSTED_INSTALL_PLUGIN_ERROR_MESSAGE);
// test setup
const component = render(<PluginConfigPage {...generateComponentProps()} />);
await screen.findByTestId(STATUS_MESSAGE_BLOCK_DATA_ID);
// assertions
expect(PluginState.selfHostedInstallPlugin).toHaveBeenCalledTimes(1);
expect(PluginState.selfHostedInstallPlugin).toHaveBeenCalledWith(processEnvOnCallApiUrl, true);
expect(component.container).toMatchSnapshot();
});
test('If onCallApiUrl is set, and updatePluginStatus returns an error, it sets an error message', async () => {
// mocks
const processEnvOnCallApiUrl = 'onCallApiUrlFromProcessEnv';

View file

@ -1,60 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonData, and ONCALL_API_URL is passed in process.env, and there is an error calling selfHostedInstallPlugin, it sets an error message 1`] = `
<div>
<legend
class="css-1w9pvsj"
>
Configure Grafana OnCall
</legend>
<p>
This page will help you configure the OnCall plugin 👋
</p>
<pre
data-testid="status-message-block"
>
<span
class="root text text--undefined text--medium"
>
ohhh nooo an error msg from self hosted install plugin
</span>
</pre>
<div
class="css-ffyaiw-horizontal-group"
style="width: 100%; height: 100%;"
>
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<button
class="css-td06pi-button"
type="button"
>
<span
class="css-1riaxdn"
>
Retry Sync
</span>
</button>
</div>
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<button
class="css-ttl745-button"
type="button"
>
<span
class="css-1riaxdn"
>
Remove current configuration
</span>
</button>
</div>
</div>
</div>
`;
exports[`PluginConfigPage If onCallApiUrl is not set in the plugin's meta jsonData, or in process.env, updatePluginStatus is not called, and the configuration form is shown 1`] = `
<div>
<legend
@ -229,38 +174,40 @@ exports[`PluginConfigPage It doesn't make any network calls if the plugin config
Connected to OnCall (v1.2.3, OpenSource)
</span>
</pre>
<div
class="css-ffyaiw-horizontal-group"
style="width: 100%; height: 100%;"
>
<div>
<div
class="css-18qv8yz-layoutChildrenWrapper"
class="css-ffyaiw-horizontal-group"
style="width: 100%; height: 100%;"
>
<a
class="css-td06pi-button"
href="/a/grafana-oncall-app/"
tabindex="0"
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-1riaxdn"
<a
class="css-td06pi-button"
href="/a/grafana-oncall-app/"
tabindex="0"
>
Open Grafana OnCall
</span>
</a>
</div>
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<button
class="css-ttl745-button"
type="button"
<span
class="css-1riaxdn"
>
Open Grafana OnCall
</span>
</a>
</div>
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-1riaxdn"
<button
class="css-ttl745-button"
type="button"
>
Remove current configuration
</span>
</button>
<span
class="css-1riaxdn"
>
Remove current configuration
</span>
</button>
</div>
</div>
</div>
</div>
@ -282,38 +229,40 @@ exports[`PluginConfigPage OnCallApiUrl is set, and checkTokenAndIfPluginIsConnec
Connected to OnCall (v1.2.3, OpenSource)
</span>
</pre>
<div
class="css-ffyaiw-horizontal-group"
style="width: 100%; height: 100%;"
>
<div>
<div
class="css-18qv8yz-layoutChildrenWrapper"
class="css-ffyaiw-horizontal-group"
style="width: 100%; height: 100%;"
>
<a
class="css-td06pi-button"
href="/a/grafana-oncall-app/"
tabindex="0"
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-1riaxdn"
<a
class="css-td06pi-button"
href="/a/grafana-oncall-app/"
tabindex="0"
>
Open Grafana OnCall
</span>
</a>
</div>
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<button
class="css-ttl745-button"
type="button"
<span
class="css-1riaxdn"
>
Open Grafana OnCall
</span>
</a>
</div>
<div
class="css-18qv8yz-layoutChildrenWrapper"
>
<span
class="css-1riaxdn"
<button
class="css-ttl745-button"
type="button"
>
Remove current configuration
</span>
</button>
<span
class="css-1riaxdn"
>
Remove current configuration
</span>
</button>
</div>
</div>
</div>
</div>

View file

@ -1,54 +0,0 @@
import { makeRequest as makeRequestOriginal } from 'network/network';
import { RootStore } from 'state/rootStore';
import { UserStore } from './user';
import { UserHelper } from './user.helpers';
const makeRequest = makeRequestOriginal as jest.Mock<ReturnType<typeof makeRequestOriginal>>;
jest.mock('network/network');
afterEach(() => {
jest.resetAllMocks();
});
describe('UserStore.fetchBackendConfirmationCode', () => {
const userPk = '5';
const backend = 'dfkjfdjkfdkjfdaaa';
const mockedQrCode = 'dfkjfdkjfdkjfdjk';
test('it makes the proper API call and returns the response', async () => {
makeRequest.mockResolvedValueOnce(mockedQrCode);
expect(await UserHelper.fetchBackendConfirmationCode(userPk, backend)).toEqual(mockedQrCode);
expect(makeRequest).toHaveBeenCalledTimes(1);
expect(makeRequest).toHaveBeenCalledWith(`/users/${userPk}/get_backend_verification_code?backend=${backend}`, {
method: 'GET',
});
});
});
describe('UserStore.unlinkBackend', () => {
const rootStore = new RootStore();
const userStore = new UserStore(rootStore);
const userPk = '5';
const backend = 'dfkjfdjkfdkjfdaaa';
test('it makes the proper API call and returns the response', async () => {
makeRequest.mockResolvedValueOnce('hello');
Object.defineProperty(userStore, 'loadCurrentUser', { value: jest.fn() });
await userStore.unlinkBackend(userPk, backend);
expect(makeRequest).toHaveBeenCalledTimes(1);
expect(makeRequest).toHaveBeenCalledWith(`/users/${userPk}/unlink_backend/?backend=${backend}`, {
method: 'POST',
});
expect(userStore.loadCurrentUser).toHaveBeenCalledTimes(1);
expect(userStore.loadCurrentUser).toHaveBeenCalledWith();
});
});

View file

@ -63,12 +63,10 @@ describe('customFetch', () => {
describe('if there is otel', () => {
const spanEndMock = jest.fn();
const setStatusMock = jest.fn();
const setAttributeMock = jest.fn();
const spanStartMock = jest.fn(() => ({
setAttribute: setAttributeMock,
end: spanEndMock,
setStatus: setStatusMock,
}));
const otel = {
trace: {
@ -93,11 +91,9 @@ describe('customFetch', () => {
expect(spanStartMock).toHaveBeenCalledTimes(1);
});
it(`adds 'X-Idempotency-Key' header`, async () => {
it(`passes request config`, async () => {
await customFetch(URL, REQUEST_CONFIG);
expect(fetchMock).toHaveBeenCalledWith(expect.any(String), {
headers: { ...REQUEST_CONFIG.headers, 'X-Idempotency-Key': expect.any(String) },
});
expect(fetchMock).toHaveBeenCalledWith(expect.any(String), REQUEST_CONFIG);
});
describe('if response is successful', () => {
@ -116,7 +112,6 @@ describe('customFetch', () => {
await expect(customFetch(URL, REQUEST_CONFIG)).rejects.toEqual(ERROR_MOCK);
expect(FaroHelper.faro.api.pushEvent).toHaveBeenCalledWith('Request failed', { url: URL });
expect(FaroHelper.faro.api.pushError).toHaveBeenCalledWith(ERROR_MOCK);
expect(setStatusMock).toHaveBeenCalledTimes(1);
expect(spanEndMock).toHaveBeenCalledTimes(1);
});
});

View file

@ -16,6 +16,10 @@ jest.mock('grafana/app/core/core', () => ({
},
},
}));
jest.mock('network/network', () => ({
__esModule: true,
makeRequest: () => ({ pk: '1' }),
}));
const onCallApiUrl = 'http://oncall-dev-engine:8080';
@ -166,7 +170,6 @@ describe('rootBaseStore', () => {
{ is_installed: true, token_ok: false },
])('signup is allowed, user is an admin, plugin installation is triggered', async (scenario) => {
const rootBaseStore = new RootBaseStore();
const mockedLoadCurrentUser = jest.fn();
contextSrv.user.orgRole = OrgRole.Admin;
contextSrv.licensedAccessControlEnabled = jest.fn().mockResolvedValueOnce(false);
@ -181,8 +184,6 @@ describe('rootBaseStore', () => {
});
isUserActionAllowed.mockReturnValueOnce(true);
PluginState.installPlugin = jest.fn().mockResolvedValueOnce(null);
Object.defineProperty(rootBaseStore.userStore, 'loadCurrentUser', { value: mockedLoadCurrentUser });
// test
await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl));
@ -209,7 +210,6 @@ describe('rootBaseStore', () => {
},
])('signup is allowed, licensedAccessControlEnabled, various roles and permissions', async (scenario) => {
const rootBaseStore = new RootBaseStore();
const mockedLoadCurrentUser = jest.fn();
contextSrv.user.orgRole = scenario.role;
contextSrv.licensedAccessControlEnabled = jest.fn().mockReturnValue(true);
@ -224,7 +224,6 @@ describe('rootBaseStore', () => {
});
isUserActionAllowed.mockReturnValueOnce(true);
PluginState.installPlugin = jest.fn().mockResolvedValueOnce(null);
Object.defineProperty(rootBaseStore.userStore, 'loadCurrentUser', { value: mockedLoadCurrentUser });
// test
await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl));
@ -290,7 +289,6 @@ describe('rootBaseStore', () => {
test('when the plugin is installed, a data sync is triggered', async () => {
const rootBaseStore = new RootBaseStore();
const mockedLoadCurrentUser = jest.fn();
PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({
is_user_anonymous: false,
@ -300,24 +298,17 @@ describe('rootBaseStore', () => {
version: 'asdfasdf',
license: 'asdfasdf',
});
Object.defineProperty(rootBaseStore.userStore, 'loadCurrentUser', { value: mockedLoadCurrentUser });
// test
await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl));
// assertions
expect(PluginState.updatePluginStatus).toHaveBeenCalledTimes(1);
expect(PluginState.updatePluginStatus).toHaveBeenCalledWith(onCallApiUrl);
expect(mockedLoadCurrentUser).toHaveBeenCalledTimes(1);
expect(mockedLoadCurrentUser).toHaveBeenCalledWith();
expect(rootBaseStore.initializationError).toBeNull();
});
test('when the plugin is installed, and the data sync returns an error, it is properly handled', async () => {
const rootBaseStore = new RootBaseStore();
const mockedLoadCurrentUser = jest.fn();
const updatePluginStatusError = 'asdasdfasdfasf';
PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce({
@ -329,7 +320,6 @@ describe('rootBaseStore', () => {
license: 'asdfasdf',
});
PluginState.updatePluginStatus = jest.fn().mockResolvedValueOnce(updatePluginStatusError);
Object.defineProperty(rootBaseStore.userStore, 'loadCurrentUser', { value: mockedLoadCurrentUser });
// test
await rootBaseStore.setupPlugin(generatePluginData(onCallApiUrl));

View file

@ -14,6 +14,8 @@ jest.mock('@grafana/faro-web-sdk', () => ({
pushLog: jest.fn(),
},
}),
LogLevel: jest.requireActual('@grafana/faro-web-sdk').LogLevel,
InternalLoggerLevel: jest.requireActual('@grafana/faro-web-sdk').InternalLoggerLevel,
getWebInstrumentations: () => [],
}));

View file

@ -5746,6 +5746,11 @@ define-data-property@^1.1.2, define-data-property@^1.1.4:
es-errors "^1.3.0"
gopd "^1.0.1"
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
define-properties@^1.1.3, define-properties@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1"
@ -8175,6 +8180,11 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
is-data-descriptor "^1.0.0"
kind-of "^6.0.2"
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@ -8464,6 +8474,13 @@ is-windows@^1.0.1, is-windows@^1.0.2:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@ -8751,6 +8768,14 @@ jest-haste-map@^29.7.0:
optionalDependencies:
fsevents "^2.3.2"
jest-html-reporters@^3.1.7:
version "3.1.7"
resolved "https://registry.yarnpkg.com/jest-html-reporters/-/jest-html-reporters-3.1.7.tgz#d8cb6f5d15fd518e601841f90165f37765e7ff34"
integrity sha512-GTmjqK6muQ0S0Mnksf9QkL9X9z2FGIpNSxC52E0PHDzjPQ1XDu2+XTI3B3FS43ZiUzD1f354/5FfwbNIBzT7ew==
dependencies:
fs-extra "^10.0.0"
open "^8.0.3"
jest-leak-detector@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728"
@ -10429,6 +10454,15 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
open@^8.0.3:
version "8.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
dependencies:
define-lazy-prop "^2.0.0"
is-docker "^2.1.1"
is-wsl "^2.2.0"
openapi-fetch@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.8.1.tgz#a2bda1f72a8311e92cc789d1c8fec7b2d8ca28b6"