Merge pull request #3988 from overleaf/ae-use-fetch-mock

Add useFetchMock hook for use in Storybook

GitOrigin-RevId: 4eb1c5edf2f94dc6ad51358e109e29c9f62d2058
This commit is contained in:
Alf Eaton 2021-05-11 15:25:22 +01:00 committed by Copybot
parent 29264061d8
commit f8cb1638d1
8 changed files with 132 additions and 100 deletions

View file

@ -1,23 +1,24 @@
import React from 'react'
import BinaryFile from '../js/features/binary-file/components/binary-file'
import fetchMock from 'fetch-mock'
import useFetchMock from './hooks/use-fetch-mock'
window.project_id = 'proj123'
fetchMock.restore()
fetchMock.head('express:/project/:project_id/file/:file_id', {
status: 201,
headers: { 'Content-Length': 10000 },
})
fetchMock.get('express:/project/:project_id/file/:file_id', 'Text file content')
fetchMock.post('express:/project/:project_id/linked_file/:file_id/refresh', {
status: 204,
})
fetchMock.post('express:/project/:project_id/references/indexAll', {
status: 204,
})
const setupFetchMock = fetchMock => {
fetchMock
.head('express:/project/:project_id/file/:file_id', {
status: 201,
headers: { 'Content-Length': 10000 },
})
.get('express:/project/:project_id/file/:file_id', 'Text file content')
.post('express:/project/:project_id/linked_file/:file_id/refresh', {
status: 204,
})
.post('express:/project/:project_id/references/indexAll', {
status: 204,
})
}
window.project_id = '1234'
@ -173,10 +174,14 @@ export default {
storeReferencesKeys: () => {},
},
decorators: [
BinaryFile => (
Story => {
useFetchMock(setupFetchMock)
return <Story />
},
Story => (
<>
<style>{'html, body { height: 100%; }'}</style>
<BinaryFile />
<Story />
</>
),
],

View file

@ -1,11 +1,11 @@
import React from 'react'
import fetchMock from 'fetch-mock'
import { v4 as uuid } from 'uuid'
import { ContextRoot } from '../js/shared/context/root-context'
import ChatPane from '../js/features/chat/components/chat-pane'
import { stubMathJax } from '../../test/frontend/features/chat/components/stubs'
import { setupContext } from './fixtures/context'
import useFetchMock from './hooks/use-fetch-mock'
const ONE_MINUTE = 60 * 1000
@ -43,31 +43,30 @@ function generateMessages(count) {
stubMathJax()
setupContext()
export const Conversation = args => <ChatPane {...args} />
Conversation.parameters = {
setupMocks: () => {
fetchMock.restore()
fetchMock.get(/messages/, generateMessages(35))
fetchMock.post(/messages/, {})
},
export const Conversation = args => {
useFetchMock(fetchMock => {
fetchMock.get(/messages/, generateMessages(35)).post(/messages/, {})
})
return <ChatPane {...args} />
}
export const NoMessages = args => <ChatPane {...args} />
NoMessages.parameters = {
setupMocks: () => {
fetchMock.restore()
export const NoMessages = args => {
useFetchMock(fetchMock => {
fetchMock.get(/messages/, [])
},
})
return <ChatPane {...args} />
}
export const Loading = args => <ChatPane {...args} />
Loading.parameters = {
setupMocks: () => {
fetchMock.restore()
export const Loading = args => {
useFetchMock(fetchMock => {
fetchMock.get(/messages/, generateMessages(6), {
delay: 1000 * 10,
})
},
})
return <ChatPane {...args} />
}
export default {
@ -80,10 +79,6 @@ export default {
resetUnreadMessages: () => {},
},
decorators: [
(Story, { parameters: { setupMocks } }) => {
if (setupMocks) setupMocks()
return <Story />
},
Story => (
<>
<style>{'html, body, .chat { height: 100%; width: 100%; }'}</style>

View file

@ -1,27 +1,29 @@
import React from 'react'
import fetchMock from 'fetch-mock'
import PropTypes from 'prop-types'
import CloneProjectModal from '../js/features/clone-project-modal/components/clone-project-modal'
import useFetchMock from './hooks/use-fetch-mock'
export const Interactive = ({
mockResponse = 200,
mockResponseDelay = 500,
...args
}) => {
fetchMock.restore().post(
'express:/project/:projectId/clone',
() => {
switch (mockResponse) {
case 400:
return { status: 400, body: 'The project name is not valid' }
useFetchMock(fetchMock => {
fetchMock.post(
'express:/project/:projectId/clone',
() => {
switch (mockResponse) {
case 400:
return { status: 400, body: 'The project name is not valid' }
default:
return mockResponse
}
},
{ delay: mockResponseDelay }
)
default:
return mockResponse
}
},
{ delay: mockResponseDelay }
)
})
return <CloneProjectModal {...args} />
}

View file

@ -1,11 +1,11 @@
import React from 'react'
import fetchMock from 'fetch-mock'
import MockedSocket from 'socket.io-mock'
import { rootFolderBase } from './fixtures/file-tree-base'
import { rootFolderLimit } from './fixtures/file-tree-limit'
import FileTreeRoot from '../js/features/file-tree/components/file-tree-root'
import FileTreeError from '../js/features/file-tree/components/file-tree-error'
import useFetchMock from './hooks/use-fetch-mock'
const MOCK_DELAY = 2000
@ -13,9 +13,8 @@ window._ide = {
socket: new MockedSocket(),
}
function defaultSetupMocks() {
function defaultSetupMocks(fetchMock) {
fetchMock
.restore()
.post(
/\/project\/\w+\/(file|doc|folder)\/\w+\/rename/,
(path, req) => {
@ -78,8 +77,11 @@ function defaultSetupMocks() {
})
}
export const FullTree = args => <FileTreeRoot {...args} />
FullTree.parameters = { setupMocks: defaultSetupMocks }
export const FullTree = args => {
useFetchMock(defaultSetupMocks)
return <FileTreeRoot {...args} />
}
export const ReadOnly = args => <FileTreeRoot {...args} />
ReadOnly.args = { hasWritePermissions: false }
@ -87,28 +89,34 @@ ReadOnly.args = { hasWritePermissions: false }
export const Disconnected = args => <FileTreeRoot {...args} />
Disconnected.args = { isConnected: false }
export const NetworkErrors = args => <FileTreeRoot {...args} />
NetworkErrors.parameters = {
setupMocks: () => {
export const NetworkErrors = args => {
useFetchMock(fetchMock => {
fetchMock
.restore()
.post(/\/project\/\w+\/folder/, 500, {
delay: MOCK_DELAY,
})
.post(/\/project\/\w+\/(file|doc|folder)\/\w+\/rename/, 500, {
delay: MOCK_DELAY,
})
.post(/\/project\/\w+\/(file|doc|folder)\/\w+\/move/, 500, {
delay: MOCK_DELAY,
})
.delete(/\/project\/\w+\/(file|doc|folder)\/\w+/, 500, {
delay: MOCK_DELAY,
})
},
})
return <FileTreeRoot {...args} />
}
export const FallbackError = args => <FileTreeError {...args} />
export const FilesLimit = args => <FileTreeRoot {...args} />
export const FilesLimit = args => {
useFetchMock(defaultSetupMocks)
return <FileTreeRoot {...args} />
}
FilesLimit.args = { rootFolder: rootFolderLimit }
FilesLimit.parameters = { setupMocks: defaultSetupMocks }
export default {
title: 'File Tree',
@ -136,10 +144,6 @@ export default {
onSelect: { action: 'onSelect' },
},
decorators: [
(Story, { parameters: { setupMocks } }) => {
if (setupMocks) setupMocks()
return <Story />
},
Story => (
<>
<style>{'html, body, .file-tree { height: 100%; width: 100%; }'}</style>

View file

@ -2,6 +2,7 @@ import React from 'react'
import { ContextRoot } from '../js/shared/context/root-context'
import { setupContext } from './fixtures/context'
import importOverleafModules from '../macros/import-overleaf-module.macro'
import useFetchMock from './hooks/use-fetch-mock'
const [
{
@ -21,7 +22,11 @@ CollaboratorModal.args = {
type: 'collaborator',
}
export const TeaserModal = args => <GitBridgeModal {...args} />
export const TeaserModal = args => {
useFetchMock(fetchMock => fetchMock.post('express:/event/:key', 202))
return <GitBridgeModal {...args} />
}
TeaserModal.args = {
type: 'teaser',
}
@ -34,7 +39,6 @@ export default {
},
argTypes: {
handleHide: { action: 'handleHide' },
startFreeTrial: { action: 'startFreeTrial' },
},
decorators: [
Story => (

View file

@ -0,0 +1,21 @@
import { useEffect } from 'react'
import fetchMock from 'fetch-mock'
/**
* Run callback to mock fetch routes, call restore() when unmounted
*/
export default function useFetchMock(callback) {
useEffect(() => {
return () => {
fetchMock.restore()
}
}, [])
// Running fetchMock.restore() here as well,
// in case there was an error before the component was unmounted.
fetchMock.restore()
// The callback has to be run here, rather than in useEffect,
// so it's run before the component is rendered.
callback(fetchMock)
}

View file

@ -1,6 +1,6 @@
import React, { useEffect } from 'react'
import fetchMock from 'fetch-mock'
import ShareProjectModal from '../js/features/share-project-modal/components/share-project-modal'
import useFetchMock from './hooks/use-fetch-mock'
const contacts = [
// user with edited name
@ -44,11 +44,10 @@ const contacts = [
},
]
const setupFetchMock = () => {
const setupFetchMock = fetchMock => {
const delay = 1000
fetchMock
.restore()
// list contacts
.get('express:/user/contacts', { contacts }, { delay })
// change privacy setting
@ -86,7 +85,7 @@ const ideWithProject = project => {
}
export const LinkSharingOff = args => {
setupFetchMock()
useFetchMock(setupFetchMock)
const project = {
...args.project,
@ -97,7 +96,7 @@ export const LinkSharingOff = args => {
}
export const LinkSharingOn = args => {
setupFetchMock()
useFetchMock(setupFetchMock)
const project = {
...args.project,
@ -108,7 +107,7 @@ export const LinkSharingOn = args => {
}
export const LinkSharingLoading = args => {
setupFetchMock()
useFetchMock(setupFetchMock)
const project = {
...args.project,
@ -167,7 +166,7 @@ export const RestrictedTokenMember = args => {
}
export const LegacyLinkSharingReadAndWrite = args => {
setupFetchMock()
useFetchMock(setupFetchMock)
const project = {
...args.project,
@ -178,7 +177,7 @@ export const LegacyLinkSharingReadAndWrite = args => {
}
export const LegacyLinkSharingReadOnly = args => {
setupFetchMock()
useFetchMock(setupFetchMock)
const project = {
...args.project,
@ -189,7 +188,7 @@ export const LegacyLinkSharingReadOnly = args => {
}
export const LimitedCollaborators = args => {
setupFetchMock()
useFetchMock(setupFetchMock)
const project = {
...args.project,

View file

@ -1,37 +1,39 @@
import React from 'react'
import fetchMock from 'fetch-mock'
import PropTypes from 'prop-types'
import WordCountModal from '../js/features/word-count-modal/components/word-count-modal'
import useFetchMock from './hooks/use-fetch-mock'
export const Interactive = ({
mockResponse = 200,
mockResponseDelay = 500,
...args
}) => {
fetchMock.restore().get(
'express:/project/:projectId/wordcount',
() => {
switch (mockResponse) {
case 400:
return { status: 400, body: 'The project id is not valid' }
useFetchMock(fetchMock => {
fetchMock.get(
'express:/project/:projectId/wordcount',
() => {
switch (mockResponse) {
case 400:
return { status: 400, body: 'The project id is not valid' }
case 200:
return {
texcount: {
headers: 4,
mathDisplay: 40,
mathInline: 400,
textWords: 4000,
},
}
case 200:
return {
texcount: {
headers: 4,
mathDisplay: 40,
mathInline: 400,
textWords: 4000,
},
}
default:
return mockResponse
}
},
{ delay: mockResponseDelay }
)
default:
return mockResponse
}
},
{ delay: mockResponseDelay }
)
})
return <WordCountModal {...args} />
}