|
| 1 | +/** |
| 2 | + * Optimize one Partial Prefetching navigation |
| 3 | + * |
| 4 | + * The fixture starts after Cache Components and Partial Prefetching adoption. |
| 5 | + * The agent must turn one accepted, URL-specific product goal into an |
| 6 | + * instant() RED-to-GREEN loop without expanding the cost to every card. |
| 7 | + */ |
| 8 | + |
| 9 | +import { expect, test } from 'vitest' |
| 10 | +import { existsSync, readFileSync, readdirSync, statSync } from 'fs' |
| 11 | +import { join } from 'path' |
| 12 | +import { environment, transcript } from '@vercel/agent-eval/eval' |
| 13 | + |
| 14 | +const IGNORE_DIRS = new Set([ |
| 15 | + '.git', |
| 16 | + '.next', |
| 17 | + 'node_modules', |
| 18 | + 'dist', |
| 19 | + 'build', |
| 20 | + 'coverage', |
| 21 | +]) |
| 22 | + |
| 23 | +function readSourceFiles(dir: string): string[] { |
| 24 | + if (!existsSync(dir)) return [] |
| 25 | + |
| 26 | + return readdirSync(dir).flatMap((entry) => { |
| 27 | + if (IGNORE_DIRS.has(entry)) return [] |
| 28 | + const path = join(dir, entry) |
| 29 | + if (statSync(path).isDirectory()) return readSourceFiles(path) |
| 30 | + if (entry === 'EVAL.ts' || !/\.(ts|tsx|js|jsx)$/.test(entry)) return [] |
| 31 | + return readFileSync(path, 'utf-8') |
| 32 | + }) |
| 33 | +} |
| 34 | + |
| 35 | +const source = readSourceFiles(process.cwd()).join('\n') |
| 36 | + |
| 37 | +test('keeps Cache Components and Partial Prefetching enabled', () => { |
| 38 | + const config = readFileSync(join(process.cwd(), 'next.config.ts'), 'utf-8') |
| 39 | + |
| 40 | + expect(config).toMatch(/cacheComponents\s*:\s*true/) |
| 41 | + expect(config).toMatch(/partialPrefetching\s*:\s*true/) |
| 42 | +}) |
| 43 | + |
| 44 | +test('adds a click-driven instant navigation regression test', () => { |
| 45 | + expect(source).toMatch(/from\s+['"]@next\/playwright['"]/) |
| 46 | + expect(source).toMatch(/\binstant\s*\(/) |
| 47 | + expect(source).toMatch(/\.click\s*\(/) |
| 48 | +}) |
| 49 | + |
| 50 | +test('adds reusable cached work', () => { |
| 51 | + expect(source).toMatch(/['"]use cache['"]/) |
| 52 | +}) |
| 53 | + |
| 54 | +test('uses the navigation stage', () => { |
| 55 | + expect(source).toMatch(/\b(?:unstable_)?navigation\s*\(/) |
| 56 | +}) |
| 57 | + |
| 58 | +test('optimizes only the selected featured navigation', async () => { |
| 59 | + await expect(environment).toSatisfyCriterion( |
| 60 | + `Only the featured Aurora keynote Link from /sessions opts into per-link prefetching with prefetch={true} or the equivalent bare prefetch prop. The other session-card Links retain the default or auto prefetch behavior. The solution does not add prefetch={false} or enable per-link prefetching for every card.` |
| 61 | + ) |
| 62 | +}) |
| 63 | + |
| 64 | +test('assigns the destination UI to the intended stages', async () => { |
| 65 | + await expect(environment).toSatisfyCriterion( |
| 66 | + `The final /sessions/aurora-keynote implementation makes the public title "Aurora Keynote", speaker "Mina Park", and summary reusable through a focused use-cache function. An explicit cacheLife is optional because the fixture has no existing time-based freshness contract; this does not remove the separate on-demand invalidation requirement for edits. The result varies by session slug, and the cache does not contain connection(), cookies(), headers(), or live audience questions. Related sessions are also reusable cached data, but an await navigation() boundary runs before that cached work is called. Live audience questions remain uncached request-time content and continue streaming after navigation.` |
| 67 | + ) |
| 68 | +}) |
| 69 | + |
| 70 | +test('invalidates cached session data after an edit', async () => { |
| 71 | + await expect(environment).toSatisfyCriterion( |
| 72 | + `The cached session read has a cache tag that identifies the edited session, and saveSessionSummary invalidates the same tag after updateSessionSummary succeeds. The invalidation provides read-your-own-writes behavior for the Server Action; cacheLife alone is not treated as sufficient.` |
| 73 | + ) |
| 74 | +}) |
| 75 | + |
| 76 | +test('ships the exact positive instant contract', async () => { |
| 77 | + await expect(environment).toSatisfyCriterion( |
| 78 | + `The project retains a production-mode @next/playwright instant() test that starts at /sessions and clicks the featured Aurora keynote Link. While instant() holds dynamic writes, the test asserts the Aurora Keynote title, Mina Park speaker, and session summary are visible, while related sessions and live audience questions are absent. After the lock releases, the deferred regions are allowed to render. The testing API is exposed only for the measured production test build.` |
| 79 | + ) |
| 80 | +}) |
| 81 | + |
| 82 | +test('completed a verified RED-to-GREEN optimizer loop', async () => { |
| 83 | + await expect(transcript).toSatisfyCriterion( |
| 84 | + `The agent verified the exact featured-Link navigation in a production build. Before changing the implementation, it proved the destination content rendered normally and that the locked instant() contract failed while the route shell remained available. It then applied the targeted optimization and reran the same locked test successfully. Merely writing the test, running only a development server, or describing commands for the user does not satisfy this criterion.` |
| 85 | + ) |
| 86 | +}) |
| 87 | + |
| 88 | +test('verified session summary freshness after a write', async () => { |
| 89 | + await expect(transcript).toSatisfyCriterion( |
| 90 | + `The agent ran a behavioral freshness check that first populated the cache for a session, then updated its summary through the existing mutation, and finally read the session again and observed the updated summary. Source inspection, merely adding invalidation code, or testing the mutation before the cache was populated does not satisfy this criterion.` |
| 91 | + ) |
| 92 | +}) |
0 commit comments