Skip to content

Commit

Permalink
Merge pull request #3 from storyofams/feat/tests
Browse files Browse the repository at this point in the history
Feat/tests
  • Loading branch information
BJvdA authored Apr 4, 2021
2 parents 1395890 + 500aad7 commit 9bd710c
Show file tree
Hide file tree
Showing 8 changed files with 560 additions and 13 deletions.
6 changes: 0 additions & 6 deletions src/image/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,12 @@ export const Image = ({
return;
} else {
// Use IntersectionObserver as fallback
if (observer.current) observer.current.disconnect();

if (imgRef.current) {
addIntersectionObserver();
}

return () => {
if (observer.current) {
if (imgRef.current) {
observer.current.unobserve(imgRef.current);
}

observer.current.disconnect();
}
};
Expand Down
10 changes: 3 additions & 7 deletions src/image/Picture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,9 @@ export const Picture = forwardRef(
) => {
const splitSrc = src?.split('/f/');
const webpSrc = `${splitSrc[0]}/filters:format(webp)/f/${splitSrc[1]}`;
let webpSrcset = srcSet || webpSrc;

if (webpSrcset) {
webpSrcset = webpSrcset
.replace(/\/filters:(.*)\/f\//gm, '/filters:$1:format(webp)/f/')
.replace(/\/(?!filters:)([^/]*)\/f\//gm, '/$1/filters:format(webp)/f/');
}
const webpSrcset = (srcSet || webpSrc)
.replace(/\/filters:(.*)\/f\//gm, '/filters:$1:format(webp)/f/')
.replace(/\/(?!filters:)([^/]*)\/f\//gm, '/$1/filters:format(webp)/f/');

return (
<picture>
Expand Down
213 changes: 213 additions & 0 deletions src/image/__tests__/Image.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import * as React from 'react';
import { cleanup, render, act, screen, waitFor } from '@testing-library/react';
import { unmountComponentAtNode } from 'react-dom';

import { createIntersectionObserver } from '../createIntersectionObserver';
import * as helpers from '../helpers';
import { Image } from '../Image';

const storyblokImage =
'https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg';

jest.mock('../createIntersectionObserver');

describe('[image] Image', () => {
afterEach(() => {
cleanup();
jest.restoreAllMocks();
});

it('should render an image with the src to load', async () => {
act(() => {
render(<Image alt="flowers" src={storyblokImage} />);
});

expect(screen.getByAltText('')).toHaveStyle('opacity: 1');

expect(screen.getByAltText('flowers')).not.toHaveAttribute('src');
expect(screen.getByAltText('flowers')).toHaveAttribute('data-src');
});

it('should let native loading handle loading if supported', async () => {
global.HTMLImageElement.prototype.loading = 'lazy';

act(() => {
render(<Image alt="flowers" src={storyblokImage} />);
});

expect(screen.getByAltText('flowers')).toHaveAttribute('src');
});

it('should use io as loading fallback', async () => {
global.HTMLImageElement.prototype.loading = undefined;
delete global.HTMLImageElement.prototype.loading;

const setLoadingMock = jest.fn();

jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => [false, setLoadingMock]);

const disconnect = jest.fn();
const createIoMock = jest.fn(() => ({
disconnect,
}));

(createIntersectionObserver as jest.Mock).mockReset();
(createIntersectionObserver as jest.Mock).mockImplementation(createIoMock);

act(() => {
render(<Image alt="flowers" src={storyblokImage} />);
});

await waitFor(() =>
expect(createIntersectionObserver as jest.Mock).toHaveBeenCalled(),
);

act(() => {
((createIntersectionObserver as jest.Mock).mock as any).calls[0][1]();
});

expect(setLoadingMock).toHaveBeenCalledWith(true);
});

it('should disconnect io on unmount', async () => {
global.HTMLImageElement.prototype.loading = undefined;
delete global.HTMLImageElement.prototype.loading;

const container = document.createElement('div');
document.body.appendChild(container);

jest.spyOn(React, 'useRef').mockReturnValueOnce({
current: { src: storyblokImage },
});

const disconnect = jest.fn();

(createIntersectionObserver as jest.Mock).mockImplementation(() => ({
disconnect,
}));

act(() => {
render(<Image alt="flowers" src={storyblokImage} />, { container });
});

expect(disconnect).not.toHaveBeenCalled();

unmountComponentAtNode(container);

await waitFor(() => expect(disconnect).toHaveBeenCalled());
});

it('should not add io if already loading', async () => {
global.HTMLImageElement.prototype.loading = undefined;

const disconnect = jest.fn();
const createIoMock = jest.fn(() => ({
disconnect,
}));

(createIntersectionObserver as jest.Mock).mockImplementation(createIoMock);

act(() => {
render(<Image alt="flowers" src={storyblokImage} lazy={false} />);
});

expect(createIoMock).not.toHaveBeenCalled();
});

it('should not add io if no image ref', async () => {
global.HTMLImageElement.prototype.loading = undefined;
delete global.HTMLImageElement.prototype.loading;

const disconnect = jest.fn();
const createIoMock = jest.fn(() => ({
disconnect,
}));

(createIntersectionObserver as jest.Mock).mockReset();
(createIntersectionObserver as jest.Mock).mockImplementation(createIoMock);

let ref = {} as any;
Object.defineProperty(ref, 'current', {
get: jest.fn(() => false),
set: jest.fn(),
});
jest.spyOn(React, 'useRef').mockReturnValue(ref);

act(() => {
render(<Image alt="flowers" src={storyblokImage} />);
});

await waitFor(() =>
expect(createIntersectionObserver as jest.Mock).not.toHaveBeenCalled(),
);

ref = {};
Object.defineProperty(ref, 'current', {
get: jest.fn(() => true),
set: jest.fn(),
});
jest.spyOn(React, 'useRef').mockReturnValueOnce(ref);

act(() => {
render(<Image alt="flowers" src={storyblokImage} />);
});

await waitFor(() =>
expect(createIntersectionObserver as jest.Mock).toHaveBeenCalled(),
);
});

it('should hide placeholder on load', async () => {
global.HTMLImageElement.prototype.loading = 'lazy';

jest.spyOn(helpers, 'useImageLoader').mockImplementation(() => ({
onLoad: jest.fn(),
isLoaded: true,
setLoaded: jest.fn(),
}));

act(() => {
render(<Image alt="flowers" src={storyblokImage} />);
});

expect(screen.getByAltText('')).toHaveStyle('opacity: 0');
expect(screen.getByAltText('flowers')).toHaveAttribute('src');
expect(screen.getByAltText('flowers')).not.toHaveAttribute('data-src');
});

it('should set loaded if img complete', async () => {
global.HTMLImageElement.prototype.loading = 'lazy';

const setLoaded = jest.fn();

jest.spyOn(console, 'error').mockImplementation(jest.fn());

jest.spyOn(helpers, 'useImageLoader').mockImplementation(() => ({
onLoad: jest.fn(),
isLoaded: false,
setLoaded,
}));

jest.spyOn(React, 'useRef').mockImplementation(() => ({
current: { src: 'image.png', complete: true },
}));

act(() => {
render(<Image alt="flowers" src="image.png" />);
});

expect(setLoaded).toHaveBeenCalled();
});

it('should render null if src is not a storyblok asset', async () => {
jest.spyOn(console, 'error').mockImplementation(jest.fn());

act(() => {
render(<Image data-testid="img" src="http://localhost/test.png" />);
});

expect(screen.queryByTestId('img')).toBeNull();
});
});
53 changes: 53 additions & 0 deletions src/image/__tests__/Picture.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import { cleanup, render, act, screen } from '@testing-library/react';

import { Picture } from '../Picture';

const storyblokImage =
'https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg';

describe('[image] Picture', () => {
afterEach(() => {
cleanup();
jest.restoreAllMocks();
});

it('should not load src initially', async () => {
act(() => {
render(<Picture alt="flowers" src={storyblokImage} />);
});

expect(screen.getByAltText('flowers')).not.toHaveAttribute('src');
expect(screen.getByAltText('flowers')).toHaveAttribute('data-src');
});

it('should set alt to empty string if undefined', async () => {
act(() => {
render(<Picture src={storyblokImage} />);
});

expect(screen.getByAltText('')).toBeInTheDocument();
});

it('should add webp srcset', async () => {
act(() => {
render(<Picture alt="flowers" src={storyblokImage} />);
});

expect(
screen.getByAltText('flowers').parentElement.childNodes[0],
).toHaveAttribute('type', 'image/webp');
expect(
(screen.getByAltText('flowers').parentElement
.childNodes[0] as any).getAttribute('srcSet'),
).toMatch(/filters:format\(webp\)/);
});

it('should load eager if not lazy', async () => {
act(() => {
render(<Picture alt="flowers" src={storyblokImage} lazy={false} />);
});

expect(screen.getByAltText('flowers')).toHaveAttribute('loading', 'eager');
});
});
Loading

1 comment on commit 9bd710c

@vercel
Copy link

@vercel vercel bot commented on 9bd710c Apr 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.