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,8 @@
module.exports = {
images: {
deviceSizes: [480, 1024, 1600, 2000],
imageSizes: [16, 32, 48, 64],
path: 'https://example.com/myaccount/',
loader: 'imgix',
},
}

View File

@@ -0,0 +1,98 @@
import React from 'react'
import Image from 'next/legacy/image'
import Link from 'next/link'
const ClientSide = () => {
return (
<div>
<p id="stubtext">This is a client side page</p>
<Image
id="basic-image"
src="foo.jpg"
loading="eager"
width={300}
height={400}
quality={60}
></Image>
<Image
id="attribute-test"
data-demo="demo-value"
src="bar.jpg"
loading="eager"
width={300}
height={400}
/>
<Image
id="secondary-image"
data-demo="demo-value"
host="secondary"
src="foo2.jpg"
loading="eager"
width={300}
height={400}
/>
<Image
id="unoptimized-image"
unoptimized
src="https://arbitraryurl.com/foo.jpg"
loading="eager"
width={300}
height={400}
/>
<Image
id="priority-image-client"
priority
src="withpriorityclient.png"
width={300}
height={400}
/>
<Image
id="preceding-slash-image"
src="/fooslash.jpg"
priority
width={300}
height={400}
/>
<Image
id="icon-image-32"
src="/icon.png"
loading="eager"
width={32}
height={32}
/>
<Image
id="icon-image-16"
src="/icon.png"
loading="eager"
width={16}
height={16}
/>
<Image
id="image-with-param-auto"
src="foo.png?auto=compress"
loading="eager"
width={300}
height={400}
/>
<Image
id="image-with-param-width"
src="foo.png?auto=format&w=500"
loading="eager"
width={500}
height={500}
/>
<Image
id="image-with-param-fit"
src="foo.png?auto=format&fit=crop&w=300&h=300"
loading="eager"
width={300}
height={300}
/>
<Link href="/errors" id="errorslink">
Errors
</Link>
</div>
)
}
export default ClientSide

View File

@@ -0,0 +1,19 @@
import React from 'react'
import Image from 'next/legacy/image'
const Errors = () => {
return (
<div>
<p id="stubtext">This is a page with errors</p>
<Image
id="nonexistant-host"
host="nope"
src="wronghost.jpg"
width={300}
height={400}
></Image>
</div>
)
}
export default Errors

View File

@@ -0,0 +1,131 @@
import React from 'react'
import Image from 'next/legacy/image'
import Link from 'next/link'
import Head from 'next/head'
const Page = () => {
return (
<div>
<p>Hello World</p>
<Image
id="image-with-sizes"
src="/test-sizes.jpg"
width={2000}
height={100}
sizes="100vw"
/>
<Image
id="basic-image"
src="foo.jpg"
loading="eager"
width={300}
height={400}
quality={60}
></Image>
<Image
id="attribute-test"
data-demo="demo-value"
src="bar.jpg"
loading="eager"
width={1024}
height={400}
/>
<Image
id="secondary-image"
data-demo="demo-value"
host="secondary"
src="foo2.jpg"
loading="eager"
width={300}
height={400}
/>
<Image
id="unoptimized-image"
unoptimized
src="https://arbitraryurl.com/foo.jpg"
loading="eager"
width={300}
height={400}
/>
<Image
id="priority-image"
priority
host="secondary"
src="withpriority2.png"
width={300}
height={400}
/>
<Image
id="priority-image"
priority
unoptimized
src="https://arbitraryurl.com/withpriority3.png"
width={300}
height={400}
/>
<Image
id="priority-image"
priority
src="withpriority.png"
width={300}
height={400}
quality={60}
/>
<Image
id="preceding-slash-image"
src="/fooslash.jpg"
priority
width={300}
height={400}
/>
<Image
id="icon-image-32"
src="/icon.png"
loading="eager"
width={32}
height={32}
/>
<Image
id="icon-image-16"
src="/icon.png"
loading="eager"
width={16}
height={16}
/>
<Image
id="image-with-param-auto"
src="foo.png?auto=compress"
loading="eager"
width={300}
height={400}
/>
<Image
id="image-with-param-width"
src="foo.png?auto=format&w=500"
loading="eager"
width={500}
height={500}
/>
<Image
id="image-with-param-fit"
src="foo.png?auto=format&fit=crop&w=300&h=300"
loading="eager"
width={300}
height={300}
/>
<Link href="/client-side" id="clientlink">
Client Side
</Link>
<Link href="/lazy" id="lazylink">
lazy
</Link>
<Head>
<link rel="stylesheet" href="styles.css" />
<link rel="preload" href="styles.css" as="style" />
</Head>
<p id="stubtext">This is the index page</p>
</div>
)
}
export default Page

