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

[sitecore-jss-nextjs] Send render event when component is updated in component library #2021

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,6 @@ describe('transform', () => {
let mergeStub: SinonStub;
let diffAndWriteFilesStub: SinonStub;
let writeFileToPathStub: SinonStub;
let transformFilenameStub: SinonStub;
let openJsonFileStub: SinonStub;
let log: SinonStub;

Expand All @@ -495,7 +494,6 @@ describe('transform', () => {
mergeStub?.restore();
diffAndWriteFilesStub?.restore();
writeFileToPathStub?.restore();
transformFilenameStub?.restore();
openJsonFileStub?.restore();
log?.restore();
});
Expand Down Expand Up @@ -841,4 +839,24 @@ describe('transform', () => {
);
});
});

describe('populateEjsData', () => {
it('should remove prerelease build number from version', () => {
const destinationPath = path.resolve('samples/next');
const answers = {
destination: destinationPath,
templates: [],
appPrefix: false,
force: false,
};

const transformModule = proxyquire('./transform', {
'../../../package.json': { version: '22.4.1-beta.33' },
});

const result = transformModule.populateEjsData(answers);

expect(result.version).to.equal('22.4.1-beta');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,8 @@ export const diffAndWriteFiles = async ({
export const populateEjsData = (answers: BaseArgs, destination?: string) => {
// pass in helper to answers object

// Don't expose canary build number in the generated app
const jssVersion = version.includes('canary')
? version.replace(/(-canary\.\d+)$/, '-canary')
: version;
// Don't expose prerelease build number in the generated app
const jssVersion = version.replace(/(\.\d+)$/, '');

const ejsData: Data = {
...answers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { RichText } from './RichText';
import { Text } from './Text';
import { Placeholder } from '..';
import {
COMPONENT_LIBRARY_READY_MESSAGE,
ComponentLibraryStatus,
ComponentUpdateEventArgs,
getComponentLibraryStatusEvent,
} from '@sitecore-jss/sitecore-jss/editing';

describe('<ComponentLibraryLayout />', () => {
Expand Down Expand Up @@ -48,6 +49,23 @@ describe('<ComponentLibraryLayout />', () => {
return components.get(componentName) || null;
};

// eslint-disable-next-line jsdoc/require-jsdoc
async function sendUpdateEvent(
eventDataDetails: ComponentUpdateEventArgs['details']
): Promise<void> {
// jsdom performs postMessage without origin. We work around, ugly (https://github.com/jsdom/jsdom/issues/2745)
// jsdom also doesn't consider `new MessageEvent()` to be of class Event - so we go very much around to get it working
const updateEvent = document.createEvent('Event');
const updateEventData: ComponentUpdateEventArgs = {
name: 'component:update',
details: eventDataDetails,
};
updateEvent.initEvent('message', false, true);
(updateEvent as any).origin = window.location.origin;
(updateEvent as any).data = updateEventData;
await window.dispatchEvent(updateEvent);
}

it('should render', () => {
const basicPage = getTestLayoutData();
rendered = mount(
Expand Down Expand Up @@ -90,6 +108,10 @@ describe('<ComponentLibraryLayout />', () => {

it('should fire component:ready event', () => {
const basicPage = getTestLayoutData();
const expectedReadyMessage = getComponentLibraryStatusEvent(
ComponentLibraryStatus.READY,
'test-content'
);
const rendered = mount(
<SitecoreContext componentFactory={componentFactory}>
<ComponentLibraryLayout {...basicPage.layoutData} />
Expand All @@ -108,9 +130,7 @@ describe('<ComponentLibraryLayout />', () => {
expect(
postMessageSpy
.getCalls()
.some(
(call) => JSON.stringify(call.args[0]) === JSON.stringify(COMPONENT_LIBRARY_READY_MESSAGE)
)
.some((call) => JSON.stringify(call.args[0]) === JSON.stringify(expectedReadyMessage))
).to.be.true;
});

Expand All @@ -130,20 +150,11 @@ describe('<ComponentLibraryLayout />', () => {
'</div></div></div></main>',
].join('')
);
// jsdom performs postMessage without origin. We work around, ugly (https://github.com/jsdom/jsdom/issues/2745)
// jsdom also doesn't consider `new MessageEvent()` to be of class Event - so we go very much around to get it working
const updateEvent = document.createEvent('Event');
const updateEventData: ComponentUpdateEventArgs = {
name: 'component:update',
details: {
uid: 'test-content',
fields: { content: { value: 'new content!' } },
},
};
updateEvent.initEvent('message', false, true);
(updateEvent as any).origin = window.location.origin;
(updateEvent as any).data = updateEventData;
await window.dispatchEvent(updateEvent);

await sendUpdateEvent({
uid: 'test-content',
fields: { content: { value: 'new content!' } },
});

rendered.update();
expect(rendered.html()).to.equal(
Expand Down Expand Up @@ -175,20 +186,11 @@ describe('<ComponentLibraryLayout />', () => {
'</main>',
].join('')
);
// jsdom performs postMessage without origin. We work around, ugly (https://github.com/jsdom/jsdom/issues/2745)
// jsdom also doesn't consider `new MessageEvent()` to be of class Event - so we go very much around to get it working
const updateEvent = document.createEvent('Event');
const updateEventData: ComponentUpdateEventArgs = {
name: 'component:update',
details: {
uid: 'test-inner',
fields: { text: { value: 'new inner content!' } },
},
};
updateEvent.initEvent('message');
(updateEvent as any).origin = window.location.origin;
(updateEvent as any).data = updateEventData;
await window.dispatchEvent(updateEvent);

await sendUpdateEvent({
uid: 'test-inner',
fields: { text: { value: 'new inner content!' } },
});

rendered.update();

Expand All @@ -205,4 +207,32 @@ describe('<ComponentLibraryLayout />', () => {
].join('')
);
});

it('should send render event when component is updated', async () => {
const basicPage = getTestLayoutData();
const rendered = mount(
<SitecoreContext componentFactory={componentFactory}>
<ComponentLibraryLayout {...basicPage.layoutData} />
</SitecoreContext>
);

await sendUpdateEvent({
uid: 'test-content',
fields: { content: { value: 'new content!' } },
});

rendered.update();

expect(
postMessageSpy
.getCalls()
.some((call) =>
JSON.stringify(call.args[0]).includes(
JSON.stringify(
getComponentLibraryStatusEvent(ComponentLibraryStatus.RENDERED, 'test-content')
)
)
)
).to.be.true;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
LayoutServiceData,
} from '@sitecore-jss/sitecore-jss/layout';
import {
ComponentLibraryStatus,
getComponentLibraryStatusEvent,
addComponentUpdateHandler,
COMPONENT_LIBRARY_READY_MESSAGE,
} from '@sitecore-jss/sitecore-jss/editing';
import { EditingScripts } from './EditingScripts';

export const ComponentLibraryLayout = (layoutData: LayoutServiceData): JSX.Element => {
const { route } = layoutData.sitecore;
const [renderKey, setRenderKey] = useState(0);
const [rootUpdate, setRootUpdate] = useState(null);
const rootComponent = route?.placeholders[EDITING_COMPONENT_PLACEHOLDER][0] as ComponentRendering;
// useEffect may execute multiple times on single render (i.e. in dev) - but we only want to fire ready event once
Expand All @@ -30,21 +32,39 @@ export const ComponentLibraryLayout = (layoutData: LayoutServiceData): JSX.Eleme
// useEffect will fire when components are ready - and we inform the whole wide world of it too
if (!componentReady) {
componentReady = true;
window.top.postMessage(COMPONENT_LIBRARY_READY_MESSAGE, '*');
window.top.postMessage(
getComponentLibraryStatusEvent(ComponentLibraryStatus.READY, rootComponent.uid),
'*'
);
}
const unsubscribe = addComponentUpdateHandler(persistedRoot, (updatedRoot) =>
setRootUpdate({ ...updatedRoot })
);
const unsubscribe = addComponentUpdateHandler(persistedRoot, (updatedRoot) => {
setRootUpdate({ ...updatedRoot });
setRenderKey((key) => key + 1);
});
// useEffect will cleanup event handler on re-render
return unsubscribe;
}, []);

useEffect(() => {
// Send a rendered event only as effect of a component update command
if (renderKey === 0) {
return;
}

window.top.postMessage(
getComponentLibraryStatusEvent(ComponentLibraryStatus.RENDERED, rootComponent.uid),
'*'
);
}, [renderKey, rootComponent.uid]);

return (
<>
<EditingScripts />
<main>
<div id={EDITING_COMPONENT_ID}>
{route && <Placeholder name={EDITING_COMPONENT_PLACEHOLDER} rendering={route} />}
{route && (
<Placeholder name={EDITING_COMPONENT_PLACEHOLDER} rendering={route} key={renderKey} />
)}
</div>
</main>
</>
Expand Down
Loading