Skip to content

Commit

Permalink
extra unit test coverage for react package (#1258)
Browse files Browse the repository at this point in the history
  • Loading branch information
art-alexeyenko authored Dec 14, 2022
1 parent c7dc04c commit 2e189ca
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/sitecore-jss-react/.nycrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
".tsx"
],
"exclude": [
"**/index.ts",
"**/*.d.ts",
"**/*.test.tsx",
"**/*.test.ts",
Expand Down
10 changes: 10 additions & 0 deletions packages/sitecore-jss-react/src/components/Image.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,14 @@ describe('<Image />', () => {
const rendered = mount(<Image media={img} />);
expect(rendered.find('img')).to.have.length(0);
});

it('should render when field prop is used instead of media prop', () => {
const imgField = {
src: '/assets/img/test0.png',
width: 8,
height: 10,
};
const rendered = mount(<Image field={imgField} />);
expect(rendered.find('img')).to.have.length(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable no-unused-expressions */
import React from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import { spy } from 'sinon';
import { ExperienceEditor } from '@sitecore-jss/sitecore-jss/utils';

import { withEditorChromes } from '../enhancers/withEditorChromes';

const SampleComponent: React.FC<{ stringProp: string }> = (props: { stringProp: string }) => {
return <div>{props.stringProp}</div>;
};

describe('withEditorChromes', () => {
it('should update chromes on update', () => {
const WrappedComponent = withEditorChromes(SampleComponent as React.FC<unknown>);
const props = {
stringProp: '123',
};
// sinon cannot spy on the resetEditorChromes instance used in withEditorChromes - so we test for method that is called by it
const utilSpy = spy(ExperienceEditor, 'isActive');

const rendered = mount(<WrappedComponent {...props} />);
expect(rendered.children.length).to.not.equal(0);
rendered.setProps({ stringProp: '456' });
expect(utilSpy.called).to.be.true;
});
});
196 changes: 196 additions & 0 deletions packages/sitecore-jss-react/src/enhancers/withPlaceholder.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-unused-expressions */
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { ReactElement } from 'react';
import { expect } from 'chai';
import { mount } from 'enzyme';
import { altData, convertedDevData as nonEeDevData } from '../test-data/non-ee-data';
import { convertedData as eeData } from '../test-data/ee-data';
import { withPlaceholder } from '../enhancers/withPlaceholder';
import { SitecoreContext } from '../components/SitecoreContext';
import { PlaceholderProps } from '../components/PlaceholderCommon';
import PropTypes from 'prop-types';
import { ComponentFactory } from '../components/sharedTypes';
import { ComponentRendering, RouteData } from '@sitecore-jss/sitecore-jss/layout';
import { Placeholder } from '../components/Placeholder';

type CalloutProps = PlaceholderProps & {
[prop: string]: unknown;
fields?: { message?: { value?: string } };
subProp?: ReactElement;
};

type HomeProps = PlaceholderProps & {
[prop: string]: unknown;
rendering?: RouteData | ComponentRendering;
subProp?: ReactElement;
};

const DownloadCallout: React.FC<CalloutProps> = (props) => (
<div className="download-callout-mock">
{props.fields?.message ? props.fields.message.value : ''}
</div>
);

const Home: React.FC<HomeProps> = ({
rendering,
render,
renderEach,
renderEmpty,
name,
subProp,
...otherProps
}) => {
if (subProp && !otherProps.reset) {
return <div className="home-mock-with-prop">{subProp}</div>;
} else {
return (
<div className="home-mock">
<Placeholder name={name} rendering={rendering} {...otherProps} />
</div>
);
}
};

const ErrorComponent: React.FC = () => {
throw 'Error!';
};

const ErrorMessageComponent: React.FC = () => (
<div className="error-handled">Your error has been... dealt with.</div>
);

const componentFactory: ComponentFactory = (componentName: string) => {
const components = new Map<string, React.FC>();

// pass otherProps to page-content to test property cascading through the Placeholder

Home.propTypes = {
placeholders: PropTypes.object,
};

components.set('Home', Home);

DownloadCallout.propTypes = {
fields: PropTypes.shape({
message: PropTypes.shape({
value: PropTypes.string,
}),
}).isRequired,
};

components.set('DownloadCallout', DownloadCallout);
components.set('Jumbotron', () => <div className="jumbotron-mock"></div>);

return components.get(componentName) || null;
};

const testData = [
{ label: 'Dev data', data: nonEeDevData },
{ label: 'LayoutService data - EE on', data: eeData },
];

describe('withPlaceholder HOC', () => {
describe('Error handling', () => {
it('should render default error component on wrapped component error', () => {
const phKey = 'page-content';
const props: PlaceholderProps = {
name: phKey,
rendering: null,
componentFactory: componentFactory,
};
const Element = withPlaceholder(phKey)(ErrorComponent);
const renderedComponent = mount(<Element {...props} />);
expect(renderedComponent.find('.sc-jss-placeholder-error').length).to.equal(1);
});

it('should render custom component error on wrapped component error, when provided', () => {
const phKey = 'page-content';
const props: PlaceholderProps = {
name: phKey,
rendering: null,
componentFactory: componentFactory,
errorComponent: ErrorMessageComponent,
};
const Element = withPlaceholder(phKey)(ErrorComponent);
const renderedComponent = mount(<Element {...props} />);
expect(renderedComponent.find('.error-handled').length).to.equal(1);
});
});

testData.forEach((dataSet) => {
describe(`with ${dataSet.label}`, () => {
it('should render a placeholder with given key', () => {
const component = (dataSet.data.sitecore.route.placeholders.main as (
| ComponentRendering
| RouteData
)[]).find((c) => (c as ComponentRendering).componentName);
const phKey = 'page-content';
const props: PlaceholderProps = {
name: phKey,
rendering: component,
componentFactory: componentFactory,
};
const Element = withPlaceholder(phKey)(Home);
const renderedComponent = mount(
<SitecoreContext componentFactory={componentFactory}>
<Element {...props} />
</SitecoreContext>
);
expect(renderedComponent.find('.download-callout-mock').length).to.equal(1);
});
it('should render a placeholder with given key and prop', () => {
const component = (dataSet.data.sitecore.route.placeholders.main as (
| ComponentRendering
| RouteData
)[]).find((c) => (c as ComponentRendering).componentName);
const phKeyAndProp = {
placeholder: 'page-header',
prop: 'subProp',
};
const props: PlaceholderProps = {
name: 'page-header',
rendering: component,
componentFactory: componentFactory,
};
const Element = withPlaceholder(phKeyAndProp)(Home);
const renderedComponent = mount(
<SitecoreContext componentFactory={componentFactory}>
<Element {...props} />
</SitecoreContext>
);
expect(renderedComponent.find('.home-mock-with-prop').length).to.not.equal(0);
expect(renderedComponent.find('.jumbotron-mock').length).to.equal(1);
});
it('should use propsTransformer method when provided', () => {
const component = (dataSet.data.sitecore.route.placeholders.main as (
| ComponentRendering
| RouteData
)[]).find((c) => (c as ComponentRendering).componentName);
const phKeyAndProp = {
placeholder: 'page-header',
prop: 'subProp',
};
const phOptions = {
propsTransformer: (props) => {
return { ...props, reset: true };
},
};
const props: PlaceholderProps = {
name: 'page-header',
rendering: component,
componentFactory: componentFactory,
};
const Element = withPlaceholder(phKeyAndProp, phOptions)(Home);
const renderedComponent = mount(
<SitecoreContext componentFactory={componentFactory}>
<Element {...props} />
</SitecoreContext>
);
expect(renderedComponent.find('.home-mock-with-prop').length).to.equal(0);
expect(renderedComponent.find('.home-mock').length).to.not.equal(0);
});
});
});
});
71 changes: 58 additions & 13 deletions packages/sitecore-jss-react/src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,64 @@
import { expect } from 'chai';
import { convertStyleAttribute } from './utils';
import { addClassName, convertAttributesToReactProps, convertStyleAttribute } from './utils';

describe('convertStyleAttribute', () => {
it('should return object representation of style attribute names and values', () => {
const data = {
style: 'background-color: white; opacity: 0.35; filter: alpha(opacity=35);',
};
describe('jss-react utils', () => {
describe('convertStyleAttribute', () => {
it('should return object representation of style attribute names and values', () => {
const data = {
style: 'background-color: white; opacity: 0.35; filter: alpha(opacity=35);',
};

const expected = {
backgroundColor: 'white',
opacity: 0.35,
filter: 'alpha(opacity=35)',
};
const expected = {
backgroundColor: 'white',
opacity: 0.35,
filter: 'alpha(opacity=35)',
};

const actual = convertStyleAttribute(data.style);
expect(actual).to.eql(expected);
const actual = convertStyleAttribute(data.style);
expect(actual).to.eql(expected);
});
});
describe('convertAttributesToReactProps', () => {
it('should covert class and style attributes', () => {
const inputAttr = {
class: 'classy',
style: 'background-color: white; opacity: 0.35; filter: alpha(opacity=35);',
};

const expected = {
className: 'classy',
style: {
backgroundColor: 'white',
opacity: 0.35,
filter: 'alpha(opacity=35)',
},
};

const outputAttr = convertAttributesToReactProps(inputAttr);
expect(outputAttr).to.deep.equal(expected);
});
});

describe('addClassName', () => {
it('should add class attribute value to className', () => {
const modifiableAttrs = {
className: 'first-class',
class: 'second-class',
};
addClassName(modifiableAttrs);
expect(modifiableAttrs).to.deep.equal({
className: 'first-class second-class',
});

it('should convert class attribute value to className when className is absent', () => {
const modifiableAttrs = {
class: 'second-class',
};
addClassName(modifiableAttrs);
expect(modifiableAttrs).to.deep.equal({
className: 'second-class',
});
});
});
});
});

0 comments on commit 2e189ca

Please sign in to comment.