From 7f869370e0a64b79038b32282e64e2ac507b9321 Mon Sep 17 00:00:00 2001 From: Bart van den Aardweg Date: Fri, 26 Mar 2021 15:20:18 +0100 Subject: [PATCH 1/6] test: start on image tests --- src/image/__tests__/Image.test.tsx | 69 +++++++++++++++++++++++ src/image/__tests__/getImageProps.test.ts | 59 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/image/__tests__/Image.test.tsx create mode 100644 src/image/__tests__/getImageProps.test.ts diff --git a/src/image/__tests__/Image.test.tsx b/src/image/__tests__/Image.test.tsx new file mode 100644 index 0000000..4557173 --- /dev/null +++ b/src/image/__tests__/Image.test.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { cleanup, render, act, screen, waitFor } from '@testing-library/react'; + +import { Image } from '../Image'; + +const storyblokImage = + 'https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg'; + +describe('[image] Image', () => { + afterEach(() => { + cleanup(); + jest.restoreAllMocks(); + }); + + it('should render an image with the src to load', async () => { + act(() => { + render(flowers); + }); + + 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(flowers); + }); + + expect(screen.getByAltText('flowers')).toHaveAttribute('src'); + }); + + it('should create io as loading fallback', async () => { + const observe = jest.fn(); + const unobserve = jest.fn(); + const disconnect = jest.fn(); + + delete global.window.location; + global.window = Object.create(window); + global.window.IntersectionObserver = jest.fn(() => ({ + observe, + unobserve, + disconnect, + })) as any; + + act(() => { + render(flowers); + }); + + await waitFor(() => + expect(screen.getByAltText('flowers')).toHaveAttribute('src'), + ); + + expect(observe).toHaveBeenCalled(); + }); + + it('should render null if src is not a storyblok asset', async () => { + jest.spyOn(console, 'error').mockImplementation(jest.fn()); + + act(() => { + render(); + }); + + expect(screen.queryByTestId('img')).toBeNull(); + }); +}); diff --git a/src/image/__tests__/getImageProps.test.ts b/src/image/__tests__/getImageProps.test.ts new file mode 100644 index 0000000..f155d1b --- /dev/null +++ b/src/image/__tests__/getImageProps.test.ts @@ -0,0 +1,59 @@ +import { getImageProps } from '../getImageProps'; + +const storyblokImage = + 'https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg'; + +describe('[image] getImageProps', () => { + it('should return normal src if fixed and fluid not set', async () => { + const props = getImageProps(storyblokImage); + + expect(props.src).toBeDefined(); + expect(props.width).toBe(3310); + expect(props.height).toBe(2192); + }); + + it('should optimize props for fixed', async () => { + const props = getImageProps(storyblokImage, { fixed: [200, 200] }); + + expect(props.src).toBeDefined(); + expect(props.srcSet).toContain(' 1x'); + expect(props.srcSet).toContain(' 2x'); + expect(props.srcSet).toContain(' 3x'); + expect(props.width).toBe(3310); + expect(props.height).toBe(2192); + }); + + it('should optimize props for fluid', async () => { + const props = getImageProps(storyblokImage, { fluid: 1080 }); + + expect(props.src).toBeDefined(); + expect(props.sizes).toBeDefined(); + expect(props.srcSet).toMatch(/(.*\dw.*){5}/gim); + expect(props.width).toBe(3310); + expect(props.height).toBe(2192); + }); + + it('should not put fluid sizes that are larger than original', async () => { + const props = getImageProps(storyblokImage, { fluid: 5000 }); + + expect(props.srcSet).toMatch(/(.*\dw.*){3}/gim); + }); + + it('should support width and height fluid', async () => { + const props = getImageProps(storyblokImage, { fluid: [1920, 1080] }); + + expect(props.srcSet).toContain('x1080'); + }); + + it('should not set smart filter if configured', async () => { + const props = getImageProps(storyblokImage, { smart: false }); + + expect(props.src).not.toContain('/smart'); + }); + + it('should return empty props if no src', async () => { + const props = getImageProps(''); + + expect(props).toMatchObject({}); + }); +}); From e165ceb9fc67c2c5e4c8b756a76350189de87e8f Mon Sep 17 00:00:00 2001 From: Bart van den Aardweg Date: Mon, 29 Mar 2021 00:06:47 +0200 Subject: [PATCH 2/6] test(image): tests for io, picture, helpers --- src/image/Picture.tsx | 10 +- src/image/__tests__/Image.test.tsx | 61 +++++-- src/image/__tests__/Picture.test.tsx | 53 ++++++ .../createIntersectionObserver.test.tsx | 156 ++++++++++++++++++ src/image/__tests__/helpers.test.tsx | 75 +++++++++ src/image/createIntersectionObserver.ts | 1 + 6 files changed, 336 insertions(+), 20 deletions(-) create mode 100644 src/image/__tests__/Picture.test.tsx create mode 100644 src/image/__tests__/createIntersectionObserver.test.tsx create mode 100644 src/image/__tests__/helpers.test.tsx diff --git a/src/image/Picture.tsx b/src/image/Picture.tsx index 0f340b1..adfc488 100644 --- a/src/image/Picture.tsx +++ b/src/image/Picture.tsx @@ -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 ( diff --git a/src/image/__tests__/Image.test.tsx b/src/image/__tests__/Image.test.tsx index 4557173..c634146 100644 --- a/src/image/__tests__/Image.test.tsx +++ b/src/image/__tests__/Image.test.tsx @@ -1,11 +1,14 @@ -import React from 'react'; +import * as React from 'react'; import { cleanup, render, act, screen, waitFor } from '@testing-library/react'; +import * as helpers from '../helpers'; import { Image } from '../Image'; const storyblokImage = 'https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg'; +jest.mock('intersection-observer', () => ({})); + describe('[image] Image', () => { afterEach(() => { cleanup(); @@ -33,28 +36,60 @@ describe('[image] Image', () => { expect(screen.getByAltText('flowers')).toHaveAttribute('src'); }); - it('should create io as loading fallback', async () => { - const observe = jest.fn(); - const unobserve = jest.fn(); - const disconnect = jest.fn(); + const observe = jest.fn(); + const unobserve = jest.fn(); + const disconnect = jest.fn(); + let observerCb; + + const observerMock = jest.fn((cb) => { + observerCb = cb; - delete global.window.location; - global.window = Object.create(window); - global.window.IntersectionObserver = jest.fn(() => ({ + return { observe, unobserve, disconnect, - })) as any; + }; + }); + + beforeEach(() => { + window.IntersectionObserver = observerMock as any; + }); + + it('should use io as loading fallback', async () => { + global.HTMLImageElement.prototype.loading = undefined; + + const setLoadingMock = jest.fn(); + + jest + .spyOn(React, 'useState') + .mockImplementation(() => [false, setLoadingMock]); act(() => { render(flowers); }); - await waitFor(() => - expect(screen.getByAltText('flowers')).toHaveAttribute('src'), - ); + await waitFor(() => expect(observe).toHaveBeenCalledTimes(1)); + + const target = document.querySelector(`img[alt="flowers"]`); + observerCb([{ target, isIntersecting: true }]); - expect(observe).toHaveBeenCalled(); + expect(setLoadingMock).toHaveBeenCalledWith(true); + }); + + it('should hide placeholder on load', async () => { + jest.spyOn(helpers, 'useImageLoader').mockImplementation(() => ({ + onLoad: jest.fn(), + isLoaded: true, + setLoaded: jest.fn(), + })); + + act(() => { + render(flowers); + }); + + expect(screen.getByAltText('')).toHaveStyle('opacity: 0'); + expect(screen.getByAltText('flowers')).toHaveAttribute('src'); + expect(screen.getByAltText('flowers')).not.toHaveAttribute('data-src'); }); it('should render null if src is not a storyblok asset', async () => { diff --git a/src/image/__tests__/Picture.test.tsx b/src/image/__tests__/Picture.test.tsx new file mode 100644 index 0000000..02470bc --- /dev/null +++ b/src/image/__tests__/Picture.test.tsx @@ -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(); + }); + + 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(); + }); + + expect(screen.getByAltText('')).toBeInTheDocument(); + }); + + it('should add webp srcset', async () => { + act(() => { + render(); + }); + + 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(); + }); + + expect(screen.getByAltText('flowers')).toHaveAttribute('loading', 'eager'); + }); +}); diff --git a/src/image/__tests__/createIntersectionObserver.test.tsx b/src/image/__tests__/createIntersectionObserver.test.tsx new file mode 100644 index 0000000..8e018f7 --- /dev/null +++ b/src/image/__tests__/createIntersectionObserver.test.tsx @@ -0,0 +1,156 @@ +import React from 'react'; +import { cleanup, render, act } from '@testing-library/react'; + +import { createIntersectionObserver } from '../createIntersectionObserver'; + +describe('[image] createIntersectionObserver', () => { + afterEach(() => { + cleanup(); + jest.restoreAllMocks(); + }); + + it('should polyfill io if needed', async () => { + const callbackMock = jest.fn(); + const component =
; + + act(() => { + render(component); + }); + + createIntersectionObserver(document.querySelector('#test'), callbackMock); + }); + + it('should trigger if visible', async () => { + const callbackMock = jest.fn(); + let callback; + const component =
; + + const observe = jest.fn(); + const unobserve = jest.fn(); + const disconnect = jest.fn(); + const ioMock = jest.fn((cb) => { + callback = cb; + + return { + observe, + unobserve, + disconnect, + }; + }); + + window.IntersectionObserver = ioMock as any; + + act(() => { + render(component); + }); + + const target = document.querySelector('#test'); + createIntersectionObserver(target as any, callbackMock); + + callback([{ target, isIntersecting: true }]); + + expect(callbackMock).toHaveBeenCalled(); + }); + + it('should not trigger if not visible', async () => { + const callbackMock = jest.fn(); + let callback; + const component =
; + + const observe = jest.fn(); + const unobserve = jest.fn(); + const disconnect = jest.fn(); + const ioMock = jest.fn((cb) => { + callback = cb; + + return { + observe, + unobserve, + disconnect, + }; + }); + + window.IntersectionObserver = ioMock as any; + + act(() => { + render(component); + }); + + const target = document.querySelector('#test'); + createIntersectionObserver(target as any, callbackMock); + + callback([ + { target: document.querySelector('body'), isIntersecting: true }, + ]); + + expect(callbackMock).not.toHaveBeenCalled(); + }); + + it('should not trigger if not intersecting', async () => { + const callbackMock = jest.fn(); + let callback; + const component =
; + + const observe = jest.fn(); + const unobserve = jest.fn(); + const disconnect = jest.fn(); + const ioMock = jest.fn((cb) => { + callback = cb; + + return { + observe, + unobserve, + disconnect, + }; + }); + + window.IntersectionObserver = ioMock as any; + + act(() => { + render(component); + }); + + const target = document.querySelector('#test'); + createIntersectionObserver(target as any, callbackMock); + + callback([{ target, isIntersecting: false }]); + + expect(callbackMock).not.toHaveBeenCalled(); + }); + + it('should have different rootmargin based on connection speed', async () => { + const callbackMock = jest.fn(); + const optionsMock = jest.fn(); + const component =
; + + const observe = jest.fn(); + const ioMock = jest.fn((_, options) => { + optionsMock(options); + + return { + observe, + }; + }); + + window.IntersectionObserver = ioMock as any; + + act(() => { + render(component); + }); + + const target = document.querySelector('#test'); + createIntersectionObserver(target as any, callbackMock); + + expect(optionsMock).toHaveBeenLastCalledWith({ rootMargin: '2500px' }); + + Object.defineProperty(window.navigator, 'connection', { + value: { + effectiveType: '4g', + }, + }); + + createIntersectionObserver(target as any, callbackMock); + + expect(optionsMock).toHaveBeenLastCalledWith({ rootMargin: '1250px' }); + }); +}); diff --git a/src/image/__tests__/helpers.test.tsx b/src/image/__tests__/helpers.test.tsx new file mode 100644 index 0000000..8d72e9a --- /dev/null +++ b/src/image/__tests__/helpers.test.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import { cleanup, waitFor } from '@testing-library/react'; +import { act, renderHook } from '@testing-library/react-hooks'; + +import { useImageLoader } from '../helpers'; + +const currentTarget = document.createElement('img'); +currentTarget.src = 'test'; +const event = { currentTarget } as any; + +describe('[bridge] helpers: useImageLoader', () => { + afterEach(() => { + cleanup(); + jest.restoreAllMocks(); + }); + + it('should load image on load', async () => { + const setLoadedMock = jest.fn(); + jest + .spyOn(React, 'useState') + .mockImplementation(() => [false, setLoadedMock]); + + jest.spyOn(global, 'Image').mockImplementation(() => ({} as any)); + + const { result } = renderHook(() => useImageLoader()); + + await act(async () => { + result.current.onLoad(event); + + await waitFor(() => expect(result.current.isLoaded).toBeTruthy()); + }); + }); + + it('should decode image on load if needed', async () => { + const setLoadedMock = jest.fn(); + jest + .spyOn(React, 'useState') + .mockImplementation(() => [false, setLoadedMock]); + + jest.spyOn(global, 'Image').mockImplementation( + () => + ({ + decode: () => + new Promise((resolve) => { + resolve(); + }), + } as any), + ); + + const { result } = renderHook(() => useImageLoader()); + + await act(async () => { + result.current.onLoad(event); + + await waitFor(() => expect(result.current.isLoaded).toBeTruthy()); + }); + }); + + it('should not load image if already loaded', async () => { + const imgMock = jest.fn(() => ({} as any)); + jest.spyOn(global, 'Image').mockImplementation(imgMock); + + const { result } = renderHook(() => useImageLoader()); + + await act(async () => { + result.current.setLoaded(true); + + await waitFor(() => expect(result.current.isLoaded).toBeTruthy()); + + result.current.onLoad(event); + + expect(imgMock).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/image/createIntersectionObserver.ts b/src/image/createIntersectionObserver.ts index c096589..32362ef 100644 --- a/src/image/createIntersectionObserver.ts +++ b/src/image/createIntersectionObserver.ts @@ -40,5 +40,6 @@ export const createIntersectionObserver = async ( // Add element to the observer io.observe(el); + return io; }; From 500aad7a18a4d936c781e9d277d20e603c76710d Mon Sep 17 00:00:00 2001 From: Bart van den Aardweg Date: Sun, 4 Apr 2021 14:08:55 +0200 Subject: [PATCH 3/6] feat(test): image io tests --- src/image/Image.tsx | 6 -- src/image/__tests__/Image.test.tsx | 153 ++++++++++++++++++++++++----- 2 files changed, 131 insertions(+), 28 deletions(-) diff --git a/src/image/Image.tsx b/src/image/Image.tsx index e472ee2..a25a6c3 100644 --- a/src/image/Image.tsx +++ b/src/image/Image.tsx @@ -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(); } }; diff --git a/src/image/__tests__/Image.test.tsx b/src/image/__tests__/Image.test.tsx index c634146..d9a3486 100644 --- a/src/image/__tests__/Image.test.tsx +++ b/src/image/__tests__/Image.test.tsx @@ -1,13 +1,15 @@ 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('intersection-observer', () => ({})); +jest.mock('../createIntersectionObserver'); describe('[image] Image', () => { afterEach(() => { @@ -36,47 +38,130 @@ describe('[image] Image', () => { expect(screen.getByAltText('flowers')).toHaveAttribute('src'); }); - const observe = jest.fn(); - const unobserve = jest.fn(); - const disconnect = jest.fn(); - let observerCb; + it('should use io as loading fallback', async () => { + global.HTMLImageElement.prototype.loading = undefined; + delete global.HTMLImageElement.prototype.loading; - const observerMock = jest.fn((cb) => { - observerCb = cb; + const setLoadingMock = jest.fn(); + + jest + .spyOn(React, 'useState') + .mockImplementationOnce(() => [false, setLoadingMock]); - return { - observe, - unobserve, + const disconnect = jest.fn(); + const createIoMock = jest.fn(() => ({ disconnect, - }; + })); + + (createIntersectionObserver as jest.Mock).mockReset(); + (createIntersectionObserver as jest.Mock).mockImplementation(createIoMock); + + act(() => { + render(flowers); + }); + + await waitFor(() => + expect(createIntersectionObserver as jest.Mock).toHaveBeenCalled(), + ); + + act(() => { + ((createIntersectionObserver as jest.Mock).mock as any).calls[0][1](); + }); + + expect(setLoadingMock).toHaveBeenCalledWith(true); }); - beforeEach(() => { - window.IntersectionObserver = observerMock as any; + 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(flowers, { container }); + }); + + expect(disconnect).not.toHaveBeenCalled(); + + unmountComponentAtNode(container); + + await waitFor(() => expect(disconnect).toHaveBeenCalled()); }); - it('should use io as loading fallback', async () => { + it('should not add io if already loading', async () => { global.HTMLImageElement.prototype.loading = undefined; - const setLoadingMock = jest.fn(); + const disconnect = jest.fn(); + const createIoMock = jest.fn(() => ({ + disconnect, + })); - jest - .spyOn(React, 'useState') - .mockImplementation(() => [false, setLoadingMock]); + (createIntersectionObserver as jest.Mock).mockImplementation(createIoMock); + + act(() => { + render(flowers); + }); + + 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(flowers); }); - await waitFor(() => expect(observe).toHaveBeenCalledTimes(1)); + await waitFor(() => + expect(createIntersectionObserver as jest.Mock).not.toHaveBeenCalled(), + ); - const target = document.querySelector(`img[alt="flowers"]`); - observerCb([{ target, isIntersecting: true }]); + ref = {}; + Object.defineProperty(ref, 'current', { + get: jest.fn(() => true), + set: jest.fn(), + }); + jest.spyOn(React, 'useRef').mockReturnValueOnce(ref); - expect(setLoadingMock).toHaveBeenCalledWith(true); + act(() => { + render(flowers); + }); + + 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, @@ -92,6 +177,30 @@ describe('[image] Image', () => { 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(flowers); + }); + + expect(setLoaded).toHaveBeenCalled(); + }); + it('should render null if src is not a storyblok asset', async () => { jest.spyOn(console, 'error').mockImplementation(jest.fn()); From 4d3c2e9b4d6542af18b01dcf29d3cb6ad8f2f5d5 Mon Sep 17 00:00:00 2001 From: Bart van den Aardweg Date: Fri, 16 Apr 2021 23:38:58 +0200 Subject: [PATCH 4/6] feat: allow hiding of lqip --- src/image/Image.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/image/Image.tsx b/src/image/Image.tsx index a25a6c3..caa6b6d 100644 --- a/src/image/Image.tsx +++ b/src/image/Image.tsx @@ -26,12 +26,19 @@ interface ImageProps * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture#the_media_attribute */ media?: string; + /** + * Show a Low-Quality Image Placeholder. + * + * @default true + */ + showPlaceholder?: boolean; } export const Image = ({ fixed, fluid, height, + showPlaceholder = true, smart, width, ref, @@ -107,7 +114,9 @@ export const Image = ({ }} /> - + {showPlaceholder && ( + + )} Date: Mon, 19 Apr 2021 17:31:04 +0200 Subject: [PATCH 5/6] docs(readme): dark mode compatible logo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 684d2a4..b1e1276 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Story of AMS + Story of AMS

@storyofams/storyblok-toolkit

@@ -17,7 +17,7 @@

-

Batteries-included toolset for efficient development of React frontends with Storyblok as a headless CMS. View docs

+

Batteries-included toolset for efficient development of React frontends with Storyblok as a headless CMS.
View docs

--- From fb9031b9fc739a631379b57bf72a8c41c423e5fe Mon Sep 17 00:00:00 2001 From: Doeke Leeuwis Date: Tue, 27 Apr 2021 09:35:38 +0200 Subject: [PATCH 6/6] docs(readme): adjusted logo size --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1e1276..20900c5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Story of AMS + Story of AMS

@storyofams/storyblok-toolkit