mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-22 09:36:43 +03:00
* fix: broken rtk-query-monitor demo not working #1126 Description: downgrades framer-motion in order to remove the runtime error "_framerMotion.motion.custom is not a function" See: https://stackoverflow.com/questions/66703410/next-js-framermotion-motion-custom-is-not-a-function * feat(rtk-query): add Data tab #1126 * fix: bump min popup width to 700px #1126 Description: improve UI of rtk-query right side tabs * fix: bump min popup window width again to 760px #1126 * chore: add changeset * feat(rtk-query): improve a11y of rtk-query-monitor tab panel #1126 * chore(rtk-query): add few integration tests to rtk-query-monitor #1126 * Fix merge * Deduplicate msw Co-authored-by: Nathan Bierema <nbierema@gmail.com>
This commit is contained in:
parent
6cf1865f55
commit
24f60a7aa7
5
.changeset/lemon-balloons-sniff.md
Normal file
5
.changeset/lemon-balloons-sniff.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@redux-devtools/rtk-query-monitor': minor
|
||||
---
|
||||
|
||||
feat(rtk-query): add Data tab to rtk-query-monitor #1126 #1129
|
5
.changeset/popular-dodos-laugh.md
Normal file
5
.changeset/popular-dodos-laugh.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'rtk-query-demo': patch
|
||||
---
|
||||
|
||||
fix: rtk-query-monitor demo not working #1126 #1129
|
5
.changeset/ten-files-return.md
Normal file
5
.changeset/ten-files-return.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'remotedev-redux-devtools-extension': patch
|
||||
---
|
||||
|
||||
bump min popup window width to 760px #1126 #1129
|
|
@ -7,7 +7,7 @@ style.
|
|||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 350px;
|
||||
min-width: 760px;
|
||||
min-height: 400px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -17,6 +17,7 @@ style.
|
|||
color: #fff;
|
||||
}
|
||||
#root {
|
||||
min-width: 760px;
|
||||
height: 100%;
|
||||
}
|
||||
#root > div {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
demo
|
||||
lib
|
||||
dist
|
||||
|
|
|
@ -9,5 +9,13 @@ module.exports = {
|
|||
project: ['./tsconfig.json'],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||
extends: '../../eslintrc.ts.react.jest.base.json',
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.test.json'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"@redux-devtools/rtk-query-monitor": "^3.0.0",
|
||||
"@reduxjs/toolkit": "^1.8.2",
|
||||
"framer-motion": "^6.3.15",
|
||||
"msw": "^0.42.3",
|
||||
"msw": "^0.43.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.4.0",
|
||||
|
|
12
packages/redux-devtools-rtk-query-monitor/jest.config.js
Normal file
12
packages/redux-devtools-rtk-query-monitor/jest.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jsdom',
|
||||
moduleNameMapper: {
|
||||
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.test.json',
|
||||
},
|
||||
},
|
||||
};
|
|
@ -37,10 +37,11 @@
|
|||
"build:esm": "babel src --config-file ./babel.config.esm.json --extensions \".ts,.tsx\" --out-dir lib/esm",
|
||||
"build:types": "tsc --emitDeclarationOnly",
|
||||
"clean": "rimraf lib",
|
||||
"test": "jest",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"type-check": "tsc --noEmit",
|
||||
"prepack": "pnpm run clean && pnpm run build",
|
||||
"prepublish": "pnpm run type-check && pnpm run lint"
|
||||
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
|
@ -68,6 +69,9 @@
|
|||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@redux-devtools/core": "^3.13.1",
|
||||
"@reduxjs/toolkit": "^1.8.2",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/hex-rgba": "^1.0.1",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/react": "^18.0.14",
|
||||
|
@ -75,11 +79,16 @@
|
|||
"@typescript-eslint/parser": "^5.29.0",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-jest": "^26.5.3",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jest": "^27.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.2",
|
||||
"redux": "^4.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^27.1.5",
|
||||
"typescript": "~4.7.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -128,6 +128,7 @@ export class QueryForm extends React.PureComponent<
|
|||
{({ styling, base16Theme }) => {
|
||||
return (
|
||||
<form
|
||||
id="rtk-query-monitor-query-selection-form"
|
||||
action="#"
|
||||
onSubmit={this.handleSubmit}
|
||||
{...styling('queryForm')}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { createSelector, Selector } from '@reduxjs/toolkit';
|
||||
import React, { ReactNode, PureComponent } from 'react';
|
||||
import { Action, AnyAction } from 'redux';
|
||||
import { QueryPreviewTabs } from '../types';
|
||||
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
|
||||
import { emptyRecord, identity } from '../utils/object';
|
||||
import { TreeView } from './TreeView';
|
||||
import { TreeView, TreeViewProps } from './TreeView';
|
||||
|
||||
export interface QueryPreviewActionsProps {
|
||||
isWideLayout: boolean;
|
||||
|
@ -11,6 +13,12 @@ export interface QueryPreviewActionsProps {
|
|||
|
||||
const keySep = ' - ';
|
||||
|
||||
const rootProps: TreeViewProps['rootProps'] = {
|
||||
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.actions),
|
||||
id: renderTabPanelId(QueryPreviewTabs.actions),
|
||||
role: 'tabpanel',
|
||||
};
|
||||
|
||||
export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps> {
|
||||
selectFormattedActions: Selector<
|
||||
AnyAction[],
|
||||
|
@ -74,6 +82,7 @@ export class QueryPreviewActions extends PureComponent<QueryPreviewActionsProps>
|
|||
|
||||
return (
|
||||
<TreeView
|
||||
rootProps={rootProps}
|
||||
data={this.selectFormattedActions(actionsOfQuery)}
|
||||
isWideLayout={isWideLayout}
|
||||
shouldExpandNode={this.shouldExpandNode}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React, { ReactNode, PureComponent } from 'react';
|
||||
import { ApiStats, RtkQueryApiState } from '../types';
|
||||
import { ApiStats, QueryPreviewTabs, RtkQueryApiState } from '../types';
|
||||
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
||||
import { TreeView } from './TreeView';
|
||||
import { TreeView, TreeViewProps } from './TreeView';
|
||||
import { renderTabPanelId, renderTabPanelButtonId } from '../utils/a11y';
|
||||
|
||||
export interface QueryPreviewApiProps {
|
||||
apiStats: ApiStats | null;
|
||||
|
@ -9,6 +10,12 @@ export interface QueryPreviewApiProps {
|
|||
isWideLayout: boolean;
|
||||
}
|
||||
|
||||
const rootProps: TreeViewProps['rootProps'] = {
|
||||
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.apiConfig),
|
||||
id: renderTabPanelId(QueryPreviewTabs.apiConfig),
|
||||
role: 'tabpanel',
|
||||
};
|
||||
|
||||
export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
|
||||
shouldExpandApiStateNode = (
|
||||
keyPath: (string | number)[],
|
||||
|
@ -33,7 +40,7 @@ export class QueryPreviewApi extends PureComponent<QueryPreviewApiProps> {
|
|||
return (
|
||||
<StyleUtilsContext.Consumer>
|
||||
{({ styling }) => (
|
||||
<article {...styling('tabContent')}>
|
||||
<article {...rootProps} {...styling('tabContent')}>
|
||||
<h2>{apiState.config.reducerPath}</h2>
|
||||
<TreeView
|
||||
before={<h3>State</h3>}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import React, { ReactNode, PureComponent } from 'react';
|
||||
import { QueryPreviewTabs, RtkResourceInfo } from '../types';
|
||||
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
|
||||
import { TreeView, TreeViewProps } from './TreeView';
|
||||
|
||||
export interface QueryPreviewDataProps {
|
||||
data: RtkResourceInfo['state']['data'];
|
||||
isWideLayout: boolean;
|
||||
}
|
||||
|
||||
const rootProps: TreeViewProps['rootProps'] = {
|
||||
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.data),
|
||||
id: renderTabPanelId(QueryPreviewTabs.data),
|
||||
role: 'tabpanel',
|
||||
};
|
||||
|
||||
export class QueryPreviewData extends PureComponent<QueryPreviewDataProps> {
|
||||
shouldExpandNode = (
|
||||
keyPath: (string | number)[],
|
||||
value: unknown,
|
||||
layer: number
|
||||
): boolean => {
|
||||
return layer <= 1;
|
||||
};
|
||||
|
||||
render(): ReactNode {
|
||||
const { data, isWideLayout } = this.props;
|
||||
|
||||
return (
|
||||
<TreeView
|
||||
rootProps={rootProps}
|
||||
data={data}
|
||||
isWideLayout={isWideLayout}
|
||||
shouldExpandNode={this.shouldExpandNode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { StyleUtilsContext } from '../styles/createStylingFromTheme';
|
||||
import { QueryPreviewTabs, TabOption } from '../types';
|
||||
import { renderTabPanelButtonId } from '../utils/a11y';
|
||||
import { emptyArray } from '../utils/object';
|
||||
|
||||
export interface QueryPreviewHeaderProps {
|
||||
|
@ -28,7 +29,11 @@ export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps>
|
|||
<div {...styling('previewHeader')}>
|
||||
<div {...styling('tabSelector')}>
|
||||
{tabs.map((tab) => (
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
id={renderTabPanelButtonId(tab.value)}
|
||||
aria-selected={tab.value === selectedTab}
|
||||
role={'tab'}
|
||||
onClick={() => this.handleTabClick(tab)}
|
||||
key={tab.value}
|
||||
{...styling(
|
||||
|
@ -42,7 +47,7 @@ export class QueryPreviewHeader extends React.Component<QueryPreviewHeaderProps>
|
|||
<span>
|
||||
{renderTabLabel ? renderTabLabel(tab) : tab.label}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { createSelector, Selector } from '@reduxjs/toolkit';
|
||||
import { QueryStatus } from '@reduxjs/toolkit/dist/query';
|
||||
import React, { ReactNode, PureComponent } from 'react';
|
||||
import { RtkResourceInfo, RTKStatusFlags } from '../types';
|
||||
import { QueryPreviewTabs, RtkResourceInfo, RTKStatusFlags } from '../types';
|
||||
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
|
||||
import { formatMs } from '../utils/formatters';
|
||||
import { identity } from '../utils/object';
|
||||
import { getQueryStatusFlags } from '../utils/rtk-query';
|
||||
import { TreeView } from './TreeView';
|
||||
import { TreeView, TreeViewProps } from './TreeView';
|
||||
|
||||
type QueryTimings = {
|
||||
startedAt: string;
|
||||
|
@ -23,6 +24,12 @@ type FormattedQuery = {
|
|||
| { query: RtkResourceInfo['state'] }
|
||||
);
|
||||
|
||||
const rootProps: TreeViewProps['rootProps'] = {
|
||||
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.queryinfo),
|
||||
id: renderTabPanelId(QueryPreviewTabs.queryinfo),
|
||||
role: 'tabpanel',
|
||||
};
|
||||
|
||||
export interface QueryPreviewInfoProps {
|
||||
resInfo: RtkResourceInfo;
|
||||
isWideLayout: boolean;
|
||||
|
@ -97,6 +104,7 @@ export class QueryPreviewInfo extends PureComponent<QueryPreviewInfoProps> {
|
|||
|
||||
return (
|
||||
<TreeView
|
||||
rootProps={rootProps}
|
||||
data={formattedQuery}
|
||||
isWideLayout={isWideLayout}
|
||||
shouldExpandNode={this.shouldExpandNode}
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import React, { ReactNode, PureComponent } from 'react';
|
||||
import { RtkQueryApiState } from '../types';
|
||||
import { TreeView } from './TreeView';
|
||||
import { QueryPreviewTabs, RtkQueryApiState } from '../types';
|
||||
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
|
||||
import { TreeView, TreeViewProps } from './TreeView';
|
||||
|
||||
const rootProps: TreeViewProps['rootProps'] = {
|
||||
'aria-labelledby': renderTabPanelButtonId(
|
||||
QueryPreviewTabs.querySubscriptions
|
||||
),
|
||||
id: renderTabPanelId(QueryPreviewTabs.querySubscriptions),
|
||||
role: 'tabpanel',
|
||||
};
|
||||
|
||||
export interface QueryPreviewSubscriptionsProps {
|
||||
subscriptions: RtkQueryApiState['subscriptions'][keyof RtkQueryApiState['subscriptions']];
|
||||
|
@ -12,7 +21,11 @@ export class QueryPreviewSubscriptions extends PureComponent<QueryPreviewSubscri
|
|||
const { subscriptions } = this.props;
|
||||
|
||||
return (
|
||||
<TreeView data={subscriptions} isWideLayout={this.props.isWideLayout} />
|
||||
<TreeView
|
||||
rootProps={rootProps}
|
||||
data={subscriptions}
|
||||
isWideLayout={this.props.isWideLayout}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
import React, { ReactNode, PureComponent } from 'react';
|
||||
import { RtkQueryTag } from '../types';
|
||||
import { TreeView } from './TreeView';
|
||||
import { QueryPreviewTabs, RtkQueryTag } from '../types';
|
||||
import { renderTabPanelButtonId, renderTabPanelId } from '../utils/a11y';
|
||||
import { TreeView, TreeViewProps } from './TreeView';
|
||||
|
||||
interface QueryPreviewTagsState {
|
||||
data: { tags: RtkQueryTag[] };
|
||||
}
|
||||
|
||||
const rootProps: TreeViewProps['rootProps'] = {
|
||||
'aria-labelledby': renderTabPanelButtonId(QueryPreviewTabs.queryTags),
|
||||
id: renderTabPanelId(QueryPreviewTabs.queryTags),
|
||||
role: 'tabpanel',
|
||||
};
|
||||
|
||||
export interface QueryPreviewTagsProps {
|
||||
tags: RtkQueryTag[];
|
||||
isWideLayout: boolean;
|
||||
|
@ -26,6 +33,8 @@ export class QueryPreviewTags extends PureComponent<
|
|||
render(): ReactNode {
|
||||
const { isWideLayout, tags } = this.props;
|
||||
|
||||
return <TreeView data={tags} isWideLayout={isWideLayout} />;
|
||||
return (
|
||||
<TreeView rootProps={rootProps} data={tags} isWideLayout={isWideLayout} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ export interface TreeViewProps
|
|||
before?: ReactNode;
|
||||
after?: ReactNode;
|
||||
children?: ReactNode;
|
||||
rootProps?: Partial<
|
||||
Omit<React.HTMLAttributes<HTMLDivElement>, 'className' | 'style'>
|
||||
>;
|
||||
}
|
||||
|
||||
export class TreeView extends React.PureComponent<TreeViewProps> {
|
||||
|
@ -80,13 +83,14 @@ export class TreeView extends React.PureComponent<TreeViewProps> {
|
|||
keyPath,
|
||||
shouldExpandNode,
|
||||
hideRoot,
|
||||
rootProps,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<StyleUtilsContext.Consumer>
|
||||
{({ styling, invertTheme, base16Theme }) => {
|
||||
return (
|
||||
<div {...styling('treeWrapper')}>
|
||||
<div {...rootProps} {...styling('treeWrapper')}>
|
||||
{before}
|
||||
<JSONTree
|
||||
keyPath={keyPath}
|
||||
|
|
|
@ -33,6 +33,10 @@ import {
|
|||
QueryPreviewActionsProps,
|
||||
} from '../components/QueryPreviewActions';
|
||||
import { isTabVisible } from '../utils/tabs';
|
||||
import {
|
||||
QueryPreviewData,
|
||||
QueryPreviewDataProps,
|
||||
} from '../components/QueryPreviewData';
|
||||
|
||||
export interface QueryPreviewProps<S = unknown> {
|
||||
readonly selectedTab: QueryPreviewTabs;
|
||||
|
@ -66,6 +70,14 @@ const MappedQueryPreviewInfo = mapProps<
|
|||
QueryPreviewInfoProps
|
||||
>(({ resInfo, isWideLayout }) => ({ resInfo, isWideLayout }))(QueryPreviewInfo);
|
||||
|
||||
const MappedQueryPreviewData = mapProps<
|
||||
QueryPreviewTabProps,
|
||||
QueryPreviewDataProps
|
||||
>(({ resInfo, isWideLayout }) => ({
|
||||
data: resInfo?.state?.data,
|
||||
isWideLayout,
|
||||
}))(QueryPreviewData);
|
||||
|
||||
const MappedQuerySubscriptipns = mapProps<
|
||||
QueryPreviewTabProps,
|
||||
QueryPreviewSubscriptionsProps
|
||||
|
@ -93,6 +105,16 @@ const MappedQueryPreviewActions = mapProps<
|
|||
const tabs: ReadonlyArray<
|
||||
TabOption<QueryPreviewTabs, QueryPreviewTabProps, RtkResourceInfo['type']>
|
||||
> = [
|
||||
{
|
||||
label: 'Data',
|
||||
value: QueryPreviewTabs.data,
|
||||
component: MappedQueryPreviewData,
|
||||
visible: {
|
||||
query: true,
|
||||
mutation: true,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Query',
|
||||
value: QueryPreviewTabs.queryinfo,
|
||||
|
|
|
@ -258,6 +258,7 @@ export function createInspectorSelectors<S>(): InspectorSelectors<S> {
|
|||
],
|
||||
(subscriptions, actions, tags) => {
|
||||
return {
|
||||
[QueryPreviewTabs.data]: 0,
|
||||
[QueryPreviewTabs.queryTags]: tags.length,
|
||||
[QueryPreviewTabs.querySubscriptions]: Object.keys(subscriptions ?? {})
|
||||
.length,
|
||||
|
|
|
@ -220,6 +220,8 @@ const getSheetFromColorMap = (map: ColorMap) => {
|
|||
padding: '0 8px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
boxShadow: 'none',
|
||||
outline: 'none',
|
||||
color: map.TEXT_COLOR,
|
||||
'border-style': 'solid',
|
||||
'border-width': '1px',
|
||||
|
|
|
@ -8,6 +8,7 @@ import type { QueryComparators } from './utils/comparators';
|
|||
import type { QueryFilters } from './utils/filters';
|
||||
|
||||
export enum QueryPreviewTabs {
|
||||
data,
|
||||
queryinfo,
|
||||
apiConfig,
|
||||
querySubscriptions,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { QueryPreviewTabs } from '../types';
|
||||
|
||||
export function renderTabPanelId(value: QueryPreviewTabs): string {
|
||||
return `rtk-query-monitor-tab-panel-${value}`;
|
||||
}
|
||||
|
||||
export function renderTabPanelButtonId(value: QueryPreviewTabs): string {
|
||||
return renderTabPanelId(value) + '-button';
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export default {};
|
|
@ -0,0 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { createDevTools } from '@redux-devtools/core';
|
||||
import { RtkQueryMonitor } from '../src';
|
||||
|
||||
const MonitorAsAny = RtkQueryMonitor as any;
|
||||
|
||||
export const ReduxDevTools = createDevTools(<MonitorAsAny />);
|
|
@ -0,0 +1,119 @@
|
|||
import * as React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { ReduxDevTools } from './devtools.mocks';
|
||||
import { BaseQueryJestMockFunction, setupStore } from './rtk-query.mocks';
|
||||
|
||||
function Providers({
|
||||
store,
|
||||
children,
|
||||
}: {
|
||||
store: ReturnType<typeof setupStore>['store'];
|
||||
children?: React.ComponentProps<typeof Provider>['children'];
|
||||
}) {
|
||||
const AnyProvider = Provider as any;
|
||||
|
||||
return (
|
||||
<div id="app-root">
|
||||
<AnyProvider store={store}>
|
||||
{children}
|
||||
<ReduxDevTools />
|
||||
</AnyProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe('rtk-query-monitor standalone integration', () => {
|
||||
// Hushes symbol.observable warning
|
||||
// @see https://github.com/reduxjs/redux-devtools/issues/1002
|
||||
jest.spyOn(console, 'warn');
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
(console.warn as jest.Mock<void>).mockImplementation(() => {});
|
||||
|
||||
const dataPanelDomId = '#rtk-query-monitor-tab-panel-0';
|
||||
|
||||
const childrenTextContent = 'Renders children';
|
||||
const fetchBaseQueryMock: BaseQueryJestMockFunction<Record<string, unknown>> =
|
||||
jest.fn((...fetchArgs) =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
name: fetchArgs[0],
|
||||
},
|
||||
})
|
||||
);
|
||||
const { store, pokemonApi } = setupStore(fetchBaseQueryMock, ReduxDevTools);
|
||||
|
||||
beforeAll(() => {
|
||||
// let's populate api
|
||||
(store.dispatch as any)(
|
||||
pokemonApi.endpoints.getPokemonByName.initiate('bulbasaur')
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fetchBaseQueryMock.mockClear();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
(console.warn as jest.Mock<void>).mockRestore();
|
||||
});
|
||||
|
||||
it('renders on a standalone app without crashing', () => {
|
||||
const { container } = render(
|
||||
<Providers store={store}>
|
||||
<div data-testid="children">{childrenTextContent}</div>
|
||||
</Providers>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('children').textContent).toBe(
|
||||
childrenTextContent
|
||||
);
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('tab', { name: /actions/i })
|
||||
?.textContent?.toLowerCase()
|
||||
.trim()
|
||||
).toBe('actions');
|
||||
expect(
|
||||
screen
|
||||
.getByRole('tab', { name: /data/i })
|
||||
?.textContent?.toLowerCase()
|
||||
.trim()
|
||||
).toBe('data');
|
||||
expect(
|
||||
screen
|
||||
.getByRole('tab', { name: /api/i })
|
||||
?.textContent?.toLowerCase()
|
||||
.trim()
|
||||
).toBe('api');
|
||||
expect(
|
||||
container.querySelector(
|
||||
'form[id="rtk-query-monitor-query-selection-form"]'
|
||||
)
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('displays query data tab content', async () => {
|
||||
// `Promise.resolve()` hushes `@typescript-eslint/await-thenable`
|
||||
await Promise.resolve(pokemonApi.util.getRunningOperationPromises());
|
||||
|
||||
const { container } = render(
|
||||
<Providers store={store}>
|
||||
<div data-testid="children">{childrenTextContent}</div>
|
||||
</Providers>
|
||||
);
|
||||
|
||||
// We need to select the query & the correct tab
|
||||
fireEvent.click(screen.getByRole('tab', { name: /data/i }));
|
||||
fireEvent.click(screen.getByText(/bulbasaur/i));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(container.querySelector(dataPanelDomId)).not.toBeNull()
|
||||
);
|
||||
|
||||
expect(container.querySelector(dataPanelDomId)?.textContent).toMatch(
|
||||
/name\W+pokemon\/bulbasaur/i
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,84 @@
|
|||
import {
|
||||
combineReducers,
|
||||
configureStore,
|
||||
EnhancedStore,
|
||||
Middleware,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import type { BaseQueryFn, FetchArgs } from '@reduxjs/toolkit/query';
|
||||
import type { ReduxDevTools } from './devtools.mocks';
|
||||
|
||||
export type MockBaseQuery<
|
||||
Result,
|
||||
Args = string | FetchArgs,
|
||||
Meta = { status?: number }
|
||||
> = BaseQueryFn<Args, Result, unknown, Meta>;
|
||||
|
||||
export type BaseQueryJestMockFunction<Result> = jest.Mock<
|
||||
ReturnType<MockBaseQuery<Result>>,
|
||||
Parameters<MockBaseQuery<Result>>
|
||||
>;
|
||||
|
||||
export function createMockBaseQuery<Result>(
|
||||
jestMockFn: BaseQueryJestMockFunction<Result>
|
||||
): MockBaseQuery<Result> {
|
||||
return async function mockBaseQuery(param, api, extra) {
|
||||
try {
|
||||
const output = await jestMockFn(param, api, extra);
|
||||
|
||||
return output;
|
||||
} catch (error) {
|
||||
return {
|
||||
error,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createPokemonApi(
|
||||
jestMockFn: BaseQueryJestMockFunction<Record<string, any>>
|
||||
) {
|
||||
return createApi({
|
||||
reducerPath: 'pokemonApi',
|
||||
keepUnusedDataFor: 9999,
|
||||
baseQuery: createMockBaseQuery(jestMockFn),
|
||||
tagTypes: ['pokemon'],
|
||||
endpoints: (builder) => ({
|
||||
getPokemonByName: builder.query<Record<string, any>, string>({
|
||||
query: (name: string) => `pokemon/${name}`,
|
||||
providesTags: (result, error, name: string) => [
|
||||
{ type: 'pokemon' },
|
||||
{ type: 'pokemon', id: name },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export function setupStore(
|
||||
jestMockFn: BaseQueryJestMockFunction<Record<string, any>>,
|
||||
devTools: typeof ReduxDevTools
|
||||
) {
|
||||
const pokemonApi = createPokemonApi(jestMockFn);
|
||||
|
||||
const reducer = combineReducers({
|
||||
[pokemonApi.reducerPath]: pokemonApi.reducer,
|
||||
});
|
||||
|
||||
const store: EnhancedStore<ReturnType<typeof reducer>> = configureStore({
|
||||
reducer,
|
||||
devTools: false,
|
||||
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat([pokemonApi.middleware]) as Middleware[],
|
||||
enhancers: [devTools.instrument()],
|
||||
});
|
||||
|
||||
return {
|
||||
jestMockFn,
|
||||
devTools,
|
||||
store,
|
||||
reducer,
|
||||
pokemonApi,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../tsconfig.react.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["jest"]
|
||||
},
|
||||
"include": ["src", "test"],
|
||||
"exclude": ["dist"]
|
||||
}
|
|
@ -1788,7 +1788,10 @@ importers:
|
|||
'@redux-devtools/core': ^3.13.1
|
||||
'@redux-devtools/ui': ^1.3.0
|
||||
'@reduxjs/toolkit': ^1.8.2
|
||||
'@testing-library/jest-dom': ^5.16.4
|
||||
'@testing-library/react': ^13.3.0
|
||||
'@types/hex-rgba': ^1.0.1
|
||||
'@types/jest': ^27.5.2
|
||||
'@types/lodash': ^4.14.182
|
||||
'@types/lodash.debounce': ^4.0.7
|
||||
'@types/prop-types': ^15.7.5
|
||||
|
@ -1798,20 +1801,25 @@ importers:
|
|||
'@typescript-eslint/parser': ^5.29.0
|
||||
eslint: ^8.18.0
|
||||
eslint-config-prettier: ^8.5.0
|
||||
eslint-plugin-jest: ^26.5.3
|
||||
eslint-plugin-react: ^7.30.1
|
||||
eslint-plugin-react-hooks: ^4.6.0
|
||||
hex-rgba: ^1.0.2
|
||||
immutable: ^4.1.0
|
||||
jest: ^27.5.1
|
||||
jss: ^10.9.0
|
||||
jss-preset-default: ^10.9.0
|
||||
lodash.debounce: ^4.0.8
|
||||
prop-types: ^15.8.1
|
||||
react: ^18.2.0
|
||||
react-base16-styling: ^0.9.1
|
||||
react-dom: ^18.2.0
|
||||
react-json-tree: ^0.17.0
|
||||
react-redux: ^8.0.2
|
||||
redux: ^4.2.0
|
||||
redux-devtools-themes: ^1.0.0
|
||||
rimraf: ^3.0.2
|
||||
ts-jest: ^27.1.5
|
||||
typescript: ~4.7.4
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
|
@ -1837,19 +1845,27 @@ importers:
|
|||
'@babel/preset-react': 7.17.12_@babel+core@7.18.5
|
||||
'@babel/preset-typescript': 7.17.12_@babel+core@7.18.5
|
||||
'@redux-devtools/core': link:../redux-devtools
|
||||
'@reduxjs/toolkit': 1.8.2_react@18.2.0
|
||||
'@reduxjs/toolkit': 1.8.2_kkwg4cbsojnjnupd3btipussee
|
||||
'@testing-library/jest-dom': 5.16.4
|
||||
'@testing-library/react': 13.3.0_biqbaboplfbrettd7655fr4n2y
|
||||
'@types/hex-rgba': 1.0.1
|
||||
'@types/jest': 27.5.2
|
||||
'@types/lodash.debounce': 4.0.7
|
||||
'@types/react': 18.0.14
|
||||
'@typescript-eslint/eslint-plugin': 5.29.0_qqmbkyiaixvppdwswpytuf2hgm
|
||||
'@typescript-eslint/parser': 5.29.0_b5e7v2qnwxfo6hmiq56u52mz3e
|
||||
eslint: 8.18.0
|
||||
eslint-config-prettier: 8.5.0_eslint@8.18.0
|
||||
eslint-plugin-jest: 26.5.3_hkaktba3mic4t6mktu5mmyzyhi
|
||||
eslint-plugin-react: 7.30.1_eslint@8.18.0
|
||||
eslint-plugin-react-hooks: 4.6.0_eslint@8.18.0
|
||||
jest: 27.5.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-redux: 8.0.2_42iqcqzqjdyq32nxzztmetzyhu
|
||||
redux: 4.2.0
|
||||
rimraf: 3.0.2
|
||||
ts-jest: 27.1.5_7giygfzd3xe4pjz4ngu3nwlqbm
|
||||
typescript: 4.7.4
|
||||
|
||||
packages/redux-devtools-rtk-query-monitor/demo:
|
||||
|
@ -1886,7 +1902,7 @@ importers:
|
|||
fork-ts-checker-webpack-plugin: ^7.2.11
|
||||
framer-motion: ^6.3.15
|
||||
html-webpack-plugin: ^5.5.0
|
||||
msw: ^0.42.3
|
||||
msw: ^0.43.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
react-icons: ^4.4.0
|
||||
|
@ -1911,7 +1927,7 @@ importers:
|
|||
'@redux-devtools/rtk-query-monitor': link:..
|
||||
'@reduxjs/toolkit': 1.8.2_kkwg4cbsojnjnupd3btipussee
|
||||
framer-motion: 6.3.15_biqbaboplfbrettd7655fr4n2y
|
||||
msw: 0.42.3_typescript@4.7.4
|
||||
msw: 0.43.0_typescript@4.7.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-icons: 4.4.0_react@18.2.0
|
||||
|
@ -5999,29 +6015,10 @@ packages:
|
|||
dependencies:
|
||||
immer: 9.0.15
|
||||
react: 18.2.0
|
||||
react-redux: 8.0.2_fzknh3pugl53jp7xsetxkg5ani
|
||||
react-redux: 8.0.2_42iqcqzqjdyq32nxzztmetzyhu
|
||||
redux: 4.2.0
|
||||
redux-thunk: 2.4.1_redux@4.2.0
|
||||
reselect: 4.1.6
|
||||
dev: false
|
||||
|
||||
/@reduxjs/toolkit/1.8.2_react@18.2.0:
|
||||
resolution: {integrity: sha512-CtPw5TkN1pHRigMFCOS/0qg3b/yfPV5qGCsltVnIz7bx4PKTJlGHYfIxm97qskLknMzuGfjExaYdXJ77QTL0vg==}
|
||||
peerDependencies:
|
||||
react: ^16.9.0 || ^17.0.0 || ^18 || 18
|
||||
react-redux: ^7.2.1 || ^8.0.0-beta
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-redux:
|
||||
optional: true
|
||||
dependencies:
|
||||
immer: 9.0.15
|
||||
react: 18.2.0
|
||||
redux: 4.2.0
|
||||
redux-thunk: 2.4.1_redux@4.2.0
|
||||
reselect: 4.1.6
|
||||
dev: true
|
||||
|
||||
/@restart/hooks/0.4.7_react@18.2.0:
|
||||
resolution: {integrity: sha512-ZbjlEHcG+FQtpDPHd7i4FzNNvJf2enAwZfJbpM8CW7BhmOAbsHpZe3tsHwfQUrBuyrxWqPYp2x5UMnilWcY22A==}
|
||||
|
@ -16403,43 +16400,6 @@ packages:
|
|||
/ms/2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
/msw/0.42.3_typescript@4.7.4:
|
||||
resolution: {integrity: sha512-zrKBIGCDsNUCZLd3DLSeUtRruZ0riwJgORg9/bSDw3D0PTI8XUGAK3nC0LJA9g0rChGuKaWK/SwObA8wpFrz4g==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
typescript: '>= 4.2.x <= 4.7.x'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@mswjs/cookies': 0.2.1
|
||||
'@mswjs/interceptors': 0.16.6
|
||||
'@open-draft/until': 1.0.3
|
||||
'@types/cookie': 0.4.1
|
||||
'@types/js-levenshtein': 1.1.1
|
||||
chalk: 4.1.1
|
||||
chokidar: 3.5.3
|
||||
cookie: 0.4.2
|
||||
graphql: 16.5.0
|
||||
headers-polyfill: 3.0.7
|
||||
inquirer: 8.2.4
|
||||
is-node-process: 1.0.1
|
||||
js-levenshtein: 1.1.6
|
||||
node-fetch: 2.6.7
|
||||
outvariant: 1.3.0
|
||||
path-to-regexp: 6.2.1
|
||||
statuses: 2.0.1
|
||||
strict-event-emitter: 0.2.4
|
||||
type-fest: 1.4.0
|
||||
typescript: 4.7.4
|
||||
yargs: 17.5.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/msw/0.43.0_typescript@4.7.4:
|
||||
resolution: {integrity: sha512-XJylZP0qW3D5WUGWh9FFefJEl3MGG4y1I+/8a833d0eedm6B+GaPm6wPVZNcnlS2YVTagvEgShVJ7ZtY66tTRQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
@ -16476,7 +16436,6 @@ packages:
|
|||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/multicast-dns/7.2.5:
|
||||
resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
|
||||
|
@ -18192,7 +18151,6 @@ packages:
|
|||
react-is: 18.2.0
|
||||
redux: 4.2.0
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
dev: true
|
||||
|
||||
/react-redux/8.0.2_fzknh3pugl53jp7xsetxkg5ani:
|
||||
resolution: {integrity: sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==}
|
||||
|
|
Loading…
Reference in New Issue
Block a user