Skip to content

Commit 8ad5af4

Browse files
authored
test: add unit tests for headers (#27)
* chore: use vitest for test runner * chore: remove old unused test * test: add unit tests for headers * fix: remove duplication * fix: typo * test: add temporary test for setCacheTagsHeaders * chore: move tests to be colocated next to code * chore: add vite config * chore: add ci test command * chore: add github workflow to run tests * chore: update github actions to latest versions * chore: tweaking types * fix: fix bug with basePath locales + update test types
1 parent d808251 commit 8ad5af4

File tree

6 files changed

+226
-44
lines changed

6 files changed

+226
-44
lines changed

.github/workflows/run-tests.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: 'Run tests'
2+
on:
3+
pull_request:
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- uses: actions/checkout@v4
11+
- name: 'Install Node'
12+
uses: actions/setup-node@v3
13+
with:
14+
node-version: '18.x'
15+
- name: 'Install dependencies'
16+
run: npm ci
17+
- name: 'Test'
18+
run: npm run test:ci

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"build": "tsc",
1717
"build:debug": "tsc --sourceMap",
1818
"build:watch": "tsc --watch",
19-
"test": "jest"
19+
"test": "vitest",
20+
"test:ci": "vitest run --reporter=default"
2021
},
2122
"repository": {
2223
"type": "git",

src/helpers/headers.test.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { expect, test, describe, vi, afterEach } from 'vitest'
2+
import { setCacheControlHeaders, setVaryHeaders, setCacheTagsHeaders } from './headers.js'
3+
import type { NextConfigComplete } from 'next/dist/server/config-shared.js'
4+
5+
describe('headers', () => {
6+
afterEach(() => {
7+
vi.restoreAllMocks()
8+
})
9+
10+
describe('setVaryHeaders', () => {
11+
const defaultConfig: Partial<NextConfigComplete> = {
12+
basePath: '',
13+
i18n: null,
14+
}
15+
const defaultUrl = 'https://example.com'
16+
17+
describe('should set "netlify-vary" header', () => {
18+
test('with expected default value', () => {
19+
const headers = new Headers()
20+
const request = new Request(defaultUrl)
21+
vi.spyOn(headers, 'set')
22+
23+
setVaryHeaders(headers, request, defaultConfig)
24+
25+
expect(headers.set).toBeCalledWith('netlify-vary', 'cookie=__prerender_bypass|__next_preview_data')
26+
})
27+
28+
test('with expected vary headers', () => {
29+
const givenHeaders = {
30+
'vary': 'Accept, Accept-Language',
31+
}
32+
const headers = new Headers(givenHeaders)
33+
const request = new Request(defaultUrl)
34+
vi.spyOn(headers, 'set')
35+
36+
setVaryHeaders(headers, request, defaultConfig)
37+
38+
expect(headers.set).toBeCalledWith('netlify-vary', 'header=Accept|Accept-Language,cookie=__prerender_bypass|__next_preview_data')
39+
})
40+
41+
test('with no languages if i18n config has localeDetection disabled', () => {
42+
const headers = new Headers()
43+
const request = new Request(defaultUrl)
44+
const config = {
45+
...defaultConfig,
46+
i18n: {
47+
localeDetection: false,
48+
locales: ['en', 'de'],
49+
defaultLocale: 'default',
50+
},
51+
}
52+
vi.spyOn(headers, 'set')
53+
54+
setVaryHeaders(headers, request, config)
55+
56+
expect(headers.set).toBeCalledWith('netlify-vary', 'cookie=__prerender_bypass|__next_preview_data')
57+
})
58+
59+
test('with expected languages if i18n config has locales', () => {
60+
const headers = new Headers()
61+
const request = new Request(defaultUrl)
62+
const config = {
63+
...defaultConfig,
64+
i18n: {
65+
locales: ['en', 'de', 'fr'],
66+
defaultLocale: 'default',
67+
},
68+
}
69+
vi.spyOn(headers, 'set')
70+
71+
setVaryHeaders(headers, request, config)
72+
73+
expect(headers.set).toBeCalledWith('netlify-vary', 'language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE')
74+
})
75+
76+
test('with expected languages if i18n config has locales and basePath matches the current path', () => {
77+
const headers = new Headers()
78+
const request = new Request(`${defaultUrl}/base/path`)
79+
const config = {
80+
...defaultConfig,
81+
basePath: '/base/path',
82+
i18n: {
83+
locales: ['en', 'de', 'fr'],
84+
defaultLocale: 'default',
85+
},
86+
}
87+
vi.spyOn(headers, 'set')
88+
89+
setVaryHeaders(headers, request, config)
90+
91+
expect(headers.set).toBeCalledWith('netlify-vary', 'language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE')
92+
})
93+
})
94+
})
95+
96+
describe('setCacheControlHeaders', () => {
97+
test('should not set any headers if "cache-control" is not set', () => {
98+
const headers = new Headers()
99+
vi.spyOn(headers, 'set')
100+
101+
setCacheControlHeaders(headers)
102+
103+
expect(headers.set).toHaveBeenCalledTimes(0)
104+
})
105+
106+
test('should not set any headers if "cache-control" is set and "cdn-cache-control" is present', () => {
107+
const givenHeaders = {
108+
'cache-control': 'public, max-age=0, must-revalidate',
109+
'cdn-cache-control': 'public, max-age=0, must-revalidate',
110+
}
111+
const headers = new Headers(givenHeaders)
112+
vi.spyOn(headers, 'set')
113+
114+
setCacheControlHeaders(headers)
115+
116+
expect(headers.set).toHaveBeenCalledTimes(0)
117+
})
118+
119+
test('should not set any headers if "cache-control" is set and "netlify-cdn-cache-control" is present', () => {
120+
const givenHeaders = {
121+
'cache-control': 'public, max-age=0, must-revalidate',
122+
'netlify-cdn-cache-control': 'public, max-age=0, must-revalidate',
123+
}
124+
const headers = new Headers(givenHeaders)
125+
vi.spyOn(headers, 'set')
126+
127+
setCacheControlHeaders(headers)
128+
129+
expect(headers.set).toHaveBeenCalledTimes(0)
130+
})
131+
132+
test('should set expected headers if "cache-control" is set and "cdn-cache-control" and "netlify-cdn-cache-control" are not present', () => {
133+
const givenHeaders = {
134+
'cache-control': 'public, max-age=0, must-revalidate',
135+
}
136+
const headers = new Headers(givenHeaders)
137+
vi.spyOn(headers, 'set')
138+
139+
setCacheControlHeaders(headers)
140+
141+
expect(headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'public, max-age=0, must-revalidate')
142+
expect(headers.set).toHaveBeenNthCalledWith(2, 'netlify-cdn-cache-control', 'public, max-age=0, must-revalidate')
143+
})
144+
145+
test('should remove "s-maxage" from "cache-control" header', () => {
146+
const givenHeaders = {
147+
'cache-control': 'public, s-maxage=604800',
148+
}
149+
const headers = new Headers(givenHeaders)
150+
vi.spyOn(headers, 'set')
151+
152+
setCacheControlHeaders(headers)
153+
154+
expect(headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'public')
155+
expect(headers.set).toHaveBeenNthCalledWith(2, 'netlify-cdn-cache-control', 'public, s-maxage=604800')
156+
})
157+
158+
test('should remove "stale-while-revalidate" from "cache-control" header', () => {
159+
const givenHeaders = {
160+
'cache-control': 'max-age=604800, stale-while-revalidate=86400',
161+
}
162+
const headers = new Headers(givenHeaders)
163+
vi.spyOn(headers, 'set')
164+
165+
setCacheControlHeaders(headers)
166+
167+
expect(headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'max-age=604800')
168+
expect(headers.set).toHaveBeenNthCalledWith(2, 'netlify-cdn-cache-control', 'max-age=604800, stale-while-revalidate=86400')
169+
})
170+
171+
test('should set default "cache-control" header if it contains both "s-maxage" and "stale-whie-revalidate"', () => {
172+
const givenHeaders = {
173+
'cache-control': 's-maxage=604800, stale-while-revalidate=86400',
174+
}
175+
const headers = new Headers(givenHeaders)
176+
vi.spyOn(headers, 'set')
177+
178+
setCacheControlHeaders(headers)
179+
180+
expect(headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'public, max-age=0, must-revalidate')
181+
expect(headers.set).toHaveBeenNthCalledWith(2, 'netlify-cdn-cache-control', 's-maxage=604800, stale-while-revalidate=86400')
182+
})
183+
})
184+
185+
describe('setCacheTagsHeaders', () => {
186+
test('TODO: function is not yet implemented', () => {
187+
expect(setCacheTagsHeaders(new Headers())).toBeUndefined()
188+
})
189+
})
190+
})

src/helpers/headers.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const removeHeaderValues = (header: string, values: string[]): string => {
3939
export const setVaryHeaders = (
4040
headers: Headers,
4141
request: Request,
42-
{ basePath, i18n }: NextConfigComplete,
42+
{ basePath, i18n }: Pick<NextConfigComplete, 'basePath' | 'i18n'>,
4343
) => {
4444
const netlifyVaryValues: NetlifyVaryValues = {
4545
headers: [],
@@ -55,12 +55,9 @@ export const setVaryHeaders = (
5555
const path = new URL(request.url).pathname
5656
const locales = i18n && i18n.localeDetection !== false ? i18n.locales : []
5757

58-
if (locales.length > 1) {
59-
const logicalPath = basePath && path.startsWith(basePath) ? path.slice(basePath.length) : path
60-
if (logicalPath === `/`) {
61-
netlifyVaryValues.languages.push(...locales)
62-
netlifyVaryValues.cookies.push(`NEXT_LOCALE`)
63-
}
58+
if (locales.length > 1 && (path === '/' || path === basePath)) {
59+
netlifyVaryValues.languages.push(...locales)
60+
netlifyVaryValues.cookies.push(`NEXT_LOCALE`)
6461
}
6562

6663
headers.set(`netlify-vary`, generateNetlifyVaryValues(netlifyVaryValues))

tests/unit/index.test.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

vite.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineConfig } from 'vitest/config'
2+
3+
export default defineConfig({
4+
root: '.',
5+
test: {
6+
restoreMocks: true,
7+
clearMocks: true,
8+
mockReset: true,
9+
environment: 'node',
10+
testTimeout: 100000,
11+
},
12+
})

0 commit comments

Comments
 (0)