Skip to content

Commit

Permalink
test: add test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
damassi committed Nov 24, 2024
1 parent ed0585c commit 50ae199
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 15 deletions.
127 changes: 127 additions & 0 deletions src/System/Router/Utils/__tests__/renderToStream.jest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { flushPromiseQueue } from "DevTools/flushPromiseQueue"
import { renderToPipeableStream } from "react-dom/server"
import { ArtsyResponse } from "Server/middleware/artsyExpress"
import { ServerStyleSheet } from "styled-components"
import { renderToStream } from "System/Router/Utils/renderToStream"

jest.mock("react-dom/server", () => ({
renderToPipeableStream: jest.fn(),
}))

describe("renderToStream", () => {
const mockRenderToPipeableStream = renderToPipeableStream as jest.Mock

const res = ({
statusCode: 0,
setHeader: jest.fn(),
} as unknown) as ArtsyResponse

const sheet = ({
_emitSheetCSS: jest.fn(() => "mock-css"),
instance: {
clearTag: jest.fn(),
},
} as unknown) as ServerStyleSheet

afterEach(() => {
jest.clearAllMocks()
})

it("should set the response status and content type on shell ready", async () => {
const mockPipe = jest.fn()
const mockAbort = jest.fn()

mockRenderToPipeableStream.mockImplementation((_, options) => {
setTimeout(() => {
options.onShellReady()
})

return { pipe: mockPipe, abort: mockAbort }
})

const jsx = <div>Hello World</div>
renderToStream({ jsx, sheet, res })

await flushPromiseQueue()

expect(res.statusCode).toBe(200)
expect(res.setHeader).toHaveBeenCalledWith(
"Content-Type",
"text/html; charset=utf-8"
)
expect(mockPipe).toHaveBeenCalled()
})

it("should handle onError and set didError to true", async () => {
const mockPipe = jest.fn()
const mockAbort = jest.fn()

mockRenderToPipeableStream.mockImplementation((_, options) => {
options.onError(new Error("Test error"))

setTimeout(() => {
options.onShellReady()
})

return { pipe: mockPipe, abort: mockAbort }
})

const jsx = <div>Hello Error</div>
renderToStream({ jsx, sheet, res })

await flushPromiseQueue()

expect(res.statusCode).toBe(500)
expect(mockPipe).toHaveBeenCalled()
})

it("should call abort if STREAM_TIMEOUT is reached", () => {
jest.useFakeTimers()
const mockAbort = jest.fn()

mockRenderToPipeableStream.mockImplementation((_, options) => {
return { pipe: jest.fn(), abort: mockAbort }
})

const jsx = <div>Timeout Test</div>
renderToStream({ jsx, sheet, res })

jest.advanceTimersByTime(5000)

expect(mockAbort).toHaveBeenCalled()
jest.useRealTimers()
})

// eslint-disable-next-line jest/no-done-callback
it("should transform the stream and inject CSS into the HTML", done => {
const mockPipe = jest.fn()
const mockAbort = jest.fn()

mockRenderToPipeableStream.mockImplementation((_, options) => {
setTimeout(() => {
options.onShellReady()
})
return { pipe: mockPipe, abort: mockAbort }
})

const jsx = <div>Stream Test</div>
const stream = renderToStream({ jsx, sheet, res })

stream.write("<html><head></head><body></body></html>")

const chunks: string[] = []
stream.on("data", chunk => {
chunks.push(chunk)
})

stream.on("end", () => {
const result = chunks.join("")
expect(result).toContain(
"<html><head>mock-css</head><body></body></html>"
)
done()
})

stream.end()
})
})
7 changes: 2 additions & 5 deletions src/System/Router/Utils/collectAssets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ENABLE_SSR_STREAMING } from "Server/config"
import { renderToStream } from "System/Router/Utils/renderToStream"
import { ArtsyResponse } from "Server/middleware/artsyExpress"
import { serializeRelayHydrationData } from "System/Router/Utils/serializeRelayHydrationData"
import { Transform } from "stream"

const STATS = "loadable-stats.json"

Expand Down Expand Up @@ -59,7 +60,7 @@ export const collectAssets = async ({
let styleTags

if (ENABLE_SSR_STREAMING) {
stream = renderToStream(jsx, sheet, res)
stream = renderToStream({ jsx, sheet, res })
} else {
html = renderToString(jsx)
styleTags = sheet.getStyleTags()
Expand All @@ -79,10 +80,6 @@ export const collectAssets = async ({
bundleScriptTags
.split("\n")
.map(script => {
/**
* In production, prefix injected script src with CDN endpoint.
* @see https://github.com/artsy/force/blob/main/src/lib/middleware/asset.ts#L23
*/
if (getENV("CDN_URL")) {
const scriptTagWithCDN = script.replace(
/src="\/assets/g,
Expand Down
18 changes: 15 additions & 3 deletions src/System/Router/Utils/renderToStream.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { ReactNode } from "react"
import { renderToPipeableStream } from "react-dom/server"
import { ArtsyResponse } from "Server/middleware/artsyExpress"
import { Transform } from "stream"
import { ServerStyleSheet } from "styled-components"

const STREAM_TIMEOUT = 5000

export function renderToStream(jsx, sheet, res: ArtsyResponse) {
interface RenderToStreamProps {
jsx: ReactNode
sheet: ServerStyleSheet
res: ArtsyResponse
}

export const renderToStream = ({
jsx,
sheet,
res,
}: RenderToStreamProps): Transform => {
let didError = false

const decoder = new TextDecoder("utf-8")
Expand Down Expand Up @@ -49,11 +61,11 @@ export function renderToStream(jsx, sheet, res: ArtsyResponse) {
const { pipe, abort } = renderToPipeableStream(jsx, {
onError: error => {
didError = true
console.error("error", error)
console.error("[renderToStream] onError:", error)
},
onShellError: error => {
didError = true
console.log("shell error", error)
console.error("[renderToStream] onShellError:", error)
},
onShellReady: () => {
res.statusCode = didError ? 500 : 200
Expand Down
9 changes: 2 additions & 7 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ initializeMiddleware(app)

const { routes, routePaths } = getRoutes()

/**
* Mount routes that will connect to global SSR router
*/
// React app routes
app.get(
routePaths,
async (req: ArtsyRequest, res: ArtsyResponse, next: NextFunction) => {
Expand All @@ -51,10 +49,7 @@ app.get(
}
)

/**
* Mount server-side Express routes
*/

// Common express routes
app
.use(appPreferencesServerRoutes)
.use(cookieConsentManagerServerRoutes)
Expand Down

0 comments on commit 50ae199

Please sign in to comment.