From de2dc7c11dc415803c6f34636ad2db2669361604 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Sun, 23 Jun 2024 14:01:53 +0300 Subject: [PATCH 01/25] default empty field editing placeholder for text field component --- .../DefaultEmptyFieldEditingComponent.tsx | 5 + .../src/components/Text.test.tsx | 91 +++++++++++++++---- .../src/components/Text.tsx | 17 +++- 3 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx diff --git a/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx new file mode 100644 index 0000000000..bc6edbb560 --- /dev/null +++ b/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export const DefaultEmptyFieldEditingComponent: React.FC = () => { + return [No text in field]; +}; diff --git a/packages/sitecore-jss-react/src/components/Text.test.tsx b/packages/sitecore-jss-react/src/components/Text.test.tsx index 633cd1fbb9..c370c8f137 100644 --- a/packages/sitecore-jss-react/src/components/Text.test.tsx +++ b/packages/sitecore-jss-react/src/components/Text.test.tsx @@ -179,7 +179,7 @@ describe('', () => { expect(rendered.html()).to.contain('value'); }); - it('should render field metadata component when metadata property is present', () => { + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -192,25 +192,78 @@ describe('', () => { rawValue: 'Test1', }; - const field = { - value: 'value', - metadata: testMetadata, - }; + it('should render field metadata component when metadata property is present', () => { + const field = { + value: 'value', + metadata: testMetadata, + }; - const rendered = mount( - -
test
-
- ); + const rendered = mount(); - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}`, - 'value', - '', - ].join('') - ); + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'value', + '', + ].join('') + ); + }); + + it('should render empty field markup in edit mode when metadata is provided and field value is not present', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder component when provided, when metadata is provided and field value is not present', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing is field value is empty, metadata is provided and editing explicitly disabled', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-react/src/components/Text.tsx b/packages/sitecore-jss-react/src/components/Text.tsx index 21f2e9b9fa..f45aa61389 100644 --- a/packages/sitecore-jss-react/src/components/Text.tsx +++ b/packages/sitecore-jss-react/src/components/Text.tsx @@ -1,7 +1,7 @@ import React, { ReactElement } from 'react'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import PropTypes from 'prop-types'; - +import { DefaultEmptyFieldEditingComponent } from './DefaultEmptyFieldEditingComponent'; export interface TextField { value?: string | number; editable?: string; @@ -26,10 +26,23 @@ export interface TextProps { * If false, HTML-encoding of the field value is disabled and the value is rendered as-is. */ encode?: boolean; + /** + * -- Edit Mode Metadata -- + * + * Custom element to render in Pages in Metadata edit mode if field value is empty + */ + emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; } export const Text: React.FC = withFieldMetadata( - ({ field, tag, editable = true, encode = true, ...otherProps }) => { + ({ field, tag, editable = true, encode = true, emptyValueEditingPlaceholder, ...otherProps }) => { + // render empty field placeholder in editMode metadata + if (field?.metadata && editable && !field?.value) { + const EmptyFieldPhComponent = + emptyValueEditingPlaceholder || DefaultEmptyFieldEditingComponent; + return ; + } + if (!field || (!field.editable && (field.value === undefined || field.value === ''))) { return null; } From e01330fa473699c82e56a1acbd55f157c3b938ac Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 24 Jun 2024 13:49:52 +0300 Subject: [PATCH 02/25] add withEmptyPlaceholderValue HOC; unit tests; default empty field editing components for text and image; modify Link and Text field components --- .../DefaultEmptyFieldEditingComponent.tsx | 5 - .../DefaultEmptyFieldEditingComponents.tsx | 24 +++ .../src/components/Link.test.tsx | 91 ++++++-- .../src/components/Link.tsx | 161 +++++++------- .../src/components/Text.test.tsx | 6 +- .../src/components/Text.tsx | 131 ++++++----- .../withEmptyValueEditingPlaceholder.test.tsx | 203 ++++++++++++++++++ .../withEmptyValueEditingPlaceholder.tsx | 56 +++++ 8 files changed, 510 insertions(+), 167 deletions(-) delete mode 100644 packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx create mode 100644 packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponents.tsx create mode 100644 packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.test.tsx create mode 100644 packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx diff --git a/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx deleted file mode 100644 index bc6edbb560..0000000000 --- a/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponent.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export const DefaultEmptyFieldEditingComponent: React.FC = () => { - return [No text in field]; -}; diff --git a/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponents.tsx b/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponents.tsx new file mode 100644 index 0000000000..be5e820996 --- /dev/null +++ b/packages/sitecore-jss-react/src/components/DefaultEmptyFieldEditingComponents.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +export const DefaultEmptyFieldEditingComponentText: React.FC = () => { + return [No text in field]; +}; + +export const DefaultEmptyFieldEditingComponentImage: React.FC = () => { + const inlineStyles = { + minWidth: '48px', + minHeight: '48px', + maxWidth: '400px', + maxHeight: '400px', + cursor: 'pointer', + }; + + return ( + + ); +}; diff --git a/packages/sitecore-jss-react/src/components/Link.test.tsx b/packages/sitecore-jss-react/src/components/Link.test.tsx index e9fc1942c4..4a515bf41d 100644 --- a/packages/sitecore-jss-react/src/components/Link.test.tsx +++ b/packages/sitecore-jss-react/src/components/Link.test.tsx @@ -126,8 +126,7 @@ describe('', () => { const link = c.find('a'); expect(ref.current?.id).to.equal(link.props().id); }); - - it('should render field metadata component when metadata property is present', () => { + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -140,21 +139,77 @@ describe('', () => { rawValue: 'Test1', }; - const field = { - href: '/lorem', - text: 'ipsum', - metadata: testMetadata, - }; - const rendered = mount(); - - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}`, - 'ipsum', - '', - ].join('') - ); + it('should render field metadata component when metadata property is present', () => { + const field = { + href: '/lorem', + text: 'ipsum', + metadata: testMetadata, + }; + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'ipsum', + '', + ].join('') + ); + }); + + it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + const field = { + value: undefined, + metadata: testMetadata, + }; + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + const field = { + value: undefined, + metadata: testMetadata, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + const field = { + value: undefined, + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-react/src/components/Link.tsx b/packages/sitecore-jss-react/src/components/Link.tsx index d702f44030..190db7ee40 100644 --- a/packages/sitecore-jss-react/src/components/Link.tsx +++ b/packages/sitecore-jss-react/src/components/Link.tsx @@ -1,6 +1,8 @@ import React, { ReactElement, RefAttributes, forwardRef } from 'react'; import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; +import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; export interface LinkFieldValue { [attributeName: string]: unknown; @@ -37,94 +39,105 @@ export type LinkProps = React.AnchorHTMLAttributes & * NOTE: when in Sitecore Experience Editor, this setting is ignored due to technical limitations, and the description is always rendered. */ showLinkTextWithChildrenPresent?: boolean; + /** + * -- Edit Mode Metadata -- + * + * Custom element to render in Pages in Metadata edit mode if field value is empty + */ + emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; }; export const Link: React.FC = withFieldMetadata( - // eslint-disable-next-line react/display-name - forwardRef( - ({ field, editable = true, showLinkTextWithChildrenPresent, ...otherProps }, ref) => { - const children = otherProps.children as React.ReactNode; - const dynamicField: LinkField | LinkFieldValue = field; - - if ( - !field || - (!dynamicField.editableFirstPart && - !dynamicField.value && - !(dynamicField as LinkFieldValue).href) - ) { - return null; - } - - const resultTags: ReactElement[] = []; + withEmptyValueEditingPlaceholder( + // eslint-disable-next-line react/display-name + forwardRef( + ({ field, editable = true, showLinkTextWithChildrenPresent, ...otherProps }, ref) => { + const children = otherProps.children as React.ReactNode; + const dynamicField: LinkField | LinkFieldValue = field; + + if ( + !field || + (!dynamicField.editableFirstPart && + !dynamicField.value && + !(dynamicField as LinkFieldValue).href) + ) { + return null; + } - // EXPERIENCE EDITOR RENDERING - if (editable && dynamicField.editableFirstPart) { - const markup = (dynamicField.editableFirstPart as string) + dynamicField.editableLastPart; + const resultTags: ReactElement[] = []; + + // EXPERIENCE EDITOR RENDERING + if (editable && dynamicField.editableFirstPart) { + const markup = (dynamicField.editableFirstPart as string) + dynamicField.editableLastPart; + + // in an ideal world, we'd pre-render React children here and inject them between editableFirstPart and editableLastPart. + // However, we cannot combine arbitrary unparsed HTML (innerHTML) based components with actual vDOM components (the children) + // because the innerHTML is not parsed - it'd make a discontinuous vDOM. So, we'll go for the next best compromise of rendering the link field and children separately + // as siblings. Should be "good enough" for most cases - and write your own helper if it isn't. Or bring xEditor out of 2006. + + const htmlProps = { + className: 'sc-link-wrapper', + dangerouslySetInnerHTML: { + __html: markup, + }, + ...otherProps, + key: 'editable', + }; + + // Exclude children, since 'dangerouslySetInnerHTML' and 'children' can't be set together + // and children will be added as a sibling + delete htmlProps.children; + + resultTags.push(); + + // don't render normal link tag when editing, if no children exist + // this preserves normal-ish behavior if not using a link body (no hacks required) + if (!children) { + return resultTags[0]; + } + } - // in an ideal world, we'd pre-render React children here and inject them between editableFirstPart and editableLastPart. - // However, we cannot combine arbitrary unparsed HTML (innerHTML) based components with actual vDOM components (the children) - // because the innerHTML is not parsed - it'd make a discontinuous vDOM. So, we'll go for the next best compromise of rendering the link field and children separately - // as siblings. Should be "good enough" for most cases - and write your own helper if it isn't. Or bring xEditor out of 2006. + // handle link directly on field for forgetful devs + const link = (dynamicField as LinkFieldValue).href + ? (field as LinkFieldValue) + : (dynamicField as LinkField).value; - const htmlProps = { - className: 'sc-link-wrapper', - dangerouslySetInnerHTML: { - __html: markup, - }, - ...otherProps, - key: 'editable', - }; + if (!link) { + return null; + } - // Exclude children, since 'dangerouslySetInnerHTML' and 'children' can't be set together - // and children will be added as a sibling - delete htmlProps.children; + const anchor = link.linktype !== 'anchor' && link.anchor ? `#${link.anchor}` : ''; + const querystring = link.querystring ? `?${link.querystring}` : ''; - resultTags.push(); + const anchorAttrs: { [attr: string]: unknown } = { + href: `${link.href}${querystring}${anchor}`, + className: link.class, + title: link.title, + target: link.target, + }; - // don't render normal link tag when editing, if no children exist - // this preserves normal-ish behavior if not using a link body (no hacks required) - if (!children) { - return resultTags[0]; + if (anchorAttrs.target === '_blank' && !anchorAttrs.rel) { + // information disclosure attack prevention keeps target blank site from getting ref to window.opener + anchorAttrs.rel = 'noopener noreferrer'; } - } - - // handle link directly on field for forgetful devs - const link = (dynamicField as LinkFieldValue).href - ? (field as LinkFieldValue) - : (dynamicField as LinkField).value; - - if (!link) { - return null; - } - const anchor = link.linktype !== 'anchor' && link.anchor ? `#${link.anchor}` : ''; - const querystring = link.querystring ? `?${link.querystring}` : ''; + const linkText = + showLinkTextWithChildrenPresent || !children ? link.text || link.href : null; - const anchorAttrs: { [attr: string]: unknown } = { - href: `${link.href}${querystring}${anchor}`, - className: link.class, - title: link.title, - target: link.target, - }; + resultTags.push( + React.createElement( + 'a', + { ...anchorAttrs, ...otherProps, key: 'link', ref }, + linkText, + children + ) + ); - if (anchorAttrs.target === '_blank' && !anchorAttrs.rel) { - // information disclosure attack prevention keeps target blank site from getting ref to window.opener - anchorAttrs.rel = 'noopener noreferrer'; + return {resultTags}; } - - const linkText = showLinkTextWithChildrenPresent || !children ? link.text || link.href : null; - - resultTags.push( - React.createElement( - 'a', - { ...anchorAttrs, ...otherProps, key: 'link', ref }, - linkText, - children - ) - ); - - return {resultTags}; - } + ), + DefaultEmptyFieldEditingComponentText, + true ), true ); diff --git a/packages/sitecore-jss-react/src/components/Text.test.tsx b/packages/sitecore-jss-react/src/components/Text.test.tsx index c370c8f137..f9fcb5843f 100644 --- a/packages/sitecore-jss-react/src/components/Text.test.tsx +++ b/packages/sitecore-jss-react/src/components/Text.test.tsx @@ -211,7 +211,7 @@ describe('', () => { ); }); - it('should render empty field markup in edit mode when metadata is provided and field value is not present', () => { + it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { const field = { value: '', metadata: testMetadata, @@ -230,7 +230,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder component when provided, when metadata is provided and field value is not present', () => { + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { const field = { value: '', metadata: testMetadata, @@ -255,7 +255,7 @@ describe('', () => { ); }); - it('should render nothing is field value is empty, metadata is provided and editing explicitly disabled', () => { + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { const field = { value: '', metadata: testMetadata, diff --git a/packages/sitecore-jss-react/src/components/Text.tsx b/packages/sitecore-jss-react/src/components/Text.tsx index f45aa61389..318e62cd09 100644 --- a/packages/sitecore-jss-react/src/components/Text.tsx +++ b/packages/sitecore-jss-react/src/components/Text.tsx @@ -1,7 +1,8 @@ import React, { ReactElement } from 'react'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; +import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import PropTypes from 'prop-types'; -import { DefaultEmptyFieldEditingComponent } from './DefaultEmptyFieldEditingComponent'; export interface TextField { value?: string | number; editable?: string; @@ -35,77 +36,73 @@ export interface TextProps { } export const Text: React.FC = withFieldMetadata( - ({ field, tag, editable = true, encode = true, emptyValueEditingPlaceholder, ...otherProps }) => { - // render empty field placeholder in editMode metadata - if (field?.metadata && editable && !field?.value) { - const EmptyFieldPhComponent = - emptyValueEditingPlaceholder || DefaultEmptyFieldEditingComponent; - return ; - } - - if (!field || (!field.editable && (field.value === undefined || field.value === ''))) { - return null; - } - - // can't use editable value if we want to output unencoded - if (!encode) { - // eslint-disable-next-line no-param-reassign - editable = false; - } - - const isEditable = field.editable && editable; - - let output: string | number | (ReactElement | string)[] = isEditable - ? field.editable || '' - : field.value === undefined - ? '' - : field.value; - - // when string value isn't formatted, we should format line breaks - if (!field.editable && typeof output === 'string') { - const splitted = String(output).split('\n'); - - if (splitted.length) { - const formatted: (ReactElement | string)[] = []; - - splitted.forEach((str, i) => { - const isLast = i === splitted.length - 1; - - formatted.push(str); - - if (!isLast) { - formatted.push(
); - } - }); - - output = formatted; + withEmptyValueEditingPlaceholder( + ({ field, tag, editable = true, encode = true, ...otherProps }) => { + if (!field || (!field.editable && (field.value === undefined || field.value === ''))) { + return null; } - } - const setDangerously = isEditable || !encode; + // can't use editable value if we want to output unencoded + if (!encode) { + // eslint-disable-next-line no-param-reassign + editable = false; + } + + const isEditable = field.editable && editable; + + let output: string | number | (ReactElement | string)[] = isEditable + ? field.editable || '' + : field.value === undefined + ? '' + : field.value; + + // when string value isn't formatted, we should format line breaks + if (!field.editable && typeof output === 'string') { + const splitted = String(output).split('\n'); + + if (splitted.length) { + const formatted: (ReactElement | string)[] = []; + + splitted.forEach((str, i) => { + const isLast = i === splitted.length - 1; + + formatted.push(str); - let children = null; - const htmlProps: { - [htmlAttributes: string]: unknown; - children?: React.ReactNode; - } = { - ...otherProps, - }; + if (!isLast) { + formatted.push(
); + } + }); - if (setDangerously) { - htmlProps.dangerouslySetInnerHTML = { - __html: output, + output = formatted; + } + } + + const setDangerously = isEditable || !encode; + + let children = null; + const htmlProps: { + [htmlAttributes: string]: unknown; + children?: React.ReactNode; + } = { + ...otherProps, }; - } else { - children = output; - } - - if (tag || setDangerously) { - return React.createElement(tag || 'span', htmlProps, children); - } else { - return {children}; - } - } + + if (setDangerously) { + htmlProps.dangerouslySetInnerHTML = { + __html: output, + }; + } else { + children = output; + } + + if (tag || setDangerously) { + return React.createElement(tag || 'span', htmlProps, children); + } else { + return {children}; + } + }, + DefaultEmptyFieldEditingComponentText + ) ); Text.propTypes = { diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.test.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.test.tsx new file mode 100644 index 0000000000..d2331b0c7b --- /dev/null +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.test.tsx @@ -0,0 +1,203 @@ +/* eslint-disable no-unused-expressions */ +import React, { forwardRef } from 'react'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { withEmptyValueEditingPlaceholder } from './withEmptyValueEditingPlaceholder'; +import { DefaultEmptyFieldEditingComponentText } from '../components/DefaultEmptyFieldEditingComponents'; +import { describe } from 'node:test'; + +describe('withEmptyValueEditingPlaceholder', () => { + const testMetadata = { + contextItem: { + id: '{09A07660-6834-476C-B93B-584248D3003B}', + language: 'en', + revision: 'a0b36ce0a7db49418edf90eb9621e145', + version: 1, + }, + fieldId: '{414061F4-FBB1-4591-BC37-BFFA67F745EB}', + fieldType: 'single-line', + rawValue: 'Test1', + }; + + type TestComponentProps = { + field?: { + value?: string; + metadata?: { [key: string]: unknown }; + }; + editable?: boolean; + }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const TestComponent = (props: TestComponentProps) => { + return ( +
+

{props.field?.value}

+

foo

+

bar

+
+ ); + }; + + // eslint-disable-next-line react/display-name + const TestComponentWithRef = forwardRef( + (props: TestComponentProps, ref: React.ForwardedRef) => { + return ( +
+

{props.field?.value}

+

foo

+

bar

+
+ ); + } + ); + + it('Should render provided default empty value placeholder component if field value is empty in metadata edit mode', () => { + const props = { + field: { + value: '', + metadata: testMetadata, + }, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponent, + DefaultEmptyFieldEditingComponentText + ); + + const rendered = mount(); + const expected = mount(); + + expect(rendered.html()).to.equal(expected.html()); + }); + + it('Should render custom empty value placeholder if provided via props if field value is empty in metadata edit mode', () => { + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const props = { + field: { + value: '', + metadata: testMetadata, + }, + emptyValueEditingPlaceholder: EmptyValueEditingPlaceholder, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponent, + DefaultEmptyFieldEditingComponentText + ); + + const rendered = mount(); + const expected = mount(); + + expect(rendered.html()).to.equal(expected.html()); + }); + + it('Should render component if field value is provided', () => { + const props = { + field: { + value: 'field value', + metadata: testMetadata, + }, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponent, + DefaultEmptyFieldEditingComponentText + ); + + const rendered = mount(); + expect(rendered.html()).to.equal('

field value

foo

bar

'); + }); + + it('Should render component if component is explicitly not editable if value is empty in metadata edit mode', () => { + const props = { + field: { + value: '', + metadata: testMetadata, + }, + editable: false, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponent, + DefaultEmptyFieldEditingComponentText + ); + + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); + }); + + it('Should render component if metadata is not provided', () => { + const props = { + field: { + value: '', + }, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponent, + DefaultEmptyFieldEditingComponentText + ); + + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); + }); + + it('Should render component with forward ref if field value is provided in metadata edit mode', () => { + const props = { + field: { + value: 'field value', + metadata: testMetadata, + }, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponentWithRef, + DefaultEmptyFieldEditingComponentText, + true + ); + const ref = React.createRef(); + const rendered = mount(); + + expect(ref.current?.outerHTML).to.equal('

foo

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

field value

foo

bar

'); + }); + + it('Should render component if field src value is provided (case for Image component)', () => { + const props = { + field: { + value: '', + metadata: testMetadata, + src: 'img src', + }, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponent, + DefaultEmptyFieldEditingComponentText + ); + + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); + }); + + it('Should render component if field href value is provided (case for Link component)', () => { + const props = { + field: { + value: '', + metadata: testMetadata, + href: 'img src', + }, + }; + + const WrappedComponent = withEmptyValueEditingPlaceholder( + TestComponent, + DefaultEmptyFieldEditingComponentText + ); + + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); + }); +}); diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx new file mode 100644 index 0000000000..c86016bbfd --- /dev/null +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx @@ -0,0 +1,56 @@ +import React, { ComponentType, forwardRef } from 'react'; +import { LinkFieldValue } from '../components/Link'; +import { ImageFieldValue } from '../components/Image'; + +interface GeneralField { + metadata?: { [key: string]: unknown }; + value?: unknown; +} + +interface WithEmptyValueEditingPlaceholderProps { + field?: GeneralField; + editable?: boolean; + emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; +} + +export function withEmptyValueEditingPlaceholder< + FieldComponentProps extends WithEmptyValueEditingPlaceholderProps, + RefElementType = HTMLElement +>( + FieldComponent: ComponentType, + defaultEmptyFieldEditingComponent: React.FC, + isForwardRef = false +) { + const hasValue = (field: GeneralField | ImageFieldValue | LinkFieldValue) => + field?.value || (field as ImageFieldValue)?.src || (field as LinkFieldValue)?.href; + + function getEmptyFieldPhComponent(props: FieldComponentProps): React.ComponentClass | React.FC { + const { editable = true } = props; + // render empty field placeholder in editMode metadata + if (props.field?.metadata && editable && !hasValue(props.field)) { + return props.emptyValueEditingPlaceholder || defaultEmptyFieldEditingComponent; + } + + return null; + } + + if (isForwardRef) { + return forwardRef((props: FieldComponentProps, ref: React.ForwardedRef) => { + const EmptyFieldPhComponent = getEmptyFieldPhComponent(props); + return ( + <> + {(EmptyFieldPhComponent && ) || ( + + )} + + ); + }); + } + + return (props: FieldComponentProps) => { + const EmptyFieldPhComponent = getEmptyFieldPhComponent(props); + return ( + <>{(EmptyFieldPhComponent && ) || } + ); + }; +} From c0d4681661c6ed5f9e8f7db6afca1580c07621a1 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 24 Jun 2024 14:22:31 +0300 Subject: [PATCH 03/25] modify Date, Image, RichText react components to use withEmptyValueEditingPlaceholder --- .../src/components/Date.test.tsx | 92 +++++++++++++++---- .../src/components/Date.tsx | 65 +++++++------ .../src/components/Image.test.tsx | 92 +++++++++++++++---- .../src/components/Image.tsx | 84 +++++++++-------- .../src/components/RichText.test.tsx | 90 ++++++++++++++---- .../src/components/RichText.tsx | 42 ++++++--- 6 files changed, 337 insertions(+), 128 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/Date.test.tsx b/packages/sitecore-jss-react/src/components/Date.test.tsx index b234517d5c..4cb83e785a 100644 --- a/packages/sitecore-jss-react/src/components/Date.test.tsx +++ b/packages/sitecore-jss-react/src/components/Date.test.tsx @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { mount, shallow } from 'enzyme'; import React from 'react'; import { DateField } from './Date'; +import { describe } from 'node:test'; describe('', () => { it('should return null if no editable or value', () => { @@ -81,7 +82,7 @@ describe('', () => { expect(c.html()).equal('

11-23-2001

'); }); - it('should render field metadata component when metadata property is present', () => { + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -94,23 +95,80 @@ describe('', () => { rawValue: 'Test1', }; - const props = { - field: { - value: '23-11-2001', + it('should render field metadata component when metadata property is present', () => { + const props = { + field: { + value: '23-11-2001', + metadata: testMetadata, + }, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '23-11-2001', + '', + ].join('') + ); + }); + + it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + const field = { + value: '', metadata: testMetadata, - }, - }; + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); - const rendered = mount(); - - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}`, - '23-11-2001', - '', - ].join('') - ); + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-react/src/components/Date.tsx b/packages/sitecore-jss-react/src/components/Date.tsx index 116df23074..361309062e 100644 --- a/packages/sitecore-jss-react/src/components/Date.tsx +++ b/packages/sitecore-jss-react/src/components/Date.tsx @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; +import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; export interface DateFieldProps { /** The date field data. */ @@ -21,39 +23,48 @@ export interface DateFieldProps { */ editable?: boolean; render?: (date: Date | null) => React.ReactNode; + /** + * -- Edit Mode Metadata -- + * + * Custom element to render in Pages in Metadata edit mode if field value is empty + */ + emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; } -export const DateField: React.FC = withFieldMetadata( - ({ field, tag, editable = true, render, ...otherProps }) => { - if (!field || (!field.editable && !field.value)) { - return null; - } +export const DateField: React.FC = withFieldMetadata( + withEmptyValueEditingPlaceholder( + ({ field, tag, editable = true, render, ...otherProps }) => { + if (!field || (!field.editable && !field.value)) { + return null; + } - let children: React.ReactNode; + let children: React.ReactNode; - const htmlProps: { - [htmlAttr: string]: unknown; - children?: React.ReactNode; - } = { - ...otherProps, - }; - - if (field.editable && editable) { - htmlProps.dangerouslySetInnerHTML = { - __html: field.editable, + const htmlProps: { + [htmlAttr: string]: unknown; + children?: React.ReactNode; + } = { + ...otherProps, }; - } else if (render) { - children = render(field.value ? new Date(field.value) : null); - } else { - children = field.value; - } - if (tag || (field.editable && editable)) { - return React.createElement(tag || 'span', htmlProps, children); - } else { - return {children}; - } - } + if (field.editable && editable) { + htmlProps.dangerouslySetInnerHTML = { + __html: field.editable, + }; + } else if (render) { + children = render(field.value ? new Date(field.value) : null); + } else { + children = field.value; + } + + if (tag || (field.editable && editable)) { + return React.createElement(tag || 'span', htmlProps, children); + } else { + return {children}; + } + }, + DefaultEmptyFieldEditingComponentText + ) ); DateField.propTypes = { diff --git a/packages/sitecore-jss-react/src/components/Image.test.tsx b/packages/sitecore-jss-react/src/components/Image.test.tsx index eb212bb02b..ae980f7b9c 100644 --- a/packages/sitecore-jss-react/src/components/Image.test.tsx +++ b/packages/sitecore-jss-react/src/components/Image.test.tsx @@ -5,6 +5,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { imageField as eeImageData } from '../test-data/ee-data'; import { Image, ImageField } from './Image'; +import { DefaultEmptyFieldEditingComponentImage } from './DefaultEmptyFieldEditingComponents'; const expect = chai.use(chaiString).expect; @@ -294,7 +295,7 @@ describe('', () => { expect(rendered.find('img')).to.have.length(1); }); - it('should render field metadata component when metadata property is present', () => { + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -307,22 +308,79 @@ describe('', () => { rawValue: 'Test1', }; - const imgField = { - src: '/assets/img/test0.png', - width: 8, - height: 10, - metadata: testMetadata, - }; - const rendered = mount(); + it('should render field metadata component when metadata property is present', () => { + const imgField = { + src: '/assets/img/test0.png', + width: 8, + height: 10, + metadata: testMetadata, + }; + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '', + '', + ].join('') + ); + }); + + it('should render default empty field placeholder for Image when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); + const defaultEmptyImagePlaceholder = mount(); + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + defaultEmptyImagePlaceholder.html(), + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}`, - '', - '', - ].join('') - ); + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index ec5c8672d8..77e147a71d 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -4,6 +4,8 @@ import React from 'react'; import { addClassName, convertAttributesToReactProps } from '../utils'; import { getAttributesString } from '../utils'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; +import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { DefaultEmptyFieldEditingComponentImage } from './DefaultEmptyFieldEditingComponents'; export interface ImageFieldValue { [attributeName: string]: unknown; @@ -65,6 +67,13 @@ export interface ImageProps { mediaUrlPrefix?: RegExp; /** HTML attributes that will be appended to the rendered tag. */ + + /** + * -- Edit Mode Metadata -- + * + * Custom element to render in Pages in Metadata edit mode if field value is empty + */ + emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; } const getEditableWrapper = (editableMarkup: string, ...otherProps: unknown[]) => ( @@ -144,42 +153,45 @@ export const getEEMarkup = ( }; export const Image: React.FC = withFieldMetadata( - ({ editable = true, imageParams, field, mediaUrlPrefix, ...otherProps }) => { - const dynamicMedia = field as ImageField | ImageFieldValue; - - if ( - !field || - (!dynamicMedia.editable && !dynamicMedia.value && !(dynamicMedia as ImageFieldValue).src) - ) { - return null; - } - - const imageField = dynamicMedia as ImageField; - - if (editable && imageField.editable) { - return getEEMarkup(imageField, imageParams, mediaUrlPrefix, otherProps); - } - - // some wise-guy/gal is passing in a 'raw' image object value - const img = (dynamicMedia as ImageFieldValue).src - ? field - : (dynamicMedia.value as ImageFieldValue); - if (!img) { - return null; - } - - // prevent metadata from being passed to the img tag - if (img.metadata) { - delete img.metadata; - } - - const attrs = getImageAttrs({ ...img, ...otherProps }, imageParams, mediaUrlPrefix); - if (attrs) { - return ; - } - - return null; // we can't handle the truth - } + withEmptyValueEditingPlaceholder( + ({ editable = true, imageParams, field, mediaUrlPrefix, ...otherProps }) => { + const dynamicMedia = field as ImageField | ImageFieldValue; + + if ( + !field || + (!dynamicMedia.editable && !dynamicMedia.value && !(dynamicMedia as ImageFieldValue).src) + ) { + return null; + } + + const imageField = dynamicMedia as ImageField; + + if (editable && imageField.editable) { + return getEEMarkup(imageField, imageParams, mediaUrlPrefix, otherProps); + } + + // some wise-guy/gal is passing in a 'raw' image object value + const img = (dynamicMedia as ImageFieldValue).src + ? field + : (dynamicMedia.value as ImageFieldValue); + if (!img) { + return null; + } + + // prevent metadata from being passed to the img tag + if (img.metadata) { + delete img.metadata; + } + + const attrs = getImageAttrs({ ...img, ...otherProps }, imageParams, mediaUrlPrefix); + if (attrs) { + return ; + } + + return null; // we can't handle the truth + }, + DefaultEmptyFieldEditingComponentImage + ) ); Image.propTypes = { diff --git a/packages/sitecore-jss-react/src/components/RichText.test.tsx b/packages/sitecore-jss-react/src/components/RichText.test.tsx index 1a741ca318..e594f34618 100644 --- a/packages/sitecore-jss-react/src/components/RichText.test.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.test.tsx @@ -4,6 +4,7 @@ import { mount } from 'enzyme'; import { RichText, RichTextField } from './RichText'; import { richTextField as eeRichTextData } from '../test-data/ee-data'; +import { describe } from 'node:test'; describe('', () => { it('should render nothing with missing field', () => { @@ -95,7 +96,7 @@ describe('', () => { expect(rendered.html()).to.contain('value'); }); - it('should render field metadata component when metadata property is present', () => { + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -108,21 +109,78 @@ describe('', () => { rawValue: 'Test1', }; - const field = { - value: 'value', - metadata: testMetadata, - }; + it('should render field metadata component when metadata property is present', () => { + const field = { + value: 'value', + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '
value
', + '', + ].join('') + ); + }); + + it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); - const rendered = mount(); - - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}`, - '
value
', - '', - ].join('') - ); + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-react/src/components/RichText.tsx b/packages/sitecore-jss-react/src/components/RichText.tsx index e9c767c22f..f655444be0 100644 --- a/packages/sitecore-jss-react/src/components/RichText.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.tsx @@ -1,6 +1,8 @@ import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; +import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; export interface RichTextField { value?: string; @@ -23,26 +25,36 @@ export interface RichTextProps { * @default true */ editable?: boolean; + /** + * -- Edit Mode Metadata -- + * + * Custom element to render in Pages in Metadata edit mode if field value is empty + */ + emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; } export const RichText: React.FC = withFieldMetadata( - // eslint-disable-next-line react/display-name - forwardRef( - ({ field, tag = 'div', editable = true, ...otherProps }, ref) => { - if (!field || (!field.editable && !field.value)) { - return null; - } + withEmptyValueEditingPlaceholder( + // eslint-disable-next-line react/display-name + forwardRef( + ({ field, tag = 'div', editable = true, ...otherProps }, ref) => { + if (!field || (!field.editable && !field.value)) { + return null; + } - const htmlProps = { - dangerouslySetInnerHTML: { - __html: field.editable && editable ? field.editable : field.value, - }, - ref, - ...otherProps, - }; + const htmlProps = { + dangerouslySetInnerHTML: { + __html: field.editable && editable ? field.editable : field.value, + }, + ref, + ...otherProps, + }; - return React.createElement(tag || 'div', htmlProps); - } + return React.createElement(tag || 'div', htmlProps); + } + ), + DefaultEmptyFieldEditingComponentText, + true ), true ); From 32bfea9fe90feadc541551184f4c4fec93c81fcb Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 24 Jun 2024 16:04:06 +0300 Subject: [PATCH 04/25] update Link, NextImage, RichText nextjs fields components to work with withEmptyValueEditingPlaceholder --- .../src/components/Link.test.tsx | 118 +++++++++--- .../src/components/Link.tsx | 5 +- .../src/components/NextImage.test.tsx | 94 ++++++++-- .../src/components/NextImage.tsx | 120 ++++++------ .../src/components/RichText.test.tsx | 171 +++++++++++++----- .../src/components/RichText.tsx | 2 +- packages/sitecore-jss-react/src/index.ts | 5 + 7 files changed, 371 insertions(+), 144 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx index 921c7eb942..178fac4e0e 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx @@ -7,6 +7,7 @@ import { mount } from 'enzyme'; import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime'; import { Link } from './Link'; import { spy } from 'sinon'; +import { describe } from 'node:test'; const Router = (): NextRouter => ({ pathname: '/', @@ -361,7 +362,7 @@ describe('', () => { expect(rendered).to.have.length(0); }); - it('should render field metadata component when metadata property is present', () => { + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -374,29 +375,96 @@ describe('', () => { rawValue: 'Test1', }; - const field = { - value: { - href: '/lorem', - text: 'ipsum', - class: 'my-link', - }, - metadata: testMetadata, - }; - - const rendered = mount( - - - - ); - - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}`, - 'ipsum', - '', - ].join('') - ); + it('should render field metadata component when metadata property is present', () => { + const field = { + value: { + href: '/lorem', + text: 'ipsum', + class: 'my-link', + }, + metadata: testMetadata, + }; + + const rendered = mount( + + + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'ipsum', + '', + ].join('') + ); + }); + + it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount( + + + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + ``, + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + ``, + ].join('') + ); + }); + + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount( + + + + ); + + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-nextjs/src/components/Link.tsx b/packages/sitecore-jss-nextjs/src/components/Link.tsx index 9325aba698..d3034d21fb 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.tsx @@ -30,7 +30,10 @@ export const Link = forwardRef( if ( !field || - (!(field as LinkFieldValue).editable && !field.value && !(field as LinkFieldValue).href) + (!(field as LinkFieldValue).editable && + !field.value && + !(field as LinkFieldValue).href && + !field.metadata) ) { return null; } diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx index cf99249d21..f59de0cd6b 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx @@ -4,7 +4,10 @@ import chaiString from 'chai-string'; import { mount } from 'enzyme'; import React from 'react'; import { NextImage } from './NextImage'; -import { ImageField } from '@sitecore-jss/sitecore-jss-react'; +import { + ImageField, + DefaultEmptyFieldEditingComponentImage, +} from '@sitecore-jss/sitecore-jss-react'; import { ImageLoader } from 'next/image'; import { spy, match } from 'sinon'; import sinonChai from 'sinon-chai'; @@ -287,7 +290,7 @@ describe('', () => { }); }); - it('should render field metadata component when metadata property is present', () => { + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -300,21 +303,78 @@ describe('', () => { rawValue: 'Test1', }; - const field = { - value: { src: '/assets/img/test0.png', alt: 'my image' }, - metadata: testMetadata, - }; + it('should render field metadata component when metadata property is present', () => { + const field = { + value: { src: '/assets/img/test0.png', alt: 'my image' }, + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'my image', + '', + ].join('') + ); + }); + + it('should render default empty field placeholder for Image when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); + const defaultEmptyImagePlaceholder = mount(); + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + defaultEmptyImagePlaceholder.html(), + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); - const rendered = mount(); - - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}`, - 'my image', - '', - ].join('') - ); + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + const field = { + value: '', + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx index 6e0f3bbe06..5123427a51 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx @@ -9,77 +9,81 @@ import { withFieldMetadata, } from '@sitecore-jss/sitecore-jss-react'; import Image, { ImageProps as NextImageProperties } from 'next/image'; +import { withEmptyValueEditingPlaceholder } from '@sitecore-jss/sitecore-jss-react'; +import { DefaultEmptyFieldEditingComponentImage } from '@sitecore-jss/sitecore-jss-react'; type NextImageProps = ImageProps & Partial; - export const NextImage: React.FC = withFieldMetadata( - ({ editable = true, imageParams, field, mediaUrlPrefix, fill, priority, ...otherProps }) => { - // next handles src and we use a custom loader, - // throw error if these are present - if (otherProps.src) { - throw new Error('Detected src prop. If you wish to use src, use next/image directly.'); - } + withEmptyValueEditingPlaceholder( + ({ editable = true, imageParams, field, mediaUrlPrefix, fill, priority, ...otherProps }) => { + // next handles src and we use a custom loader, + // throw error if these are present + if (otherProps.src) { + throw new Error('Detected src prop. If you wish to use src, use next/image directly.'); + } - const dynamicMedia = field as ImageField | ImageFieldValue; + const dynamicMedia = field as ImageField | ImageFieldValue; - if ( - !field || - (!dynamicMedia.editable && !dynamicMedia.value && !(dynamicMedia as ImageFieldValue).src) - ) { - return null; - } + if ( + !field || + (!dynamicMedia.editable && !dynamicMedia.value && !(dynamicMedia as ImageFieldValue).src) + ) { + return null; + } - const imageField = dynamicMedia as ImageField; + const imageField = dynamicMedia as ImageField; - // we likely have an experience editor value, should be a string - if (editable && imageField.editable) { - return getEEMarkup( - imageField, - imageParams as { [paramName: string]: string | number }, - mediaUrlPrefix as RegExp, - otherProps as { src: string } - ); - } + // we likely have an experience editor value, should be a string + if (editable && imageField.editable) { + return getEEMarkup( + imageField, + imageParams as { [paramName: string]: string | number }, + mediaUrlPrefix as RegExp, + otherProps as { src: string } + ); + } - // some wise-guy/gal is passing in a 'raw' image object value - const img: ImageFieldValue = (dynamicMedia as ImageFieldValue).src - ? (field as ImageFieldValue) - : (dynamicMedia.value as ImageFieldValue); - if (!img) { - return null; - } + // some wise-guy/gal is passing in a 'raw' image object value + const img: ImageFieldValue = (dynamicMedia as ImageFieldValue).src + ? (field as ImageFieldValue) + : (dynamicMedia.value as ImageFieldValue); + if (!img) { + return null; + } - const attrs = { - ...img, - ...otherProps, - fill, - priority, - src: mediaApi.updateImageUrl( - img.src as string, - imageParams as { [paramName: string]: string | number }, - mediaUrlPrefix as RegExp - ), - }; + const attrs = { + ...img, + ...otherProps, + fill, + priority, + src: mediaApi.updateImageUrl( + img.src as string, + imageParams as { [paramName: string]: string | number }, + mediaUrlPrefix as RegExp + ), + }; - const imageProps = { - ...attrs, - // force replace /media with /jssmedia in src since we _know_ we will be adding a 'mw' query string parameter - // this is required for Sitecore media API resizing to work properly - src: mediaApi.replaceMediaUrlPrefix(attrs.src, mediaUrlPrefix as RegExp), - }; + const imageProps = { + ...attrs, + // force replace /media with /jssmedia in src since we _know_ we will be adding a 'mw' query string parameter + // this is required for Sitecore media API resizing to work properly + src: mediaApi.replaceMediaUrlPrefix(attrs.src, mediaUrlPrefix as RegExp), + }; - // Exclude `width`, `height` in case image is responsive, `fill` is used - if (imageProps.fill) { - delete imageProps.width; - delete imageProps.height; - } + // Exclude `width`, `height` in case image is responsive, `fill` is used + if (imageProps.fill) { + delete imageProps.width; + delete imageProps.height; + } - if (attrs) { - return ; - } + if (attrs) { + return ; + } - return null; // we can't handle the truth - } + return null; // we can't handle the truth + }, + DefaultEmptyFieldEditingComponentImage + ) ); NextImage.propTypes = { diff --git a/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx b/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx index f8879a7dc0..e09f1e2313 100644 --- a/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx @@ -231,7 +231,7 @@ describe('RichText', () => { const link1 = links && links[0]; const link2 = links && links[1]; - expect(link1!.href).to.endWith('/testpath/t1?test=sample1'); + expect(link1!.href).to.endsWith('/testpath/t1?test=sample1'); expect(link2!.pathname).to.equal('/t2'); link1 && link1.click(); @@ -380,13 +380,7 @@ describe('RichText', () => { expect(router.prefetch).callCount(0); }); - it('should render field metadata component when metadata property is present', () => { - const app = document.createElement('main'); - - document.body.appendChild(app); - - const router = Router(); - + describe('editMode metadata', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -399,39 +393,132 @@ describe('RichText', () => { rawValue: 'Test1', }; - const props = { - field: { - value: ` -
-

Hello!

- 1 - 2 - Title -
`, - metadata: testMetadata, - }, - }; - - const rendered = mount( - - - , - { attachTo: app } - ); - - expect(rendered.html()).to.equal( - [ - `${JSON.stringify( - testMetadata - )}
- `, - `
-

Hello!

- 1 - 2 - Title -
`, - ].join('') - ); + it('should render field metadata component when metadata property is present', () => { + const app = document.createElement('main'); + + document.body.appendChild(app); + + const router = Router(); + + const props = { + field: { + value: ` +
+

Hello!

+ 1 + 2 + Title +
`, + metadata: testMetadata, + }, + }; + + const rendered = mount( + + + , + { attachTo: app } + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}
+ `, + `
+

Hello!

+ 1 + 2 + Title +
`, + ].join('') + ); + }); + + it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + const app = document.createElement('main'); + document.body.appendChild(app); + const router = Router(); + + const props = { + field: { + value: '', + metadata: testMetadata, + }, + }; + + const rendered = mount( + + + , + { attachTo: app } + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + ``, + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + const app = document.createElement('main'); + document.body.appendChild(app); + const router = Router(); + + const props = { + field: { + value: '', + metadata: testMetadata, + }, + }; + + const EmptyValueEditingPlaceholder: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + + , + { attachTo: app } + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + ``, + ].join('') + ); + }); + + it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + const app = document.createElement('main'); + document.body.appendChild(app); + const router = Router(); + + const props = { + field: { + value: '', + metadata: testMetadata, + }, + }; + const rendered = mount( + + + , + { attachTo: app } + ); + + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-nextjs/src/components/RichText.tsx b/packages/sitecore-jss-nextjs/src/components/RichText.tsx index 9e8d88a804..fb88b2b23e 100644 --- a/packages/sitecore-jss-nextjs/src/components/RichText.tsx +++ b/packages/sitecore-jss-nextjs/src/components/RichText.tsx @@ -74,7 +74,7 @@ export const RichText = (props: RichTextProps): JSX.Element => { }); }; - return ; + return ; }; RichText.propTypes = { diff --git a/packages/sitecore-jss-react/src/index.ts b/packages/sitecore-jss-react/src/index.ts index f9ff82f176..56de7ec19f 100644 --- a/packages/sitecore-jss-react/src/index.ts +++ b/packages/sitecore-jss-react/src/index.ts @@ -103,4 +103,9 @@ export { withDatasourceCheck } from './enhancers/withDatasourceCheck'; export { EditFrameProps, EditFrame } from './components/EditFrame'; export { ComponentBuilder, ComponentBuilderConfig } from './ComponentBuilder'; export { withFieldMetadata } from './enhancers/withFieldMetadata'; +export { withEmptyValueEditingPlaceholder } from './enhancers/withEmptyValueEditingPlaceholder'; export { EditingScripts } from './components/EditingScripts'; +export { + DefaultEmptyFieldEditingComponentText, + DefaultEmptyFieldEditingComponentImage, +} from './components/DefaultEmptyFieldEditingComponents'; From be9b0be32cb443248765a369aa611cdaa07d8ce8 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 24 Jun 2024 16:13:22 +0300 Subject: [PATCH 05/25] add proptype for custom empty value placeholder component prop --- packages/sitecore-jss-react/src/components/Date.tsx | 1 + packages/sitecore-jss-react/src/components/Image.tsx | 1 + packages/sitecore-jss-react/src/components/Link.tsx | 1 + packages/sitecore-jss-react/src/components/RichText.tsx | 1 + packages/sitecore-jss-react/src/components/Text.tsx | 1 + 5 files changed, 5 insertions(+) diff --git a/packages/sitecore-jss-react/src/components/Date.tsx b/packages/sitecore-jss-react/src/components/Date.tsx index 361309062e..0620e51f46 100644 --- a/packages/sitecore-jss-react/src/components/Date.tsx +++ b/packages/sitecore-jss-react/src/components/Date.tsx @@ -76,6 +76,7 @@ DateField.propTypes = { tag: PropTypes.string, editable: PropTypes.bool, render: PropTypes.func, + emptyValueEditingPlaceholder: PropTypes.func, }; DateField.displayName = 'Date'; diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index 77e147a71d..92d1485b7e 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -209,6 +209,7 @@ Image.propTypes = { imageParams: PropTypes.objectOf( PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]).isRequired ), + emptyValueEditingPlaceholder: PropTypes.func, }; Image.displayName = 'Image'; diff --git a/packages/sitecore-jss-react/src/components/Link.tsx b/packages/sitecore-jss-react/src/components/Link.tsx index 190db7ee40..289ce616d2 100644 --- a/packages/sitecore-jss-react/src/components/Link.tsx +++ b/packages/sitecore-jss-react/src/components/Link.tsx @@ -155,6 +155,7 @@ export const LinkPropTypes = { ]).isRequired, editable: PropTypes.bool, showLinkTextWithChildrenPresent: PropTypes.bool, + emptyValueEditingPlaceholder: PropTypes.func, }; Link.propTypes = LinkPropTypes; diff --git a/packages/sitecore-jss-react/src/components/RichText.tsx b/packages/sitecore-jss-react/src/components/RichText.tsx index f655444be0..812183c870 100644 --- a/packages/sitecore-jss-react/src/components/RichText.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.tsx @@ -67,6 +67,7 @@ export const RichTextPropTypes = { }), tag: PropTypes.string, editable: PropTypes.bool, + emptyValueEditingPlaceholder: PropTypes.func, }; RichText.propTypes = RichTextPropTypes; diff --git a/packages/sitecore-jss-react/src/components/Text.tsx b/packages/sitecore-jss-react/src/components/Text.tsx index 318e62cd09..50f51031f4 100644 --- a/packages/sitecore-jss-react/src/components/Text.tsx +++ b/packages/sitecore-jss-react/src/components/Text.tsx @@ -114,6 +114,7 @@ Text.propTypes = { tag: PropTypes.string, editable: PropTypes.bool, encode: PropTypes.bool, + emptyValueEditingPlaceholder: PropTypes.func, }; Text.displayName = 'Text'; From 2fd41cc84359a1b471703e10810a51230b078d96 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 24 Jun 2024 17:33:10 +0300 Subject: [PATCH 06/25] added comments, fixed lint errors --- .../withEmptyValueEditingPlaceholder.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx index c86016bbfd..15dbdeb40a 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx @@ -13,6 +13,12 @@ interface WithEmptyValueEditingPlaceholderProps { emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; } +/** + * Returns the passed field component or placeholder content in case field value is empty and edit mode is 'metadata' + * @param {ComponentType} FieldComponent the field component + * @param {React.FC} defaultEmptyFieldEditingComponent the default empty field placeholder component + * @param {boolean} isForwardRef set to 'true' if forward reference is needed + */ export function withEmptyValueEditingPlaceholder< FieldComponentProps extends WithEmptyValueEditingPlaceholderProps, RefElementType = HTMLElement @@ -24,17 +30,19 @@ export function withEmptyValueEditingPlaceholder< const hasValue = (field: GeneralField | ImageFieldValue | LinkFieldValue) => field?.value || (field as ImageFieldValue)?.src || (field as LinkFieldValue)?.href; - function getEmptyFieldPhComponent(props: FieldComponentProps): React.ComponentClass | React.FC { + const getEmptyFieldPhComponent = ( + props: FieldComponentProps + ): React.ComponentClass | React.FC => { const { editable = true } = props; - // render empty field placeholder in editMode metadata if (props.field?.metadata && editable && !hasValue(props.field)) { return props.emptyValueEditingPlaceholder || defaultEmptyFieldEditingComponent; } return null; - } + }; if (isForwardRef) { + // eslint-disable-next-line react/display-name return forwardRef((props: FieldComponentProps, ref: React.ForwardedRef) => { const EmptyFieldPhComponent = getEmptyFieldPhComponent(props); return ( @@ -47,6 +55,7 @@ export function withEmptyValueEditingPlaceholder< }); } + // eslint-disable-next-line react/display-name return (props: FieldComponentProps) => { const EmptyFieldPhComponent = getEmptyFieldPhComponent(props); return ( From 801ebf6eb1c3820eadfb5bdace0f1a2d130b7de1 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Tue, 25 Jun 2024 09:52:22 +0300 Subject: [PATCH 07/25] fix lint errors --- packages/sitecore-jss-nextjs/src/components/Link.test.tsx | 4 ++-- packages/sitecore-jss-nextjs/src/components/RichText.test.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx index 178fac4e0e..0349f9d19e 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx @@ -420,7 +420,7 @@ describe('', () => { testMetadata )}`, '[No text in field]', - ``, + '', ].join('') ); }); @@ -447,7 +447,7 @@ describe('', () => { testMetadata )}`, 'Custom Empty field value', - ``, + '', ].join('') ); }); diff --git a/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx b/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx index e09f1e2313..9e9b542f8d 100644 --- a/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx @@ -461,7 +461,7 @@ describe('RichText', () => { testMetadata )}`, '[No text in field]', - ``, + '', ].join('') ); }); @@ -495,7 +495,7 @@ describe('RichText', () => { testMetadata )}`, 'Custom Empty field value', - ``, + '', ].join('') ); }); From 237d1bdfc704ff9d390947685468530eabbb127f Mon Sep 17 00:00:00 2001 From: yavorsk Date: Tue, 25 Jun 2024 11:39:30 +0300 Subject: [PATCH 08/25] reexport default empty field editing components and withEmptyValueEditingPlaceholder from sitecore-jss-nextjs package --- packages/sitecore-jss-nextjs/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index 629d573594..1e5971e9d1 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -149,6 +149,8 @@ export { File, FileField, RichTextField, + DefaultEmptyFieldEditingComponentImage, + DefaultEmptyFieldEditingComponentText, VisitorIdentification, PlaceholderComponentProps, SitecoreContext, @@ -165,5 +167,6 @@ export { WithSitecoreContextProps, WithSitecoreContextHocProps, withFieldMetadata, + withEmptyValueEditingPlaceholder, EditingScripts, } from '@sitecore-jss/sitecore-jss-react'; From c1164a72e26f4d7a038119d78cd892ff3922e84f Mon Sep 17 00:00:00 2001 From: yavorsk Date: Thu, 27 Jun 2024 10:03:38 +0300 Subject: [PATCH 09/25] renamings HOC, properties - withEmptyFieldEditingComponent --- .../src/components/Link.test.tsx | 4 +-- .../src/components/NextImage.test.tsx | 4 +-- .../src/components/NextImage.tsx | 4 +-- .../src/components/RichText.test.tsx | 4 +-- packages/sitecore-jss-nextjs/src/index.ts | 2 +- .../src/components/Date.test.tsx | 4 +-- .../src/components/Date.tsx | 8 +++--- .../src/components/Image.test.tsx | 4 +-- .../src/components/Image.tsx | 8 +++--- .../src/components/Link.test.tsx | 4 +-- .../src/components/Link.tsx | 8 +++--- .../src/components/RichText.test.tsx | 4 +-- .../src/components/RichText.tsx | 8 +++--- .../src/components/Text.test.tsx | 4 +-- .../src/components/Text.tsx | 17 ++++++++---- ...> withEmptyFieldEditingComponent.test.tsx} | 26 +++++++++---------- ...tsx => withEmptyFieldEditingComponent.tsx} | 10 +++---- packages/sitecore-jss-react/src/index.ts | 2 +- 18 files changed, 66 insertions(+), 59 deletions(-) rename packages/sitecore-jss-react/src/enhancers/{withEmptyValueEditingPlaceholder.test.tsx => withEmptyFieldEditingComponent.test.tsx} (83%) rename packages/sitecore-jss-react/src/enhancers/{withEmptyValueEditingPlaceholder.tsx => withEmptyFieldEditingComponent.tsx} (86%) diff --git a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx index 0349f9d19e..2ecd8e9517 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx @@ -431,13 +431,13 @@ describe('', () => { metadata: testMetadata, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + ); diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx index f59de0cd6b..445a6f30a7 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx @@ -347,12 +347,12 @@ describe('', () => { metadata: testMetadata, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + ); expect(rendered.html()).to.equal( diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx index 5123427a51..3cac1ba2eb 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx @@ -9,12 +9,12 @@ import { withFieldMetadata, } from '@sitecore-jss/sitecore-jss-react'; import Image, { ImageProps as NextImageProperties } from 'next/image'; -import { withEmptyValueEditingPlaceholder } from '@sitecore-jss/sitecore-jss-react'; +import { withEmptyFieldEditingComponent } from '@sitecore-jss/sitecore-jss-react'; import { DefaultEmptyFieldEditingComponentImage } from '@sitecore-jss/sitecore-jss-react'; type NextImageProps = ImageProps & Partial; export const NextImage: React.FC = withFieldMetadata( - withEmptyValueEditingPlaceholder( + withEmptyFieldEditingComponent( ({ editable = true, imageParams, field, mediaUrlPrefix, fill, priority, ...otherProps }) => { // next handles src and we use a custom loader, // throw error if these are present diff --git a/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx b/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx index 9e9b542f8d..df250b149b 100644 --- a/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/RichText.test.tsx @@ -478,13 +478,13 @@ describe('RichText', () => { }, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + , { attachTo: app } ); diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index 1e5971e9d1..98dde6fcf4 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -167,6 +167,6 @@ export { WithSitecoreContextProps, WithSitecoreContextHocProps, withFieldMetadata, - withEmptyValueEditingPlaceholder, + withEmptyFieldEditingComponent, EditingScripts, } from '@sitecore-jss/sitecore-jss-react'; diff --git a/packages/sitecore-jss-react/src/components/Date.test.tsx b/packages/sitecore-jss-react/src/components/Date.test.tsx index 4cb83e785a..69e5303303 100644 --- a/packages/sitecore-jss-react/src/components/Date.test.tsx +++ b/packages/sitecore-jss-react/src/components/Date.test.tsx @@ -141,12 +141,12 @@ describe('', () => { metadata: testMetadata, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + ); expect(rendered.html()).to.equal( diff --git a/packages/sitecore-jss-react/src/components/Date.tsx b/packages/sitecore-jss-react/src/components/Date.tsx index 0620e51f46..95e95d0883 100644 --- a/packages/sitecore-jss-react/src/components/Date.tsx +++ b/packages/sitecore-jss-react/src/components/Date.tsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; -import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; export interface DateFieldProps { @@ -28,11 +28,11 @@ export interface DateFieldProps { * * Custom element to render in Pages in Metadata edit mode if field value is empty */ - emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; + emptyFieldEditingComponent?: React.ComponentClass | React.FC; } export const DateField: React.FC = withFieldMetadata( - withEmptyValueEditingPlaceholder( + withEmptyFieldEditingComponent( ({ field, tag, editable = true, render, ...otherProps }) => { if (!field || (!field.editable && !field.value)) { return null; @@ -76,7 +76,7 @@ DateField.propTypes = { tag: PropTypes.string, editable: PropTypes.bool, render: PropTypes.func, - emptyValueEditingPlaceholder: PropTypes.func, + emptyFieldEditingComponent: PropTypes.func, }; DateField.displayName = 'Date'; diff --git a/packages/sitecore-jss-react/src/components/Image.test.tsx b/packages/sitecore-jss-react/src/components/Image.test.tsx index ae980f7b9c..231588bee4 100644 --- a/packages/sitecore-jss-react/src/components/Image.test.tsx +++ b/packages/sitecore-jss-react/src/components/Image.test.tsx @@ -353,12 +353,12 @@ describe('', () => { metadata: testMetadata, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + ); expect(rendered.html()).to.equal( diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index 92d1485b7e..47ccdd526f 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { addClassName, convertAttributesToReactProps } from '../utils'; import { getAttributesString } from '../utils'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; -import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentImage } from './DefaultEmptyFieldEditingComponents'; export interface ImageFieldValue { @@ -73,7 +73,7 @@ export interface ImageProps { * * Custom element to render in Pages in Metadata edit mode if field value is empty */ - emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; + emptyFieldEditingComponent?: React.ComponentClass | React.FC; } const getEditableWrapper = (editableMarkup: string, ...otherProps: unknown[]) => ( @@ -153,7 +153,7 @@ export const getEEMarkup = ( }; export const Image: React.FC = withFieldMetadata( - withEmptyValueEditingPlaceholder( + withEmptyFieldEditingComponent( ({ editable = true, imageParams, field, mediaUrlPrefix, ...otherProps }) => { const dynamicMedia = field as ImageField | ImageFieldValue; @@ -209,7 +209,7 @@ Image.propTypes = { imageParams: PropTypes.objectOf( PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]).isRequired ), - emptyValueEditingPlaceholder: PropTypes.func, + emptyFieldEditingComponent: PropTypes.func, }; Image.displayName = 'Image'; diff --git a/packages/sitecore-jss-react/src/components/Link.test.tsx b/packages/sitecore-jss-react/src/components/Link.test.tsx index 4a515bf41d..f3b6e54277 100644 --- a/packages/sitecore-jss-react/src/components/Link.test.tsx +++ b/packages/sitecore-jss-react/src/components/Link.test.tsx @@ -182,12 +182,12 @@ describe('', () => { metadata: testMetadata, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + ); expect(rendered.html()).to.equal( diff --git a/packages/sitecore-jss-react/src/components/Link.tsx b/packages/sitecore-jss-react/src/components/Link.tsx index 289ce616d2..bd3ac92752 100644 --- a/packages/sitecore-jss-react/src/components/Link.tsx +++ b/packages/sitecore-jss-react/src/components/Link.tsx @@ -1,7 +1,7 @@ import React, { ReactElement, RefAttributes, forwardRef } from 'react'; import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; -import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; export interface LinkFieldValue { @@ -44,11 +44,11 @@ export type LinkProps = React.AnchorHTMLAttributes & * * Custom element to render in Pages in Metadata edit mode if field value is empty */ - emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; + emptyFieldEditingComponent?: React.ComponentClass | React.FC; }; export const Link: React.FC = withFieldMetadata( - withEmptyValueEditingPlaceholder( + withEmptyFieldEditingComponent( // eslint-disable-next-line react/display-name forwardRef( ({ field, editable = true, showLinkTextWithChildrenPresent, ...otherProps }, ref) => { @@ -155,7 +155,7 @@ export const LinkPropTypes = { ]).isRequired, editable: PropTypes.bool, showLinkTextWithChildrenPresent: PropTypes.bool, - emptyValueEditingPlaceholder: PropTypes.func, + emptyFieldEditingComponent: PropTypes.func, }; Link.propTypes = LinkPropTypes; diff --git a/packages/sitecore-jss-react/src/components/RichText.test.tsx b/packages/sitecore-jss-react/src/components/RichText.test.tsx index e594f34618..de06f486ae 100644 --- a/packages/sitecore-jss-react/src/components/RichText.test.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.test.tsx @@ -153,12 +153,12 @@ describe('', () => { metadata: testMetadata, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + ); expect(rendered.html()).to.equal( diff --git a/packages/sitecore-jss-react/src/components/RichText.tsx b/packages/sitecore-jss-react/src/components/RichText.tsx index 812183c870..ad048944a6 100644 --- a/packages/sitecore-jss-react/src/components/RichText.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.tsx @@ -1,7 +1,7 @@ import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; -import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; export interface RichTextField { @@ -30,11 +30,11 @@ export interface RichTextProps { * * Custom element to render in Pages in Metadata edit mode if field value is empty */ - emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; + emptyFieldEditingComponent?: React.ComponentClass | React.FC; } export const RichText: React.FC = withFieldMetadata( - withEmptyValueEditingPlaceholder( + withEmptyFieldEditingComponent( // eslint-disable-next-line react/display-name forwardRef( ({ field, tag = 'div', editable = true, ...otherProps }, ref) => { @@ -67,7 +67,7 @@ export const RichTextPropTypes = { }), tag: PropTypes.string, editable: PropTypes.bool, - emptyValueEditingPlaceholder: PropTypes.func, + emptyFieldEditingComponent: PropTypes.func, }; RichText.propTypes = RichTextPropTypes; diff --git a/packages/sitecore-jss-react/src/components/Text.test.tsx b/packages/sitecore-jss-react/src/components/Text.test.tsx index f9fcb5843f..dfe0c5767c 100644 --- a/packages/sitecore-jss-react/src/components/Text.test.tsx +++ b/packages/sitecore-jss-react/src/components/Text.test.tsx @@ -236,12 +236,12 @@ describe('', () => { metadata: testMetadata, }; - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); const rendered = mount( - + ); expect(rendered.html()).to.equal( diff --git a/packages/sitecore-jss-react/src/components/Text.tsx b/packages/sitecore-jss-react/src/components/Text.tsx index 50f51031f4..307097f99f 100644 --- a/packages/sitecore-jss-react/src/components/Text.tsx +++ b/packages/sitecore-jss-react/src/components/Text.tsx @@ -1,6 +1,6 @@ import React, { ReactElement } from 'react'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; -import { withEmptyValueEditingPlaceholder } from '../enhancers/withEmptyValueEditingPlaceholder'; +import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import PropTypes from 'prop-types'; export interface TextField { @@ -32,12 +32,19 @@ export interface TextProps { * * Custom element to render in Pages in Metadata edit mode if field value is empty */ - emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; + emptyFieldEditingComponent: React.ComponentClass | React.FC; } export const Text: React.FC = withFieldMetadata( - withEmptyValueEditingPlaceholder( - ({ field, tag, editable = true, encode = true, ...otherProps }) => { + withEmptyFieldEditingComponent( + ({ + field, + tag, + editable = true, + encode = true, + emptyFieldEditingComponent = DefaultEmptyFieldEditingComponentText, + ...otherProps + }) => { if (!field || (!field.editable && (field.value === undefined || field.value === ''))) { return null; } @@ -114,7 +121,7 @@ Text.propTypes = { tag: PropTypes.string, editable: PropTypes.bool, encode: PropTypes.bool, - emptyValueEditingPlaceholder: PropTypes.func, + emptyFieldEditingComponent: PropTypes.func, }; Text.displayName = 'Text'; diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.test.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx similarity index 83% rename from packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.test.tsx rename to packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx index d2331b0c7b..5844484973 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.test.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx @@ -2,11 +2,11 @@ import React, { forwardRef } from 'react'; import { expect } from 'chai'; import { mount } from 'enzyme'; -import { withEmptyValueEditingPlaceholder } from './withEmptyValueEditingPlaceholder'; +import { withEmptyFieldEditingComponent } from './withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from '../components/DefaultEmptyFieldEditingComponents'; import { describe } from 'node:test'; -describe('withEmptyValueEditingPlaceholder', () => { +describe('withEmptyFieldEditingComponent', () => { const testMetadata = { contextItem: { id: '{09A07660-6834-476C-B93B-584248D3003B}', @@ -59,7 +59,7 @@ describe('withEmptyValueEditingPlaceholder', () => { }, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponent, DefaultEmptyFieldEditingComponentText ); @@ -71,7 +71,7 @@ describe('withEmptyValueEditingPlaceholder', () => { }); it('Should render custom empty value placeholder if provided via props if field value is empty in metadata edit mode', () => { - const EmptyValueEditingPlaceholder: React.FC = () => ( + const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); @@ -80,16 +80,16 @@ describe('withEmptyValueEditingPlaceholder', () => { value: '', metadata: testMetadata, }, - emptyValueEditingPlaceholder: EmptyValueEditingPlaceholder, + emptyFieldEditingComponent: EmptyFieldEditingComponent, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponent, DefaultEmptyFieldEditingComponentText ); const rendered = mount(); - const expected = mount(); + const expected = mount(); expect(rendered.html()).to.equal(expected.html()); }); @@ -102,7 +102,7 @@ describe('withEmptyValueEditingPlaceholder', () => { }, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponent, DefaultEmptyFieldEditingComponentText ); @@ -120,7 +120,7 @@ describe('withEmptyValueEditingPlaceholder', () => { editable: false, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponent, DefaultEmptyFieldEditingComponentText ); @@ -136,7 +136,7 @@ describe('withEmptyValueEditingPlaceholder', () => { }, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponent, DefaultEmptyFieldEditingComponentText ); @@ -153,7 +153,7 @@ describe('withEmptyValueEditingPlaceholder', () => { }, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponentWithRef, DefaultEmptyFieldEditingComponentText, true @@ -174,7 +174,7 @@ describe('withEmptyValueEditingPlaceholder', () => { }, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponent, DefaultEmptyFieldEditingComponentText ); @@ -192,7 +192,7 @@ describe('withEmptyValueEditingPlaceholder', () => { }, }; - const WrappedComponent = withEmptyValueEditingPlaceholder( + const WrappedComponent = withEmptyFieldEditingComponent( TestComponent, DefaultEmptyFieldEditingComponentText ); diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx similarity index 86% rename from packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx rename to packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx index 15dbdeb40a..1569689835 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyValueEditingPlaceholder.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx @@ -7,10 +7,10 @@ interface GeneralField { value?: unknown; } -interface WithEmptyValueEditingPlaceholderProps { +interface WithEmptyFieldEditingComponentProps { field?: GeneralField; editable?: boolean; - emptyValueEditingPlaceholder?: React.ComponentClass | React.FC; + emptyFieldEditingComponent?: React.ComponentClass | React.FC; } /** @@ -19,8 +19,8 @@ interface WithEmptyValueEditingPlaceholderProps { * @param {React.FC} defaultEmptyFieldEditingComponent the default empty field placeholder component * @param {boolean} isForwardRef set to 'true' if forward reference is needed */ -export function withEmptyValueEditingPlaceholder< - FieldComponentProps extends WithEmptyValueEditingPlaceholderProps, +export function withEmptyFieldEditingComponent< + FieldComponentProps extends WithEmptyFieldEditingComponentProps, RefElementType = HTMLElement >( FieldComponent: ComponentType, @@ -35,7 +35,7 @@ export function withEmptyValueEditingPlaceholder< ): React.ComponentClass | React.FC => { const { editable = true } = props; if (props.field?.metadata && editable && !hasValue(props.field)) { - return props.emptyValueEditingPlaceholder || defaultEmptyFieldEditingComponent; + return props.emptyFieldEditingComponent || defaultEmptyFieldEditingComponent; } return null; diff --git a/packages/sitecore-jss-react/src/index.ts b/packages/sitecore-jss-react/src/index.ts index 56de7ec19f..d11283ee90 100644 --- a/packages/sitecore-jss-react/src/index.ts +++ b/packages/sitecore-jss-react/src/index.ts @@ -103,7 +103,7 @@ export { withDatasourceCheck } from './enhancers/withDatasourceCheck'; export { EditFrameProps, EditFrame } from './components/EditFrame'; export { ComponentBuilder, ComponentBuilderConfig } from './ComponentBuilder'; export { withFieldMetadata } from './enhancers/withFieldMetadata'; -export { withEmptyValueEditingPlaceholder } from './enhancers/withEmptyValueEditingPlaceholder'; +export { withEmptyFieldEditingComponent } from './enhancers/withEmptyFieldEditingComponent'; export { EditingScripts } from './components/EditingScripts'; export { DefaultEmptyFieldEditingComponentText, From 79a0b4e294dbf52ca6869bd4d090253499ffc601 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Thu, 27 Jun 2024 12:55:53 +0300 Subject: [PATCH 10/25] update hasValue logic --- .../src/enhancers/withEmptyFieldEditingComponent.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx index 1569689835..323e56fea0 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx @@ -28,7 +28,11 @@ export function withEmptyFieldEditingComponent< isForwardRef = false ) { const hasValue = (field: GeneralField | ImageFieldValue | LinkFieldValue) => - field?.value || (field as ImageFieldValue)?.src || (field as LinkFieldValue)?.href; + (field?.value as ImageFieldValue)?.src || + (field?.value as LinkFieldValue)?.href || + field?.value || + (field as ImageFieldValue)?.src || + (field as LinkFieldValue)?.href; const getEmptyFieldPhComponent = ( props: FieldComponentProps From 085e09b96b4678880e2e287e468d3bad10c1be29 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 1 Jul 2024 12:05:59 +0300 Subject: [PATCH 11/25] react field components - extract common properties into EditableFieldProps interface --- .../sitecore-jss-react/src/components/Date.tsx | 16 +++------------- .../sitecore-jss-react/src/components/Image.tsx | 17 ++--------------- .../sitecore-jss-react/src/components/Link.tsx | 16 +++------------- .../src/components/RichText.tsx | 15 ++------------- .../sitecore-jss-react/src/components/Text.tsx | 16 +++------------- .../src/components/sharedTypes.ts | 15 +++++++++++++++ 6 files changed, 28 insertions(+), 67 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/Date.tsx b/packages/sitecore-jss-react/src/components/Date.tsx index 95e95d0883..6343aeaf3f 100644 --- a/packages/sitecore-jss-react/src/components/Date.tsx +++ b/packages/sitecore-jss-react/src/components/Date.tsx @@ -3,8 +3,9 @@ import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; +import { EditableFieldProps } from './sharedTypes'; -export interface DateFieldProps { +export interface DateFieldProps extends EditableFieldProps { /** The date field data. */ [htmlAttributes: string]: unknown; field: { @@ -16,19 +17,8 @@ export interface DateFieldProps { * The HTML element that will wrap the contents of the field. */ tag?: string; - /** - * Can be used to explicitly disable inline editing. - * If true and `field.editable` has a value, then `field.editable` will be processed and rendered as component output. If false, `field.editable` value will be ignored and not rendered. - * @default true - */ - editable?: boolean; + render?: (date: Date | null) => React.ReactNode; - /** - * -- Edit Mode Metadata -- - * - * Custom element to render in Pages in Metadata edit mode if field value is empty - */ - emptyFieldEditingComponent?: React.ComponentClass | React.FC; } export const DateField: React.FC = withFieldMetadata( diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index 47ccdd526f..c48b61e44f 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -6,6 +6,7 @@ import { getAttributesString } from '../utils'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentImage } from './DefaultEmptyFieldEditingComponents'; +import { EditableFieldProps } from './sharedTypes'; export interface ImageFieldValue { [attributeName: string]: unknown; @@ -36,18 +37,11 @@ export interface ImageSizeParameters { sc?: number; } -export interface ImageProps { +export interface ImageProps extends EditableFieldProps { [attributeName: string]: unknown; /** Image field data (consistent with other field types) */ field?: (ImageField | ImageFieldValue) & { metadata?: { [key: string]: unknown } }; - /** - * Can be used to explicitly disable inline editing. - * If true and `media.editable` has a value, then `media.editable` will be processed - * and rendered as component output. If false, `media.editable` value will be ignored and not rendered. - */ - editable?: boolean; - /** * Parameters that will be attached to Sitecore media URLs */ @@ -67,13 +61,6 @@ export interface ImageProps { mediaUrlPrefix?: RegExp; /** HTML attributes that will be appended to the rendered tag. */ - - /** - * -- Edit Mode Metadata -- - * - * Custom element to render in Pages in Metadata edit mode if field value is empty - */ - emptyFieldEditingComponent?: React.ComponentClass | React.FC; } const getEditableWrapper = (editableMarkup: string, ...otherProps: unknown[]) => ( diff --git a/packages/sitecore-jss-react/src/components/Link.tsx b/packages/sitecore-jss-react/src/components/Link.tsx index bd3ac92752..834623c0e1 100644 --- a/packages/sitecore-jss-react/src/components/Link.tsx +++ b/packages/sitecore-jss-react/src/components/Link.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; +import { EditableFieldProps } from './sharedTypes'; export interface LinkFieldValue { [attributeName: string]: unknown; @@ -23,28 +24,17 @@ export interface LinkField { editableLastPart?: string; } -export type LinkProps = React.AnchorHTMLAttributes & +export type LinkProps = EditableFieldProps & + React.AnchorHTMLAttributes & RefAttributes & { /** The link field data. */ field: (LinkField | LinkFieldValue) & { metadata?: { [key: string]: unknown } }; - /** - * Can be used to explicitly disable inline editing. - * If true and `field.editable` has a value, then `field.editable` will be processed and rendered as component output. If false, `field.editable` value will be ignored and not rendered. - * @default true - */ - editable?: boolean; /** * Displays a link text ('description' in Sitecore) even when children exist * NOTE: when in Sitecore Experience Editor, this setting is ignored due to technical limitations, and the description is always rendered. */ showLinkTextWithChildrenPresent?: boolean; - /** - * -- Edit Mode Metadata -- - * - * Custom element to render in Pages in Metadata edit mode if field value is empty - */ - emptyFieldEditingComponent?: React.ComponentClass | React.FC; }; export const Link: React.FC = withFieldMetadata( diff --git a/packages/sitecore-jss-react/src/components/RichText.tsx b/packages/sitecore-jss-react/src/components/RichText.tsx index ad048944a6..d8f686ad4e 100644 --- a/packages/sitecore-jss-react/src/components/RichText.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; +import { EditableFieldProps } from './sharedTypes'; export interface RichTextField { value?: string; @@ -10,7 +11,7 @@ export interface RichTextField { metadata?: { [key: string]: unknown }; } -export interface RichTextProps { +export interface RichTextProps extends EditableFieldProps { [htmlAttributes: string]: unknown; /** The rich text field data. */ field?: RichTextField; @@ -19,18 +20,6 @@ export interface RichTextProps { * @default
*/ tag?: string; - /** - * Can be used to explicitly disable inline editing. - * If true and `field.editable` has a value, then `field.editable` will be processed and rendered as component output. If false, `field.editable` value will be ignored and not rendered. - * @default true - */ - editable?: boolean; - /** - * -- Edit Mode Metadata -- - * - * Custom element to render in Pages in Metadata edit mode if field value is empty - */ - emptyFieldEditingComponent?: React.ComponentClass | React.FC; } export const RichText: React.FC = withFieldMetadata( diff --git a/packages/sitecore-jss-react/src/components/Text.tsx b/packages/sitecore-jss-react/src/components/Text.tsx index 307097f99f..c6e50056d0 100644 --- a/packages/sitecore-jss-react/src/components/Text.tsx +++ b/packages/sitecore-jss-react/src/components/Text.tsx @@ -3,13 +3,15 @@ import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import PropTypes from 'prop-types'; +import { EditableFieldProps } from './sharedTypes'; + export interface TextField { value?: string | number; editable?: string; metadata?: { [key: string]: unknown }; } -export interface TextProps { +export interface TextProps extends EditableFieldProps { [htmlAttributes: string]: unknown; /** The text field data. */ field?: TextField; @@ -17,22 +19,10 @@ export interface TextProps { * The HTML element that will wrap the contents of the field. */ tag?: string; - /** - * Can be used to explicitly disable inline editing. - * If true and `field.editable` has a value, then `field.editable` will be processed and rendered as component output. If false, `field.editable` value will be ignored and not rendered. - * @default true - */ - editable?: boolean; /** * If false, HTML-encoding of the field value is disabled and the value is rendered as-is. */ encode?: boolean; - /** - * -- Edit Mode Metadata -- - * - * Custom element to render in Pages in Metadata edit mode if field value is empty - */ - emptyFieldEditingComponent: React.ComponentClass | React.FC; } export const Text: React.FC = withFieldMetadata( diff --git a/packages/sitecore-jss-react/src/components/sharedTypes.ts b/packages/sitecore-jss-react/src/components/sharedTypes.ts index 0ca7d22af6..f925196b1e 100644 --- a/packages/sitecore-jss-react/src/components/sharedTypes.ts +++ b/packages/sitecore-jss-react/src/components/sharedTypes.ts @@ -17,3 +17,18 @@ export type JssComponentType = ComponentType & { // react elements will not have it - so it's optional here render?: { [key: string]: unknown }; }; + +export interface EditableFieldProps { + /** + * Can be used to explicitly disable inline editing. + * If true and `field.editable` has a value, then `field.editable` will be processed and rendered as component output. If false, `field.editable` value will be ignored and not rendered. + * @default true + */ + editable?: boolean; + /** + * -- Edit Mode Metadata -- + * + * Custom element to render in Pages in Metadata edit mode if field value is empty + */ + emptyFieldEditingComponent?: React.ComponentClass | React.FC; +} From aca9f147ffb82313379d3e84e2eebbff758a72f0 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Thu, 4 Jul 2024 15:39:19 +0300 Subject: [PATCH 12/25] introduce a function fieldValueIsEmpty in base packages and use it in withEmptyFieldEditingComponent --- .../src/components/Link.test.tsx | 4 +-- .../withEmptyFieldEditingComponent.test.tsx | 2 -- .../withEmptyFieldEditingComponent.tsx | 16 ++++----- packages/sitecore-jss/src/layout/index.ts | 3 +- packages/sitecore-jss/src/layout/utils.ts | 35 ++++++++++++++++++- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/Link.test.tsx b/packages/sitecore-jss-react/src/components/Link.test.tsx index f3b6e54277..a0f6c6a0cc 100644 --- a/packages/sitecore-jss-react/src/components/Link.test.tsx +++ b/packages/sitecore-jss-react/src/components/Link.test.tsx @@ -160,7 +160,7 @@ describe('', () => { it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { const field = { - value: undefined, + value: '', metadata: testMetadata, }; const rendered = mount(); @@ -178,7 +178,7 @@ describe('', () => { it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { const field = { - value: undefined, + value: '', metadata: testMetadata, }; diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx index 5844484973..46766bfd7b 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx @@ -168,7 +168,6 @@ describe('withEmptyFieldEditingComponent', () => { it('Should render component if field src value is provided (case for Image component)', () => { const props = { field: { - value: '', metadata: testMetadata, src: 'img src', }, @@ -186,7 +185,6 @@ describe('withEmptyFieldEditingComponent', () => { it('Should render component if field href value is provided (case for Link component)', () => { const props = { field: { - value: '', metadata: testMetadata, href: 'img src', }, diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx index 323e56fea0..91517112de 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx @@ -1,6 +1,5 @@ import React, { ComponentType, forwardRef } from 'react'; -import { LinkFieldValue } from '../components/Link'; -import { ImageFieldValue } from '../components/Image'; +import { GenericFieldValue, Field, fieldValueIsEmpty } from '@sitecore-jss/sitecore-jss/layout'; interface GeneralField { metadata?: { [key: string]: unknown }; @@ -27,18 +26,15 @@ export function withEmptyFieldEditingComponent< defaultEmptyFieldEditingComponent: React.FC, isForwardRef = false ) { - const hasValue = (field: GeneralField | ImageFieldValue | LinkFieldValue) => - (field?.value as ImageFieldValue)?.src || - (field?.value as LinkFieldValue)?.href || - field?.value || - (field as ImageFieldValue)?.src || - (field as LinkFieldValue)?.href; - const getEmptyFieldPhComponent = ( props: FieldComponentProps ): React.ComponentClass | React.FC => { const { editable = true } = props; - if (props.field?.metadata && editable && !hasValue(props.field)) { + if ( + props.field?.metadata && + editable && + fieldValueIsEmpty(props.field as GenericFieldValue | Field) + ) { return props.emptyFieldEditingComponent || defaultEmptyFieldEditingComponent; } diff --git a/packages/sitecore-jss/src/layout/index.ts b/packages/sitecore-jss/src/layout/index.ts index bea0c1b392..22d06e95a3 100644 --- a/packages/sitecore-jss/src/layout/index.ts +++ b/packages/sitecore-jss/src/layout/index.ts @@ -9,6 +9,7 @@ export { ComponentRendering, HtmlElementRendering, Field, + GenericFieldValue, Item, PlaceholdersData, ComponentFields, @@ -16,7 +17,7 @@ export { EditMode, } from './models'; -export { getFieldValue, getChildPlaceholder } from './utils'; +export { getFieldValue, getChildPlaceholder, fieldValueIsEmpty } from './utils'; export { getContentStylesheetLink } from './content-styles'; diff --git a/packages/sitecore-jss/src/layout/utils.ts b/packages/sitecore-jss/src/layout/utils.ts index e13d918e00..c2097b15dd 100644 --- a/packages/sitecore-jss/src/layout/utils.ts +++ b/packages/sitecore-jss/src/layout/utils.ts @@ -1,4 +1,10 @@ -import { ComponentRendering, ComponentFields, Field, HtmlElementRendering } from './models'; +import { + ComponentRendering, + ComponentFields, + Field, + GenericFieldValue, + HtmlElementRendering, +} from './models'; /** * Safely extracts a field value from a rendering or fields object. @@ -72,3 +78,30 @@ export function getChildPlaceholder( return rendering.placeholders[placeholderName]; } + +/** + * Determines if the passed in field object's value is empty. + * @param {GenericFieldValue | Field} the field object + */ +export function fieldValueIsEmpty(field: GenericFieldValue | Field): boolean { + const isEmpty = (fieldValue: GenericFieldValue) => { + if ( + (fieldValue as { [key: string]: unknown })['src'] || + (fieldValue as { [key: string]: unknown })['href'] || + fieldValue + ) { + return false; + } + + return true; + }; + + if (!field) return true; + + const dynamicField = field as Field; + if (dynamicField.value !== undefined) { + return isEmpty(dynamicField.value); + } + + return isEmpty(field as GenericFieldValue); +} From 881362cb3f686a808f77a301708fedec49ce831a Mon Sep 17 00:00:00 2001 From: yavorsk Date: Thu, 4 Jul 2024 15:49:47 +0300 Subject: [PATCH 13/25] fix lint error --- packages/sitecore-jss/src/layout/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sitecore-jss/src/layout/utils.ts b/packages/sitecore-jss/src/layout/utils.ts index c2097b15dd..9170d32e82 100644 --- a/packages/sitecore-jss/src/layout/utils.ts +++ b/packages/sitecore-jss/src/layout/utils.ts @@ -81,7 +81,7 @@ export function getChildPlaceholder( /** * Determines if the passed in field object's value is empty. - * @param {GenericFieldValue | Field} the field object + * @param {GenericFieldValue | Field} field the field object */ export function fieldValueIsEmpty(field: GenericFieldValue | Field): boolean { const isEmpty = (fieldValue: GenericFieldValue) => { From 54f02827f6db1080e4a32aaeda57ac95c5446ff0 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Thu, 4 Jul 2024 15:56:59 +0300 Subject: [PATCH 14/25] fix lint errors --- packages/sitecore-jss/src/layout/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sitecore-jss/src/layout/utils.ts b/packages/sitecore-jss/src/layout/utils.ts index 9170d32e82..2e874fb2d2 100644 --- a/packages/sitecore-jss/src/layout/utils.ts +++ b/packages/sitecore-jss/src/layout/utils.ts @@ -86,8 +86,8 @@ export function getChildPlaceholder( export function fieldValueIsEmpty(field: GenericFieldValue | Field): boolean { const isEmpty = (fieldValue: GenericFieldValue) => { if ( - (fieldValue as { [key: string]: unknown })['src'] || - (fieldValue as { [key: string]: unknown })['href'] || + (fieldValue as { [key: string]: unknown }).src || + (fieldValue as { [key: string]: unknown }).href || fieldValue ) { return false; From abbb0d7b7433a0dd4d6fa7f7b5e7459a6acaebfc Mon Sep 17 00:00:00 2001 From: yavorsk Date: Fri, 5 Jul 2024 12:05:14 +0300 Subject: [PATCH 15/25] introduced FieldMetadata interface in core package; use it accross field components; some renamings and additional code comments --- .../src/components/Date.tsx | 4 +- .../src/components/Image.tsx | 3 +- .../src/components/Link.tsx | 3 +- .../src/components/RichText.tsx | 4 +- .../src/components/Text.tsx | 13 ++----- .../src/components/sharedTypes.ts | 3 ++ .../withEmptyFieldEditingComponent.tsx | 37 ++++++++++--------- packages/sitecore-jss/src/layout/index.ts | 3 +- packages/sitecore-jss/src/layout/models.ts | 9 ++++- packages/sitecore-jss/src/layout/utils.ts | 6 +-- 10 files changed, 46 insertions(+), 39 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/Date.tsx b/packages/sitecore-jss-react/src/components/Date.tsx index 6343aeaf3f..a53c7af649 100644 --- a/packages/sitecore-jss-react/src/components/Date.tsx +++ b/packages/sitecore-jss-react/src/components/Date.tsx @@ -4,14 +4,14 @@ import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; +import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; export interface DateFieldProps extends EditableFieldProps { /** The date field data. */ [htmlAttributes: string]: unknown; - field: { + field: FieldMetadata & { value?: string; editable?: string; - metadata?: { [key: string]: unknown }; }; /** * The HTML element that will wrap the contents of the field. diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index c48b61e44f..b739faf720 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -7,6 +7,7 @@ import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentImage } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; +import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; export interface ImageFieldValue { [attributeName: string]: unknown; @@ -40,7 +41,7 @@ export interface ImageSizeParameters { export interface ImageProps extends EditableFieldProps { [attributeName: string]: unknown; /** Image field data (consistent with other field types) */ - field?: (ImageField | ImageFieldValue) & { metadata?: { [key: string]: unknown } }; + field?: (ImageField | ImageFieldValue) & FieldMetadata; /** * Parameters that will be attached to Sitecore media URLs diff --git a/packages/sitecore-jss-react/src/components/Link.tsx b/packages/sitecore-jss-react/src/components/Link.tsx index 834623c0e1..0ebe6e2218 100644 --- a/packages/sitecore-jss-react/src/components/Link.tsx +++ b/packages/sitecore-jss-react/src/components/Link.tsx @@ -4,6 +4,7 @@ import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; +import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; export interface LinkFieldValue { [attributeName: string]: unknown; @@ -28,7 +29,7 @@ export type LinkProps = EditableFieldProps & React.AnchorHTMLAttributes & RefAttributes & { /** The link field data. */ - field: (LinkField | LinkFieldValue) & { metadata?: { [key: string]: unknown } }; + field: (LinkField | LinkFieldValue) & FieldMetadata; /** * Displays a link text ('description' in Sitecore) even when children exist diff --git a/packages/sitecore-jss-react/src/components/RichText.tsx b/packages/sitecore-jss-react/src/components/RichText.tsx index d8f686ad4e..9df0c6f131 100644 --- a/packages/sitecore-jss-react/src/components/RichText.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.tsx @@ -4,11 +4,11 @@ import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; +import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; -export interface RichTextField { +export interface RichTextField extends FieldMetadata { value?: string; editable?: string; - metadata?: { [key: string]: unknown }; } export interface RichTextProps extends EditableFieldProps { diff --git a/packages/sitecore-jss-react/src/components/Text.tsx b/packages/sitecore-jss-react/src/components/Text.tsx index c6e50056d0..23329c9b6d 100644 --- a/packages/sitecore-jss-react/src/components/Text.tsx +++ b/packages/sitecore-jss-react/src/components/Text.tsx @@ -4,11 +4,11 @@ import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditi import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import PropTypes from 'prop-types'; import { EditableFieldProps } from './sharedTypes'; +import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; -export interface TextField { +export interface TextField extends FieldMetadata { value?: string | number; editable?: string; - metadata?: { [key: string]: unknown }; } export interface TextProps extends EditableFieldProps { @@ -27,14 +27,7 @@ export interface TextProps extends EditableFieldProps { export const Text: React.FC = withFieldMetadata( withEmptyFieldEditingComponent( - ({ - field, - tag, - editable = true, - encode = true, - emptyFieldEditingComponent = DefaultEmptyFieldEditingComponentText, - ...otherProps - }) => { + ({ field, tag, editable = true, encode = true, ...otherProps }) => { if (!field || (!field.editable && (field.value === undefined || field.value === ''))) { return null; } diff --git a/packages/sitecore-jss-react/src/components/sharedTypes.ts b/packages/sitecore-jss-react/src/components/sharedTypes.ts index f925196b1e..d0ffb9a362 100644 --- a/packages/sitecore-jss-react/src/components/sharedTypes.ts +++ b/packages/sitecore-jss-react/src/components/sharedTypes.ts @@ -18,6 +18,9 @@ export type JssComponentType = ComponentType & { render?: { [key: string]: unknown }; }; +/** + * Shared editing field props + */ export interface EditableFieldProps { /** * Can be used to explicitly disable inline editing. diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx index 91517112de..43b049873e 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx @@ -1,19 +1,20 @@ import React, { ComponentType, forwardRef } from 'react'; -import { GenericFieldValue, Field, fieldValueIsEmpty } from '@sitecore-jss/sitecore-jss/layout'; - -interface GeneralField { - metadata?: { [key: string]: unknown }; - value?: unknown; -} +import { + GenericFieldValue, + Field, + isFieldValueEmpty, + FieldMetadata, +} from '@sitecore-jss/sitecore-jss/layout'; interface WithEmptyFieldEditingComponentProps { - field?: GeneralField; + // Parial is used here because field.value could be required or optional for the different field types + field?: (Partial | GenericFieldValue) & FieldMetadata; editable?: boolean; emptyFieldEditingComponent?: React.ComponentClass | React.FC; } /** - * Returns the passed field component or placeholder content in case field value is empty and edit mode is 'metadata' + * Returns the passed field component or default component in case field value is empty and edit mode is 'metadata' * @param {ComponentType} FieldComponent the field component * @param {React.FC} defaultEmptyFieldEditingComponent the default empty field placeholder component * @param {boolean} isForwardRef set to 'true' if forward reference is needed @@ -26,15 +27,11 @@ export function withEmptyFieldEditingComponent< defaultEmptyFieldEditingComponent: React.FC, isForwardRef = false ) { - const getEmptyFieldPhComponent = ( + const getEmptyFieldEditingComponent = ( props: FieldComponentProps ): React.ComponentClass | React.FC => { const { editable = true } = props; - if ( - props.field?.metadata && - editable && - fieldValueIsEmpty(props.field as GenericFieldValue | Field) - ) { + if (props.field?.metadata && editable && isFieldValueEmpty(props.field)) { return props.emptyFieldEditingComponent || defaultEmptyFieldEditingComponent; } @@ -44,10 +41,10 @@ export function withEmptyFieldEditingComponent< if (isForwardRef) { // eslint-disable-next-line react/display-name return forwardRef((props: FieldComponentProps, ref: React.ForwardedRef) => { - const EmptyFieldPhComponent = getEmptyFieldPhComponent(props); + const EmptyFieldEditingComponent = getEmptyFieldEditingComponent(props); return ( <> - {(EmptyFieldPhComponent && ) || ( + {(EmptyFieldEditingComponent && ) || ( )} @@ -57,9 +54,13 @@ export function withEmptyFieldEditingComponent< // eslint-disable-next-line react/display-name return (props: FieldComponentProps) => { - const EmptyFieldPhComponent = getEmptyFieldPhComponent(props); + const EmptyFieldEditingComponent = getEmptyFieldEditingComponent(props); return ( - <>{(EmptyFieldPhComponent && ) || } + <> + {(EmptyFieldEditingComponent && ) || ( + + )} + ); }; } diff --git a/packages/sitecore-jss/src/layout/index.ts b/packages/sitecore-jss/src/layout/index.ts index 22d06e95a3..17c2d61c3b 100644 --- a/packages/sitecore-jss/src/layout/index.ts +++ b/packages/sitecore-jss/src/layout/index.ts @@ -15,9 +15,10 @@ export { ComponentFields, ComponentParams, EditMode, + FieldMetadata, } from './models'; -export { getFieldValue, getChildPlaceholder, fieldValueIsEmpty } from './utils'; +export { getFieldValue, getChildPlaceholder, isFieldValueEmpty } from './utils'; export { getContentStylesheetLink } from './content-styles'; diff --git a/packages/sitecore-jss/src/layout/models.ts b/packages/sitecore-jss/src/layout/models.ts index eae3853605..3f60e02081 100644 --- a/packages/sitecore-jss/src/layout/models.ts +++ b/packages/sitecore-jss/src/layout/models.ts @@ -124,11 +124,18 @@ export type GenericFieldValue = | { [key: string]: unknown } | Array<{ [key: string]: unknown }>; -export interface Field { +export interface Field extends FieldMetadata { value: T; editable?: string; } +/** + * represents the field metadata provided by layout service in editMode 'metadata' + */ +export interface FieldMetadata { + metadata?: { [key: string]: unknown }; +} + /** * Content data returned from Content Service */ diff --git a/packages/sitecore-jss/src/layout/utils.ts b/packages/sitecore-jss/src/layout/utils.ts index 2e874fb2d2..a119446603 100644 --- a/packages/sitecore-jss/src/layout/utils.ts +++ b/packages/sitecore-jss/src/layout/utils.ts @@ -81,9 +81,9 @@ export function getChildPlaceholder( /** * Determines if the passed in field object's value is empty. - * @param {GenericFieldValue | Field} field the field object + * @param {GenericFieldValue | Partial} field the field object; Parial is used here because field.value could be required or optional for the different field types */ -export function fieldValueIsEmpty(field: GenericFieldValue | Field): boolean { +export function isFieldValueEmpty(field: GenericFieldValue | Partial): boolean { const isEmpty = (fieldValue: GenericFieldValue) => { if ( (fieldValue as { [key: string]: unknown }).src || @@ -98,7 +98,7 @@ export function fieldValueIsEmpty(field: GenericFieldValue | Field): boolean { if (!field) return true; - const dynamicField = field as Field; + const dynamicField = field as Partial; if (dynamicField.value !== undefined) { return isEmpty(dynamicField.value); } From a1edea7270da95bfe98c0695aeae74109143aaae Mon Sep 17 00:00:00 2001 From: yavorsk Date: Fri, 5 Jul 2024 12:29:51 +0300 Subject: [PATCH 16/25] update withEmptyEditingComponent hoc to accept single options parameter --- .../src/components/NextImage.tsx | 2 +- .../src/components/Date.tsx | 2 +- .../src/components/Image.tsx | 2 +- .../src/components/Link.tsx | 3 +- .../src/components/RichText.tsx | 3 +- .../src/components/Text.tsx | 2 +- .../withEmptyFieldEditingComponent.test.tsx | 55 +++++++++---------- .../withEmptyFieldEditingComponent.tsx | 20 +++++-- 8 files changed, 45 insertions(+), 44 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx index 3cac1ba2eb..77689fc0f7 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx @@ -82,7 +82,7 @@ export const NextImage: React.FC = withFieldMetadata = withFieldMetadata{children}; } }, - DefaultEmptyFieldEditingComponentText + { defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText } ) ); diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index b739faf720..ed535f95ae 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -178,7 +178,7 @@ export const Image: React.FC = withFieldMetadata( return null; // we can't handle the truth }, - DefaultEmptyFieldEditingComponentImage + { defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentImage } ) ); diff --git a/packages/sitecore-jss-react/src/components/Link.tsx b/packages/sitecore-jss-react/src/components/Link.tsx index 0ebe6e2218..1bf0e89151 100644 --- a/packages/sitecore-jss-react/src/components/Link.tsx +++ b/packages/sitecore-jss-react/src/components/Link.tsx @@ -127,8 +127,7 @@ export const Link: React.FC = withFieldMetadata{resultTags}; } ), - DefaultEmptyFieldEditingComponentText, - true + { defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, isForwardRef: true } ), true ); diff --git a/packages/sitecore-jss-react/src/components/RichText.tsx b/packages/sitecore-jss-react/src/components/RichText.tsx index 9df0c6f131..7bef4abf21 100644 --- a/packages/sitecore-jss-react/src/components/RichText.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.tsx @@ -42,8 +42,7 @@ export const RichText: React.FC = withFieldMetadata = withFieldMetadata( return {children}; } }, - DefaultEmptyFieldEditingComponentText + { defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText } ) ); diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx index 46766bfd7b..82ca9989c9 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx @@ -59,10 +59,9 @@ describe('withEmptyFieldEditingComponent', () => { }, }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponent, - DefaultEmptyFieldEditingComponentText - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); const rendered = mount(); const expected = mount(); @@ -83,10 +82,9 @@ describe('withEmptyFieldEditingComponent', () => { emptyFieldEditingComponent: EmptyFieldEditingComponent, }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponent, - DefaultEmptyFieldEditingComponentText - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); const rendered = mount(); const expected = mount(); @@ -102,10 +100,9 @@ describe('withEmptyFieldEditingComponent', () => { }, }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponent, - DefaultEmptyFieldEditingComponentText - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); const rendered = mount(); expect(rendered.html()).to.equal('

field value

foo

bar

'); @@ -120,10 +117,9 @@ describe('withEmptyFieldEditingComponent', () => { editable: false, }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponent, - DefaultEmptyFieldEditingComponentText - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); const rendered = mount(); expect(rendered.html()).to.equal('

foo

bar

'); @@ -136,10 +132,9 @@ describe('withEmptyFieldEditingComponent', () => { }, }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponent, - DefaultEmptyFieldEditingComponentText - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); const rendered = mount(); expect(rendered.html()).to.equal('

foo

bar

'); @@ -155,8 +150,10 @@ describe('withEmptyFieldEditingComponent', () => { const WrappedComponent = withEmptyFieldEditingComponent( TestComponentWithRef, - DefaultEmptyFieldEditingComponentText, - true + { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + isForwardRef: true, + } ); const ref = React.createRef(); const rendered = mount(); @@ -173,10 +170,9 @@ describe('withEmptyFieldEditingComponent', () => { }, }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponent, - DefaultEmptyFieldEditingComponentText - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); const rendered = mount(); expect(rendered.html()).to.equal('

foo

bar

'); @@ -190,10 +186,9 @@ describe('withEmptyFieldEditingComponent', () => { }, }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponent, - DefaultEmptyFieldEditingComponentText - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); const rendered = mount(); expect(rendered.html()).to.equal('

foo

bar

'); diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx index 43b049873e..b085d98ffe 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx @@ -6,6 +6,16 @@ import { FieldMetadata, } from '@sitecore-jss/sitecore-jss/layout'; +/** + * The HOC options: + * defaultEmptyFieldEditingComponent: the default empty field placeholder component + * isForwardRef: set to 'true' if forward reference is needed + * */ +export interface WithEmptyFieldEditingComponentOptions { + defaultEmptyFieldEditingComponent: React.FC; + isForwardRef?: boolean; +} + interface WithEmptyFieldEditingComponentProps { // Parial is used here because field.value could be required or optional for the different field types field?: (Partial | GenericFieldValue) & FieldMetadata; @@ -16,29 +26,27 @@ interface WithEmptyFieldEditingComponentProps { /** * Returns the passed field component or default component in case field value is empty and edit mode is 'metadata' * @param {ComponentType} FieldComponent the field component - * @param {React.FC} defaultEmptyFieldEditingComponent the default empty field placeholder component - * @param {boolean} isForwardRef set to 'true' if forward reference is needed + * @param {WithEmptyFieldEditingComponentProps} options the options of the HOC; */ export function withEmptyFieldEditingComponent< FieldComponentProps extends WithEmptyFieldEditingComponentProps, RefElementType = HTMLElement >( FieldComponent: ComponentType, - defaultEmptyFieldEditingComponent: React.FC, - isForwardRef = false + options: WithEmptyFieldEditingComponentOptions ) { const getEmptyFieldEditingComponent = ( props: FieldComponentProps ): React.ComponentClass | React.FC => { const { editable = true } = props; if (props.field?.metadata && editable && isFieldValueEmpty(props.field)) { - return props.emptyFieldEditingComponent || defaultEmptyFieldEditingComponent; + return props.emptyFieldEditingComponent || options.defaultEmptyFieldEditingComponent; } return null; }; - if (isForwardRef) { + if (options.isForwardRef) { // eslint-disable-next-line react/display-name return forwardRef((props: FieldComponentProps, ref: React.ForwardedRef) => { const EmptyFieldEditingComponent = getEmptyFieldEditingComponent(props); From 6a94a33ea0d5cf4c19172ab0760732e3204517f8 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Fri, 5 Jul 2024 13:17:43 +0300 Subject: [PATCH 17/25] add unit tests for isValueEmpty; isValueEmpty update --- .../sitecore-jss/src/layout/utils.test.ts | 115 +++++++++++++++++- packages/sitecore-jss/src/layout/utils.ts | 19 +-- 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/packages/sitecore-jss/src/layout/utils.test.ts b/packages/sitecore-jss/src/layout/utils.test.ts index 18ee43363a..69ba3cec48 100644 --- a/packages/sitecore-jss/src/layout/utils.test.ts +++ b/packages/sitecore-jss/src/layout/utils.test.ts @@ -1,7 +1,8 @@ /* eslint-disable no-unused-expressions */ import { expect } from 'chai'; import { ComponentRendering } from '../../layout'; -import { getFieldValue, getChildPlaceholder } from './utils'; +import { getFieldValue, getChildPlaceholder, isFieldValueEmpty } from './utils'; +import { describe } from 'node:test'; describe('sitecore-jss layout utils', () => { describe('getFieldValue', () => { @@ -53,4 +54,116 @@ describe('sitecore-jss layout utils', () => { expect((result[0] as ComponentRendering).componentName).to.be.equal('placed'); }); }); + + describe('isFieldValueEmpty', () => { + it('should return true if passed parameter is not present', () => { + const field = {}; + const result = isFieldValueEmpty(field); + expect(result).to.be.true; + }); + + it('should return true if src is empty for GenericFieldValue', () => { + const fieldValue = { + src: '', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.true; + }); + + it('should return true if href is empty for GenericFieldValue', () => { + const fieldValue = { + href: '', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.true; + }); + + it('should return true if src is empty for Field', () => { + const field = { + value: { + src: '', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.true; + }); + + it('should return true if href is empty for Field', () => { + const field = { + value: { + href: '', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.true; + }); + + it('should return true if field value is empty for Field', () => { + const field = { + value: '', + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.true; + }); + + it('should return false if src is not empty for GenericFieldValue', () => { + const fieldValue = { + src: 'imagesrc', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.false; + }); + + it('should return false if href is not empty for GenericFieldValue', () => { + const fieldValue = { + href: 'some.url//', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.false; + }); + + it('should return false if src is not empty for Field', () => { + const field = { + value: { + src: 'the image src', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + + it('should return false if href is not empty for Field', () => { + const field = { + value: { + href: 'some.url//', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + + it('should return false if field value is not empty for Field', () => { + const field = { + value: 'field value', + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + + it('should return false if field value has number value', () => { + const field = { + value: 1, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + + it('should return false if field value has value number 0', () => { + const field = { + value: 0, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + }); }); diff --git a/packages/sitecore-jss/src/layout/utils.ts b/packages/sitecore-jss/src/layout/utils.ts index a119446603..75ae31827e 100644 --- a/packages/sitecore-jss/src/layout/utils.ts +++ b/packages/sitecore-jss/src/layout/utils.ts @@ -85,15 +85,20 @@ export function getChildPlaceholder( */ export function isFieldValueEmpty(field: GenericFieldValue | Partial): boolean { const isEmpty = (fieldValue: GenericFieldValue) => { - if ( - (fieldValue as { [key: string]: unknown }).src || - (fieldValue as { [key: string]: unknown }).href || - fieldValue - ) { + if (typeof fieldValue === 'object') { + if ( + (fieldValue as { [key: string]: unknown }).src || + (fieldValue as { [key: string]: unknown }).href + ) { + return false; + } + + return true; + } else if (typeof fieldValue === 'number') { return false; + } else { + return !fieldValue; } - - return true; }; if (!field) return true; From 052fc3ba48c36964fd57e4affb3478032061bd0e Mon Sep 17 00:00:00 2001 From: yavorsk Date: Fri, 5 Jul 2024 16:42:17 +0300 Subject: [PATCH 18/25] isFieldValueEmpty - account for boolean value; add additional code comments, update some existing comments --- .../src/components/Link.test.tsx | 12 +- .../src/components/NextImage.tsx | 1 + .../withEmptyFieldEditingComponent.test.tsx | 293 +++++++++--------- .../withEmptyFieldEditingComponent.tsx | 15 +- .../sitecore-jss/src/layout/utils.test.ts | 17 +- packages/sitecore-jss/src/layout/utils.ts | 6 +- 6 files changed, 188 insertions(+), 156 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx index 2ecd8e9517..9c7a79543f 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx @@ -402,9 +402,11 @@ describe('', () => { ); }); - it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + it('should render default empty field component when field value is empty in edit mode metadata', () => { const field = { - value: '', + value: { + href: '', + }, metadata: testMetadata, }; @@ -425,9 +427,11 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + it('should render custom empty field component when provided, when field value is empty in edit mode metadata', () => { const field = { - value: '', + value: { + href: '', + }, metadata: testMetadata, }; diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx index 77689fc0f7..541248b5fc 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx @@ -101,6 +101,7 @@ NextImage.propTypes = { imageParams: PropTypes.objectOf( PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]).isRequired ), + emptyFieldEditingComponent: PropTypes.func, }; NextImage.displayName = 'NextImage'; diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx index 82ca9989c9..7b7017ba07 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx @@ -4,193 +4,194 @@ import { expect } from 'chai'; import { mount } from 'enzyme'; import { withEmptyFieldEditingComponent } from './withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from '../components/DefaultEmptyFieldEditingComponents'; -import { describe } from 'node:test'; describe('withEmptyFieldEditingComponent', () => { - const testMetadata = { - contextItem: { - id: '{09A07660-6834-476C-B93B-584248D3003B}', - language: 'en', - revision: 'a0b36ce0a7db49418edf90eb9621e145', - version: 1, - }, - fieldId: '{414061F4-FBB1-4591-BC37-BFFA67F745EB}', - fieldType: 'single-line', - rawValue: 'Test1', - }; - - type TestComponentProps = { - field?: { - value?: string; - metadata?: { [key: string]: unknown }; + describe('Metadata', () => { + const testMetadata = { + contextItem: { + id: '{09A07660-6834-476C-B93B-584248D3003B}', + language: 'en', + revision: 'a0b36ce0a7db49418edf90eb9621e145', + version: 1, + }, + fieldId: '{414061F4-FBB1-4591-BC37-BFFA67F745EB}', + fieldType: 'single-line', + rawValue: 'Test1', + }; + + type TestComponentProps = { + field?: { + value?: string; + metadata?: { [key: string]: unknown }; + }; + editable?: boolean; }; - editable?: boolean; - }; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const TestComponent = (props: TestComponentProps) => { - return ( -
-

{props.field?.value}

-

foo

-

bar

-
- ); - }; - // eslint-disable-next-line react/display-name - const TestComponentWithRef = forwardRef( - (props: TestComponentProps, ref: React.ForwardedRef) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const TestComponent = (props: TestComponentProps) => { return (

{props.field?.value}

-

foo

+

foo

bar

); - } - ); - - it('Should render provided default empty value placeholder component if field value is empty in metadata edit mode', () => { - const props = { - field: { - value: '', - metadata: testMetadata, - }, }; - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, - }); - - const rendered = mount(); - const expected = mount(); + // eslint-disable-next-line react/display-name + const TestComponentWithRef = forwardRef( + (props: TestComponentProps, ref: React.ForwardedRef) => { + return ( +
+

{props.field?.value}

+

foo

+

bar

+
+ ); + } + ); - expect(rendered.html()).to.equal(expected.html()); - }); + it('Should render provided default empty value component component if field value is empty', () => { + const props = { + field: { + value: '', + metadata: testMetadata, + }, + }; - it('Should render custom empty value placeholder if provided via props if field value is empty in metadata edit mode', () => { - const EmptyFieldEditingComponent: React.FC = () => ( - Custom Empty field value - ); + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); - const props = { - field: { - value: '', - metadata: testMetadata, - }, - emptyFieldEditingComponent: EmptyFieldEditingComponent, - }; + const rendered = mount(); + const expected = mount(); - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + expect(rendered.html()).to.equal(expected.html()); }); - const rendered = mount(); - const expected = mount(); + it('Should render custom empty value component if provided via props if field value is empty', () => { + const EmptyFieldEditingComponent: React.FC = () => ( + Custom Empty field value + ); - expect(rendered.html()).to.equal(expected.html()); - }); + const props = { + field: { + value: '', + metadata: testMetadata, + }, + emptyFieldEditingComponent: EmptyFieldEditingComponent, + }; - it('Should render component if field value is provided', () => { - const props = { - field: { - value: 'field value', - metadata: testMetadata, - }, - }; + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + const expected = mount(); - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + expect(rendered.html()).to.equal(expected.html()); }); - const rendered = mount(); - expect(rendered.html()).to.equal('

field value

foo

bar

'); - }); + it('Should render component if field value is provided', () => { + const props = { + field: { + value: 'field value', + metadata: testMetadata, + }, + }; - it('Should render component if component is explicitly not editable if value is empty in metadata edit mode', () => { - const props = { - field: { - value: '', - metadata: testMetadata, - }, - editable: false, - }; + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + const rendered = mount(); + expect(rendered.html()).to.equal('

field value

foo

bar

'); }); - const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

'); - }); + it('Should render component if component is explicitly not editable if value is empty', () => { + const props = { + field: { + value: '', + metadata: testMetadata, + }, + editable: false, + }; - it('Should render component if metadata is not provided', () => { - const props = { - field: { - value: '', - }, - }; + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); }); - const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

'); - }); - - it('Should render component with forward ref if field value is provided in metadata edit mode', () => { - const props = { - field: { - value: 'field value', - metadata: testMetadata, - }, - }; + it('Should render component if metadata is not provided', () => { + const props = { + field: { + value: '', + }, + }; - const WrappedComponent = withEmptyFieldEditingComponent( - TestComponentWithRef, - { + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, - isForwardRef: true, - } - ); - const ref = React.createRef(); - const rendered = mount(); + }); - expect(ref.current?.outerHTML).to.equal('

foo

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

field value

foo

bar

'); - }); + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); + }); - it('Should render component if field src value is provided (case for Image component)', () => { - const props = { - field: { - metadata: testMetadata, - src: 'img src', - }, - }; + it('Should render component with forward ref if field value is provided', () => { + const props = { + field: { + value: 'field value', + metadata: testMetadata, + }, + }; + + const WrappedComponent = withEmptyFieldEditingComponent( + TestComponentWithRef, + { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + isForwardRef: true, + } + ); + const ref = React.createRef(); + const rendered = mount(); - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + expect(ref.current?.outerHTML).to.equal('

foo

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

field value

foo

bar

'); }); - const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

'); - }); + it('Should render component if field src value is provided (case for Image component)', () => { + const props = { + field: { + metadata: testMetadata, + src: 'img src', + }, + }; - it('Should render component if field href value is provided (case for Link component)', () => { - const props = { - field: { - metadata: testMetadata, - href: 'img src', - }, - }; + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); }); - const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

'); + it('Should render component if field href value is provided (case for Link component)', () => { + const props = { + field: { + metadata: testMetadata, + href: 'img src', + }, + }; + + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + expect(rendered.html()).to.equal('

foo

bar

'); + }); }); }); diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx index b085d98ffe..942b1d6659 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx @@ -7,17 +7,24 @@ import { } from '@sitecore-jss/sitecore-jss/layout'; /** - * The HOC options: - * defaultEmptyFieldEditingComponent: the default empty field placeholder component - * isForwardRef: set to 'true' if forward reference is needed + * The HOC options * */ export interface WithEmptyFieldEditingComponentOptions { + /** + * the default empty field placeholder component + */ defaultEmptyFieldEditingComponent: React.FC; + /** + * 'true' if forward reference is needed + */ isForwardRef?: boolean; } +/* + * represents the WithEmptyFieldEditingComponent HOC's props + */ interface WithEmptyFieldEditingComponentProps { - // Parial is used here because field.value could be required or optional for the different field types + // Partial type is used here because _field.value_ could be required or optional for the different field types field?: (Partial | GenericFieldValue) & FieldMetadata; editable?: boolean; emptyFieldEditingComponent?: React.ComponentClass | React.FC; diff --git a/packages/sitecore-jss/src/layout/utils.test.ts b/packages/sitecore-jss/src/layout/utils.test.ts index 69ba3cec48..bdf6afa401 100644 --- a/packages/sitecore-jss/src/layout/utils.test.ts +++ b/packages/sitecore-jss/src/layout/utils.test.ts @@ -2,7 +2,6 @@ import { expect } from 'chai'; import { ComponentRendering } from '../../layout'; import { getFieldValue, getChildPlaceholder, isFieldValueEmpty } from './utils'; -import { describe } from 'node:test'; describe('sitecore-jss layout utils', () => { describe('getFieldValue', () => { @@ -165,5 +164,21 @@ describe('sitecore-jss layout utils', () => { const result = isFieldValueEmpty(field); expect(result).to.be.false; }); + + it('should return false if field value is boolean false', () => { + const field = { + value: false, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + + it('should return false if field value is boolean true', () => { + const field = { + value: true, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); }); }); diff --git a/packages/sitecore-jss/src/layout/utils.ts b/packages/sitecore-jss/src/layout/utils.ts index 75ae31827e..dca3ed8795 100644 --- a/packages/sitecore-jss/src/layout/utils.ts +++ b/packages/sitecore-jss/src/layout/utils.ts @@ -81,7 +81,8 @@ export function getChildPlaceholder( /** * Determines if the passed in field object's value is empty. - * @param {GenericFieldValue | Partial} field the field object; Parial is used here because field.value could be required or optional for the different field types + * @param {GenericFieldValue | Partial} field the field object. + * Partial type is used here because _field.value_ could be required or optional for the different field types */ export function isFieldValueEmpty(field: GenericFieldValue | Partial): boolean { const isEmpty = (fieldValue: GenericFieldValue) => { @@ -95,6 +96,9 @@ export function isFieldValueEmpty(field: GenericFieldValue | Partial): bo return true; } else if (typeof fieldValue === 'number') { + // Avoid returning true for 0 + return false; + } else if (typeof fieldValue === 'boolean') { return false; } else { return !fieldValue; From 74960b35707ea7b0be25fb05613b2c0c0d1d275e Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 8 Jul 2024 08:34:16 +0300 Subject: [PATCH 19/25] isEmptyField refactoring and unit tests update --- .../sitecore-jss/src/layout/utils.test.ts | 212 +++++++++--------- packages/sitecore-jss/src/layout/utils.ts | 26 ++- 2 files changed, 124 insertions(+), 114 deletions(-) diff --git a/packages/sitecore-jss/src/layout/utils.test.ts b/packages/sitecore-jss/src/layout/utils.test.ts index bdf6afa401..7da3c77c1f 100644 --- a/packages/sitecore-jss/src/layout/utils.test.ts +++ b/packages/sitecore-jss/src/layout/utils.test.ts @@ -61,42 +61,6 @@ describe('sitecore-jss layout utils', () => { expect(result).to.be.true; }); - it('should return true if src is empty for GenericFieldValue', () => { - const fieldValue = { - src: '', - }; - const result = isFieldValueEmpty(fieldValue); - expect(result).to.be.true; - }); - - it('should return true if href is empty for GenericFieldValue', () => { - const fieldValue = { - href: '', - }; - const result = isFieldValueEmpty(fieldValue); - expect(result).to.be.true; - }); - - it('should return true if src is empty for Field', () => { - const field = { - value: { - src: '', - }, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.true; - }); - - it('should return true if href is empty for Field', () => { - const field = { - value: { - href: '', - }, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.true; - }); - it('should return true if field value is empty for Field', () => { const field = { value: '', @@ -105,42 +69,6 @@ describe('sitecore-jss layout utils', () => { expect(result).to.be.true; }); - it('should return false if src is not empty for GenericFieldValue', () => { - const fieldValue = { - src: 'imagesrc', - }; - const result = isFieldValueEmpty(fieldValue); - expect(result).to.be.false; - }); - - it('should return false if href is not empty for GenericFieldValue', () => { - const fieldValue = { - href: 'some.url//', - }; - const result = isFieldValueEmpty(fieldValue); - expect(result).to.be.false; - }); - - it('should return false if src is not empty for Field', () => { - const field = { - value: { - src: 'the image src', - }, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.false; - }); - - it('should return false if href is not empty for Field', () => { - const field = { - value: { - href: 'some.url//', - }, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.false; - }); - it('should return false if field value is not empty for Field', () => { const field = { value: 'field value', @@ -149,36 +77,116 @@ describe('sitecore-jss layout utils', () => { expect(result).to.be.false; }); - it('should return false if field value has number value', () => { - const field = { - value: 1, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.false; - }); - - it('should return false if field value has value number 0', () => { - const field = { - value: 0, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.false; - }); - - it('should return false if field value is boolean false', () => { - const field = { - value: false, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.false; - }); - - it('should return false if field value is boolean true', () => { - const field = { - value: true, - }; - const result = isFieldValueEmpty(field); - expect(result).to.be.false; + describe('Image', () => { + it('should return true if src is empty for GenericFieldValue', () => { + const fieldValue = { + src: '', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.true; + }); + + it('should return true if src is empty for Field', () => { + const field = { + value: { + src: '', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.true; + }); + + it('should return false if src is not empty for GenericFieldValue', () => { + const fieldValue = { + src: 'imagesrc', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.false; + }); + + it('should return false if src is not empty for Field', () => { + const field = { + value: { + src: 'the image src', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + }); + + describe('Link', () => { + it('should return true if href is empty for GenericFieldValue', () => { + const fieldValue = { + href: '', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.true; + }); + + it('should return true if href is empty for Field', () => { + const field = { + value: { + href: '', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.true; + }); + + it('should return false if href is not empty for GenericFieldValue', () => { + const fieldValue = { + href: 'some.url//', + }; + const result = isFieldValueEmpty(fieldValue); + expect(result).to.be.false; + }); + + it('should return false if href is not empty for Field', () => { + const field = { + value: { + href: 'some.url//', + }, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + }); + + describe('boolean', () => { + it('should return false if field value is boolean false', () => { + const field = { + value: false, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + + it('should return false if field value is boolean true', () => { + const field = { + value: true, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + }); + + describe('number', () => { + it('should return false if field value has number value', () => { + const field = { + value: 1, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); + + it('should return false if field value has value number 0', () => { + const field = { + value: 0, + }; + const result = isFieldValueEmpty(field); + expect(result).to.be.false; + }); }); }); }); diff --git a/packages/sitecore-jss/src/layout/utils.ts b/packages/sitecore-jss/src/layout/utils.ts index dca3ed8795..56811b092f 100644 --- a/packages/sitecore-jss/src/layout/utils.ts +++ b/packages/sitecore-jss/src/layout/utils.ts @@ -85,20 +85,22 @@ export function getChildPlaceholder( * Partial type is used here because _field.value_ could be required or optional for the different field types */ export function isFieldValueEmpty(field: GenericFieldValue | Partial): boolean { + const isImageFieldEmpty = (fieldValue: GenericFieldValue) => + !(fieldValue as { [key: string]: unknown }).src; + const isFileFieldEmpty = (fieldValue: GenericFieldValue) => + !(fieldValue as { [key: string]: unknown }).src; + const isLinkFieldEmpty = (fieldValue: GenericFieldValue) => + !(fieldValue as { [key: string]: unknown }).href; + const isEmpty = (fieldValue: GenericFieldValue) => { if (typeof fieldValue === 'object') { - if ( - (fieldValue as { [key: string]: unknown }).src || - (fieldValue as { [key: string]: unknown }).href - ) { - return false; - } - - return true; - } else if (typeof fieldValue === 'number') { - // Avoid returning true for 0 - return false; - } else if (typeof fieldValue === 'boolean') { + return ( + isImageFieldEmpty(fieldValue) && + isFileFieldEmpty(fieldValue) && + isLinkFieldEmpty(fieldValue) + ); + } else if (typeof fieldValue === 'number' || typeof fieldValue === 'boolean') { + // Avoid returning true for 0 and false values return false; } else { return !fieldValue; From 48a2af206b1151ae5f4db7164070a9c5472a2094 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 8 Jul 2024 08:34:30 +0300 Subject: [PATCH 20/25] unit tests update --- .../withEmptyFieldEditingComponent.test.tsx | 183 +++++++++++++++--- 1 file changed, 152 insertions(+), 31 deletions(-) diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx index 7b7017ba07..2cd08c9c60 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.test.tsx @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { mount } from 'enzyme'; import { withEmptyFieldEditingComponent } from './withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from '../components/DefaultEmptyFieldEditingComponents'; +import { describe } from 'node:test'; describe('withEmptyFieldEditingComponent', () => { describe('Metadata', () => { @@ -21,8 +22,10 @@ describe('withEmptyFieldEditingComponent', () => { type TestComponentProps = { field?: { - value?: string; + value?: { [key: string]: string | undefined } | string; metadata?: { [key: string]: unknown }; + src?: string; + href?: string; }; editable?: boolean; }; @@ -31,7 +34,7 @@ describe('withEmptyFieldEditingComponent', () => { const TestComponent = (props: TestComponentProps) => { return (
-

{props.field?.value}

+

hi

foo

bar

@@ -43,7 +46,7 @@ describe('withEmptyFieldEditingComponent', () => { (props: TestComponentProps, ref: React.ForwardedRef) => { return (
-

{props.field?.value}

+

hi

foo

bar

@@ -51,7 +54,7 @@ describe('withEmptyFieldEditingComponent', () => { } ); - it('Should render provided default empty value component component if field value is empty', () => { + it('Should render provided default empty value component component if field value is not provided', () => { const props = { field: { value: '', @@ -69,7 +72,7 @@ describe('withEmptyFieldEditingComponent', () => { expect(rendered.html()).to.equal(expected.html()); }); - it('Should render custom empty value component if provided via props if field value is empty', () => { + it('Should render custom empty value component if provided via props if field value is not provided', () => { const EmptyFieldEditingComponent: React.FC = () => ( Custom Empty field value ); @@ -105,7 +108,7 @@ describe('withEmptyFieldEditingComponent', () => { }); const rendered = mount(); - expect(rendered.html()).to.equal('

field value

foo

bar

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

hi

foo

bar

'); }); it('Should render component if component is explicitly not editable if value is empty', () => { @@ -122,7 +125,7 @@ describe('withEmptyFieldEditingComponent', () => { }); const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

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

hi

foo

bar

'); }); it('Should render component if metadata is not provided', () => { @@ -137,7 +140,7 @@ describe('withEmptyFieldEditingComponent', () => { }); const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

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

hi

foo

bar

'); }); it('Should render component with forward ref if field value is provided', () => { @@ -159,39 +162,157 @@ describe('withEmptyFieldEditingComponent', () => { const rendered = mount(); expect(ref.current?.outerHTML).to.equal('

foo

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

field value

foo

bar

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

hi

foo

bar

'); }); - it('Should render component if field src value is provided (case for Image component)', () => { - const props = { - field: { - metadata: testMetadata, - src: 'img src', - }, - }; + describe('Image', () => { + it('Should render component if field src is provided', () => { + const props = { + field: { + metadata: testMetadata, + src: 'img src', + }, + }; - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + expect(rendered.html()).to.equal('

hi

foo

bar

'); }); - const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

'); + it('Should render component if field value src is provided', () => { + const props = { + field: { + metadata: testMetadata, + value: { src: 'img src' }, + }, + }; + + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + expect(rendered.html()).to.equal('

hi

foo

bar

'); + }); + + it('Should render provided default empty value component component if field value src is not provided', () => { + const props = { + field: { + value: { src: undefined }, + metadata: testMetadata, + }, + }; + + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + const expected = mount(); + + expect(rendered.html()).to.equal(expected.html()); + }); + + it('Should render custom empty value component if provided via props if field src is not provided', () => { + const EmptyFieldEditingComponent: React.FC = () => ( + Custom Empty field value + ); + + const props = { + field: { + src: undefined, + metadata: testMetadata, + }, + emptyFieldEditingComponent: EmptyFieldEditingComponent, + }; + + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + const expected = mount(); + + expect(rendered.html()).to.equal(expected.html()); + }); }); - it('Should render component if field href value is provided (case for Link component)', () => { - const props = { - field: { - metadata: testMetadata, - href: 'img src', - }, - }; + describe('Link', () => { + it('Should render component if field href is provided', () => { + const props = { + field: { + metadata: testMetadata, + href: 'img src', + }, + }; - const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { - defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + expect(rendered.html()).to.equal('

hi

foo

bar

'); }); - const rendered = mount(); - expect(rendered.html()).to.equal('

foo

bar

'); + it('Should render component if field value href is provided', () => { + const props = { + field: { + metadata: testMetadata, + value: { href: 'img src' }, + }, + }; + + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + expect(rendered.html()).to.equal('

hi

foo

bar

'); + }); + + it('Should render provided default empty value component component if field value href is not provided', () => { + const props = { + field: { + value: { href: undefined }, + metadata: testMetadata, + }, + }; + + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + const expected = mount(); + + expect(rendered.html()).to.equal(expected.html()); + }); + + it('Should render custom empty value component if provided via props if field href is not provided', () => { + const EmptyFieldEditingComponent: React.FC = () => ( + Custom Empty field value + ); + + const props = { + field: { + href: undefined, + metadata: testMetadata, + }, + emptyFieldEditingComponent: EmptyFieldEditingComponent, + }; + + const WrappedComponent = withEmptyFieldEditingComponent(TestComponent, { + defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentText, + }); + + const rendered = mount(); + const expected = mount(); + + expect(rendered.html()).to.equal(expected.html()); + }); }); }); }); From 62bf07360c34df4ca30e4a3e95be75380f268c3b Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 8 Jul 2024 08:36:17 +0300 Subject: [PATCH 21/25] update emptyFieldComponent prop proptype; use isFieldValueEmpty in field components; update Image and Link unit tests --- .../src/components/Date.tsx | 10 ++- .../src/components/File.tsx | 3 +- .../src/components/Image.test.tsx | 67 +++++++++++++++++-- .../src/components/Image.tsx | 13 ++-- .../src/components/Link.test.tsx | 64 ++++++++++++++++-- .../src/components/Link.tsx | 15 ++--- .../src/components/RichText.tsx | 11 +-- .../src/components/Text.tsx | 11 +-- .../src/components/sharedTypes.ts | 2 +- 9 files changed, 158 insertions(+), 38 deletions(-) diff --git a/packages/sitecore-jss-react/src/components/Date.tsx b/packages/sitecore-jss-react/src/components/Date.tsx index 4b94e31f57..913b03cb03 100644 --- a/packages/sitecore-jss-react/src/components/Date.tsx +++ b/packages/sitecore-jss-react/src/components/Date.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import PropTypes, { Requireable } from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; +import { isFieldValueEmpty } from '@sitecore-jss/sitecore-jss/layout'; export interface DateFieldProps extends EditableFieldProps { /** The date field data. */ @@ -24,7 +25,7 @@ export interface DateFieldProps extends EditableFieldProps { export const DateField: React.FC = withFieldMetadata( withEmptyFieldEditingComponent( ({ field, tag, editable = true, render, ...otherProps }) => { - if (!field || (!field.editable && !field.value)) { + if (!field || (!field.editable && isFieldValueEmpty(field))) { return null; } @@ -66,7 +67,10 @@ DateField.propTypes = { tag: PropTypes.string, editable: PropTypes.bool, render: PropTypes.func, - emptyFieldEditingComponent: PropTypes.func, + emptyFieldEditingComponent: PropTypes.oneOfType([ + PropTypes.object as Requireable>, + PropTypes.func as Requireable>, + ]), }; DateField.displayName = 'Date'; diff --git a/packages/sitecore-jss-react/src/components/File.tsx b/packages/sitecore-jss-react/src/components/File.tsx index 510688b6b2..165f6b305b 100644 --- a/packages/sitecore-jss-react/src/components/File.tsx +++ b/packages/sitecore-jss-react/src/components/File.tsx @@ -1,3 +1,4 @@ +import { isFieldValueEmpty } from '@sitecore-jss/sitecore-jss/layout'; import PropTypes from 'prop-types'; import React from 'react'; @@ -27,7 +28,7 @@ export const File: React.FC = ({ field, children, ...otherProps }) => const dynamicField: FileField | FileFieldValue = field; - if (!field || (!dynamicField.value && !(dynamicField as FileFieldValue).src)) { + if (!field || isFieldValueEmpty(dynamicField)) { return null; } diff --git a/packages/sitecore-jss-react/src/components/Image.test.tsx b/packages/sitecore-jss-react/src/components/Image.test.tsx index 231588bee4..559ec660ea 100644 --- a/packages/sitecore-jss-react/src/components/Image.test.tsx +++ b/packages/sitecore-jss-react/src/components/Image.test.tsx @@ -328,9 +328,9 @@ describe('', () => { ); }); - it('should render default empty field placeholder for Image when field value is empty in edit mode metadata', () => { + it('should render default empty field placeholder for Image when field value src is not present', () => { const field = { - value: '', + value: { src: undefined }, metadata: testMetadata, }; @@ -347,9 +347,28 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + it('should render default empty field placeholder for Image when field src is not present', () => { const field = { - value: '', + src: undefined, + metadata: testMetadata, + }; + + const rendered = mount(); + const defaultEmptyImagePlaceholder = mount(); + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + defaultEmptyImagePlaceholder.html(), + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value src is not present', () => { + const field = { + value: { src: undefined }, metadata: testMetadata, }; @@ -372,9 +391,45 @@ describe('', () => { ); }); - it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + it('should render custom empty field placeholder when provided, when field src is not present', () => { + const field = { + src: undefined, + metadata: testMetadata, + }; + + const EmptyFieldEditingComponent: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing when field value src is not present, when editing is explicitly disabled', () => { + const field = { + value: { src: undefined }, + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal(''); + }); + + it('should render nothing when field src is not present, when editing is explicitly disabled', () => { const field = { - value: '', + src: undefined, metadata: testMetadata, }; diff --git a/packages/sitecore-jss-react/src/components/Image.tsx b/packages/sitecore-jss-react/src/components/Image.tsx index ed535f95ae..81125870b7 100644 --- a/packages/sitecore-jss-react/src/components/Image.tsx +++ b/packages/sitecore-jss-react/src/components/Image.tsx @@ -1,5 +1,5 @@ import { mediaApi } from '@sitecore-jss/sitecore-jss/media'; -import PropTypes from 'prop-types'; +import PropTypes, { Requireable } from 'prop-types'; import React from 'react'; import { addClassName, convertAttributesToReactProps } from '../utils'; import { getAttributesString } from '../utils'; @@ -8,6 +8,7 @@ import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditi import { DefaultEmptyFieldEditingComponentImage } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; +import { isFieldValueEmpty } from '@sitecore-jss/sitecore-jss/layout'; export interface ImageFieldValue { [attributeName: string]: unknown; @@ -145,10 +146,7 @@ export const Image: React.FC = withFieldMetadata( ({ editable = true, imageParams, field, mediaUrlPrefix, ...otherProps }) => { const dynamicMedia = field as ImageField | ImageFieldValue; - if ( - !field || - (!dynamicMedia.editable && !dynamicMedia.value && !(dynamicMedia as ImageFieldValue).src) - ) { + if (!field || (!dynamicMedia.editable && isFieldValueEmpty(dynamicMedia))) { return null; } @@ -197,7 +195,10 @@ Image.propTypes = { imageParams: PropTypes.objectOf( PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]).isRequired ), - emptyFieldEditingComponent: PropTypes.func, + emptyFieldEditingComponent: PropTypes.oneOfType([ + PropTypes.object as Requireable>, + PropTypes.func as Requireable>, + ]), }; Image.displayName = 'Image'; diff --git a/packages/sitecore-jss-react/src/components/Link.test.tsx b/packages/sitecore-jss-react/src/components/Link.test.tsx index a0f6c6a0cc..6a69b3afb0 100644 --- a/packages/sitecore-jss-react/src/components/Link.test.tsx +++ b/packages/sitecore-jss-react/src/components/Link.test.tsx @@ -158,9 +158,9 @@ describe('', () => { ); }); - it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + it('should render default empty field placeholder when field value is not present', () => { const field = { - value: '', + value: { href: undefined }, metadata: testMetadata, }; const rendered = mount(); @@ -176,9 +176,52 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + it('should render default empty field placeholder when field value href is not present', () => { const field = { - value: '', + href: undefined, + metadata: testMetadata, + }; + const rendered = mount(); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value is not present', () => { + const field = { + value: { href: undefined }, + metadata: testMetadata, + }; + + const EmptyFieldEditingComponent: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value href is not present', () => { + const field = { + href: undefined, metadata: testMetadata, }; @@ -201,7 +244,7 @@ describe('', () => { ); }); - it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + it('should render nothing when field value is not present, when editing is explicitly disabled', () => { const field = { value: undefined, metadata: testMetadata, @@ -211,5 +254,16 @@ describe('', () => { expect(rendered.html()).to.equal(''); }); + + it('should render nothing when field value href is empty, when editing is explicitly disabled', () => { + const field = { + value: { href: undefined }, + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal(''); + }); }); }); diff --git a/packages/sitecore-jss-react/src/components/Link.tsx b/packages/sitecore-jss-react/src/components/Link.tsx index 1bf0e89151..75130b9ad8 100644 --- a/packages/sitecore-jss-react/src/components/Link.tsx +++ b/packages/sitecore-jss-react/src/components/Link.tsx @@ -1,10 +1,11 @@ import React, { ReactElement, RefAttributes, forwardRef } from 'react'; -import PropTypes from 'prop-types'; +import PropTypes, { Requireable } from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; +import { isFieldValueEmpty } from '@sitecore-jss/sitecore-jss/layout'; export interface LinkFieldValue { [attributeName: string]: unknown; @@ -46,12 +47,7 @@ export const Link: React.FC = withFieldMetadata>, + PropTypes.func as Requireable>, + ]), }; Link.propTypes = LinkPropTypes; diff --git a/packages/sitecore-jss-react/src/components/RichText.tsx b/packages/sitecore-jss-react/src/components/RichText.tsx index 7bef4abf21..f878b8856a 100644 --- a/packages/sitecore-jss-react/src/components/RichText.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.tsx @@ -1,10 +1,10 @@ import React, { forwardRef } from 'react'; -import PropTypes from 'prop-types'; +import PropTypes, { Requireable } from 'prop-types'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; import { EditableFieldProps } from './sharedTypes'; -import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; +import { FieldMetadata, isFieldValueEmpty } from '@sitecore-jss/sitecore-jss/layout'; export interface RichTextField extends FieldMetadata { value?: string; @@ -27,7 +27,7 @@ export const RichText: React.FC = withFieldMetadata( ({ field, tag = 'div', editable = true, ...otherProps }, ref) => { - if (!field || (!field.editable && !field.value)) { + if (!field || (!field.editable && isFieldValueEmpty(field))) { return null; } @@ -55,7 +55,10 @@ export const RichTextPropTypes = { }), tag: PropTypes.string, editable: PropTypes.bool, - emptyFieldEditingComponent: PropTypes.func, + emptyFieldEditingComponent: PropTypes.oneOfType([ + PropTypes.object as Requireable>, + PropTypes.func as Requireable>, + ]), }; RichText.propTypes = RichTextPropTypes; diff --git a/packages/sitecore-jss-react/src/components/Text.tsx b/packages/sitecore-jss-react/src/components/Text.tsx index 9851106b03..74c54307aa 100644 --- a/packages/sitecore-jss-react/src/components/Text.tsx +++ b/packages/sitecore-jss-react/src/components/Text.tsx @@ -2,9 +2,9 @@ import React, { ReactElement } from 'react'; import { withFieldMetadata } from '../enhancers/withFieldMetadata'; import { withEmptyFieldEditingComponent } from '../enhancers/withEmptyFieldEditingComponent'; import { DefaultEmptyFieldEditingComponentText } from './DefaultEmptyFieldEditingComponents'; -import PropTypes from 'prop-types'; +import PropTypes, { Requireable } from 'prop-types'; import { EditableFieldProps } from './sharedTypes'; -import { FieldMetadata } from '@sitecore-jss/sitecore-jss/layout'; +import { FieldMetadata, isFieldValueEmpty } from '@sitecore-jss/sitecore-jss/layout'; export interface TextField extends FieldMetadata { value?: string | number; @@ -28,7 +28,7 @@ export interface TextProps extends EditableFieldProps { export const Text: React.FC = withFieldMetadata( withEmptyFieldEditingComponent( ({ field, tag, editable = true, encode = true, ...otherProps }) => { - if (!field || (!field.editable && (field.value === undefined || field.value === ''))) { + if (!field || (!field.editable && isFieldValueEmpty(field))) { return null; } @@ -104,7 +104,10 @@ Text.propTypes = { tag: PropTypes.string, editable: PropTypes.bool, encode: PropTypes.bool, - emptyFieldEditingComponent: PropTypes.func, + emptyFieldEditingComponent: PropTypes.oneOfType([ + PropTypes.object as Requireable>, + PropTypes.func as Requireable>, + ]), }; Text.displayName = 'Text'; diff --git a/packages/sitecore-jss-react/src/components/sharedTypes.ts b/packages/sitecore-jss-react/src/components/sharedTypes.ts index d0ffb9a362..ed9f5fe643 100644 --- a/packages/sitecore-jss-react/src/components/sharedTypes.ts +++ b/packages/sitecore-jss-react/src/components/sharedTypes.ts @@ -33,5 +33,5 @@ export interface EditableFieldProps { * * Custom element to render in Pages in Metadata edit mode if field value is empty */ - emptyFieldEditingComponent?: React.ComponentClass | React.FC; + emptyFieldEditingComponent?: React.ComponentClass | React.FC; } From 2a05d7cb98cbab47103b3b039b02fac4653f865a Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 8 Jul 2024 08:37:01 +0300 Subject: [PATCH 22/25] update nextjs Link and NextImage unit tests, small updates --- .../src/components/Link.test.tsx | 77 +++++++++++++++++-- .../src/components/Link.tsx | 2 +- .../src/components/NextImage.test.tsx | 67 ++++++++++++++-- .../src/components/NextImage.tsx | 13 ++-- 4 files changed, 140 insertions(+), 19 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx index 9c7a79543f..3f295391ad 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx @@ -402,10 +402,10 @@ describe('', () => { ); }); - it('should render default empty field component when field value is empty in edit mode metadata', () => { + it('should render default empty field component when field value href is not present', () => { const field = { value: { - href: '', + href: undefined, }, metadata: testMetadata, }; @@ -427,10 +427,33 @@ describe('', () => { ); }); - it('should render custom empty field component when provided, when field value is empty in edit mode metadata', () => { + it('should render default empty field component when field href is not present', () => { + const field = { + href: undefined, + metadata: testMetadata, + }; + + const rendered = mount( + + + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + '[No text in field]', + '', + ].join('') + ); + }); + + it('should render custom empty field component when provided, when field value href is not present', () => { const field = { value: { - href: '', + href: undefined, }, metadata: testMetadata, }; @@ -456,9 +479,51 @@ describe('', () => { ); }); - it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + it('should render custom empty field component when provided, when field href is not present', () => { + const field = { + href: undefined, + metadata: testMetadata, + }; + + const EmptyFieldEditingComponent: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing when field value href is not present and editing is explicitly disabled', () => { + const field = { + value: { href: undefined }, + metadata: testMetadata, + }; + + const rendered = mount( + + + + ); + + expect(rendered.html()).to.equal(''); + }); + + it('should render nothing when field href is not present and editing is explicitly disabled', () => { const field = { - value: '', + href: undefined, metadata: testMetadata, }; diff --git a/packages/sitecore-jss-nextjs/src/components/Link.tsx b/packages/sitecore-jss-nextjs/src/components/Link.tsx index d3034d21fb..ed4d3ccc58 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.tsx @@ -41,7 +41,7 @@ export const Link = forwardRef( const value = ((field as LinkFieldValue).href ? field : (field as LinkField).value) as LinkFieldValue; - const { href, querystring, anchor } = value; + const { href, querystring, anchor } = value || {}; const isEditing = editable && ((field as LinkFieldValue).editable || (field as LinkFieldValue).metadata); diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx index 445a6f30a7..9981624475 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx @@ -322,9 +322,9 @@ describe('', () => { ); }); - it('should render default empty field placeholder for Image when field value is empty in edit mode metadata', () => { + it('should render default empty field placeholder for Image when field value src is not present', () => { const field = { - value: '', + value: {}, metadata: testMetadata, }; @@ -341,9 +341,28 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + it('should render default empty field placeholder for Image when field src is not present', () => { const field = { - value: '', + src: undefined, + metadata: testMetadata, + }; + + const rendered = mount(); + const defaultEmptyImagePlaceholder = mount(); + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + defaultEmptyImagePlaceholder.html(), + '', + ].join('') + ); + }); + + it('should render custom empty field placeholder when provided, when field value src is not present', () => { + const field = { + value: {}, metadata: testMetadata, }; @@ -366,9 +385,45 @@ describe('', () => { ); }); - it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + it('should render custom empty field placeholder when provided, when field src is not present', () => { + const field = { + src: undefined, + metadata: testMetadata, + }; + + const EmptyFieldEditingComponent: React.FC = () => ( + Custom Empty field value + ); + + const rendered = mount( + + ); + + expect(rendered.html()).to.equal( + [ + `${JSON.stringify( + testMetadata + )}`, + 'Custom Empty field value', + '', + ].join('') + ); + }); + + it('should render nothing when field value is not present, when editing is explicitly disabled', () => { + const field = { + value: {}, + metadata: testMetadata, + }; + + const rendered = mount(); + + expect(rendered.html()).to.equal(''); + }); + + it('should render nothing when field src is not present, when editing is explicitly disabled', () => { const field = { - value: '', + src: undefined, metadata: testMetadata, }; diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx index 541248b5fc..96b4a31c13 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.tsx @@ -1,5 +1,5 @@ import { mediaApi } from '@sitecore-jss/sitecore-jss/media'; -import PropTypes from 'prop-types'; +import PropTypes, { Requireable } from 'prop-types'; import React from 'react'; import { getEEMarkup, @@ -11,6 +11,7 @@ import { import Image, { ImageProps as NextImageProperties } from 'next/image'; import { withEmptyFieldEditingComponent } from '@sitecore-jss/sitecore-jss-react'; import { DefaultEmptyFieldEditingComponentImage } from '@sitecore-jss/sitecore-jss-react'; +import { isFieldValueEmpty } from '@sitecore-jss/sitecore-jss/layout'; type NextImageProps = ImageProps & Partial; export const NextImage: React.FC = withFieldMetadata( @@ -24,10 +25,7 @@ export const NextImage: React.FC = withFieldMetadata>, + PropTypes.func as Requireable>, + ]), }; NextImage.displayName = 'NextImage'; From 534a0f2e2caa35f5a3b808c82971ee19cf65eef7 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 8 Jul 2024 11:00:02 +0300 Subject: [PATCH 23/25] some test title updates --- .../sitecore-jss-nextjs/src/components/NextImage.test.tsx | 8 ++++---- packages/sitecore-jss-react/src/components/Date.test.tsx | 6 +++--- packages/sitecore-jss-react/src/components/Image.test.tsx | 8 ++++---- packages/sitecore-jss-react/src/components/Link.test.tsx | 8 ++++---- .../sitecore-jss-react/src/components/RichText.test.tsx | 6 +++--- packages/sitecore-jss-react/src/components/Text.test.tsx | 4 ++-- .../src/enhancers/withEmptyFieldEditingComponent.tsx | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx index 9981624475..e39b28c793 100644 --- a/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx +++ b/packages/sitecore-jss-nextjs/src/components/NextImage.test.tsx @@ -322,7 +322,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder for Image when field value src is not present', () => { + it('should render default empty field component for Image when field value src is not present', () => { const field = { value: {}, metadata: testMetadata, @@ -341,7 +341,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder for Image when field src is not present', () => { + it('should render default empty field component for Image when field src is not present', () => { const field = { src: undefined, metadata: testMetadata, @@ -360,7 +360,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value src is not present', () => { + it('should render custom empty field component when provided, when field value src is not present', () => { const field = { value: {}, metadata: testMetadata, @@ -385,7 +385,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field src is not present', () => { + it('should render custom empty field component when provided, when field src is not present', () => { const field = { src: undefined, metadata: testMetadata, diff --git a/packages/sitecore-jss-react/src/components/Date.test.tsx b/packages/sitecore-jss-react/src/components/Date.test.tsx index 69e5303303..d62d7f5e17 100644 --- a/packages/sitecore-jss-react/src/components/Date.test.tsx +++ b/packages/sitecore-jss-react/src/components/Date.test.tsx @@ -116,7 +116,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + it('should render default empty field component when field value is empty', () => { const field = { value: '', metadata: testMetadata, @@ -135,7 +135,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + it('should render custom empty field component when provided, when field value is empty', () => { const field = { value: '', metadata: testMetadata, @@ -160,7 +160,7 @@ describe('', () => { ); }); - it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + it('should render nothing when field value is empty, when editing is explicitly disabled ', () => { const field = { value: '', metadata: testMetadata, diff --git a/packages/sitecore-jss-react/src/components/Image.test.tsx b/packages/sitecore-jss-react/src/components/Image.test.tsx index 559ec660ea..910f10e296 100644 --- a/packages/sitecore-jss-react/src/components/Image.test.tsx +++ b/packages/sitecore-jss-react/src/components/Image.test.tsx @@ -328,7 +328,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder for Image when field value src is not present', () => { + it('should render default empty field component for Image when field value src is not present', () => { const field = { value: { src: undefined }, metadata: testMetadata, @@ -347,7 +347,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder for Image when field src is not present', () => { + it('should render default empty field component for Image when field src is not present', () => { const field = { src: undefined, metadata: testMetadata, @@ -366,7 +366,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value src is not present', () => { + it('should render custom empty field component when provided, when field value src is not present', () => { const field = { value: { src: undefined }, metadata: testMetadata, @@ -391,7 +391,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field src is not present', () => { + it('should render custom empty field component when provided, when field src is not present', () => { const field = { src: undefined, metadata: testMetadata, diff --git a/packages/sitecore-jss-react/src/components/Link.test.tsx b/packages/sitecore-jss-react/src/components/Link.test.tsx index 6a69b3afb0..db2784e214 100644 --- a/packages/sitecore-jss-react/src/components/Link.test.tsx +++ b/packages/sitecore-jss-react/src/components/Link.test.tsx @@ -158,7 +158,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder when field value is not present', () => { + it('should render default empty field component when field value is not present', () => { const field = { value: { href: undefined }, metadata: testMetadata, @@ -176,7 +176,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder when field value href is not present', () => { + it('should render default empty field component when field value href is not present', () => { const field = { href: undefined, metadata: testMetadata, @@ -194,7 +194,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is not present', () => { + it('should render custom empty field component when provided, when field value is not present', () => { const field = { value: { href: undefined }, metadata: testMetadata, @@ -219,7 +219,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value href is not present', () => { + it('should render custom empty field component when provided, when field value href is not present', () => { const field = { href: undefined, metadata: testMetadata, diff --git a/packages/sitecore-jss-react/src/components/RichText.test.tsx b/packages/sitecore-jss-react/src/components/RichText.test.tsx index de06f486ae..fb2d36874c 100644 --- a/packages/sitecore-jss-react/src/components/RichText.test.tsx +++ b/packages/sitecore-jss-react/src/components/RichText.test.tsx @@ -128,7 +128,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + it('should render default empty field component when field value is empty', () => { const field = { value: '', metadata: testMetadata, @@ -147,7 +147,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + it('should render custom empty field component when provided, when field value is empty', () => { const field = { value: '', metadata: testMetadata, @@ -172,7 +172,7 @@ describe('', () => { ); }); - it('should render nothing when field value is empty, when editing is explicitly disabled in edit mode metadata ', () => { + it('should render nothing when field value is empty, when editing is explicitly disabled ', () => { const field = { value: '', metadata: testMetadata, diff --git a/packages/sitecore-jss-react/src/components/Text.test.tsx b/packages/sitecore-jss-react/src/components/Text.test.tsx index dfe0c5767c..de724563b4 100644 --- a/packages/sitecore-jss-react/src/components/Text.test.tsx +++ b/packages/sitecore-jss-react/src/components/Text.test.tsx @@ -211,7 +211,7 @@ describe('', () => { ); }); - it('should render default empty field placeholder when field value is empty in edit mode metadata', () => { + it('should render default empty field component when field value is empty in edit mode metadata', () => { const field = { value: '', metadata: testMetadata, @@ -230,7 +230,7 @@ describe('', () => { ); }); - it('should render custom empty field placeholder when provided, when field value is empty in edit mode metadata', () => { + it('should render custom empty field component when provided, when field value is empty in edit mode metadata', () => { const field = { value: '', metadata: testMetadata, diff --git a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx index 942b1d6659..b24dd5ddd5 100644 --- a/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx +++ b/packages/sitecore-jss-react/src/enhancers/withEmptyFieldEditingComponent.tsx @@ -11,7 +11,7 @@ import { * */ export interface WithEmptyFieldEditingComponentOptions { /** - * the default empty field placeholder component + * the default empty field component */ defaultEmptyFieldEditingComponent: React.FC; /** From b669ab5a913abcd7041b2240bb1c08ac50143c6d Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 8 Jul 2024 11:12:53 +0300 Subject: [PATCH 24/25] add additional check for metadata for isEditing var in next richtext --- packages/sitecore-jss-nextjs/src/components/RichText.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sitecore-jss-nextjs/src/components/RichText.tsx b/packages/sitecore-jss-nextjs/src/components/RichText.tsx index fb88b2b23e..2357b28501 100644 --- a/packages/sitecore-jss-nextjs/src/components/RichText.tsx +++ b/packages/sitecore-jss-nextjs/src/components/RichText.tsx @@ -32,7 +32,7 @@ export const RichText = (props: RichTextProps): JSX.Element => { ...rest } = props; const hasText = props.field && props.field.value; - const isEditing = editable && props.field && props.field.editable; + const isEditing = editable && props.field && (props.field.editable || props.field.metadata); const router = useRouter(); const richTextRef = useRef(null); From f18852aa2b89327d0e79b35222094aa4ee96fff5 Mon Sep 17 00:00:00 2001 From: yavorsk Date: Mon, 8 Jul 2024 13:55:18 +0300 Subject: [PATCH 25/25] add clarifying comment --- packages/sitecore-jss-nextjs/src/components/Link.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sitecore-jss-nextjs/src/components/Link.tsx b/packages/sitecore-jss-nextjs/src/components/Link.tsx index ed4d3ccc58..6307187bd4 100644 --- a/packages/sitecore-jss-nextjs/src/components/Link.tsx +++ b/packages/sitecore-jss-nextjs/src/components/Link.tsx @@ -41,6 +41,7 @@ export const Link = forwardRef( const value = ((field as LinkFieldValue).href ? field : (field as LinkField).value) as LinkFieldValue; + // fallback to {} if value is undefined; could happen if field is LinkFieldValue, href is empty in metadata mode const { href, querystring, anchor } = value || {}; const isEditing =