dari-ai/nextjs-selfbench
Next.js Selfbench Next.js Selfbench is a 47-task software-engineering evaluation packaged for the Harbor evaluation framework. Each task asks an agent to implement a change in a frozen revision of vercel/next.js, then evaluates the resulting patch with task-specific tests. This repository contains the raw evaluation only. It does not include model outputs, scores, costs, or benchmark result artifacts. Use Download the raw task package with the Hugging Face CLI: hf… See the full description on the dataset page: https://huggingface.co/datasets/dari-ai/nextjs-selfbench.
0448
1diff --git a/test/e2e/app-dir/partial-fallback-root-blocking/next.config.js b/test/e2e/app-dir/partial-fallback-root-blocking/next.config.js2index e64bae22..5e50f0b7 1006443--- a/test/e2e/app-dir/partial-fallback-root-blocking/next.config.js4+++ b/test/e2e/app-dir/partial-fallback-root-blocking/next.config.js5@@ -3,6 +3,7 @@6 */7 const nextConfig = {8 cacheComponents: true,9+ partialPrefetching: true,10 }11 12 module.exports = nextConfig13diff --git a/test/e2e/app-dir/partial-fallback-root-blocking/partial-fallback-root-blocking.test.ts b/test/e2e/app-dir/partial-fallback-root-blocking/partial-fallback-root-blocking.test.ts14index 76293240..bfe46a4a 10064415--- a/test/e2e/app-dir/partial-fallback-root-blocking/partial-fallback-root-blocking.test.ts16+++ b/test/e2e/app-dir/partial-fallback-root-blocking/partial-fallback-root-blocking.test.ts17@@ -42,22 +42,29 @@ describe('partial-fallback-root-blocking', () => {18 19 // TODO: Re-enable once infra supports multiple layers of fallbacks20 if (!isNextDeploy) {21- it('should not reuse a shared shell cache entry for unknown root branches', async () => {22+ it('should serve unknown root branches with the root param resolved and other params deferred', async () => {23 const firstResult = await fetchSplitHTML('/fr/two')24 25 expect(firstResult.response.status).toBe(200)26 expect(firstResult.static$('#lang-fallback').length).toBe(0)27 expect(firstResult.static$('#lang').text()).toBe('fr')28- expect(firstResult.static$('#slug').text()).toBe('two')29- expect(firstResult.dynamicPart).toBe('')30+31+ expect(firstResult.static$('#slug-fallback').text()).toBe(32+ 'loading slug...'33+ )34+ expect(firstResult.static$('#slug').length).toBe(0)35+ expect(firstResult.dynamicPart).toContain('<div id="slug">two</div>')36 37 const secondResult = await fetchSplitHTML('/fr/other')38 39 expect(secondResult.response.status).toBe(200)40 expect(secondResult.static$('#lang-fallback').length).toBe(0)41 expect(secondResult.static$('#lang').text()).toBe('fr')42- expect(secondResult.static$('#slug').text()).toBe('other')43- expect(secondResult.dynamicPart).toBe('')44+ expect(secondResult.static$('#slug-fallback').text()).toBe(45+ 'loading slug...'46+ )47+ expect(secondResult.static$('#slug').length).toBe(0)48+ expect(secondResult.dynamicPart).toContain('<div id="slug">other</div>')49 })50 }51 52diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/adapter-partial-fallback-blocking.test.ts b/test/production/app-dir/adapter-partial-fallback-blocking/adapter-partial-fallback-blocking.test.ts53new file mode 10064454index 00000000..acdfa23955--- /dev/null56+++ b/test/production/app-dir/adapter-partial-fallback-blocking/adapter-partial-fallback-blocking.test.ts57@@ -0,0 +1,132 @@58+import { nextTestSetup } from 'e2e-utils'59+import type { NextAdapter } from 'next'60+61+describe('adapter-partial-fallback-blocking', () => {62+ const { next } = nextTestSetup({63+ files: __dirname,64+ })65+66+ function expectAllowedParams(allowQuery: string[], expected: string[]) {67+ // Adapter query allowlists are sets. Their serialized order is not part68+ // of the build output contract.69+ expect(new Set(allowQuery)).toEqual(new Set(expected))70+ }71+72+ it('should exclude never-prerenderable params from allowQuery for blocking routes with empty shells', async () => {73+ const { outputs }: Parameters<NextAdapter['onBuildComplete']>[0] =74+ await next.readJSON('build-complete.json')75+76+ const genericEmptyShellPrerender = outputs.prerenders.find(77+ (output) => output.pathname === '/empty-shell/[one]/[two]'78+ )79+ const genericEmptyShellDataPrerender = outputs.prerenders.find(80+ (output) => output.pathname === '/empty-shell/[one]/[two].rsc'81+ )82+ const genericEmptyShellSegmentPrerenders = outputs.prerenders.filter(83+ (output) =>84+ output.pathname.startsWith('/empty-shell/[one]/[two].segments/')85+ )86+ const generatedEmptyShellPrerender = outputs.prerenders.find(87+ (output) => output.pathname === '/empty-shell/a/[two]'88+ )89+90+ expect(genericEmptyShellPrerender).toBeDefined()91+ expect(genericEmptyShellDataPrerender).toBeDefined()92+ expect(genericEmptyShellSegmentPrerenders.length).toBeGreaterThan(0)93+ expect(generatedEmptyShellPrerender).toBeDefined()94+95+ expectAllowedParams(genericEmptyShellPrerender.config.allowQuery, [96+ 'nxtPone',97+ ])98+ expectAllowedParams(genericEmptyShellDataPrerender.config.allowQuery, [99+ 'nxtPone',100+ ])101+ for (const output of genericEmptyShellSegmentPrerenders) {102+ expectAllowedParams(output.config.allowQuery, ['nxtPone'])103+ }104+105+ expect(generatedEmptyShellPrerender.config.partialFallback).toBeUndefined()106+ expectAllowedParams(generatedEmptyShellPrerender.config.allowQuery, [])107+ })108+109+ it('should exclude never-prerenderable params from allowQuery for entries with a resolved root param', async () => {110+ const { outputs }: Parameters<NextAdapter['onBuildComplete']>[0] =111+ await next.readJSON('build-complete.json')112+113+ const emptyShellPrerender = outputs.prerenders.find(114+ (output) =>115+ output.pathname === '/with-root-param/en/empty-shell/[category]/[id]'116+ )117+ const emptyShellDataPrerender = outputs.prerenders.find(118+ (output) =>119+ output.pathname ===120+ '/with-root-param/en/empty-shell/[category]/[id].rsc'121+ )122+ const nonEmptyShellPrerender = outputs.prerenders.find(123+ (output) =>124+ output.pathname ===125+ '/with-root-param/en/non-empty-shell/[category]/[id]'126+ )127+ const emptyShellLeafPrerender = outputs.prerenders.find(128+ (output) =>129+ output.pathname === '/with-root-param/en/empty-shell/shoes/[id]'130+ )131+132+ expect(emptyShellPrerender).toBeDefined()133+ expect(emptyShellDataPrerender).toBeDefined()134+ expect(nonEmptyShellPrerender).toBeDefined()135+ expect(emptyShellLeafPrerender).toBeDefined()136+137+ expectAllowedParams(emptyShellPrerender.config.allowQuery, [138+ 'nxtPcategory',139+ ])140+ expectAllowedParams(emptyShellDataPrerender.config.allowQuery, [141+ 'nxtPcategory',142+ ])143+144+ expectAllowedParams(nonEmptyShellPrerender.config.allowQuery, [145+ 'nxtPcategory',146+ ])147+ expect(nonEmptyShellPrerender.config.partialFallback).toBe(true)148+149+ expectAllowedParams(emptyShellLeafPrerender.config.allowQuery, [])150+ })151+152+ it('should exclude never-prerenderable params from allowQuery for entries with an unresolved root param', async () => {153+ const { outputs }: Parameters<NextAdapter['onBuildComplete']>[0] =154+ await next.readJSON('build-complete.json')155+156+ const emptyShellBasePrerender = outputs.prerenders.find(157+ (output) =>158+ output.pathname ===159+ '/with-root-param/[lang]/empty-shell/[category]/[id]'160+ )161+ const emptyShellBaseDataPrerender = outputs.prerenders.find(162+ (output) =>163+ output.pathname ===164+ '/with-root-param/[lang]/empty-shell/[category]/[id].rsc'165+ )166+ const nonEmptyShellBasePrerender = outputs.prerenders.find(167+ (output) =>168+ output.pathname ===169+ '/with-root-param/[lang]/non-empty-shell/[category]/[id]'170+ )171+172+ expect(emptyShellBasePrerender).toBeDefined()173+ expect(emptyShellBaseDataPrerender).toBeDefined()174+ expect(nonEmptyShellBasePrerender).toBeDefined()175+176+ expectAllowedParams(emptyShellBasePrerender.config.allowQuery, [177+ 'nxtPlang',178+ 'nxtPcategory',179+ ])180+ expectAllowedParams(emptyShellBaseDataPrerender.config.allowQuery, [181+ 'nxtPlang',182+ 'nxtPcategory',183+ ])184+ expectAllowedParams(nonEmptyShellBasePrerender.config.allowQuery, [185+ 'nxtPlang',186+ 'nxtPcategory',187+ ])188+ })189+})190diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/app/(standard)/empty-shell/[one]/[two]/page.tsx b/test/production/app-dir/adapter-partial-fallback-blocking/app/(standard)/empty-shell/[one]/[two]/page.tsx191new file mode 100644192index 00000000..65d28a99193--- /dev/null194+++ b/test/production/app-dir/adapter-partial-fallback-blocking/app/(standard)/empty-shell/[one]/[two]/page.tsx195@@ -0,0 +1,27 @@196+// This route intentionally produces EMPTY build-time shells: the params are197+// read outside of any Suspense boundary (and there is no loading.tsx), so the198+// postpone propagates to the root. `instant = false` opts the route out of199+// requiring an instant (non-empty) shell.200+//201+// The empty shells downgrade the generic route to a blocking route, but `two`202+// is still never provided by `generateStaticParams`: an on-demand render may203+// only complete `one`, so only `one` may participate in the cache key.204+export function generateStaticParams() {205+ return [{ one: 'a' }]206+}207+208+export const instant = false209+210+export default async function Page({211+ params,212+}: {213+ params: Promise<{ one: string; two: string }>214+}) {215+ const { one, two } = await params216+217+ return (218+ <div>219+ {one}:{two}220+ </div>221+ )222+}223diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/app/(standard)/layout.tsx b/test/production/app-dir/adapter-partial-fallback-blocking/app/(standard)/layout.tsx224new file mode 100644225index 00000000..e7a42507226--- /dev/null227+++ b/test/production/app-dir/adapter-partial-fallback-blocking/app/(standard)/layout.tsx228@@ -0,0 +1,11 @@229+import { ReactNode } from 'react'230+231+// Root layout for the (standard) branch: all params in this branch are232+// below the root layout, so none of them are root params.233+export default function Root({ children }: { children: ReactNode }) {234+ return (235+ <html>236+ <body>{children}</body>237+ </html>238+ )239+}240diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/empty-shell/[category]/[id]/page.tsx b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/empty-shell/[category]/[id]/page.tsx241new file mode 100644242index 00000000..d2b6d1b1243--- /dev/null244+++ b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/empty-shell/[category]/[id]/page.tsx245@@ -0,0 +1,38 @@246+// Empty-shell variant under a root param: the params are read outside of247+// any Suspense boundary, so the postpone propagates to the root and every248+// build-time shell is empty. `instant = false` opts the route out of249+// requiring an instant (non-empty) shell.250+//251+// `generateStaticParams` provides `lang` in every entry (required for root252+// params) and partially covers `category`; `id` is never provided, so `id`253+// must never be resolved into a cached shell and must never be part of a254+// cache key — including for requests whose `lang` value was not enumerated255+// (e.g. /fr/...), which match the base route entry where `lang` is an256+// unresolved root param.257+export async function generateStaticParams() {258+ return [{ lang: 'en' }, { lang: 'en', category: 'shoes' }]259+}260+261+export const instant = false262+263+export default async function Page({264+ params,265+}: {266+ params: Promise<{ lang: string; category: string; id: string }>267+}) {268+ const { lang, category, id } = await params269+270+ // Per-render marker (prerenderable): must be re-rendered per request and271+ // never repeat across fetches — a repeated value proves a stored render272+ // is being replayed from the cache.273+ const renderedAt = performance.now()274+275+ return (276+ <div>277+ <div id="lang">{lang}</div>278+ <div id="category">{category}</div>279+ <div id="id">{id}</div>280+ <div id="rendered-at">{renderedAt}</div>281+ </div>282+ )283+}284diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/layout.tsx b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/layout.tsx285new file mode 100644286index 00000000..bc155c67287--- /dev/null288+++ b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/layout.tsx289@@ -0,0 +1,19 @@290+// The ROOT layout lives inside [lang], making `lang` a ROOT param: the291+// document itself (the html tag) varies by lang with no Suspense boundary.292+// Root params must be provided by every generateStaticParams result — the293+// build enforces this — so `lang` is always a prerenderable param.294+export default async function RootLayout({295+ children,296+ params,297+}: {298+ children: React.ReactNode299+ params: Promise<{ lang: string }>300+}) {301+ const { lang } = await params302+303+ return (304+ <html lang={lang}>305+ <body>{children}</body>306+ </html>307+ )308+}309diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/non-empty-shell/[category]/[id]/page.tsx b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/non-empty-shell/[category]/[id]/page.tsx310new file mode 100644311index 00000000..ba731670312--- /dev/null313+++ b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/non-empty-shell/[category]/[id]/page.tsx314@@ -0,0 +1,49 @@315+import { Suspense } from 'react'316+317+// Non-empty-shell variant under a root param: static page content plus318+// Suspense boundaries around the non-root param reads, so shells contain319+// static content and no empty-shell downgrade happens.320+//321+// `generateStaticParams` provides `lang` in every entry (required for root322+// params) and partially covers `category`; `id` is never provided and must323+// never resolve into a cached shell nor participate in a cache key.324+export async function generateStaticParams() {325+ return [{ lang: 'en' }, { lang: 'en', category: 'shoes' }]326+}327+328+async function Id({ params }: { params: Promise<{ id: string }> }) {329+ const { id } = await params330+331+ // Per-render marker (prerenderable), read after `await params` so it332+ // belongs to the deferred region: it must be re-rendered (resumed) per333+ // request and never repeat across fetches.334+ const renderedAt = performance.now()335+336+ return (337+ <>338+ <div id="id">{id}</div>339+ <div id="rendered-at">{renderedAt}</div>340+ </>341+ )342+}343+344+export default function Page({345+ params,346+}: {347+ params: Promise<{ lang: string; category: string; id: string }>348+}) {349+ return (350+ <div>351+ <div id="static">static page content</div>352+ <Suspense353+ fallback={354+ <div id="id-fallback" data-fallback>355+ loading id...356+ </div>357+ }358+ >359+ <Id params={params} />360+ </Suspense>361+ </div>362+ )363+}364diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/non-empty-shell/[category]/layout.tsx b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/non-empty-shell/[category]/layout.tsx365new file mode 100644366index 00000000..a0da238a367--- /dev/null368+++ b/test/production/app-dir/adapter-partial-fallback-blocking/app/with-root-param/[lang]/non-empty-shell/[category]/layout.tsx369@@ -0,0 +1,40 @@370+import { Suspense, type ReactNode } from 'react'371+372+// `category` is read in its own Suspense boundary: shells where it is373+// concrete render it statically, and generic shells show374+// `#category-fallback`. This makes the served shell's specialization375+// observable from the static part of the response. (`lang` is a root param376+// and is part of the document itself, rendered by the root layout.)377+async function LayoutImpl({378+ children,379+ params,380+}: {381+ children: ReactNode382+ params: Promise<{ category: string }>383+}) {384+ const { category } = await params385+386+ return (387+ <div>388+ <div id="category">{category}</div>389+ {children}390+ </div>391+ )392+}393+394+export default function Layout(props: {395+ children: ReactNode396+ params: Promise<{ category: string }>397+}) {398+ return (399+ <Suspense400+ fallback={401+ <div id="category-fallback" data-fallback>402+ loading category...403+ </div>404+ }405+ >406+ <LayoutImpl {...props} />407+ </Suspense>408+ )409+}410diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/my-adapter.mjs b/test/production/app-dir/adapter-partial-fallback-blocking/my-adapter.mjs411new file mode 100644412index 00000000..548129ec413--- /dev/null414+++ b/test/production/app-dir/adapter-partial-fallback-blocking/my-adapter.mjs415@@ -0,0 +1,9 @@416+import fs from 'fs/promises'417+418+/** @type {import('next').NextAdapter } */419+export default {420+ name: 'adapter-partial-fallback-blocking',421+ async onBuildComplete(ctx) {422+ await fs.writeFile('build-complete.json', JSON.stringify(ctx, null, 2))423+ },424+}425diff --git a/test/production/app-dir/adapter-partial-fallback-blocking/next.config.js b/test/production/app-dir/adapter-partial-fallback-blocking/next.config.js426new file mode 100644427index 00000000..39584cc1428--- /dev/null429+++ b/test/production/app-dir/adapter-partial-fallback-blocking/next.config.js430@@ -0,0 +1,10 @@431+/**432+ * @type {import('next').NextConfig}433+ */434+const nextConfig = {435+ cacheComponents: true,436+ partialPrefetching: true,437+ adapterPath: require.resolve('./my-adapter.mjs'),438+}439+440+module.exports = nextConfig441 