// @vitest-environment node
import { createWebpackBundleRenderer } from './compile-with-webpack'
describe('SSR: bundle renderer', () => {
createAssertions(true)
// createAssertions(false)
})
function createAssertions(runInNewContext) {
it('renderToString', async () => {
const renderer = await createWebpackBundleRenderer('app.js', {
runInNewContext
})
const context: any = { url: '/test' }
const res = await renderer.renderToString(context)
expect(res).toBe('
/test
')
expect(context.msg).toBe('hello')
})
it('renderToStream', async () => {
const renderer = await createWebpackBundleRenderer('app.js', {
runInNewContext
})
const context: any = { url: '/test' }
const res = await new Promise((resolve, reject) => {
const stream = renderer.renderToStream(context)
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('error', reject)
stream.on('end', () => {
resolve(res)
})
})
expect(res).toBe('/test
')
expect(context.msg).toBe('hello')
})
it('renderToString catch error', async () => {
const renderer = await createWebpackBundleRenderer('error.js', {
runInNewContext
})
try {
await renderer.renderToString()
} catch (err: any) {
expect(err.message).toBe('foo')
}
})
it('renderToString catch Promise rejection', async () => {
const renderer = await createWebpackBundleRenderer('promise-rejection.js', {
runInNewContext
})
try {
await renderer.renderToString()
} catch (err: any) {
expect(err.message).toBe('foo')
}
})
it('renderToStream catch error', async () => {
const renderer = await createWebpackBundleRenderer('error.js', {
runInNewContext
})
const err = await new Promise(resolve => {
const stream = renderer.renderToStream()
stream.on('error', resolve)
})
expect(err.message).toBe('foo')
})
it('renderToStream catch Promise rejection', async () => {
const renderer = await createWebpackBundleRenderer('promise-rejection.js', {
runInNewContext
})
const err = await new Promise(resolve => {
const stream = renderer.renderToStream()
stream.on('error', resolve)
})
expect(err.message).toBe('foo')
})
it('render with cache (get/set)', async () => {
const cache = {}
const get = vi.fn()
const set = vi.fn()
const options = {
runInNewContext,
cache: {
// async
get: (key, cb) => {
setTimeout(() => {
get(key)
cb(cache[key])
}, 0)
},
set: (key, val) => {
set(key, val)
cache[key] = val
}
}
}
const renderer = await createWebpackBundleRenderer('cache.js', options)
const expected = '/test
'
const key = 'app::1'
const res = await renderer.renderToString()
expect(res).toBe(expected)
expect(get).toHaveBeenCalledWith(key)
const setArgs = set.mock.calls[0]
expect(setArgs[0]).toBe(key)
expect(setArgs[1].html).toBe(expected)
expect(cache[key].html).toBe(expected)
const res2 = await renderer.renderToString()
expect(res2).toBe(expected)
expect(get.mock.calls.length).toBe(2)
expect(set.mock.calls.length).toBe(1)
})
it('render with cache (get/set/has)', async () => {
const cache = {}
const has = vi.fn()
const get = vi.fn()
const set = vi.fn()
const options = {
runInNewContext,
cache: {
// async
has: (key, cb) => {
has(key)
cb(!!cache[key])
},
// sync
get: key => {
get(key)
return cache[key]
},
set: (key, val) => {
set(key, val)
cache[key] = val
}
}
}
const renderer = await createWebpackBundleRenderer('cache.js', options)
const expected = '/test
'
const key = 'app::1'
const res = await renderer.renderToString()
expect(res).toBe(expected)
expect(has).toHaveBeenCalledWith(key)
expect(get).not.toHaveBeenCalled()
const setArgs = set.mock.calls[0]
expect(setArgs[0]).toBe(key)
expect(setArgs[1].html).toBe(expected)
expect(cache[key].html).toBe(expected)
const res2 = await renderer.renderToString()
expect(res2).toBe(expected)
expect(has.mock.calls.length).toBe(2)
expect(get.mock.calls.length).toBe(1)
expect(set.mock.calls.length).toBe(1)
})
it('render with cache (nested)', async () => {
const cache = new Map() as any
vi.spyOn(cache, 'get')
vi.spyOn(cache, 'set')
const options = {
cache,
runInNewContext
}
const renderer = await createWebpackBundleRenderer(
'nested-cache.js',
options
)
const expected = '/test
'
const key = 'app::1'
const context1 = { registered: [] }
const context2 = { registered: [] }
const res = await renderer.renderToString(context1)
expect(res).toBe(expected)
expect(cache.set.mock.calls.length).toBe(3) // 3 nested components cached
const cached = cache.get(key)
expect(cached.html).toBe(expected)
expect(cache.get.mock.calls.length).toBe(1)
// assert component usage registration for nested children
expect(context1.registered).toEqual(['app', 'child', 'grandchild'])
const res2 = await renderer.renderToString(context2)
expect(res2).toBe(expected)
expect(cache.set.mock.calls.length).toBe(3) // no new cache sets
expect(cache.get.mock.calls.length).toBe(2) // 1 get for root
expect(context2.registered).toEqual(['app', 'child', 'grandchild'])
})
it('render with cache (opt-out)', async () => {
const cache = {}
const get = vi.fn()
const set = vi.fn()
const options = {
runInNewContext,
cache: {
// async
get: (key, cb) => {
setTimeout(() => {
get(key)
cb(cache[key])
}, 0)
},
set: (key, val) => {
set(key, val)
cache[key] = val
}
}
}
const renderer = await createWebpackBundleRenderer(
'cache-opt-out.js',
options
)
const expected = '/test
'
const res = await renderer.renderToString()
expect(res).toBe(expected)
expect(get).not.toHaveBeenCalled()
expect(set).not.toHaveBeenCalled()
const res2 = await renderer.renderToString()
expect(res2).toBe(expected)
expect(get).not.toHaveBeenCalled()
expect(set).not.toHaveBeenCalled()
})
it('renderToString (bundle format with code split)', async () => {
const renderer = await createWebpackBundleRenderer('split.js', {
runInNewContext,
asBundle: true
})
const context = { url: '/test' }
const res = await renderer.renderToString(context)
expect(res).toBe(
'/test
async test.woff2 test.png
'
)
})
it('renderToStream (bundle format with code split)', async () => {
const renderer = await createWebpackBundleRenderer('split.js', {
runInNewContext,
asBundle: true
})
const context = { url: '/test' }
const res = await new Promise((resolve, reject) => {
const stream = renderer.renderToStream(context)
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('error', reject)
stream.on('end', () => {
resolve(res)
})
})
expect(res).toBe(
'/test
async test.woff2 test.png
'
)
})
it('renderToString catch error (bundle format with source map)', async () => {
const renderer = await createWebpackBundleRenderer('error.js', {
runInNewContext,
asBundle: true
})
try {
await renderer.renderToString()
} catch (err: any) {
expect(err.stack).toContain('server-renderer/test/fixtures/error.js:1:0')
expect(err.message).toBe('foo')
}
})
it('renderToStream catch error (bundle format with source map)', async () => {
const renderer = await createWebpackBundleRenderer('error.js', {
runInNewContext,
asBundle: true
})
const err = await new Promise(resolve => {
const stream = renderer.renderToStream()
stream.on('error', resolve)
})
expect(err.stack).toContain('server-renderer/test/fixtures/error.js:1:0')
expect(err.message).toBe('foo')
})
it('renderToString w/ callback', async () => {
const renderer = await createWebpackBundleRenderer('app.js', {
runInNewContext
})
const context: any = { url: '/test' }
const res = await new Promise(r =>
renderer.renderToString(context, (_err, res) => r(res))
)
expect(res).toBe('/test
')
expect(context.msg).toBe('hello')
})
it('renderToString error handling w/ callback', async () => {
const renderer = await createWebpackBundleRenderer('error.js', {
runInNewContext
})
const err = await new Promise(r => renderer.renderToString(r))
expect(err.message).toBe('foo')
})
}