Files
next.js/test/production/app-dir/image-jest-qualities/image-jest-qualities.test.ts
6aa3dbd2e8 Fix: Image component ignores images.qualities in Jest environment (#89536)
- Fixes #89492
- Fixes #18415 

## Problem

When testing Next.js <Image /> components with Jest, custom
images.qualities configured in next.config.js are ignored, causing:
- Image URLs to use default quality (q=75) instead of configured values
- Warning: "Image with src ... is using quality X which is not
configured in images.qualities [75]"

### Root Cause

In production and development, Next.js uses webpack's DefinePlugin to
inject the image configuration via process.env.__NEXT_IMAGE_OPTS.
However, *Jest tests don't use webpack*, so
process.env.__NEXT_IMAGE_OPTS is undefined, causing the Image component
to fall back to default configuration.

## Solution

This PR implements a two-part fix:

1. *Jest globals population* (packages/next/src/build/jest/jest.ts):
   - Expose nextConfig.images via Jest globals.__NEXT_IMAGE_OPTS

2. *Runtime fallback* (image components):
- When process.env.__NEXT_IMAGE_OPTS is undefined, fall back to
globalThis.__NEXT_IMAGE_OPTS
   - Applied to:
     - packages/next/src/client/image-component.tsx
     - packages/next/src/shared/lib/image-external.tsx  
     - packages/next/src/client/legacy/image.tsx

## Changes

- ✅ Minimal, targeted fix (4 files, ~40 lines)
- ✅ No production/development behavior changes
- ✅ No test-only conditionals or environment checks
- ✅ Comprehensive test coverage added

## Testing

Added test in test/production/app-dir/image-jest-qualities/ that:
- Configures custom qualities [90, 100] in next.config.js
- Renders <Image quality={100} /> in Jest with jsdom
- Asserts generated image URLs contain q=100 not q=75

Test correctly *fails on canary* (shows warning about unconfigured
quality) and *passes with fix*.

## Verification

- ✅ New test passes with fix
- ✅ Existing image tests unaffected
- ✅ Addresses original issue reproduction exactly

---------

Co-authored-by: Steven <[email protected]>
2026-02-23 16:19:06 -05:00

115 lines
2.8 KiB
TypeScript

import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'e2e-utils'
import path from 'path'
import execa from 'execa'
const appDir = path.join(__dirname, 'app')
describe('next/jest image qualities config', () => {
let next: NextInstance
beforeAll(async () => {
next = await createNext({
skipStart: true,
files: {
'next.config.js': `
module.exports = {
images: {
qualities: [90, 100],
},
}
`,
app: new FileRef(path.join(appDir, 'app')),
'jest.config.js': `
const nextJest = require('next/jest')
const createJestConfig = nextJest({
dir: './',
})
const customJestConfig = {
testEnvironment: 'jsdom',
}
module.exports = createJestConfig(customJestConfig)
`,
[`tests/image.test.tsx`]: `
import Image from 'next/image'
import { render, screen } from '@testing-library/react'
describe('Image quality config', () => {
it('respects custom qualities from next.config.js', () => {
render(
<Image
src="/test.jpg"
alt="test"
width={500}
height={500}
quality={100}
/>
)
const img = screen.getByRole('img')
// Should use quality=100 from our config, not default q=75
expect(img.getAttribute('src')).toContain('q=100')
expect(img.getAttribute('src')).not.toContain('q=75')
})
it('uses custom qualities for srcset generation', () => {
render(
<Image
src="/test.jpg"
alt="test"
width={500}
height={500}
sizes="100vw"
/>
)
const img = screen.getByRole('img')
// Should generate srcset with our custom qualities [90, 100]
// and not include default quality 75
const srcSet = img.getAttribute('srcset') || ''
const srcSetEntries = srcSet.split(',')
const hasCustomQualities = srcSetEntries.some(entry =>
entry.includes('q=90') || entry.includes('q=100')
)
const hasDefaultQuality = srcSetEntries.some(entry =>
entry.includes('q=75')
)
expect(hasCustomQualities).toBe(true)
expect(hasDefaultQuality).toBe(false)
})
})
`,
},
dependencies: {
jest: '29.7.0',
'jest-environment-jsdom': '29.7.0',
'@testing-library/react': '15.0.2',
'@testing-library/jest-dom': '5.17.0',
},
})
})
afterAll(() => next.destroy())
it('should pass jest tests with custom image qualities', async () => {
const result = await execa(
'pnpm',
['jest', 'tests/image.test.tsx', '--forceExit', '--verbose'],
{
cwd: next.testDir,
reject: false,
}
)
const output = result.stdout || result.stderr || ''
console.log('Jest output:', output)
expect(output).toContain('PASS')
expect(output).toMatch(/2 passed/)
})
})