View File

@@ -0,0 +1,60 @@
import React from 'react'
import Image from 'next/legacy/image'
const Lazy = () => {
return (
<div>
<p id="stubtext">This is a page with lazy-loaded images</p>
<Image
id="lazy-top"
src="lazy1.jpg"
height={400}
width={1024}
loading="lazy"
></Image>
<div style={{ height: '2000px' }}></div>
<Image
id="lazy-mid"
src="lazy2.jpg"
loading="lazy"
height={400}
width={300}
className="exampleclass"
></Image>
<div style={{ height: '2000px' }}></div>
<Image
id="lazy-bottom"
src="https://www.otherhost.com/lazy3.jpg"
height={400}
width={300}
unoptimized
loading="lazy"
></Image>
<div style={{ height: '2000px' }}></div>
<Image
id="lazy-without-attribute"
src="lazy4.jpg"
height={400}
width={800}
></Image>
<div style={{ height: '2000px' }}></div>
<Image
id="eager-loading"
src="lazy5.jpg"
loading="eager"
height={400}
width={1900}
></Image>
<div style={{ height: '2000px' }}></div>
<Image
id="lazy-boundary"
src="lazy6.jpg"
height={400}
width={800}
lazyBoundary="0px 0px 500px 0px"
></Image>
</div>
)
}
export default Lazy

View File

@@ -0,0 +1,23 @@
import Image from 'next/legacy/image'
const LoaderExample = () => {
return (
<div>
<p>Custom loader in both next.config.js and loader prop</p>
<Image
id="loader-prop-img"
src="foo.jpg"
width={300}
height={400}
loader={({ config, src, width }) => {
if (config) {
return 'https://example.vercel.sh/error-unexpected-config'
}
return `https://example.vercel.sh/success/${src}?width=${width}`
}}
/>
</div>
)
}
export default LoaderExample

View File

@@ -0,0 +1,3 @@
p {
color: red;
}

View File

