mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-06-14 22:01:19 -04:00
feat: [UEPR-57] update tests to use react testing library and remove use of default props
This commit is contained in:
parent
a739682d91
commit
dce8c99c1b
65 changed files with 1721 additions and 1463 deletions
.gitignore
bin
package.jsonsrc
components
avatar
cappednumber
carousel
emoji-text
extension-landing
flex-row
forms
grid
helpform
join-flow
modal-navigation
nestedcarousel
news
os-chooser
overflow-menu
people-grid
progression
projects-carousel
registration
social-message
spinner
subnavigation
teacher-banner
thumbnail
thumbnailcolumn
tooltip
welcome
youtube-video-modal
views
test
helpers
unit/components
become-a-scratcher.test.jsxcaptcha.test.jsxcompose-comment.test.jsxdonate-banner.test.jsxemail-step.test.jsxerrorboundary.test.jsxextension-requirements.test.jsxfeedback-form.test.jsxformik-input.test.jsxformik-select.test.jsxinfo-button.test.jsxjoin-flow-step.test.jsxjoin-flow.test.jsxmodal.test.jsxmute-modal.test.jsxmute-step.test.jsxnavigation.test.jsxnext-step-button.test.jsxos-chooser.test.jsxpage.test.jsxregistration-error-step.test.jsxstudio-admin-panel.test.jsxstudio-comments.test.jsxusername-step.test.jsx
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,6 +21,7 @@ ENV
|
|||
|
||||
# Test
|
||||
/test/results/*
|
||||
/test/generated/generated-locales.js
|
||||
/.nyc_output
|
||||
/coverage
|
||||
/bin/lib/localized-urls.json
|
||||
|
|
48
bin/build-translations.js
Normal file
48
bin/build-translations.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
const routes = require('../src/routes.json');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const merge = require('lodash.merge');
|
||||
|
||||
const globalTemplateFile = path.resolve(__dirname, '../src/l10n.json');
|
||||
const generatedLocales = {
|
||||
en: JSON.parse(fs.readFileSync(globalTemplateFile, 'utf8'))
|
||||
};
|
||||
const defaultLocales = {};
|
||||
const views = [];
|
||||
|
||||
for (const route in routes) {
|
||||
if (typeof routes[route].redirect !== 'undefined') {
|
||||
continue;
|
||||
}
|
||||
|
||||
views.push(routes[route].name);
|
||||
try {
|
||||
const subdir = routes[route].view.split('/');
|
||||
subdir.pop();
|
||||
const l10n = path.resolve(
|
||||
__dirname,
|
||||
`../src/views/${subdir.join('/')}/l10n.json`
|
||||
);
|
||||
const viewIds = JSON.parse(fs.readFileSync(l10n, 'utf8'));
|
||||
defaultLocales[routes[route].name] = viewIds;
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
views
|
||||
.map(view => defaultLocales[view])
|
||||
.reduce((acc, curr) => merge(acc, curr), generatedLocales.en);
|
||||
|
||||
const dirPath = './test/generated';
|
||||
const filePath = './test/generated/generated-locales.js';
|
||||
const variableName = 'generatedLocales';
|
||||
const variableValue = JSON.stringify(generatedLocales, null, 2);
|
||||
const content = `const ${variableName} = ${variableValue};
|
||||
export {${variableName}};
|
||||
`;
|
||||
|
||||
fs.mkdirSync(dirPath, {recursive: true});
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
|
@ -9,13 +9,14 @@
|
|||
"test:lint:ci": "eslint . --ext .js,.jsx,.json --format junit -o ./test/results/lint-results.xml",
|
||||
"test:health": "jest ./test/health/*.test.js",
|
||||
"test:integration": "jest ./test/integration/*.test.js --reporters=default --maxWorkers=5",
|
||||
"test:unit": "npm run test:unit:jest && npm run test:unit:tap",
|
||||
"test:unit": "npm run test:build-translations && npm run test:unit:jest && npm run test:unit:tap",
|
||||
"test:unit:jest": "npm run test:unit:jest:unit && npm run test:unit:jest:localization",
|
||||
"test:unit:jest:unit": "jest ./test/unit/ --env=jsdom --reporters=default",
|
||||
"test:unit:jest:localization": "jest ./test/localization/*.test.js --reporters=default",
|
||||
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/ --no-coverage -R classic",
|
||||
"test:unit:convertReportToXunit": "tap ./test/results/unit-raw.tap --no-coverage -R xunit > ./test/results/unit-tap-results.xml",
|
||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/ --coverage --coverage-report=lcov",
|
||||
"test:build-translations": "node ./bin/build-translations.js",
|
||||
"build": "npm run clean && npm run translate && NODE_OPTIONS=--max_old_space_size=8000 webpack --bail",
|
||||
"build:analyze": "ANALYZE_BUNDLE=true npm run build",
|
||||
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
const classNames = require('classnames');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const Avatar = props => (
|
||||
const Avatar = ({
|
||||
className,
|
||||
src = '//uploads.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96',
|
||||
...rest
|
||||
}) => (
|
||||
<img
|
||||
className={classNames('avatar', props.className)}
|
||||
{...omit(props, ['className'])}
|
||||
className={classNames('avatar', className)}
|
||||
src={src}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -15,8 +19,4 @@ Avatar.propTypes = {
|
|||
src: PropTypes.string
|
||||
};
|
||||
|
||||
Avatar.defaultProps = {
|
||||
src: '//uploads.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96'
|
||||
};
|
||||
|
||||
module.exports = Avatar;
|
||||
|
|
|
@ -2,19 +2,21 @@ const PropTypes = require('prop-types');
|
|||
const React = require('react');
|
||||
const FormattedNumber = require('react-intl').FormattedNumber;
|
||||
|
||||
const CappedNumber = props => (
|
||||
<props.as className={props.className}>
|
||||
<FormattedNumber value={Math.min(props.value, 100)} />
|
||||
{props.value > 100 ? '+' : ''}
|
||||
</props.as>
|
||||
const CappedNumber = ({
|
||||
as: Component = 'span',
|
||||
className,
|
||||
value
|
||||
}) => (
|
||||
<Component className={className}>
|
||||
<FormattedNumber value={Math.min(value, 100)} />
|
||||
{value > 100 ? '+' : ''}
|
||||
</Component>
|
||||
);
|
||||
|
||||
CappedNumber.propTypes = {
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.number.isRequired
|
||||
value: PropTypes.number.isRequired,
|
||||
as: PropTypes.elementType
|
||||
};
|
||||
|
||||
CappedNumber.defaultProps = {
|
||||
as: 'span'
|
||||
};
|
||||
module.exports = CappedNumber;
|
||||
|
|
|
@ -11,8 +11,15 @@ require('slick-carousel/slick/slick.scss');
|
|||
require('slick-carousel/slick/slick-theme.scss');
|
||||
require('./carousel.scss');
|
||||
|
||||
const Carousel = props => {
|
||||
defaults(props.settings, {
|
||||
const Carousel = ({
|
||||
className,
|
||||
items = require('./carousel.json'),
|
||||
settings = {},
|
||||
showRemixes = false,
|
||||
showLoves = false,
|
||||
type = 'project'
|
||||
}) => {
|
||||
defaults(settings, {
|
||||
centerMode: false,
|
||||
dots: false,
|
||||
infinite: false,
|
||||
|
@ -49,12 +56,12 @@ const Carousel = props => {
|
|||
|
||||
return (
|
||||
<Slider
|
||||
className={classNames('carousel', props.className)}
|
||||
{... props.settings}
|
||||
className={classNames('carousel', className)}
|
||||
{...settings}
|
||||
>
|
||||
{props.items.map(item => {
|
||||
{items.map(item => {
|
||||
let href = '';
|
||||
switch (props.type) {
|
||||
switch (type) {
|
||||
case 'gallery':
|
||||
href = `/studios/${item.id}/`;
|
||||
break;
|
||||
|
@ -69,14 +76,14 @@ const Carousel = props => {
|
|||
<Thumbnail
|
||||
creator={item.author.username}
|
||||
href={href}
|
||||
key={[props.type, item.id].join('.')}
|
||||
key={`${type}.${item.id}`}
|
||||
loves={item.stats.loves}
|
||||
remixes={item.stats.remixes}
|
||||
showLoves={props.showLoves}
|
||||
showRemixes={props.showRemixes}
|
||||
showLoves={showLoves}
|
||||
showRemixes={showRemixes}
|
||||
src={item.image}
|
||||
title={item.title}
|
||||
type={props.type}
|
||||
type={type}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -102,12 +109,4 @@ Carousel.propTypes = {
|
|||
type: PropTypes.string
|
||||
};
|
||||
|
||||
Carousel.defaultProps = {
|
||||
items: require('./carousel.json'),
|
||||
settings: {},
|
||||
showRemixes: false,
|
||||
showLoves: false,
|
||||
type: 'project'
|
||||
};
|
||||
|
||||
module.exports = Carousel;
|
||||
|
|
|
@ -15,8 +15,15 @@ require('slick-carousel/slick/slick.scss');
|
|||
require('slick-carousel/slick/slick-theme.scss');
|
||||
require('./carousel.scss');
|
||||
|
||||
const Carousel = props => {
|
||||
defaults(props.settings, {
|
||||
const Carousel = ({
|
||||
className,
|
||||
items = require('./carousel.json'),
|
||||
settings = {},
|
||||
showRemixes = false,
|
||||
showLoves = false,
|
||||
type = 'project'
|
||||
}) => {
|
||||
defaults(settings, {
|
||||
centerMode: false,
|
||||
dots: false,
|
||||
infinite: false,
|
||||
|
@ -50,14 +57,16 @@ const Carousel = props => {
|
|||
}
|
||||
]
|
||||
});
|
||||
const arrows = props.items.length > props.settings.slidesToShow;
|
||||
|
||||
const arrows = items.length > settings.slidesToShow;
|
||||
|
||||
return (
|
||||
<Slider
|
||||
arrows={arrows}
|
||||
className={classNames('carousel', props.className)}
|
||||
{... props.settings}
|
||||
className={classNames('carousel', className)}
|
||||
{...settings}
|
||||
>
|
||||
{props.items.map(item => {
|
||||
{items.map(item => {
|
||||
let href = '';
|
||||
switch (item.type) {
|
||||
case 'gallery':
|
||||
|
@ -74,11 +83,11 @@ const Carousel = props => {
|
|||
<Thumbnail
|
||||
creator={item.creator}
|
||||
href={href}
|
||||
key={[props.type, item.id].join('.')}
|
||||
key={[type, item.id].join('.')}
|
||||
loves={item.love_count}
|
||||
remixes={item.remixers_count}
|
||||
showLoves={props.showLoves}
|
||||
showRemixes={props.showRemixes}
|
||||
showLoves={showLoves}
|
||||
showRemixes={showRemixes}
|
||||
src={item.thumbnail_url}
|
||||
title={item.title}
|
||||
type={item.type}
|
||||
|
@ -107,12 +116,4 @@ Carousel.propTypes = {
|
|||
type: PropTypes.string
|
||||
};
|
||||
|
||||
Carousel.defaultProps = {
|
||||
items: require('./carousel.json'),
|
||||
settings: {},
|
||||
showRemixes: false,
|
||||
showLoves: false,
|
||||
type: 'project'
|
||||
};
|
||||
|
||||
module.exports = Carousel;
|
||||
|
|
|
@ -4,22 +4,23 @@ const React = require('react');
|
|||
|
||||
require('./emoji-text.scss');
|
||||
|
||||
const EmojiText = props => (
|
||||
<props.as
|
||||
className={classNames('emoji-text', props.className)}
|
||||
const EmojiText = ({
|
||||
as: Component = 'p',
|
||||
className,
|
||||
text
|
||||
}) => (
|
||||
<Component
|
||||
className={classNames('emoji-text', className)}
|
||||
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
|
||||
__html: props.text
|
||||
__html: text
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
EmojiText.propTypes = {
|
||||
className: PropTypes.string,
|
||||
text: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
EmojiText.defaultProps = {
|
||||
as: 'p'
|
||||
text: PropTypes.string.isRequired,
|
||||
as: PropTypes.elementType
|
||||
};
|
||||
|
||||
module.exports = EmojiText;
|
||||
|
|
|
@ -6,13 +6,20 @@ const FlexRow = require('../../components/flex-row/flex-row.jsx');
|
|||
|
||||
require('./extension-landing.scss');
|
||||
|
||||
const ExtensionRequirements = props => (
|
||||
const ExtensionRequirements = ({
|
||||
hideAndroid = false,
|
||||
hideBluetooth = false,
|
||||
hideChromeOS = false,
|
||||
hideMac = false,
|
||||
hideScratchLink = false,
|
||||
hideWindows = false
|
||||
}) => (
|
||||
<FlexRow className="column extension-requirements-container">
|
||||
<span className="requirements-header">
|
||||
<FormattedMessage id="extensionHeader.requirements" />
|
||||
</span>
|
||||
<FlexRow className="extension-requirements">
|
||||
{!props.hideWindows && (
|
||||
{!hideWindows && (
|
||||
<span>
|
||||
<img
|
||||
alt=""
|
||||
|
@ -21,7 +28,7 @@ const ExtensionRequirements = props => (
|
|||
Windows 10 version 1709+
|
||||
</span>
|
||||
)}
|
||||
{!props.hideMac && (
|
||||
{!hideMac && (
|
||||
<span>
|
||||
<img
|
||||
alt=""
|
||||
|
@ -30,7 +37,7 @@ const ExtensionRequirements = props => (
|
|||
macOS 10.15+
|
||||
</span>
|
||||
)}
|
||||
{!props.hideChromeOS && (
|
||||
{!hideChromeOS && (
|
||||
<span>
|
||||
<img
|
||||
alt=""
|
||||
|
@ -39,7 +46,7 @@ const ExtensionRequirements = props => (
|
|||
ChromeOS
|
||||
</span>
|
||||
)}
|
||||
{!props.hideAndroid && (
|
||||
{!hideAndroid && (
|
||||
<span>
|
||||
<img
|
||||
alt=""
|
||||
|
@ -48,13 +55,13 @@ const ExtensionRequirements = props => (
|
|||
Android 6.0+
|
||||
</span>
|
||||
)}
|
||||
{!props.hideBluetooth && (
|
||||
{!hideBluetooth && (
|
||||
<span>
|
||||
<img src="/svgs/extensions/bluetooth.svg" />
|
||||
Bluetooth
|
||||
</span>
|
||||
)}
|
||||
{!props.hideScratchLink && (
|
||||
{!hideScratchLink && (
|
||||
<span>
|
||||
<img
|
||||
alt=""
|
||||
|
@ -76,13 +83,4 @@ ExtensionRequirements.propTypes = {
|
|||
hideWindows: PropTypes.bool
|
||||
};
|
||||
|
||||
ExtensionRequirements.defaultProps = {
|
||||
hideAndroid: false,
|
||||
hideBluetooth: false,
|
||||
hideChromeOS: false,
|
||||
hideMac: false,
|
||||
hideScratchLink: false,
|
||||
hideWindows: false
|
||||
};
|
||||
|
||||
module.exports = ExtensionRequirements;
|
||||
|
|
|
@ -4,19 +4,20 @@ const React = require('react');
|
|||
|
||||
require('./flex-row.scss');
|
||||
|
||||
const FlexRow = props => (
|
||||
<props.as className={classNames('flex-row', props.className)}>
|
||||
{props.children}
|
||||
</props.as>
|
||||
const FlexRow = ({
|
||||
as: Component = 'div',
|
||||
className,
|
||||
children
|
||||
}) => (
|
||||
<Component className={classNames('flex-row', className)}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
|
||||
FlexRow.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
FlexRow.defaultProps = {
|
||||
as: 'div'
|
||||
className: PropTypes.string,
|
||||
as: PropTypes.elementType
|
||||
};
|
||||
|
||||
module.exports = FlexRow;
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
const classNames = require('classnames');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
require('./button.scss');
|
||||
|
||||
const Button = props => {
|
||||
const classes = classNames('button', props.className, {'forms-close-button': props.isCloseType});
|
||||
const Button = ({
|
||||
children,
|
||||
className = '',
|
||||
isCloseType = false,
|
||||
...restProps
|
||||
}) => {
|
||||
const classes = classNames('button', className, {'forms-close-button': isCloseType});
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classes}
|
||||
{...omit(props, ['className', 'children', 'isCloseType'])}
|
||||
{...restProps}
|
||||
>
|
||||
{
|
||||
props.isCloseType ? (
|
||||
<img
|
||||
alt="close-icon"
|
||||
className="modal-content-close-img"
|
||||
draggable="false"
|
||||
src="/svgs/modal/close-x.svg"
|
||||
/>
|
||||
) : [
|
||||
props.children
|
||||
]
|
||||
}
|
||||
{isCloseType ? (
|
||||
<img
|
||||
alt="close-icon"
|
||||
className="modal-content-close-img"
|
||||
draggable="false"
|
||||
src="/svgs/modal/close-x.svg"
|
||||
/>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
@ -35,9 +37,4 @@ Button.propTypes = {
|
|||
isCloseType: PropTypes.bool
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
className: '',
|
||||
isCloseType: false
|
||||
};
|
||||
|
||||
module.exports = Button;
|
||||
|
|
|
@ -4,13 +4,17 @@ const React = require('react');
|
|||
|
||||
require('./charcount.scss');
|
||||
|
||||
const CharCount = props => (
|
||||
const CharCount = ({
|
||||
className = '',
|
||||
currentCharacters = 0,
|
||||
maxCharacters = 0
|
||||
}) => (
|
||||
<p
|
||||
className={classNames('char-count', props.className, {
|
||||
overmax: (props.currentCharacters > props.maxCharacters)
|
||||
className={classNames('char-count', className, {
|
||||
overmax: (currentCharacters > maxCharacters)
|
||||
})}
|
||||
>
|
||||
{props.currentCharacters}/{props.maxCharacters}
|
||||
{currentCharacters}/{maxCharacters}
|
||||
</p>
|
||||
);
|
||||
|
||||
|
@ -20,9 +24,4 @@ CharCount.propTypes = {
|
|||
maxCharacters: PropTypes.number
|
||||
};
|
||||
|
||||
CharCount.defaultProps = {
|
||||
currentCharacters: 0,
|
||||
maxCharacters: 0
|
||||
};
|
||||
|
||||
module.exports = CharCount;
|
||||
|
|
|
@ -9,9 +9,16 @@ const inputHOC = require('./input-hoc.jsx');
|
|||
require('./row.scss');
|
||||
require('./checkbox.scss');
|
||||
|
||||
const Checkbox = props => (
|
||||
const Checkbox = ({
|
||||
className = '',
|
||||
value = false,
|
||||
valueLabel = '',
|
||||
...props
|
||||
}) => (
|
||||
<FRCCheckbox
|
||||
rowClassName={classNames('checkbox-row', props.className)}
|
||||
rowClassName={classNames('checkbox-row', className)}
|
||||
value={value}
|
||||
valueLabel={valueLabel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -22,9 +29,4 @@ Checkbox.propTypes = {
|
|||
valueLabel: PropTypes.string
|
||||
};
|
||||
|
||||
Checkbox.defaultProps = {
|
||||
value: false,
|
||||
valueLabel: ''
|
||||
};
|
||||
|
||||
module.exports = inputHOC(defaultValidationHOC(Checkbox));
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
|
@ -8,10 +7,15 @@ const React = require('react');
|
|||
* @return {React.Component} a wrapped input component
|
||||
*/
|
||||
module.exports = Component => {
|
||||
const InputComponent = props => (
|
||||
const InputComponent = ({
|
||||
messages = {'general.notRequired': 'Not Required'},
|
||||
required,
|
||||
...props
|
||||
}) => (
|
||||
<Component
|
||||
help={props.required ? null : props.messages['general.notRequired']}
|
||||
{...omit(props, ['messages'])}
|
||||
help={required ? null : messages['general.notRequired']}
|
||||
required={required}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -22,11 +26,5 @@ module.exports = Component => {
|
|||
required: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
|
||||
};
|
||||
|
||||
InputComponent.defaultProps = {
|
||||
messages: {
|
||||
'general.notRequired': 'Not Required'
|
||||
}
|
||||
};
|
||||
|
||||
return InputComponent;
|
||||
};
|
||||
|
|
|
@ -8,12 +8,21 @@ const thumbnailUrl = require('../../lib/user-thumbnail');
|
|||
|
||||
require('./grid.scss');
|
||||
|
||||
const Grid = props => (
|
||||
<div className={classNames('grid', props.className)}>
|
||||
const Grid = ({
|
||||
className = '',
|
||||
itemType = 'projects',
|
||||
items = require('./grid.json'),
|
||||
showAvatar = false,
|
||||
showFavorites = false,
|
||||
showLoves = false,
|
||||
showRemixes = false,
|
||||
showViews = false
|
||||
}) => (
|
||||
<div className={classNames('grid', className)}>
|
||||
<FlexRow>
|
||||
{props.items.map((item, key) => {
|
||||
const href = `/${props.itemType}/${item.id}/`;
|
||||
if (props.itemType === 'projects') {
|
||||
{items.map((item, key) => {
|
||||
const href = `/${itemType}/${item.id}/`;
|
||||
if (itemType === 'projects') {
|
||||
return (
|
||||
<Thumbnail
|
||||
avatar={thumbnailUrl(item.author.id)}
|
||||
|
@ -23,11 +32,11 @@ const Grid = props => (
|
|||
key={key}
|
||||
loves={item.stats.loves}
|
||||
remixes={item.stats.remixes}
|
||||
showAvatar={props.showAvatar}
|
||||
showFavorites={props.showFavorites}
|
||||
showLoves={props.showLoves}
|
||||
showRemixes={props.showRemixes}
|
||||
showViews={props.showViews}
|
||||
showAvatar={showAvatar}
|
||||
showFavorites={showFavorites}
|
||||
showLoves={showLoves}
|
||||
showRemixes={showRemixes}
|
||||
showViews={showViews}
|
||||
src={item.image}
|
||||
title={item.title}
|
||||
type={'project'}
|
||||
|
@ -63,14 +72,4 @@ Grid.propTypes = {
|
|||
showViews: PropTypes.bool
|
||||
};
|
||||
|
||||
Grid.defaultProps = {
|
||||
items: require('./grid.json'),
|
||||
itemType: 'projects',
|
||||
showLoves: false,
|
||||
showFavorites: false,
|
||||
showRemixes: false,
|
||||
showViews: false,
|
||||
showAvatar: false
|
||||
};
|
||||
|
||||
module.exports = Grid;
|
||||
|
|
|
@ -3,12 +3,18 @@ const PropTypes = require('prop-types');
|
|||
const React = require('react');
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
|
||||
const HelpForm = props => {
|
||||
const HelpForm = ({
|
||||
body = '',
|
||||
subject = '',
|
||||
title = '',
|
||||
user = {username: ''}
|
||||
}) => {
|
||||
const prefix = 'https://mitscratch.freshdesk.com/widgets/feedback_widget/new?&widgetType=embedded&widgetView=yes&screenshot=No&searchArea=No';
|
||||
const title = `formTitle=${props.title}`;
|
||||
const username = `helpdesk_ticket[custom_field][cf_scratch_name_40167]=${props.user.username || ''}`;
|
||||
const formSubject = `helpdesk_ticket[subject]=${props.subject}`;
|
||||
const reportLink = `helpdesk_ticket[custom_field][cf_inappropriate_report_link_40167]=${props.body || ''}`;
|
||||
const formTitle = `formTitle=${title}`;
|
||||
const username = `helpdesk_ticket[custom_field][cf_scratch_name_40167]=${user.username || ''}`;
|
||||
const formSubject = `helpdesk_ticket[subject]=${subject}`;
|
||||
const reportLink = `helpdesk_ticket[custom_field][cf_inappropriate_report_link_40167]=${body || ''}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<script
|
||||
|
@ -29,7 +35,7 @@ const HelpForm = props => {
|
|||
height="644px"
|
||||
id="freshwidget-embedded-form"
|
||||
scrolling="no"
|
||||
src={`${prefix}&${title}&${username}&${formSubject}&${reportLink}`}
|
||||
src={`${prefix}&${formTitle}&${username}&${formSubject}&${reportLink}`}
|
||||
title={<FormattedMessage id="contactUs.questionsForum" />}
|
||||
width="100%"
|
||||
/>
|
||||
|
@ -49,13 +55,6 @@ HelpForm.propTypes = {
|
|||
})
|
||||
};
|
||||
|
||||
HelpForm.defaultProps = {
|
||||
body: '',
|
||||
subject: '',
|
||||
title: '',
|
||||
user: {username: ''}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: state.session.session.user
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ const JoinFlowStep = ({
|
|||
onSubmit,
|
||||
title,
|
||||
titleClassName,
|
||||
waiting
|
||||
waiting = false
|
||||
}) => (
|
||||
<form
|
||||
autoComplete="off"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const omit = require('lodash.omit');
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
|
@ -9,19 +8,24 @@ const ModalTitle = require('../modal/base/modal-title.jsx');
|
|||
|
||||
require('./next-step-button.scss');
|
||||
|
||||
const NextStepButton = props => (
|
||||
const NextStepButton = ({
|
||||
content,
|
||||
intl,
|
||||
waiting = false,
|
||||
...restProps
|
||||
} = {}) => (
|
||||
<button
|
||||
className="modal-flush-bottom-button"
|
||||
disabled={props.waiting}
|
||||
disabled={waiting}
|
||||
type="submit"
|
||||
{...omit(props, ['intl', 'text', 'waiting'])}
|
||||
{...restProps}
|
||||
>
|
||||
{props.waiting ? (
|
||||
{waiting ? (
|
||||
<Spinner className="next-step-spinner" />
|
||||
) : (
|
||||
<ModalTitle
|
||||
className="next-step-title"
|
||||
title={props.content ? props.content : props.intl.formatMessage({id: 'general.next'})}
|
||||
title={content ? content : intl.formatMessage({id: 'general.next'})}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
@ -33,8 +37,4 @@ NextStepButton.propTypes = {
|
|||
waiting: PropTypes.bool
|
||||
};
|
||||
|
||||
NextStepButton.defaultProps = {
|
||||
waiting: false
|
||||
};
|
||||
|
||||
module.exports = injectIntl(NextStepButton);
|
||||
|
|
|
@ -12,47 +12,48 @@ const ModalNavigation = ({
|
|||
onBackPage,
|
||||
nextButtonText,
|
||||
prevButtonText,
|
||||
nextButtonImageSrc,
|
||||
prevButtonImageSrc,
|
||||
nextButtonImageSrc = '/images/onboarding/right-arrow.svg',
|
||||
prevButtonImageSrc = '/images/onboarding/left-arrow.svg',
|
||||
className
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
new Image().src = nextButtonImageSrc;
|
||||
new Image().src = prevButtonImageSrc;
|
||||
}, []);
|
||||
}, [nextButtonImageSrc, prevButtonImageSrc]);
|
||||
|
||||
const dots = useMemo(() => {
|
||||
const dotsComponents = [];
|
||||
if (currentPage >= 0 && totalDots){
|
||||
for (let i = 0; i < totalDots; i++){
|
||||
dotsComponents.push(<div
|
||||
key={`dot page-${currentPage} ${i}`}
|
||||
className={`dot ${currentPage === i && 'active'}`}
|
||||
/>);
|
||||
if (currentPage >= 0 && totalDots) {
|
||||
for (let i = 0; i < totalDots; i++) {
|
||||
dotsComponents.push(
|
||||
<div
|
||||
key={`dot page-${currentPage} ${i}`}
|
||||
className={`dot ${currentPage === i && 'active'}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return dotsComponents;
|
||||
}, [currentPage, totalDots]);
|
||||
|
||||
|
||||
return (
|
||||
<div className={classNames('navigation', className)}>
|
||||
{
|
||||
<Button
|
||||
onClick={onBackPage}
|
||||
className={classNames('navigation-button', {
|
||||
hidden: !onBackPage,
|
||||
transparent: !prevButtonText
|
||||
})}
|
||||
>
|
||||
<img
|
||||
className="left-arrow"
|
||||
alt=""
|
||||
src={prevButtonImageSrc}
|
||||
/>
|
||||
<span className="navText">
|
||||
{prevButtonText}
|
||||
</span>
|
||||
</Button> }
|
||||
<Button
|
||||
onClick={onBackPage}
|
||||
className={classNames('navigation-button', {
|
||||
hidden: !onBackPage,
|
||||
transparent: !prevButtonText
|
||||
})}
|
||||
>
|
||||
<img
|
||||
className="left-arrow"
|
||||
alt=""
|
||||
src={prevButtonImageSrc}
|
||||
/>
|
||||
<span className="navText">
|
||||
{prevButtonText}
|
||||
</span>
|
||||
</Button>
|
||||
{(currentPage >= 0 && totalDots) &&
|
||||
<div className="dotRow">
|
||||
{dots}
|
||||
|
@ -76,6 +77,7 @@ const ModalNavigation = ({
|
|||
|
||||
);
|
||||
};
|
||||
|
||||
ModalNavigation.propTypes = {
|
||||
currentPage: PropTypes.number,
|
||||
totalDots: PropTypes.number,
|
||||
|
@ -88,9 +90,4 @@ ModalNavigation.propTypes = {
|
|||
className: PropTypes.string
|
||||
};
|
||||
|
||||
ModalNavigation.defaultProps = {
|
||||
nextButtonImageSrc: '/images/onboarding/right-arrow.svg',
|
||||
prevButtonImageSrc: '/images/onboarding/left-arrow.svg'
|
||||
};
|
||||
|
||||
export default ModalNavigation;
|
||||
|
|
|
@ -10,6 +10,8 @@ require('slick-carousel/slick/slick.scss');
|
|||
require('slick-carousel/slick/slick-theme.scss');
|
||||
require('./nestedcarousel.scss');
|
||||
|
||||
const defaultItems = require('./nestedcarousel.json');
|
||||
|
||||
|
||||
/*
|
||||
NestedCarousel is used to show a carousel, where each slide is composed of a few
|
||||
|
@ -18,8 +20,12 @@ require('./nestedcarousel.scss');
|
|||
|
||||
Each slide has a title, and then a list of thumbnails, that will be shown together.
|
||||
*/
|
||||
const NestedCarousel = props => {
|
||||
defaults(props.settings, {
|
||||
const NestedCarousel = ({
|
||||
className,
|
||||
items = defaultItems,
|
||||
settings = {}
|
||||
}) => {
|
||||
defaults(settings, {
|
||||
dots: true,
|
||||
infinite: false,
|
||||
lazyLoad: true,
|
||||
|
@ -27,36 +33,32 @@ const NestedCarousel = props => {
|
|||
slidesToScroll: 1,
|
||||
variableWidth: false
|
||||
});
|
||||
|
||||
const arrows = props.items.length > props.settings.slidesToShow;
|
||||
const arrows = items.length > settings.slidesToShow;
|
||||
const stages = [];
|
||||
|
||||
for (let i = 0; i < props.items.length; i++) {
|
||||
const items = props.items[i].thumbnails;
|
||||
const thumbnails = [];
|
||||
for (let j = 0; j < items.length; j++) {
|
||||
const item = items[j];
|
||||
thumbnails.push(
|
||||
<Thumbnail
|
||||
key={`inner_${i}_${j}`}
|
||||
linkTitle={false}
|
||||
src={item.thumbnailUrl}
|
||||
title={item.title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const thumbnails = items[i].thumbnails.map((item, j) => (
|
||||
<Thumbnail
|
||||
key={`inner_${i}_${j}`}
|
||||
linkTitle={false}
|
||||
src={item.thumbnailUrl}
|
||||
title={item.title}
|
||||
/>
|
||||
));
|
||||
|
||||
stages.push(
|
||||
<div key={`outer_${i}`}>
|
||||
<h3>{props.items[i].title}</h3>
|
||||
<h3>{items[i].title}</h3>
|
||||
{thumbnails}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Slider
|
||||
arrows={arrows}
|
||||
className={classNames('nestedcarousel', 'carousel', props.className)}
|
||||
{...props.settings}
|
||||
className={classNames('nestedcarousel', 'carousel', className)}
|
||||
{...settings}
|
||||
>
|
||||
{stages}
|
||||
</Slider>
|
||||
|
@ -76,9 +78,4 @@ NestedCarousel.propTypes = {
|
|||
})
|
||||
};
|
||||
|
||||
NestedCarousel.defaultProps = {
|
||||
settings: {},
|
||||
items: require('./nestedcarousel.json')
|
||||
};
|
||||
|
||||
module.exports = NestedCarousel;
|
||||
|
|
|
@ -5,15 +5,23 @@ const Box = require('../box/box.jsx');
|
|||
|
||||
require('./news.scss');
|
||||
|
||||
const News = props => (
|
||||
const defaultItems = require('./news.json');
|
||||
|
||||
const News = ({
|
||||
items = defaultItems,
|
||||
messages = {
|
||||
'general.viewAll': 'View All',
|
||||
'news.scratchNews': 'Scratch News'
|
||||
}
|
||||
}) => (
|
||||
<Box
|
||||
className="news"
|
||||
moreHref="/discuss/5/"
|
||||
moreTitle={props.messages['general.viewAll']}
|
||||
title={props.messages['news.scratchNews']}
|
||||
moreTitle={messages['general.viewAll']}
|
||||
title={messages['news.scratchNews']}
|
||||
>
|
||||
<ul>
|
||||
{props.items.map(item => (
|
||||
{items.map(item => (
|
||||
<li key={item.id}>
|
||||
<a href={item.url}>
|
||||
<img
|
||||
|
@ -42,12 +50,4 @@ News.propTypes = {
|
|||
})
|
||||
};
|
||||
|
||||
News.defaultProps = {
|
||||
items: require('./news.json'),
|
||||
messages: {
|
||||
'general.viewAll': 'View All',
|
||||
'news.scratchNews': 'Scratch News'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = News;
|
||||
|
|
|
@ -11,51 +11,62 @@ const OS_ENUM = require('../../lib/os-enum.js');
|
|||
|
||||
require('./os-chooser.scss');
|
||||
|
||||
const OSChooser = props => (
|
||||
const OSChooser = ({
|
||||
currentOS,
|
||||
handleSetOS,
|
||||
hideAndroid = false,
|
||||
hideChromeOS = false,
|
||||
hideMac = false,
|
||||
hideWindows = false
|
||||
}) => (
|
||||
<div className="os-chooser">
|
||||
<FlexRow className="inner">
|
||||
<FormattedMessage id="oschooser.choose" />
|
||||
{!props.hideWindows && (
|
||||
{!hideWindows && (
|
||||
<Button
|
||||
className={classNames({active: props.currentOS === OS_ENUM.WINDOWS})}
|
||||
onClick={() => // eslint-disable-line react/jsx-no-bind
|
||||
props.handleSetOS(OS_ENUM.WINDOWS)
|
||||
}
|
||||
className={classNames({active: currentOS === OS_ENUM.WINDOWS})}
|
||||
onClick={() => handleSetOS(OS_ENUM.WINDOWS)} // eslint-disable-line react/jsx-no-bind
|
||||
>
|
||||
<img src="/svgs/extensions/windows.svg" />
|
||||
<img
|
||||
src="/svgs/extensions/windows.svg"
|
||||
alt=""
|
||||
/>
|
||||
Windows
|
||||
</Button>
|
||||
)}
|
||||
{!props.hideMac && (
|
||||
{!hideMac && (
|
||||
<Button
|
||||
className={classNames({active: props.currentOS === OS_ENUM.MACOS})}
|
||||
onClick={() => // eslint-disable-line react/jsx-no-bind
|
||||
props.handleSetOS(OS_ENUM.MACOS)
|
||||
}
|
||||
className={classNames({active: currentOS === OS_ENUM.MACOS})}
|
||||
onClick={() => handleSetOS(OS_ENUM.MACOS)} // eslint-disable-line react/jsx-no-bind
|
||||
>
|
||||
<img src="/svgs/extensions/mac.svg" />
|
||||
<img
|
||||
src="/svgs/extensions/mac.svg"
|
||||
alt=""
|
||||
/>
|
||||
macOS
|
||||
</Button>
|
||||
)}
|
||||
{!props.hideChromeOS && (
|
||||
{!hideChromeOS && (
|
||||
<Button
|
||||
className={classNames({active: props.currentOS === OS_ENUM.CHROMEOS})}
|
||||
onClick={() => // eslint-disable-line react/jsx-no-bind
|
||||
props.handleSetOS(OS_ENUM.CHROMEOS)
|
||||
}
|
||||
className={classNames({active: currentOS === OS_ENUM.CHROMEOS})}
|
||||
onClick={() => handleSetOS(OS_ENUM.CHROMEOS)} // eslint-disable-line react/jsx-no-bind
|
||||
>
|
||||
<img src="/svgs/extensions/chromeos.svg" />
|
||||
<img
|
||||
src="/svgs/extensions/chromeos.svg"
|
||||
alt=""
|
||||
/>
|
||||
ChromeOS
|
||||
</Button>
|
||||
)}
|
||||
{!props.hideAndroid && (
|
||||
{!hideAndroid && (
|
||||
<Button
|
||||
className={classNames({active: props.currentOS === OS_ENUM.ANDROID})}
|
||||
onClick={() => // eslint-disable-line react/jsx-no-bind
|
||||
props.handleSetOS(OS_ENUM.ANDROID)
|
||||
}
|
||||
className={classNames({active: currentOS === OS_ENUM.ANDROID})}
|
||||
onClick={() => handleSetOS(OS_ENUM.ANDROID)} // eslint-disable-line react/jsx-no-bind
|
||||
>
|
||||
<img src="/svgs/extensions/android.svg" />
|
||||
<img
|
||||
src="/svgs/extensions/android.svg"
|
||||
alt=""
|
||||
/>
|
||||
Android
|
||||
</Button>
|
||||
)}
|
||||
|
@ -72,13 +83,6 @@ OSChooser.propTypes = {
|
|||
hideWindows: PropTypes.bool
|
||||
};
|
||||
|
||||
OSChooser.defaultProps = {
|
||||
hideAndroid: false,
|
||||
hideChromeOS: false,
|
||||
hideMac: false,
|
||||
hideWindows: false
|
||||
};
|
||||
|
||||
const wrappedOSChooser = injectIntl(OSChooser);
|
||||
|
||||
module.exports = wrappedOSChooser;
|
||||
|
|
|
@ -8,7 +8,11 @@ import overflowIcon from './overflow-icon.svg';
|
|||
|
||||
import './overflow-menu.scss';
|
||||
|
||||
const OverflowMenu = ({children, dropdownAs, className}) => {
|
||||
const OverflowMenu = ({
|
||||
children,
|
||||
dropdownAs = 'ul',
|
||||
className
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div className={classNames('overflow-menu-container', className)}>
|
||||
|
@ -38,8 +42,4 @@ OverflowMenu.propTypes = {
|
|||
className: PropTypes.string
|
||||
};
|
||||
|
||||
OverflowMenu.defaultProps = {
|
||||
dropdownAs: 'ul'
|
||||
};
|
||||
|
||||
export default OverflowMenu;
|
||||
|
|
|
@ -4,9 +4,12 @@ const Avatar = require('../../components/avatar/avatar.jsx');
|
|||
|
||||
require('./people-grid.scss');
|
||||
|
||||
const PeopleGrid = props => (
|
||||
const PeopleGrid = ({
|
||||
linkToNewTab = false,
|
||||
people
|
||||
}) => (
|
||||
<ul className="avatar-grid">
|
||||
{props.people.map((person, index) => (
|
||||
{people.map((person, index) => (
|
||||
<li
|
||||
className="avatar-item"
|
||||
key={`person-${index}`}
|
||||
|
@ -16,7 +19,7 @@ const PeopleGrid = props => (
|
|||
<a
|
||||
href={`https://scratch.mit.edu/users/${person.userName}/`}
|
||||
rel="noreferrer noopener"
|
||||
target={props.linkToNewTab ? '_blank' : '_self'}
|
||||
target={linkToNewTab ? '_blank' : '_self'}
|
||||
>
|
||||
<Avatar
|
||||
alt=""
|
||||
|
@ -27,7 +30,7 @@ const PeopleGrid = props => (
|
|||
/* if userName is not given, there's no chance userId is given */
|
||||
<Avatar
|
||||
alt=""
|
||||
src={`https://uploads.scratch.mit.edu/get_image/user/default_80x80.png`}
|
||||
src="https://uploads.scratch.mit.edu/get_image/user/default_80x80.png"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -48,8 +51,4 @@ PeopleGrid.propTypes = {
|
|||
}))
|
||||
};
|
||||
|
||||
PeopleGrid.defaultProps = {
|
||||
linkToNewTab: false
|
||||
};
|
||||
|
||||
module.exports = PeopleGrid;
|
||||
|
|
|
@ -2,18 +2,24 @@ const classNames = require('classnames');
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const Progression = props => {
|
||||
const Progression = ({
|
||||
children,
|
||||
className,
|
||||
step = 0,
|
||||
...restProps
|
||||
}) => {
|
||||
const childProps = {
|
||||
activeStep: props.step,
|
||||
totalSteps: React.Children.count(props.children)
|
||||
activeStep: step,
|
||||
totalSteps: React.Children.count(children)
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('progression', props.className)}
|
||||
{...props}
|
||||
className={classNames('progression', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{React.Children.map(props.children, (child, id) => {
|
||||
if (id === props.step) {
|
||||
{React.Children.map(children, (child, id) => {
|
||||
if (id === step) {
|
||||
return React.cloneElement(child, childProps);
|
||||
}
|
||||
})}
|
||||
|
@ -38,8 +44,4 @@ Progression.propTypes = {
|
|||
}
|
||||
};
|
||||
|
||||
Progression.defaultProps = {
|
||||
step: 0
|
||||
};
|
||||
|
||||
module.exports = Progression;
|
||||
|
|
|
@ -7,8 +7,18 @@ import {framelessDetailed} from '../../lib/frameless.js';
|
|||
import Carousel from '../carousel/carousel.jsx';
|
||||
import './projects-carousel.scss';
|
||||
|
||||
export const ProjectsCarousel = props => {
|
||||
defaults(props.settings, {
|
||||
export const ProjectsCarousel = ({
|
||||
className,
|
||||
items,
|
||||
settings = {},
|
||||
showLoves,
|
||||
showRemixes,
|
||||
title,
|
||||
type,
|
||||
useDetailedBreakpoints,
|
||||
...restProps
|
||||
}) => {
|
||||
defaults(settings, {
|
||||
slidesToShow: 5,
|
||||
arrows: true,
|
||||
slidesToScroll: 1,
|
||||
|
@ -48,15 +58,25 @@ export const ProjectsCarousel = props => {
|
|||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<div className={classNames('projects-carousel', props.className)}>
|
||||
<div className={classNames('projects-carousel', className)}>
|
||||
<div className="header">
|
||||
{props.title}
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
<Carousel {...props} />
|
||||
<Carousel
|
||||
className={className}
|
||||
items={items}
|
||||
settings={settings}
|
||||
showLoves={showLoves}
|
||||
showRemixes={showRemixes}
|
||||
title={title}
|
||||
type={type}
|
||||
useDetailedBreakpoints={useDetailedBreakpoints}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -81,7 +101,3 @@ ProjectsCarousel.propTypes = {
|
|||
type: PropTypes.string,
|
||||
useDetailedBreakpoints: PropTypes.bool
|
||||
};
|
||||
|
||||
ProjectsCarousel.defaultProps = {
|
||||
settings: {}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable react/no-multi-comp */
|
||||
const bindAll = require('lodash.bindall');
|
||||
const {injectIntl, FormattedMessage} = require('react-intl');
|
||||
const omit = require('lodash.omit');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
|
@ -48,16 +47,20 @@ const getCountryOptions = reactIntl => (
|
|||
]
|
||||
);
|
||||
|
||||
const NextStepButton = props => (
|
||||
const NextStepButton = ({
|
||||
waiting = false,
|
||||
text = 'Next Step',
|
||||
...restProps
|
||||
}) => (
|
||||
<Button
|
||||
className="card-button"
|
||||
disabled={props.waiting}
|
||||
disabled={waiting}
|
||||
type="submit"
|
||||
{...omit(props, ['text', 'waiting'])}
|
||||
{...restProps}
|
||||
>
|
||||
{props.waiting ?
|
||||
{waiting ?
|
||||
<Spinner /> :
|
||||
props.text
|
||||
text
|
||||
}
|
||||
</Button>
|
||||
);
|
||||
|
@ -67,11 +70,6 @@ NextStepButton.propTypes = {
|
|||
waiting: PropTypes.bool
|
||||
};
|
||||
|
||||
NextStepButton.defaultProps = {
|
||||
waiting: false,
|
||||
text: 'Next Step'
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* USERNAME STEP
|
||||
|
@ -626,7 +624,13 @@ const IntlDemographicsStep = injectIntl(DemographicsStep);
|
|||
/*
|
||||
* NAME STEP
|
||||
*/
|
||||
const NameStep = props => (
|
||||
const NameStep = ({
|
||||
activeStep,
|
||||
intl,
|
||||
onNextStep,
|
||||
totalSteps,
|
||||
waiting = false
|
||||
}) => (
|
||||
<Slide className="registration-step name-step">
|
||||
<h2>
|
||||
<FormattedMessage id="teacherRegistration.nameStepTitleNew" />
|
||||
|
@ -634,25 +638,19 @@ const NameStep = props => (
|
|||
<p className="description">
|
||||
<FormattedMessage id="teacherRegistration.nameStepDescription" />
|
||||
<Tooltip
|
||||
tipContent={
|
||||
props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
||||
}
|
||||
tipContent={intl.formatMessage({id: 'registration.nameStepTooltip'})}
|
||||
title={'?'}
|
||||
/>
|
||||
</p>
|
||||
<Card>
|
||||
<Form onValidSubmit={props.onNextStep}>
|
||||
<Form onValidSubmit={onNextStep}>
|
||||
<Input
|
||||
required
|
||||
label={
|
||||
props.intl.formatMessage({id: 'teacherRegistration.firstName'})
|
||||
}
|
||||
label={intl.formatMessage({id: 'teacherRegistration.firstName'})}
|
||||
name="user.name.first"
|
||||
type="text"
|
||||
validationErrors={{
|
||||
maxLength: props.intl.formatMessage({
|
||||
id: 'registration.validationMaxLength'
|
||||
})
|
||||
maxLength: intl.formatMessage({id: 'registration.validationMaxLength'})
|
||||
}}
|
||||
validations={{
|
||||
maxLength: 50
|
||||
|
@ -660,15 +658,11 @@ const NameStep = props => (
|
|||
/>
|
||||
<Input
|
||||
required
|
||||
label={
|
||||
props.intl.formatMessage({id: 'teacherRegistration.lastName'})
|
||||
}
|
||||
label={intl.formatMessage({id: 'teacherRegistration.lastName'})}
|
||||
name="user.name.last"
|
||||
type="text"
|
||||
validationErrors={{
|
||||
maxLength: props.intl.formatMessage({
|
||||
id: 'registration.validationMaxLength'
|
||||
})
|
||||
maxLength: intl.formatMessage({id: 'registration.validationMaxLength'})
|
||||
}}
|
||||
validations={{
|
||||
maxLength: 50
|
||||
|
@ -676,13 +670,13 @@ const NameStep = props => (
|
|||
/>
|
||||
<NextStepButton
|
||||
text={<FormattedMessage id="registration.nextStep" />}
|
||||
waiting={props.waiting}
|
||||
waiting={waiting}
|
||||
/>
|
||||
</Form>
|
||||
</Card>
|
||||
<StepNavigation
|
||||
active={props.activeStep}
|
||||
steps={props.totalSteps - 1}
|
||||
active={activeStep}
|
||||
steps={totalSteps - 1}
|
||||
/>
|
||||
</Slide>
|
||||
);
|
||||
|
@ -695,10 +689,6 @@ NameStep.propTypes = {
|
|||
waiting: PropTypes.bool
|
||||
};
|
||||
|
||||
NameStep.defaultProps = {
|
||||
waiting: false
|
||||
};
|
||||
|
||||
const IntlNameStep = injectIntl(NameStep);
|
||||
|
||||
|
||||
|
@ -1297,7 +1287,11 @@ const Link = chunks => <a href={chunks}>{chunks}</a>;
|
|||
/*
|
||||
* TEACHER APPROVAL STEP
|
||||
*/
|
||||
const TeacherApprovalStep = props => (
|
||||
const TeacherApprovalStep = ({
|
||||
confirmed = false,
|
||||
email = null,
|
||||
invited = false
|
||||
}) => (
|
||||
<Slide className="registration-step last-step">
|
||||
<h2>
|
||||
<FormattedMessage id="registration.lastStepTitle" />
|
||||
|
@ -1305,7 +1299,7 @@ const TeacherApprovalStep = props => (
|
|||
<p className="description">
|
||||
<FormattedMessage id="registration.lastStepDescription" />
|
||||
</p>
|
||||
{props.confirmed || !props.email ?
|
||||
{confirmed || !email ?
|
||||
[] : (
|
||||
<Card className="confirm">
|
||||
<h4><FormattedMessage id="registration.confirmYourEmail" /></h4>
|
||||
|
@ -1327,11 +1321,11 @@ const TeacherApprovalStep = props => (
|
|||
<FormattedMessage
|
||||
id="teacherRegistration.confirmationEmail"
|
||||
/>
|
||||
<strong>{props.email}</strong></p>
|
||||
<strong>{email}</strong></p>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
{props.invited ?
|
||||
{invited ?
|
||||
<Card className="wait">
|
||||
<h4><FormattedMessage id="registration.waitForApproval" /></h4>
|
||||
<p>
|
||||
|
@ -1366,12 +1360,6 @@ TeacherApprovalStep.propTypes = {
|
|||
invited: PropTypes.bool
|
||||
};
|
||||
|
||||
TeacherApprovalStep.defaultProps = {
|
||||
confirmed: false,
|
||||
email: null,
|
||||
invited: false
|
||||
};
|
||||
|
||||
const IntlTeacherApprovalStep = injectIntl(TeacherApprovalStep);
|
||||
|
||||
|
||||
|
|
|
@ -7,27 +7,35 @@ const RelativeTime = require('../relative-time/relative-time.jsx');
|
|||
|
||||
require('./social-message.scss');
|
||||
|
||||
const SocialMessage = props => (
|
||||
<props.as className={classNames('social-message', props.className)}>
|
||||
const SocialMessage = ({
|
||||
as: Tag = 'li',
|
||||
children,
|
||||
className,
|
||||
datetime,
|
||||
iconAlt,
|
||||
iconSrc,
|
||||
imgClassName
|
||||
}) => (
|
||||
<Tag className={classNames('social-message', className)}>
|
||||
<FlexRow className="mod-social-message">
|
||||
<div className="social-message-content">
|
||||
{typeof props.iconSrc === 'undefined' ? [] : [
|
||||
{typeof iconSrc === 'undefined' ? [] : [
|
||||
<img
|
||||
alt={props.iconAlt}
|
||||
className={classNames('social-message-icon', props.imgClassName)}
|
||||
alt={iconAlt}
|
||||
className={classNames('social-message-icon', imgClassName)}
|
||||
key="social-message-icon"
|
||||
src={props.iconSrc}
|
||||
src={iconSrc}
|
||||
/>
|
||||
]}
|
||||
<div>
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<span className="social-message-date">
|
||||
<RelativeTime value={new Date(props.datetime)} />
|
||||
<RelativeTime value={new Date(datetime)} />
|
||||
</span>
|
||||
</FlexRow>
|
||||
</props.as>
|
||||
</Tag>
|
||||
);
|
||||
|
||||
SocialMessage.propTypes = {
|
||||
|
@ -40,8 +48,4 @@ SocialMessage.propTypes = {
|
|||
imgClassName: PropTypes.string
|
||||
};
|
||||
|
||||
SocialMessage.defaultProps = {
|
||||
as: 'li'
|
||||
};
|
||||
|
||||
module.exports = SocialMessage;
|
||||
|
|
|
@ -5,10 +5,7 @@ const classNames = require('classnames');
|
|||
require('./spinner.scss');
|
||||
|
||||
// Adapted from http://tobiasahlin.com/spinkit/
|
||||
const Spinner = ({
|
||||
className,
|
||||
color
|
||||
}) => (
|
||||
const Spinner = ({className, color = 'white'}) => (
|
||||
<img
|
||||
alt="loading animation"
|
||||
className={classNames('studio-status-icon-spinner', className)}
|
||||
|
@ -16,10 +13,6 @@ const Spinner = ({
|
|||
/>
|
||||
);
|
||||
|
||||
Spinner.defaultProps = {
|
||||
color: 'white'
|
||||
};
|
||||
|
||||
Spinner.propTypes = {
|
||||
className: PropTypes.string,
|
||||
color: PropTypes.oneOf(['white', 'blue', 'transparent-gray'])
|
||||
|
|
|
@ -8,21 +8,21 @@ require('./subnavigation.scss');
|
|||
* Container for a custom, horizontal list of navigation elements
|
||||
* that can be displayed within a view or component.
|
||||
*/
|
||||
const SubNavigation = props => (
|
||||
const SubNavigation = ({align = 'middle', children, className, role}) => (
|
||||
<div
|
||||
className={classNames(
|
||||
[
|
||||
'sub-nav',
|
||||
props.className
|
||||
className
|
||||
],
|
||||
{
|
||||
'sub-nav-align-left': props.align === 'left',
|
||||
'sub-nav-align-right': props.align === 'right'
|
||||
'sub-nav-align-left': align === 'left',
|
||||
'sub-nav-align-right': align === 'right'
|
||||
}
|
||||
)}
|
||||
role={props.role}
|
||||
role={role}
|
||||
>
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -33,8 +33,4 @@ SubNavigation.propTypes = {
|
|||
className: PropTypes.string
|
||||
};
|
||||
|
||||
SubNavigation.defaultProps = {
|
||||
align: 'middle'
|
||||
};
|
||||
|
||||
module.exports = SubNavigation;
|
||||
|
|
|
@ -11,34 +11,45 @@ const FlexRow = require('../flex-row/flex-row.jsx');
|
|||
|
||||
require('./teacher-banner.scss');
|
||||
|
||||
const TeacherBanner = props => (
|
||||
<TitleBanner className={classNames('teacher-banner', props.className)}>
|
||||
const TeacherBanner = ({
|
||||
className,
|
||||
messages = {
|
||||
'teacherbanner.greeting': 'Hi',
|
||||
'teacherbanner.subgreeting': 'Teacher Account',
|
||||
'teacherbanner.classesButton': 'My Classes',
|
||||
'teacherbanner.resourcesButton': 'Educator Resources',
|
||||
'teacherbanner.faqButton': 'Teacher Account FAQ'
|
||||
},
|
||||
sessionStatus,
|
||||
user = {}
|
||||
}) => (
|
||||
<TitleBanner className={classNames('teacher-banner', className)}>
|
||||
<FlexRow className="inner">
|
||||
<div className="welcome">
|
||||
{props.sessionStatus === sessionActions.Status.FETCHED ? (
|
||||
props.user ? [
|
||||
{sessionStatus === sessionActions.Status.FETCHED ? (
|
||||
user ? [
|
||||
<h3 key="greeting">
|
||||
{props.messages['teacherbanner.greeting']},{' '}
|
||||
{props.user.username}
|
||||
{messages['teacherbanner.greeting']},{' '}
|
||||
{user.username}
|
||||
</h3>,
|
||||
<p
|
||||
className="title-banner-p"
|
||||
key="subgreeting"
|
||||
>
|
||||
{props.messages['teacherbanner.subgreeting']}
|
||||
{messages['teacherbanner.subgreeting']}
|
||||
</p>
|
||||
] : []
|
||||
) : []}
|
||||
</div>
|
||||
<FlexRow className="quick-links">
|
||||
{props.sessionStatus === sessionActions.Status.FETCHED ? (
|
||||
props.user ? [
|
||||
{sessionStatus === sessionActions.Status.FETCHED ? (
|
||||
user ? [
|
||||
<a
|
||||
href="/educators/classes"
|
||||
key="classes-button"
|
||||
>
|
||||
<Button>
|
||||
{props.messages['teacherbanner.classesButton']}
|
||||
{messages['teacherbanner.classesButton']}
|
||||
</Button>
|
||||
</a>,
|
||||
<a
|
||||
|
@ -46,7 +57,7 @@ const TeacherBanner = props => (
|
|||
key="resources-button"
|
||||
>
|
||||
<Button>
|
||||
{props.messages['teacherbanner.resourcesButton']}
|
||||
{messages['teacherbanner.resourcesButton']}
|
||||
</Button>
|
||||
</a>,
|
||||
<a
|
||||
|
@ -54,7 +65,7 @@ const TeacherBanner = props => (
|
|||
key="faq-button"
|
||||
>
|
||||
<Button>
|
||||
{props.messages['teacherbanner.faqButton']}
|
||||
{messages['teacherbanner.faqButton']}
|
||||
</Button>
|
||||
</a>
|
||||
] : []
|
||||
|
@ -79,20 +90,9 @@ TeacherBanner.propTypes = {
|
|||
})
|
||||
};
|
||||
|
||||
TeacherBanner.defaultProps = {
|
||||
messages: {
|
||||
'teacherbanner.greeting': 'Hi',
|
||||
'teacherbanner.subgreeting': 'Teacher Account',
|
||||
'teacherbanner.classesButton': 'My Classes',
|
||||
'teacherbanner.resourcesButton': 'Educator Resources',
|
||||
'teacherbanner.faqButton': 'Teacher Account FAQ'
|
||||
},
|
||||
user: {}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
sessionStatus: state.session.status,
|
||||
user: state.session.session.user
|
||||
user: state.session.session.user || {}
|
||||
});
|
||||
|
||||
const ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
|
||||
|
|
|
@ -4,51 +4,70 @@ const React = require('react');
|
|||
|
||||
require('./thumbnail.scss');
|
||||
|
||||
const Thumbnail = props => {
|
||||
const Thumbnail = ({
|
||||
alt = '',
|
||||
avatar = '',
|
||||
className,
|
||||
creator,
|
||||
favorites,
|
||||
href = '#',
|
||||
linkTitle = true,
|
||||
loves,
|
||||
remixes,
|
||||
showAvatar = false,
|
||||
showFavorites = false,
|
||||
showLoves = false,
|
||||
showRemixes = false,
|
||||
showViews = false,
|
||||
src = '',
|
||||
title = 'Project',
|
||||
type = 'project',
|
||||
views
|
||||
}) => {
|
||||
const extra = [];
|
||||
const info = [];
|
||||
|
||||
if (props.loves && props.showLoves) {
|
||||
if (loves && showLoves) {
|
||||
extra.push(
|
||||
<div
|
||||
className="thumbnail-loves"
|
||||
key="loves"
|
||||
title={`${props.loves} loves`}
|
||||
title={`${loves} loves`}
|
||||
>
|
||||
{props.loves}
|
||||
{loves}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (props.favorites && props.showFavorites) {
|
||||
if (favorites && showFavorites) {
|
||||
extra.push(
|
||||
<div
|
||||
className="thumbnail-favorites"
|
||||
key="favorites"
|
||||
title={`${props.favorites} favorites`}
|
||||
title={`${favorites} favorites`}
|
||||
>
|
||||
{props.favorites}
|
||||
{favorites}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (props.remixes && props.showRemixes) {
|
||||
if (remixes && showRemixes) {
|
||||
extra.push(
|
||||
<div
|
||||
className="thumbnail-remixes"
|
||||
key="remixes"
|
||||
title={`${props.remixes} remixes`}
|
||||
title={`${remixes} remixes`}
|
||||
>
|
||||
{props.remixes}
|
||||
{remixes}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (props.views && props.showViews) {
|
||||
if (views && showViews) {
|
||||
extra.push(
|
||||
<div
|
||||
className="thumbnail-views"
|
||||
key="views"
|
||||
title={`${props.views} views`}
|
||||
title={`${views} views`}
|
||||
>
|
||||
{props.views}
|
||||
{views}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -57,65 +76,66 @@ const Thumbnail = props => {
|
|||
let titleElement;
|
||||
let avatarElement;
|
||||
|
||||
if (props.linkTitle) {
|
||||
if (linkTitle) {
|
||||
imgElement = (
|
||||
<a
|
||||
className="thumbnail-image"
|
||||
href={props.href}
|
||||
href={href}
|
||||
key="imgElement"
|
||||
>
|
||||
<img
|
||||
alt={props.alt}
|
||||
src={props.src}
|
||||
alt={alt}
|
||||
src={src}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
titleElement = (
|
||||
<a
|
||||
href={props.href}
|
||||
href={href}
|
||||
key="titleElement"
|
||||
title={props.title}
|
||||
title={title}
|
||||
>
|
||||
{props.title}
|
||||
{title}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
imgElement = <img src={props.src} />;
|
||||
titleElement = props.title;
|
||||
imgElement = <img src={src} />;
|
||||
titleElement = title;
|
||||
}
|
||||
|
||||
info.push(titleElement);
|
||||
|
||||
if (props.creator) {
|
||||
if (creator) {
|
||||
info.push(
|
||||
<div
|
||||
className="thumbnail-creator"
|
||||
key="creator"
|
||||
>
|
||||
<a href={`/users/${props.creator}/`}>{props.creator}</a>
|
||||
<a href={`/users/${creator}/`}>{creator}</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.avatar && props.showAvatar) {
|
||||
if (avatar && showAvatar) {
|
||||
avatarElement = (
|
||||
<a
|
||||
className="creator-image"
|
||||
href={`/users/${props.creator}/`}
|
||||
href={`/users/${creator}/`}
|
||||
>
|
||||
<img
|
||||
alt={props.creator}
|
||||
src={props.avatar}
|
||||
alt={creator}
|
||||
src={avatar}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'thumbnail',
|
||||
props.type,
|
||||
props.className
|
||||
type,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{imgElement}
|
||||
|
@ -151,19 +171,4 @@ Thumbnail.propTypes = {
|
|||
views: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||
};
|
||||
|
||||
Thumbnail.defaultProps = {
|
||||
alt: '',
|
||||
avatar: '',
|
||||
href: '#',
|
||||
linkTitle: true,
|
||||
showAvatar: false,
|
||||
showFavorites: false,
|
||||
showLoves: false,
|
||||
showRemixes: false,
|
||||
showViews: false,
|
||||
src: '',
|
||||
title: 'Project',
|
||||
type: 'project'
|
||||
};
|
||||
|
||||
module.exports = Thumbnail;
|
||||
|
|
|
@ -8,11 +8,20 @@ const thumbnailUrl = require('../../lib/user-thumbnail');
|
|||
|
||||
require('./thumbnailcolumn.scss');
|
||||
|
||||
const ThumbnailColumn = props => (
|
||||
<FlexRow className={classNames('thumbnail-column', props.className)}>
|
||||
{props.items.map((item, key) => {
|
||||
const href = `/${props.itemType}/${item.id}/`;
|
||||
if (props.itemType === 'projects') {
|
||||
const ThumbnailColumn = ({
|
||||
className,
|
||||
itemType = 'projects',
|
||||
items,
|
||||
showAvatar = false,
|
||||
showFavorites = false,
|
||||
showLoves = false,
|
||||
showRemixes = false,
|
||||
showViews = false
|
||||
}) => (
|
||||
<FlexRow className={classNames('thumbnail-column', className)}>
|
||||
{items.map((item, key) => {
|
||||
const href = `/${itemType}/${item.id}/`;
|
||||
if (itemType === 'projects') {
|
||||
return (
|
||||
<Thumbnail
|
||||
avatar={thumbnailUrl(item.author.id)}
|
||||
|
@ -22,11 +31,11 @@ const ThumbnailColumn = props => (
|
|||
key={key}
|
||||
loves={item.stats.loves}
|
||||
remixes={item.stats.remixes}
|
||||
showAvatar={props.showAvatar}
|
||||
showFavorites={props.showFavorites}
|
||||
showLoves={props.showLoves}
|
||||
showRemixes={props.showRemixes}
|
||||
showViews={props.showViews}
|
||||
showAvatar={showAvatar}
|
||||
showFavorites={showFavorites}
|
||||
showLoves={showLoves}
|
||||
showRemixes={showRemixes}
|
||||
showViews={showViews}
|
||||
src={item.image}
|
||||
title={item.title}
|
||||
type={'project'}
|
||||
|
@ -59,13 +68,4 @@ ThumbnailColumn.propTypes = {
|
|||
showViews: PropTypes.bool
|
||||
};
|
||||
|
||||
ThumbnailColumn.defaultProps = {
|
||||
itemType: 'projects',
|
||||
showLoves: false,
|
||||
showFavorites: false,
|
||||
showRemixes: false,
|
||||
showViews: false,
|
||||
showAvatar: false
|
||||
};
|
||||
|
||||
module.exports = ThumbnailColumn;
|
||||
|
|
|
@ -4,12 +4,17 @@ const React = require('react');
|
|||
|
||||
require('./tooltip.scss');
|
||||
|
||||
const Tooltip = props => (
|
||||
const Tooltip = ({
|
||||
className,
|
||||
currentCharacters,
|
||||
maxCharacters,
|
||||
tipContent = ''
|
||||
}) => (
|
||||
<span
|
||||
className={classNames(
|
||||
'tooltip',
|
||||
props.className,
|
||||
{overmax: (props.currentCharacters > props.maxCharacters)}
|
||||
className,
|
||||
{overmax: currentCharacters > maxCharacters}
|
||||
)}
|
||||
>
|
||||
<span className="tip">
|
||||
|
@ -19,7 +24,7 @@ const Tooltip = props => (
|
|||
/>
|
||||
</span>
|
||||
<span className="expand">
|
||||
{props.tipContent}
|
||||
{tipContent}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
@ -31,9 +36,4 @@ Tooltip.propTypes = {
|
|||
tipContent: PropTypes.node
|
||||
};
|
||||
|
||||
Tooltip.defaultProps = {
|
||||
title: '',
|
||||
tipContent: ''
|
||||
};
|
||||
|
||||
module.exports = Tooltip;
|
||||
|
|
|
@ -6,17 +6,30 @@ const Box = require('../box/box.jsx');
|
|||
|
||||
require('./welcome.scss');
|
||||
|
||||
const Welcome = props => (
|
||||
const Welcome = ({
|
||||
messages = {
|
||||
'welcome.welcomeToScratch': 'Welcome to Scratch!',
|
||||
'welcome.explore': 'Explore Starter Projects',
|
||||
'welcome.exploreAlt': 'Starter Projects',
|
||||
'welcome.community': 'Learn about the community',
|
||||
'welcome.communityAlt': 'Community Guidelines',
|
||||
'welcome.create': 'Create a Project',
|
||||
'welcome.createAlt': 'Get Started'
|
||||
},
|
||||
onDismiss,
|
||||
permissions,
|
||||
user
|
||||
}) => (
|
||||
<Box
|
||||
className="welcome"
|
||||
moreHref="#"
|
||||
moreProps={{
|
||||
className: 'close',
|
||||
title: 'Dismiss',
|
||||
onClick: props.onDismiss
|
||||
onClick: onDismiss
|
||||
}}
|
||||
moreTitle="x"
|
||||
title={props.messages['welcome.welcomeToScratch']}
|
||||
title={messages['welcome.welcomeToScratch']}
|
||||
>
|
||||
<div className="welcome-options">
|
||||
<a
|
||||
|
@ -24,9 +37,9 @@ const Welcome = props => (
|
|||
className="welcome-option-button"
|
||||
href="/starter-projects"
|
||||
>
|
||||
{props.messages['welcome.explore']}
|
||||
{messages['welcome.explore']}
|
||||
<img
|
||||
alt={props.messages['welcome.exploreAlt']}
|
||||
alt={messages['welcome.exploreAlt']}
|
||||
src="/images/explore_starter_projects.svg"
|
||||
/>
|
||||
</a>
|
||||
|
@ -35,9 +48,9 @@ const Welcome = props => (
|
|||
className="welcome-option-button"
|
||||
href="/community_guidelines"
|
||||
>
|
||||
{props.messages['welcome.community']}
|
||||
{messages['welcome.community']}
|
||||
<img
|
||||
alt={props.messages['welcome.communityAlt']}
|
||||
alt={messages['welcome.communityAlt']}
|
||||
src="/images/learn_about_the_community.svg"
|
||||
/>
|
||||
</a>
|
||||
|
@ -45,14 +58,14 @@ const Welcome = props => (
|
|||
id="welcome.create"
|
||||
className="welcome-option-button"
|
||||
href={
|
||||
shouldDisplayOnboarding(props.user, props.permissions) ?
|
||||
shouldDisplayOnboarding(user, permissions) ?
|
||||
'/projects/editor/' :
|
||||
'/projects/editor/?tutorial=getStarted'
|
||||
}
|
||||
>
|
||||
{props.messages['welcome.create']}
|
||||
{messages['welcome.create']}
|
||||
<img
|
||||
alt={props.messages['welcome.createAlt']}
|
||||
alt={messages['welcome.createAlt']}
|
||||
src="/images/create_a_project.svg"
|
||||
/>
|
||||
</a>
|
||||
|
@ -75,16 +88,4 @@ Welcome.propTypes = {
|
|||
user: PropTypes.object
|
||||
};
|
||||
|
||||
Welcome.defaultProps = {
|
||||
messages: {
|
||||
'welcome.welcomeToScratch': 'Welcome to Scratch!',
|
||||
'welcome.explore': 'Explore Starter Projects',
|
||||
'welcome.exploreAlt': 'Starter Projects',
|
||||
'welcome.community': 'Learn about the community',
|
||||
'welcome.communityAlt': 'Community Guidelines',
|
||||
'welcome.create': 'Create a Project',
|
||||
'welcome.createAlt': 'Get Started'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Welcome;
|
||||
|
|
|
@ -6,7 +6,7 @@ const classNames = require('classnames');
|
|||
|
||||
import './youtube-video-modal.scss';
|
||||
|
||||
export const YoutubeVideoModal = ({videoId, onClose = () => {}, className}) => {
|
||||
export const YoutubeVideoModal = ({videoId, onClose = () => {}, className = 'mint-green'}) => {
|
||||
if (!videoId) return null;
|
||||
return (
|
||||
<ReactModal
|
||||
|
@ -37,12 +37,10 @@ export const YoutubeVideoModal = ({videoId, onClose = () => {}, className}) => {
|
|||
);
|
||||
};
|
||||
|
||||
YoutubeVideoModal.defaultProps = {
|
||||
className: 'mint-green'
|
||||
};
|
||||
|
||||
YoutubeVideoModal.propTypes = {
|
||||
videoId: PropTypes.string,
|
||||
onClose: PropTypes.func,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
export default YoutubeVideoModal;
|
||||
|
|
|
@ -251,12 +251,24 @@ SocialMessagesList.defaultProps = {
|
|||
numNewMessages: 0
|
||||
};
|
||||
|
||||
const MessagesPresentation = props => {
|
||||
let adminMessageLength = props.adminMessages.length;
|
||||
if (Object.keys(props.scratcherInvite).length > 0) {
|
||||
export const MessagesPresentation = ({
|
||||
adminMessages,
|
||||
filter = '',
|
||||
intl,
|
||||
loadMore,
|
||||
messages,
|
||||
numNewMessages = 0,
|
||||
onAdminDismiss,
|
||||
onFilterClick,
|
||||
onLoadMoreMethod,
|
||||
requestStatus,
|
||||
scratcherInvite
|
||||
}) => {
|
||||
let adminMessageLength = adminMessages.length;
|
||||
if (Object.keys(scratcherInvite).length > 0) {
|
||||
adminMessageLength = adminMessageLength + 1;
|
||||
}
|
||||
let numNewSocialMessages = props.numNewMessages - adminMessageLength;
|
||||
let numNewSocialMessages = numNewMessages - adminMessageLength;
|
||||
if (numNewSocialMessages < 0) {
|
||||
numNewSocialMessages = 0;
|
||||
}
|
||||
|
@ -271,39 +283,24 @@ const MessagesPresentation = props => {
|
|||
<div className="messages-title-filter">
|
||||
<Form>
|
||||
<Select
|
||||
label={props.intl.formatMessage({id: 'messages.filterBy'})}
|
||||
label={intl.formatMessage({id: 'messages.filterBy'})}
|
||||
name="messages.filter"
|
||||
options={[
|
||||
{
|
||||
label: props.intl.formatMessage({id: 'messages.activityAll'}),
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
label: props.intl.formatMessage({id: 'messages.activityComments'}),
|
||||
value: 'comments'
|
||||
},
|
||||
{
|
||||
label: props.intl.formatMessage({id: 'messages.activityProjects'}),
|
||||
value: 'projects'
|
||||
},
|
||||
{
|
||||
label: props.intl.formatMessage({id: 'messages.activityStudios'}),
|
||||
value: 'studios'
|
||||
},
|
||||
{
|
||||
label: props.intl.formatMessage({id: 'messages.activityForums'}),
|
||||
value: 'forums'
|
||||
}
|
||||
{label: intl.formatMessage({id: 'messages.activityAll'}), value: ''},
|
||||
{label: intl.formatMessage({id: 'messages.activityComments'}), value: 'comments'},
|
||||
{label: intl.formatMessage({id: 'messages.activityProjects'}), value: 'projects'},
|
||||
{label: intl.formatMessage({id: 'messages.activityStudios'}), value: 'studios'},
|
||||
{label: intl.formatMessage({id: 'messages.activityForums'}), value: 'forums'}
|
||||
]}
|
||||
value={props.filter}
|
||||
onChange={props.onFilterClick}
|
||||
value={filter}
|
||||
onChange={onFilterClick}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</FlexRow>
|
||||
</TitleBanner>
|
||||
<div className="messages-details inner">
|
||||
{props.adminMessages.length > 0 || Object.keys(props.scratcherInvite).length > 0 ? [
|
||||
{adminMessages.length > 0 || Object.keys(scratcherInvite).length > 0 ? [
|
||||
<section
|
||||
className="messages-admin"
|
||||
key="messages-admin"
|
||||
|
@ -317,31 +314,31 @@ const MessagesPresentation = props => {
|
|||
</h4>
|
||||
</div>
|
||||
<ul className="messages-admin-list">
|
||||
{Object.keys(props.scratcherInvite).length > 0 ? [
|
||||
{Object.keys(scratcherInvite).length > 0 ? [
|
||||
<ScratcherInvite
|
||||
datetimeCreated={props.scratcherInvite.datetime_created}
|
||||
id={props.scratcherInvite.id}
|
||||
key={`invite${props.scratcherInvite.id}`}
|
||||
datetimeCreated={scratcherInvite.datetime_created}
|
||||
id={scratcherInvite.id}
|
||||
key={`invite${scratcherInvite.id}`}
|
||||
onDismiss={() => { // eslint-disable-line react/jsx-no-bind
|
||||
props.onAdminDismiss('invite', props.scratcherInvite.id);
|
||||
onAdminDismiss('invite', scratcherInvite.id);
|
||||
}}
|
||||
/>
|
||||
] : []}
|
||||
{props.adminMessages.map(item => (
|
||||
{adminMessages.map(item => (
|
||||
<AdminMessage
|
||||
datetimeCreated={item.datetime_created}
|
||||
id={item.id}
|
||||
key={`adminmessage${item.id}`}
|
||||
message={item.message}
|
||||
onDismiss={() => { // eslint-disable-line react/jsx-no-bind
|
||||
props.onAdminDismiss('notification', item.id);
|
||||
onAdminDismiss('notification', item.id);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
] : []}
|
||||
{props.requestStatus.admin === messageStatuses.ADMIN_ERROR ? [
|
||||
{requestStatus.admin === messageStatuses.ADMIN_ERROR ? [
|
||||
<section
|
||||
className="messages-admin"
|
||||
key="messages-admin-error"
|
||||
|
@ -353,11 +350,11 @@ const MessagesPresentation = props => {
|
|||
</section>
|
||||
] : []}
|
||||
<SocialMessagesList
|
||||
loadMore={props.loadMore}
|
||||
loadStatus={props.requestStatus.message}
|
||||
messages={props.messages}
|
||||
loadMore={loadMore}
|
||||
loadStatus={requestStatus.message}
|
||||
messages={messages}
|
||||
numNewMessages={numNewSocialMessages}
|
||||
onLoadMoreMethod={props.onLoadMoreMethod}
|
||||
onLoadMoreMethod={onLoadMoreMethod}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -380,23 +377,7 @@ MessagesPresentation.propTypes = {
|
|||
message: PropTypes.string,
|
||||
delete: PropTypes.string
|
||||
}).isRequired,
|
||||
scratcherInvite: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
banned: PropTypes.bool,
|
||||
vpn_required: PropTypes.bool,
|
||||
token: PropTypes.string,
|
||||
thumbnailUrl: PropTypes.string,
|
||||
dateJoined: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
classroomId: PropTypes.string
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
MessagesPresentation.defaultProps = {
|
||||
filter: '',
|
||||
filterOpen: false,
|
||||
numNewMessages: 0
|
||||
scratcherInvite: PropTypes.object.isRequired // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
|
||||
module.exports = injectIntl(MessagesPresentation);
|
||||
|
|
|
@ -10,7 +10,7 @@ import {formatRelativeTime} from '../../lib/format-time.js';
|
|||
|
||||
const StudioMuteEditMessage = ({
|
||||
className,
|
||||
messageId,
|
||||
messageId = 'studio.mutedEdit',
|
||||
muteExpiresAtMs
|
||||
}) => (
|
||||
<ValidationMessage
|
||||
|
@ -32,10 +32,6 @@ StudioMuteEditMessage.propTypes = {
|
|||
muteExpiresAtMs: PropTypes.number
|
||||
};
|
||||
|
||||
StudioMuteEditMessage.defaultProps = {
|
||||
messageId: 'studio.mutedEdit'
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
|
||||
|
|
|
@ -1,45 +1,13 @@
|
|||
import React from 'react';
|
||||
import {render} from '@testing-library/react';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import routes from '../../src/routes.json';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import merge from 'lodash.merge';
|
||||
|
||||
// TBD: Move code to script that executes before running all tests,
|
||||
// fix issue where texts for views don't load
|
||||
|
||||
const globalTemplateFile = path.resolve(__dirname, '../../src/l10n.json');
|
||||
const generalLocales = {en: JSON.parse(fs.readFileSync(globalTemplateFile, 'utf8'))};
|
||||
const defaultLocales = {};
|
||||
const views = [];
|
||||
|
||||
for (const route in routes) {
|
||||
if (typeof routes[route].redirect !== 'undefined') {
|
||||
continue;
|
||||
}
|
||||
|
||||
views.push(routes[route].name);
|
||||
try {
|
||||
const subdir = routes[route].view.split('/');
|
||||
subdir.pop();
|
||||
const l10n = path.resolve(__dirname, `../../src/views/${subdir.join('/')}/l10n.json`);
|
||||
const viewIds = JSON.parse(fs.readFileSync(l10n, 'utf8'));
|
||||
defaultLocales[routes[route].name] = viewIds;
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
views.map(view => defaultLocales[view]).reduce((acc, curr) => merge(acc, curr), generalLocales);
|
||||
import {generatedLocales} from '../generated/generated-locales';
|
||||
|
||||
const renderWithIntl = ui => ({
|
||||
...render(
|
||||
<IntlProvider
|
||||
locale="en"
|
||||
messages={generalLocales.en}
|
||||
messages={generatedLocales.en}
|
||||
>
|
||||
{ui}
|
||||
</IntlProvider>
|
||||
|
|
68
test/helpers/react-testing-library-wrapper.js
vendored
68
test/helpers/react-testing-library-wrapper.js
vendored
|
@ -1,68 +0,0 @@
|
|||
import {render} from '@testing-library/react';
|
||||
import {renderWithIntl} from './intl-helpers';
|
||||
|
||||
const findNode = (fiberNode, selector, comparator) => {
|
||||
if (fiberNode &&
|
||||
fiberNode.stateNode &&
|
||||
fiberNode.stateNode.state &&
|
||||
comparator(selector, fiberNode)) {
|
||||
return fiberNode;
|
||||
}
|
||||
|
||||
let currentNode;
|
||||
|
||||
if (!currentNode && fiberNode && fiberNode.child) {
|
||||
currentNode = findNode(fiberNode.child, selector, comparator);
|
||||
}
|
||||
|
||||
if (!currentNode && fiberNode && fiberNode.sibling) {
|
||||
currentNode = findNode(fiberNode.sibling, selector, comparator);
|
||||
}
|
||||
|
||||
return currentNode;
|
||||
};
|
||||
|
||||
const getInstance = (container, selector, comparator) => {
|
||||
const rootFiberKey = Object.keys(container).find(key =>
|
||||
key.startsWith('__reactContainer')
|
||||
);
|
||||
const rootFiber = container[rootFiberKey];
|
||||
|
||||
return findNode(rootFiber.stateNode.current, selector, comparator) || null;
|
||||
};
|
||||
|
||||
const compareComponentName = (componentName, fiberNode) => fiberNode.elementType?.name.startsWith(componentName);
|
||||
|
||||
const renderWithInstance = (ux, componentName) => {
|
||||
const component = render(ux);
|
||||
|
||||
return {
|
||||
instance: () => getInstance(
|
||||
component.container,
|
||||
componentName,
|
||||
compareComponentName)?.stateNode,
|
||||
findByComponentName: selector => getInstance(
|
||||
component.container,
|
||||
selector,
|
||||
compareComponentName)?.stateNode,
|
||||
...component
|
||||
};
|
||||
};
|
||||
|
||||
const renderWithInstanceAndIntl = (ux, componentName) => {
|
||||
const component = renderWithIntl(ux);
|
||||
|
||||
return {
|
||||
instance: () => getInstance(
|
||||
component.container,
|
||||
componentName,
|
||||
compareComponentName)?.stateNode,
|
||||
findByComponentName: selector => getInstance(
|
||||
component.container,
|
||||
selector,
|
||||
compareComponentName)?.stateNode,
|
||||
...component
|
||||
};
|
||||
};
|
||||
|
||||
export {renderWithInstance as render, renderWithInstanceAndIntl as renderWithIntl};
|
146
test/helpers/react-testing-library-wrapper.jsx
Normal file
146
test/helpers/react-testing-library-wrapper.jsx
Normal file
|
@ -0,0 +1,146 @@
|
|||
import React from 'react';
|
||||
import {render} from '@testing-library/react';
|
||||
import {renderWithIntl} from './intl-helpers';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import {generatedLocales} from '../generated/generated-locales';
|
||||
|
||||
const findNode = (fiberNode, selector, comparator) => {
|
||||
if (fiberNode &&
|
||||
comparator(selector, fiberNode)) {
|
||||
return fiberNode;
|
||||
}
|
||||
|
||||
let currentNode;
|
||||
|
||||
if (!currentNode && fiberNode && fiberNode.child) {
|
||||
currentNode = findNode(fiberNode.child, selector, comparator);
|
||||
}
|
||||
|
||||
if (!currentNode && fiberNode && fiberNode.sibling) {
|
||||
currentNode = findNode(fiberNode.sibling, selector, comparator);
|
||||
}
|
||||
|
||||
return currentNode;
|
||||
};
|
||||
|
||||
const findAllNodes = (fiberNode, selector, comparator) => {
|
||||
let currentNodes = [];
|
||||
|
||||
if (fiberNode &&
|
||||
comparator(selector, fiberNode)) {
|
||||
currentNodes.push(fiberNode);
|
||||
}
|
||||
|
||||
if (fiberNode && fiberNode.child) {
|
||||
currentNodes = [...currentNodes, ...findAllNodes(fiberNode.child, selector, comparator)];
|
||||
}
|
||||
|
||||
if (fiberNode && fiberNode.sibling) {
|
||||
currentNodes = [...currentNodes, ...findAllNodes(fiberNode.sibling, selector, comparator)];
|
||||
}
|
||||
|
||||
if (fiberNode && fiberNode._reactInternals) {
|
||||
currentNodes = [...currentNodes, ...findAllNodes(fiberNode._reactInternals, selector, comparator)];
|
||||
}
|
||||
|
||||
return currentNodes;
|
||||
};
|
||||
|
||||
|
||||
const getInstance = (container, selector, comparator) => {
|
||||
const rootFiberKey = Object.keys(container).find(key =>
|
||||
key.startsWith('__reactContainer')
|
||||
);
|
||||
const rootFiber = container[rootFiberKey];
|
||||
|
||||
return findNode(rootFiber.stateNode.current, selector, comparator) || null;
|
||||
};
|
||||
|
||||
const compareComponentName = (componentName, fiberNode) => fiberNode.elementType?.name?.startsWith(componentName);
|
||||
|
||||
const renderWithInstance = (ux, componentName) => {
|
||||
const component = render(ux);
|
||||
|
||||
const instance = getInstance(
|
||||
component.container,
|
||||
componentName,
|
||||
compareComponentName);
|
||||
|
||||
return {
|
||||
instance: () => {
|
||||
const node = getInstance(
|
||||
component.container,
|
||||
componentName,
|
||||
compareComponentName);
|
||||
|
||||
return node?.stateNode || node;
|
||||
},
|
||||
findByComponentName: selector => {
|
||||
const node = findNode(
|
||||
instance,
|
||||
selector,
|
||||
compareComponentName);
|
||||
|
||||
return node?.stateNode || node;
|
||||
},
|
||||
findAllByComponentName: selector => {
|
||||
const nodes = findAllNodes(
|
||||
instance,
|
||||
selector,
|
||||
compareComponentName);
|
||||
|
||||
return nodes;
|
||||
},
|
||||
...component
|
||||
};
|
||||
};
|
||||
|
||||
const renderWithInstanceAndIntl = (ux, componentName) => {
|
||||
const component = renderWithIntl(ux);
|
||||
|
||||
return {
|
||||
instance: () => {
|
||||
const node = getInstance(
|
||||
component.container,
|
||||
componentName,
|
||||
compareComponentName);
|
||||
|
||||
return node?.stateNode || node;
|
||||
},
|
||||
findByComponentName: selector => {
|
||||
const node = findNode(
|
||||
getInstance(
|
||||
component.container,
|
||||
componentName,
|
||||
compareComponentName),
|
||||
selector,
|
||||
compareComponentName);
|
||||
|
||||
return node?.stateNode || node;
|
||||
},
|
||||
findAllByComponentName: selector => {
|
||||
const nodes = findAllNodes(
|
||||
getInstance(
|
||||
component.container,
|
||||
componentName,
|
||||
compareComponentName),
|
||||
selector,
|
||||
compareComponentName);
|
||||
|
||||
return nodes;
|
||||
},
|
||||
rerenderWithIntl: newUx => {
|
||||
component.rerender(
|
||||
<IntlProvider
|
||||
locale="en"
|
||||
messages={generatedLocales.en}
|
||||
>
|
||||
{newUx ?? ux}
|
||||
</IntlProvider>
|
||||
);
|
||||
},
|
||||
...component
|
||||
};
|
||||
};
|
||||
|
||||
export {renderWithInstance as render, renderWithInstanceAndIntl as renderWithIntl};
|
|
@ -1,14 +1,14 @@
|
|||
/* eslint-disable max-len */
|
||||
const React = require('react');
|
||||
const {renderWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {ConnectedBecomeAScratcher as BecomeAScratcherPage} from '../../../src/views/become-a-scratcher/become-a-scratcher.jsx';
|
||||
import sessionActions from '../../../src/redux/session.js';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import '@testing-library/jest-dom';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
jest.mock('react-dom', () => ({
|
||||
render: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('../../../src/lib/render.jsx', () => jest.fn());
|
||||
|
||||
jest.mock('../../../src/components/modal/base/modal.jsx', () => () => 'MockModal');
|
||||
|
||||
|
@ -26,7 +26,9 @@ describe('BecomeAScratcherPage', () => {
|
|||
}
|
||||
});
|
||||
const {container} = renderWithIntl(
|
||||
<BecomeAScratcherPage />, {context: {store: NotLoggedInUserStore}}
|
||||
<Provider store={NotLoggedInUserStore}>
|
||||
<BecomeAScratcherPage />
|
||||
</Provider>
|
||||
);
|
||||
expect(container.querySelector('div.not-available-outer')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -47,9 +49,11 @@ describe('BecomeAScratcherPage', () => {
|
|||
}
|
||||
});
|
||||
const {container} = renderWithIntl(
|
||||
<BecomeAScratcherPage />, {context: {store: NotInvitedUserStore}}
|
||||
<Provider store={NotInvitedUserStore}>
|
||||
<BecomeAScratcherPage />
|
||||
</Provider>
|
||||
);
|
||||
expect(container.querySelector('div.no-invitation').exists()).toBeInTheDocument();
|
||||
expect(container.querySelector('div.no-invitation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Display Onboarding when user is invited', () => {
|
||||
|
@ -69,9 +73,11 @@ describe('BecomeAScratcherPage', () => {
|
|||
}
|
||||
});
|
||||
const {container} = renderWithIntl(
|
||||
<BecomeAScratcherPage />, {context: {store: InvitedUserStore}}
|
||||
<Provider store={InvitedUserStore}>
|
||||
<BecomeAScratcherPage />
|
||||
</Provider>
|
||||
);
|
||||
expect(container.querySelector('div.congratulations-page').exists()).toBeInTheDocument();
|
||||
expect(container.querySelector('div.congratulations-page')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Display celebration page when user is already a scratcher', () => {
|
||||
|
@ -91,8 +97,10 @@ describe('BecomeAScratcherPage', () => {
|
|||
}
|
||||
});
|
||||
const {container} = renderWithIntl(
|
||||
<BecomeAScratcherPage />, {context: {store: AlreadyScratcherStore}}
|
||||
<Provider store={AlreadyScratcherStore}>
|
||||
<BecomeAScratcherPage />
|
||||
</Provider>
|
||||
);
|
||||
expect(container.querySelector('div.hooray-screen').exists()).toBeInTheDocument();
|
||||
expect(container.querySelector('div.hooray-screen')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const React = require('react');
|
||||
|
||||
const Captcha = require('../../../src/components/captcha/captcha.jsx');
|
||||
const {render} = require('@testing-library/react');
|
||||
import {render} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
|
||||
describe('Captcha test', () => {
|
||||
global.grecaptcha = {
|
||||
|
@ -25,10 +25,8 @@ describe('Captcha test', () => {
|
|||
const props = {
|
||||
onCaptchaLoad: jest.fn()
|
||||
};
|
||||
const {container} = (<Captcha
|
||||
{...props}
|
||||
/>);
|
||||
container.executeCaptcha();
|
||||
const captchaInstance = render(<Captcha {...props} />, 'Captcha').instance();
|
||||
captchaInstance.executeCaptcha();
|
||||
expect(global.grecaptcha.execute).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import {Provider} from 'react-redux';
|
|||
const ComposeComment = require('../../../src/views/preview/comment/compose-comment.jsx');
|
||||
import configureStore from 'redux-mock-store';
|
||||
import '@testing-library/jest-dom';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.js';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {screen} from '@testing-library/react';
|
||||
|
||||
describe('Compose Comment test', () => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable max-len */
|
||||
import React from 'react';
|
||||
import {renderWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import DonateTopBanner from '../../../src/views/splash/donate/donate-banner';
|
||||
|
@ -14,27 +15,26 @@ describe('DonateBannerTest', () => {
|
|||
});
|
||||
test('Testing 2024 EOY campaign message', () => {
|
||||
global.Date.now = () => new Date(2024, 11, 16).getTime();
|
||||
const {container} = renderWithIntl(
|
||||
<DonateTopBanner />
|
||||
);
|
||||
const {container} = renderWithIntl(<DonateTopBanner />);
|
||||
|
||||
expect(container.querySelector('div.donate-banner')).toBeInTheDocument();
|
||||
expect(container.querySelector('p.donate-text')).toBeInTheDocument();
|
||||
|
||||
const donateText = container.querySelector('p.donate-text');
|
||||
expect(donateText.innerHTML).toEqual('donatebanner.eoyCampaign');
|
||||
|
||||
expect(donateText.textContent).toEqual(
|
||||
'Scratch is a nonprofit that relies on donations to keep our platform free for all kids. Your gift of $5 will make a difference.'
|
||||
);
|
||||
});
|
||||
test('testing default message comes back after January 9, 2025', () => {
|
||||
// Date after Scratch week
|
||||
// Date after Scratch week
|
||||
global.Date.now = () => new Date(2025, 0, 10).getTime();
|
||||
const {container} = renderWithIntl(
|
||||
<DonateTopBanner />
|
||||
);
|
||||
const {container} = renderWithIntl(<DonateTopBanner />);
|
||||
expect(container.querySelector('div.donate-banner')).toBeInTheDocument();
|
||||
expect(container.querySelector('p.donate-text')).toBeInTheDocument();
|
||||
|
||||
const donateText = container.querySelector('p.donate-text');
|
||||
expect(donateText.innerHTML).toEqual('donatebanner.askSupport');
|
||||
expect(donateText.textContent).toEqual(
|
||||
"Scratch is the world's largest free coding community for kids. Your support makes a difference."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
const React = require('react');
|
||||
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
const {mountWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
const JoinFlowStep = require('../../../src/components/join-flow/join-flow-step.jsx');
|
||||
const FormikInput = require('../../../src/components/formik-forms/formik-input.jsx');
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
|
||||
const requestSuccessResponse = {
|
||||
requestSucceeded: true,
|
||||
|
@ -44,52 +41,55 @@ describe('EmailStep test', () => {
|
|||
});
|
||||
|
||||
test('send correct props to formik', () => {
|
||||
const intlWrapper = shallowWithIntl(<EmailStep
|
||||
const {findByComponentName, instance} = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
expect(emailStepWrapper.props().initialValues.email).toBe('');
|
||||
expect(emailStepWrapper.props().validateOnBlur).toBe(false);
|
||||
expect(emailStepWrapper.props().validateOnChange).toBe(false);
|
||||
expect(emailStepWrapper.props().validate).toBe(emailStepWrapper.instance().validateForm);
|
||||
expect(emailStepWrapper.props().onSubmit).toBe(emailStepWrapper.instance().handleValidSubmit);
|
||||
/>, 'EmailStep');
|
||||
|
||||
const formikComponent = findByComponentName('Formik');
|
||||
const emailStepInstance = instance();
|
||||
|
||||
expect(formikComponent.memoizedProps.initialValues.email).toBe('');
|
||||
expect(formikComponent.memoizedProps.validateOnBlur).toBe(false);
|
||||
expect(formikComponent.memoizedProps.validateOnChange).toBe(false);
|
||||
expect(formikComponent.memoizedProps.validate).toBe(emailStepInstance.validateForm);
|
||||
expect(formikComponent.memoizedProps.onSubmit).toBe(emailStepInstance.handleValidSubmit);
|
||||
});
|
||||
|
||||
test('props sent to JoinFlowStep', () => {
|
||||
const intlWrapper = shallowWithIntl(<EmailStep
|
||||
const {findAllByComponentName} = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
// Dive to get past the intl wrapper
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
// Dive to get past the anonymous component.
|
||||
const joinFlowWrapper = emailStepWrapper.dive().find(JoinFlowStep);
|
||||
expect(joinFlowWrapper).toHaveLength(1);
|
||||
expect(joinFlowWrapper.props().footerContent.props.id).toBe('registration.acceptTermsOfUse');
|
||||
expect(joinFlowWrapper.props().headerImgSrc).toBe('/images/join-flow/email-header.png');
|
||||
expect(joinFlowWrapper.props().innerClassName).toBe('join-flow-inner-email-step');
|
||||
expect(joinFlowWrapper.props().nextButton).toBe('registration.createAccount');
|
||||
expect(joinFlowWrapper.props().title).toBe('registration.emailStepTitle');
|
||||
expect(joinFlowWrapper.props().titleClassName).toBe('join-flow-email-title');
|
||||
expect(joinFlowWrapper.props().waiting).toBe(true);
|
||||
/>, 'EmailStep');
|
||||
|
||||
const joinFlowSteps = findAllByComponentName('JoinFlowStep');
|
||||
expect(joinFlowSteps).toHaveLength(1);
|
||||
expect(joinFlowSteps[0].memoizedProps.footerContent.props.id).toBe('registration.acceptTermsOfUse');
|
||||
expect(joinFlowSteps[0].memoizedProps.headerImgSrc).toBe('/images/join-flow/email-header.png');
|
||||
expect(joinFlowSteps[0].memoizedProps.innerClassName).toBe('join-flow-inner-email-step');
|
||||
expect(joinFlowSteps[0].memoizedProps.nextButton).toBe('Create Your Account');
|
||||
expect(joinFlowSteps[0].memoizedProps.title).toBe('What\'s your email?');
|
||||
expect(joinFlowSteps[0].memoizedProps.titleClassName).toBe('join-flow-email-title');
|
||||
expect(joinFlowSteps[0].memoizedProps.waiting).toBe(true);
|
||||
});
|
||||
|
||||
test('props sent to FormikInput for email', () => {
|
||||
const intlWrapper = shallowWithIntl(<EmailStep
|
||||
const {findByComponentName, findAllByComponentName, instance} = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
// Dive to get past the intl wrapper
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
// Dive to get past the anonymous component.
|
||||
const joinFlowWrapper = emailStepWrapper.dive().find(JoinFlowStep);
|
||||
expect(joinFlowWrapper).toHaveLength(1);
|
||||
const emailInputWrapper = joinFlowWrapper.find(FormikInput).first();
|
||||
expect(emailInputWrapper.props().id).toEqual('email');
|
||||
expect(emailInputWrapper.props().error).toBeUndefined();
|
||||
expect(emailInputWrapper.props().name).toEqual('email');
|
||||
expect(emailInputWrapper.props().placeholder).toEqual('general.emailAddress');
|
||||
expect(emailInputWrapper.props().validationClassName).toEqual('validation-full-width-input');
|
||||
expect(emailInputWrapper.props().onSetRef).toEqual(emailStepWrapper.instance().handleSetEmailRef);
|
||||
expect(emailInputWrapper.props().validate).toEqual(emailStepWrapper.instance().validateEmail);
|
||||
/>, 'EmailStep');
|
||||
|
||||
const emailStepInstance = instance();
|
||||
|
||||
const joinFlowSteps = findAllByComponentName('JoinFlowStep');
|
||||
expect(joinFlowSteps).toHaveLength(1);
|
||||
|
||||
const emailInput = findByComponentName('FormikInput');
|
||||
|
||||
expect(emailInput.memoizedProps.id).toEqual('email');
|
||||
expect(emailInput.memoizedProps.error).toBeUndefined();
|
||||
expect(emailInput.memoizedProps.name).toEqual('email');
|
||||
expect(emailInput.memoizedProps.placeholder).toEqual('Email address');
|
||||
expect(emailInput.memoizedProps.validationClassName).toEqual('validation-full-width-input');
|
||||
expect(emailInput.memoizedProps.onSetRef).toEqual(emailStepInstance.handleSetEmailRef);
|
||||
expect(emailInput.memoizedProps.validate).toEqual(emailStepInstance.validateEmail);
|
||||
});
|
||||
|
||||
test('handleValidSubmit passes formData to next step', () => {
|
||||
|
@ -101,14 +101,11 @@ describe('EmailStep test', () => {
|
|||
executeCaptcha: jest.fn()
|
||||
};
|
||||
const formData = {item1: 'thing', item2: 'otherthing'};
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
emailStepWrapper.instance().setCaptchaRef(captchaRef);
|
||||
emailStepWrapper.instance().handleValidSubmit(formData, formikBag);
|
||||
const emailStepInstance = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
/>, 'EmailStep').instance();
|
||||
emailStepInstance.setCaptchaRef(captchaRef);
|
||||
emailStepInstance.handleValidSubmit(formData, formikBag);
|
||||
|
||||
expect(formikBag.setSubmitting).toHaveBeenCalledWith(false);
|
||||
expect(captchaRef.executeCaptcha).toHaveBeenCalled();
|
||||
|
@ -125,19 +122,16 @@ describe('EmailStep test', () => {
|
|||
executeCaptcha: jest.fn()
|
||||
};
|
||||
const formData = {item1: 'thing', item2: 'otherthing'};
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep
|
||||
{...defaultProps()}
|
||||
{...props}
|
||||
/>);
|
||||
const emailStepInstance = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
{...props}
|
||||
/>, 'EmailStep').instance();
|
||||
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
// Call these to setup captcha.
|
||||
emailStepWrapper.instance().setCaptchaRef(captchaRef); // to setup catpcha state
|
||||
emailStepWrapper.instance().handleValidSubmit(formData, formikBag);
|
||||
emailStepInstance.setCaptchaRef(captchaRef); // to setup catpcha state
|
||||
emailStepInstance.handleValidSubmit(formData, formikBag);
|
||||
|
||||
const captchaToken = 'abcd';
|
||||
emailStepWrapper.instance().handleCaptchaSolved(captchaToken);
|
||||
emailStepInstance.handleCaptchaSolved(captchaToken);
|
||||
// Make sure captchaSolved calls onNextStep with formData that has
|
||||
// a captcha token and left everything else in the object in place.
|
||||
expect(props.onNextStep).toHaveBeenCalledWith(
|
||||
|
@ -152,7 +146,7 @@ describe('EmailStep test', () => {
|
|||
test('Component logs analytics', () => {
|
||||
const sendAnalyticsFn = jest.fn();
|
||||
const onCaptchaError = jest.fn();
|
||||
mountWithIntl(
|
||||
renderWithIntl(
|
||||
<EmailStep
|
||||
sendAnalytics={sendAnalyticsFn}
|
||||
onCaptchaError={onCaptchaError}
|
||||
|
@ -161,33 +155,30 @@ describe('EmailStep test', () => {
|
|||
});
|
||||
|
||||
test('validateEmail test email empty', () => {
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
const val = emailStepWrapper.instance().validateEmail('');
|
||||
expect(val).toBe('general.required');
|
||||
const emailStepInstance = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
/>, 'EmailStep').instance();
|
||||
|
||||
const val = emailStepInstance.validateEmail('');
|
||||
expect(val).toBe('Required');
|
||||
});
|
||||
|
||||
test('validateEmail test email null', () => {
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
const val = emailStepWrapper.instance().validateEmail(null);
|
||||
expect(val).toBe('general.required');
|
||||
const emailStepInstance = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
/>, 'EmailStep').instance();
|
||||
|
||||
const val = emailStepInstance.validateEmail(null);
|
||||
expect(val).toBe('Required');
|
||||
});
|
||||
|
||||
test('validateEmail test email undefined', () => {
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
const emailStepWrapper = intlWrapper.dive();
|
||||
const val = emailStepWrapper.instance().validateEmail();
|
||||
expect(val).toBe('general.required');
|
||||
const emailStepInstance = renderWithIntl(<EmailStep
|
||||
{...defaultProps()}
|
||||
/>, 'EmailStep').instance();
|
||||
|
||||
const val = emailStepInstance.validateEmail();
|
||||
expect(val).toBe('Required');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -198,12 +189,9 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
|
|||
});
|
||||
|
||||
test('validateEmailRemotelyWithCache calls validate.validateEmailRemotely', done => {
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep />
|
||||
);
|
||||
const instance = intlWrapper.dive().instance();
|
||||
const emailStepInstance = renderWithIntl(<EmailStep />, 'EmailStep').instance();
|
||||
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalled();
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -213,12 +201,9 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
|
|||
});
|
||||
|
||||
test('validateEmailRemotelyWithCache, called twice with different data, makes two remote requests', done => {
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep />
|
||||
);
|
||||
const instance = intlWrapper.dive().instance();
|
||||
const emailStepInstance = renderWithIntl(<EmailStep />, 'EmailStep').instance();
|
||||
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(1);
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -226,7 +211,7 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
|
|||
})
|
||||
.then(() => {
|
||||
// make the same request a second time
|
||||
instance.validateEmailRemotelyWithCache('different-email@some-domain.org')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('different-email@some-domain.org')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(2);
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -237,12 +222,9 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
|
|||
});
|
||||
|
||||
test('validateEmailRemotelyWithCache, called twice with same data, only makes one remote request', done => {
|
||||
const intlWrapper = shallowWithIntl(
|
||||
<EmailStep />
|
||||
);
|
||||
const instance = intlWrapper.dive().instance();
|
||||
const emailStepInstance = renderWithIntl(<EmailStep />, 'EmailStep').instance();
|
||||
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(1);
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -250,7 +232,7 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
|
|||
})
|
||||
.then(() => {
|
||||
// make the same request a second time
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(1);
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -275,11 +257,9 @@ describe('validateEmailRemotelyWithCache test with failing requests', () => {
|
|||
});
|
||||
|
||||
test('validateEmailRemotelyWithCache calls validate.validateEmailRemotely', done => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<EmailStep />);
|
||||
const instance = wrapper.dive().instance();
|
||||
const emailStepInstance = renderWithIntl(<EmailStep />, 'EmailStep').instance();
|
||||
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalled();
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -289,12 +269,9 @@ describe('validateEmailRemotelyWithCache test with failing requests', () => {
|
|||
});
|
||||
|
||||
test('validateEmailRemotelyWithCache, called twice with different data, makes two remote requests', done => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<EmailStep />
|
||||
);
|
||||
const instance = wrapper.dive().instance();
|
||||
const emailStepInstance = renderWithIntl(<EmailStep />, 'EmailStep').instance();
|
||||
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(1);
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -302,7 +279,7 @@ describe('validateEmailRemotelyWithCache test with failing requests', () => {
|
|||
})
|
||||
.then(() => {
|
||||
// make the same request a second time
|
||||
instance.validateEmailRemotelyWithCache('different-email@some-domain.org')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('different-email@some-domain.org')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(2);
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -313,12 +290,9 @@ describe('validateEmailRemotelyWithCache test with failing requests', () => {
|
|||
});
|
||||
|
||||
test('validateEmailRemotelyWithCache, called 2x w/same data, makes 2 requests, since 1st not stored', done => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<EmailStep />
|
||||
);
|
||||
const instance = wrapper.dive().instance();
|
||||
const emailStepInstance = renderWithIntl(<EmailStep />, 'EmailStep').instance();
|
||||
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(1);
|
||||
expect(response.valid).toBe(false);
|
||||
|
@ -326,7 +300,7 @@ describe('validateEmailRemotelyWithCache test with failing requests', () => {
|
|||
})
|
||||
.then(() => {
|
||||
// make the same request a second time
|
||||
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
emailStepInstance.validateEmailRemotelyWithCache('some-email@some-domain.com')
|
||||
.then(response => {
|
||||
expect(mockedValidateEmailRemotely).toHaveBeenCalledTimes(2);
|
||||
expect(response.valid).toBe(false);
|
||||
|
|
|
@ -1,24 +1,41 @@
|
|||
import React from 'react';
|
||||
const {mountWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
|
||||
import CrashMessageComponent from '../../../src/components/crashmessage/crashmessage.jsx';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import ErrorBoundary from '../../../src/components/errorboundary/errorboundary.jsx';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {fireEvent, screen} from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
const ChildComponent = () => <div>hello</div>;
|
||||
const ChildComponent = () => {
|
||||
const hasRendered = useRef(false);
|
||||
const [text, setText] = useState('hello');
|
||||
|
||||
useEffect(() => {
|
||||
if (hasRendered.current) {
|
||||
throw new Error('This component has been re-rendered!');
|
||||
} else {
|
||||
hasRendered.current = true;
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
return <div onClick={() => setText('not hello')}>{text}</div>;
|
||||
};
|
||||
|
||||
describe('ErrorBoundary', () => {
|
||||
|
||||
test('ErrorBoundary shows children before error and CrashMessageComponent after', () => {
|
||||
const child = <ChildComponent />;
|
||||
const wrapper = mountWithIntl(<ErrorBoundary>{child}</ErrorBoundary>);
|
||||
const childWrapper = wrapper.childAt(0);
|
||||
const {findByComponentName} = renderWithIntl(
|
||||
<ErrorBoundary>{child}</ErrorBoundary>,
|
||||
'ErrorBoundary'
|
||||
);
|
||||
|
||||
expect(wrapper.containsMatchingElement(child)).toBeTruthy();
|
||||
expect(wrapper.containsMatchingElement(<CrashMessageComponent />)).toBeFalsy();
|
||||
expect(findByComponentName('ChildComponent')).toBeTruthy();
|
||||
expect(findByComponentName('CrashMessage')).toBeFalsy();
|
||||
|
||||
childWrapper.simulateError(new Error('fake error for testing purposes'));
|
||||
const helloDiv = screen.getByText('hello');
|
||||
fireEvent.click(helloDiv);
|
||||
|
||||
expect(wrapper.containsMatchingElement(child)).toBeFalsy();
|
||||
expect(wrapper.containsMatchingElement(<CrashMessageComponent />)).toBeTruthy();
|
||||
expect(findByComponentName('ChildComponent')).toBeFalsy();
|
||||
expect(findByComponentName('CrashMessage')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import ExtensionRequirements from '../../../src/components/extension-landing/extension-requirements';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
|
||||
describe('ExtensionRequirements', () => {
|
||||
|
||||
test('shows default extension requirements', () => {
|
||||
const component = mountWithIntl(<ExtensionRequirements />);
|
||||
const {container} = renderWithIntl(<ExtensionRequirements />, 'ExtensionRequirements');
|
||||
|
||||
const requirements = component.find('.extension-requirements span').map(span => span.text());
|
||||
const spans = container.querySelectorAll('.extension-requirements span');
|
||||
const requirements = Array.from(spans).map(span => span.textContent.trim());
|
||||
|
||||
expect(requirements).toEqual(
|
||||
['Windows 10 version 1709+', 'macOS 10.15+', 'ChromeOS', 'Android 6.0+', 'Bluetooth', 'Scratch Link']
|
||||
|
@ -15,15 +16,15 @@ describe('ExtensionRequirements', () => {
|
|||
});
|
||||
|
||||
test('hides requirements', () => {
|
||||
const component = mountWithIntl(<ExtensionRequirements
|
||||
const {container} = renderWithIntl(<ExtensionRequirements
|
||||
hideWindows
|
||||
hideMac
|
||||
hideChromeOS
|
||||
hideAndroid
|
||||
hideBluetooth
|
||||
hideScratchLink
|
||||
/>);
|
||||
/>, 'ExtensionRequirements');
|
||||
|
||||
expect(component.find('.extension-requirements span').length).toEqual(0);
|
||||
expect(container.querySelectorAll('.extension-requirements span').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,53 +1,50 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import FeedbackForm from '../../../src/components/modal/mute/feedback-form';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper';
|
||||
|
||||
describe('FeedbackFormTest', () => {
|
||||
test('Feedback form empty feedback invalid', () => {
|
||||
const submitFn = jest.fn();
|
||||
const message = 'too short';
|
||||
const component = mountWithIntl(
|
||||
const feedbackFormInstance = renderWithIntl(
|
||||
<FeedbackForm
|
||||
emptyErrorMessage={message}
|
||||
onSubmit={submitFn}
|
||||
/>
|
||||
);
|
||||
expect(component.find('FeedbackForm').instance()
|
||||
.validateFeedback('')
|
||||
).toBe(message);
|
||||
/>,
|
||||
'FeedbackForm'
|
||||
).instance();
|
||||
expect(feedbackFormInstance.validateFeedback('')).toBe(message);
|
||||
});
|
||||
|
||||
test('Feedback form shorter than minLength invalid', () => {
|
||||
const submitFn = jest.fn();
|
||||
const message = 'too short';
|
||||
const min = 7;
|
||||
const component = mountWithIntl(
|
||||
const feedbackFormInstance = renderWithIntl(
|
||||
<FeedbackForm
|
||||
emptyErrorMessage={message}
|
||||
minLength={min}
|
||||
onSubmit={submitFn}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find('FeedbackForm').instance()
|
||||
.validateFeedback('123456')
|
||||
).toBe(message);
|
||||
/>,
|
||||
'FeedbackForm'
|
||||
).instance();
|
||||
|
||||
expect(feedbackFormInstance.validateFeedback('123456')).toBe(message);
|
||||
});
|
||||
|
||||
test('Feedback form greater than or equal to minLength invalid', () => {
|
||||
const submitFn = jest.fn();
|
||||
const message = 'too short';
|
||||
const min = 7;
|
||||
const component = mountWithIntl(
|
||||
const feedbackFormInstance = renderWithIntl(
|
||||
<FeedbackForm
|
||||
emptyErrorMessage={message}
|
||||
minLength={min}
|
||||
onSubmit={submitFn}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find('FeedbackForm').instance()
|
||||
.validateFeedback('1234567')
|
||||
).toBeNull();
|
||||
/>,
|
||||
'FeedbackForm'
|
||||
).instance();
|
||||
|
||||
expect(feedbackFormInstance.validateFeedback('1234567')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,85 +1,92 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import FormikInput from '../../../src/components/formik-forms/formik-input.jsx';
|
||||
import {Formik} from 'formik';
|
||||
import '@testing-library/jest-dom';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
|
||||
describe('FormikInput', () => {
|
||||
test('No validation message with empty error, empty toolTip', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error=""
|
||||
toolTip=""
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
expect(findByComponentName('ValidationMessage')).toBeFalsy();
|
||||
expect(container.querySelector('div.validation-error')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('div.validation-info')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('No validation message with false error, false toolTip', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error={false}
|
||||
toolTip={false}
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
expect(findByComponentName('ValidationMessage')).toBeFalsy();
|
||||
expect(container.querySelector('div.validation-error')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('div.validation-info')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('No validation message with nonexistent error or toolTip', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikInput />
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
expect(findByComponentName('ValidationMessage')).toBeFalsy();
|
||||
expect(container.querySelector('div.validation-error')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('div.validation-info')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Validation message shown when error given', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error="There was an error"
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
expect(findByComponentName('ValidationMessage')).toBeTruthy();
|
||||
expect(container.querySelector('div.validation-error')).toBeInTheDocument();
|
||||
expect(container.querySelector('div.validation-info')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Tooltip shown when toolTip given', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
toolTip="Have fun out there!"
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(true);
|
||||
expect(findByComponentName('ValidationMessage')).toBeTruthy();
|
||||
expect(container.querySelector('div.validation-error')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('div.validation-info')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('If both error and toolTip messages, error takes precedence', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error="There was an error"
|
||||
toolTip="Have fun out there!"
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
expect(findByComponentName('ValidationMessage')).toBeTruthy();
|
||||
expect(container.querySelector('div.validation-error')).toBeInTheDocument();
|
||||
expect(container.querySelector('div.validation-info')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import FormikSelect from '../../../src/components/formik-forms/formik-select.jsx';
|
||||
import {Field, Formik} from 'formik';
|
||||
import {Formik} from 'formik';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
|
||||
describe('FormikSelect', () => {
|
||||
test('No validation message without an error', () => {
|
||||
const component = mountWithIntl(
|
||||
const {findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikSelect
|
||||
error=""
|
||||
options={[]}
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find(Field).exists()).toEqual(true);
|
||||
expect(findByComponentName('ValidationMessage')).toBeFalsy();
|
||||
expect(findByComponentName('Field')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Validation message shown when error present', () => {
|
||||
const component = mountWithIntl(
|
||||
const {findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikSelect
|
||||
error="uh oh. error"
|
||||
options={[]}
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find(Field).exists()).toEqual(true);
|
||||
expect(findByComponentName('ValidationMessage')).toBeTruthy();
|
||||
expect(findByComponentName('Field')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('list of options passed to formik', () => {
|
||||
|
@ -45,15 +47,16 @@ describe('FormikSelect', () => {
|
|||
}
|
||||
|
||||
];
|
||||
const component = mountWithIntl(
|
||||
const {findByComponentName} = renderWithIntl(
|
||||
<Formik>
|
||||
<FormikSelect
|
||||
error=""
|
||||
options={optionList}
|
||||
/>
|
||||
</Formik>
|
||||
</Formik>,
|
||||
'Formik'
|
||||
);
|
||||
expect(component.find(Field).exists()).toEqual(true);
|
||||
expect(component.find(Field).prop('children').length).toEqual(2);
|
||||
expect(findByComponentName('Field')).toBeTruthy();
|
||||
expect(findByComponentName('Field').memoizedProps.children.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import React, {act} from 'react';
|
||||
import InfoButton from '../../../src/components/info-button/info-button';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {fireEvent} from '@testing-library/react';
|
||||
|
||||
describe('InfoButton', () => {
|
||||
// mock window.addEventListener
|
||||
|
@ -13,125 +14,132 @@ describe('InfoButton', () => {
|
|||
/* eslint-enable no-undef */
|
||||
|
||||
test('Info button defaults to not visible', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container} = renderWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||
expect(container.querySelector('div.info-button-message')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('mouseOver on info button makes info message visible', done => {
|
||||
const component = mountWithIntl(
|
||||
test('mouseOver on info button makes info message visible', async () => {
|
||||
const {container} = renderWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
);
|
||||
|
||||
// mouseOver info button
|
||||
component.find('div.info-button').simulate('mouseOver');
|
||||
setTimeout(() => { // necessary because mouseover uses debounce
|
||||
// crucial: if we don't call update(), then find() below looks through an OLD
|
||||
// version of the DOM! see https://github.com/airbnb/enzyme/issues/1233#issuecomment-358915200
|
||||
component.update();
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
done();
|
||||
}, 500);
|
||||
await act(() => {
|
||||
fireEvent.mouseOver(container.querySelector(('div.info-button')));
|
||||
});
|
||||
|
||||
expect(container.querySelector('div.info-button-message')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('clicking on info button makes info message visible', () => {
|
||||
const component = mountWithIntl(
|
||||
test('clicking on info button makes info message visible', async () => {
|
||||
const {container, instance} = renderWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
/>,
|
||||
'InfoButton'
|
||||
);
|
||||
const buttonRef = component.instance().buttonRef;
|
||||
const buttonRef = instance().buttonRef;
|
||||
|
||||
// click on info button
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
component.update();
|
||||
expect(component.find('div.info-button').exists()).toEqual(true);
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
await act(() => {
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
});
|
||||
|
||||
expect(container.querySelector('div.info-button')).toBeTruthy();
|
||||
expect(container.querySelector('div.info-button-message')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('clicking on info button, then mousing out makes info message still appear', done => {
|
||||
const component = mountWithIntl(
|
||||
test('clicking on info button, then mousing out makes info message still appear', async () => {
|
||||
const {container, instance} = renderWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
/>,
|
||||
'InfoButton'
|
||||
);
|
||||
const buttonRef = component.instance().buttonRef;
|
||||
const buttonRef = instance().buttonRef;
|
||||
await act(() => {
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
});
|
||||
|
||||
// click on info button
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
component.update();
|
||||
expect(component.find('div.info-button').exists()).toEqual(true);
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
expect(container.querySelector('div.info-button')).toBeTruthy();
|
||||
expect(container.querySelector('div.info-button-message')).toBeTruthy();
|
||||
|
||||
// mouseLeave from info button
|
||||
component.find('div.info-button').simulate('mouseLeave');
|
||||
setTimeout(() => { // necessary because mouseover uses debounce
|
||||
component.update();
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
done();
|
||||
}, 500);
|
||||
await act(() => {
|
||||
fireEvent.mouseLeave(container.querySelector(('div.info-button')));
|
||||
});
|
||||
|
||||
expect(container.querySelector('div.info-button-message')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('clicking on info button, then clicking on it again makes info message go away', () => {
|
||||
const component = mountWithIntl(
|
||||
test('clicking on info button, then clicking on it again makes info message go away', async () => {
|
||||
const {container, instance} = renderWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
/>,
|
||||
'InfoButton'
|
||||
);
|
||||
const buttonRef = component.instance().buttonRef;
|
||||
const buttonRef = instance().buttonRef;
|
||||
|
||||
// click on info button
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
component.update();
|
||||
expect(component.find('div.info-button').exists()).toEqual(true);
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
await act(() => {
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
});
|
||||
|
||||
expect(container.querySelector('div.info-button')).toBeTruthy();
|
||||
expect(container.querySelector('div.info-button-message')).toBeTruthy();
|
||||
|
||||
// click on info button again
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
component.update();
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||
await act(() => {
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
});
|
||||
expect(container.querySelector('div.info-button-message')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('clicking on info button, then clicking somewhere else', () => {
|
||||
const component = mountWithIntl(
|
||||
test('clicking on info button, then clicking somewhere else', async () => {
|
||||
const {container, instance} = renderWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
/>,
|
||||
'InfoButton'
|
||||
);
|
||||
const buttonRef = component.instance().buttonRef;
|
||||
const buttonRef = instance().buttonRef;
|
||||
|
||||
// click on info button
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
component.update();
|
||||
expect(component.find('div.info-button').exists()).toEqual(true);
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
await act(() => {
|
||||
mockedAddEventListener.mousedown({target: buttonRef});
|
||||
});
|
||||
expect(container.querySelector('div.info-button')).toBeTruthy();
|
||||
expect(container.querySelector('div.info-button-message')).toBeTruthy();
|
||||
|
||||
// click on some other target
|
||||
mockedAddEventListener.mousedown({target: null});
|
||||
component.update();
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||
await act(() => {
|
||||
mockedAddEventListener.mousedown({target: null});
|
||||
});
|
||||
expect(container.querySelector('div.info-button-message')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('after message is visible, mouseLeave makes it vanish', () => {
|
||||
const component = mountWithIntl(
|
||||
test('after message is visible, mouseLeave makes it vanish', async () => {
|
||||
const {container} = renderWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
/>,
|
||||
'InfoButton'
|
||||
);
|
||||
|
||||
// mouseOver info button
|
||||
component.find('div.info-button').simulate('mouseOver');
|
||||
component.update();
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
await act(() => {
|
||||
fireEvent.mouseOver(container.querySelector(('div.info-button')));
|
||||
});
|
||||
expect(container.querySelector('div.info-button-message')).toBeTruthy();
|
||||
|
||||
// mouseLeave away from info button
|
||||
component.find('div.info-button').simulate('mouseLeave');
|
||||
component.update();
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||
await act(() => {
|
||||
fireEvent.mouseLeave(container.querySelector(('div.info-button')));
|
||||
});
|
||||
expect(container.querySelector('div.info-button-message')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import JoinFlowStep from '../../../src/components/join-flow/join-flow-step';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {fireEvent} from '@testing-library/react';
|
||||
|
||||
describe('JoinFlowStep', () => {
|
||||
|
||||
|
@ -15,50 +16,59 @@ describe('JoinFlowStep', () => {
|
|||
title: 'join flow step title',
|
||||
waiting: true
|
||||
};
|
||||
const component = mountWithIntl(
|
||||
const {container, unmount, findByComponentName} = renderWithIntl(
|
||||
<JoinFlowStep
|
||||
{...props}
|
||||
/>
|
||||
/>,
|
||||
'JoinFlowStep'
|
||||
);
|
||||
expect(component.find('img.join-flow-header-image').exists()).toEqual(true);
|
||||
expect(component.find({src: props.headerImgSrc}).exists()).toEqual(true);
|
||||
expect(component.find('.join-flow-inner-content').exists()).toEqual(true);
|
||||
expect(component.find('.join-flow-title').exists()).toEqual(true);
|
||||
expect(component.find('.join-flow-title').first()
|
||||
.prop('title')).toEqual(props.title);
|
||||
expect(component.find('div.join-flow-description').exists()).toEqual(true);
|
||||
expect(component.find('div.join-flow-description').text()).toEqual(props.description);
|
||||
expect(component.find('NextStepButton').prop('waiting')).toEqual(true);
|
||||
expect(component.find('NextStepButton').prop('content')).toEqual(props.nextButton);
|
||||
const img = container.querySelector('img.join-flow-header-image');
|
||||
expect(img).toBeTruthy();
|
||||
expect(img.src).toContain(props.headerImgSrc);
|
||||
|
||||
component.unmount();
|
||||
expect(container.querySelector('.join-flow-inner-content')).toBeTruthy();
|
||||
|
||||
const title = container.querySelector('.join-flow-title');
|
||||
expect(title).toBeTruthy();
|
||||
expect(title.textContent).toEqual(props.title);
|
||||
|
||||
expect(container.querySelector('div.join-flow-description')).toBeTruthy();
|
||||
expect(container.querySelector('div.join-flow-description').textContent).toEqual(props.description);
|
||||
|
||||
const nextStepButton = findByComponentName('NextStepButton');
|
||||
expect(nextStepButton).toBeTruthy();
|
||||
expect(nextStepButton.memoizedProps.waiting).toEqual(true);
|
||||
expect(nextStepButton.memoizedProps.content).toEqual(props.nextButton);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
test('components do not exist when props not present', () => {
|
||||
const component = mountWithIntl(
|
||||
<JoinFlowStep />
|
||||
const {container, unmount, findByComponentName} = renderWithIntl(
|
||||
<JoinFlowStep />,
|
||||
'JoinFlowStep'
|
||||
);
|
||||
|
||||
expect(component.find('img.join-flow-header-image').exists()).toEqual(false);
|
||||
expect(component.find('.join-flow-inner-content').exists()).toEqual(true);
|
||||
expect(component.find('.join-flow-title').exists()).toEqual(false);
|
||||
expect(component.find('div.join-flow-description').exists()).toEqual(false);
|
||||
expect(component.find('NextStepButton').prop('waiting')).toEqual(false);
|
||||
expect(container.querySelector('img.join-flow-header-image')).toBeFalsy();
|
||||
expect(container.querySelector('.join-flow-inner-content')).toBeTruthy();
|
||||
expect(container.querySelector('.join-flow-title')).toBeFalsy();
|
||||
expect(container.querySelector('div.join-flow-description')).toBeFalsy();
|
||||
expect(findByComponentName('NextStepButton').memoizedProps.waiting).toEqual(false);
|
||||
|
||||
component.unmount();
|
||||
unmount();
|
||||
});
|
||||
|
||||
test('clicking submit calls passed in function', () => {
|
||||
const props = {
|
||||
onSubmit: jest.fn()
|
||||
};
|
||||
const component = mountWithIntl(
|
||||
const {container, unmount} = renderWithIntl(
|
||||
<JoinFlowStep
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
component.find('button[type="submit"]').simulate('submit');
|
||||
fireEvent.submit(container.querySelector('button[type="submit"]'));
|
||||
expect(props.onSubmit).toHaveBeenCalled();
|
||||
component.unmount();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import React from 'react';
|
||||
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
import React, {act} from 'react';
|
||||
const defaults = require('lodash.defaultsdeep');
|
||||
import configureStore from 'redux-mock-store';
|
||||
import JoinFlow from '../../../src/components/join-flow/join-flow';
|
||||
import Progression from '../../../src/components/progression/progression.jsx';
|
||||
import RegistrationErrorStep from '../../../src/components/join-flow/registration-error-step';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
describe('JoinFlow', () => {
|
||||
const mockStore = configureStore();
|
||||
|
@ -46,160 +45,200 @@ describe('JoinFlow', () => {
|
|||
});
|
||||
|
||||
const getJoinFlowWrapper = props => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<JoinFlow
|
||||
{...props}
|
||||
/>
|
||||
, {context: {store}}
|
||||
const wrapper = renderWithIntl(
|
||||
<Provider store={store}>
|
||||
<JoinFlow
|
||||
{...props}
|
||||
/>
|
||||
</Provider>,
|
||||
'JoinFlow'
|
||||
);
|
||||
return wrapper
|
||||
.dive() // unwrap redux connect(injectIntl(JoinFlow))
|
||||
.dive(); // unwrap injectIntl(JoinFlow)
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
test('handleCaptchaError gives state with captcha message', () => {
|
||||
test('handleCaptchaError gives state with captcha message', async () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
joinFlowInstance.setState({});
|
||||
joinFlowInstance.handleCaptchaError();
|
||||
await act(() => {
|
||||
joinFlowInstance.setState({});
|
||||
});
|
||||
await act(() => {
|
||||
joinFlowInstance.handleCaptchaError();
|
||||
});
|
||||
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: 'registration.errorCaptcha'
|
||||
errorMsg: 'There was a problem with the CAPTCHA test.'
|
||||
});
|
||||
});
|
||||
|
||||
test('sendAnalytics calls GTM with correct params', () => {
|
||||
test('sendAnalytics calls GTM with correct params', async () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
global.window.dataLayer = {push: jest.fn()};
|
||||
global.window.GA_ID = '1234';
|
||||
joinFlowInstance.sendAnalytics('page-path');
|
||||
await act(() => {
|
||||
joinFlowInstance.sendAnalytics('page-path');
|
||||
});
|
||||
expect(global.window.dataLayer.push).toHaveBeenCalledWith({
|
||||
event: 'join_flow',
|
||||
joinFlowStep: 'page-path'
|
||||
});
|
||||
});
|
||||
|
||||
test('handleAdvanceStep', () => {
|
||||
test('handleAdvanceStep', async () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
joinFlowInstance.setState({formData: {username: 'ScratchCat123'}, step: 2});
|
||||
joinFlowInstance.handleAdvanceStep({email: 'scratchcat123@scratch.mit.edu'});
|
||||
await act(() => {
|
||||
joinFlowInstance.setState({formData: {username: 'ScratchCat123'}, step: 2});
|
||||
});
|
||||
await act(() => {
|
||||
joinFlowInstance.handleAdvanceStep({email: 'scratchcat123@scratch.mit.edu'});
|
||||
});
|
||||
expect(joinFlowInstance.state.formData.username).toBe('ScratchCat123');
|
||||
expect(joinFlowInstance.state.formData.email).toBe('scratchcat123@scratch.mit.edu');
|
||||
expect(joinFlowInstance.state.step).toBe(3);
|
||||
});
|
||||
|
||||
test('when state.registrationError has error message, we show RegistrationErrorStep', () => {
|
||||
test('when state.registrationError has error message, we show RegistrationErrorStep', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
joinFlowWrapper.instance().setState({registrationError: 'halp there is a errors!!'});
|
||||
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
|
||||
const progressionWrapper = joinFlowWrapper.find(Progression);
|
||||
await act(() => {
|
||||
joinFlowWrapper.instance().setState({registrationError: 'halp there is a errors!!'});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
const registrationErrorWrapper = joinFlowWrapper.findAllByComponentName('RegistrationErrorStep');
|
||||
const progressionWrapper = joinFlowWrapper.findAllByComponentName('Progression');
|
||||
expect(registrationErrorWrapper).toHaveLength(1);
|
||||
expect(progressionWrapper).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('when state.registrationError has null error message, we show Progression', () => {
|
||||
test('when state.registrationError has null error message, we show Progression', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
joinFlowWrapper.instance().setState({registrationError: null});
|
||||
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
|
||||
const progressionWrapper = joinFlowWrapper.find(Progression);
|
||||
await act(() => {
|
||||
joinFlowWrapper.instance().setState({registrationError: null});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
const registrationErrorWrapper = joinFlowWrapper.findAllByComponentName('RegistrationErrorStep');
|
||||
const progressionWrapper = joinFlowWrapper.findAllByComponentName('Progression');
|
||||
expect(registrationErrorWrapper).toHaveLength(0);
|
||||
expect(progressionWrapper).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('when state.registrationError has empty error message, we show Progression', () => {
|
||||
test('when state.registrationError has empty error message, we show Progression', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
joinFlowWrapper.instance().setState({registrationError: ''});
|
||||
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
|
||||
const progressionWrapper = joinFlowWrapper.find(Progression);
|
||||
await act(() => {
|
||||
joinFlowWrapper.instance().setState({registrationError: ''});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
const registrationErrorWrapper = joinFlowWrapper.findAllByComponentName('RegistrationErrorStep');
|
||||
const progressionWrapper = joinFlowWrapper.findAllByComponentName('Progression');
|
||||
expect(registrationErrorWrapper).toHaveLength(0);
|
||||
expect(progressionWrapper).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('when numAttempts is 0 and registrationError errorAllowsTryAgain is true, ' +
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value true', () => {
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value true', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 0,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: true,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
await act(() => {
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 0,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: true,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
});
|
||||
});
|
||||
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
|
||||
expect(registrationErrorWrapper.first().props().canTryAgain).toEqual(true);
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
const registrationErrorWrapper = joinFlowWrapper.findAllByComponentName('RegistrationErrorStep');
|
||||
expect(registrationErrorWrapper[0].stateNode.props.canTryAgain).toEqual(true);
|
||||
});
|
||||
|
||||
test('when numAttempts is 1 and registrationError errorAllowsTryAgain is true, ' +
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value true', () => {
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value true', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 1,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: true,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
await act(() => {
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 1,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: true,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
});
|
||||
});
|
||||
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
|
||||
expect(registrationErrorWrapper.first().props().canTryAgain).toEqual(true);
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
const registrationErrorWrapper = joinFlowWrapper.findAllByComponentName('RegistrationErrorStep');
|
||||
expect(registrationErrorWrapper[0].stateNode.props.canTryAgain).toEqual(true);
|
||||
});
|
||||
|
||||
test('when numAttempts is 2 and registrationError errorAllowsTryAgain is true, ' +
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value false', () => {
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value false', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 2,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: true,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
await act(() => {
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 2,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: true,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
});
|
||||
});
|
||||
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
|
||||
expect(registrationErrorWrapper.first().props().canTryAgain).toEqual(false);
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
const registrationErrorWrapper = joinFlowWrapper.findAllByComponentName('RegistrationErrorStep');
|
||||
expect(registrationErrorWrapper[0].memoizedProps.canTryAgain).toEqual(false);
|
||||
});
|
||||
|
||||
test('when numAttempts is 0 and registrationError errorAllowsTryAgain is false, ' +
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value false', () => {
|
||||
'RegistrationErrorStep receives errorAllowsTryAgain prop with value false', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 0,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
await act(() => {
|
||||
joinFlowWrapper.instance().setState({
|
||||
numAttempts: 0,
|
||||
registrationError: {
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: 'halp there is a errors!!'
|
||||
}
|
||||
});
|
||||
});
|
||||
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
|
||||
expect(registrationErrorWrapper.first().props().canTryAgain).toEqual(false);
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
const registrationErrorWrapper = joinFlowWrapper.findAllByComponentName('RegistrationErrorStep');
|
||||
expect(registrationErrorWrapper[0].stateNode.props.canTryAgain).toEqual(false);
|
||||
});
|
||||
|
||||
test('resetState resets entire state, does not leave any state keys out', () => {
|
||||
test('resetState resets entire state, does not leave any state keys out', async () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
Object.keys(joinFlowInstance.state).forEach(key => {
|
||||
joinFlowInstance.setState({[key]: 'Different than the initial value'});
|
||||
await act(() => {
|
||||
Object.keys(joinFlowInstance.state).forEach(key => {
|
||||
joinFlowInstance.setState({[key]: 'Different than the initial value'});
|
||||
});
|
||||
joinFlowInstance.resetState();
|
||||
});
|
||||
joinFlowInstance.resetState();
|
||||
Object.keys(joinFlowInstance.state).forEach(key => {
|
||||
expect(joinFlowInstance.state[key]).not.toEqual('Different than the initial value');
|
||||
});
|
||||
});
|
||||
|
||||
test('resetState makes each state field match initial state', () => {
|
||||
test('resetState makes each state field match initial state', async () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
const stateSnapshot = {};
|
||||
Object.keys(joinFlowInstance.state).forEach(key => {
|
||||
stateSnapshot[key] = joinFlowInstance.state[key];
|
||||
});
|
||||
joinFlowInstance.resetState();
|
||||
await act(() => {
|
||||
joinFlowInstance.resetState();
|
||||
});
|
||||
Object.keys(joinFlowInstance.state).forEach(key => {
|
||||
expect(stateSnapshot[key]).toEqual(joinFlowInstance.state[key]);
|
||||
});
|
||||
});
|
||||
|
||||
test('calling resetState results in state.formData which is not same reference as before', () => {
|
||||
test('calling resetState results in state.formData which is not same reference as before', async () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
joinFlowInstance.setState({
|
||||
formData: defaults({}, {username: 'abcdef'})
|
||||
await act(() => {
|
||||
joinFlowInstance.setState({
|
||||
formData: defaults({}, {username: 'abcdef'})
|
||||
});
|
||||
});
|
||||
const formDataReference = joinFlowInstance.state.formData;
|
||||
joinFlowInstance.resetState();
|
||||
await act(() => {
|
||||
joinFlowInstance.resetState();
|
||||
});
|
||||
expect(formDataReference).not.toBe(joinFlowInstance.state.formData);
|
||||
expect(formDataReference).not.toEqual(joinFlowInstance.state.formData);
|
||||
});
|
||||
|
@ -245,7 +284,7 @@ describe('JoinFlow', () => {
|
|||
const errorsFromResponse =
|
||||
joinFlowInstance.getErrorsFromResponse(null, responseBodyMultipleErrs, {statusCode: 200});
|
||||
const customErrMsg = joinFlowInstance.getCustomErrMsg(errorsFromResponse);
|
||||
expect(customErrMsg).toEqual('registration.problemsAre: "username: This field is required.; ' +
|
||||
expect(customErrMsg).toEqual('The problems are:: "username: This field is required.; ' +
|
||||
'recaptcha: Incorrect, please try again."');
|
||||
});
|
||||
|
||||
|
@ -254,7 +293,7 @@ describe('JoinFlow', () => {
|
|||
const errorsFromResponse =
|
||||
joinFlowInstance.getErrorsFromResponse(null, responseBodySingleErr, {statusCode: 200});
|
||||
const customErrMsg = joinFlowInstance.getCustomErrMsg(errorsFromResponse);
|
||||
expect(customErrMsg).toEqual('registration.problemsAre: "recaptcha: Incorrect, please try again."');
|
||||
expect(customErrMsg).toEqual('The problems are:: "recaptcha: Incorrect, please try again."');
|
||||
});
|
||||
|
||||
test('registrationIsSuccessful returns true when given response body with single error', () => {
|
||||
|
@ -291,14 +330,18 @@ describe('JoinFlow', () => {
|
|||
joinFlowInstance.handleRegistrationResponse(null, responseBodySuccess, {statusCode: 200});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse advances to next step when passed body with success', () => {
|
||||
test('handleRegistrationResponse advances to next step when passed body with success', async () => {
|
||||
const props = {
|
||||
refreshSessionWithRetry: () => (new Promise(resolve => { // eslint-disable-line no-undef
|
||||
resolve();
|
||||
}))
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodySuccess, {statusCode: 200});
|
||||
const joinFlowWrapper = getJoinFlowWrapper(props);
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodySuccess, {statusCode: 200});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
process.nextTick(
|
||||
() => {
|
||||
expect(joinFlowInstance.state.registrationError).toEqual(null);
|
||||
|
@ -308,20 +351,25 @@ describe('JoinFlow', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse when passed body with preset server error', () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodySingleErr, {statusCode: 200});
|
||||
test('handleRegistrationResponse when passed body with preset server error', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodySingleErr, {statusCode: 200});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: 'registration.errorCaptcha'
|
||||
errorMsg: 'There was a problem with the CAPTCHA test.'
|
||||
});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse with failure response, with error fields missing', () => {
|
||||
test('handleRegistrationResponse with failure response, with error fields missing', async () => {
|
||||
const props = {
|
||||
refreshSessionWithRetry: jest.fn()
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
const joinFlowWrapper = getJoinFlowWrapper(props);
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
const responseErr = null;
|
||||
const responseBody = [
|
||||
{
|
||||
|
@ -332,7 +380,10 @@ describe('JoinFlow', () => {
|
|||
const responseObj = {
|
||||
statusCode: 200
|
||||
};
|
||||
joinFlowInstance.handleRegistrationResponse(responseErr, responseBody, responseObj);
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse(responseErr, responseBody, responseObj);
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
expect(joinFlowInstance.props.refreshSessionWithRetry).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: false,
|
||||
|
@ -340,21 +391,26 @@ describe('JoinFlow', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse when passed body with unfamiliar server error', () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodyMultipleErrs, {statusCode: 200});
|
||||
test('handleRegistrationResponse when passed body with unfamiliar server error', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodyMultipleErrs, {statusCode: 200});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: false,
|
||||
errorMsg: 'registration.problemsAre: "username: This field is required.; ' +
|
||||
errorMsg: 'The problems are:: "username: This field is required.; ' +
|
||||
'recaptcha: Incorrect, please try again."'
|
||||
});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse with failure response, with no text explanation', () => {
|
||||
test('handleRegistrationResponse with failure response, with no text explanation', async () => {
|
||||
const props = {
|
||||
refreshSessionWithRetry: jest.fn()
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
const joinFlowWrapper = getJoinFlowWrapper(props);
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
const responseErr = null;
|
||||
const responseBody = [
|
||||
{
|
||||
|
@ -364,7 +420,10 @@ describe('JoinFlow', () => {
|
|||
const responseObj = {
|
||||
statusCode: 200
|
||||
};
|
||||
joinFlowInstance.handleRegistrationResponse(responseErr, responseBody, responseObj);
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse(responseErr, responseBody, responseObj);
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
expect(joinFlowInstance.props.refreshSessionWithRetry).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: false,
|
||||
|
@ -372,29 +431,41 @@ describe('JoinFlow', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse when passed non null outgoing request error', () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
joinFlowInstance.handleRegistrationResponse({}, responseBodyMultipleErrs, {statusCode: 200});
|
||||
test('handleRegistrationResponse when passed non null outgoing request error', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse({}, responseBodyMultipleErrs, {statusCode: 200});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: true
|
||||
});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse when passed status 400', () => {
|
||||
test('handleRegistrationResponse when passed status 400', async () => {
|
||||
const props = {
|
||||
refreshSessionWithRetry: jest.fn()
|
||||
};
|
||||
const joinFlowInstance = getJoinFlowWrapper(props).instance();
|
||||
joinFlowInstance.handleRegistrationResponse({}, responseBodyMultipleErrs, {statusCode: 400});
|
||||
const joinFlowWrapper = getJoinFlowWrapper(props);
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse({}, responseBodyMultipleErrs, {statusCode: 400});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
expect(joinFlowInstance.props.refreshSessionWithRetry).not.toHaveBeenCalled();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: true
|
||||
});
|
||||
});
|
||||
|
||||
test('handleRegistrationResponse when passed status 500', () => {
|
||||
const joinFlowInstance = getJoinFlowWrapper().instance();
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodyMultipleErrs, {statusCode: 500});
|
||||
test('handleRegistrationResponse when passed status 500', async () => {
|
||||
const joinFlowWrapper = getJoinFlowWrapper();
|
||||
const joinFlowInstance = joinFlowWrapper.instance();
|
||||
await act(() => {
|
||||
joinFlowInstance.handleRegistrationResponse(null, responseBodyMultipleErrs, {statusCode: 500});
|
||||
});
|
||||
joinFlowWrapper.rerenderWithIntl();
|
||||
expect(joinFlowInstance.state.registrationError).toEqual({
|
||||
errorAllowsTryAgain: true
|
||||
});
|
||||
|
|
|
@ -1,34 +1,40 @@
|
|||
const React = require('react');
|
||||
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
const Modal = require('../../../src/components/modal/base/modal.jsx');
|
||||
const {
|
||||
renderWithIntl
|
||||
} = require('../../helpers/react-testing-library-wrapper.jsx');
|
||||
|
||||
describe('Modal', () => {
|
||||
test('Close button not shown when showCloseButton false', () => {
|
||||
const showClose = true;
|
||||
const component = shallowWithIntl(
|
||||
<Modal
|
||||
showCloseButton={showClose}
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.modal-content-close').exists()).toBe(true);
|
||||
expect(component.find('img.modal-content-close-img').exists()).toBe(true);
|
||||
renderWithIntl(<Modal
|
||||
isOpen
|
||||
showCloseButton
|
||||
/>);
|
||||
expect(
|
||||
global.document.querySelector('div.modal-content-close')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
global.document.querySelector('img.modal-content-close-img')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Close button shown by default', () => {
|
||||
const component = shallowWithIntl(
|
||||
<Modal />
|
||||
);
|
||||
expect(component.find('div.modal-content-close').exists()).toBe(true);
|
||||
expect(component.find('img.modal-content-close-img').exists()).toBe(true);
|
||||
renderWithIntl(<Modal isOpen />);
|
||||
expect(
|
||||
global.document.querySelector('div.modal-content-close')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
global.document.querySelector('img.modal-content-close-img')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Close button shown when showCloseButton true', () => {
|
||||
const showClose = false;
|
||||
const component = shallowWithIntl(
|
||||
<Modal
|
||||
showCloseButton={showClose}
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.modal-content-close').exists()).toBe(false);
|
||||
expect(component.find('img.modal-content-close-img').exists()).toBe(false);
|
||||
renderWithIntl(<Modal showCloseButton={false} />);
|
||||
expect(
|
||||
global.document.querySelector('div.modal-content-close')
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
global.document.querySelector('img.modal-content-close-img')
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl, shallowWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import React, {act} from 'react';
|
||||
import MuteModal from '../../../src/components/modal/mute/modal';
|
||||
import Modal from '../../../src/components/modal/base/modal';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {fireEvent} from '@testing-library/react';
|
||||
|
||||
|
||||
describe('MuteModalTest', () => {
|
||||
|
@ -11,183 +11,214 @@ describe('MuteModalTest', () => {
|
|||
muteStepContent: ['comment.general.content1']
|
||||
};
|
||||
|
||||
|
||||
test('Mute Modal rendering', () => {
|
||||
const component = shallowWithIntl(
|
||||
renderWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />
|
||||
).dive();
|
||||
expect(component.find('div.mute-modal-header').exists()).toEqual(true);
|
||||
);
|
||||
expect(global.document.querySelector('div.mute-modal-header')).toBeTruthy();
|
||||
|
||||
});
|
||||
|
||||
test('Mute Modal only shows next button on initial step', () => {
|
||||
const component = mountWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />
|
||||
const {findAllByComponentName, instance} = renderWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />,
|
||||
'MuteModal'
|
||||
);
|
||||
|
||||
expect(component.find('div.mute-nav').exists()).toEqual(true);
|
||||
expect(component.find('button.next-button').exists()).toEqual(true);
|
||||
expect(component.find('button.next-button').getElements()[0].props.onClick)
|
||||
.toEqual(component.find('MuteModal').instance().handleNext);
|
||||
expect(component.find('button.close-button').exists()).toEqual(false);
|
||||
expect(component.find('button.back-button').exists()).toEqual(false);
|
||||
|
||||
expect(global.document.querySelector('div.mute-nav')).toBeTruthy();
|
||||
expect(global.document.querySelector('button.next-button')).toBeTruthy();
|
||||
expect(findAllByComponentName('Button')[0].memoizedProps.onClick)
|
||||
.toEqual(instance().handleNext);
|
||||
expect(global.document.querySelector('button.close-button')).toBeFalsy();
|
||||
expect(global.document.querySelector('button.back-button')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('Mute Modal shows extra showWarning step', () => {
|
||||
const component = mountWithIntl(
|
||||
const {findAllByComponentName, instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
showWarning
|
||||
muteModalMessages={defaultMessages}
|
||||
/>
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 1});
|
||||
expect(component.find('button.next-button').exists()).toEqual(true);
|
||||
expect(component.find('button.next-button').getElements()[0].props.onClick)
|
||||
.toEqual(component.find('MuteModal').instance().handleNext);
|
||||
component.find('MuteModal').instance()
|
||||
.handleNext();
|
||||
expect(component.find('MuteModal').instance().state.step).toEqual(2);
|
||||
const muteModalInstance = instance();
|
||||
const nextButton = findAllByComponentName('Button')[0];
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 1});
|
||||
});
|
||||
expect(global.document.querySelector('button.next-button')).toBeTruthy();
|
||||
expect(nextButton.memoizedProps.onClick)
|
||||
.toEqual(muteModalInstance.handleNext);
|
||||
fireEvent.click(global.document.querySelector('button.next-button'));
|
||||
expect(muteModalInstance.state.step).toEqual(2);
|
||||
});
|
||||
|
||||
test('Mute Modal shows back & close button on last step', () => {
|
||||
const component = mountWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />
|
||||
const {findAllByComponentName, instance} = renderWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />,
|
||||
'MuteModal'
|
||||
);
|
||||
// Step 1 is the last step.
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 1});
|
||||
component.update();
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 1});
|
||||
});
|
||||
const buttons = findAllByComponentName('Button');
|
||||
const closeButton = buttons[0];
|
||||
const backButton = buttons[1];
|
||||
|
||||
expect(component.find('div.mute-nav').exists()).toEqual(true);
|
||||
expect(component.find('button.next-button').exists()).toEqual(false);
|
||||
expect(component.find('button.back-button').exists()).toEqual(true);
|
||||
expect(component.find('button.back-button').getElements()[0].props.onClick)
|
||||
.toEqual(component.find('MuteModal').instance().handlePrevious);
|
||||
expect(component.find('button.close-button').exists()).toEqual(true);
|
||||
expect(component.find('button.close-button').getElements()[0].props.onClick)
|
||||
.toEqual(component.find('MuteModal').instance().props.onRequestClose);
|
||||
expect(global.document.querySelector('div.mute-nav')).toBeTruthy();
|
||||
expect(global.document.querySelector('button.next-button')).toBeFalsy();
|
||||
expect(global.document.querySelector('button.back-button')).toBeTruthy();
|
||||
expect(backButton.memoizedProps.onClick)
|
||||
.toEqual(muteModalInstance.handlePrevious);
|
||||
expect(global.document.querySelector('button.close-button')).toBeTruthy();
|
||||
expect(closeButton.memoizedProps.onClick)
|
||||
.toEqual(muteModalInstance.onRequestClose);
|
||||
});
|
||||
|
||||
test('Mute modal sends correct props to Modal', () => {
|
||||
const closeFn = jest.fn();
|
||||
const component = shallowWithIntl(
|
||||
const {findByComponentName} = renderWithIntl(
|
||||
<MuteModal
|
||||
muteModalMessages={defaultMessages}
|
||||
onRequestClose={closeFn}
|
||||
/>
|
||||
).dive();
|
||||
const modal = component.find(Modal);
|
||||
expect(modal).toHaveLength(1);
|
||||
expect(modal.props().showCloseButton).toBe(false);
|
||||
expect(modal.props().isOpen).toBe(true);
|
||||
expect(modal.props().className).toBe('modal-mute');
|
||||
expect(modal.props().onRequestClose).toBe(closeFn);
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
const modal = findByComponentName('Modal');
|
||||
expect(modal.props.showCloseButton).toBe(false);
|
||||
expect(modal.props.isOpen).toBe(true);
|
||||
expect(modal.props.className).toBe('modal-mute');
|
||||
expect(modal.props.onRequestClose).toBe(closeFn);
|
||||
});
|
||||
|
||||
test('Mute modal handle next step', () => {
|
||||
const closeFn = jest.fn();
|
||||
const component = shallowWithIntl(
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
muteModalMessages={defaultMessages}
|
||||
onRequestClose={closeFn}
|
||||
/>
|
||||
).dive();
|
||||
expect(component.instance().state.step).toBe(0);
|
||||
component.instance().handleNext();
|
||||
expect(component.instance().state.step).toBe(1);
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
const muteModalInstance = instance();
|
||||
expect(muteModalInstance.state.step).toBe(0);
|
||||
fireEvent.click(global.document.querySelector('button.next-button'));
|
||||
expect(muteModalInstance.state.step).toBe(1);
|
||||
});
|
||||
|
||||
test('Mute modal handle previous step', () => {
|
||||
const component = shallowWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />
|
||||
).dive();
|
||||
component.instance().setState({step: 1});
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />,
|
||||
'MuteModal'
|
||||
);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 1});
|
||||
});
|
||||
|
||||
component.instance().handlePrevious();
|
||||
expect(component.instance().state.step).toBe(0);
|
||||
fireEvent.click(global.document.querySelector('button.back-button'));
|
||||
expect(muteModalInstance.state.step).toBe(0);
|
||||
});
|
||||
|
||||
test('Mute modal handle previous step stops at 0', () => {
|
||||
const component = shallowWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />
|
||||
).dive();
|
||||
component.instance().setState({step: 0});
|
||||
component.instance().handlePrevious();
|
||||
expect(component.instance().state.step).toBe(0);
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal muteModalMessages={defaultMessages} />,
|
||||
'MuteModal'
|
||||
);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 0});
|
||||
});
|
||||
muteModalInstance.handlePrevious();
|
||||
expect(muteModalInstance.state.step).toBe(0);
|
||||
});
|
||||
|
||||
test('Mute modal asks for feedback if showFeedback', () => {
|
||||
const component = mountWithIntl(
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
showFeedback
|
||||
muteModalMessages={defaultMessages}
|
||||
/>
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 1});
|
||||
component.update();
|
||||
expect(component.find('p.feedback-prompt').exists()).toEqual(true);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 1});
|
||||
});
|
||||
expect(global.document.querySelector('p.feedback-prompt')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Mute modal do not ask for feedback if not showFeedback', () => {
|
||||
const component = mountWithIntl(
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
muteModalMessages={defaultMessages}
|
||||
/>
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 1});
|
||||
component.update();
|
||||
expect(component.find('p.feedback-prompt').exists()).toEqual(false);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 1});
|
||||
});
|
||||
expect(global.document.querySelector('p.feedback-prompt')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('Mute modal asks for feedback on extra showWarning step if showFeedback', () => {
|
||||
const component = mountWithIntl(
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
showFeedback
|
||||
showWarning
|
||||
muteModalMessages={defaultMessages}
|
||||
/>
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 1});
|
||||
component.update();
|
||||
expect(component.find('p.feedback-prompt').exists()).toEqual(false);
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 2});
|
||||
component.update();
|
||||
expect(component.find('p.feedback-prompt').exists()).toEqual(true);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 1});
|
||||
});
|
||||
expect(global.document.querySelector('p.feedback-prompt')).toBeFalsy();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 2});
|
||||
});
|
||||
expect(global.document.querySelector('p.feedback-prompt')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Mute modal does not for feedback on extra showWarning step if not showFeedback', () => {
|
||||
const component = mountWithIntl(
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
showWarning
|
||||
muteModalMessages={defaultMessages}
|
||||
/>
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 1});
|
||||
component.update();
|
||||
expect(component.find('p.feedback-prompt').exists()).toEqual(false);
|
||||
component.find('MuteModal').instance()
|
||||
.setState({step: 2});
|
||||
component.update();
|
||||
expect(component.find('p.feedback-prompt').exists()).toEqual(false);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 1});
|
||||
});
|
||||
expect(global.document.querySelector('p.feedback-prompt')).toBeFalsy();
|
||||
act(() => {
|
||||
muteModalInstance.setState({step: 2});
|
||||
});
|
||||
expect(global.document.querySelector('p.feedback-prompt')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('Mute modal handle go to feedback', () => {
|
||||
const component = shallowWithIntl(
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
muteModalMessages={defaultMessages}
|
||||
/>
|
||||
).dive();
|
||||
component.instance().handleGoToFeedback();
|
||||
expect(component.instance().state.step).toBe(3);
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.handleGoToFeedback();
|
||||
});
|
||||
expect(muteModalInstance.state.step).toBe(3);
|
||||
});
|
||||
|
||||
test('Mute modal submit feedback gives thank you step', () => {
|
||||
const component = shallowWithIntl(
|
||||
const {instance} = renderWithIntl(
|
||||
<MuteModal
|
||||
muteModalMessages={defaultMessages}
|
||||
user={{
|
||||
|
@ -196,9 +227,13 @@ describe('MuteModalTest', () => {
|
|||
token: 'mytoken',
|
||||
thumbnailUrl: 'mythumbnail'
|
||||
}}
|
||||
/>
|
||||
).dive();
|
||||
component.instance().handleFeedbackSubmit('something');
|
||||
expect(component.instance().state.step).toBe(4);
|
||||
/>,
|
||||
'MuteModal'
|
||||
);
|
||||
const muteModalInstance = instance();
|
||||
act(() => {
|
||||
muteModalInstance.handleFeedbackSubmit('something');
|
||||
});
|
||||
expect(muteModalInstance.state.step).toBe(4);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,49 +1,52 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import MuteStep from '../../../src/components/modal/mute/mute-step';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper';
|
||||
|
||||
describe('MuteStepTest', () => {
|
||||
test('Mute Step with no images', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container} = renderWithIntl(
|
||||
<MuteStep
|
||||
header="header text"
|
||||
|
||||
/>
|
||||
/>,
|
||||
'MuteStep'
|
||||
);
|
||||
expect(component.find('div.mute-step').exists()).toEqual(true);
|
||||
expect(component.find('div.mute-header').exists()).toEqual(true);
|
||||
expect(component.find('div.mute-right-column').exists()).toEqual(true);
|
||||
expect(container.querySelector('div.mute-step')).toBeTruthy();
|
||||
expect(container.querySelector('div.mute-header')).toBeTruthy();
|
||||
expect(container.querySelector('div.mute-right-column')).toBeTruthy();
|
||||
// No images and no left column.
|
||||
expect(component.find('img').exists()).toEqual(false);
|
||||
expect(component.find('div.left-column').exists()).toEqual(false);
|
||||
expect(container.querySelector('img')).toBeFalsy();
|
||||
expect(container.querySelector('div.left-column')).toBeFalsy();
|
||||
|
||||
});
|
||||
|
||||
test('Mute Step with side image', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container} = renderWithIntl(
|
||||
<MuteStep
|
||||
sideImg="/path/to/img.png"
|
||||
sideImgClass="side-img"
|
||||
/>
|
||||
/>,
|
||||
'MuteStep'
|
||||
);
|
||||
expect(component.find('div.mute-step').exists()).toEqual(true);
|
||||
expect(component.find('div.mute-header').exists()).toEqual(true);
|
||||
expect(component.find('div.mute-right-column').exists()).toEqual(true);
|
||||
expect(component.find('div.left-column').exists()).toEqual(true);
|
||||
expect(component.find('img.side-img').exists()).toEqual(true);
|
||||
expect(container.querySelector('div.mute-step')).toBeTruthy();
|
||||
expect(container.querySelector('div.mute-header')).toBeTruthy();
|
||||
expect(container.querySelector('div.mute-right-column')).toBeTruthy();
|
||||
expect(container.querySelector('div.left-column')).toBeTruthy();
|
||||
expect(container.querySelector('img.side-img')).toBeTruthy();
|
||||
|
||||
});
|
||||
|
||||
test('Mute Step with bottom image', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container} = renderWithIntl(
|
||||
<MuteStep
|
||||
bottomImg="/path/to/img.png"
|
||||
bottomImgClass="bottom-image"
|
||||
/>
|
||||
/>,
|
||||
'MuteStep'
|
||||
);
|
||||
expect(component.find('div.mute-step').exists()).toEqual(true);
|
||||
expect(component.find('div.mute-header').exists()).toEqual(true);
|
||||
expect(component.find('div.mute-right-column').exists()).toEqual(true);
|
||||
expect(component.find('img.bottom-image').exists()).toEqual(true);
|
||||
expect(container.querySelector('div.mute-step')).toBeTruthy();
|
||||
expect(container.querySelector('div.mute-header')).toBeTruthy();
|
||||
expect(container.querySelector('div.mute-right-column')).toBeTruthy();
|
||||
expect(container.querySelector('img.bottom-image')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
const React = require('react');
|
||||
const {shallowWithIntl, mountWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
import {Provider} from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {fireEvent} from '@testing-library/react';
|
||||
const Navigation = require('../../../src/components/navigation/www/navigation.jsx');
|
||||
const Registration = require('../../../src/components/registration/registration.jsx');
|
||||
const sessionActions = require('../../../src/redux/session.js');
|
||||
|
||||
describe('Navigation', () => {
|
||||
|
@ -17,15 +18,15 @@ describe('Navigation', () => {
|
|||
});
|
||||
|
||||
const getNavigationWrapper = props => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<Navigation
|
||||
{...props}
|
||||
/>
|
||||
, {context: {store}}
|
||||
const wrapper = renderWithIntl(
|
||||
<Provider store={store}>
|
||||
<Navigation
|
||||
{...props}
|
||||
/>
|
||||
</Provider>,
|
||||
'Navigation'
|
||||
);
|
||||
return wrapper
|
||||
.dive() // unwrap redux connect(injectIntl(JoinFlow))
|
||||
.dive(); // unwrap injectIntl(JoinFlow)
|
||||
return wrapper; // unwrap injectIntl(JoinFlow)
|
||||
};
|
||||
|
||||
test('when using old join flow, when registrationOpen is true, iframe shows', () => {
|
||||
|
@ -42,7 +43,7 @@ describe('Navigation', () => {
|
|||
}
|
||||
});
|
||||
const navWrapper = getNavigationWrapper();
|
||||
expect(navWrapper.contains(<Registration />)).toEqual(true);
|
||||
expect(navWrapper.findByComponentName('Registration')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('when using new join flow, when registrationOpen is true, iframe does not show', () => {
|
||||
|
@ -59,7 +60,7 @@ describe('Navigation', () => {
|
|||
}
|
||||
});
|
||||
const navWrapper = getNavigationWrapper();
|
||||
expect(navWrapper.contains(<Registration />)).toEqual(false);
|
||||
expect(navWrapper.findByComponentName('Registration')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('when using old join flow, clicking Join Scratch calls handleRegistrationRequested', () => {
|
||||
|
@ -81,7 +82,7 @@ describe('Navigation', () => {
|
|||
const navInstance = navWrapper.instance();
|
||||
|
||||
// simulate click, with mocked event
|
||||
navWrapper.find('a.registrationLink').simulate('click', {preventDefault () {}});
|
||||
fireEvent.click(navWrapper.container.querySelector('a.registrationLink'));
|
||||
expect(navInstance.props.handleClickRegistration).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -103,7 +104,7 @@ describe('Navigation', () => {
|
|||
const navWrapper = getNavigationWrapper(props);
|
||||
const navInstance = navWrapper.instance();
|
||||
|
||||
navWrapper.find('a.registrationLink').simulate('click', {preventDefault () {}});
|
||||
fireEvent.click(navWrapper.container.querySelector('a.registrationLink'));
|
||||
expect(navInstance.props.handleClickRegistration).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -123,15 +124,9 @@ describe('Navigation', () => {
|
|||
},
|
||||
getMessageCount: jest.fn()
|
||||
};
|
||||
const intlWrapper = mountWithIntl(
|
||||
<Navigation
|
||||
{...props}
|
||||
/>, {context: {store},
|
||||
childContextTypes: {store}
|
||||
});
|
||||
const intlWrapper = getNavigationWrapper(props);
|
||||
|
||||
const navInstance = intlWrapper.children().find('Navigation')
|
||||
.instance();
|
||||
const navInstance = intlWrapper.findByComponentName('Navigation');
|
||||
const twoMin = 2 * 60 * 1000;
|
||||
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), twoMin);
|
||||
expect(navInstance.messageCountTimeoutId).not.toEqual(-1);
|
||||
|
@ -157,15 +152,9 @@ describe('Navigation', () => {
|
|||
},
|
||||
getMessageCount: jest.fn()
|
||||
};
|
||||
const intlWrapper = mountWithIntl(
|
||||
<Navigation
|
||||
{...props}
|
||||
/>, {context: {store},
|
||||
childContextTypes: {store}
|
||||
});
|
||||
const intlWrapper = getNavigationWrapper(props);
|
||||
|
||||
const navInstance = intlWrapper.children().find('Navigation')
|
||||
.instance();
|
||||
const navInstance = intlWrapper.findByComponentName('Navigation');
|
||||
const twoMin = 2 * 60 * 1000;
|
||||
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), twoMin);
|
||||
expect(navInstance.messageCountTimeoutId).not.toEqual(-1);
|
||||
|
@ -190,15 +179,9 @@ describe('Navigation', () => {
|
|||
},
|
||||
getMessageCount: jest.fn()
|
||||
};
|
||||
const intlWrapper = mountWithIntl(
|
||||
<Navigation
|
||||
{...props}
|
||||
/>, {context: {store},
|
||||
childContextTypes: {store}
|
||||
});
|
||||
const intlWrapper = getNavigationWrapper(props);
|
||||
|
||||
const navInstance = intlWrapper.children().find('Navigation')
|
||||
.instance();
|
||||
const navInstance = intlWrapper.findByComponentName('Navigation');
|
||||
// Clear the timers and mocks because componentDidMount
|
||||
// has already called pollForMessages.
|
||||
jest.clearAllTimers();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import NextStepButton from '../../../src/components/join-flow/next-step-button';
|
||||
import Spinner from '../../../src/components/spinner/spinner.jsx';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
|
||||
describe('NextStepButton', () => {
|
||||
const defaultProps = () => ({
|
||||
|
@ -10,23 +9,25 @@ describe('NextStepButton', () => {
|
|||
|
||||
});
|
||||
test('testing spinner does not show and button enabled', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<NextStepButton
|
||||
{...defaultProps()}
|
||||
/>
|
||||
/>,
|
||||
'NextStepButton'
|
||||
);
|
||||
expect(component.find(Spinner).exists()).toEqual(false);
|
||||
expect(component.find('button[type="submit"]').prop('disabled')).toBe(false);
|
||||
expect(findByComponentName('Spinner')).toBeFalsy();
|
||||
expect(container.querySelector('button[type="submit"]').disabled).toBe(false);
|
||||
|
||||
});
|
||||
test('testing spinner does show and button disabled', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<NextStepButton
|
||||
{...defaultProps()}
|
||||
/>
|
||||
waiting
|
||||
/>,
|
||||
'NextStepButton'
|
||||
);
|
||||
component.setProps({waiting: true});
|
||||
expect(component.find(Spinner).exists()).toEqual(true);
|
||||
expect(component.find('button[type="submit"]').prop('disabled')).toBe(true);
|
||||
expect(findByComponentName('Spinner')).toBeTruthy();
|
||||
expect(container.querySelector('button[type="submit"]').disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,32 +1,35 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import OSChooser from '../../../src/components/os-chooser/os-chooser';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper';
|
||||
import {fireEvent} from '@testing-library/react';
|
||||
|
||||
describe('OSChooser', () => {
|
||||
test('calls callback when OS is selected', () => {
|
||||
const onSetOs = jest.fn();
|
||||
const component = mountWithIntl(<OSChooser handleSetOS={onSetOs} />);
|
||||
const {container} = renderWithIntl(<OSChooser handleSetOS={onSetOs} />, 'OSChooser');
|
||||
|
||||
component.find('button').last()
|
||||
.simulate('click');
|
||||
const buttons = container.querySelectorAll('button');
|
||||
fireEvent.click(buttons[buttons.length - 1]);
|
||||
|
||||
expect(onSetOs).toHaveBeenCalledWith('Android');
|
||||
});
|
||||
|
||||
test('has all 4 operating systems', () => {
|
||||
const component = mountWithIntl(<OSChooser />);
|
||||
const {container} = renderWithIntl(<OSChooser />, 'OSChooser');
|
||||
|
||||
expect(component.find('button').length).toEqual(4);
|
||||
expect(container.querySelectorAll('button').length).toEqual(4);
|
||||
});
|
||||
|
||||
test('hides operating systems', () => {
|
||||
const component = mountWithIntl(<OSChooser
|
||||
const {container} = renderWithIntl(<OSChooser
|
||||
hideWindows
|
||||
hideMac
|
||||
hideChromeOS
|
||||
hideAndroid
|
||||
/>);
|
||||
/>,
|
||||
'OSChooser'
|
||||
);
|
||||
|
||||
expect(component.find('button').length).toEqual(0);
|
||||
expect(container.querySelectorAll('button').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,21 +1,48 @@
|
|||
const React = require('react');
|
||||
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
const Page = require('../../../src/components/page/www/page.jsx');
|
||||
const {renderWithIntl} = require('../../helpers/react-testing-library-wrapper.jsx');
|
||||
const {default: configureStore} = require('redux-mock-store');
|
||||
const {Provider} = require('react-redux');
|
||||
const sessionActions = require('../../../src/redux/session.js');
|
||||
|
||||
describe('Page', () => {
|
||||
const mockStore = configureStore();
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore({
|
||||
navigation: {
|
||||
registrationOpen: true,
|
||||
useScratch3Registration: false
|
||||
},
|
||||
session: {
|
||||
status: sessionActions.Status.FETCHED
|
||||
},
|
||||
messageCount: {
|
||||
messageCount: 0
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Do not show donor recognition', () => {
|
||||
const component = shallowWithIntl(
|
||||
<Page />
|
||||
const {findAllByComponentName} = renderWithIntl(
|
||||
<Provider store={store}>
|
||||
<Page />
|
||||
</Provider>,
|
||||
'Page'
|
||||
);
|
||||
expect(component.find('#donor')).toHaveLength(0);
|
||||
expect(findAllByComponentName('DonorRecognition')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Show donor recognition', () => {
|
||||
const component = shallowWithIntl(
|
||||
<Page
|
||||
showDonorRecognition
|
||||
/>
|
||||
const {findAllByComponentName} = renderWithIntl(
|
||||
<Provider store={store}>
|
||||
<Page
|
||||
showDonorRecognition
|
||||
/>
|
||||
</Provider>,
|
||||
'Page'
|
||||
);
|
||||
expect(component.find('#donor')).toHaveLength(1);
|
||||
expect(findAllByComponentName('DonorRecognition')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl, shallowWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import JoinFlowStep from '../../../src/components/join-flow/join-flow-step';
|
||||
import RegistrationErrorStep from '../../../src/components/join-flow/registration-error-step';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper';
|
||||
|
||||
describe('RegistrationErrorStep', () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
const getRegistrationErrorStepWrapper = props => {
|
||||
const wrapper = shallowWithIntl(
|
||||
const wrapper = renderWithIntl(
|
||||
<RegistrationErrorStep
|
||||
sendAnalytics={jest.fn()}
|
||||
{...props}
|
||||
/>
|
||||
/>,
|
||||
'RegistrationErrorStep'
|
||||
);
|
||||
return wrapper
|
||||
.dive(); // unwrap injectIntl()
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
test('registrationError has JoinFlowStep', () => {
|
||||
|
@ -22,7 +21,10 @@ describe('RegistrationErrorStep', () => {
|
|||
canTryAgain: true,
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
const joinFlowStepWrapper =
|
||||
getRegistrationErrorStepWrapper(props).findAllByComponentName(
|
||||
'JoinFlowStep'
|
||||
);
|
||||
expect(joinFlowStepWrapper).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
@ -32,11 +34,11 @@ describe('RegistrationErrorStep', () => {
|
|||
errorMsg: 'halp there is a errors!!',
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
const joinFlowStepInstance = joinFlowStepWrapper.dive();
|
||||
const errMsgElement = joinFlowStepInstance.find('.registration-error-msg');
|
||||
expect(errMsgElement).toHaveLength(1);
|
||||
expect(errMsgElement.text()).toEqual('halp there is a errors!!');
|
||||
const errMsgElement = getRegistrationErrorStepWrapper(
|
||||
props
|
||||
).container.querySelector('.registration-error-msg');
|
||||
expect(errMsgElement).toBeTruthy();
|
||||
expect(errMsgElement.textContent).toEqual('halp there is a errors!!');
|
||||
});
|
||||
|
||||
test('when errorMsg is null, registrationError does not show it', () => {
|
||||
|
@ -45,10 +47,10 @@ describe('RegistrationErrorStep', () => {
|
|||
errorMsg: null,
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
const joinFlowStepInstance = joinFlowStepWrapper.dive();
|
||||
const errMsgElement = joinFlowStepInstance.find('.registration-error-msg');
|
||||
expect(errMsgElement).toHaveLength(0);
|
||||
const errMsgElement = getRegistrationErrorStepWrapper(
|
||||
props
|
||||
).container.querySelector('.registration-error-msg');
|
||||
expect(errMsgElement).toBeFalsy();
|
||||
});
|
||||
|
||||
test('when no errorMsg provided, registrationError does not show it', () => {
|
||||
|
@ -56,18 +58,15 @@ describe('RegistrationErrorStep', () => {
|
|||
canTryAgain: true,
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
const joinFlowStepInstance = joinFlowStepWrapper.dive();
|
||||
const errMsgElement = joinFlowStepInstance.find('.registration-error-msg');
|
||||
expect(errMsgElement).toHaveLength(0);
|
||||
const errMsgElement = getRegistrationErrorStepWrapper(
|
||||
props
|
||||
).container.querySelector('.registration-error-msg');
|
||||
expect(errMsgElement).toBeFalsy();
|
||||
});
|
||||
|
||||
test('logs to analytics', () => {
|
||||
const analyticsFn = jest.fn();
|
||||
mountWithIntl(
|
||||
<RegistrationErrorStep
|
||||
sendAnalytics={analyticsFn}
|
||||
/>);
|
||||
renderWithIntl(<RegistrationErrorStep sendAnalytics={analyticsFn} />);
|
||||
expect(analyticsFn).toHaveBeenCalledWith('join-error');
|
||||
});
|
||||
test('when canTryAgain is true, show tryAgain message', () => {
|
||||
|
@ -76,9 +75,12 @@ describe('RegistrationErrorStep', () => {
|
|||
errorMsg: 'halp there is a errors!!',
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
const joinFlowStepWrapper =
|
||||
getRegistrationErrorStepWrapper(props).findAllByComponentName(
|
||||
'JoinFlowStep'
|
||||
);
|
||||
expect(joinFlowStepWrapper).toHaveLength(1);
|
||||
expect(joinFlowStepWrapper.props().nextButton).toBe('general.tryAgain');
|
||||
expect(joinFlowStepWrapper[0].memoizedProps.nextButton).toBe('Try again');
|
||||
});
|
||||
|
||||
test('when canTryAgain is false, show startOver message', () => {
|
||||
|
@ -87,9 +89,12 @@ describe('RegistrationErrorStep', () => {
|
|||
errorMsg: 'halp there is a errors!!',
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
const joinFlowStepWrapper =
|
||||
getRegistrationErrorStepWrapper(props).findAllByComponentName(
|
||||
'JoinFlowStep'
|
||||
);
|
||||
expect(joinFlowStepWrapper).toHaveLength(1);
|
||||
expect(joinFlowStepWrapper.props().nextButton).toBe('general.startOver');
|
||||
expect(joinFlowStepWrapper[0].memoizedProps.nextButton).toBe('Start over');
|
||||
});
|
||||
|
||||
test('when canTryAgain is missing, show startOver message', () => {
|
||||
|
@ -97,9 +102,12 @@ describe('RegistrationErrorStep', () => {
|
|||
errorMsg: 'halp there is a errors!!',
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
const joinFlowStepWrapper =
|
||||
getRegistrationErrorStepWrapper(props).findAllByComponentName(
|
||||
'JoinFlowStep'
|
||||
);
|
||||
expect(joinFlowStepWrapper).toHaveLength(1);
|
||||
expect(joinFlowStepWrapper.props().nextButton).toBe('general.startOver');
|
||||
expect(joinFlowStepWrapper[0].memoizedProps.nextButton).toBe('Start over');
|
||||
});
|
||||
|
||||
test('when submitted, onSubmit is called', () => {
|
||||
|
@ -108,8 +116,11 @@ describe('RegistrationErrorStep', () => {
|
|||
errorMsg: 'halp there is a errors!!',
|
||||
onSubmit: onSubmit
|
||||
};
|
||||
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
|
||||
joinFlowStepWrapper.props().onSubmit(new Event('event')); // eslint-disable-line no-undef
|
||||
const joinFlowStepWrapper =
|
||||
getRegistrationErrorStepWrapper(props).findByComponentName(
|
||||
'JoinFlowStep'
|
||||
);
|
||||
joinFlowStepWrapper.memoizedProps.onSubmit(new Event('event')); // eslint-disable-line no-undef
|
||||
expect(onSubmit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import React from 'react';
|
||||
import {act} from 'react-dom/test-utils';
|
||||
import React, {act} from 'react';
|
||||
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import AdminPanel from '../../../src/components/adminpanel/adminpanel.jsx';
|
||||
import {
|
||||
StudioAdminPanel, adminPanelOpenClass, adminPanelOpenKey
|
||||
} from '../../../src/views/studio/studio-admin-panel.jsx';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
import {fireEvent} from '@testing-library/react';
|
||||
|
||||
let viewEl;
|
||||
describe('Studio comments', () => {
|
||||
|
@ -21,60 +20,61 @@ describe('Studio comments', () => {
|
|||
describe('gets stored state from local storage if available', () => {
|
||||
test('stored as open', () => {
|
||||
global.localStorage.setItem(adminPanelOpenKey, 'open');
|
||||
const component = mountWithIntl(<StudioAdminPanel showAdminPanel />);
|
||||
const child = component.find(AdminPanel);
|
||||
expect(child.prop('isOpen')).toBe(true);
|
||||
const {findByComponentName} = renderWithIntl(<StudioAdminPanel showAdminPanel />, 'StudioAdminPanel');
|
||||
const child = findByComponentName('AdminPanel');
|
||||
expect(child.memoizedProps.isOpen).toBe(true);
|
||||
});
|
||||
test('stored as closed', () => {
|
||||
global.localStorage.setItem(adminPanelOpenKey, 'closed');
|
||||
const component = mountWithIntl(<StudioAdminPanel showAdminPanel />);
|
||||
const child = component.find(AdminPanel);
|
||||
expect(child.prop('isOpen')).toBe(false);
|
||||
const {findByComponentName} = renderWithIntl(<StudioAdminPanel showAdminPanel />, 'StudioAdminPanel');
|
||||
const child = findByComponentName('AdminPanel');
|
||||
expect(child.memoizedProps.isOpen).toBe(false);
|
||||
});
|
||||
test('not stored', () => {
|
||||
const component = mountWithIntl(
|
||||
<StudioAdminPanel showAdminPanel />
|
||||
const {findByComponentName} = renderWithIntl(
|
||||
<StudioAdminPanel showAdminPanel />, 'StudioAdminPanel'
|
||||
);
|
||||
const child = component.find(AdminPanel);
|
||||
expect(child.prop('isOpen')).toBe(false);
|
||||
const child = findByComponentName('AdminPanel');
|
||||
expect(child.memoizedProps.isOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('non admins', () => {
|
||||
test('should not have localStorage set with a false value', () => {
|
||||
mountWithIntl(<StudioAdminPanel showAdminPanel={false} />);
|
||||
renderWithIntl(<StudioAdminPanel showAdminPanel={false} />);
|
||||
expect(global.localStorage.getItem(adminPanelOpenKey)).toBe(null);
|
||||
});
|
||||
test('should not have css class set even if localStorage contains open key', () => {
|
||||
// Regression test for situation where admin logs out but localStorage still
|
||||
// contains "open", causing extra space to appear
|
||||
global.localStorage.setItem(adminPanelOpenKey, 'open');
|
||||
mountWithIntl(<StudioAdminPanel showAdminPanel={false} />);
|
||||
renderWithIntl(<StudioAdminPanel showAdminPanel={false} />);
|
||||
expect(viewEl.classList.contains(adminPanelOpenClass)).toBe(false);
|
||||
});
|
||||
});
|
||||
test('calling onOpen sets a class on the #viewEl and records in local storage', () => {
|
||||
const component = mountWithIntl(<StudioAdminPanel showAdminPanel />);
|
||||
const child = component.find(AdminPanel);
|
||||
const {container} = renderWithIntl(<StudioAdminPanel showAdminPanel />, 'StudioAdminPanel');
|
||||
const child = container.querySelector('.toggle');
|
||||
expect(viewEl.classList.contains(adminPanelOpenClass)).toBe(false);
|
||||
// `act` is a test-util function for making react state updates sync
|
||||
act(child.prop('onOpen'));
|
||||
fireEvent.click(child);
|
||||
expect(viewEl.classList.contains(adminPanelOpenClass)).toBe(true);
|
||||
expect(global.localStorage.getItem(adminPanelOpenKey)).toBe('open');
|
||||
});
|
||||
test('renders the correct iframe when open', () => {
|
||||
global.localStorage.setItem(adminPanelOpenKey, 'open');
|
||||
const component = mountWithIntl(
|
||||
const {container} = renderWithIntl(
|
||||
<StudioAdminPanel
|
||||
studioId={123}
|
||||
showAdminPanel
|
||||
/>
|
||||
/>,
|
||||
'StudioAdminPanel'
|
||||
);
|
||||
const child = component.find('iframe');
|
||||
expect(child.getDOMNode().src).toMatch('/scratch2-studios/123/adminpanel');
|
||||
const child = container.querySelector('iframe');
|
||||
expect(child.src).toMatch('/scratch2-studios/123/adminpanel');
|
||||
});
|
||||
test('responds to closePanel MessageEvent from the iframe', () => {
|
||||
global.localStorage.setItem(adminPanelOpenKey, 'open');
|
||||
mountWithIntl(<StudioAdminPanel showAdminPanel />);
|
||||
renderWithIntl(<StudioAdminPanel showAdminPanel />);
|
||||
expect(viewEl.classList.contains(adminPanelOpenClass)).toBe(true);
|
||||
act(() => {
|
||||
global.window.dispatchEvent(new global.MessageEvent('message', {data: 'closePanel'}));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
|
||||
import {StudioComments} from '../../../src/views/studio/studio-comments.jsx';
|
||||
import {renderWithIntl} from '../../helpers/react-testing-library-wrapper.jsx';
|
||||
|
||||
// Replace customized studio comment with default comment to avoid redux issues in the test
|
||||
jest.mock('../../../src/views/studio/studio-comment.js', () => (
|
||||
|
@ -13,61 +13,76 @@ describe('Studio comments', () => {
|
|||
|
||||
test('if there are no comments, they get loaded', () => {
|
||||
const loadComments = jest.fn();
|
||||
const component = mountWithIntl(
|
||||
const props = {
|
||||
hasFetchedSession: false,
|
||||
comments: [],
|
||||
handleLoadMoreComments: loadComments
|
||||
};
|
||||
const {rerenderWithIntl} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession={false}
|
||||
comments={[]}
|
||||
handleLoadMoreComments={loadComments}
|
||||
/>
|
||||
{...props}
|
||||
/>,
|
||||
'StudioComments'
|
||||
);
|
||||
expect(loadComments).not.toHaveBeenCalled();
|
||||
component.setProps({hasFetchedSession: true});
|
||||
component.update();
|
||||
rerenderWithIntl(<StudioComments
|
||||
{...props}
|
||||
hasFetchedSession
|
||||
/>);
|
||||
expect(loadComments).toHaveBeenCalled();
|
||||
|
||||
// When updated to have comments, load is not called again
|
||||
loadComments.mockClear();
|
||||
component.setProps({comments: testComments});
|
||||
component.update();
|
||||
rerenderWithIntl(<StudioComments
|
||||
{...props}
|
||||
comments={testComments}
|
||||
/>);
|
||||
expect(loadComments).not.toHaveBeenCalled();
|
||||
|
||||
// When reset to have no comments again, load is called again
|
||||
loadComments.mockClear();
|
||||
component.setProps({comments: []});
|
||||
component.update();
|
||||
rerenderWithIntl(<StudioComments
|
||||
{...props}
|
||||
hasFetchedSession
|
||||
comments={[]}
|
||||
/>);
|
||||
expect(loadComments).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('becoming an admin resets the comments', () => {
|
||||
const resetComments = jest.fn();
|
||||
const component = mountWithIntl(
|
||||
const props = {
|
||||
hasFetchedSession: true,
|
||||
isAdmin: false,
|
||||
comments: testComments,
|
||||
handleResetComments: resetComments
|
||||
};
|
||||
const {rerenderWithIntl} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={testComments}
|
||||
handleResetComments={resetComments}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
expect(resetComments).not.toHaveBeenCalled();
|
||||
|
||||
// When updated to isAdmin=true, reset is called
|
||||
resetComments.mockClear();
|
||||
component.setProps({isAdmin: true});
|
||||
component.update();
|
||||
rerenderWithIntl(<StudioComments
|
||||
{...props}
|
||||
isAdmin
|
||||
/>);
|
||||
expect(resetComments).toHaveBeenCalled();
|
||||
|
||||
// If updated back to isAdmin=false, reset is also called
|
||||
// not currently possible in the UI, but if it was, we'd want to clear comments
|
||||
resetComments.mockClear();
|
||||
component.setProps({isAdmin: false});
|
||||
component.update();
|
||||
rerenderWithIntl();
|
||||
expect(resetComments).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('being an admin on initial render doesnt reset comments', () => {
|
||||
// This ensures that comments don't get reloaded when changing tabs
|
||||
const resetComments = jest.fn();
|
||||
mountWithIntl(
|
||||
renderWithIntl(
|
||||
<StudioComments
|
||||
isAdmin
|
||||
hasFetchedSession
|
||||
|
@ -79,85 +94,90 @@ describe('Studio comments', () => {
|
|||
});
|
||||
|
||||
test('Comments do not show when shouldShowCommentsList is false', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={testComments}
|
||||
shouldShowCommentsList={false}
|
||||
/>
|
||||
/>,
|
||||
'StudioComments'
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(false);
|
||||
expect(container.querySelector('div.studio-compose-container')).toBeTruthy();
|
||||
expect(findByComponentName('TopLevelComment')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('Comments show when shouldShowCommentsList is true', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={testComments}
|
||||
shouldShowCommentsList
|
||||
|
||||
/>
|
||||
/>,
|
||||
'StudioComments'
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(true);
|
||||
expect(container.querySelector('div.studio-compose-container')).toBeTruthy();
|
||||
expect(findByComponentName('TopLevelComment')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Single comment load more shows when shouldShowCommentsList is true', () => {
|
||||
// Make the component think this is a single view.
|
||||
global.window.location.hash = '#comments-6';
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={testComments}
|
||||
shouldShowCommentsList
|
||||
singleCommentId
|
||||
/>
|
||||
/>,
|
||||
'StudioComments'
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(true);
|
||||
expect(component.find('Button').exists()).toBe(true);
|
||||
expect(component.find('button.load-more-button').exists()).toBe(true);
|
||||
expect(container.querySelector('div.studio-compose-container')).toBeTruthy();
|
||||
expect(findByComponentName('TopLevelComment')).toBeTruthy();
|
||||
expect(findByComponentName('Button')).toBeTruthy();
|
||||
expect(container.querySelector('button.load-more-button')).toBeTruthy();
|
||||
global.window.location.hash = '';
|
||||
});
|
||||
|
||||
test('Single comment does not show when shouldShowCommentsList is false', () => {
|
||||
// Make the component think this is a single view.
|
||||
global.window.location.hash = '#comments-6';
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession
|
||||
isAdmin={false}
|
||||
comments={testComments}
|
||||
shouldShowCommentsList={false}
|
||||
singleCommentId
|
||||
/>
|
||||
/>,
|
||||
'StudioComments'
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('TopLevelComment').exists()).toBe(false);
|
||||
expect(component.find('Button').exists()).toBe(false);
|
||||
expect(component.find('button.load-more-button').exists()).toBe(false);
|
||||
expect(container.querySelector('div.studio-compose-container')).toBeTruthy();
|
||||
expect(findByComponentName('TopLevelComment')).toBeFalsy();
|
||||
expect(container.querySelector('Button')).toBeFalsy();
|
||||
expect(container.querySelector('button.load-more-button')).toBeFalsy();
|
||||
global.window.location.hash = '';
|
||||
});
|
||||
|
||||
test('Comment status error shows when shoudlShowCommentsGloballyOffError is true', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container, findByComponentName} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession={false}
|
||||
isAdmin={false}
|
||||
comments={testComments}
|
||||
shouldShowCommentsGloballyOffError
|
||||
/>
|
||||
/>,
|
||||
'StudioComments'
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('CommentingStatus').exists()).toBe(true);
|
||||
expect(container.querySelector('div.studio-compose-container')).toBeTruthy();
|
||||
expect(findByComponentName('CommentingStatus')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Comment status error does not show when shoudlShowCommentsGloballyOffError is false', () => {
|
||||
const component = mountWithIntl(
|
||||
const {container} = renderWithIntl(
|
||||
<StudioComments
|
||||
hasFetchedSession={false}
|
||||
isAdmin={false}
|
||||
|
@ -165,7 +185,7 @@ describe('Studio comments', () => {
|
|||
shouldShowCommentsGloballyOffError={false}
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.studio-compose-container').exists()).toBe(true);
|
||||
expect(component.find('CommentingStatus').exists()).toBe(false);
|
||||
expect(container.querySelector('div.studio-compose-container')).toBeTruthy();
|
||||
expect(container.querySelector('CommentingStatus')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
const React = require('react');
|
||||
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
const {mountWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||
|
||||
const requestSuccessResponse = {
|
||||
requestSucceeded: true,
|
||||
|
@ -33,6 +31,7 @@ jest.mock('../../../src/lib/validate.js', () => (
|
|||
|
||||
// must come after validation mocks, so validate.js will be mocked before it is required
|
||||
const UsernameStep = require('../../../src/components/join-flow/username-step.jsx');
|
||||
const {renderWithIntl} = require('../../helpers/react-testing-library-wrapper.jsx');
|
||||
|
||||
|
||||
describe('UsernameStep tests', () => {
|
||||
|
@ -44,25 +43,27 @@ describe('UsernameStep tests', () => {
|
|||
});
|
||||
|
||||
test('send correct props to formik', () => {
|
||||
const wrapper = shallowWithIntl(<UsernameStep
|
||||
const {instance, findByComponentName} = renderWithIntl(<UsernameStep
|
||||
{...defaultProps()}
|
||||
/>);
|
||||
const formikWrapper = wrapper.dive();
|
||||
expect(formikWrapper.props().initialValues.username).toBe('');
|
||||
expect(formikWrapper.props().initialValues.password).toBe('');
|
||||
expect(formikWrapper.props().initialValues.passwordConfirm).toBe('');
|
||||
expect(formikWrapper.props().initialValues.showPassword).toBe(true);
|
||||
expect(formikWrapper.props().validateOnBlur).toBe(false);
|
||||
expect(formikWrapper.props().validateOnChange).toBe(false);
|
||||
expect(formikWrapper.props().validate).toBe(formikWrapper.instance().validateForm);
|
||||
expect(formikWrapper.props().onSubmit).toBe(formikWrapper.instance().handleValidSubmit);
|
||||
/>,
|
||||
'UsernameStep');
|
||||
const formikComponent = findByComponentName('Formik');
|
||||
const usernameStepInstance = instance();
|
||||
expect(formikComponent.memoizedProps.initialValues.username).toBe('');
|
||||
expect(formikComponent.memoizedProps.initialValues.password).toBe('');
|
||||
expect(formikComponent.memoizedProps.initialValues.passwordConfirm).toBe('');
|
||||
expect(formikComponent.memoizedProps.initialValues.showPassword).toBe(true);
|
||||
expect(formikComponent.memoizedProps.validateOnBlur).toBe(false);
|
||||
expect(formikComponent.memoizedProps.validateOnChange).toBe(false);
|
||||
expect(formikComponent.memoizedProps.validate).toBe(usernameStepInstance.validateForm);
|
||||
expect(formikComponent.memoizedProps.onSubmit).toBe(usernameStepInstance.handleValidSubmit);
|
||||
});
|
||||
|
||||
test('Component does not log if path is /join', () => {
|
||||
const sendAnalyticsFn = jest.fn();
|
||||
|
||||
global.window.history.pushState({}, '', '/join');
|
||||
mountWithIntl(
|
||||
renderWithIntl(
|
||||
<UsernameStep
|
||||
sendAnalytics={sendAnalyticsFn}
|
||||
/>);
|
||||
|
@ -73,7 +74,7 @@ describe('UsernameStep tests', () => {
|
|||
// Make sure '/join' is NOT in the path
|
||||
global.window.history.pushState({}, '', '/');
|
||||
const sendAnalyticsFn = jest.fn();
|
||||
mountWithIntl(
|
||||
renderWithIntl(
|
||||
<UsernameStep
|
||||
sendAnalytics={sendAnalyticsFn}
|
||||
/>);
|
||||
|
@ -86,13 +87,14 @@ describe('UsernameStep tests', () => {
|
|||
};
|
||||
const formData = {item1: 'thing', item2: 'otherthing'};
|
||||
const mockedOnNextStep = jest.fn();
|
||||
const wrapper = shallowWithIntl(
|
||||
const wrapper = renderWithIntl(
|
||||
<UsernameStep
|
||||
{...defaultProps()}
|
||||
onNextStep={mockedOnNextStep}
|
||||
/>
|
||||
/>,
|
||||
'UsernameStep'
|
||||
);
|
||||
const instance = wrapper.dive().instance();
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.handleValidSubmit(formData, formikBag);
|
||||
expect(formikBag.setSubmitting).toHaveBeenCalledWith(false);
|
||||
|
@ -108,8 +110,8 @@ describe('validateUsernameRemotelyWithCache test with successful requests', () =
|
|||
});
|
||||
|
||||
test('validateUsernameRemotelyWithCache calls validate.validateUsernameRemotely', done => {
|
||||
const wrapper = shallowWithIntl(<UsernameStep />);
|
||||
const instance = wrapper.dive().instance();
|
||||
const wrapper = renderWithIntl(<UsernameStep />, 'UsernameStep');
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||
.then(response => {
|
||||
|
@ -122,10 +124,11 @@ describe('validateUsernameRemotelyWithCache test with successful requests', () =
|
|||
});
|
||||
|
||||
test('validateUsernameRemotelyWithCache, called twice with different data, makes two remote requests', done => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UsernameStep />
|
||||
const wrapper = renderWithIntl(
|
||||
<UsernameStep />,
|
||||
'UsernameStep'
|
||||
);
|
||||
const instance = wrapper.dive().instance();
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||
.then(response => {
|
||||
|
@ -148,10 +151,11 @@ describe('validateUsernameRemotelyWithCache test with successful requests', () =
|
|||
});
|
||||
|
||||
test('validateUsernameRemotelyWithCache, called twice with same data, only makes one remote request', done => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UsernameStep />
|
||||
const wrapper = renderWithIntl(
|
||||
<UsernameStep />,
|
||||
'UsernameStep'
|
||||
);
|
||||
const instance = wrapper.dive().instance();
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||
.then(response => {
|
||||
|
@ -187,8 +191,8 @@ describe('validateUsernameRemotelyWithCache test with failing requests', () => {
|
|||
});
|
||||
|
||||
test('validateUsernameRemotelyWithCache calls validate.validateUsernameRemotely', done => {
|
||||
const wrapper = shallowWithIntl(<UsernameStep />);
|
||||
const instance = wrapper.dive().instance();
|
||||
const wrapper = renderWithIntl(<UsernameStep />, 'UsernameStep');
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||
.then(response => {
|
||||
|
@ -201,10 +205,11 @@ describe('validateUsernameRemotelyWithCache test with failing requests', () => {
|
|||
});
|
||||
|
||||
test('validateUsernameRemotelyWithCache, called twice with different data, makes two remote requests', done => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UsernameStep />
|
||||
const wrapper = renderWithIntl(
|
||||
<UsernameStep />,
|
||||
'UsernameStep'
|
||||
);
|
||||
const instance = wrapper.dive().instance();
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||
.then(response => {
|
||||
|
@ -227,10 +232,11 @@ describe('validateUsernameRemotelyWithCache test with failing requests', () => {
|
|||
});
|
||||
|
||||
test('validateUsernameRemotelyWithCache, called 2x w/same data, makes 2 requests, since 1st not stored', done => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UsernameStep />
|
||||
const wrapper = renderWithIntl(
|
||||
<UsernameStep />,
|
||||
'UsernameStep'
|
||||
);
|
||||
const instance = wrapper.dive().instance();
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
|
||||
.then(response => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue