first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
// this file must not use the edge-runtime env setup
// so that we test node runtime properly
import { expectTypeOf } from 'expect-type'
import { NextRequest } from 'next/dist/server/web/spec-extension/request'
it('should have 1 required parameter for constructor', () => {
expect(NextRequest.length).toBe(1)
})
it('should allow the 2nd parameter to be undefined', () => {
const request = new NextRequest('https://vercel.com')
expectTypeOf(request).toMatchTypeOf<NextRequest>()
expect(new NextRequest('https://vercel.com')).toHaveProperty(
'nextUrl.pathname',
'/'
)
})
it('should clone Request with headers', () => {
const request = new Request('https://example.com', {
headers: { 'x-foo': 'bar' },
})
const nextRequest = new NextRequest(request)
expect(Object.fromEntries(nextRequest.headers)).toEqual(
Object.fromEntries(request.headers)
)
// Second argument should override headers
const headers = new Headers({ 'x-header': 'some header' })
const nextRequest2 = new NextRequest(request, { headers })
expect(Object.fromEntries(nextRequest2.headers)).toEqual(
Object.fromEntries(headers)
)
})
it('should handle Request with body', () => {
let nextRequest = new NextRequest('https://example.com', {
body: new ReadableStream(),
method: 'POST',
})
expect(nextRequest.body).toBeTruthy()
expect(nextRequest.method).toBe('POST')
const request = new Request('https://example.com', {
body: new ReadableStream(),
method: 'POST',
// @ts-expect-error this exists but not in type
duplex: 'half',
})
nextRequest = new NextRequest(request)
expect(nextRequest.body).toBeTruthy()
expect(nextRequest.method).toBe('POST')
})

View File

@@ -0,0 +1,61 @@
/**
* @jest-environment @edge-runtime/jest-environment
*/
import { expectTypeOf } from 'expect-type'
import { NextRequest } from 'next/dist/server/web/spec-extension/request'
it('should have 1 required parameter for constructor', () => {
expect(NextRequest.length).toBe(1)
})
it('should allow the 2nd parameter to be undefined', () => {
const request = new NextRequest('https://vercel.com')
expectTypeOf(request).toMatchTypeOf<NextRequest>()
expect(new NextRequest('https://vercel.com')).toHaveProperty(
'nextUrl.pathname',
'/'
)
})
it('should clone Request with headers', () => {
const request = new Request('https://example.com', {
headers: { 'x-foo': 'bar' },
})
const nextRequest = new NextRequest(request)
expect(Object.fromEntries(nextRequest.headers)).toEqual(
Object.fromEntries(request.headers)
)
// Second argument should override headers
const headers = new Headers({ 'x-header': 'some header' })
const nextRequest2 = new NextRequest(request, { headers })
expect(Object.fromEntries(nextRequest2.headers)).toEqual(
Object.fromEntries(headers)
)
})
it('should handle Request with body', () => {
let nextRequest = new NextRequest('https://example.com', {
body: new ReadableStream(),
method: 'POST',
})
expect(nextRequest.body).toBeTruthy()
expect(nextRequest.method).toBe('POST')
const request = new Request('https://example.com', {
body: new ReadableStream(),
method: 'POST',
// @ts-expect-error this exists but not in type
duplex: 'half',
})
nextRequest = new NextRequest(request)
expect(nextRequest.body).toBeTruthy()
expect(nextRequest.method).toBe('POST')
})

View File

@@ -0,0 +1,121 @@
/**
* @jest-environment @edge-runtime/jest-environment
*/
import { NextResponse } from 'next/dist/server/web/spec-extension/response'
it('reflect .set into `set-cookie`', async () => {
const response = new NextResponse()
expect(response.cookies.get('foo')?.value).toBe(undefined)
expect(response.cookies.get('foo')).toEqual(undefined)
response.cookies
.set('foo', 'bar', { path: '/test' })
.set('fooz', 'barz', { path: '/test2' })
expect(response.cookies.get('foo')?.value).toBe('bar')
expect(response.cookies.get('fooz')?.value).toBe('barz')
expect(response.cookies.get('foo')).toEqual({
name: 'foo',
path: '/test',
value: 'bar',
})
expect(response.cookies.get('fooz')).toEqual({
name: 'fooz',
path: '/test2',
value: 'barz',
})
expect(response.headers.get('set-cookie')).toBe(
'foo=bar; Path=/test, fooz=barz; Path=/test2'
)
expect(
Array.from(response.headers.entries()).filter((entry) => {
return entry[0] === 'set-cookie'
})
).toEqual([
['set-cookie', 'foo=bar; Path=/test'],
['set-cookie', 'fooz=barz; Path=/test2'],
])
})
it('reflect .delete into `set-cookie`', async () => {
const { NextResponse } = await import(
'next/dist/server/web/spec-extension/response'
)
const response = new NextResponse()
response.cookies.set('foo', 'bar')
expect(Object.fromEntries(response.headers.entries())['set-cookie']).toBe(
'foo=bar; Path=/'
)
expect(response.cookies.get('foo')?.value).toBe('bar')
expect(response.cookies.get('foo')).toEqual({
name: 'foo',
path: '/',
value: 'bar',
})
response.cookies.set('fooz', 'barz')
expect(response.headers.get('set-cookie')).toBe(
'foo=bar; Path=/, fooz=barz; Path=/'
)
expect(
Array.from(response.headers.entries()).filter((entry) => {
return entry[0] === 'set-cookie'
})
).toEqual([
['set-cookie', 'foo=bar; Path=/'],
['set-cookie', 'fooz=barz; Path=/'],
])
expect(response.cookies.get('fooz')?.value).toBe('barz')
expect(response.cookies.get('fooz')).toEqual({
name: 'fooz',
path: '/',
value: 'barz',
})
response.cookies.delete('foo')
expect(response.headers.get('set-cookie')).toBe(
'foo=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT, fooz=barz; Path=/'
)
expect(response.cookies.get('foo')?.value).toBe('')
expect(response.cookies.get('foo')).toEqual({
expires: new Date(0),
name: 'foo',
value: '',
path: '/',
})
response.cookies.delete('fooz')
expect(response.headers.get('set-cookie')).toBe(
'foo=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT, fooz=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT'
)
expect(response.cookies.get('fooz')?.value).toBe('')
expect(response.cookies.get('fooz')).toEqual({
expires: new Date(0),
name: 'fooz',
value: '',
path: '/',
})
})
it('response.cookie does not modify options', async () => {
const { NextResponse } = await import(
'next/dist/server/web/spec-extension/response'
)
const options = { maxAge: 10000 }
const response = new NextResponse(null, {
headers: { 'content-type': 'application/json' },
})
response.cookies.set('cookieName', 'cookieValue', options)
expect(options).toEqual({ maxAge: 10000 })
})

View File

@@ -0,0 +1,60 @@
/**
* @jest-environment @edge-runtime/jest-environment
*/
import { NextResponse } from 'next/dist/server/web/spec-extension/response'
const toJSON = async (response: Response) => ({
body: await response.json(),
contentType: response.headers.get('content-type'),
status: response.status,
})
it('automatically parses and formats JSON', async () => {
expect(await toJSON(NextResponse.json({ message: 'hello!' }))).toMatchObject({
contentType: 'application/json',
body: { message: 'hello!' },
})
expect(
await toJSON(NextResponse.json({ status: 'success' }, { status: 201 }))
).toMatchObject({
contentType: 'application/json',
body: { status: 'success' },
status: 201,
})
expect(
await toJSON(
NextResponse.json({ error: { code: 'bad_request' } }, { status: 400 })
)
).toMatchObject({
contentType: 'application/json',
body: { error: { code: 'bad_request' } },
status: 400,
})
expect(await toJSON(NextResponse.json(null))).toMatchObject({
contentType: 'application/json',
body: null,
})
expect(await toJSON(NextResponse.json(''))).toMatchObject({
contentType: 'application/json',
body: '',
})
})
it('can be cloned', async () => {
const fetchResponse = await fetch('https://example.vercel.sh')
const newResponse = new NextResponse(fetchResponse.body, fetchResponse)
expect(await newResponse.text()).toContain('Example Domain')
expect(Object.fromEntries(newResponse.headers)).toMatchObject({
server: 'Vercel',
})
})
it('can return JSON', async () => {
const response = NextResponse.json({ hello: 'world' })
expect(await response.json()).toEqual({ hello: 'world' })
})

View File

@@ -0,0 +1,3 @@
it('should be able to require next/server outside edge', () => {
require('next/server')
})

View File