@@ -0,0 +1,406 @@
/* eslint-env jest */
import {
check,
findPort,
killApp,
nextBuild,
nextStart,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const appDir = join(__dirname, '../')
let appPort
let app
let browser
const emptyImage =
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
function runTests() {
it('should render an image tag', async () => {
await waitFor(1000)
expect(await browser.hasElementByCssSelector('img')).toBeTruthy()
})
it('should support passing through arbitrary attributes', async () => {
expect(
await browser.hasElementByCssSelector('img#attribute-test')
).toBeTruthy()
expect(
await browser.elementByCss('img#attribute-test').getAttribute('data-demo')
).toBe('demo-value')
})
it('should modify src with the loader', async () => {
expect(await browser.elementById('basic-image').getAttribute('src')).toBe(
'https://example.com/myaccount/foo.jpg?auto=format&fit=max&w=1024&q=60'
)
})
it('should correctly generate src even if preceding slash is included in prop', async () => {
expect(
await browser.elementById('preceding-slash-image').getAttribute('src')
).toBe(
'https://example.com/myaccount/fooslash.jpg?auto=format&fit=max&w=1024'
)
})
it('should add a srcset based on the loader', async () => {
expect(
await browser.elementById('basic-image').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/foo.jpg?auto=format&fit=max&w=480&q=60 1x, https://example.com/myaccount/foo.jpg?auto=format&fit=max&w=1024&q=60 2x'
)
})
it('should add a srcset even with preceding slash in prop', async () => {
expect(
await browser.elementById('preceding-slash-image').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/fooslash.jpg?auto=format&fit=max&w=480 1x, https://example.com/myaccount/fooslash.jpg?auto=format&fit=max&w=1024 2x'
)
})
it('should use imageSizes when width matches, not deviceSizes from next.config.js', async () => {
expect(await browser.elementById('icon-image-16').getAttribute('src')).toBe(
'https://example.com/myaccount/icon.png?auto=format&fit=max&w=32'
)
expect(
await browser.elementById('icon-image-16').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/icon.png?auto=format&fit=max&w=16 1x, https://example.com/myaccount/icon.png?auto=format&fit=max&w=32 2x'
)
expect(await browser.elementById('icon-image-32').getAttribute('src')).toBe(
'https://example.com/myaccount/icon.png?auto=format&fit=max&w=64'
)
expect(
await browser.elementById('icon-image-32').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/icon.png?auto=format&fit=max&w=32 1x, https://example.com/myaccount/icon.png?auto=format&fit=max&w=64 2x'
)
})
it('should support the unoptimized attribute', async () => {
expect(
await browser.elementById('unoptimized-image').getAttribute('src')
).toBe('https://arbitraryurl.com/foo.jpg')
})
it('should not add a srcset if unoptimized attribute present', async () => {
expect(
await browser.elementById('unoptimized-image').getAttribute('srcset')
).toBeFalsy()
})
it('should keep auto parameter if already set', async () => {
expect(
await browser.elementById('image-with-param-auto').getAttribute('src')
).toBe('https://example.com/myaccount/foo.png?auto=compress&fit=max&w=1024')
})
it('should keep width parameter if already set', async () => {
expect(
await browser.elementById('image-with-param-width').getAttribute('src')
).toBe('https://example.com/myaccount/foo.png?auto=format&w=500&fit=max')
})
it('should keep fit parameter if already set', async () => {
expect(
await browser.elementById('image-with-param-fit').getAttribute('src')
).toBe(
'https://example.com/myaccount/foo.png?auto=format&fit=crop&w=300&h=300'
)
})
}
function lazyLoadingTests() {
it('should have loaded the first image immediately', async () => {
expect(await browser.elementById('lazy-top').getAttribute('src')).toBe(
'https://example.com/myaccount/lazy1.jpg?auto=format&fit=max&w=2000'
)
expect(await browser.elementById('lazy-top').getAttribute('srcset')).toBe(
'https://example.com/myaccount/lazy1.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/lazy1.jpg?auto=format&fit=max&w=2000 2x'
)
})
it('should not have loaded the second image immediately', async () => {
expect(await browser.elementById('lazy-mid').getAttribute('src')).toBe(
emptyImage
)
expect(
await browser.elementById('lazy-mid').getAttribute('srcset')
).toBeFalsy()
})
it('should pass through classes on a lazy loaded image', async () => {
expect(await browser.elementById('lazy-mid').getAttribute('class')).toBe(
'exampleclass'
)
})
it('should load the second image after scrolling down', async () => {
let viewportHeight = await browser.eval(`window.innerHeight`)
let topOfMidImage = await browser.eval(
`document.getElementById('lazy-mid').parentElement.offsetTop`
)
let buffer = 150
await browser.eval(
`window.scrollTo(0, ${topOfMidImage - (viewportHeight + buffer)})`
)
await check(() => {
return browser.elementById('lazy-mid').getAttribute('src')
}, 'https://example.com/myaccount/lazy2.jpg?auto=format&fit=max&w=1024')
await check(() => {
return browser.elementById('lazy-mid').getAttribute('srcset')
}, 'https://example.com/myaccount/lazy2.jpg?auto=format&fit=max&w=480 1x, https://example.com/myaccount/lazy2.jpg?auto=format&fit=max&w=1024 2x')
})
it('should not have loaded the third image after scrolling down', async () => {
expect(await browser.elementById('lazy-bottom').getAttribute('src')).toBe(
emptyImage
)
expect(
await browser.elementById('lazy-bottom').getAttribute('srcset')
).toBeFalsy()
})
it('should load the third image, which is unoptimized, after scrolling further down', async () => {
let viewportHeight = await browser.eval(`window.innerHeight`)
let topOfBottomImage = await browser.eval(
`document.getElementById('lazy-bottom').parentElement.offsetTop`
)
let buffer = 150
await browser.eval(
`window.scrollTo(0, ${topOfBottomImage - (viewportHeight + buffer)})`
)
await waitFor(200)
expect(await browser.elementById('lazy-bottom').getAttribute('src')).toBe(
'https://www.otherhost.com/lazy3.jpg'
)
expect(
await browser.elementById('lazy-bottom').getAttribute('srcset')
).toBeFalsy()
})
it('should load the fourth image lazily after scrolling down', async () => {
expect(
await browser.elementById('lazy-without-attribute').getAttribute('src')
).toBe(emptyImage)
expect(
await browser.elementById('lazy-without-attribute').getAttribute('srcset')
).toBeFalsy()
let viewportHeight = await browser.eval(`window.innerHeight`)
let topOfBottomImage = await browser.eval(
`document.getElementById('lazy-without-attribute').parentElement.offsetTop`
)
let buffer = 150
await browser.eval(
`window.scrollTo(0, ${topOfBottomImage - (viewportHeight + buffer)})`
)
await waitFor(200)
expect(
await browser.elementById('lazy-without-attribute').getAttribute('src')
).toBe('https://example.com/myaccount/lazy4.jpg?auto=format&fit=max&w=1600')
expect(
await browser.elementById('lazy-without-attribute').getAttribute('srcset')
).toBe(
'https://example.com/myaccount/lazy4.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/lazy4.jpg?auto=format&fit=max&w=1600 2x'
)
})
it('should load the fifth image eagerly, without scrolling', async () => {
expect(await browser.elementById('eager-loading').getAttribute('src')).toBe(
'https://example.com/myaccount/lazy5.jpg?auto=format&fit=max&w=2000'
)
expect(
await browser.elementById('eager-loading').getAttribute('srcset')
).toBeTruthy()
})
it('should load the sixth image, which has lazyBoundary property after scrolling down', async () => {
expect(await browser.elementById('lazy-boundary').getAttribute('src')).toBe(
emptyImage
)
expect(
await browser.elementById('lazy-boundary').getAttribute('srcset')
).toBeFalsy()
let viewportHeight = await browser.eval(`window.innerHeight`)
let topOfBottomImage = await browser.eval(
`document.getElementById('lazy-boundary').parentElement.offsetTop`
)
let buffer = 450
await browser.eval(
`window.scrollTo(0, ${topOfBottomImage - (viewportHeight + buffer)})`
)
await check(() => {
return browser.eval(
'document.querySelector("#lazy-boundary").getAttribute("src")'
)
}, 'https://example.com/myaccount/lazy6.jpg?auto=format&fit=max&w=1600')
await check(() => {
return browser.eval(
'document.querySelector("#lazy-boundary").getAttribute("srcset")'
)
}, 'https://example.com/myaccount/lazy6.jpg?auto=format&fit=max&w=1024 1x, https://example.com/myaccount/lazy6.jpg?auto=format&fit=max&w=1600 2x')
})
}
async function hasPreloadLinkMatchingUrl(url) {
const links = await browser.elementsByCss('link[rel=preload][as=image]')
for (const link of links) {
const imagesrcset = await link.getAttribute('imagesrcset')
const href = await link.getAttribute('href')
if (imagesrcset?.includes(url) || (!imagesrcset && href === url)) {
return true
}
}
return false
}
async function hasImagePreloadBeforeCSSPreload() {
const links = await browser.elementsByCss('link')
let foundImage = false
for (const link of links) {
const rel = await link.getAttribute('rel')
if (rel === 'preload') {
const linkAs = await link.getAttribute('as')
if (linkAs === 'image') {
foundImage = true
} else if (linkAs === 'style' && foundImage) {
return true
}
}
}
return false
}
describe('Image Component Tests', () => {
;(process.env.TURBOPACK_DEV ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
describe('SSR Image Component Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
})
afterAll(async () => {
browser = null
})
runTests()
it('should add a preload tag for a priority image', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriority.png?auto=format&fit=max&w=1024&q=60'
)
).toBe(true)
})
it('should add a preload tag for a priority image with preceding slash', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/fooslash.jpg?auto=format&fit=max&w=1024'
)
).toBe(true)
})
it('should add a preload tag for a priority image, with arbitrary host', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://arbitraryurl.com/withpriority3.png'
)
).toBe(true)
})
it('should add a preload tag for a priority image, with quality', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriority.png?auto=format&fit=max&w=1024&q=60'
)
).toBe(true)
})
it('should not create any preload tags higher up the page than CSS preload tags', async () => {
expect(await hasImagePreloadBeforeCSSPreload()).toBe(false)
})
it('should add data-nimg data attribute based on layout', async () => {
expect(
await browser
.elementById('image-with-sizes')
.getAttribute('data-nimg')
).toBe('responsive')
expect(
await browser.elementById('basic-image').getAttribute('data-nimg')
).toBe('intrinsic')
})
it('should not pass config to custom loader prop', async () => {
browser = await webdriver(appPort, '/loader-prop')
expect(
await browser.elementById('loader-prop-img').getAttribute('src')
).toBe('https://example.vercel.sh/success/foo.jpg?width=1024')
expect(
await browser.elementById('loader-prop-img').getAttribute('srcset')
).toBe(
'https://example.vercel.sh/success/foo.jpg?width=480 1x, https://example.vercel.sh/success/foo.jpg?width=1024 2x'
)
})
})
describe('Client-side Image Component Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#clientlink').click()
})
afterAll(async () => {
browser = null
})
runTests()
// FIXME: this test
it.skip('should NOT add a preload tag for a priority image', async () => {
expect(
await hasPreloadLinkMatchingUrl(
'https://example.com/myaccount/withpriorityclient.png?auto=format&fit=max'
)
).toBe(false)
})
it('should only be loaded once if `sizes` is set', async () => {
const numRequests = await browser.eval(`(function() {
const entries = window.performance.getEntries()
return entries.filter(function(entry) {
return entry.name.includes('test-sizes.jpg')
}).length
})()`)
expect(numRequests).toBe(1)
})
describe('Client-side Errors', () => {
beforeAll(async () => {
await browser.eval(`(function() {
window.gotHostError = false
const origError = console.error
window.console.error = function () {
if (arguments[0].match(/Image host identifier/)) {
window.gotHostError = true
}
origError.apply(this, arguments)
}
})()`)
await browser.waitForElementByCss('#errorslink').click()
})
it('Should not log an error when an unregistered host is used in production', async () => {
const foundError = await browser.eval('window.gotHostError')
expect(foundError).toBe(false)
})
})
})
describe('SSR Lazy Loading Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/lazy')
})
afterAll(async () => {
browser = null
})
lazyLoadingTests()
})
describe('Client-side Lazy Loading Tests', () => {
beforeAll(async () => {
browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#lazylink').click()
await waitFor(500)
})
afterAll(async () => {
browser = null
})
lazyLoadingTests()
})
}
)
})