Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GH#4] Addon Panel UI #5

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
*.log
dist/
node_modules/
22 changes: 22 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "jest"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/@typescript-eslint",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jest/recommended"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"react/prop-types": 0
}
}
5 changes: 0 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ jobs:
env:
CI: true

- name: Test
run: yarn test --ci --coverage --maxWorkers=2
env:
CI: true

- name: Build
run: yarn build
env:
Expand Down
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*.log
.DS_Store
node_modules
dist
*.log
dist/
node_modules/
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"tabWidth": 2,
"useTabs": false
}
54 changes: 33 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,34 @@ yarn add --dev storybook-addon-apollo-client
npm install -D storybook-addon-apollo-client
```

## As a decorator in a story
Add the addon to your configuration in `.storybook/main.js`
```
module.exports = {
...config,
addons: [
...your addons
"storybook-addon-apollo-client",
],
};
```


add the following to your `.storybook/preview.js`

```js
import { MockedProvider } from '@apollo/client/testing'; // Use for Apollo Version 3+
// import { MockedProvider } from "@apollo/react-testing"; // Use for Apollo Version < 3


export const parameters = {
apolloClient: {
Provider: MockedProvider,
// any props you want to pass to MockedProvider on every story
},
};
```

## Using with a story with a query

```jsx
import { withApolloClient } from 'storybook-addon-apollo-client';
Expand All @@ -25,7 +52,6 @@ import MyComponentThatHasAQuery, {

export default {
title: 'My Story',
decorators: [withApolloClient],
};

export const example = () => <MyComponentThatHasAQuery />;
Expand All @@ -41,27 +67,13 @@ example.story = {
};
```

## Usage in `preview.js`

```js
import { withApolloClient } from 'storybook-addon-apollo-client';
import { addDecorator } from '@storybook/react';
import { InMemoryCache } from 'apollo-cache-inmemory';

const cache = new InMemoryCache();

addDecorator(
withApolloClient({
cache,
...// take a look at all the options in https://www.apollographql.com/docs/react/development-testing/testing
// everything that is used in `storybook-addon-apollo-client` is a 1 to 1 mapping of MockedProvider
})
);
```
Read more about the options available for MockedProvider at https://www.apollographql.com/docs/react/development-testing/testing

if you setup `withApolloClient` in preview, it will not need to be added to the `decorators` key in each story, consider doing this if you have a lot of stories that depend on Apollo.
### Usage
In Storybook, click "Show Addons" and navigate to the "Apollo Client" tab.

Read more about the options available for MockedProvider at https://www.apollographql.com/docs/react/development-testing/testing
**Addon UI Preview**
![Addon UI Preview](https://raw.githubusercontent.com/lifeiscontent/storybook-addon-apollo-client/preview.png)

## Example App

Expand Down
60 changes: 33 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,58 @@
"url": "https://github.com/lifeiscontent/storybook-addon-apollo-client.git"
},
"devDependencies": {
"@apollo/client": "3.x",
"@types/jest": "25.1.4",
"graphql": "15.3.0",
"husky": "4.2.3",
"react": "16.x",
"react-dom": "16.13.1",
"tsdx": "0.13.0",
"tslib": "1.11.1",
"typescript": "3.8.3"
"@apollo/client": "3.2.9",
"@types/jest": "26.0.15",
"@types/webpack-env": "1.16.0",
"@typescript-eslint/eslint-plugin": "4.8.2",
"@typescript-eslint/parser": "4.8.2",
"eslint": "7.14.0",
"eslint-config-prettier": "6.15.0",
"eslint-plugin-jest": "24.1.3",
"eslint-plugin-react": "7.21.5",
"eslint-plugin-react-hooks": "4.2.0",
"graphql": "15.4.0",
"husky": "4.3.0",
"jest": "26.6.3",
"npm-run-all": "^4.1.5",
"prettier": "2.2.1",
"react": "17.0.1",
"rimraf": "^3.0.2",
"tslib": "2.0.3",
"typescript": "4.1.2"
},
"dependencies": {
"@storybook/addons": "6.x"
"@storybook/addons": "6.x",
"@storybook/api": "6.x",
"@storybook/components": "6.x"
},
"engines": {
"node": ">=10"
},
"files": [
"dist",
"src"
"register.js"
],
"husky": {
"hooks": {
"pre-commit": "tsdx lint"
"pre-commit": "npm run lint"
}
},
"license": "MIT",
"main": "dist/index.js",
"module": "dist/storybook-addon-apollo-client.esm.js",
"name": "storybook-addon-apollo-client",
"peerDependencies": {
"@apollo/client": "3.x",
"react": "16.x"
},
"prettier": {
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
"graphql": "*",
"react": "16.x || 17.x"
},
"scripts": {
"start": "tsdx watch",
"build": "tsdx build",
"test": "tsdx test",
"lint": "tsdx lint",
"prepare": "tsdx build"
"start": "yarn build --watch",
"clean": "rimraf dist",
"prebuild": "yarn clean",
"build": "tsc --build",
"test": "jest",
"lint": "eslint .",
"prepare": "yarn build"
},
"typings": "dist/index.d.ts",
"version": "3.0.0"
}
2 changes: 2 additions & 0 deletions preset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* eslint-env node */
module.exports = require('./dist/preset');
Binary file added preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* eslint-env node */
require('./dist/register');
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ADDON_ID = 'addon-apolloClient';
export const PARAM_KEY = 'apolloClient';
export const NAME = 'ApolloClient';
22 changes: 22 additions & 0 deletions src/decorators.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { StoryFn, StoryContext } from '@storybook/addons';
import { PARAM_KEY } from './constants';

export const withApolloClient = (
storyFn: StoryFn,
context: StoryContext
): JSX.Element => {
const { Provider, ...providerProps } = context.parameters[PARAM_KEY] || {};

if (!Provider) {
console.warn(
'storybook-addon-apollo-client: Provider is missing from params'
);

return storyFn() as JSX.Element;
}

return (
<Provider {...providerProps}>{storyFn(context) as JSX.Element}</Provider>
);
};
18 changes: 3 additions & 15 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
import React from 'react';
import { makeDecorator } from '@storybook/addons';
import { MockedProvider } from '@apollo/client/testing';

export const withApolloClient = makeDecorator({
name: 'ApolloClient',
parameterName: 'apolloClient',
wrapper(getStory, context, settings) {
return (
<MockedProvider {...settings.options} {...settings.parameters}>
{getStory(context)}
</MockedProvider>
);
},
});
if (module && module.hot && module.hot.decline) {
module.hot.decline();
}
110 changes: 110 additions & 0 deletions src/panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { Fragment, useState } from 'react';
import { useParameter } from '@storybook/api';
import { Form, Placeholder, TabsState } from '@storybook/components';
import { Kind, OperationDefinitionNode, print } from 'graphql';
import { PARAM_KEY } from './constants';
import { Parameters, MockedResponse } from './types';
import { SafeHighligher } from './safe-highligher';

const isObject = (value: unknown): value is Record<string, unknown> =>
typeof value === 'object' && value !== null;
const isOperationDefinitionNode = (
value: unknown
): value is OperationDefinitionNode =>
isObject(value) && value.kind === Kind.OPERATION_DEFINITION;

const getOperationName = (mockedResponse: MockedResponse): string => {
if (mockedResponse.request.operationName) {
return mockedResponse.request.operationName;
}

const operationDefinition = mockedResponse.request.query.definitions.find(
isOperationDefinitionNode
);

if (operationDefinition && operationDefinition.name) {
return print(operationDefinition.name);
}

return 'Unnamed';
};

export const ApolloClientPanel: React.FC = () => {
const [mockedResponse, setMockedResponse] = useState<MockedResponse | null>(
null
);
const { mocks = [] } = useParameter<Parameters>(PARAM_KEY) || {};

if (mocks.length === 0) {
return <Placeholder>No mocks for this story</Placeholder>;
}

return (
<Fragment>
<Form.Field label="Mocks">
<Form.Select
defaultValue="-1"
onChange={(event) => {
setMockedResponse(
event.currentTarget.value !== '-1'
? mocks[Number(event.currentTarget.value)]
: null
);
}}
size="auto"
>
<option value="-1">Select Mock</option>
{mocks.map((mock, index) => (
<option key={index} value={index}>
{index + 1}. {getOperationName(mock)}
</option>
))}
</Form.Select>
</Form.Field>
{mockedResponse && (
<TabsState initial="request">
<div key="request" id="request" title="Request">
<SafeHighligher
kind="query"
language="graphql"
type="request"
value={mockedResponse.request.query}
/>
</div>
<div key="variables" id="variables" title="Variables">
<SafeHighligher
kind="variables"
language="json"
type="request"
value={mockedResponse.request.variables}
/>
</div>
<div key="context" id="context" title="Context">
<SafeHighligher
kind="context"
language="json"
type="request"
value={mockedResponse.request.context}
/>
</div>
<div key="result" id="result" title="Result">
<SafeHighligher
kind="data"
language="json"
type="result"
value={mockedResponse.result}
/>
</div>
<div key="error" id="error" title="Error">
<SafeHighligher
kind="error"
language="json"
type="result"
value={mockedResponse.error}
/>
</div>
</TabsState>
)}
</Fragment>
);
};
3 changes: 3 additions & 0 deletions src/preset/addDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { withApolloClient } from '../decorators';

export const decorators = [withApolloClient];
7 changes: 7 additions & 0 deletions src/preset/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function config(entry: unknown[] = []): unknown[] {
return [...entry, require.resolve('./addDecorator')];
}

export function managerEntries(entry: unknown[] = []): unknown[] {
return [...entry, require.resolve('../register')];
}
Loading