From 49c7ffddcd56b99a25579f76d66fcbce505ffdde Mon Sep 17 00:00:00 2001 From: Phelipe Teles Date: Wed, 23 Feb 2022 22:49:51 -0300 Subject: [PATCH] Add demo mocking requests made with Fetch API --- src/components/FilmCard/FilmCard.js | 28 +++++++++++++ src/components/FilmCard/index.js | 1 + src/components/index.js | 1 + src/demos/fetch/App.js | 61 +++++++++++++++++++++++++++++ src/demos/fetch/App.stories.js | 58 +++++++++++++++++++++++++++ storybook/index.js | 6 ++- storybook/mswDecorator.js | 34 ++++++++++++++++ storybook/stories/index.js | 2 - 8 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/components/FilmCard/FilmCard.js create mode 100644 src/components/FilmCard/index.js create mode 100644 src/components/index.js create mode 100644 src/demos/fetch/App.js create mode 100644 src/demos/fetch/App.stories.js create mode 100644 storybook/mswDecorator.js delete mode 100644 storybook/stories/index.js diff --git a/src/components/FilmCard/FilmCard.js b/src/components/FilmCard/FilmCard.js new file mode 100644 index 0000000..e2f324a --- /dev/null +++ b/src/components/FilmCard/FilmCard.js @@ -0,0 +1,28 @@ +import React from 'react'; +import {View, Text, StyleSheet} from 'react-native'; + +export const FilmCard = ({film}) => { + return ( + + {film.title} + {film.opening_crawl} + + ); +}; + +const styles = StyleSheet.create({ + filmCard: { + borderRadius: 8, + borderWidth: 2, + borderColor: '#c7d2fe', + backgroundColor: '#e0e7ff', + paddingHorizontal: 16, + paddingVertical: 24, + marginBottom: 16, + }, + filmTitle: { + color: '#1e40af', + fontSize: 24, + marginBottom: 8, + }, +}); diff --git a/src/components/FilmCard/index.js b/src/components/FilmCard/index.js new file mode 100644 index 0000000..25eb61b --- /dev/null +++ b/src/components/FilmCard/index.js @@ -0,0 +1 @@ +export * from './FilmCard'; diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..25eb61b --- /dev/null +++ b/src/components/index.js @@ -0,0 +1 @@ +export * from './FilmCard'; diff --git a/src/demos/fetch/App.js b/src/demos/fetch/App.js new file mode 100644 index 0000000..41f296b --- /dev/null +++ b/src/demos/fetch/App.js @@ -0,0 +1,61 @@ +import React, {useState, useEffect} from 'react'; +import {ScrollView, Text, StyleSheet} from 'react-native'; + +import {FilmCard} from '../../components'; + +function useFetchFilms() { + const [status, setStatus] = useState('idle'); + const [data, setData] = useState([]); + + useEffect(() => { + setStatus('loading'); + + fetch('https://swapi.dev/api/films/') + .then(res => { + if (!res.ok) { + throw new Error(res.statusText); + } + return res; + }) + .then(res => res.json()) + .then(({results}) => { + setStatus('success'); + setData(results); + }) + .catch(() => { + setStatus('error'); + }); + }, []); + + return { + status, + data, + }; +} + +export const App = () => { + const {status, data: films} = useFetchFilms(); + + if (status === 'loading') { + return Fetching Star Wars data...; + } + + if (status === 'error') { + return Could not fetch Star Wars data; + } + + return ( + + {films.map(film => ( + + ))} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + gap: 30, + }, +}); diff --git a/src/demos/fetch/App.stories.js b/src/demos/fetch/App.stories.js new file mode 100644 index 0000000..2fba1da --- /dev/null +++ b/src/demos/fetch/App.stories.js @@ -0,0 +1,58 @@ +import React from 'react'; +import {rest} from 'msw'; +import {App} from './App'; +import {storiesOf} from '@storybook/react-native'; + +export default { + title: 'Demos/Fetch', + component: App, +}; + +export const DefaultBehavior = () => ; + +const MockTemplate = () => ; + +const films = [ + { + title: 'A New Hope', + episode_id: 4, + opening_crawl: + '(Mocked) Rebel spaceships have won their first victory against the evil Galactic Empire.', + }, + { + title: 'Empire Strikes Back', + episode_id: 5, + opening_crawl: + '(Mocked) Imperial troops are pursuing the Rebel forces across the galaxy.', + }, + { + title: 'Return of the Jedi', + episode_id: 6, + opening_crawl: + '(Mocked) Luke Skywalker has returned to his home planet of Tatooine to rescue Han Solo.', + }, +]; + +storiesOf('Demos/Fetch', module) + .add('MockedSuccess', MockTemplate, { + msw: { + handlers: [ + rest.get('https://swapi.dev/api/films/', (_, res, ctx) => { + return res( + ctx.json({ + results: films, + }), + ); + }), + ], + }, + }) + .add('MockedError', MockTemplate, { + msw: { + handlers: [ + rest.get('https://swapi.dev/api/films/', (_, res, ctx) => { + return res(ctx.delay(800), ctx.status(403)); + }), + ], + }, + }); diff --git a/storybook/index.js b/storybook/index.js index 89d1945..c576112 100644 --- a/storybook/index.js +++ b/storybook/index.js @@ -2,15 +2,19 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import {getStorybookUI, configure, addDecorator} from '@storybook/react-native'; import {withKnobs} from '@storybook/addon-knobs'; +import {initialize, withMsw} from './mswDecorator'; import './rn-addons'; // enables knobs for all stories addDecorator(withKnobs); +initialize(); +addDecorator(withMsw); + // import stories configure(() => { - require('./stories'); + require('../src/demos/fetch/App.stories'); }, module); // Refer to https://github.com/storybookjs/react-native/tree/master/app/react-native#getstorybookui-options diff --git a/storybook/mswDecorator.js b/storybook/mswDecorator.js new file mode 100644 index 0000000..bd54ff6 --- /dev/null +++ b/storybook/mswDecorator.js @@ -0,0 +1,34 @@ +import 'react-native-url-polyfill/auto'; +import {setupServer} from 'msw/native'; + +const server = setupServer(); + +export const initialize = () => { + // Do not warn or error out if a non-mocked request happens. + // If we don't use this, Storybook will be spammy about requests made to + // fetch the JS bundle etc. + server.listen({onUnhandledRequest: 'bypass'}); +}; + +export const withMsw = (storyFn, {parameters: {msw}}) => { + server.resetHandlers(); + + if (msw) { + if (Array.isArray(msw) && msw.length > 0) { + // Support an Array of request handlers (backwards compatibility). + server.use(...msw); + } else if ('handlers' in msw && msw.handlers) { + // Support an Array named request handlers handlers + // or an Object of named request handlers with named arrays of handlers + const handlers = Object.values(msw.handlers) + .filter(Boolean) + .reduce((handlers, handlersList) => handlers.concat(handlersList), []); + + if (handlers.length > 0) { + server.use(...handlers); + } + } + } + + return storyFn(); +}; diff --git a/storybook/stories/index.js b/storybook/stories/index.js deleted file mode 100644 index 5c9079a..0000000 --- a/storybook/stories/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './Button/Button.stories'; -import './Welcome/Welcome.stories';