Skip to content

Commit a8ea2bd

Browse files
committed
chore: update tests for caching and localizing routes
1 parent 0e61a26 commit a8ea2bd

File tree

2 files changed

+136
-53
lines changed

2 files changed

+136
-53
lines changed
Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,82 @@
1-
import { removeTrailingSlash, ensureLocalePrefix } from './handlerUtils'
1+
import { normalizeRoute, unlocalizeRoute, localizeRoute, localizeDataRoute } from './handlerUtils'
22

3-
describe('removeTrailingSlash', () => {
4-
it('removes a trailing slash from a string', () => {
5-
expect(removeTrailingSlash('/foo/')).toEqual('/foo')
3+
describe('normalizeRoute', () => {
4+
it('removes a trailing slash from a route', () => {
5+
expect(normalizeRoute('/foo/')).toEqual('/foo')
66
})
77
it('ignores a string without a trailing slash', () => {
8-
expect(removeTrailingSlash('/foo')).toEqual('/foo')
8+
expect(normalizeRoute('/foo')).toEqual('/foo')
99
})
10-
it('does not remove a slash on its own', () => {
11-
expect(removeTrailingSlash('/')).toEqual('/')
10+
it('does not remove a lone slash', () => {
11+
expect(normalizeRoute('/')).toEqual('/')
1212
})
1313
})
1414

15-
describe('ensureLocalePrefix', () => {
16-
it('adds default locale prefix if missing', () => {
15+
describe('unlocalizeRoute', () => {
16+
it('removes the locale prefix from an i18n route', () => {
1717
expect(
18-
ensureLocalePrefix('/foo', {
18+
unlocalizeRoute('/fr/foo', {
19+
defaultLocale: 'en',
20+
locales: ['en', 'fr', 'de'],
21+
}),
22+
).toEqual('/foo')
23+
})
24+
it('removes the locale prefix from a root i18n route', () => {
25+
expect(
26+
unlocalizeRoute('/de', {
27+
defaultLocale: 'en',
28+
locales: ['en', 'fr', 'de'],
29+
}),
30+
).toEqual('/')
31+
})
32+
it('does not modify a default locale route', () => {
33+
expect(
34+
unlocalizeRoute('/foo', {
35+
defaultLocale: 'en',
36+
locales: ['en', 'fr', 'de'],
37+
}),
38+
).toEqual('/foo')
39+
})
40+
})
41+
42+
describe('localizeRoute', () => {
43+
it('adds the locale prefix to an i18n route', () => {
44+
expect(
45+
localizeRoute('/foo', {
1946
defaultLocale: 'en',
2047
locales: ['en', 'fr', 'de'],
2148
}),
2249
).toEqual('/en/foo')
2350
})
24-
it('skips prefixing if locale is present', () => {
51+
it('adds the locale prefix to a root i18n route', () => {
2552
expect(
26-
ensureLocalePrefix('/fr/foo', {
53+
localizeRoute('/', {
2754
defaultLocale: 'en',
2855
locales: ['en', 'fr', 'de'],
2956
}),
30-
).toEqual('/fr/foo')
57+
).toEqual('/en')
58+
})
59+
it('does not modify a prefixed i18n route', () => {
3160
expect(
32-
ensureLocalePrefix('/en/foo', {
61+
localizeRoute('/en/foo', {
3362
defaultLocale: 'en',
3463
locales: ['en', 'fr', 'de'],
3564
}),
3665
).toEqual('/en/foo')
3766
})
38-
it('skips localization if i18n not configured', () => {
39-
expect(ensureLocalePrefix('/foo')).toEqual('/foo')
67+
})
68+
69+
describe('localizeDataRoute', () => {
70+
it('adds the locale prefix to a data route', () => {
71+
expect(localizeDataRoute('/_next/data/build/foo.json', '/en/foo')).toEqual('/_next/data/build/en/foo.json')
72+
})
73+
it('removes the index suffix from a root route', () => {
74+
expect(localizeDataRoute('/_next/data/build/index.json', '/en')).toEqual('/_next/data/build/en.json')
75+
})
76+
it('does not add the locale prefix if it already exists in the data route', () => {
77+
expect(localizeDataRoute('/_next/data/build/en/foo.json', '/en/foo')).toEqual('/_next/data/build/en/foo.json')
78+
})
79+
it('does not modify an RSC data route', () => {
80+
expect(localizeDataRoute('/foo.rsc', '/foo')).toEqual('/foo.rsc')
4081
})
4182
})
Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
/* eslint-disable max-lines-per-function */
12
import { mockRequest } from 'next/dist/server/lib/mock-request'
3+
import { Options } from 'next/dist/server/next-server'
24

35
import { getNextServer, NextServerType, netlifyApiFetch } from './handlerUtils'
4-
import { NetlifyNextServer } from './server'
6+
import { NetlifyNextServer, NetlifyConfig } from './server'
57

68
jest.mock('./handlerUtils', () => {
79
const originalModule = jest.requireActual('./handlerUtils')
@@ -14,15 +16,35 @@ jest.mock('./handlerUtils', () => {
1416
})
1517
const mockedApiFetch = netlifyApiFetch as jest.MockedFunction<typeof netlifyApiFetch>
1618

19+
const mocki18nConfig = {
20+
i18n: {
21+
defaultLocale: 'en',
22+
locales: ['en', 'fr', 'de'],
23+
},
24+
}
25+
26+
const mockTokenConfig = {
27+
revalidateToken: 'test',
28+
}
29+
30+
const mockBuildId = 'build-id'
31+
1732
jest.mock(
1833
'prerender-manifest.json',
1934
() => ({
2035
routes: {
21-
'/en/getStaticProps/with-revalidate': {
22-
dataRoute: '/_next/data/en/getStaticProps/with-revalidate.json',
36+
'/non-i18n/with-revalidate': {
37+
dataRoute: `/_next/data/${mockBuildId}/non-i18n/with-revalidate.json`,
38+
},
39+
'/en/i18n/with-revalidate': {
40+
dataRoute: `/_next/data/${mockBuildId}/i18n/with-revalidate.json`,
2341
},
2442
},
2543
dynamicRoutes: {
44+
'/posts/[title]': {
45+
routeRegex: '^/posts/([^/]+?)(?:/)?$',
46+
dataRoute: `/_next/data/${mockBuildId}/posts/[title].json`,
47+
},
2648
'/blog/[author]/[slug]': {
2749
routeRegex: '^/blog/([^/]+?)/([^/]+?)(?:/)?$',
2850
dataRoute: '/blog/[author]/[slug].rsc',
@@ -36,41 +58,52 @@ beforeAll(() => {
3658
const NextServer: NextServerType = getNextServer()
3759
jest.spyOn(NextServer.prototype, 'getRequestHandler').mockImplementation(() => () => Promise.resolve())
3860

39-
const MockNetlifyNextServerConstructor = function () {
61+
const MockNetlifyNextServerConstructor = function (nextOptions: Options, netlifyConfig: NetlifyConfig) {
4062
this.distDir = '.'
41-
this.buildId = 'build-id'
42-
this.nextConfig = {
43-
i18n: {
44-
defaultLocale: 'en',
45-
locales: ['en', 'fr', 'de'],
46-
},
47-
}
63+
this.buildId = mockBuildId
64+
this.nextConfig = nextOptions.conf
65+
this.netlifyConfig = netlifyConfig
4866
}
4967
Object.setPrototypeOf(NetlifyNextServer, MockNetlifyNextServerConstructor)
5068
})
5169

5270
describe('the netlify next server', () => {
53-
it('revalidates a request containing an `x-prerender-revalidate` header', async () => {
54-
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, {})
71+
it('does not revalidate a request without an `x-prerender-revalidate` header', async () => {
72+
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, { ...mockTokenConfig })
73+
const requestHandler = netlifyNextServer.getRequestHandler()
74+
75+
const { req: mockReq, res: mockRes } = mockRequest('/getStaticProps/with-revalidate/', {}, 'GET')
76+
await requestHandler(mockReq, mockRes)
77+
78+
expect(mockedApiFetch).not.toHaveBeenCalled()
79+
})
80+
81+
it('revalidates a static non-i18n route with an `x-prerender-revalidate` header', async () => {
82+
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, { ...mockTokenConfig })
5583
const requestHandler = netlifyNextServer.getRequestHandler()
5684

5785
const { req: mockReq, res: mockRes } = mockRequest(
58-
'/getStaticProps/with-revalidate/',
86+
'/non-i18n/with-revalidate/',
5987
{ 'x-prerender-revalidate': 'test' },
6088
'GET',
6189
)
62-
const response = await requestHandler(mockReq, mockRes)
90+
await requestHandler(mockReq, mockRes)
6391

64-
expect(mockedApiFetch).toHaveBeenCalled()
65-
expect(response).toBe(undefined)
92+
expect(mockedApiFetch).toHaveBeenCalledWith(
93+
expect.objectContaining({
94+
payload: expect.objectContaining({
95+
paths: ['/non-i18n/with-revalidate/', `/_next/data/${mockBuildId}/non-i18n/with-revalidate.json`],
96+
}),
97+
}),
98+
)
6699
})
67100

68-
it('matches a normalized static route to find the data route', async () => {
69-
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, {})
101+
it('revalidates a static i18n route with an `x-prerender-revalidate` header', async () => {
102+
const netlifyNextServer = new NetlifyNextServer({ conf: { ...mocki18nConfig } }, { ...mockTokenConfig })
70103
const requestHandler = netlifyNextServer.getRequestHandler()
71104

72105
const { req: mockReq, res: mockRes } = mockRequest(
73-
'/getStaticProps/with-revalidate/',
106+
'/i18n/with-revalidate/',
74107
{ 'x-prerender-revalidate': 'test' },
75108
'GET',
76109
)
@@ -79,14 +112,14 @@ describe('the netlify next server', () => {
79112
expect(mockedApiFetch).toHaveBeenCalledWith(
80113
expect.objectContaining({
81114
payload: expect.objectContaining({
82-
paths: ['/getStaticProps/with-revalidate/', '/_next/data/build-id/en/getStaticProps/with-revalidate.json'],
115+
paths: ['/i18n/with-revalidate/', `/_next/data/${mockBuildId}/en/i18n/with-revalidate.json`],
83116
}),
84117
}),
85118
)
86119
})
87120

88-
it('matches a normalized dynamic route to find the data', async () => {
89-
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, {})
121+
it('revalidates a dynamic non-i18n route with an `x-prerender-revalidate` header', async () => {
122+
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, { ...mockTokenConfig })
90123
const requestHandler = netlifyNextServer.getRequestHandler()
91124

92125
const { req: mockReq, res: mockRes } = mockRequest('/blog/rob/hello', { 'x-prerender-revalidate': 'test' }, 'GET')
@@ -101,8 +134,24 @@ describe('the netlify next server', () => {
101134
)
102135
})
103136

137+
it('revalidates a dynamic i18n route with an `x-prerender-revalidate` header', async () => {
138+
const netlifyNextServer = new NetlifyNextServer({ conf: { ...mocki18nConfig } }, { ...mockTokenConfig })
139+
const requestHandler = netlifyNextServer.getRequestHandler()
140+
141+
const { req: mockReq, res: mockRes } = mockRequest('/fr/posts/hello', { 'x-prerender-revalidate': 'test' }, 'GET')
142+
await requestHandler(mockReq, mockRes)
143+
144+
expect(mockedApiFetch).toHaveBeenCalledWith(
145+
expect.objectContaining({
146+
payload: expect.objectContaining({
147+
paths: ['/fr/posts/hello', `/_next/data/${mockBuildId}/fr/posts/hello.json`],
148+
}),
149+
}),
150+
)
151+
})
152+
104153
it('throws an error when route is not found in the manifest', async () => {
105-
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, {})
154+
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, mockTokenConfig)
106155
const requestHandler = netlifyNextServer.getRequestHandler()
107156

108157
const { req: mockReq, res: mockRes } = mockRequest(
@@ -111,34 +160,27 @@ describe('the netlify next server', () => {
111160
'GET',
112161
)
113162

114-
await expect(requestHandler(mockReq, mockRes)).rejects.toThrow('could not find a route')
163+
await expect(requestHandler(mockReq, mockRes)).rejects.toThrow('not an ISR route')
115164
})
116165

117166
it('throws an error when paths are not found by the API', async () => {
118-
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, {})
167+
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, mockTokenConfig)
119168
const requestHandler = netlifyNextServer.getRequestHandler()
120169

121-
const { req: mockReq, res: mockRes } = mockRequest(
122-
'/getStaticProps/with-revalidate/',
123-
{ 'x-prerender-revalidate': 'test' },
124-
'GET',
125-
)
170+
const { req: mockReq, res: mockRes } = mockRequest('/posts/hello/', { 'x-prerender-revalidate': 'test' }, 'GET')
126171

127172
mockedApiFetch.mockResolvedValueOnce({ code: 500, message: 'Failed to revalidate' })
128173
await expect(requestHandler(mockReq, mockRes)).rejects.toThrow('Failed to revalidate')
129174
})
130175

131176
it('throws an error when the revalidate API is unreachable', async () => {
132-
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, {})
177+
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, mockTokenConfig)
133178
const requestHandler = netlifyNextServer.getRequestHandler()
134179

135-
const { req: mockReq, res: mockRes } = mockRequest(
136-
'/getStaticProps/with-revalidate/',
137-
{ 'x-prerender-revalidate': 'test' },
138-
'GET',
139-
)
180+
const { req: mockReq, res: mockRes } = mockRequest('/posts/hello', { 'x-prerender-revalidate': 'test' }, 'GET')
140181

141182
mockedApiFetch.mockRejectedValueOnce(new Error('Unable to connect'))
142183
await expect(requestHandler(mockReq, mockRes)).rejects.toThrow('Unable to connect')
143184
})
144185
})
186+
/* eslint-enable max-lines-per-function */

0 commit comments

Comments
 (0)