fix(client): remove algolia and hot keys modules from landing pages (#42394)
* fix(client): remove algolia and hot keys from landing pages Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -26,7 +26,7 @@ import {
|
||||
renderSignInEmail
|
||||
} from '../utils';
|
||||
|
||||
import { blocklistedUsernames } from '../../server/utils/constants.js';
|
||||
import { blocklistedUsernames } from '../../../../config/constants';
|
||||
import { wrapHandledError } from '../../server/utils/create-handled-error.js';
|
||||
import { saveUser, observeMethod } from '../../server/utils/rx.js';
|
||||
import { getEmailSender } from '../../server/utils/url-utils';
|
||||
|
@ -395,7 +395,8 @@
|
||||
"analytics": "A bar chart and line graph",
|
||||
"shield": "A shield with a checkmark",
|
||||
"tensorflow": "Tensorflow icon",
|
||||
"algorithm": "Branching nodes"
|
||||
"algorithm": "Branching nodes",
|
||||
"magnifier": "magnifier"
|
||||
},
|
||||
"aria": {
|
||||
"fcc-logo": "freeCodeCamp Logo",
|
||||
|
24
client/src/assets/icons/Magnifier.tsx
Normal file
24
client/src/assets/icons/Magnifier.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Magnifier = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className='sr-only'>{t('icons.Magnifier')}</span>
|
||||
<svg
|
||||
className='ais-SearchBox-submitIcon'
|
||||
height='10'
|
||||
viewBox='0 0 40 40'
|
||||
width='10'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z' />
|
||||
</svg>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Magnifier.displayName = 'Magnifier';
|
||||
export default Magnifier;
|
@ -3,10 +3,17 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { Link, SkeletonSprite } from '../../helpers';
|
||||
import NavLogo from './NavLogo';
|
||||
import SearchBar from '../../search/searchBar/SearchBar';
|
||||
import MenuButton from './MenuButton';
|
||||
import NavLinks from './NavLinks';
|
||||
import './universalNav.css';
|
||||
import { isLanding } from '../../../utils/path-parsers';
|
||||
|
||||
import Loadable from '@loadable/component';
|
||||
|
||||
const SearchBar = Loadable(() => import('../../search/searchBar/SearchBar'));
|
||||
const SearchBarOptimized = Loadable(() =>
|
||||
import('../../search/searchBar/search-bar-optimized')
|
||||
);
|
||||
|
||||
export const UniversalNav = ({
|
||||
displayMenu,
|
||||
@ -17,6 +24,14 @@ export const UniversalNav = ({
|
||||
fetchState
|
||||
}) => {
|
||||
const { pending } = fetchState;
|
||||
|
||||
const search =
|
||||
typeof window !== `undefined` && isLanding(window.location.pathname) ? (
|
||||
<SearchBarOptimized />
|
||||
) : (
|
||||
<SearchBar innerRef={searchBarRef} />
|
||||
);
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={'universal-nav' + (displayMenu ? ' expand-nav' : '')}
|
||||
@ -27,7 +42,7 @@ export const UniversalNav = ({
|
||||
'universal-nav-left' + (displayMenu ? ' display-search' : '')
|
||||
}
|
||||
>
|
||||
<SearchBar innerRef={searchBarRef} />
|
||||
{search}
|
||||
</div>
|
||||
<div className='universal-nav-middle'>
|
||||
<Link id='universal-nav-logo' to='/learn'>
|
||||
|
@ -21,7 +21,6 @@ import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
|
||||
|
||||
import { isBrowser } from '../../../utils';
|
||||
|
||||
import WithInstantSearch from '../search/WithInstantSearch';
|
||||
import OfflineWarning from '../OfflineWarning';
|
||||
import Flash from '../Flash';
|
||||
import Header from '../Header';
|
||||
@ -201,17 +200,15 @@ class DefaultLayout extends Component {
|
||||
/>
|
||||
<style>{fontawesome.dom.css()}</style>
|
||||
</Helmet>
|
||||
<WithInstantSearch>
|
||||
<div className={`default-layout`}>
|
||||
<Header fetchState={fetchState} user={user} />
|
||||
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
|
||||
{hasMessage && flashMessage ? (
|
||||
<Flash flashMessage={flashMessage} onClose={removeFlashMessage} />
|
||||
) : null}
|
||||
{children}
|
||||
</div>
|
||||
{showFooter && <Footer />}
|
||||
</WithInstantSearch>
|
||||
<div className={`default-layout`}>
|
||||
<Header fetchState={fetchState} user={user} />
|
||||
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
|
||||
{hasMessage && flashMessage ? (
|
||||
<Flash flashMessage={flashMessage} onClose={removeFlashMessage} />
|
||||
) : null}
|
||||
{children}
|
||||
</div>
|
||||
{showFooter && <Footer />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import { isEqual } from 'lodash-es';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { searchPageUrl } from '../../../utils/algolia-locale-setup';
|
||||
|
||||
import WithInstantSearch from '../WithInstantSearch';
|
||||
|
||||
import {
|
||||
isSearchDropdownEnabledSelector,
|
||||
isSearchBarFocusedSelector,
|
||||
@ -177,33 +179,39 @@ export class SearchBar extends Component {
|
||||
const placeholder = t('search.placeholder');
|
||||
|
||||
return (
|
||||
<div className='fcc_searchBar' data-testid='fcc_searchBar' ref={innerRef}>
|
||||
<HotKeys handlers={this.keyHandlers} keyMap={this.keyMap}>
|
||||
<div className='fcc_search_wrapper'>
|
||||
<label className='fcc_sr_only' htmlFor='fcc_instantsearch'>
|
||||
{t('search.label')}
|
||||
</label>
|
||||
<ObserveKeys except={['Space']}>
|
||||
<SearchBox
|
||||
focusShortcuts={[83, 191]}
|
||||
onChange={this.handleChange}
|
||||
onFocus={this.handleFocus}
|
||||
onSubmit={this.handleSearch}
|
||||
showLoadingIndicator={false}
|
||||
translations={{ placeholder }}
|
||||
/>
|
||||
</ObserveKeys>
|
||||
{isDropdownEnabled && isSearchFocused && (
|
||||
<SearchHits
|
||||
handleHits={this.handleHits}
|
||||
handleMouseEnter={this.handleMouseEnter}
|
||||
handleMouseLeave={this.handleMouseLeave}
|
||||
selectedIndex={index}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</HotKeys>
|
||||
</div>
|
||||
<WithInstantSearch>
|
||||
<div
|
||||
className='fcc_searchBar'
|
||||
data-testid='fcc_searchBar'
|
||||
ref={innerRef}
|
||||
>
|
||||
<HotKeys handlers={this.keyHandlers} keyMap={this.keyMap}>
|
||||
<div className='fcc_search_wrapper'>
|
||||
<label className='fcc_sr_only' htmlFor='fcc_instantsearch'>
|
||||
{t('search.label')}
|
||||
</label>
|
||||
<ObserveKeys except={['Space']}>
|
||||
<SearchBox
|
||||
focusShortcuts={[83, 191]}
|
||||
onChange={this.handleChange}
|
||||
onFocus={this.handleFocus}
|
||||
onSubmit={this.handleSearch}
|
||||
showLoadingIndicator={false}
|
||||
translations={{ placeholder }}
|
||||
/>
|
||||
</ObserveKeys>
|
||||
{isDropdownEnabled && isSearchFocused && (
|
||||
<SearchHits
|
||||
handleHits={this.handleHits}
|
||||
handleMouseEnter={this.handleMouseEnter}
|
||||
handleMouseLeave={this.handleMouseLeave}
|
||||
selectedIndex={index}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</HotKeys>
|
||||
</div>
|
||||
</WithInstantSearch>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ const CustomHits = connectHits(
|
||||
|
||||
return (
|
||||
<div className='ais-Hits'>
|
||||
<ul className='ais-Hits-list'>
|
||||
<ul className='ais-Hits-list' data-cy='ais-Hits-list'>
|
||||
{allHits.map((hit, i) => (
|
||||
<li
|
||||
className={
|
||||
|
@ -0,0 +1,59 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Magnifier from '../../../assets/icons/Magnifier';
|
||||
import { searchPageUrl } from '../../../utils/algolia-locale-setup';
|
||||
|
||||
const SearchBarOptimized = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const placeholder = t('search.placeholder');
|
||||
const searchUrl: string = searchPageUrl as string;
|
||||
const [value, setValue] = useState('');
|
||||
const onChange = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(event.target.value);
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (value && value.length > 1) {
|
||||
window.open(`${searchUrl}?query=${encodeURIComponent(value)}`, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='fcc_searchBar' data-testid='fcc_searchBar'>
|
||||
<div className='fcc_search_wrapper'>
|
||||
<div className='ais-SearchBox' data-cy='ais-SearchBox'>
|
||||
<form
|
||||
action=''
|
||||
className='ais-SearchBox-form'
|
||||
data-cy='ais-SearchBox-form'
|
||||
onSubmit={onSubmit}
|
||||
role='search'
|
||||
>
|
||||
<input
|
||||
aria-label='Search'
|
||||
autoCapitalize='off'
|
||||
autoComplete='off'
|
||||
autoCorrect='off'
|
||||
className='ais-SearchBox-input'
|
||||
maxLength={512}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
spellCheck='false'
|
||||
type='search'
|
||||
value={value}
|
||||
/>
|
||||
<button
|
||||
className='ais-SearchBox-submit'
|
||||
title='Submit your search query.'
|
||||
type='submit'
|
||||
>
|
||||
<Magnifier />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SearchBarOptimized.displayName = 'SearchBarOptimized';
|
||||
export default SearchBarOptimized;
|
@ -1,56 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Highlight,
|
||||
connectStateResults,
|
||||
connectAutoComplete
|
||||
} from 'react-instantsearch-dom';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
|
||||
import EmptySearch from './EmptySearch';
|
||||
import NoResults from './NoResults';
|
||||
|
||||
import './search-page-hits.css';
|
||||
|
||||
const AllHits = connectAutoComplete(({ hits, currentRefinement }) => {
|
||||
const isHitsEmpty = !hits.length;
|
||||
|
||||
return currentRefinement && !isHitsEmpty ? (
|
||||
<div className='ais-Hits search-page'>
|
||||
<ul className='ais-Hits-list'>
|
||||
{hits.map(result => (
|
||||
<a
|
||||
href={result.url}
|
||||
key={result.objectID}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<li className='ais-Hits-item dataset-node'>
|
||||
<p>
|
||||
<Highlight attribute='title' hit={result} />
|
||||
</p>
|
||||
</li>
|
||||
</a>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<NoResults query={currentRefinement} />
|
||||
);
|
||||
});
|
||||
|
||||
AllHits.displayName = 'AllHits';
|
||||
|
||||
const SearchHits = connectStateResults(({ handleClick, searchState }) => {
|
||||
const isSearchEmpty = isEmpty(searchState) || isEmpty(searchState.query);
|
||||
|
||||
return isSearchEmpty ? (
|
||||
<EmptySearch />
|
||||
) : (
|
||||
<AllHits handleClick={handleClick} />
|
||||
);
|
||||
});
|
||||
|
||||
SearchHits.displayName = 'SearchHits';
|
||||
|
||||
export default SearchHits;
|
@ -1,48 +0,0 @@
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Helmet from 'react-helmet';
|
||||
import { connect } from 'react-redux';
|
||||
import { Index } from 'react-instantsearch-dom';
|
||||
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
import { updateSearchQuery } from '../components/search/redux';
|
||||
import SearchPageHits from '../components/search/searchPage/SearchPageHits';
|
||||
|
||||
import './search.css';
|
||||
|
||||
const propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
updateSearchQuery: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapDispatchToProps = { updateSearchQuery };
|
||||
|
||||
class SearchPage extends Component {
|
||||
componentWillUnmount() {
|
||||
this.props.updateSearchQuery('');
|
||||
}
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet title={`${t('search.label')} | freeCodeCamp.org`} />
|
||||
<Index indexName='news' />
|
||||
<Grid>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<main>
|
||||
<SearchPageHits />
|
||||
</main>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SearchPage.displayName = 'SearchPage';
|
||||
SearchPage.propTypes = propTypes;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withTranslation()(SearchPage));
|
66
client/src/utils/path-parsers.test.js
Normal file
66
client/src/utils/path-parsers.test.js
Normal file
@ -0,0 +1,66 @@
|
||||
/* global describe it expect */
|
||||
|
||||
import { isChallenge, isLanding } from './path-parsers';
|
||||
|
||||
const pathnames = {
|
||||
english: {
|
||||
landing: '/',
|
||||
superBlock: '/learn/responsive-web-design',
|
||||
challenge:
|
||||
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements'
|
||||
},
|
||||
espanol: {
|
||||
landing: '/espanol',
|
||||
superBlock: '/espanol/learn/responsive-web-design',
|
||||
challenge:
|
||||
'/espanol/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements'
|
||||
}
|
||||
};
|
||||
|
||||
describe('isLanding', () => {
|
||||
it('returns a booleans', () => {
|
||||
expect(typeof isLanding('/')).toBe('boolean');
|
||||
});
|
||||
it('return true for Espanol landing pathname', () => {
|
||||
expect(isLanding(pathnames.espanol.landing)).toBe(true);
|
||||
});
|
||||
it('returns false for Espanol super block pathname', () => {
|
||||
expect(isLanding(pathnames.espanol.superBlock)).toBe(false);
|
||||
});
|
||||
it('returns false for Espanol challenge pathname', () => {
|
||||
expect(isLanding(pathnames.espanol.challenge)).toBe(false);
|
||||
});
|
||||
it('returns true for English landing pathname', () => {
|
||||
expect(isLanding(pathnames.english.landing)).toBe(true);
|
||||
});
|
||||
it('returns false for English super block pathname', () => {
|
||||
expect(isLanding(pathnames.english.superBlock)).toBe(false);
|
||||
});
|
||||
it('returns false for English challenge pathname', () => {
|
||||
expect(isLanding(pathnames.english.challenge)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isChallenge', () => {
|
||||
it('returns a boolean', () => {
|
||||
expect(typeof isChallenge('/')).toBe('boolean');
|
||||
});
|
||||
it('returns false for Espanol landing pathname', () => {
|
||||
expect(isChallenge(pathnames.espanol.landing)).toBe(false);
|
||||
});
|
||||
it('returns false for Espanol super block pathname', () => {
|
||||
expect(isChallenge(pathnames.espanol.superBlock)).toBe(false);
|
||||
});
|
||||
it('returns true for Espanol challenge pathname', () => {
|
||||
expect(isChallenge(pathnames.espanol.challenge)).toBe(true);
|
||||
});
|
||||
it('returns false for English landing pathname', () => {
|
||||
expect(isChallenge(pathnames.english.landing)).toBe(false);
|
||||
});
|
||||
it('returns false for English super block pathname', () => {
|
||||
expect(isChallenge(pathnames.english.superBlock)).toBe(false);
|
||||
});
|
||||
it('returns true for English challenge pathname', () => {
|
||||
expect(isChallenge(pathnames.english.challenge)).toBe(true);
|
||||
});
|
||||
});
|
23
client/src/utils/path-parsers.ts
Normal file
23
client/src/utils/path-parsers.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { i18nConstants } from '../../../config/constants';
|
||||
|
||||
const splitPath = (pathname: string): string[] =>
|
||||
pathname.split('/').filter(x => x);
|
||||
|
||||
export const isChallenge = (pathname: string): boolean => {
|
||||
const pathArray = splitPath(pathname);
|
||||
return (
|
||||
(pathArray.length === 4 && pathArray[0]) === 'learn' ||
|
||||
(pathArray.length === 5 && pathArray[1]) === 'learn'
|
||||
);
|
||||
};
|
||||
|
||||
export const isLanding = (pathname: string): boolean => {
|
||||
const pathArray = splitPath(pathname);
|
||||
const isEnglishLanding = pathArray.length === 0;
|
||||
const isI18Landing =
|
||||
pathArray.length === 1 && i18nConstants.includes(pathArray[0]);
|
||||
return isEnglishLanding || isI18Landing;
|
||||
};
|
||||
|
||||
const pathParsers = { isLanding, isChallenge };
|
||||
export default pathParsers;
|
@ -6,6 +6,7 @@ import {
|
||||
DefaultLayout
|
||||
} from '../../src/components/layouts';
|
||||
import FourOhFourPage from '../../src/pages/404';
|
||||
import { isChallenge } from '../../src/utils/path-parsers';
|
||||
|
||||
export default function layoutSelector({ element, props }) {
|
||||
const {
|
||||
@ -18,32 +19,23 @@ export default function layoutSelector({ element, props }) {
|
||||
{element}
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
if (/\/certification\//.test(pathname)) {
|
||||
} else if (/\/certification\//.test(pathname)) {
|
||||
return (
|
||||
<CertificationLayout pathname={pathname}>{element}</CertificationLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const splitPath = pathname.split('/').filter(x => x);
|
||||
|
||||
const isChallenge =
|
||||
(splitPath.length === 4 && splitPath[0]) === 'learn' ||
|
||||
(splitPath.length === 5 && splitPath[1]) === 'learn';
|
||||
|
||||
if (isChallenge) {
|
||||
} else if (isChallenge(pathname)) {
|
||||
return (
|
||||
<DefaultLayout pathname={pathname} showFooter={false}>
|
||||
{element}
|
||||
</DefaultLayout>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DefaultLayout pathname={pathname} showFooter={true}>
|
||||
{element}
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaultLayout pathname={pathname} showFooter={true}>
|
||||
{element}
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
|
||||
layoutSelector.propTypes = {
|
||||
|
@ -4,8 +4,44 @@ for (let i = 0; i < 26; i++) {
|
||||
alphabet = alphabet.concat(String.fromCharCode(97 + i));
|
||||
}
|
||||
|
||||
const i18nConstants = [
|
||||
// reserved paths for localizations
|
||||
'afrikaans',
|
||||
'arabic',
|
||||
'bengali',
|
||||
'catalan',
|
||||
'chinese',
|
||||
'czech',
|
||||
'danish',
|
||||
'dutch',
|
||||
'espanol',
|
||||
'finnish',
|
||||
'french',
|
||||
'german',
|
||||
'greek',
|
||||
'hebrew',
|
||||
'hindi',
|
||||
'hungarian',
|
||||
'italian',
|
||||
'japanese',
|
||||
'korean',
|
||||
'norwegian',
|
||||
'polish',
|
||||
'portuguese',
|
||||
'romanian',
|
||||
'russian',
|
||||
'serbian',
|
||||
'spanish',
|
||||
'swahili',
|
||||
'swedish',
|
||||
'turkish',
|
||||
'ukrainian',
|
||||
'vietnamese'
|
||||
];
|
||||
|
||||
let blocklist = [
|
||||
...alphabet.split(''),
|
||||
...i18nConstants,
|
||||
'about',
|
||||
'academic-honesty',
|
||||
'account',
|
||||
@ -85,40 +121,6 @@ let blocklist = [
|
||||
'user',
|
||||
'username',
|
||||
'wiki',
|
||||
|
||||
// reserved paths for localizations
|
||||
'afrikaans',
|
||||
'arabic',
|
||||
'bengali',
|
||||
'catalan',
|
||||
'chinese',
|
||||
'czech',
|
||||
'danish',
|
||||
'dutch',
|
||||
'espanol',
|
||||
'finnish',
|
||||
'french',
|
||||
'german',
|
||||
'greek',
|
||||
'hebrew',
|
||||
'hindi',
|
||||
'hungarian',
|
||||
'italian',
|
||||
'japanese',
|
||||
'korean',
|
||||
'norwegian',
|
||||
'polish',
|
||||
'portuguese',
|
||||
'romanian',
|
||||
'russian',
|
||||
'serbian',
|
||||
'spanish',
|
||||
'swahili',
|
||||
'swedish',
|
||||
'turkish',
|
||||
'ukrainian',
|
||||
'vietnamese',
|
||||
|
||||
// some more names from https://github.com/marteinn/The-Big-Username-Blacklist-JS/blob/master/src/list.js
|
||||
'.htaccess',
|
||||
'.htpasswd',
|
||||
@ -647,4 +649,5 @@ let blocklist = [
|
||||
'zlib'
|
||||
];
|
||||
|
||||
export const blocklistedUsernames = [...new Set(blocklist)];
|
||||
exports.blocklistedUsernames = [...new Set(blocklist)];
|
||||
exports.i18nConstants = i18nConstants;
|
@ -14,7 +14,7 @@ const clear = () => {
|
||||
});
|
||||
};
|
||||
|
||||
describe('Search bar', () => {
|
||||
describe('Search bar optimized', () => {
|
||||
before(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
@ -23,6 +23,38 @@ describe('Search bar', () => {
|
||||
clear();
|
||||
});
|
||||
|
||||
it('Should render properly', () => {
|
||||
cy.get('[data-cy=ais-SearchBox]').should('be.visible');
|
||||
});
|
||||
|
||||
it('Should not display hits', () => {
|
||||
search('freeCodeCamp');
|
||||
cy.get('[data-cy=ais-Hits-list]').should('not.exist');
|
||||
});
|
||||
|
||||
it('Should open a new tab ', () => {
|
||||
cy.visit('/', {
|
||||
onBeforeLoad(win) {
|
||||
cy.stub(win, 'open').as('open');
|
||||
}
|
||||
});
|
||||
search('freeCodeCamp');
|
||||
cy.get('[data-cy=ais-SearchBox-form]').submit();
|
||||
const queryUrl =
|
||||
'https://www.freecodecamp.org/news/search/?query=freeCodeCamp';
|
||||
cy.get('@open').should('have.been.calledOnceWith', queryUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search bar', () => {
|
||||
before(() => {
|
||||
cy.visit('/learn');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
clear();
|
||||
});
|
||||
|
||||
it('Should render properly', () => {
|
||||
cy.get('.ais-SearchBox').should('be.visible');
|
||||
});
|
||||
@ -30,13 +62,17 @@ describe('Search bar', () => {
|
||||
it('Should accept input and display hits', () => {
|
||||
search('freeCodeCamp');
|
||||
|
||||
cy.get('.ais-Hits-list').children().should('to.have.length.of.at.least', 1);
|
||||
cy.get('[data-cy=ais-Hits-list]')
|
||||
.children()
|
||||
.should('to.have.length.of.at.least', 1);
|
||||
});
|
||||
|
||||
it('Should clear hits when input is cleared', () => {
|
||||
search('freeCodeCamp');
|
||||
|
||||
cy.get('.ais-Hits-list').children().should('to.have.length.of.at.least', 1);
|
||||
cy.get('[data-cy=ais-Hits-list]')
|
||||
.children()
|
||||
.should('to.have.length.of.at.least', 1);
|
||||
|
||||
clear();
|
||||
|
||||
@ -48,7 +84,7 @@ describe('Search bar', () => {
|
||||
|
||||
search('freeCodeCamp');
|
||||
|
||||
cy.get('.ais-Hits-list').children().should('to.have.length.of', 8);
|
||||
cy.get('[data-cy=ais-Hits-list]').children().should('to.have.length.of', 8);
|
||||
});
|
||||
|
||||
it('Should show up to 5 hits when height < 768px', () => {
|
||||
@ -56,13 +92,13 @@ describe('Search bar', () => {
|
||||
|
||||
search('freeCodeCamp');
|
||||
|
||||
cy.get('.ais-Hits-list').children().should('to.have.length.of', 5);
|
||||
cy.get('[data-cy=ais-Hits-list]').children().should('to.have.length.of', 5);
|
||||
});
|
||||
|
||||
it('Should show no hits for queries that do not exist in the Algolia index', () => {
|
||||
search('testtttt');
|
||||
|
||||
cy.get('.ais-Hits-list').children().should('to.have.length.of', 0);
|
||||
cy.get('[data-cy=ais-Hits-list]').children().should('to.have.length.of', 0);
|
||||
|
||||
cy.contains('No tutorials found');
|
||||
});
|
||||
|
@ -54,8 +54,7 @@ describe('Learn Landing page (not logged in)', () => {
|
||||
|
||||
describe('Quotes', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
cy.contains("Get started (it's free)").click();
|
||||
cy.login();
|
||||
});
|
||||
|
||||
it('Should show a quote', () => {
|
||||
@ -73,8 +72,7 @@ describe('Quotes', () => {
|
||||
|
||||
describe('Superblocks and Blocks', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
cy.contains("Get started (it's free)").click();
|
||||
cy.login();
|
||||
});
|
||||
|
||||
it('Has all superblocks visible', () => {
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
describe('Settings', () => {
|
||||
it('should be possible to reset your progress', () => {
|
||||
cy.visit('/');
|
||||
cy.contains("Get started (it's free)").click();
|
||||
cy.login();
|
||||
cy.visit('/settings');
|
||||
cy.contains('Reset all of my progress').click();
|
||||
cy.contains('Reset everything. I want to start from the beginning').click();
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
describe('Username input field', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
cy.contains("Get started (it's free)").click({ force: true });
|
||||
cy.login();
|
||||
cy.visit('/settings');
|
||||
|
||||
// Setting aliases here
|
||||
|
@ -35,7 +35,8 @@
|
||||
|
||||
Cypress.Commands.add('login', () => {
|
||||
cy.visit('/');
|
||||
cy.contains("Get started (it's free)").click({ force: true });
|
||||
cy.contains("Get started (it's free)").click();
|
||||
cy.contains('Welcome back');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('resetUsername', () => {
|
||||
|
Reference in New Issue
Block a user