@@ -0,0 +1,442 @@
/**
* @jest-environment @edge-runtime/jest-environment
*/
import { NextURL } from 'next/dist/server/web/next-url'
// TODO Make NextURL extend URL
it.skip('has the right shape and prototype', () => {
const parsed = new NextURL('/about?param1=value1', 'http://127.0.0.1')
expect(parsed).toBeInstanceOf(URL)
})
it('allows to the pathname', async () => {
const parsed = new NextURL('/about?param1=value1', 'http://127.0.0.1:3000')
expect(parsed.basePath).toEqual('')
expect(parsed.hostname).toEqual('localhost')
expect(parsed.host).toEqual('localhost:3000')
expect(parsed.href).toEqual('http://localhost:3000/about?param1=value1')
parsed.pathname = '/hihi'
expect(parsed.href).toEqual('http://localhost:3000/hihi?param1=value1')
})
it('allows to change the host', () => {
const parsed = new NextURL('/about?param1=value1', 'http://127.0.0.1')
expect(parsed.hostname).toEqual('localhost')
expect(parsed.host).toEqual('localhost')
expect(parsed.href).toEqual('http://localhost/about?param1=value1')
parsed.hostname = 'foo.com'
expect(parsed.hostname).toEqual('foo.com')
expect(parsed.host).toEqual('foo.com')
expect(parsed.href).toEqual('http://foo.com/about?param1=value1')
expect(parsed.toString()).toEqual('http://foo.com/about?param1=value1')
})
it('does noop changing to an invalid hostname', () => {
const url = new NextURL('https://foo.com/example')
url.hostname = ''
expect(url.toString()).toEqual('https://foo.com/example')
})
it('preserves the fragment', () => {
const url = new NextURL(
'https://example.com/path/to?param1=value1#this-is-fragment'
)
expect(url.toString()).toEqual(
'https://example.com/path/to?param1=value1#this-is-fragment'
)
})
it('allows to change the whole href', () => {
const url = new NextURL('https://localhost.com/foo')
expect(url.hostname).toEqual('localhost.com')
expect(url.protocol).toEqual('https:')
expect(url.host).toEqual('localhost.com')
url.href = 'http://foo.com/bar'
expect(url.hostname).toEqual('foo.com')
expect(url.protocol).toEqual('http:')
expect(url.host).toEqual('foo.com')
})
it('allows to update search params', () => {
const url = new NextURL('/example', 'http://localhost.com')
url.searchParams.set('foo', 'bar')
expect(url.search).toEqual('?foo=bar')
expect(url.toString()).toEqual('http://localhost.com/example?foo=bar')
})
it('parses and formats the basePath', () => {
const url = new NextURL('/root/example', {
base: 'http://127.0.0.1',
nextConfig: { basePath: '/root' },
})
expect(url.basePath).toEqual('/root')
expect(url.pathname).toEqual('/example')
expect(url.toString()).toEqual('http://localhost/root/example')
const url2 = new NextURL('https://foo.com/root/bar', {
nextConfig: { basePath: '/root' },
})
expect(url2.basePath).toEqual('/root')
expect(url2.pathname).toEqual('/bar')
expect(url2.toString()).toEqual('https://foo.com/root/bar')
url2.basePath = '/test'
expect(url2.basePath).toEqual('/test')
expect(url2.pathname).toEqual('/bar')
expect(url2.toString()).toEqual('https://foo.com/test/bar')
const url3 = new NextURL('https://foo.com/example', {
nextConfig: { basePath: '/root' },
})
expect(url3.basePath).toEqual('')
url3.href = 'http://localhost.com/root/example'
expect(url3.basePath).toEqual('/root')
expect(url3.pathname).toEqual('/example')
expect(url3.toString()).toEqual('http://localhost.com/root/example')
})
it('allows to get empty locale when there is no locale', () => {
const url = new NextURL('https://localhost:3000/foo')
expect(url.locale).toEqual('')
})
it('doesnt allow to set an unexisting locale', () => {
const url = new NextURL('https://localhost:3000/foo')
let error: Error | null = null
try {
url.locale = 'foo'
} catch (err) {
error = err
}
expect(error).toBeInstanceOf(TypeError)
expect(error.message).toEqual(
'The NextURL configuration includes no locale "foo"'
)
})
it('always get a default locale', () => {
const url = new NextURL('/bar', {
base: 'http://127.0.0.1',
nextConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
expect(url.locale).toEqual('en')
})
it('parses and formats the default locale', () => {
const url = new NextURL('/es/bar', {
base: 'http://127.0.0.1',
nextConfig: {
basePath: '/root',
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
expect(url.locale).toEqual('es')
expect(url.toString()).toEqual('http://localhost/es/bar')
url.basePath = '/root'
expect(url.locale).toEqual('es')
expect(url.toString()).toEqual('http://localhost/root/es/bar')
url.locale = 'en'
expect(url.locale).toEqual('en')
expect(url.toString()).toEqual('http://localhost/root/bar')
url.locale = 'fr'
expect(url.locale).toEqual('fr')
expect(url.toString()).toEqual('http://localhost/root/fr/bar')
})
it('parses and formats the default locale with forceLocale', () => {
const url = new NextURL('/es/bar', {
base: 'http://127.0.0.1',
forceLocale: true,
nextConfig: {
basePath: '/root',
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
expect(url.locale).toEqual('es')
expect(url.toString()).toEqual('http://localhost/es/bar')
url.basePath = '/root'
expect(url.locale).toEqual('es')
expect(url.toString()).toEqual('http://localhost/root/es/bar')
url.locale = 'en'
expect(url.locale).toEqual('en')
expect(url.toString()).toEqual('http://localhost/root/en/bar')
url.locale = 'fr'
expect(url.locale).toEqual('fr')
expect(url.toString()).toEqual('http://localhost/root/fr/bar')
})
it('consider 127.0.0.1 and variations as localhost', () => {
const httpUrl = new NextURL('http://localhost:3000/hello')
expect(new NextURL('http://127.0.0.1:3000/hello')).toStrictEqual(httpUrl)
expect(new NextURL('http://127.0.1.0:3000/hello')).toStrictEqual(httpUrl)
expect(new NextURL('http://[::1]:3000/hello')).toStrictEqual(httpUrl)
const httpsUrl = new NextURL('https://localhost:3000/hello')
expect(new NextURL('https://127.0.0.1:3000/hello')).toStrictEqual(httpsUrl)
expect(new NextURL('https://127.0.1.0:3000/hello')).toStrictEqual(httpsUrl)
expect(new NextURL('https://[::1]:3000/hello')).toStrictEqual(httpsUrl)
})
it('preserves loopback hostnames inside encoded query parameter values', () => {
const url = new NextURL(
'https://example.com/api/echo?redirect_uri=http%3A%2F%2F127.0.0.1%3A12345%2Ftest'
)
expect(url.searchParams.get('redirect_uri')).toEqual(
'http://127.0.0.1:12345/test'
)
expect(url.href).toEqual(
'https://example.com/api/echo?redirect_uri=http%3A%2F%2F127.0.0.1%3A12345%2Ftest'
)
})
it('allows to change the port', () => {
const url = new NextURL('https://localhost:3000/foo')
url.port = '3001'
expect(url.href).toEqual('https://localhost:3001/foo')
url.port = '80'
expect(url.href).toEqual('https://localhost:80/foo')
url.port = ''
expect(url.href).toEqual('https://localhost/foo')
})
it('allows to clone a new copy', () => {
const url = new NextURL('/root/es/bar', {
base: 'http://127.0.0.1',
nextConfig: {
basePath: '/root',
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
const clone = url.clone()
clone.pathname = '/test'
clone.basePath = '/root-test'
expect(url.toString()).toEqual('http://localhost/root/es/bar')
expect(clone.toString()).toEqual('http://localhost/root-test/es/test')
})
it('does not add locale for api route', () => {
const url = new NextURL('http:///localhost:3000/api', {
nextConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
url.locale = 'fr'
let expected = 'http://localhost:3000/api'
expect(url.href).toEqual(expected)
expect(url.toString()).toEqual(expected)
expect(url.toJSON()).toEqual(expected)
url.pathname = '/api/hello'
expected = 'http://localhost:3000/api/hello'
expect(url.href).toEqual(expected)
expect(url.toString()).toEqual(expected)
expect(url.toJSON()).toEqual(expected)
})
it('correctly parses a prefetch url', async () => {
const url = new NextURL(
'/_next/data/1234/en/hello.json',
'http://127.0.0.1:3000'
)
expect(url.buildId).toEqual('1234')
expect(url.pathname).toEqual('/en/hello')
expect(url.locale).toEqual('')
expect(String(url)).toEqual(
'http://localhost:3000/_next/data/1234/en/hello.json'
)
})
it('correctly handles trailing slash in _next/data', async () => {
const url = new NextURL('/abc/', 'http://127.0.0.1:3000')
url.buildId = '1234'
expect(url.pathname).toEqual('/abc/')
expect(url.locale).toEqual('')
expect(String(url)).toEqual('http://localhost:3000/_next/data/1234/abc.json')
})
it('correctly handles trailing slash in _next/data with config', async () => {
const url = new NextURL('/abc/', 'http://127.0.0.1:3000', {
nextConfig: { trailingSlash: true },
})
url.buildId = '1234'
expect(url.pathname).toEqual('/abc/')
expect(url.locale).toEqual('')
expect(String(url)).toEqual('http://localhost:3000/_next/data/1234/abc.json')
})
it('correctly handles trailing slash in _next/data with basePath', async () => {
const url = new NextURL('/docs/abc/', 'http://127.0.0.1:3000', {
nextConfig: { basePath: '/docs', trailingSlash: true },
})
url.buildId = '1234'
expect(url.pathname).toEqual('/abc/')
expect(url.locale).toEqual('')
expect(String(url)).toEqual(
'http://localhost:3000/docs/_next/data/1234/abc.json'
)
})
it('correctly parses a prefetch index url', async () => {
const url = new NextURL(
'/_next/data/development/index.json',
'http://127.0.0.1:3000'
)
expect(url.pathname).toEqual('/')
})
it('correctly parses a prefetch url with i18n', async () => {
const url = new NextURL(
'/_next/data/development/en/hello.json',
'http://127.0.0.1:3000',
{
nextConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
}
)
expect(url.buildId).toEqual('development')
expect(url.pathname).toEqual('/hello')
expect(url.locale).toEqual('en')
expect(String(url)).toEqual(
'http://localhost:3000/_next/data/development/en/hello.json'
)
})
it('allows to update the pathname for a prefetch url', async () => {
const url = new NextURL(
'/_next/data/development/en/hello.json',
'http://127.0.0.1:3000',
{
nextConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
}
)
url.pathname = '/foo'
expect(String(url)).toEqual(
'http://localhost:3000/_next/data/development/en/foo.json'
)
})
it('allows to update the pathname to the root path for a prefetch url', async () => {
const url = new NextURL(
'/_next/data/development/hello.json',
'http://127.0.0.1:3000'
)
url.pathname = '/'
expect(String(url)).toEqual(
'http://localhost:3000/_next/data/development/index.json'
)
})
it('preserves the trailingSlash', async () => {
const url = new NextURL('/es/', {
base: 'http://127.0.0.1:3000',
nextConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
expect(String(url)).toEqual('http://localhost:3000/es/')
})
it('formats correctly the trailingSlash for root pages', async () => {
const url = new NextURL('/', {
base: 'http://127.0.0.1:3000',
nextConfig: {
trailingSlash: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
url.locale = 'es'
expect(String(url)).toEqual('http://localhost:3000/es/')
})
it('keeps the trailingSlash format for non root pages', async () => {
const url = new NextURL('/es', {
base: 'http://127.0.0.1:3000',
nextConfig: {
trailingSlash: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
})
expect(String(url)).toEqual('http://localhost:3000/es')
})
it('allows to preserve a json request', async () => {
const url = new NextURL(
'http://localhost:3000/_next/static/development/_devMiddlewareManifest.json',
{
nextConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
},
}
)
expect(String(url)).toEqual(
'http://localhost:3000/_next/static/development/_devMiddlewareManifest.json'
)
})

View File

@@ -0,0 +1,60 @@
/**
* @jest-environment @edge-runtime/jest-environment
*/
import { userAgentFromString, userAgent, NextRequest } from 'next/server'
const UA_STRING =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
it('parse an user agent', () => {
const parser = userAgentFromString(UA_STRING)
expect(parser.ua).toBe(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
)
expect(parser.browser).toStrictEqual({
name: 'Chrome',
version: '89.0.4389.90',
major: '89',
})
expect(parser.engine).toStrictEqual({
name: 'Blink',
version: '89.0.4389.90',
})
expect(parser.os).toStrictEqual({ name: 'Mac OS', version: '11.2.3' })
expect(parser.cpu).toStrictEqual({ architecture: undefined })
expect(parser.isBot).toBe(false)
})
it('parse empty user agent', () => {
expect.assertions(3)
for (const input of [undefined, null, '']) {
expect(userAgentFromString(input)).toStrictEqual({
ua: '',
browser: { name: undefined, version: undefined, major: undefined },
engine: { name: undefined, version: undefined },
os: { name: undefined, version: undefined },
device: { vendor: undefined, model: undefined, type: undefined },
cpu: { architecture: undefined },
isBot: false,
})
}
})
it('parse user agent from a NextRequest instance', () => {
const request = new NextRequest('https://vercel.com', {
headers: {
'user-agent': UA_STRING,
},
})
expect(userAgent(request)).toStrictEqual({
ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
browser: { name: 'Chrome', version: '89.0.4389.90', major: '89' },
engine: { name: 'Blink', version: '89.0.4389.90' },
os: { name: 'Mac OS', version: '11.2.3' },
device: { vendor: 'Apple', model: 'Macintosh', type: undefined },
cpu: { architecture: undefined },
isBot: false,
})
})