diff --git a/packages/next/build/webpack/loaders/next-image-loader.js b/packages/next/build/webpack/loaders/next-image-loader.js index 06cce69a5469..af1fb8d0be60 100644 --- a/packages/next/build/webpack/loaders/next-image-loader.js +++ b/packages/next/build/webpack/loaders/next-image-loader.js @@ -28,6 +28,8 @@ function nextImageLoader(content) { getImageSize(content, extension) ) let blurDataURL + let blurWidth + let blurHeight if (VALID_BLUR_EXT.includes(extension)) { if (isDev) { @@ -39,18 +41,21 @@ function nextImageLoader(content) { blurDataURL = url.href.slice(prefix.length) } else { // Shrink the image's largest dimension - const dimension = - imageSize.width >= imageSize.height ? 'width' : 'height' + if (imageSize.width >= imageSize.height) { + blurWidth = BLUR_IMG_SIZE + blurHeight = Math.round( + (imageSize.height / imageSize.width) * BLUR_IMG_SIZE + ) + } else { + blurWidth = Math.round( + (imageSize.width / imageSize.height) * BLUR_IMG_SIZE + ) + blurHeight = BLUR_IMG_SIZE + } const resizeImageSpan = imageLoaderSpan.traceChild('image-resize') const resizedImage = await resizeImageSpan.traceAsyncFn(() => - resizeImage( - content, - dimension, - BLUR_IMG_SIZE, - extension, - BLUR_QUALITY - ) + resizeImage(content, blurWidth, blurHeight, extension, BLUR_QUALITY) ) const blurDataURLSpan = imageLoaderSpan.traceChild( 'image-base64-tostring' @@ -70,6 +75,8 @@ function nextImageLoader(content) { height: imageSize.height, width: imageSize.width, blurDataURL, + blurWidth, + blurHeight, }) ) diff --git a/packages/next/client/future/image.tsx b/packages/next/client/future/image.tsx index f3ffe5b7033e..f53be503d6d0 100644 --- a/packages/next/client/future/image.tsx +++ b/packages/next/client/future/image.tsx @@ -67,6 +67,8 @@ export interface StaticImageData { height: number width: number blurDataURL?: string + blurWidth?: number + blurHeight?: number } interface StaticRequire { @@ -578,6 +580,8 @@ export default function Image({ } let staticSrc = '' + let blurWidth: number | undefined + let blurHeight: number | undefined if (isStaticImport(src)) { const staticImageData = isStaticRequire(src) ? src.default : src @@ -588,6 +592,8 @@ export default function Image({ )}` ) } + blurWidth = staticImageData.blurWidth + blurHeight = staticImageData.blurHeight blurDataURL = blurDataURL || staticImageData.blurDataURL staticSrc = staticImageData.src @@ -785,16 +791,24 @@ export default function Image({ bottom: 0, } : {}, - showAltText || placeholder === 'blur' ? {} : { color: 'transparent' }, + showAltText ? {} : { color: 'transparent' }, style ) - const svgBlurPlaceholder = `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 ${widthInt} ${heightInt}'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='${blurDataURL}'/%3E%3C/svg%3E")` + const std = blurWidth && blurHeight ? '1' : '20' + const svgWidth = blurWidth || widthInt + const svgHeight = blurHeight || heightInt + const feComponentTransfer = blurDataURL?.startsWith('data:image/jpeg') + ? `%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%` + : '' + const svgBlurPlaceholder = `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 ${svgWidth} ${svgHeight}'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='${std}'/%3E${feComponentTransfer}%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='${blurDataURL}'/%3E%3C/svg%3E")` + const blurStyle = - placeholder === 'blur' && !blurComplete + placeholder === 'blur' && blurDataURL && !blurComplete ? { backgroundSize: imgStyle.objectFit || 'cover', - backgroundPosition: imgStyle.objectPosition || '0% 0%', - ...(blurDataURL?.startsWith('data:image') + backgroundPosition: imgStyle.objectPosition || '50% 50%', + backgroundRepeat: 'no-repeat', + ...(blurDataURL.startsWith('data:image') && svgWidth && svgHeight ? { backgroundImage: svgBlurPlaceholder, } diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts index 562e28d6aeb1..36d8921f8bbf 100644 --- a/packages/next/server/image-optimizer.ts +++ b/packages/next/server/image-optimizer.ts @@ -719,8 +719,8 @@ export function sendResponse( export async function resizeImage( content: Buffer, - dimension: 'width' | 'height', - size: number, + width: number, + height: number, // Should match VALID_BLUR_EXT extension: 'avif' | 'webp' | 'png' | 'jpeg', quality: number @@ -748,18 +748,11 @@ export async function resizeImage( } else if (extension === 'jpeg') { transformer.jpeg({ quality }) } - if (dimension === 'width') { - transformer.resize(size) - } else { - transformer.resize(null, size) - } + transformer.resize(width, height) const buf = await transformer.toBuffer() return buf } else { - const resizeOperationOpts: Operation = - dimension === 'width' - ? { type: 'resize', width: size } - : { type: 'resize', height: size } + const resizeOperationOpts: Operation = { type: 'resize', width, height } const buf = await processBuffer( content, [resizeOperationOpts], diff --git a/packages/next/server/lib/squoosh/impl.ts b/packages/next/server/lib/squoosh/impl.ts index 8b5577304823..182664e7e35d 100644 --- a/packages/next/server/lib/squoosh/impl.ts +++ b/packages/next/server/lib/squoosh/impl.ts @@ -62,6 +62,7 @@ export async function rotate( type ResizeOpts = { image: ImageData } & ( | { width: number; height?: never } | { height: number; width?: never } + | { height: number; width: number } ) export async function resize({ image, width, height }: ResizeOpts) { diff --git a/packages/next/server/lib/squoosh/main.ts b/packages/next/server/lib/squoosh/main.ts index 15cc49b3c1d7..2d326cbf1cc2 100644 --- a/packages/next/server/lib/squoosh/main.ts +++ b/packages/next/server/lib/squoosh/main.ts @@ -9,7 +9,11 @@ type RotateOperation = { } type ResizeOperation = { type: 'resize' -} & ({ width: number; height?: never } | { height: number; width?: never }) +} & ( + | { width: number; height?: never } + | { height: number; width?: never } + | { width: number; height: number } +) export type Operation = RotateOperation | ResizeOperation export type Encoding = 'jpeg' | 'png' | 'webp' | 'avif' @@ -37,24 +41,24 @@ export async function processBuffer( if (operation.type === 'rotate') { imageData = await worker.rotate(imageData, operation.numRotations) } else if (operation.type === 'resize') { + const opt = { image: imageData, width: 0, height: 0 } if ( operation.width && imageData.width && imageData.width > operation.width ) { - imageData = await worker.resize({ - image: imageData, - width: operation.width, - }) - } else if ( + opt.width = operation.width + } + if ( operation.height && imageData.height && imageData.height > operation.height ) { - imageData = await worker.resize({ - image: imageData, - height: operation.height, - }) + opt.height = operation.height + } + + if (opt.width > 0 || opt.height > 0) { + imageData = await worker.resize(opt) } } } diff --git a/test/integration/image-future/base-path/test/static.test.js b/test/integration/image-future/base-path/test/static.test.js index 0a55ccc0d9a5..fa262880c619 100644 --- a/test/integration/image-future/base-path/test/static.test.js +++ b/test/integration/image-future/base-path/test/static.test.js @@ -5,9 +5,11 @@ import { nextStart, renderViaHTTP, File, + launchApp, waitFor, } from 'next-test-utils' import webdriver from 'next-webdriver' +import cheerio from 'cheerio' import { join } from 'path' const appDir = join(__dirname, '../') @@ -15,10 +17,11 @@ let appPort let app let browser let html +let $ const indexPage = new File(join(appDir, 'pages/static-img.js')) -const runTests = () => { +const runTests = (isDev) => { it('Should allow an image with a static src to omit height and width', async () => { expect(await browser.elementById('basic-static')).toBeTruthy() expect(await browser.elementById('blur-png')).toBeTruthy() @@ -31,42 +34,71 @@ const runTests = () => { expect(await browser.elementById('static-ico')).toBeTruthy() expect(await browser.elementById('static-unoptimized')).toBeTruthy() }) - it('Should use immutable cache-control header for static import', async () => { - await browser.eval( - `document.getElementById("basic-static").scrollIntoView()` - ) - await waitFor(1000) - const url = await browser.eval( - `document.getElementById("basic-static").src` - ) - const res = await fetch(url) - expect(res.headers.get('cache-control')).toBe( - 'public, max-age=315360000, immutable' - ) - }) - it('Should use immutable cache-control header even when unoptimized', async () => { - await browser.eval( - `document.getElementById("static-unoptimized").scrollIntoView()` - ) - await waitFor(1000) - const url = await browser.eval( - `document.getElementById("static-unoptimized").src` - ) - const res = await fetch(url) - expect(res.headers.get('cache-control')).toBe( - 'public, max-age=31536000, immutable' - ) - }) + if (!isDev) { + // cache-control is set to "0, no-store" in dev mode + it('Should use immutable cache-control header for static import', async () => { + await browser.eval( + `document.getElementById("basic-static").scrollIntoView()` + ) + await waitFor(1000) + const url = await browser.eval( + `document.getElementById("basic-static").src` + ) + const res = await fetch(url) + expect(res.headers.get('cache-control')).toBe( + 'public, max-age=315360000, immutable' + ) + }) + + it('Should use immutable cache-control header even when unoptimized', async () => { + await browser.eval( + `document.getElementById("static-unoptimized").scrollIntoView()` + ) + await waitFor(1000) + const url = await browser.eval( + `document.getElementById("static-unoptimized").src` + ) + const res = await fetch(url) + expect(res.headers.get('cache-control')).toBe( + 'public, max-age=31536000, immutable' + ) + }) + } it('Should automatically provide an image height and width', async () => { - expect(html).toContain('width="400" height="300"') + const img = $('#basic-non-static') + expect(img.attr('width')).toBe('400') + expect(img.attr('height')).toBe('300') }) it('Should allow provided width and height to override intrinsic', async () => { - expect(html).toContain('width="150" height="150"') + const img = $('#defined-size-static') + expect(img.attr('width')).toBe('150') + expect(img.attr('height')).toBe('150') }) - it('Should add a blur to a statically imported image', async () => { - expect(html).toContain( - `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` - ) + + it('Should add a blur placeholder a statically imported jpg', async () => { + const style = $('#basic-static').attr('style') + if (isDev) { + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;filter:blur(20px);background-image:url("/docs/_next/image?url=%2Fdocs%2F_next%2Fstatic%2Fmedia%2Ftest-rect.f323a148.jpg&w=8&q=70")` + ) + } else { + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 8 6'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='1'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` + ) + } + }) + + it('Should add a blur placeholder a statically imported png', async () => { + const style = $('#blur-png').attr('style') + if (isDev) { + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;filter:blur(20px);background-image:url("/docs/_next/image?url=%2Fdocs%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=8&q=70")` + ) + } else { + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='1'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAAOklEQVR42iWGsQkAIBDE0iuIdiLOJjiGIzjiL/Meb4okiNYIlLjK3hJMzCQG1/0qmXXOUkjAV+m9wAMe3QiV6Ne8VgAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + ) + } }) } @@ -90,15 +122,32 @@ describe('Build Error Tests', () => { }) }) describe('Future Static Image Component Tests for basePath', () => { - beforeAll(async () => { - await nextBuild(appDir) - appPort = await findPort() - app = await nextStart(appDir, appPort) - html = await renderViaHTTP(appPort, '/docs/static-img') - browser = await webdriver(appPort, '/docs/static-img') + describe('production mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + html = await renderViaHTTP(appPort, '/docs/static-img') + $ = cheerio.load(html) + browser = await webdriver(appPort, '/docs/static-img') + }) + afterAll(() => { + killApp(app) + }) + runTests(false) }) - afterAll(() => { - killApp(app) + + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + html = await renderViaHTTP(appPort, '/docs/static-img') + $ = cheerio.load(html) + browser = await webdriver(appPort, '/docs/static-img') + }) + afterAll(() => { + killApp(app) + }) + runTests(true) }) - runTests() }) diff --git a/test/integration/image-future/default/pages/static-img.js b/test/integration/image-future/default/pages/static-img.js index 6b6b4cfd5739..472c5906387e 100644 --- a/test/integration/image-future/default/pages/static-img.js +++ b/test/integration/image-future/default/pages/static-img.js @@ -32,15 +32,22 @@ const Page = () => { +
+

+ + + + +
) diff --git a/test/integration/image-future/default/test/index.test.ts b/test/integration/image-future/default/test/index.test.ts index 0b779d5fa4d8..d10c94adec38 100644 --- a/test/integration/image-future/default/test/index.test.ts +++ b/test/integration/image-future/default/test/index.test.ts @@ -536,7 +536,7 @@ function runTests(mode) { ) expect(await browser.elementById('blur1').getAttribute('sizes')).toBeNull() expect(await browser.elementById('blur1').getAttribute('style')).toMatch( - 'background-size:cover;background-position:0% 0%;' + 'color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;' ) expect(await browser.elementById('blur1').getAttribute('height')).toBe( '400' @@ -559,7 +559,9 @@ function runTests(mode) { 'lazy' ) expect(await browser.elementById('blur1').getAttribute('sizes')).toBeNull() - expect(await browser.elementById('blur1').getAttribute('style')).toMatch('') + expect(await browser.elementById('blur1').getAttribute('style')).toBe( + 'color: transparent;' + ) expect(await browser.elementById('blur1').getAttribute('height')).toBe( '400' ) @@ -579,7 +581,7 @@ function runTests(mode) { 'lazy' ) expect(await browser.elementById('blur2').getAttribute('style')).toMatch( - 'background-size:cover;background-position:0% 0%;' + 'color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat' ) expect(await browser.elementById('blur2').getAttribute('height')).toBe( '400' @@ -604,7 +606,9 @@ function runTests(mode) { expect(await browser.elementById('blur2').getAttribute('loading')).toBe( 'lazy' ) - expect(await browser.elementById('blur2').getAttribute('style')).toBe('') + expect(await browser.elementById('blur2').getAttribute('style')).toBe( + 'color: transparent;' + ) expect(await browser.elementById('blur2').getAttribute('height')).toBe( '400' ) @@ -1082,11 +1086,11 @@ function runTests(mode) { $html('noscript > img').attr('id', 'unused') expect($html('#blurry-placeholder-raw')[0].attribs.style).toContain( - `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8P4nhDwAGuAKPn6cicwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8P4nhDwAGuAKPn6cicwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` ) expect($html('#blurry-placeholder-with-lazy')[0].attribs.style).toContain( - `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` ) }) @@ -1096,7 +1100,7 @@ function runTests(mode) { const img = $html('noscript > img')[0] expect(img).toBeDefined() expect(img.attribs.id).toBe('blurry-placeholder-raw') - expect(img.attribs.style).toBeUndefined() + expect(img.attribs.style).toBe('color:transparent') }) it('should remove blurry placeholder after image loads', async () => { @@ -1117,7 +1121,7 @@ function runTests(mode) { 'background-image' ) ).toBe( - `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` ) await browser.eval('document.getElementById("spacer").remove()') diff --git a/test/integration/image-future/default/test/static.test.ts b/test/integration/image-future/default/test/static.test.ts index e40d548ba17d..2bd883d8976c 100644 --- a/test/integration/image-future/default/test/static.test.ts +++ b/test/integration/image-future/default/test/static.test.ts @@ -9,6 +9,7 @@ import { launchApp, } from 'next-test-utils' import webdriver from 'next-webdriver' +import cheerio from 'cheerio' import { join } from 'path' const appDir = join(__dirname, '../') @@ -16,10 +17,11 @@ let appPort let app let browser let html +let $ const indexPage = new File(join(appDir, 'pages/static-img.js')) -const runTests = (isDev = false) => { +const runTests = (isDev) => { it('Should allow an image with a static src to omit height and width', async () => { expect(await browser.elementById('basic-static')).toBeTruthy() expect(await browser.elementById('blur-png')).toBeTruthy() @@ -68,22 +70,51 @@ const runTests = (isDev = false) => { }) } it('Should automatically provide an image height and width', async () => { - expect(html).toContain('width="400" height="300"') + const img = $('#basic-non-static') + expect(img.attr('width')).toBe('400') + expect(img.attr('height')).toBe('300') }) it('Should allow provided width and height to override intrinsic', async () => { - expect(html).toContain('width="150" height="150"') + const img = $('#defined-size-static') + expect(img.attr('width')).toBe('150') + expect(img.attr('height')).toBe('150') }) - it('Should add a blur to a statically imported image in "raw" mode', async () => { - // next-image-loader uses different blur behavior for dev/prod mode - // See next/build/webpack/loaders/next-image-loader + it('Should add a blur placeholder a statically imported jpg', async () => { + const style = $('#basic-static').attr('style') if (isDev) { - expect(html).toContain( - `style="background-size:cover;background-position:0% 0%;filter:blur(20px);background-image:url("/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest-rect.f323a148.jpg&w=8&q=70")"` + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;filter:blur(20px);background-image:url("/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest-rect.f323a148.jpg&w=8&q=70")` ) } else { - expect(html).toContain( - `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 8 6'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='1'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` + ) + } + }) + + it('Should add a blur placeholder a statically imported png', async () => { + const style = $('#blur-png').attr('style') + if (isDev) { + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;filter:blur(20px);background-image:url("/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=8&q=70")` + ) + } else { + expect(style).toBe( + `color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='1'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAAOklEQVR42iWGsQkAIBDE0iuIdiLOJjiGIzjiL/Meb4okiNYIlLjK3hJMzCQG1/0qmXXOUkjAV+m9wAMe3QiV6Ne8VgAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + ) + } + }) + + it('Should add a blur placeholder a statically imported png with fill', async () => { + const style = $('#blur-png-fill').attr('style') + if (isDev) { + expect(style).toBe( + `position:absolute;height:100%;width:100%;left:0;top:0;right:0;bottom:0;color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;filter:blur(20px);background-image:url("/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftest.3f1a293b.png&w=8&q=70")` + ) + } else { + expect(style).toBe( + `position:absolute;height:100%;width:100%;left:0;top:0;right:0;bottom:0;color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='1'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAAOklEQVR42iWGsQkAIBDE0iuIdiLOJjiGIzjiL/Meb4okiNYIlLjK3hJMzCQG1/0qmXXOUkjAV+m9wAMe3QiV6Ne8VgAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` ) } }) @@ -115,6 +146,7 @@ describe('Future Static Image Component Tests', () => { appPort = await findPort() app = await nextStart(appDir, appPort) html = await renderViaHTTP(appPort, '/static-img') + $ = cheerio.load(html) browser = await webdriver(appPort, '/static-img') }) afterAll(() => { @@ -128,6 +160,7 @@ describe('Future Static Image Component Tests', () => { appPort = await findPort() app = await launchApp(appDir, appPort) html = await renderViaHTTP(appPort, '/static-img') + $ = cheerio.load(html) browser = await webdriver(appPort, '/static-img') }) afterAll(() => {