Skip to content

Commit

Permalink
[templates/nextjs-xmcloud] Disable image optimization for edit/preview (
Browse files Browse the repository at this point in the history
  • Loading branch information
art-alexeyenko authored Aug 14, 2024
1 parent 69c9a81 commit 8c72bce
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Our versioning strategy is as follows:
* `[templates/nextjs-sxa]` The background image in the Container component was being generated from the image ID instead of the mediaUrl parameter. This fix changes that behavior. ([#1879](https://github.com/Sitecore/jss/pull/1879))
* `[templates/nextjs-sxa]` The caption of image component has been fixed. ([#1874](https://github.com/Sitecore/jss/pull/1874))
* `[sitecore-jss-nextjs]` A bug has been fixed in the redirect middleware that occurred when a user clicked on a link rendered by the Link component from the Next.js library(next/link). ([#1876](https://github.com/Sitecore/jss/pull/1876))
* `[sitecore-jss-nextjs]` Disable nextjs image optimization in edit and preview modes. This prevents rendering issues in XM Cloud Pages Edit and Preview.

### 🎉 New Features & Improvements

Expand Down
206 changes: 175 additions & 31 deletions packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@ import { NextImage } from './NextImage';
import {
ImageField,
DefaultEmptyFieldEditingComponentImage,
LayoutServicePageState,
SitecoreContextReactContext,
} from '@sitecore-jss/sitecore-jss-react';
import { ImageLoader } from 'next/image';
import Image, { ImageLoader } from 'next/image';
import { spy, match } from 'sinon';
import sinonChai from 'sinon-chai';
import { SinonSpy } from 'sinon';

use(sinonChai);
const setContext = spy();
const expect = chai.use(chaiString).expect;
const testContextProps = {
context: {
pageState: LayoutServicePageState.Normal,
},
setContext,
};

describe('<NextImage />', () => {
const HOSTNAME = 'https://cm.jss.localhost';
Expand All @@ -39,7 +48,11 @@ describe('<NextImage />', () => {
height: 10,
};

const mounted = mount(<NextImage loader={mockLoader} {...props} />);
const mounted = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
);
const rendered = mounted.find('img');
it('should render image with url', () => {
expect(rendered).to.have.lengthOf(1);
Expand All @@ -64,7 +77,11 @@ describe('<NextImage />', () => {
className: 'the-dude-abides',
};

const rendered = mount(<NextImage loader={mockLoader} {...props} />).find('img');
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
).find('img');

it('should render image with needed props', () => {
expect(rendered).to.have.length(1);
Expand All @@ -88,9 +105,11 @@ describe('<NextImage />', () => {
const field = {
value: { src: '/assets/img/test0.png', alt: 'my image', width: 200, height: 400 },
};
const rendered = mount(<NextImage loader={mockLoader} {...props} field={field} fill />).find(
'img'
);
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} field={field} fill />
</SitecoreContextReactContext.Provider>
).find('img');

expect(rendered).to.have.length(1);
expect(rendered.prop('src')).to.equal(`${HOSTNAME}${props.field.value.src}?w=${props.width}`);
Expand All @@ -112,7 +131,11 @@ describe('<NextImage />', () => {
id: 'some-id',
className: 'the-dude-abides',
};
const rendered = mount(<NextImage loader={mockLoader} {...props} />).find('img');
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
).find('img');

it('should render image component with "value" properties', () => {
expect(rendered).to.have.length(1);
Expand Down Expand Up @@ -141,7 +164,11 @@ describe('<NextImage />', () => {
className: 'the-dude-abides',
};

const rendered = mount(<NextImage loader={mockLoader} {...props} />).find('img');
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
).find('img');

expect(rendered).to.have.length(1);
expect(rendered.prop('src')).to.eql(`${HOSTNAME}${props.field.value.src}?w=${props.width}`);
Expand All @@ -162,7 +189,11 @@ describe('<NextImage />', () => {
editable: false,
className: 'the-dude-abides w-100',
};
const rendered = mount(<NextImage loader={mockLoader} {...props} />).find('img');
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
).find('img');

it('should render image component with "value" properties', () => {
expect(rendered).to.have.length(1);
Expand Down Expand Up @@ -191,16 +222,25 @@ describe('<NextImage />', () => {
imageParams: { foo: 'bar' },
mediaUrlPrefix: /\/([-~]{1})assets\//i,
};
const rendered = mount(<NextImage loader={mockLoader} {...props} />);
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
);

expect(rendered.find('img').prop('src')).to.equal(
`${HOSTNAME}/~/jssmedia/img/test0.png?foo=bar&w=8`
);
rendered.setProps({
const props2 = {
...props,
field: { src: '/-assets/img/test0.png' },
});
expect(rendered.find('img').prop('src')).to.equal(
};
const rendered2 = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props2} />
</SitecoreContextReactContext.Provider>
);
expect(rendered2.find('img').prop('src')).to.equal(
`${HOSTNAME}/-/jssmedia/img/test0.png?foo=bar&w=8`
);
expect(mockLoader.called).to.be.true;
Expand All @@ -223,17 +263,26 @@ describe('<NextImage />', () => {
imageParams: { foo: 'bar' },
mediaUrlPrefix: /\/([-~]{1})assets\//i,
};
const rendered = mount(<NextImage loader={mockLoader} {...props} />);
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
);
expect(rendered.find('img').prop('src')).to.equal(
`${HOSTNAME}/~/jssmedia/img/test0.png?foo=bar&w=8`
);
rendered.setProps({
const props2 = {
...props,
field: { src: '/-assets/img/test0.png' },
width,
height: 10,
});
expect(rendered.find('img').prop('src')).to.equal(
};
const rendered2 = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...props2} />
</SitecoreContextReactContext.Provider>
);
expect(rendered2.find('img').prop('src')).to.equal(
`${HOSTNAME}/-/jssmedia/img/test0.png?foo=bar&w=8`
);
expect(mockLoader.called).to.be.true;
Expand All @@ -247,7 +296,11 @@ describe('<NextImage />', () => {

it('should render no image when field prop is empty', () => {
const img = '' as ImageField;
const rendered = mount(<NextImage field={img} />).find('img');
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage field={img} />
</SitecoreContextReactContext.Provider>
).find('img');
expect(rendered).to.have.length(0);
});
});
Expand All @@ -258,9 +311,13 @@ describe('<NextImage />', () => {
const field = {
src: '/assets/img/test0.png',
};
expect(() => mount(<NextImage src={src} field={field} />)).to.throw(
'Detected src prop. If you wish to use src, use next/image directly.'
);
expect(() =>
mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage src={src} field={field} />
</SitecoreContextReactContext.Provider>
)
).to.throw('Detected src prop. If you wish to use src, use next/image directly.');
});
});

Expand All @@ -276,7 +333,11 @@ describe('<NextImage />', () => {
loader: userMockLoader,
};

const rendered = mount(<NextImage {...props} />).find('img');
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage {...props} />
</SitecoreContextReactContext.Provider>
).find('img');

it('should render image with url', () => {
expect(rendered).to.have.lengthOf(1);
Expand All @@ -291,6 +352,12 @@ describe('<NextImage />', () => {
});

describe('editMode metadata', () => {
const testEditingContext = {
...testContextProps,
context: {
pageState: LayoutServicePageState.Edit,
},
};
const testMetadata = {
contextItem: {
id: '{09A07660-6834-476C-B93B-584248D3003B}',
Expand All @@ -309,14 +376,18 @@ describe('<NextImage />', () => {
metadata: testMetadata,
};

const rendered = mount(<NextImage field={field} fill={true} />);

const rendered = mount(
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage field={field} fill={true} />
</SitecoreContextReactContext.Provider>
);
// we expect imgSrc from nextjs optimizations to be absent in editing/metadata mode
expect(rendered.html()).to.equal(
[
`<code type="text/sitecore" chrometype="field" class="scpm" kind="open">${JSON.stringify(
testMetadata
)}</code>`,
'<img alt="my image" loading="lazy" decoding="async" data-nimg="fill" style="position: absolute; height: 100%; width: 100%; left: 0px; top: 0px; right: 0px; bottom: 0px; color: transparent;" sizes="100vw" srcset="/_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=640&amp;q=75 640w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=750&amp;q=75 750w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=828&amp;q=75 828w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=1080&amp;q=75 1080w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=1200&amp;q=75 1200w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=1920&amp;q=75 1920w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=2048&amp;q=75 2048w, /_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=3840&amp;q=75 3840w" src="/_next/image?url=%2Fassets%2Fimg%2Ftest0.png&amp;w=3840&amp;q=75">',
'<img alt="my image" loading="lazy" decoding="async" data-nimg="fill" style="position: absolute; height: 100%; width: 100%; left: 0px; top: 0px; right: 0px; bottom: 0px; color: transparent;" src="/assets/img/test0.png">',
'<code type="text/sitecore" chrometype="field" class="scpm" kind="close"></code>',
].join('')
);
Expand All @@ -328,7 +399,11 @@ describe('<NextImage />', () => {
metadata: testMetadata,
};

const rendered = mount(<NextImage field={field} />);
const rendered = mount(
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage field={field} />
</SitecoreContextReactContext.Provider>
);
const defaultEmptyImagePlaceholder = mount(<DefaultEmptyFieldEditingComponentImage />);
expect(rendered.html()).to.equal(
[
Expand All @@ -347,7 +422,11 @@ describe('<NextImage />', () => {
metadata: testMetadata,
};

const rendered = mount(<NextImage field={field} />);
const rendered = mount(
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage field={field} />
</SitecoreContextReactContext.Provider>
);
const defaultEmptyImagePlaceholder = mount(<DefaultEmptyFieldEditingComponentImage />);
expect(rendered.html()).to.equal(
[
Expand All @@ -371,7 +450,9 @@ describe('<NextImage />', () => {
);

const rendered = mount(
<NextImage field={field} emptyFieldEditingComponent={EmptyFieldEditingComponent} />
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage field={field} emptyFieldEditingComponent={EmptyFieldEditingComponent} />
</SitecoreContextReactContext.Provider>
);

expect(rendered.html()).to.equal(
Expand All @@ -396,7 +477,9 @@ describe('<NextImage />', () => {
);

const rendered = mount(
<NextImage field={field} emptyFieldEditingComponent={EmptyFieldEditingComponent} />
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage field={field} emptyFieldEditingComponent={EmptyFieldEditingComponent} />
</SitecoreContextReactContext.Provider>
);

expect(rendered.html()).to.equal(
Expand All @@ -416,7 +499,11 @@ describe('<NextImage />', () => {
metadata: testMetadata,
};

const rendered = mount(<NextImage field={field} editable={false} />);
const rendered = mount(
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage field={field} editable={false} />
</SitecoreContextReactContext.Provider>
);

expect(rendered.html()).to.equal('');
});
Expand All @@ -427,9 +514,66 @@ describe('<NextImage />', () => {
metadata: testMetadata,
};

const rendered = mount(<NextImage field={field} editable={false} />);
const rendered = mount(
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage field={field} editable={false} />
</SitecoreContextReactContext.Provider>
);

expect(rendered.html()).to.equal('');
});
});

describe('unoptimized property manipulation', () => {
const props = {
field: { value: { src: '/assets/img/test0.png' } },
width,
height: 10,
id: 'some-id',
className: 'the-dude-abides',
};

it('should render unoptimized image in edit mode', () => {
const testEditingContext = {
...testContextProps,
context: {
pageState: LayoutServicePageState.Edit,
},
};
const rendered = mount(
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
).find(Image);
expect(rendered.prop('unoptimized')).to.equal(true);
});

it('should render unoptimized image in preview mode', () => {
const testEditingContext = {
...testContextProps,
context: {
pageState: LayoutServicePageState.Preview,
},
};
const rendered = mount(
<SitecoreContextReactContext.Provider value={testEditingContext}>
<NextImage loader={mockLoader} {...props} />
</SitecoreContextReactContext.Provider>
).find(Image);
expect(rendered.prop('unoptimized')).to.equal(true);
});

it('should render respect original unoptimized value in normal mode', () => {
const modifiedProps = {
...props,
unoptimized: true,
};
const rendered = mount(
<SitecoreContextReactContext.Provider value={testContextProps}>
<NextImage loader={mockLoader} {...modifiedProps} />
</SitecoreContextReactContext.Provider>
).find(Image);
expect(rendered.prop('unoptimized')).to.equal(true);
});
});
});
Loading

0 comments on commit 8c72bce

Please sign in to comment.