Skip to content

Commit 702857d

Browse files
author
Jason Gao
committed
add tests for hooks and use heads hook for postRenderHeads
1 parent 3c64203 commit 702857d

File tree

5 files changed

+109
-12
lines changed

5 files changed

+109
-12
lines changed

packages/fastify-renderer/src/node/DocumentTemplate.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export interface TemplateData<Props> {
66
tail: NodeJS.ReadableStream
77
content: string | NodeJS.ReadableStream
88
props: Props
9-
postRenderHead: NodeJS.ReadableStream
109
}
1110

1211
/** A template renders out a full HTML document given the content for the document and the scripts for the document, and can optionally grab values out of the props to use for other bits like the page title, metatags, or non-client-side-hydrated body content. */
@@ -20,7 +19,6 @@ export const DefaultDocumentTemplate: Template = (data: TemplateData<any>) => te
2019
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
2120
<title>${data.props.title || 'Fastify Renderer App'}</title>
2221
${data.head}
23-
${data.postRenderHead}
2422
</head>
2523
<body>
2624
<div id="fstrapp">${data.content}</div>

packages/fastify-renderer/src/node/renderers/react/ReactRenderer.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export class ReactRenderer implements Renderer {
8282

8383
try {
8484
const url = this.entrypointRequirePathForServer(render)
85+
8586
// we load all the context needed for this render from one `loadModule` call, which is really important for keeping the same copy of React around in all of the different bits that touch it.
8687
const { React, ReactDOMServer, Router, RenderBusContext, Layout, Entrypoint } = (await this.loadModule(url))
8788
.default
@@ -204,8 +205,9 @@ export class ReactRenderer implements Renderer {
204205

205206
private renderStreamingTemplate<Props>(app: JSX.Element, bus: RenderBus, ReactDOMServer: any, render: Render<Props>) {
206207
this.runHeadHooks(bus)
207-
// Do nothing for now when using streaming template
208-
bus.push('postRenderHead', null)
208+
// There are not postRenderHead hooks for streaming templates
209+
// so let's end the head stack
210+
bus.push('head', null)
209211
const contentStream = ReactDOMServer.renderToNodeStream(app)
210212
contentStream.on('end', () => {
211213
this.runTailHooks(bus)
@@ -216,7 +218,6 @@ export class ReactRenderer implements Renderer {
216218
head: bus.stack('head'),
217219
tail: bus.stack('tail'),
218220
props: render.props,
219-
postRenderHead: bus.stack('postRenderHead'),
220221
})
221222
}
222223

@@ -236,18 +237,17 @@ export class ReactRenderer implements Renderer {
236237
head: bus.stack('head'),
237238
tail: bus.stack('tail'),
238239
props: render.props,
239-
postRenderHead: bus.stack('postRenderHead'),
240240
})
241241
}
242242

243243
private runPostRenderHeadHooks(bus: RenderBus) {
244244
// Run any heads hooks that might want to push something after the content
245245
for (const hook of this.plugin.hooks) {
246246
if (hook.postRenderHeads) {
247-
bus.push('postRenderHead', hook.postRenderHeads())
247+
bus.push('head', hook.postRenderHeads())
248248
}
249249
}
250-
bus.push('postRenderHead', null)
250+
bus.push('head', null)
251251
}
252252

253253
private runHeadHooks(bus: RenderBus) {
@@ -257,7 +257,6 @@ export class ReactRenderer implements Renderer {
257257
bus.push('head', hook.heads())
258258
}
259259
}
260-
bus.push('head', null)
261260
}
262261

263262
private runTailHooks(bus: RenderBus) {

packages/fastify-renderer/test/helpers.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import fastify, { FastifyServerOptions } from 'fastify'
22
import fastifyAccepts from 'fastify-accepts'
33
import Middie from 'middie'
4+
import path from 'path'
5+
import { Readable } from 'stream'
46
import { FastifyRendererOptions, FastifyRendererPlugin } from '../src/node/Plugin'
57
import { RenderBus } from '../src/node/RenderBus'
68
import { ReactRenderer, ReactRendererOptions } from '../src/node/renderers/react/ReactRenderer'
9+
import { Render } from '../src/node/renderers/Renderer'
710

811
const logLevel = process.env.LOG_LEVEL || 'error'
912

@@ -26,3 +29,22 @@ export const newReactRenderer = (options?: ReactRendererOptions): ReactRenderer
2629
const plugin = newFastifyRendererPlugin({ renderer: options })
2730
return plugin.renderer as ReactRenderer
2831
}
32+
33+
export const getMockRender = <T>(props: T): Render<T> => {
34+
return {
35+
props,
36+
renderable: path.resolve(__dirname, 'fixtures', 'test-module.tsx'),
37+
url: 'test-url',
38+
layout: path.resolve(__dirname, 'fixtures', 'test-layout.tsx'),
39+
base: '',
40+
document: (data) => Readable.from(''),
41+
request: {
42+
url: 'test-url',
43+
} as any,
44+
reply: {
45+
send: (payload: unknown) => {
46+
throw new Error('Send is not implemented')
47+
},
48+
} as any,
49+
}
50+
}

packages/fastify-renderer/test/renderers/ReactRenderer.spec.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
// import { ReactRenderer } from "../../src/node/renderers/react/ReactRenderer";
1+
import path from 'path'
2+
import React from 'react'
23
import { DefaultDocumentTemplate } from '../../src/node/DocumentTemplate'
34
import { RenderableRoute } from '../../src/node/renderers/Renderer'
4-
import { newReactRenderer } from '../helpers'
5+
import { getMockRender, newReactRenderer, newRenderBus } from '../helpers'
6+
7+
const testLayoutComponent = require.resolve(path.join(__dirname, '..', 'fixtures', 'test-layout.tsx'))
58

69
describe('ReactRenderer', () => {
710
test('should create an instance and initialize the client module path', async () => {
@@ -45,6 +48,37 @@ describe('ReactRenderer', () => {
4548
test.skip('should throw on rendering failure', async () => {
4649
return
4750
})
51+
52+
test('should call postRenderHooks after dom render', async () => {
53+
const renderer = newReactRenderer()
54+
const callOrder: string[] = []
55+
renderer.plugin.hooks = [
56+
{
57+
heads: () => {
58+
callOrder.push('heads')
59+
return 'heads'
60+
},
61+
postRenderHeads: () => {
62+
callOrder.push('postRenderHeads')
63+
return 'postRenderHeads'
64+
},
65+
},
66+
]
67+
68+
renderer['renderSynchronousTemplate'](
69+
React.createElement(testLayoutComponent, {}),
70+
newRenderBus(),
71+
{
72+
renderToString: () => {
73+
callOrder.push('render')
74+
return 'test'
75+
},
76+
},
77+
getMockRender({})
78+
)
79+
80+
expect(callOrder).toEqual(['heads', 'render', 'postRenderHeads'])
81+
})
4882
})
4983

5084
describe('buildVirtualClientEntrypointModuleID()', () => {

packages/fastify-renderer/test/routes.spec.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,27 @@ import { newFastify } from './helpers'
44

55
const testComponent = require.resolve(path.join(__dirname, 'fixtures', 'test-module.tsx'))
66
const testLayoutComponent = require.resolve(path.join(__dirname, 'fixtures', 'test-layout.tsx'))
7-
const options = { vite: { root: __dirname, logLevel: (process.env.LOG_LEVEL ?? 'info') as any } }
7+
const options = {
8+
vite: { root: __dirname, logLevel: (process.env.LOG_LEVEL ?? 'info') as any },
9+
devMode: true,
10+
renderer: {
11+
mode: 'sync' as const,
12+
type: 'react' as const,
13+
},
14+
hooks: [
15+
{
16+
heads: () => {
17+
return 'head'
18+
},
19+
transform: (app) => {
20+
return app
21+
},
22+
postRenderHeads: () => {
23+
return 'postRenderHead'
24+
},
25+
},
26+
],
27+
}
828

929
describe('FastifyRenderer', () => {
1030
let server
@@ -73,4 +93,28 @@ describe('FastifyRenderer', () => {
7393
expect(response.statusCode).toEqual(201)
7494
expect(response.body).toEqual('hello')
7595
})
96+
97+
test('should call hooks in correct order', async () => {
98+
const callOrder: string[] = []
99+
jest.spyOn(options.hooks[0], 'heads').mockImplementation(() => {
100+
callOrder.push('heads')
101+
return 'head'
102+
})
103+
jest.spyOn(options.hooks[0], 'transform').mockImplementation((app) => {
104+
callOrder.push('transforms')
105+
return app
106+
})
107+
jest.spyOn(options.hooks[0], 'postRenderHeads').mockImplementation(() => {
108+
callOrder.push('postRenders')
109+
return 'postRender'
110+
})
111+
112+
await server.inject({
113+
method: 'GET',
114+
url: '/render-test',
115+
headers: { Accept: 'text/html' },
116+
})
117+
118+
expect(callOrder).toEqual(['transforms', 'heads', 'postRenders'])
119+
})
76120
})

0 commit comments

Comments
 (0)