mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
feat(vue-app): new fetch syntax (#6880)
This commit is contained in:
parent
e271aa0a0a
commit
6db325c321
5
examples/new-fetch/README.md
Normal file
5
examples/new-fetch/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# New fetch() with Nuxt.js
|
||||
|
||||
Nuxt.js `v2.12` introduces a new hook called `fetch` in any of your Vue components.
|
||||
|
||||
See [live demo](https://nuxt-new-fetch.surge.sh) and [documentation](https://nuxtjs.org/api/pages-fetch).
|
30
examples/new-fetch/components/Author.vue
Normal file
30
examples/new-fetch/components/Author.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<p v-if="$fetchState.error">
|
||||
Could not fetch Author
|
||||
</p>
|
||||
<p v-else>
|
||||
Written by {{ $fetchState.pending ? '...' : user.name }} <button @click="$fetch">
|
||||
Refresh
|
||||
</button>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
userId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
async fetch () {
|
||||
this.user = await this.$http.$get(`https://jsonplaceholder.typicode.com/users/${this.userId}`)
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
user: {}
|
||||
}
|
||||
},
|
||||
fetchOnServer: false
|
||||
}
|
||||
</script>
|
28
examples/new-fetch/layouts/default.vue
Normal file
28
examples/new-fetch/layouts/default.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <p>Fetching: {{ $nuxt.isFetching }} ({{ $nuxt.nbFetching }})</p> -->
|
||||
<nuxt />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
head: {
|
||||
link: [
|
||||
{ rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/gh/kognise/water.css@1.4.0/dist/light.min.css' }
|
||||
]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
.page-enter-active, .page-leave-active {
|
||||
transition: opacity .3s;
|
||||
}
|
||||
.page-enter, .page-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
18
examples/new-fetch/nuxt.config.js
Normal file
18
examples/new-fetch/nuxt.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
const fetch = require('node-fetch')
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
'@/plugins/vue-placeholders.js'
|
||||
],
|
||||
modules: [
|
||||
'@nuxt/http'
|
||||
],
|
||||
generate: {
|
||||
async routes () {
|
||||
const posts = await fetch('https://jsonplaceholder.typicode.com/posts').then(res => res.json()).then(d => d.slice(0, 20))
|
||||
const routes = posts.map(post => `/posts/${post.id}`)
|
||||
|
||||
return ['/'].concat(routes)
|
||||
}
|
||||
}
|
||||
}
|
18
examples/new-fetch/package.json
Normal file
18
examples/new-fetch/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "example-hello-world",
|
||||
"dependencies": {
|
||||
"@nuxt/http": "^0.3.8",
|
||||
"nuxt-start": "latest",
|
||||
"vue-content-placeholders": "^0.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nuxt": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start",
|
||||
"generate": "nuxt generate",
|
||||
"post-update": "yarn upgrade --latest"
|
||||
}
|
||||
}
|
43
examples/new-fetch/pages/index.vue
Normal file
43
examples/new-fetch/pages/index.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Blog posts</h1>
|
||||
<template v-if="$fetchState.pending">
|
||||
<content-placeholders>
|
||||
<content-placeholders-text :lines="20" />
|
||||
</content-placeholders>
|
||||
</template>
|
||||
<template v-else-if="$fetchState.error">
|
||||
<p>
|
||||
Error while fetching posts: {{ error }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ul>
|
||||
<li v-for="post of posts" :key="post.id">
|
||||
<n-link :to="`/posts/${post.id}`">
|
||||
{{ post.title }}
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/posts/404">
|
||||
404 post
|
||||
</n-link>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
this.posts = await this.$http.$get('https://jsonplaceholder.typicode.com/posts')
|
||||
.then(posts => posts.slice(0, 20))
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
posts: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
54
examples/new-fetch/pages/posts/_id.vue
Normal file
54
examples/new-fetch/pages/posts/_id.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<button @click="$fetch">
|
||||
Refresh
|
||||
</button>
|
||||
<template v-if="$fetchState.pending">
|
||||
<content-placeholders>
|
||||
<content-placeholders-heading />
|
||||
<content-placeholders-text :lines="10" />
|
||||
</content-placeholders>
|
||||
</template>
|
||||
<template v-else-if="$fetchState.error">
|
||||
<h1>
|
||||
Post #{{ $route.params.id }} not found
|
||||
</h1>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1>{{ post.title }}</h1>
|
||||
<author :user-id="post.userId" />
|
||||
<pre>{{ post.body }}</pre>
|
||||
<p>
|
||||
<n-link :to="{ name: 'posts-id', params: { id: (post.id + 1) } }">
|
||||
Next article
|
||||
</n-link>
|
||||
</p>
|
||||
</template>
|
||||
<p>
|
||||
<n-link to="/">
|
||||
Home
|
||||
</n-link>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Author from '~/components/Author.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Author
|
||||
},
|
||||
async fetch () {
|
||||
this.post = await this.$http.$get(`https://jsonplaceholder.typicode.com/posts/${this.$route.params.id}`)
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
post: {}
|
||||
}
|
||||
},
|
||||
head () {
|
||||
return { title: this.post.title }
|
||||
}
|
||||
}
|
||||
</script>
|
4
examples/new-fetch/plugins/vue-placeholders.js
Normal file
4
examples/new-fetch/plugins/vue-placeholders.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Vue from 'vue'
|
||||
import VueContentPlaceholders from 'vue-content-placeholders'
|
||||
|
||||
Vue.use(VueContentPlaceholders)
|
@ -13,6 +13,8 @@ export const template = {
|
||||
'server.js',
|
||||
'utils.js',
|
||||
'empty.js',
|
||||
'mixins/fetch.server.js',
|
||||
'mixins/fetch.client.js',
|
||||
'components/nuxt-error.vue',
|
||||
'components/nuxt-child.js',
|
||||
'components/nuxt-link.server.js',
|
||||
|
@ -2,6 +2,7 @@ import Vue from 'vue'
|
||||
<% if (features.asyncData || features.fetch) { %>
|
||||
import {
|
||||
getMatchedComponentsInstances,
|
||||
getChildrenComponentInstancesUsingFetch,
|
||||
promisify,
|
||||
globalHandleError
|
||||
} from './utils'
|
||||
@ -88,7 +89,10 @@ export default {
|
||||
<% } %>
|
||||
<% if (features.layouts) { %>
|
||||
layout: null,
|
||||
layoutName: ''
|
||||
layoutName: '',
|
||||
<% } %>
|
||||
<% if (features.fetch) { %>
|
||||
nbFetching: 0
|
||||
<% } %>
|
||||
}),
|
||||
<% } %>
|
||||
@ -125,7 +129,12 @@ export default {
|
||||
computed: {
|
||||
isOffline () {
|
||||
return !this.isOnline
|
||||
},
|
||||
<% if (features.fetch) { %>
|
||||
isFetching() {
|
||||
return this.nbFetching > 0
|
||||
}
|
||||
<% } %>
|
||||
},
|
||||
<% } %>
|
||||
methods: {
|
||||
@ -157,9 +166,18 @@ export default {
|
||||
const p = []
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
if (page.$options.fetch) {
|
||||
// Old fetch
|
||||
if (page.$options.fetch && page.$options.fetch.length) {
|
||||
p.push(promisify(page.$options.fetch, this.context))
|
||||
}
|
||||
if (page.$fetch) {
|
||||
p.push(page.$fetch())
|
||||
} else {
|
||||
// Get all component instance to call $fetch
|
||||
for (const component of getChildrenComponentInstancesUsingFetch(page.$vnode.componentInstance)) {
|
||||
p.push(component.$fetch())
|
||||
}
|
||||
}
|
||||
<% } %>
|
||||
<% if (features.asyncData) { %>
|
||||
if (page.$options.asyncData) {
|
||||
|
@ -17,8 +17,17 @@ import {
|
||||
globalHandleError
|
||||
} from './utils.js'
|
||||
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
|
||||
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.client'<% } %>
|
||||
import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch ? "client" : "server" %>.js' // should be included after ./index.js
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
// Fetch mixin
|
||||
if (!Vue.__nuxt__fetch__mixin__) {
|
||||
Vue.mixin(fetchMixin)
|
||||
Vue.__nuxt__fetch__mixin__ = true
|
||||
}
|
||||
<% } %>
|
||||
|
||||
// Component: <NuxtLink>
|
||||
Vue.component(NuxtLink.name, NuxtLink)
|
||||
<% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
|
||||
@ -458,7 +467,10 @@ async function render (to, from, next) {
|
||||
<% } %>
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
const hasFetch = Boolean(Component.options.fetch)
|
||||
const hasFetch = Boolean(Component.options.fetch) && Component.options.fetch.length
|
||||
if (hasFetch) {
|
||||
console.warn('fetch(context) has been deprecated, please use middleware(context)')
|
||||
}
|
||||
<% } else { %>
|
||||
const hasFetch = false
|
||||
<% } %>
|
||||
@ -738,7 +750,7 @@ function addHotReload ($component, depth) {
|
||||
<% if (features.fetch) { %>
|
||||
// Call fetch()
|
||||
Component.options.fetch = Component.options.fetch || noopFetch
|
||||
let pFetch = Component.options.fetch(context)
|
||||
let pFetch = Component.options.fetch.length && Component.options.fetch(context)
|
||||
if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) }
|
||||
<%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
|
||||
promises.push(pFetch)
|
||||
|
@ -13,7 +13,8 @@ export default {
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
render (h, { parent, data, props }) {
|
||||
render (_, { parent, data, props }) {
|
||||
const h = parent.$createElement
|
||||
<% if (features.transitions) { %>
|
||||
data.nuxtChild = true
|
||||
const _parent = parent
|
||||
@ -42,6 +43,7 @@ export default {
|
||||
listeners[key] = transition[key].bind(_parent)
|
||||
}
|
||||
})
|
||||
if (process.client) {
|
||||
// Add triggerScroll event on beforeEnter (fix #1376)
|
||||
const beforeEnter = listeners.beforeEnter
|
||||
listeners.beforeEnter = (el) => {
|
||||
@ -53,6 +55,7 @@ export default {
|
||||
return beforeEnter.call(_parent, el)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure that leave is called asynchronous (fix #5703)
|
||||
if (transition.css === false) {
|
||||
|
80
packages/vue-app/template/mixins/fetch.client.js
Normal file
80
packages/vue-app/template/mixins/fetch.client.js
Normal file
@ -0,0 +1,80 @@
|
||||
import Vue from 'vue'
|
||||
import { hasFetch, normalizeError, addLifecycleHook } from '../utils'
|
||||
|
||||
const isSsrHydration = (vm) => vm.$vnode && vm.$vnode.elm && vm.$vnode.elm.dataset && vm.$vnode.elm.dataset.ssrKey
|
||||
const nuxtState = window.<%= globals.context %>
|
||||
|
||||
export default {
|
||||
beforeCreate () {
|
||||
if (!hasFetch(this)) {
|
||||
return
|
||||
}
|
||||
|
||||
this._fetchDelay = typeof this.$options.fetchDelay === 'number' ? this.$options.fetchDelay : 200
|
||||
|
||||
Vue.util.defineReactive(this, '$fetchState', {
|
||||
pending: false,
|
||||
error: null,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
this.$fetch = $fetch.bind(this)
|
||||
addLifecycleHook(this, 'created', created)
|
||||
addLifecycleHook(this, 'beforeMount', beforeMount)
|
||||
}
|
||||
}
|
||||
|
||||
function beforeMount() {
|
||||
if (!this._hydrated) {
|
||||
return this.$fetch()
|
||||
}
|
||||
}
|
||||
|
||||
function created() {
|
||||
if (!isSsrHydration(this)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Hydrate component
|
||||
this._hydrated = true
|
||||
this._ssrKey = +this.$vnode.elm.dataset.ssrKey
|
||||
const data = nuxtState.fetch[this._ssrKey]
|
||||
|
||||
// If fetch error
|
||||
if (data && data._error) {
|
||||
this.$fetchState.error = data._error
|
||||
return
|
||||
}
|
||||
|
||||
// Merge data
|
||||
for (const key in data) {
|
||||
Vue.set(this.$data, key, data[key])
|
||||
}
|
||||
}
|
||||
|
||||
async function $fetch() {
|
||||
this.$nuxt.nbFetching++
|
||||
this.$fetchState.pending = true
|
||||
this.$fetchState.error = null
|
||||
this._hydrated = false
|
||||
let error = null
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
await this.$options.fetch.call(this)
|
||||
} catch (err) {
|
||||
error = normalizeError(err)
|
||||
}
|
||||
|
||||
const delayLeft = this._fetchDelay - (Date.now() - startTime)
|
||||
if (delayLeft > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, delayLeft))
|
||||
}
|
||||
|
||||
this.$fetchState.error = error
|
||||
this.$fetchState.pending = false
|
||||
this.$fetchState.timestamp = Date.now()
|
||||
|
||||
this.$nextTick(() => this.$nuxt.nbFetching--)
|
||||
}
|
||||
|
49
packages/vue-app/template/mixins/fetch.server.js
Normal file
49
packages/vue-app/template/mixins/fetch.server.js
Normal file
@ -0,0 +1,49 @@
|
||||
import Vue from 'vue'
|
||||
import { hasFetch, normalizeError, addLifecycleHook } from '../utils'
|
||||
|
||||
async function serverPrefetch() {
|
||||
if (!this._fetchOnServer) {
|
||||
return
|
||||
}
|
||||
|
||||
// Call and await on $fetch
|
||||
try {
|
||||
await this.$options.fetch.call(this)
|
||||
} catch (err) {
|
||||
this.$fetchState.error = normalizeError(err)
|
||||
}
|
||||
this.$fetchState.pending = false
|
||||
|
||||
|
||||
// Define an ssrKey for hydration
|
||||
this._ssrKey = this.$ssrContext.nuxt.fetch.length
|
||||
|
||||
// Add data-ssr-key on parent element of Component
|
||||
const attrs = this.$vnode.data.attrs = this.$vnode.data.attrs || {}
|
||||
attrs['data-ssr-key'] = this._ssrKey
|
||||
|
||||
// Call asyncData & add to ssrContext for window.__NUXT__.fetch
|
||||
this.$ssrContext.nuxt.fetch.push(this.$fetchState.error ? { _error: this.$fetchState.error } : this._data)
|
||||
}
|
||||
|
||||
export default {
|
||||
beforeCreate() {
|
||||
if (!hasFetch(this)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof this.$options.fetchOnServer === 'function') {
|
||||
this._fetchOnServer = this.$options.fetchOnServer.call(this) !== false
|
||||
} else {
|
||||
this._fetchOnServer = this.$options.fetchOnServer !== false
|
||||
}
|
||||
|
||||
Vue.util.defineReactive(this, '$fetchState', {
|
||||
pending: true,
|
||||
error: null,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
addLifecycleHook(this, 'serverPrefetch', serverPrefetch)
|
||||
}
|
||||
}
|
@ -9,9 +9,21 @@ import {
|
||||
getMatchedComponents,
|
||||
promisify
|
||||
} from './utils.js'
|
||||
<% if (features.fetch) { %>import fetchMixin from './mixins/fetch.server'<% } %>
|
||||
import { createApp<% if (features.layouts) { %>, NuxtError<% } %> } from './index.js'
|
||||
import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
// Update serverPrefetch strategy
|
||||
Vue.config.optionMergeStrategies.serverPrefetch = Vue.config.optionMergeStrategies.created
|
||||
|
||||
// Fetch mixin
|
||||
if (!Vue.__nuxt__fetch__mixin__) {
|
||||
Vue.mixin(fetchMixin)
|
||||
Vue.__nuxt__fetch__mixin__ = true
|
||||
}
|
||||
<% } %>
|
||||
|
||||
// Component: <NuxtLink>
|
||||
Vue.component(NuxtLink.name, NuxtLink)
|
||||
<% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
|
||||
@ -60,7 +72,7 @@ export default async (ssrContext) => {
|
||||
// Used for beforeNuxtRender({ Components, nuxtState })
|
||||
ssrContext.beforeRenderFns = []
|
||||
// Nuxt object (window{{globals.context}}, defaults to window.__NUXT__)
|
||||
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
|
||||
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], <% if (features.fetch) { %>fetch: [], <% } %>error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
|
||||
// Create the app definition and the instance (created for each request)
|
||||
const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)
|
||||
const _app = new Vue(app)
|
||||
@ -267,7 +279,8 @@ export default async (ssrContext) => {
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
// Call fetch(context)
|
||||
if (Component.options.fetch) {
|
||||
if (Component.options.fetch && Component.options.fetch.length) {
|
||||
console.warn('fetch(context) has been deprecated, please use middleware(context)')
|
||||
promises.push(Component.options.fetch(app.context))
|
||||
} else {
|
||||
promises.push(null)
|
||||
|
@ -21,6 +21,24 @@ export function interopDefault (promise) {
|
||||
return promise.then(m => m.default || m)
|
||||
}
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
export function hasFetch(vm) {
|
||||
return vm.$options && typeof vm.$options.fetch === 'function' && !vm.$options.fetch.length
|
||||
}
|
||||
export function getChildrenComponentInstancesUsingFetch(vm, instances = []) {
|
||||
const children = vm.$children || []
|
||||
for (const child of children) {
|
||||
if (child.$fetch) {
|
||||
instances.push(child)
|
||||
continue; // Don't get the children since it will reload the template
|
||||
}
|
||||
if (child.$children) {
|
||||
getChildrenComponentInstancesUsingFetch(child, instances)
|
||||
}
|
||||
}
|
||||
return instances
|
||||
}
|
||||
<% } %>
|
||||
<% if (features.asyncData) { %>
|
||||
export function applyAsyncData (Component, asyncData) {
|
||||
if (
|
||||
@ -615,3 +633,10 @@ function formatQuery (query) {
|
||||
}).filter(Boolean).join('&')
|
||||
}
|
||||
<% } %>
|
||||
|
||||
export function addLifecycleHook(vm, hook, fn) {
|
||||
if (!vm.$options[hook]) {
|
||||
vm.$options[hook] = []
|
||||
}
|
||||
vm.$options[hook].push(fn)
|
||||
}
|
||||
|
159
test/e2e/fetch.browser.test.js
Normal file
159
test/e2e/fetch.browser.test.js
Normal file
@ -0,0 +1,159 @@
|
||||
import Browser from '../utils/browser'
|
||||
import { loadFixture, getPort, Nuxt } from '../utils'
|
||||
|
||||
let port
|
||||
const browser = new Browser()
|
||||
const url = route => 'http://localhost:' + port + route
|
||||
|
||||
let nuxt = null
|
||||
let page = null
|
||||
|
||||
describe('basic browser', () => {
|
||||
beforeAll(async () => {
|
||||
const config = await loadFixture('fetch')
|
||||
nuxt = new Nuxt(config)
|
||||
await nuxt.ready()
|
||||
|
||||
port = await getPort()
|
||||
await nuxt.server.listen(port, 'localhost')
|
||||
|
||||
await browser.start({
|
||||
// slowMo: 50,
|
||||
// headless: false
|
||||
})
|
||||
})
|
||||
|
||||
test('Open /', async () => {
|
||||
page = await browser.page(url('/'))
|
||||
expect(await page.$text('pre')).toContain('Atinux')
|
||||
})
|
||||
|
||||
test('/fetch-client', async () => {
|
||||
await page.nuxt.navigate('/fetch-client')
|
||||
expect(await page.$text('p')).toContain('Fetching...')
|
||||
await page.waitForSelector('pre')
|
||||
expect(await page.$text('pre')).toContain('pi0')
|
||||
})
|
||||
|
||||
test('/fetch-error', async () => {
|
||||
await page.nuxt.navigate('/fetch-error')
|
||||
expect(await page.$text('p')).toContain('Fetching...')
|
||||
await page.waitForSelector('#error')
|
||||
expect(await page.$text('#error')).toContain('fetch-error')
|
||||
})
|
||||
|
||||
test('/fetch-component', async () => {
|
||||
await page.nuxt.navigate('/fetch-component')
|
||||
expect(await page.$text('p')).toContain('Fetching...')
|
||||
await page.waitForSelector('pre')
|
||||
expect(await page.$text('pre')).toContain('clarkdo')
|
||||
})
|
||||
|
||||
test('/fetch-delay', async () => {
|
||||
const now = Date.now()
|
||||
await page.nuxt.navigate('/fetch-delay')
|
||||
expect(await page.$text('p')).toContain('Fetching for 1 second')
|
||||
await page.waitForSelector('pre')
|
||||
const delay = Date.now() - now
|
||||
expect(await page.$text('pre')).toContain('alexchopin')
|
||||
expect(delay).toBeGreaterThanOrEqual(1000)
|
||||
})
|
||||
|
||||
test('/fetch-button', async () => {
|
||||
await page.nuxt.navigate('/fetch-button')
|
||||
expect(await page.$text('p')).toContain('Fetching...')
|
||||
await page.waitForSelector('pre')
|
||||
expect(await page.$text('pre')).toContain('kevinmarrec')
|
||||
await page.click('button')
|
||||
expect(await page.$text('p')).toContain('Fetching...')
|
||||
await page.waitForSelector('pre')
|
||||
expect(await page.$text('pre')).toContain('kevinmarrec')
|
||||
})
|
||||
|
||||
test('/old-fetch', async () => {
|
||||
const msg = new Promise(resolve =>
|
||||
page.on('console', msg => resolve(msg.text()))
|
||||
)
|
||||
await page.nuxt.navigate('/old-fetch')
|
||||
expect(await msg).toBe('fetch(context) has been deprecated, please use middleware(context)')
|
||||
})
|
||||
|
||||
test('ssr: /fetch-client', async () => {
|
||||
const page = await browser.page(url('/fetch-client'))
|
||||
expect(await page.$text('p')).toContain('Fetching...')
|
||||
await page.waitForSelector('pre')
|
||||
expect(await page.$text('pre')).toContain('pi0')
|
||||
page.close()
|
||||
})
|
||||
|
||||
test('ssr: /fetch-conditional', async () => {
|
||||
const page = await browser.page(url('/fetch-conditional'))
|
||||
expect(await page.$text('pre')).toContain('galvez')
|
||||
page.close()
|
||||
})
|
||||
|
||||
test('ssr: /fetch-conditional?fetch_client=true', async () => {
|
||||
const page = await browser.page(url('/fetch-conditional?fetch_client=true'))
|
||||
expect(await page.$text('p')).toContain('Fetching...')
|
||||
await page.waitForSelector('pre')
|
||||
expect(await page.$text('pre')).toContain('pimlie')
|
||||
page.close()
|
||||
})
|
||||
|
||||
test('ssr: /fetch-error', async () => {
|
||||
const page = await browser.page(url('/fetch-error'))
|
||||
expect(await page.$text('#error')).toContain('fetch-error')
|
||||
page.close()
|
||||
})
|
||||
|
||||
test('ssr: /fetch-deep', async () => {
|
||||
const page = await browser.page(url('/fetch-deep'))
|
||||
const expectedState = {
|
||||
foo: 'barbar',
|
||||
user: {
|
||||
name: 'Potato',
|
||||
inventory: {
|
||||
type: 'green',
|
||||
items: ['A', 'B']
|
||||
}
|
||||
},
|
||||
async: 'data',
|
||||
async2: 'data2fetch'
|
||||
}
|
||||
|
||||
// Hydrated HTML
|
||||
const renderedData = await page.$text('#data').then(t => JSON.parse(t))
|
||||
expect(renderedData).toMatchObject(expectedState)
|
||||
|
||||
// Fragments
|
||||
const { data, fetch } = await page.evaluate(() => window.__NUXT__)
|
||||
expect(data.length).toBe(1)
|
||||
expect(fetch.length).toBe(1)
|
||||
|
||||
// asyncData mutations
|
||||
expect(data[0]).toMatchObject({ async: 'data', async2: 'data2' })
|
||||
|
||||
// fetch mutations
|
||||
expect(fetch[0]).toMatchObject({
|
||||
user: {
|
||||
inventory: { items: ['A', 'B'] },
|
||||
name: 'Potato'
|
||||
},
|
||||
foo: 'barbar',
|
||||
async2: 'data2fetch'
|
||||
})
|
||||
|
||||
page.close()
|
||||
})
|
||||
|
||||
// Close server and ask nuxt to stop listening to file changes
|
||||
afterAll(async () => {
|
||||
await nuxt.close()
|
||||
})
|
||||
|
||||
// Stop browser
|
||||
afterAll(async () => {
|
||||
await page.close()
|
||||
await browser.close()
|
||||
})
|
||||
})
|
2
test/fixtures/basic/pages/error-object.vue
vendored
2
test/fixtures/basic/pages/error-object.vue
vendored
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
fetch () {
|
||||
middleware (context) {
|
||||
throw { error: 'fetch error!' } // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/basic/pages/error-string.vue
vendored
2
test/fixtures/basic/pages/error-string.vue
vendored
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
fetch () {
|
||||
middleware (context) {
|
||||
throw 'fetch error!' // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
fetch ({ redirect }) {
|
||||
middleware ({ redirect }) {
|
||||
return redirect('https://nuxtjs.org/')
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/basic/pages/redirect-name.vue
vendored
2
test/fixtures/basic/pages/redirect-name.vue
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
fetch ({ redirect }) {
|
||||
middleware ({ redirect }) {
|
||||
return redirect({ name: 'stateless' })
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/basic/pages/redirect.vue
vendored
2
test/fixtures/basic/pages/redirect.vue
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
fetch ({ redirect }) {
|
||||
middleware ({ redirect }) {
|
||||
return redirect('/')
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/basic/pages/special-state.vue
vendored
2
test/fixtures/basic/pages/special-state.vue
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
fetch ({ beforeNuxtRender }) {
|
||||
middleware ({ beforeNuxtRender }) {
|
||||
if (process.server) {
|
||||
beforeNuxtRender(({ nuxtState }) => {
|
||||
nuxtState.test = true
|
||||
|
2
test/fixtures/basic/pages/store-module.vue
vendored
2
test/fixtures/basic/pages/store-module.vue
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
fetch ({ store }) {
|
||||
middleware ({ store }) {
|
||||
store.dispatch('simpleModule/mutate')
|
||||
}
|
||||
}
|
||||
|
26
test/fixtures/fetch/components/Team.vue
vendored
Normal file
26
test/fixtures/fetch/components/Team.vue
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="$fetchState.pending">
|
||||
Fetching...
|
||||
</p>
|
||||
<p v-else-if="$fetchState.error">
|
||||
{{ $fetchState.error }}
|
||||
</p>
|
||||
<pre v-else>{{ team }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
|
||||
|
||||
this.team = await fetch(`${url}/team.json`).then(res => res.json())
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
3
test/fixtures/fetch/fetch.test.js
vendored
Normal file
3
test/fixtures/fetch/fetch.test.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { buildFixture } from '../../utils/build'
|
||||
|
||||
buildFixture('fetch')
|
52
test/fixtures/fetch/layouts/default.vue
vendored
Normal file
52
test/fixtures/fetch/layouts/default.vue
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul>
|
||||
<li>
|
||||
<n-link to="/">
|
||||
Fetch
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/fetch-client">
|
||||
Fetch on client
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/fetch-conditional">
|
||||
Fetch conditional
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/fetch-error">
|
||||
Fetch error
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/fetch-delay">
|
||||
Fetch delay
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/fetch-button">
|
||||
Fetch button
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/fetch-component">
|
||||
Fetch in component
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/fetch-deep">
|
||||
Fetch with deep update updates
|
||||
</n-link>
|
||||
</li>
|
||||
<li>
|
||||
<n-link to="/old-fetch">
|
||||
Deprecated fetch
|
||||
</n-link>
|
||||
</li>
|
||||
</ul>
|
||||
<nuxt />
|
||||
</div>
|
||||
</template>
|
1
test/fixtures/fetch/nuxt.config.js
vendored
Normal file
1
test/fixtures/fetch/nuxt.config.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default {}
|
26
test/fixtures/fetch/pages/fetch-button.vue
vendored
Normal file
26
test/fixtures/fetch/pages/fetch-button.vue
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="$fetchState.pending">
|
||||
Fetching...
|
||||
</p>
|
||||
<pre v-else>{{ team }}</pre>
|
||||
<button @click="$fetch">
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
|
||||
|
||||
this.team = await fetch(`${url}/team.json`).then(res => res.json())
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
22
test/fixtures/fetch/pages/fetch-client.vue
vendored
Normal file
22
test/fixtures/fetch/pages/fetch-client.vue
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="$fetchState.pending">
|
||||
Fetching...
|
||||
</p>
|
||||
<pre v-else>{{ team }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
this.team = await fetch('/team.json').then(res => res.json())
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
},
|
||||
fetchOnServer: false
|
||||
}
|
||||
</script>
|
13
test/fixtures/fetch/pages/fetch-component.vue
vendored
Normal file
13
test/fixtures/fetch/pages/fetch-component.vue
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<team />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Team from '@/components/Team.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Team
|
||||
}
|
||||
}
|
||||
</script>
|
27
test/fixtures/fetch/pages/fetch-conditional.vue
vendored
Normal file
27
test/fixtures/fetch/pages/fetch-conditional.vue
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<nuxt-link to="/fetch-conditional?fetch_client=true">Fetch on client</nuxt-link>
|
||||
<p v-if="$fetchState.pending">
|
||||
Fetching...
|
||||
</p>
|
||||
<pre v-else>{{ team }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
|
||||
|
||||
this.team = await fetch(`${url}/team.json`).then(res => res.json())
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
},
|
||||
fetchOnServer () {
|
||||
return !this.$route.query.fetch_client
|
||||
}
|
||||
}
|
||||
</script>
|
35
test/fixtures/fetch/pages/fetch-deep.vue
vendored
Normal file
35
test/fixtures/fetch/pages/fetch-deep.vue
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<pre id="data" v-html="JSON.stringify($data)" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
this.user.inventory.items.push('B')
|
||||
this.user.name = 'Potato'
|
||||
this.foo = 'barbar'
|
||||
this.async2 = 'data2fetch'
|
||||
},
|
||||
async asyncData () {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
return {
|
||||
async: 'data',
|
||||
async2: 'data2'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
foo: 'bar',
|
||||
user: {
|
||||
name: 'Baz',
|
||||
inventory: {
|
||||
type: 'green',
|
||||
items: ['A']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
24
test/fixtures/fetch/pages/fetch-delay.vue
vendored
Normal file
24
test/fixtures/fetch/pages/fetch-delay.vue
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="$fetchState.pending">
|
||||
Fetching for 1 second
|
||||
</p>
|
||||
<pre v-else>{{ team }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
|
||||
|
||||
this.team = await fetch(`${url}/team.json`).then(res => res.json())
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
},
|
||||
fetchDelay: 1000
|
||||
}
|
||||
</script>
|
24
test/fixtures/fetch/pages/fetch-error.vue
vendored
Normal file
24
test/fixtures/fetch/pages/fetch-error.vue
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="$fetchState.pending">
|
||||
Fetching...
|
||||
</p>
|
||||
<p v-else-if="$fetchState.error" id="error">
|
||||
{{ $fetchState.error.message }}
|
||||
</p>
|
||||
<pre v-else>{{ team }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
await new Promise((resolve, reject) => reject(new Error('fetch-error')))
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
23
test/fixtures/fetch/pages/index.vue
vendored
Normal file
23
test/fixtures/fetch/pages/index.vue
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="$fetchState.pending">
|
||||
Fetching...
|
||||
</p>
|
||||
<pre v-else>{{ team }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch () {
|
||||
const url = (process.server ? `http://${this.$ssrContext.req.headers.host}` : '')
|
||||
|
||||
this.team = await fetch(`${url}/team.json`).then(res => res.json())
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
18
test/fixtures/fetch/pages/old-fetch.vue
vendored
Normal file
18
test/fixtures/fetch/pages/old-fetch.vue
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
Display a warning in the console
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
async fetch (context) {
|
||||
// Should display a warning
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
team: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
11
test/fixtures/fetch/static/team.json
vendored
Normal file
11
test/fixtures/fetch/static/team.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
"Atinux",
|
||||
"alexchopin",
|
||||
"pi0",
|
||||
"clarkdo",
|
||||
"manniL",
|
||||
"galvez",
|
||||
"aldarund",
|
||||
"kevinmarrec",
|
||||
"pimlie"
|
||||
]
|
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
fetch () {
|
||||
fetch (context) {
|
||||
throw { message: 'fetch error!' } // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
fetch () {
|
||||
fetch (context) {
|
||||
throw 'fetch error!' // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
2
test/fixtures/spa/pages/error-handler.vue
vendored
2
test/fixtures/spa/pages/error-handler.vue
vendored
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
fetch () {
|
||||
fetch (context) {
|
||||
throw new Error('fetch error!')
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user