Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[templates/nextjs-xmcloud] Disable image optimization for edit/preview #1887

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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