Skip to content

Commit

Permalink
Fix bug #349 typings for Placeholder prop functions render, renderEac…
Browse files Browse the repository at this point in the history
…h, renderEmpty (#350)
  • Loading branch information
vsirakova authored Apr 2, 2020
1 parent 110a3dc commit 2d7a934
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 12 deletions.
86 changes: 85 additions & 1 deletion packages/sitecore-jss-react/src/components/Placeholder.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const componentFactory: ComponentFactory = (componentName: string) => {
const components = new Map<string, any>();

// pass otherProps to page-content to test property cascading through the Placeholder
const Home: React.SFC<any> = ({ rendering, ...otherProps }) => (
const Home: React.SFC<any> = ({ rendering, render, renderEach, renderEmpty, ...otherProps }) => (
<div className="home-mock">
<Placeholder name="page-header" rendering={rendering} />
<Placeholder name="page-content" rendering={rendering} {...otherProps} />
Expand Down Expand Up @@ -85,6 +85,90 @@ describe('<Placeholder />', () => {
expect(renderedComponent.find('.download-callout-mock').length).to.equal(1);
});

it('should render components based on the rendereach function', () => {
const component: any = dataSet.data.sitecore.route;
const phKey = 'main';

const renderedComponent = mount(
<SitecoreContext componentFactory={componentFactory}>
<Placeholder
name={phKey}
rendering={component}
renderEach={(comp) => <div className="wrapper">{comp}</div>}
/>
</SitecoreContext>
);

expect(renderedComponent.find('.wrapper').length).to.equal(1);
});

it('should render components based on the render function', () => {
const component: any = dataSet.data.sitecore.route;
const phKey = 'main';

const renderedComponent = mount(
<SitecoreContext componentFactory={componentFactory}>
<Placeholder
name={phKey}
rendering={component}
render={(comp) => <div className="wrapper">{comp}</div>}
/>
</SitecoreContext>
);

expect(renderedComponent.find('.wrapper').length).to.equal(1);
});

it('when null passed to render function', () => {
it('should render empty placeholder', () => {
const component: any = dataSet.data.sitecore.route;
const phKey = 'main';

const renderedComponent = mount(
<SitecoreContext componentFactory={componentFactory}>
<Placeholder
name={phKey}
rendering={component}
render={() => null}
/>
</SitecoreContext>
);

const placeholder = renderedComponent.find(Placeholder)
expect(placeholder.length).to.equal(1);
expect(placeholder.children()).to.be.empty;
});
})

it('should render output based on the renderEmpty function in case of no renderings', () => {
let component: any = dataSet.data.sitecore.route;
const renderings = component.placeholders.main.filter(({ componentName }: any) => !componentName);
const myComponent = {
...component,
placeholders: {
...component.placeholders,
main: [...renderings],
},
};

const phKey = 'main';

const renderedComponent = mount(
<SitecoreContext componentFactory={componentFactory}>
<Placeholder
name={phKey}
rendering={myComponent}
renderEmpty={(comp) => <div className="wrapper">{comp}</div>}
/>
</SitecoreContext>
);

expect(renderedComponent.find('.wrapper').length).to.equal(1);
expect(renderedComponent.find('.download-callout-mock').length).to.equal(0);
expect(renderedComponent.find('.home-mock').length).to.equal(0);
expect(renderedComponent.find('.jumbotron-mock').length).to.equal(0);
});

it('should pass properties to nested components', () => {
const component = dataSet.data.sitecore.route as any;
const phKey = 'main';
Expand Down
28 changes: 22 additions & 6 deletions packages/sitecore-jss-react/src/components/Placeholder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,38 @@ import { withComponentFactory } from '../enhancers/withComponentFactory';
import { ComponentRendering, HtmlElementRendering } from '@sitecore-jss/sitecore-jss';

export interface PlaceholderComponentProps extends PlaceholderProps {
/**
* Render props function that is called when the placeholder contains no content components.
* Can be used to wrap the Sitecore EE empty placeholder markup in something that's visually correct
*/
renderEmpty?: (
components: React.ReactNode[]
) => React.ComponentClass<any> | React.SFC<any> | React.ReactNode;
/**
* Render props function that enables control over the rendering of the components in the placeholder.
* Useful for techniques like wrapping each child in a wrapper component.
*/
render?: (components: React.ReactNode[], data: (ComponentRendering | HtmlElementRendering)[], props: PlaceholderProps) => React.ComponentClass<any> | React.SFC<any>;
render?: (
components: React.ReactNode[],
data: (ComponentRendering | HtmlElementRendering)[],
props: PlaceholderProps
) => React.ComponentClass<any> | React.SFC<any> | React.ReactNode;

/**
* Render props function that is called for each non-system component added to the placeholder.
* Mutually exclusive with `render`. System components added during Experience Editor are automatically rendered as-is.
*/
renderEach?: (components: React.ReactNode[], data: (ComponentRendering | HtmlElementRendering)[], props: PlaceholderProps) => React.ComponentClass<any> | React.SFC<any>;
renderEach?: (
component: React.ReactNode,
index: number
) => React.ComponentClass<any> | React.SFC<any> | React.ReactNode;
}

function isRawRendering(rendering: HtmlElementRendering | ComponentRendering): rendering is HtmlElementRendering {
return !(rendering as ComponentRendering).componentName && (rendering as HtmlElementRendering).name !== undefined;
}

class PlaceholderComponent extends PlaceholderCommon {
class PlaceholderComponent extends PlaceholderCommon<PlaceholderComponentProps> {
static propTypes = PlaceholderCommon.propTypes;

constructor(props: PlaceholderComponentProps) {
Expand All @@ -48,19 +62,21 @@ class PlaceholderComponent extends PlaceholderCommon {
const renderingData = childProps.rendering;

const placeholderData = PlaceholderCommon.getPlaceholderDataFromRenderingData(renderingData, this.props.name);
const components = this.getComponentsForRenderingData(placeholderData);
const components = this.getComponentsForRenderingData(placeholderData);

if (this.props.renderEmpty && placeholderData.every((rendering: ComponentRendering | HtmlElementRendering) => isRawRendering(rendering))) {
return this.props.renderEmpty(components, placeholderData, childProps);
return this.props.renderEmpty(components);
} else if (this.props.render) {
return this.props.render(components, placeholderData, childProps);
} else if (this.props.renderEach) {
const renderEach = this.props.renderEach;

return components.map((component, index) => {
if (component && component.props && component.props.type === 'text/sitecore') {
return component;
}

return this.props.renderEach(component, index);
return renderEach(component, index);
});
} else {
return components;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export interface PlaceholderProps {
[key: string]: any;
}

export class PlaceholderCommon extends React.Component<PlaceholderProps> {
export class PlaceholderCommon<T extends PlaceholderProps> extends React.Component<T> {
static propTypes = {
rendering: PropTypes.oneOfType([
PropTypes.object as Requireable<RouteData>,
Expand Down Expand Up @@ -93,7 +93,7 @@ export class PlaceholderCommon extends React.Component<PlaceholderProps> {
return result;
}

constructor(props: PlaceholderProps) {
constructor(props: T) {
super(props);
this.nodeRefs = [];
this.state = {};
Expand Down Expand Up @@ -191,8 +191,8 @@ export class PlaceholderCommon extends React.Component<PlaceholderProps> {
};

/* Since we can't set the "key" attribute via React, stash it
* so we can set in the DOM after render.
*/
* so we can set in the DOM after render.
*/
if (attributes && attributes.chrometype === 'placeholder') {
props.phkey = elem.attributes.key; // props that get rendered as dom attribute names need to be lowercase, otherwise React complains.
props.ref = this.addRef; // only need ref for placeholder containers, trying to add it to other components (e.g. stateless components) may result in a warning.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type WithPlaceholderSpec = (string | PlaceholderToPropMapping) | (string

export function withPlaceholder(placeholders: WithPlaceholderSpec, options?: WithPlaceholderOptions) {
return (WrappedComponent: React.ComponentClass<any> | React.SFC<any>) => {
class WithPlaceholder extends PlaceholderCommon {
class WithPlaceholder extends PlaceholderCommon<PlaceholderProps> {
static propTypes = PlaceholderCommon.propTypes;

constructor(props: any) {
Expand Down

0 comments on commit 2d7a934

Please sign in to comment.