mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-27 19:42:11 +10:00
Add richText interpolation for breaking long words with spans
This commit is contained in:
parent
1d6d88bdab
commit
c75b1ed0ba
9 changed files with 509 additions and 29 deletions
|
|
@ -120,3 +120,11 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.longWord {
|
||||
/* fallback option */
|
||||
word-break: break-all;
|
||||
/* use break-word if available */
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ import { NamedLink, ResponsiveImage } from '../../components';
|
|||
import { propTypes } from '../../util/types';
|
||||
import { formatMoney } from '../../util/currency';
|
||||
import { ensureListing, ensureUser } from '../../util/data';
|
||||
import { richText } from '../../util/richText';
|
||||
import { createSlug } from '../../util/urlHelpers';
|
||||
import config from '../../config';
|
||||
|
||||
import css from './ListingCard.css';
|
||||
|
||||
const MAX_LENGTH_FOR_WORDS_IN_TITLE = 10;
|
||||
const MIN_LENGTH_FOR_LONG_WORDS = 10;
|
||||
|
||||
const priceData = (price, intl) => {
|
||||
if (price && price.currency === config.currency) {
|
||||
|
|
@ -32,23 +33,6 @@ const priceData = (price, intl) => {
|
|||
return {};
|
||||
};
|
||||
|
||||
// Cards are not fixed sizes - So, long words in title make flexboxed items to grow too big.
|
||||
// 1. We split title to an array of words and spaces.
|
||||
// "foo bar".split(/([^\s]+)/gi) => ["", "foo", " ", "bar", ""]
|
||||
// 2. Then we break long words by adding a '<span>' with word-break: 'break-all';
|
||||
const formatTitle = (title, maxLength) => {
|
||||
const nonWhiteSpaceSequence = /([^\s]+)/gi;
|
||||
return title.split(nonWhiteSpaceSequence).map((word, index) => {
|
||||
return word.length > maxLength ? (
|
||||
<span key={index} style={{ wordBreak: 'break-all' }}>
|
||||
{word}
|
||||
</span>
|
||||
) : (
|
||||
word
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const ListingCardComponent = props => {
|
||||
const { className, rootClassName, intl, listing, setActiveListing } = props;
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
|
@ -92,7 +76,12 @@ export const ListingCardComponent = props => {
|
|||
</div>
|
||||
</div>
|
||||
<div className={css.mainInfo}>
|
||||
<div className={css.title}>{formatTitle(title, MAX_LENGTH_FOR_WORDS_IN_TITLE)}</div>
|
||||
<div className={css.title}>
|
||||
{richText(title, {
|
||||
longWordMinLength: MIN_LENGTH_FOR_LONG_WORDS,
|
||||
longWordClass: css.longWord,
|
||||
})}
|
||||
</div>
|
||||
<div className={css.authorInfo}>
|
||||
<FormattedMessage
|
||||
className={css.authorName}
|
||||
|
|
|
|||
7
src/components/ListingLink/ListingLink.css
Normal file
7
src/components/ListingLink/ListingLink.css
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.longWord {
|
||||
/* fallback option */
|
||||
word-break: break-all;
|
||||
/* use break-word if available */
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
|
@ -8,9 +8,14 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { string, oneOfType, node } from 'prop-types';
|
||||
import { NamedLink } from '../../components';
|
||||
import { richText } from '../../util/richText';
|
||||
import { LISTING_STATE_PENDING_APPROVAL, propTypes } from '../../util/types';
|
||||
import { LISTING_PAGE_PENDING_APPROVAL_VARIANT, createSlug } from '../../util/urlHelpers';
|
||||
import { NamedLink } from '../../components';
|
||||
|
||||
import css from './ListingLink.css';
|
||||
|
||||
const MIN_LENGTH_FOR_LONG_WORDS = 16;
|
||||
|
||||
const ListingLink = props => {
|
||||
const { className, listing, children } = props;
|
||||
|
|
@ -21,6 +26,15 @@ const ListingLink = props => {
|
|||
const id = listing.id.uuid;
|
||||
const { title, state } = listing.attributes;
|
||||
const slug = createSlug(title);
|
||||
const richTitle = (
|
||||
<span>
|
||||
{richText(title, {
|
||||
longWordMinLength: MIN_LENGTH_FOR_LONG_WORDS,
|
||||
longWordClass: css.longWord,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
|
||||
const isPendingApproval = state === LISTING_STATE_PENDING_APPROVAL;
|
||||
const linkProps = isPendingApproval
|
||||
? {
|
||||
|
|
@ -37,7 +51,7 @@ const ListingLink = props => {
|
|||
};
|
||||
return (
|
||||
<NamedLink className={className} {...linkProps}>
|
||||
{children ? children : listing.attributes.title || ''}
|
||||
{children ? children : richTitle || ''}
|
||||
</NamedLink>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -744,3 +744,11 @@
|
|||
padding: 15px 24px 15px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.longWord {
|
||||
/* fallback option */
|
||||
word-break: break-all;
|
||||
/* use break-word if available */
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { LISTING_PAGE_PENDING_APPROVAL_VARIANT, createSlug, parse } from '../../
|
|||
import { formatMoney } from '../../util/currency';
|
||||
import { createResourceLocatorString, findRouteByRouteName } from '../../util/routes';
|
||||
import { ensureListing, ensureOwnListing, ensureUser, userDisplayName } from '../../util/data';
|
||||
import { richText } from '../../util/richText';
|
||||
import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck';
|
||||
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
|
||||
import {
|
||||
|
|
@ -47,6 +48,8 @@ import css from './ListingPage.css';
|
|||
|
||||
// This defines when ModalInMobile shows content as Modal
|
||||
const MODAL_BREAKPOINT = 1023;
|
||||
const MIN_LENGTH_FOR_LONG_WORDS_IN_TITLE = 16;
|
||||
const MIN_LENGTH_FOR_LONG_WORDS_IN_DESCRIPTION = 20;
|
||||
|
||||
const { UUID } = sdkTypes;
|
||||
|
||||
|
|
@ -292,6 +295,15 @@ export class ListingPageComponent extends Component {
|
|||
publicData,
|
||||
} = currentListing.attributes;
|
||||
|
||||
const richTitle = (
|
||||
<span>
|
||||
{richText(title, {
|
||||
longWordMinLength: MIN_LENGTH_FOR_LONG_WORDS_IN_TITLE,
|
||||
longWordClass: css.longWord,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
|
||||
const category =
|
||||
publicData && publicData.category ? (
|
||||
<span>
|
||||
|
|
@ -393,7 +405,7 @@ export class ListingPageComponent extends Component {
|
|||
const bookingHeading = (
|
||||
<div className={css.bookingHeading}>
|
||||
<h2 className={css.bookingTitle}>
|
||||
<FormattedMessage id="ListingPage.bookingTitle" values={{ title }} />
|
||||
<FormattedMessage id="ListingPage.bookingTitle" values={{ title: richTitle }} />
|
||||
</h2>
|
||||
<div className={css.bookingHelp}>
|
||||
<FormattedMessage
|
||||
|
|
@ -564,7 +576,7 @@ export class ListingPageComponent extends Component {
|
|||
</div>
|
||||
</div>
|
||||
<div className={css.heading}>
|
||||
<h1 className={css.title}>{title}</h1>
|
||||
<h1 className={css.title}>{richTitle}</h1>
|
||||
<div className={css.author}>
|
||||
{category}
|
||||
<FormattedMessage id="ListingPage.hostedBy" values={{ name: hostLink }} />
|
||||
|
|
@ -587,7 +599,12 @@ export class ListingPageComponent extends Component {
|
|||
<h2 className={css.descriptionTitle}>
|
||||
<FormattedMessage id="ListingPage.descriptionTitle" />
|
||||
</h2>
|
||||
<p className={css.description}>{description}</p>
|
||||
<p className={css.description}>
|
||||
{richText(description, {
|
||||
longWordMinLength: MIN_LENGTH_FOR_LONG_WORDS_IN_DESCRIPTION,
|
||||
longWordClass: css.longWord,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={css.featuresContainer}>
|
||||
|
|
@ -663,7 +680,7 @@ export class ListingPageComponent extends Component {
|
|||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
>
|
||||
<div className={css.modalHeading}>
|
||||
<h1 className={css.title}>{title}</h1>
|
||||
<h1 className={css.title}>{richTitle}</h1>
|
||||
<div className={css.author}>
|
||||
<span className={css.authorName}>
|
||||
<FormattedMessage
|
||||
|
|
|
|||
|
|
@ -217,7 +217,11 @@ exports[`ListingPage matches snapshot 1`] = `
|
|||
</div>
|
||||
<div>
|
||||
<h1>
|
||||
listing1 title
|
||||
<span>
|
||||
listing1
|
||||
|
||||
title
|
||||
</span>
|
||||
</h1>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
|
|
@ -267,7 +271,9 @@ exports[`ListingPage matches snapshot 1`] = `
|
|||
/>
|
||||
</h2>
|
||||
<p>
|
||||
listing1 description
|
||||
listing1
|
||||
|
||||
description
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -431,7 +437,11 @@ exports[`ListingPage matches snapshot 1`] = `
|
|||
>
|
||||
<div>
|
||||
<h1>
|
||||
listing1 title
|
||||
<span>
|
||||
listing1
|
||||
|
||||
title
|
||||
</span>
|
||||
</h1>
|
||||
<div>
|
||||
<span>
|
||||
|
|
@ -452,7 +462,11 @@ exports[`ListingPage matches snapshot 1`] = `
|
|||
id="ListingPage.bookingTitle"
|
||||
values={
|
||||
Object {
|
||||
"title": "listing1 title",
|
||||
"title": <span>
|
||||
listing1
|
||||
|
||||
title
|
||||
</span>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
135
src/util/richText.js
Normal file
135
src/util/richText.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import React from 'react';
|
||||
import { chain } from 'lodash';
|
||||
import { ExternalLink } from '../components';
|
||||
|
||||
/**
|
||||
* Add zero width space (zwsp) around given breakchars (default '/') to make word break possible.
|
||||
* E.g. "one/two/three" => ["one", "/", "two" "/" "three"]
|
||||
*
|
||||
* @param {string} wordToBreak word to be broken from special character points.
|
||||
* @param {string} breakChars string containing possible chars that can be surrounded with zwsp.
|
||||
* @return {Array<string>} returns an array containing strings-
|
||||
*/
|
||||
export const zwspAroundSpecialCharsSplit = (wordToBreak, breakChars = '/') => {
|
||||
if (typeof wordToBreak !== 'string') {
|
||||
return wordToBreak;
|
||||
}
|
||||
|
||||
const bcArray = breakChars.split('');
|
||||
|
||||
// Escape special regular expression chars
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
||||
const escapedBCArray = bcArray.map(c => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||
const reSplit = new RegExp('([' + escapedBCArray.join('') + '])');
|
||||
|
||||
const zwsp = '';
|
||||
return wordToBreak.split(reSplit).map(w => (bcArray.includes(w) ? `${zwsp}${w}${zwsp}` : w));
|
||||
};
|
||||
|
||||
/**
|
||||
* Layouts are not fixed sizes - So, long words in text make flexboxed items to grow too big.
|
||||
* This wraps long words with span and adds given class to it
|
||||
*
|
||||
* @param {string} word to be wrapped if requirement (longWordMinLength) is met
|
||||
* @param {number} key span needs a key in React/JSX
|
||||
* @param {number} longWordMinLength minimum length when word is considered long
|
||||
* @param {string} longWordClass class to be added to spans
|
||||
* @return {node} returns a string or component
|
||||
*/
|
||||
export const wrapLongWord = (word, key, options = {}) => {
|
||||
const { longWordMinLength, longWordClass } = options;
|
||||
if (typeof word !== 'string' || !(longWordMinLength && longWordClass)) {
|
||||
return word;
|
||||
}
|
||||
|
||||
const isShortWord = word.length <= longWordMinLength;
|
||||
return isShortWord ? (
|
||||
word
|
||||
) : (
|
||||
<span key={key} className={longWordClass}>
|
||||
{word}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find links from words and surround them with <ExternalLink> component
|
||||
*
|
||||
* @param {string} word to be linkified if requirement (link) is met
|
||||
* @param {number} key span needs a key in React/JSX
|
||||
* @param {Object} options than can contain keys: linkify, linkClass.
|
||||
* @return {Array<node>} returns a array containing ExternalLink component or strings
|
||||
*/
|
||||
export const linkifyOrWrapLinkSplit = (word, key, options = {}) => {
|
||||
if (typeof word !== 'string') {
|
||||
return word;
|
||||
}
|
||||
const { linkify, linkClass } = options;
|
||||
|
||||
// TODO This can't handle links that contain parenthesis:
|
||||
// '(http://example.org/path_(etc))'
|
||||
// Currently extracts:
|
||||
// '(<a href=\"http://example.org/path_\" ...>http://example.org/path_</a>(etc))'
|
||||
//
|
||||
// We need to
|
||||
// 1) track whether token before link contains parenthesis as a last character
|
||||
// before link token ("word.split(urlRegex)[linkIndex - 1]") and
|
||||
// 2) add enough characters to the end of link-token from the next token
|
||||
// after link ("word.split(urlRegex)[linkIndex + 1]")
|
||||
|
||||
// urlRegex modified from examples in
|
||||
// https://stackoverflow.com/questions/1500260/detect-urls-in-text-with-javascript
|
||||
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const urlRegex = /(\bhttps?:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi;
|
||||
if (word.match(urlRegex)) {
|
||||
// Split strings like "(http://www.example.com)" to ["(","http://www.example.com",")"]
|
||||
return word.split(urlRegex).map(w => {
|
||||
return !w.match(urlRegex) ? (
|
||||
w
|
||||
) : linkify ? (
|
||||
<ExternalLink key={key} href={w} className={linkClass}>
|
||||
{w}
|
||||
</ExternalLink>
|
||||
) : linkClass ? (
|
||||
<span key={key} className={linkClass}>
|
||||
{w}
|
||||
</span>
|
||||
) : (
|
||||
w
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return word;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scan text to fill in wrappers for long words and add links.
|
||||
* Wrap long words: options should contain longWordMinLength & longWordClass
|
||||
* Linkify found links: options should contain "linkify: true" (linkClass is optional)
|
||||
*
|
||||
* @param {string} text check text content
|
||||
* @param {object} options { longWordMinLength, longWordClass, linkify = false, linkClass }
|
||||
* @return {Array<node>} returns a child array containing strings and inline elements
|
||||
*/
|
||||
export const richText = (text, options) => {
|
||||
if (typeof text !== 'string') {
|
||||
return text;
|
||||
}
|
||||
|
||||
// longWordMinLength & longWordClass are needed for long words to be spanned
|
||||
// linkify = true is needed for links to be linkified (linkClass is optional)
|
||||
const { longWordMinLength, longWordClass, linkify = false, linkClass } = options;
|
||||
const linkOrLongWordClass = linkClass ? linkClass : longWordClass;
|
||||
const nonWhiteSpaceSequence = /([^\s]+)/gi;
|
||||
|
||||
return text.split(nonWhiteSpaceSequence).reduce((acc, nextChild, i) => {
|
||||
const parts = chain([nextChild])
|
||||
.flatMap(w => linkifyOrWrapLinkSplit(w, i, { linkify, linkClass: linkOrLongWordClass }))
|
||||
.flatMap(w => zwspAroundSpecialCharsSplit(w, '/,'))
|
||||
.map((w, j) => wrapLongWord(w, `${i}${j}`, { longWordMinLength, longWordClass }))
|
||||
.value();
|
||||
return acc.concat(parts);
|
||||
}, []);
|
||||
};
|
||||
288
src/util/richText.test.js
Normal file
288
src/util/richText.test.js
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import {
|
||||
zwspAroundSpecialCharsSplit,
|
||||
linkifyOrWrapLinkSplit,
|
||||
wrapLongWord,
|
||||
richText,
|
||||
} from './richText';
|
||||
|
||||
describe('richText', () => {
|
||||
// There variables contain zero-width-space on both sides of the visible character.
|
||||
const slashWithZWSP = '/';
|
||||
const commaWithZWSP = ',';
|
||||
const dollarSignWithZWSP = '$';
|
||||
const backslashWithZWSP = '\\';
|
||||
|
||||
describe('zwspAroundSpecialChars(word, optionalBreakingChars)', () => {
|
||||
it('should not add anything to strings withouth word boundary', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word')).toEqual(['word']);
|
||||
});
|
||||
it('should add zwsp to a word with slash', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word/another')).toEqual([
|
||||
'word',
|
||||
slashWithZWSP,
|
||||
'another',
|
||||
]);
|
||||
});
|
||||
it('should add zwsp to strings with spaces and slashes', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word one/another/third word')).toEqual([
|
||||
'word one',
|
||||
slashWithZWSP,
|
||||
'another',
|
||||
slashWithZWSP,
|
||||
'third word',
|
||||
]);
|
||||
});
|
||||
it('should add zwsp to strings with spaces and slashes when "/" is given', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word one/another/third word', '/')).toEqual([
|
||||
'word one',
|
||||
slashWithZWSP,
|
||||
'another',
|
||||
slashWithZWSP,
|
||||
'third word',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add zwsp to strings with given char (","), no char', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word/another', ',')).toEqual(['word/another']);
|
||||
});
|
||||
it('should add zwsp to strings with given char (","), single char', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word,another', ',')).toEqual([
|
||||
'word',
|
||||
commaWithZWSP,
|
||||
'another',
|
||||
]);
|
||||
});
|
||||
it('should add zwsp to strings with given chars (","), single char and spaces', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word,another/third forth', ',')).toEqual([
|
||||
'word',
|
||||
commaWithZWSP,
|
||||
'another/third forth',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add zwsp to strings with given chars (",/"): multiple occurrences', () => {
|
||||
expect(
|
||||
zwspAroundSpecialCharsSplit('word/another/third,fourth and fifth,sixth', ',/')
|
||||
).toEqual([
|
||||
'word',
|
||||
slashWithZWSP,
|
||||
'another',
|
||||
slashWithZWSP,
|
||||
'third',
|
||||
commaWithZWSP,
|
||||
'fourth and fifth',
|
||||
commaWithZWSP,
|
||||
'sixth',
|
||||
]);
|
||||
});
|
||||
it('should add zwsp to strings with given chars (",/:"), when not-found-chars are included', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word/another,third', '.,/:')).toEqual([
|
||||
'word',
|
||||
slashWithZWSP,
|
||||
'another',
|
||||
commaWithZWSP,
|
||||
'third',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add zwsp to strings with given chars ("?\\$") escapable chars not found', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word/another,third', '?\\$')).toEqual([
|
||||
'word/another,third',
|
||||
]);
|
||||
});
|
||||
it('should add zwsp to strings with given chars ("?\\$") escapable chars $ found', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word/another$third', '?\\$')).toEqual([
|
||||
'word/another',
|
||||
dollarSignWithZWSP,
|
||||
'third',
|
||||
]);
|
||||
});
|
||||
it('should add zwsp to strings with given chars ("?\\$") escapable chars backslash found', () => {
|
||||
expect(zwspAroundSpecialCharsSplit('word/another\\third', '?\\$')).toEqual([
|
||||
'word/another',
|
||||
backslashWithZWSP,
|
||||
'third',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapLongWord(word, key, longWordMinLength, longWordClass)', () => {
|
||||
it('should not add anything to short word', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{wrapLongWord('word', 'key', { longWordMinLength: 10, longWordClass: 'longWord' })}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual('<span>word</span>');
|
||||
});
|
||||
it('should add span around long word', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{wrapLongWord('Pneumonoultramicroscopicsilicovolcanoconiosis', 'key', {
|
||||
longWordMinLength: 10,
|
||||
longWordClass: 'longWord',
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual(
|
||||
'<span><span class="longWord">Pneumonoultramicroscopicsilicovolcanoconiosis</span></span>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('linkifyOrWrapLinkSplit(word, key, linkClass)', () => {
|
||||
it('should not add anything to words without links', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{linkifyOrWrapLinkSplit('word', 'key', { linkify: true, linkClass: 'linkClass' })}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual('<span>word</span>');
|
||||
});
|
||||
it('should add link around words that are links', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{linkifyOrWrapLinkSplit('http://www.example.com', 'key', {
|
||||
linkify: true,
|
||||
linkClass: 'linkClass',
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual(
|
||||
'<span><a href="http://www.example.com" class="linkClass" target="_blank" rel="noopener noreferrer">http://www.example.com</a></span>'
|
||||
);
|
||||
});
|
||||
it('should add link around words that are links even inside parenthesis', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{linkifyOrWrapLinkSplit('(http://www.example.com)', 'key', {
|
||||
linkify: true,
|
||||
linkClass: 'linkClass',
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual(
|
||||
'<span>(<a href="http://www.example.com" class="linkClass" target="_blank" rel="noopener noreferrer">http://www.example.com</a>)</span>'
|
||||
);
|
||||
});
|
||||
it('should add link around words that are links even inside brackets', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{linkifyOrWrapLinkSplit('[http://www.example.com]', 'key', {
|
||||
linkify: true,
|
||||
linkClass: 'linkClass',
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual(
|
||||
'<span>[<a href="http://www.example.com" class="linkClass" target="_blank" rel="noopener noreferrer">http://www.example.com</a>]</span>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('richText(text, { longWordMinLength, longWordClass })', () => {
|
||||
const options = { longWordMinLength: 10, longWordClass: 'longWord' };
|
||||
|
||||
it('should not add anything to strings with short words', () => {
|
||||
const wrapper = shallow(<span>{richText('word word word', options)}</span>);
|
||||
|
||||
expect(wrapper.html()).toEqual('<span>word word word</span>');
|
||||
});
|
||||
it('should add span around a string with a single long word', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{richText(
|
||||
'word Pneumonoultramicroscopicsilicovolcanoconiosis is the longest word',
|
||||
options
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual(
|
||||
'<span>word <span class="longWord">Pneumonoultramicroscopicsilicovolcanoconiosis</span> is the longest word</span>'
|
||||
);
|
||||
});
|
||||
it('should add span around a string with multiple long words', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{richText(
|
||||
'word Pneumonoultramicroscopicsilicovolcanoconiosis is the longest word - Pseudopseudohypoparathyroidism is shorter',
|
||||
options
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
expect(wrapper.html()).toEqual(
|
||||
'<span>word <span class="longWord">Pneumonoultramicroscopicsilicovolcanoconiosis</span> is the longest word - <span class="longWord">Pseudopseudohypoparathyroidism</span> is shorter</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add span around a string with multiple long words and containing slashes', () => {
|
||||
const wrapper = shallow(
|
||||
<span>{richText('Chars one/two/three - count until exhaustion…', options)}</span>
|
||||
);
|
||||
// <span>
|
||||
// Chars one/two/three - count until <span class=\"classX\">exhaustion…</span>
|
||||
// </span>
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<span>Chars one${slashWithZWSP}two${slashWithZWSP}three - count until <span class=\"longWord\">exhaustion…</span></span>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should add span around a string with a long word and containing slashes and commas', () => {
|
||||
const wrapper = shallow(
|
||||
<span>{richText('Chars one/two/three, count until exhaustion…', options)}</span>
|
||||
);
|
||||
// <span>
|
||||
// Chars one/two/three, count until <span class=\"classX\">exhaustion…</span>
|
||||
// </span>
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<span>Chars one${slashWithZWSP}two${slashWithZWSP}three${commaWithZWSP} count until <span class=\"longWord\">exhaustion…</span></span>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should add span around a string with a long word, containing slashes and a link', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{richText(
|
||||
'Chars one/two/three - count until exhaustion… and a random link: http://www.example.com',
|
||||
{ ...options, linkify: true, linkClass: 'link' }
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
// <span>
|
||||
// Chars one/two/three - count until <span class=\"classX\">exhaustion…</span> and a random link: <a href=\"http://www.example.com\" target=\"_blank\" rel=\"noopener noreferrer\">http://www.example.com</a>
|
||||
// </span>
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<span>Chars one${slashWithZWSP}two${slashWithZWSP}three - count until <span class=\"longWord\">exhaustion…</span> and a random link: <a href=\"http://www.example.com\" class=\"link\" target=\"_blank\" rel=\"noopener noreferrer\">http://www.example.com</a></span>`
|
||||
);
|
||||
});
|
||||
it('should add link inside non-whitespace-sequence (http://example.com)', () => {
|
||||
const wrapper = shallow(
|
||||
<span>{richText('Link: (http://example.com)', { ...options, linkify: true })}</span>
|
||||
);
|
||||
// <span>
|
||||
// Link: (<a href=\"http://example.com\" target=\"_blank\" rel=\"noopener noreferrer\">http://example.com</a>)
|
||||
// </span>
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<span>Link: (<a href=\"http://example.com\" class=\"longWord\" target=\"_blank\" rel=\"noopener noreferrer\">http://example.com</a>)</span>`
|
||||
);
|
||||
});
|
||||
it('should not add span around a string if no linkify option is given', () => {
|
||||
const wrapper = shallow(
|
||||
<span>
|
||||
{richText(
|
||||
'Chars one/two/three - count until exhaustion… and a random link: http://www.example.com',
|
||||
options
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
// <span>
|
||||
// Chars one/two/three - count until <span class=\"classX\">exhaustion…</span> and a random link: http://<span class=\"longWord\">www.example.com</span>
|
||||
// </span>
|
||||
expect(wrapper.html()).toEqual(
|
||||
`<span>Chars one${slashWithZWSP}two${slashWithZWSP}three - count until <span class=\"longWord\">exhaustion…</span> and a random link: <span class=\"longWord\">http://www.example.com</span></span>`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue