Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

feat: Added a test suite for App Router. #176

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ module.exports = {
parserOptions: {
ecmaVersion: 2020
},
ignorePatterns: ['tests/versioned/app']
ignorePatterns: ['tests/versioned/app', 'tests/versioned/app-dir']
}
4 changes: 2 additions & 2 deletions lib/next-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
function wrapRenderToResponseWithComponents(shim, originalFn) {
return function wrappedRenderToResponseWithComponents() {
const [ctx, result] = arguments
const { pathname } = ctx
const { pathname, renderOpts } = ctx

Check warning on line 30 in lib/next-server.js

View check run for this annotation

Codecov / codecov/patch

lib/next-server.js#L30

Added line #L30 was not covered by tests
// this is not query params but instead url params for dynamic routes
const { query, components } = result

Expand All @@ -52,7 +52,7 @@

shim.setTransactionUri(pathname)

const urlParams = extractRouteParams(ctx.query, query)
const urlParams = extractRouteParams(ctx.query, renderOpts?.params || query)

Check warning on line 55 in lib/next-server.js

View check run for this annotation

Codecov / codecov/patch

lib/next-server.js#L55

Added line #L55 was not covered by tests
assignParameters(shim, urlParams)

return originalFn.apply(this, arguments)
Expand Down
2 changes: 1 addition & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const MAX_MW_SUPPORTED_VERSION = '13.4.12'
utils.MAX_MW_SUPPORTED_VERSION = MAX_MW_SUPPORTED_VERSION
utils.MIN_MW_SUPPORTED_VERSION = MIN_MW_SUPPORTED_VERSION
/**
* Middlware instrumentation has had quite the journey for us.
* Middleware instrumentation has had quite the journey for us.
* As of 8/7/23 it no longer functions because it is running in a worker thread.
* Our instrumentation cannot propagate context in threads so for now we will no longer record this
* span.
Expand Down
3 changes: 2 additions & 1 deletion tests/versioned/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
package-lock.json
app/.next
app-dir/.next
145 changes: 145 additions & 0 deletions tests/versioned/app-dir.tap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const tap = require('tap')
const helpers = require('./helpers')
const utils = require('@newrelic/test-utilities')
const NEXT_TRANSACTION_PREFIX = 'WebTransaction/WebFrameworkUri/Nextjs/GET/'
const DESTINATIONS = {
NONE: 0x00,
TRANS_EVENT: 0x01,
TRANS_TRACE: 0x02,
ERROR_EVENT: 0x04,
BROWSER_EVENT: 0x08,
SPAN_EVENT: 0x10,
TRANS_SEGMENT: 0x20
}

tap.Test.prototype.addAssert('nextCLMAttrs', 1, function ({ segments, clmEnabled }) {
segments.forEach(({ segment, name, filepath }) => {
const attrs = segment.getAttributes()
if (clmEnabled) {
this.match(
attrs,
{
'code.function': name,
'code.filepath': filepath
},
'should add code.function and code.filepath when CLM is enabled.'
)
} else {
this.notOk(attrs['code.function'], 'should not add code.function when CLM is disabled.')
this.notOk(attrs['code.filepath'], 'should not add code.filepath when CLM is disabled.')
}
})
})

tap.test('Next.js', (t) => {
t.autoend()
let agent
let server

t.before(async () => {
await helpers.build(__dirname, 'app-dir')

agent = utils.TestAgent.makeInstrumented({
attributes: {
include: ['request.parameters.*']
}
})
helpers.registerInstrumentation(agent)

// TODO: would be nice to run a new server per test so there are not chained failures
// but currently has issues. Potentially due to module caching.
server = await helpers.start(__dirname, 'app-dir', '3002')
})

t.teardown(async () => {
await server.close()
agent.unload()
})

// since we setup agent in before we need to remove
// the transactionFinished listener between tests to avoid
// context leaking
function setupTransactionHandler(t) {
return new Promise((resolve) => {
function txHandler(transaction) {
resolve(transaction)
}

agent.agent.on('transactionFinished', txHandler)

t.teardown(() => {
agent.agent.removeListener('transactionFinished', txHandler)
})
})
}

t.test('should capture query params for static, non-dynamic route, page', async (t) => {
const prom = setupTransactionHandler(t)

const res = await helpers.makeRequest('/static/standard?first=one&second=two', 3002)
t.equal(res.statusCode, 200)
const tx = await prom

const agentAttributes = getTransactionEventAgentAttributes(tx)

t.match(agentAttributes, {
'request.parameters.first': 'one',
'request.parameters.second': 'two'
})
t.equal(tx.name, `${NEXT_TRANSACTION_PREFIX}/static/standard`)
})

t.test('should capture query and route params for static, dynamic route, page', async (t) => {
const prom = setupTransactionHandler(t)

const res = await helpers.makeRequest('/static/dynamic/testing?queryParam=queryValue', 3002)
t.equal(res.statusCode, 200)
const tx = await prom

const agentAttributes = getTransactionEventAgentAttributes(tx)

t.match(agentAttributes, {
'request.parameters.route.value': 'testing', // route [value] param
'request.parameters.queryParam': 'queryValue'
})

t.notOk(agentAttributes['request.parameters.route.queryParam'])
t.equal(tx.name, `${NEXT_TRANSACTION_PREFIX}/static/dynamic/[value]`)
})

t.test(
'should capture query params for server-side rendered, non-dynamic route, page',
async (t) => {
const prom = setupTransactionHandler(t)
const res = await helpers.makeRequest('/person/1?first=one&second=two', 3002)
t.equal(res.statusCode, 200)
const tx = await prom

const agentAttributes = getTransactionEventAgentAttributes(tx)

t.match(
agentAttributes,
{
'request.parameters.first': 'one',
'request.parameters.second': 'two'
},
'should match transaction attributes'
)

t.notOk(agentAttributes['request.parameters.route.first'])
t.notOk(agentAttributes['request.parameters.route.second'])
t.equal(tx.name, `${NEXT_TRANSACTION_PREFIX}/person/[id]`)
}
)

function getTransactionEventAgentAttributes(transaction) {
return transaction.trace.attributes.get(DESTINATIONS.TRANS_EVENT)
}
})
17 changes: 17 additions & 0 deletions tests/versioned/app-dir/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

export default function Layout({ children }) {
return (
<html lang="en">
<head />
<body>
<header>
<h1>This is my header</h1>
</header>
<main>{children}</main>
<footer>
<p>This is my footer</p>
</footer>
</body>
</html>
)
}
11 changes: 11 additions & 0 deletions tests/versioned/app-dir/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

export default function MyApp() {
return (
<section>This is the homepage</section>
)
}

17 changes: 17 additions & 0 deletions tests/versioned/app-dir/app/person/[id]/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { getPerson } from '../../../lib/functions'

export default async function Person({ params }) {
const user = await getPerson(params.id)

return (
<div>
<pre>{JSON.stringify(user, null, 4)}</pre>
</div>
)
}

33 changes: 33 additions & 0 deletions tests/versioned/app-dir/app/static/dynamic/[value]/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import Head from 'next/head'

export async function getProps(params) {
return {
title: 'This is a statically built dynamic route page.',
value: params.value
}
}

export async function generateStaticPaths() {
return [
{ value: 'testing' }
]
}


export default async function Standard({ params }) {
const { title, value } = await getProps(params)
return (
<>
<Head>
<title>{title}</title>
</Head>
<h1>{title}</h1>
<div>Value: {value}</div>
</>
)
}
25 changes: 25 additions & 0 deletions tests/versioned/app-dir/app/static/standard/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import Head from 'next/head'

export async function getProps() {
return {
title: 'This is a standard statically built page.'
}
}


export default async function Standard() {
const { title } = await getProps()
return (
<>
<Head>
<title>{title}</title>
</Head>
<h1>{title}</h1>
</>
)
}
28 changes: 28 additions & 0 deletions tests/versioned/app-dir/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

export const data = [
{
id: 1,
firstName: 'LeBron',
middleName: 'Raymone',
lastName: 'James',
age: 36
},
{
id: 2,
firstName: 'Lil',
middleName: 'Nas',
lastName: 'X',
age: 22
},
{
id: 3,
firstName: 'Beyoncé',
middleName: 'Giselle',
lastName: 'Knowles-Carter',
age: 40
}
]
28 changes: 28 additions & 0 deletions tests/versioned/app-dir/lib/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

export const data = [
{
id: 1,
firstName: 'LeBron',
middleName: 'Raymone',
lastName: 'James',
age: 36
},
{
id: 2,
firstName: 'Lil',
middleName: 'Nas',
lastName: 'X',
age: 22
},
{
id: 3,
firstName: 'Beyoncé',
middleName: 'Giselle',
lastName: 'Knowles-Carter',
age: 40
}
]
6 changes: 6 additions & 0 deletions tests/versioned/app-dir/lib/functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { data } from '../data'
export async function getPerson(id) {
const person = data.find((datum) => datum.id.toString() === id)

return person || `Could not find person with id of ${id}`
}
1 change: 0 additions & 1 deletion tests/versioned/app/.gitignore

This file was deleted.

17 changes: 17 additions & 0 deletions tests/versioned/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

module.exports = {
eslint: {
// Warning: This allows production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true
},
experimental: {
appDir: true
}
}
13 changes: 13 additions & 0 deletions tests/versioned/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@
"transaction-naming.tap.js"
]
},
{
"engines": {
"node": ">=18"
},
"dependencies": {
"next": ">=13.4.19",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"files": [
"app-dir.tap.js"
]
},
{
"engines": {
"node": ">=18"
Expand Down
Loading