diff --git a/packages/gatsby-plugin-image/package.json b/packages/gatsby-plugin-image/package.json index 20e79febe5e93..1221523bf69d8 100644 --- a/packages/gatsby-plugin-image/package.json +++ b/packages/gatsby-plugin-image/package.json @@ -48,6 +48,7 @@ "devDependencies": { "@babel/cli": "^7.8.7", "@babel/core": "^7.8.7", + "@testing-library/react": "^11.1.1", "@types/babel__core": "^7.1.12", "@types/babel__traverse": "^7.0.15", "@types/fs-extra": "^8.1.0", diff --git a/packages/gatsby-plugin-image/src/components/__tests__/gatsby-image.server.tsx b/packages/gatsby-plugin-image/src/components/__tests__/gatsby-image.server.tsx new file mode 100644 index 0000000000000..60fb237a7fe7a --- /dev/null +++ b/packages/gatsby-plugin-image/src/components/__tests__/gatsby-image.server.tsx @@ -0,0 +1,422 @@ +import React from "react" +import { render, screen } from "@testing-library/react" +import { GatsbyImage } from "../gatsby-image.server" +import { ISharpGatsbyImageData } from "../gatsby-image.browser" +import { SourceProps } from "../picture" + +type GlobalOverride = NodeJS.Global & + typeof global.globalThis & { + SERVER: boolean + GATSBY___IMAGE: boolean + } + +// Prevents terser for bailing because we're not in a babel plugin +jest.mock(`../../../macros/terser.macro`, () => (strs): string => strs.join(``)) + +describe(`GatsbyImage server`, () => { + beforeEach(() => { + console.warn = jest.fn() + ;(global as GlobalOverride).SERVER = true + ;(global as GlobalOverride).GATSBY___IMAGE = true + }) + + afterEach(() => { + jest.clearAllMocks() + ;(global as GlobalOverride).SERVER = undefined + ;(global as GlobalOverride).GATSBY___IMAGE = undefined + }) + + it(`shows nothing when the image props is not passed`, () => { + // Allows to get rid of typescript error when not passing image + // This is helpful for user using JavaScript and not getting advent of + // TS types + const GatsbyImageAny = GatsbyImage as React.FC + const { container } = render() + + // Verifying implementation details but it's for the UX, acceptable tradeoffs + expect(console.warn).toBeCalledWith( + `[gatsby-plugin-image] Missing image prop` + ) + expect(container.firstChild).toBeNull() + }) + + describe(`style verifications`, () => { + it(`has a valid style attributes for fluid layout`, () => { + const layout = `fluid` + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout, + images: { sources: [] }, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + render( + + ) + + const wrapper = document.querySelector(`[data-gatsby-image-wrapper=""]`) + expect((wrapper as HTMLElement).style).toMatchInlineSnapshot(` + CSSStyleDeclaration { + "0": "position", + "_importants": Object { + "position": undefined, + }, + "_length": 1, + "_onChange": [Function], + "_values": Object { + "position": "relative", + }, + } + `) + }) + + it(`has a valid style attributes for fixed layout`, () => { + const layout = `fixed` + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout, + images: { sources: [] }, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + render( + + ) + + const wrapper = document.querySelector(`[data-gatsby-image-wrapper=""]`) + expect((wrapper as HTMLElement).style).toMatchInlineSnapshot(` + CSSStyleDeclaration { + "0": "position", + "1": "width", + "2": "height", + "_importants": Object { + "height": undefined, + "position": undefined, + "width": undefined, + }, + "_length": 3, + "_onChange": [Function], + "_values": Object { + "height": "100px", + "position": "relative", + "width": "100px", + }, + } + `) + }) + + it(`has a valid style attributes for constrained layout`, () => { + const layout = `constrained` + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout, + images: { sources: [] }, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + render( + + ) + + const wrapper = document.querySelector(`[data-gatsby-image-wrapper=""]`) + expect((wrapper as HTMLElement).style).toMatchInlineSnapshot(` + CSSStyleDeclaration { + "0": "position", + "1": "display", + "_importants": Object { + "display": undefined, + "position": undefined, + }, + "_length": 2, + "_onChange": [Function], + "_values": Object { + "display": "inline-block", + "position": "relative", + }, + } + `) + }) + }) + + describe(`fallback verifications`, () => { + it(`doesn't have an src or srcSet when fallback is not provided in images`, () => { + // no fallback provided + const images = {} + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout: `constrained`, + images, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + render( + + ) + + const img = screen.getByRole(`img`) + expect(img).toMatchInlineSnapshot(` + A fake image for testing purpose + `) + }) + + it(`has a valid src value when fallback is provided in images`, () => { + const images = { fallback: { src: `some-src-fallback.jpg` } } + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout: `constrained`, + images, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + render( + + ) + + const img = screen.getByRole(`img`) + expect(img).toMatchInlineSnapshot(` + A fake image for testing purpose + `) + }) + + it(`has a valid srcSet value when provided in the fallback prop of images`, () => { + const images = { + fallback: { + src: `some-src-fallback.jpg`, + srcSet: `icon32px.png 32w, +icon64px.png 64w, +icon-retina.png 2x, +icon-ultra.png 3x, +icon.svg`, + }, + } + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout: `constrained`, + images, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + render( + + ) + + const img = screen.getByRole(`img`) + expect(img).toMatchInlineSnapshot(` + A fake image for testing purpose + `) + }) + }) + + describe(`sources verifications`, () => { + it(`doesn't have an src or srcSet when sources is not provided in images`, () => { + // no fallback provided + const images = {} + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout: `constrained`, + images, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + render( + + ) + + const img = screen.getByRole(`img`) + expect(img).toMatchInlineSnapshot(` + A fake image for testing purpose + `) + }) + + it(`has valid sizes and srcSet when provided in the images`, () => { + const sources: Array = [ + { + media: `some-media`, + sizes: `192x192,56x56`, + srcSet: `icon32px.png 32w, +icon64px.png 64w, +icon-retina.png 2x, +icon-ultra.png 3x, +icon.svg`, + }, + ] + + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout: `constrained`, + images: { sources }, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + const { container } = render( + + ) + + const picture = container.querySelector(`picture`) + + expect(picture).toMatchInlineSnapshot(` + + + A fake image for testing purpose + + `) + }) + }) + + describe(`placeholder verifications`, () => { + it(`has a placeholder in a div with valid styles for fluid layout`, () => { + const image: ISharpGatsbyImageData = { + width: 100, + height: 100, + layout: `fluid`, + images: {}, + placeholder: { sources: [] }, + sizes: `192x192`, + backgroundColor: `red`, + } + + const { container } = render( + + ) + const placeholder = container.querySelector(`[data-placeholder-image=""]`) + + expect(placeholder).toMatchInlineSnapshot(` +