-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from microcipcip/feature/useFetch
Feature/use fetch
- Loading branch information
Showing
12 changed files
with
1,404 additions
and
635 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './useFetch' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<template> | ||
<div> | ||
<div class="actions"> | ||
<button | ||
class="button is-primary" | ||
@click="startWithSuccess" | ||
:disabled="isLoading" | ||
v-text="isInit ? 'Fetch again!' : 'Fetch'" | ||
/> | ||
<button | ||
class="button is-info" | ||
@click="startWithFailed" | ||
:disabled="isLoading" | ||
v-text="`Fetch with failure`" | ||
/> | ||
<button | ||
class="button is-danger" | ||
@click="stop" | ||
:disabled="!isLoading" | ||
v-text="`Abort fetch`" | ||
/> | ||
</div> | ||
|
||
<!-- isAborted --> | ||
<use-fetch-demo-table status="Aborted" v-if="isAborted"> | ||
Resource fetch aborted with status code | ||
<strong>{{ status }}</strong> and message | ||
<strong>{{ statusText }}</strong> | ||
</use-fetch-demo-table> | ||
|
||
<!-- isFailed --> | ||
<use-fetch-demo-table status="Failed" v-else-if="isFailed"> | ||
Resource fetch failed with status code | ||
<strong>{{ status }}</strong> and message | ||
<strong>{{ statusText }}</strong> | ||
</use-fetch-demo-table> | ||
|
||
<!-- isSuccess --> | ||
<div v-else> | ||
<!-- !isInit --> | ||
<use-fetch-demo-table status="Not initialized" v-if="!isInit"> | ||
Click "Fetch" to initialize the request. | ||
</use-fetch-demo-table> | ||
|
||
<!-- isLoading --> | ||
<use-fetch-demo-table status="Loading" v-else-if="isLoading"> | ||
Resource is being fetched... | ||
</use-fetch-demo-table> | ||
|
||
<!-- Fetched --> | ||
<use-fetch-demo-table status="Success" v-else-if="data"> | ||
<p> | ||
Resource fetched successfully with status code | ||
<strong>{{ status }}</strong> and message | ||
<strong>{{ statusText }}</strong> | ||
</p> | ||
<img class="img" :src="data.message" alt="" /> | ||
</use-fetch-demo-table> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import Vue from 'vue' | ||
import { ref } from '@src/api' | ||
import { useFetch } from '@src/vue-use-kit' | ||
import UseFetchDemoTable from './UseFetchDemoTable.vue' | ||
export default Vue.extend({ | ||
name: 'UseFetchDemo', | ||
components: { UseFetchDemoTable }, | ||
setup() { | ||
const isInit = ref(false) | ||
const delayUrl = 'http://deelay.me/2000' | ||
const randomDogUrl = `${delayUrl}/https://dog.ceo/api/breeds/image/random` | ||
const url = ref(randomDogUrl) | ||
const { | ||
data, | ||
status, | ||
statusText, | ||
isLoading, | ||
isFailed, | ||
isAborted, | ||
start, | ||
stop | ||
} = useFetch(url, {}, false) | ||
const startWithSuccess = () => { | ||
isInit.value = true | ||
url.value = randomDogUrl | ||
start() | ||
} | ||
const startWithFailed = () => { | ||
isInit.value = true | ||
url.value = `${delayUrl}/https://dog.ceo` | ||
start() | ||
} | ||
return { | ||
data, | ||
status, | ||
statusText, | ||
isInit, | ||
isLoading, | ||
isFailed, | ||
isAborted, | ||
startWithSuccess, | ||
startWithFailed, | ||
stop | ||
} | ||
} | ||
}) | ||
</script> | ||
|
||
<style scoped> | ||
.actions { | ||
padding-bottom: 20px; | ||
} | ||
.img { | ||
display: block; | ||
margin: 20px 0 0; | ||
max-width: 300px; | ||
border-radius: 3px; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<template> | ||
<table class="table is-fullwidth"> | ||
<thead> | ||
<tr> | ||
<th>Status</th> | ||
<th>Message</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td v-text="status" /> | ||
<td width="100%"><slot></slot></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import Vue from 'vue' | ||
export default Vue.extend({ | ||
name: 'UseFetchDemoTable', | ||
props: { | ||
status: String | ||
} | ||
}) | ||
</script> | ||
|
||
<style scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# useFetch | ||
|
||
Vue function that fetch resources asynchronously across the network. | ||
|
||
## Reference | ||
|
||
```typescript | ||
type TUseFetchUrl = RequestInfo | Ref<RequestInfo> | ||
``` | ||
```typescript | ||
function useFetch( | ||
url: TUseFetchUrl, | ||
options?: RequestInit, | ||
runOnMount?: boolean | ||
): { | ||
data: Ref<any> | ||
status: Ref<number | null> | ||
statusText: Ref<string | null> | ||
isLoading: Ref<boolean> | ||
isFailed: Ref<boolean> | ||
isAborted: Ref<boolean> | ||
start: () => Promise<void> | ||
stop: () => void | ||
} | ||
``` | ||
|
||
### Parameters | ||
|
||
- `url: TUseFetchUrl` the fetch url value, can be type string or type `RequestInfo`. | ||
- `options: RequestInit` the fetch url options. | ||
- `runOnMount: boolean` whether to fetch on mount, `true` by default. | ||
|
||
### Returns | ||
|
||
- `data: Ref<any>` the response data, has to be of JSON type otherwise will return an error | ||
- `status: Ref<number | null>` the status code of the response | ||
- `statusText: Ref<string | null>` the status text of the response | ||
- `isLoading: Ref<boolean>` whether fetch request is loading or not | ||
- `isFailed: Ref<boolean>` whether fetch request has failed or not | ||
- `isAborted: Ref<boolean>` whether fetch request has been aborted or not | ||
- `start: Function` the function used for starting fetch request | ||
- `stop: Function` the function used for aborting fetch request | ||
|
||
## Usage | ||
|
||
```html | ||
<template> | ||
<div> | ||
<div v-if="isFailed">Failed!</div> | ||
<div v-else-if="isLoading">Loading...</div> | ||
<div v-else-if="data"> | ||
<img :src="data.message" alt="" /> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import Vue from 'vue' | ||
import { useFetch } from 'vue-use-kit' | ||
export default Vue.extend({ | ||
name: 'UseFetchDemo', | ||
setup() { | ||
const url = 'https://dog.ceo/api/breeds/image/random' | ||
const { data, isLoading, isFailed } = useFetch(url) | ||
return { data, isLoading, isFailed } | ||
} | ||
}) | ||
</script> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { storiesOf } from '@storybook/vue' | ||
import path from 'path' | ||
import StoryTitle from '@src/helpers/StoryTitle.vue' | ||
import UseFetchDemo from './UseFetchDemo.vue' | ||
|
||
const functionName = 'useFetch' | ||
const functionPath = path.resolve(__dirname, '..') | ||
const notes = require(`./${functionName}.md`).default | ||
|
||
const basicDemo = () => ({ | ||
components: { StoryTitle, demo: UseFetchDemo }, | ||
template: ` | ||
<div class="container"> | ||
<story-title | ||
function-path="${functionPath}" | ||
source-name="${functionName}" | ||
demo-name="UseFetchDemo.vue" | ||
> | ||
<template v-slot:title></template> | ||
<template v-slot:intro></template> | ||
</story-title> | ||
<demo /> | ||
</div>` | ||
}) | ||
|
||
storiesOf('side effects|useFetch', module) | ||
.addParameters({ notes }) | ||
.add('Demo', basicDemo) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { mount, flushPromises } from '@src/helpers/test' | ||
import { useFetch } from '@src/vue-use-kit' | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
const abortError = 'AbortError' | ||
|
||
const mockFetch = ({ | ||
data = { text: 'Message here' }, | ||
header = 'abcd;application/json', | ||
isAborted = false | ||
} = {}) => { | ||
;(window as any).fetch = () => { | ||
if (isAborted) { | ||
const err = new Error() | ||
err.name = abortError | ||
throw err | ||
} | ||
|
||
return { | ||
headers: { | ||
get: () => header | ||
}, | ||
json: () => data | ||
} | ||
} | ||
} | ||
|
||
const testComponent = (url = '', opts: RequestInit = {}, onMount = true) => ({ | ||
template: ` | ||
<div> | ||
<div id="isLoading" v-if="isLoading"></div> | ||
<div id="isFailed" v-if="isFailed"></div> | ||
<div id="isAborted" v-if="isAborted"></div> | ||
<div id="data" v-if="data" v-text="data.text"></div> | ||
<button id="start" @click="start"></button> | ||
<button id="stop" @click="stop"></button> | ||
</div> | ||
`, | ||
setup() { | ||
const { data, isLoading, isFailed, isAborted, start, stop } = useFetch( | ||
url, | ||
opts, | ||
onMount | ||
) | ||
return { data, isLoading, isFailed, isAborted, start, stop } | ||
} | ||
}) | ||
|
||
describe('useFetch', () => { | ||
const url = 'http://test.com' | ||
|
||
it('should show #isLoading in the correct order', async () => { | ||
mockFetch() | ||
const wrapper = mount(testComponent(url)) | ||
expect(wrapper.find('#isLoading').exists()).toBe(false) | ||
await wrapper.vm.$nextTick() | ||
expect(wrapper.find('#isLoading').exists()).toBe(true) | ||
}) | ||
|
||
it('should show #data when provided', async () => { | ||
const data = { text: 'Here is some data' } | ||
mockFetch({ data }) | ||
const wrapper = mount(testComponent(url)) | ||
await flushPromises() | ||
expect(wrapper.find('#data').text()).toBe(data.text) | ||
}) | ||
|
||
it('should show #isFailed when the header is not of json type', async () => { | ||
mockFetch({ header: 'brokenHeader' }) | ||
const wrapper = mount(testComponent(url)) | ||
await flushPromises() | ||
expect(wrapper.find('#isFailed').exists()).toBe(true) | ||
}) | ||
|
||
it('should show #isAborted when aborted is triggered', async () => { | ||
mockFetch({ isAborted: true }) | ||
const wrapper = mount(testComponent(url)) | ||
await flushPromises() | ||
expect(wrapper.find('#isFailed').exists()).toBe(true) | ||
expect(wrapper.find('#isAborted').exists()).toBe(true) | ||
}) | ||
}) |
Oops, something went wrong.