-/** @type {import('vue').VueConstructor} */
-// @ts-ignore
-const Vue = require('vue')
-const Router = require('../../src').default
-const { components, isMocha } = require('../utils')
-const { createRenderer } = require('vue-server-renderer')
+const { renderApp, renderer } = require('./shared')
-describe.skip('SSR: basicRenderer', () => {
- Vue.use(Router)
-
- function createRouter() {
- // TODO: a more complex routing that can be used for most tests
- return new Router({
- mode: 'history',
- routes: [
- {
- path: '/',
- component: components.Home,
- },
- {
- path: '/foo',
- component: components.Foo,
- },
- ],
- })
- }
-
- function createApp() {
- // create router instance
- const router = createRouter()
-
- const app = new Vue({
- // @ts-ignore
- router,
- render: h => h('div', {}, [h('RouterView')]),
- })
-
- // return both the app and the router
- return { app, router }
- }
-
- function renderApp(context) {
- return new Promise((resolve, reject) => {
- const { app, router } = createApp()
-
- // set server-side router's location
- router.push(context.url)
-
- // wait until router has resolved possible async components and hooks
- // TODO: rename the promise one to isReady
- router.onReady().then(() => {
- // const matchedComponents = router.getMatchedComponents()
- // no matched routes, reject with 404
- if (!matchedComponents.length) {
- return reject({ code: 404 })
- }
+describe('SSR: basicRenderer', () => {
+ it('renders the view', async () => {
+ const app = await renderApp({ url: '/' })
+ const result = await renderer.renderToString(app)
+ expect(result).toMatchInlineSnapshot(
+ `"<div data-server-rendered=\\"true\\"><div>Home</div></div>"`
+ )
+ })
- // the Promise should resolve to the app instance so it can be rendered
- resolve(app)
- }, reject)
- })
- }
+ /**
+ * TODO:
+ * - KeepAlive
+ * - Suspense
+ * - Navigation Guards
+ * - Cancelled
+ * - Redirection
+ * - Async components
+ * - Views
+ * - Inner components
+ */
- it('should work', done => {
- renderToString(
- new Vue({
+ it('should work', async () => {
+ const app = await renderApp(
+ { url: '/' },
+ {},
+ {
template: `
<div>
<p class="hi">yoyo</p>
})
},
},
- }),
- (err, result) => {
- expect(err).toBeNull()
- expect(result).toContain(
- '<div data-server-rendered="true">' +
- '<p class="hi">yoyo</p> ' +
- '<div id="ho" class="red"></div> ' +
- '<span>hi</span> ' +
- '<input value="hi"> ' +
- '<img src="https://vuejs.org/images/logo.png"> ' +
- '<div class="a">test</div> ' +
- '<span class="b">testAsync</span>' +
- '</div>'
- )
- done()
}
)
- })
+ const result = await renderer.renderToString(app)
- // #5941
- it('should work peoperly when accessing $ssrContext in root component', done => {
- let ssrContext
- renderToString(
- new Vue({
- template: `
- <div></div>
- `,
- created() {
- ssrContext = this.$ssrContext
- },
- }),
- err => {
- expect(err).toBeNull()
- expect(ssrContext).toBeUndefined()
- done()
- }
+ expect(result).toContain(
+ '<div data-server-rendered="true">' +
+ '<p class="hi">yoyo</p> ' +
+ '<div id="ho" class="red"></div> ' +
+ '<span>hi</span> ' +
+ '<input value="hi"> ' +
+ '<img src="https://vuejs.org/images/logo.png"> ' +
+ '<div class="a">test</div> ' +
+ '<span class="b">testAsync</span>' +
+ '</div>'
)
})
})
--- /dev/null
+import Vue from 'vue'
+import Router from '../../src'
+import { components } from '../utils'
+
+import { createRenderer } from 'vue-server-renderer'
+import { RouterOptions } from '../../src/router'
+
+Vue.use(Router)
+
+export const renderer = createRenderer()
+
+export function createRouter(options?: Partial<RouterOptions>) {
+ // TODO: a more complex routing that can be used for most tests
+ return new Router({
+ mode: 'history',
+ routes: [
+ {
+ path: '/',
+ component: components.Home,
+ },
+ {
+ path: '/foo',
+ component: components.Foo,
+ },
+ ],
+ ...options,
+ })
+}
+
+export function createApp(
+ routerOptions?: Partial<RouterOptions>,
+ options?: any
+) {
+ // create router instance
+ const router = createRouter(routerOptions)
+
+ const app = new Vue({
+ // @ts-ignore
+ router,
+ template: `<div>
+ <router-view/>
+ </div>`,
+ ...options,
+ // render: h => h('div', {}, [h('RouterView')]),
+ })
+
+ // return both the app and the router
+ return { app, router }
+}
+
+export function renderApp(
+ context: { url: string },
+ routerOptions?: Partial<RouterOptions>,
+ vueOptions?: any
+) {
+ return new Promise<ReturnType<typeof createApp>['app']>((resolve, reject) => {
+ const { app, router } = createApp(routerOptions, vueOptions)
+
+ // set server-side router's location
+ router.push(context.url).catch(err => {})
+
+ // wait until router has resolved possible async components and hooks
+ // TODO: rename the promise one to isReady
+ router.onReady().then(() => {
+ // const matchedComponents = router.getMatchedComponents()
+ const matchedComponents = router.currentRoute.matched
+ // no matched routes, reject with 404
+ if (!matchedComponents.length) {
+ return reject({ code: 404 })
+ }
+
+ // the Promise should resolve to the app instance so it can be rendered
+ resolve(app)
+ }, reject)
+ })
+}
// true
)
- router.doInitialNavigation()
+ router.doInitialNavigation().catch(() => {})
} else {
// @ts-ignore we are adding this
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
*/
protected markAsReady(err?: any): void {
if (this.ready) return
- for (const [resolve, reject] of this.onReadyCbs) {
- if (err) reject(err)
- else resolve()
+ for (const [resolve] of this.onReadyCbs) {
+ // TODO: is this okay?
+ // always resolve, as the router is ready even if there was an error
+ // @ts-ignore
+ resolve(err)
+ // if (err) reject(err)
+ // else resolve()
}
this.onReadyCbs = []
this.ready = true
try {
await this.navigate(toLocation, this.currentRoute)
} catch (error) {
+ this.markAsReady(error)
if (NavigationGuardRedirect.is(error)) {
// push was called while waiting in guards
if (this.pendingLocation !== toLocation) {
throw new NavigationCancelled(toLocation, this.currentRoute)
}
+ // this throws, so nothing ahead happens
this.triggerError(error)
}
}
// push was called while waiting in guards
if (this.pendingLocation !== toLocation) {
- throw new NavigationCancelled(toLocation, this.currentRoute)
+ const error = new NavigationCancelled(toLocation, this.currentRoute)
+ this.markAsReady(error)
+ throw error
}
// NOTE: here we removed the pushing to history part as the history
// navigation is confirmed, call afterGuards
for (const guard of this.afterGuards) guard(toLocation, from)
+ this.markAsReady()
return this.currentRoute
}