I'm trying to write tests for the rendering function Game(), which is for a Connect 4 react page.
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import * as useGame from "./hooks/use-game";
import Game from ".";
const STATE_SPY = jest.spyOn(useGame, "default");
STATE_SPY.mockReturnValue({
winner: "",
dimensions: {
numRows: 4,
numCols: 4,
},
squares: [
["", "", "", ""],
["", "", "", ""],
["", "", "", ""],
["", "", "", ""],
],
});
const { container } = render(<Game />, document.getElementById("game"));
When I run the tests with npm test, I get Target container is not a DOM element.
If I create an element around the error I get a different error:
const { container } = render(<Game />, document.createElement("div"));
TypeError: Cannot destructure property 'container' of '(0 , _reactDom.render)(...)' as it is null.
Any idea how I need to write this render() function properly? I've searched all over the Internet, but it seems that I have set this up in a pretty standard way.
Here's the code being tested:
import React from "react";
import { useGame } from "./hooks";
import { Board, Settings } from "./components";
import { DARK_SYMBOL, LIGHT_SYMBOL, UseStyles } from "styles/styles";
import { dimensionsFormInput } from "types/form-inputs";
const INIT_ROW = 6;
const INIT_COL = 7;
// Game keeps track of the active player and winners of the Connect Four game
export default function Game(): JSX.Element {
const {
dimensions,
setDimensions,
squares,
darkIsNext,
winner,
handleSquareClick,
} = useGame(INIT_ROW, INIT_COL);
const classes = UseStyles();
return (
<div id="game">
<Board
squares={squares}
onClick={(row: number, col: number) => handleSquareClick(row, col)}
winner={winner}
/>
<span className={classes.blueText}>
{winner.length > 0
? "Winner is " + winner
: "Next piece: ".concat(darkIsNext ? DARK_SYMBOL : LIGHT_SYMBOL)}
</span>
<Settings
dimensions={dimensions}
onSubmit={(data: dimensionsFormInput) => setDimensions(data)}
/>
</div>
);
}
did you try to create your own render function?, something like this, adapting the function to your needs, and use it in your test:
import React from 'react';
import { render as rtlRender } from '#testing-library/react';
import ConnectedRouter from 'react-router-redux/ConnectedRouter';
import { Provider } from 'react-redux';
import ReduxConnectedIntlProvider from '../ReduxConnectedIntlProvider';
import ThemeContext from '../context/theme-context';
import { store } from '../store';
import theme from '../theme/theme';
import { createMemoryHistory } from 'history';
const render = (
ui,
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
...renderOptions
} = {}
) => {
const Wrapper = ({ children }) => {
return (
<Provider store={store}>
<ReduxConnectedIntlProvider>
<ThemeContext.Provider value={theme}>
<ConnectedRouter history={history}>{children}</ConnectedRouter>
</ThemeContext.Provider>
</ReduxConnectedIntlProvider>
</Provider>
);
};
return { ...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }), history };
};
// re-export everything
export * from '#testing-library/react';
// override render method
export { render };
The code for the ReduxConnectedIntlProvider:
import { connect } from 'react-redux';
import { IntlProvider } from 'react-intl';
function mapStateToProps(state) {
const { language, messages } = state.intl;
return { locale: language, key: language, messages };
}
export default connect(mapStateToProps)(IntlProvider);
Regards.
This import is correct? import Game from "." i think that the correct is: import Game from "./"
Instead of using render from ReactDOM, I used render from 1#testing-library/react`. This allows me to conduct the tests I want to.
Related
Am new in writing testcases using React Test library.
Here is my component
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
class MyContainer extends React.Component {
static propTypes = {
graphicalData: PropTypes.object.isRequired,
};
render() {
const { graphicalData } = this.props;
return (
graphicalData && (
<div>
/////some action and rendering
</div>
))}
}
const mapStateToProps = (state) => {
return {
graphicalData: state.design.contents ? state.design.contents.graphicalData : {},
};
};
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(MyContainer)));
So i am writing my test case file using React Testing library
import React from 'react';
import '#testing-library/jest-dom';
import { render, cleanup, shallow } from '#testing-library/react';
import MyContainer from '../../MyContainer';
import configureMockStore from 'redux-mock-store';
import ReactDOM from 'react-dom';
const mockStore = configureMockStore();
import { Provider } from 'react-redux';
const store = mockStore({
state: {
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
},
});
afterEach(cleanup);
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<MyContainer />
</Provider>,
div
);
ReactDOM.unmountComponentAtNode(div);
});
Am not sure what went wrong , my idea is to check if the component loads without crashing , also if the cars array length is greater than 0 , check something rendered on page.
But am getting some error, any help with example or suggestion will save my day
The error seems correct. Check the structure that you have passed into the mockStore() function.
It is
state: {
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
},
So, when you access in your MyContainer, you should access it as
state.state.design.contents
It is just that you have an extra hierarchy in the state object.
Or, if you don't want to change the component, change the structure of the state passed into your mockStore() method as:
const store = mockStore({
design: {
contents: {
graphicalModel: { cars: [{},{}], bikes: [{},{}] },
},
},
});
I have the following component, using Flow:
//#flow
import React, { type Node } from 'react';
import { useIntl } from 'react-intl';
type Props = { balance: Object };
const AvailableDiscount = ({ balance }: Props): Node => {
const { formatMessage, locale } = useIntl();
return (
<div>
{formatMessage({ id: 'XAM_DISCOUNT_DETAILS' })}: {balance.value}
</>
);
};
And while testing it, I seem to have a problem when trying it so mount it with shallow, using Enzyme:
// #flow
import { mount, shallow } from 'enzyme';
import React from 'react';
import { IntlProvider } from 'react-intl';
import balance from '../../../utils/testHelpers/testData/customerBalance';
import AvailableDiscount from './AvailableDiscount';
describe('AvailableDiscount', () => {
it('renders correctly', () => {
const component = <AvailableDiscount balance={balance} />;
const wrappingOptions = {
wrappingComponent: IntlProvider,
wrappingComponentProps: {
locale: 'en',
defaultLocale: 'en',
messages: {},
},
};
const mountedComponent = mount(component, wrappingOptions); // <-- This works
const shallowComponent = shallow(component, wrappingOptions); // <-- This does NOT work
});
});
It tells me that the component does not seem to be wrapped in the provider.
While this seems to work for mount, shallow keeps giving me this error. Why could this be?
I'm trying to use hooks but, I'm not able to figure out the problem which is appearing. I want to set the token value in the useEffect but Im not able to define it.
import React, { useEffect } from "react";
import "./App.css";
import Login from "./Login";
import { getTokenFromUrl } from "./spotify";
import SpotifyWebApi from "spotify-web-api-js";
import Player from "./Player";
import { useDataLayerValue } from "./DataLayer";
const spotify = new SpotifyWebApi();
function App() {
const [{ token }, dispatch] = useDataLayerValue();
useEffect(() => {
const hash = getTokenFromUrl();
window.location.hash = "";
let _token = hash.access_token;
if (_token) {
dispatch({
type: "SET_TOKEN",
token: _token,
});
spotify.setAccessToken(_token);
spotify.getMe().then((user) => {
dispatch({
type: "SET_USER",
user: user,
});
});
}
console.log(token);
}, [dispatch, token]);
return <div className="App">{token ? <Player /> : <Login />}</div>;
}
export default App;
import React, { createContext, useContext, useReducer } from "react";
export const DataLayerContext = createContext();
export const DataLayer = ({ reducer, inititalState, children }) => (
<DataLayerContext.Provider value={useReducer(reducer, inititalState)}>
{children}
</DataLayerContext.Provider>
);
export const useDataLayerValue = () => useContext(DataLayerContext);
export const initialState = {
user: null,
playlists: [],
playing: false,
item: null,
token: null,
};
const reducer = (state, action) => {
switch (action.type) {
case "SET_USER":
return {
...state,
user: action.user,
};
case "SET_TOKEN":
return {
...state,
token: action.token,
};
default:
return state;
}
};
export default reducer;
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { DataLayer } from "./DataLayer";
import reducer, { initialState } from "./reducer";
ReactDOM.render(
<React.StrictMode>
<DataLayer initialState={initialState} reducer={reducer}>
<App />
</DataLayer>
</React.StrictMode>,
document.getElementById("root")
);
It is throwing the following error: TypeError: Cannot read property 'token' of undefined.
Im not able to understand why it is saying undefined. Can anyone please help me to do this.
The problem is that there appears to be no DataLayer being rendered, thus DataLayerContext remains undefined and then useDataLayerValue() returns undefined.
Long story short: You need to wrap your App component in a DataLayer and/or change export const DataLayerContext = createContext(<Default value with a token>)
This is a really long post, but I really need some help :/
I will be eternally grateful if someone would be able to help.
I have managed to get Auth0 working for an application i am working on with just react. It is an Overwatch SR tracker, and is essentially just a spreadsheet so I wasn't too concerned with protecting backend routes when I make them. There isn't any private information there.
My application state/props network became too complicated to manage, and through the process of implementing redux I simply cannot get it to work. I've been at it for three days, and I'm running out of ideas. Do I need Thunk with my current Auth setup to do this? I would imagine it is async since it needs to go get something that isnt there.
Granted I am a junior Dev, and dont have much experience with authentication. Can someone take a look at my working react application and guide me in the direction of what i may need to do to set it up with redux? I do have an understanding of redux flow, so if the proper method to do this was explained to me i feel i might get it.
here is some code:
my Auth.js file :
/*eslint no-restricted-globals: 0 */
import auth0 from "auth0-js";
import jwtDecode from 'jwt-decode';
const LOGIN_SUCCESS_PAGE = '/menu';
const LOGIN_FAILURE_PAGE = '/';
export default class Auth {
auth0 = new auth0.WebAuth({
domain: "redacted.auth0.com",
clientID: "redacted",
redirectUri: "http://localhost:3000/callback",
audience: "https://redacted.auth0.com/userinfo",
responseType: "token id_token",
scope: "openid profile"
});
constructor() {
this.login = this.login.bind(this);
}
login() {
this.auth0.authorize();
}
handleAuthentication() {
this.auth0.parseHash((err, authResults) => {
if (authResults && authResuslts.accessToken && authResults.idToken) {
let expiresAt = JSON.stringify((authResults.expiresIn) * 1000 + new Date().getTime());
localStorage.setItem("access_token", authResults.accessToken);
localStorage.setItem("id_token", authResults.idToken);
localStorage.setItem("expires_at", expiresAt);
location.hash = "";
location.pathname = LOGIN_SUCCESS_PAGE;
} else if (err) {
location.pathname = LOGIN_FAILURE_PAGE;
console.log(err);
}
});
}
isAuthenticated() {
let expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return new Date().getTime() < expiresAt;
}
logout() {
localStorage.removeItem("access_token");
localStorage.removeItem("id_token");
localStorage.removeItem('expires_at');
location.pathname = LOGIN_FAILURE_PAGE;
}
getProfile() {
if (localStorage.getItem("id_token")) {
console.log(jwtDecode(localStorage.getItem("id_token")))
console.log(localStorage.getItem("id_token"));
return jwtDecode(localStorage.getItem("id_token"));
} else {
return {
name: 'Anon',
nickname: 'Anon',
picture: 'placeholder',
uid: null,
}
}
}
}
my index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import Auth from './Auth';
import { BrowserRouter } from 'react-router-dom';
const auth = new Auth();
let state = {};
window.setState = (changes) => {
state = Object.assign({}, state, changes)
ReactDOM.render(
<BrowserRouter>
<App {...state} />
</BrowserRouter>,
document.getElementById('root'));
}
/* eslint no-restricted-globals: 0*/
let getUserProfile = auth.getProfile();
let initialState = {
owSrTrackInfo: {
infoSaved: false,
accounts: [],
},
user: getUserProfile,
location: location.pathname.replace(/^\/?|\/$/g, ""),
auth,
}
window.setState(initialState);
registerServiceWorker();
my App.js file:
import React, { Component } from "react";
import "./App.css";
import Main from "./Components/Main/Main";
import Menu from "./Pages/Menu/Menu";
import NotFound from "./Components/NotFound/NotFound";
import Callback from './Components/Callback/Callback';
import Header from './Components/Header/Header';
class App extends Component {
render() {
let mainComponent = "";
switch (this.props.location) {
case "":
mainComponent = <Main {...this.props} />;
break;
case "callback":
mainComponent = <Callback />
break;
case "menu":
mainComponent = this.props.auth.isAuthenticated() ? < Menu {...this.props} /> : <NotFound />;
break;
default:
mainComponent = <NotFound />;
}
return (
<div className="app">
<Header {...this.props} />
{mainComponent}
</div>
);
}
}
export default App;
my Callback.js component:
import React, {Component} from 'react';
import Auth from '../../Auth'
export default class Callback extends Component {
componentDidMount() {
const auth = new Auth();
auth.handleAuthentication();
}
render() {
return(
<p className="loading">Loading.....</p>
)
}
}
My current MAIN.js component:
import React, { Component } from "react";
export default class Main extends Component {
render() {
console.log(this.props.auth.getProfile())
return (
<div className="container">
<div className='container--logged-out'>
<h1 className="heading u-margin-bottom-small">welcome to redacteds' overwatch sr tracker</h1>
<p>Hello there {this.props.user.nickname}! Sign in single click or email via Auth0 so we can save your results, and make the app usable by more than one person. I intend for more than one person to use this, so just to launch it and so the app knows your spreadsheet from someone elses I'll tie each user to their own UID. Feel free to come back, log in, and get your spreadsheet for the season back anytime.</p>
</div>
Go to the app menu!
<button onClick={() =>this.props.auth.getProfile()}>asdgkljsdngk</button>
</div>
);
}
}
my current HEADER.js component:
import React, { Component } from 'react';
export default class Header extends Component {
render() {
return (
<header className="header">
<h1 className='header__text'>SR TRACKER</h1>
{this.props.auth.isAuthenticated() ?
<button className='btn btn--logout' onClick={() => this.props.auth.logout()}>Logout</button>
:
<button className='btn btn--login' onClick={() => this.props.auth.login()}>Login or Sign Up</button>}
</header>
)
}
}
I simply want to map this authentication to a redux store instead to be consitent with the rest of my app (when redux is implemented) I have blown it away and started over multiple times, but a rough idea of what my redux flow might look like is like this template i use and have successfully implemented several times:
redux store:
import { createStore, compose, applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import rootReducer from './reducers/rootReducer';
export default function configureStore(initialState) {
const middleware = [
createLogger({
collapsed: false,
duration: true,
diff: true,
}),
thunk,
];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.devToolsExtension ? window.devToolsExtension() : format => format, // add support for Redux dev tools),
),
);
return store;
}
actionTypes.js in actions folder:
const actions = {
GET_FRIENDS: 'GET_FRIENDS',
REMOVE_FRIEND: 'REMOVE_FRIEND',
GET_MOVIES: 'GET_MOVIES',
GET_MOVIES_SUCCESS: 'GET_MOVIES_SUCCESS',
GET_MOVIES_FAILURE: 'GET_MOVIES_FAILURE',
DEVIN_FUN: 'DEVIN_FUN',
};
export default actions;
Sample actions page:
import axios from 'axios';
import actionTypes from './actionTypes';
export const getMoviesSuccess = data => {
return {
type: actionTypes.GET_MOVIES_SUCCESS,
data,
};
};
export const getMoviesFailure = () => {
return {
type: actionTypes.GET_MOVIES_FAILURE,
};
};
export const devinIsHavingFun = () => {
return {
type: actionTypes.DEVIN_FUN,
};
};
export const retrieveMovies = () => {
return function(dispatch) {
const API_KEY = 'trilogy';
dispatch(devinIsHavingFun());
axios
.get(`http://www.omdbapi.com?apikey=${API_KEY}&s=frozen`)
.then(data => {
dispatch(getMoviesSuccess(data.data.Search));
})
.catch(error => {
console.log(error);
dispatch(getMoviesFailure());
});
};
};
in the reducers folder wed have some files like initialState.js and root reducer that look like this respectively:
initialState.js:
export default {
friends: [],
movies: [],
};
rootReducer.js:
import { combineReducers } from 'redux';
import friends from './friendReducer';
import movies from './movieReducer';
const rootReducer = combineReducers({
friends,
movies,
});
export default rootReducer;
and a sample reducer:
import actionTypes from '../actions/actionTypes';
import initialState from './initialState';
export default function movieReducer(state = initialState.movies, action) {
switch (action.type) {
case actionTypes.GET_MOVIES_SUCCESS: {
return action.data;
}
default: {
return state;
}
}
}
I just dont know what to do. Do i need to use thunk? am I overthinking this? I'm pulling my hair out.
I also do connect my components in this fashion when redux is implemented :
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as friendActionCreators from './actions/friendActions';
import * as movieActionCreators from './actions/movieActions';
....................
function mapStateToProps(state) {
return {
myFriends: state.friends,
movies: state.movies,
};
}
function mapDispatchToProps(dispatch) {
return {
friendActions: bindActionCreators(friendActionCreators, dispatch),
movieActions: bindActionCreators(movieActionCreators, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Please let me know if anyone can point me in the right direction. thank you so much in advance.
I'm trying to figure out how to change language using React-Intl. This is my first React App and it has been made with create-react-app, I'm not using Redux nor Flux.
In my index.js I have the following code:
import React from 'react';
import ReactDOM from 'react-dom';
import TodoApp from './components/TodoApp';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
// Bootstrap CSS libraries
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';
import { IntlProvider, addLocaleData } from 'react-intl';
import intlEN from 'react-intl/locale-data/en';
import intlES from 'react-intl/locale-data/es';
import intlMessagesES from './i18n/locales/es.json';
import intlMessagesEN from './i18n/locales/en.json';
addLocaleData([...intlEN, ...intlES]);
/* Define your translations */
let i18nConfig = {
locale: 'es',
messages: intlMessagesES
};
let changeLanguage = (lang) => {
i18nConfig = { locale: lang, messages: intlMessagesEN };
return i18nConfig;
}
ReactDOM.render(
<IntlProvider locale={ i18nConfig.locale } key={ i18nConfig.locale } messages={ i18nConfig.messages }>
<TodoApp onChangeLanguage={changeLanguage} />
</IntlProvider>,
document.getElementById('root'));
registerServiceWorker();
TodoApp is sending a string on 'lang' parameter by props (i.e.: 'es' or 'en'), when I change i18nConfig nothing seems to change with IntlProvider. My thought was that change my i18nConfig variable then all my app would change language as well.
I have FormattedMessages in TodoApp and my two JSON messages are filled like this:
// i18n/locales/en.json
{
"footer.add.placeholder": "Enter a name ...",
"footer.add.priority0.text": "No priority",
"footer.add.priority1.text": "Priority 1",
...
}
Do you know what am I missing on my code ?? Maybe I have not understand something right about React-Intl. Any advice will be helpful, thank you.
It works if you remove all from root:
ReactDOM.render(<TodoApp />, document.getElementById('root'));
But now we change TodoApp component like this:
1) We add 'locale' as component state and import React-Intl:
import { IntlProvider, addLocaleData } from 'react-intl';
import intlEN from 'react-intl/locale-data/en';
import intlES from 'react-intl/locale-data/es';
import intlMessagesES from './../i18n/locales/es.json';
import intlMessagesEN from './../i18n/locales/en.json';
addLocaleData([...intlEN, ...intlES]);
/* Define your default translations */
let i18nConfig = {
locale: 'en',
messages: intlMessagesEN
};
2) Change our changeLanguage function (this time called 'onChangeLanguage'), this function receives 'lang' from a subComponent language selector:
onChangeLanguage(lang) {
switch (lang) {
case 'ES': i18nConfig.messages = intlMessagesES; break;
case 'EN': i18nConfig.messages = intlMessagesEN; break;
default: i18nConfig.messages = intlMessagesEN; break;
}
this.setState({ locale: lang });
i18nConfig.locale = lang;
}
And finally render:
render() {
return (
<IntlProvider key={ i18nConfig.locale } locale={ i18nConfig.locale } messages={ i18nConfig.messages }>
<div>
<Header onChangeLanguage={this.onChangeLanguage} />
// Other components ...
</div>
</IntlProvider>
);
}
If someone doesn't understand at all, ask in comments! Thanks to #TomásEhrich
With a new React Context API it is quite easy to do. Create a wrapper:
import React from "react";
import Types from "prop-types";
import { IntlProvider, addLocaleData } from "react-intl";
import en from "react-intl/locale-data/en";
import de from "react-intl/locale-data/de";
import deTranslation from "../../lang/de";
import enTranslation from "../../lang/en";
addLocaleData([...en, ...de]);
const Context = React.createContext();
class IntlProviderWrapper extends React.Component {
constructor(...args) {
super(...args);
this.switchToEnglish = () =>
this.setState({ locale: "en", messages: enTranslation });
this.switchToDeutsch = () =>
this.setState({ locale: "de", messages: deTranslation });
// pass everything in state to avoid creating object inside render method (like explained in the documentation)
this.state = {
locale: "en",
messages: enTranslation,
switchToEnglish: this.switchToEnglish,
switchToDeutsch: this.switchToDeutsch
};
}
render() {
const { children } = this.props;
const { locale, messages } = this.state;
return (
<Context.Provider value={this.state}>
<IntlProvider
key={locale}
locale={locale}
messages={messages}
defaultLocale="en"
>
{children}
</IntlProvider>
</Context.Provider>
);
}
}
export { IntlProviderWrapper, Context as IntlContext };
And then use that Provider and Consumer:
import { Provider } from "react-redux";
import { IntlProviderWrapper } from "./IntlContext";
class App extends Component {
render() {
return (
<Provider store={store}>
<IntlProviderWrapper>
...
</IntlProviderWrapper>
</Provider>
);
}
}
somewhere in the app:
import React from "react";
import { Text, Button } from "native-base";
import { IntlContext } from "../IntlContext";
const LanguageSwitch = () => (
<IntlContext.Consumer>
{({ switchToEnglish, switchToDeutsch }) => (
<React.Fragment>
<button onClick={switchToEnglish}>
English
</button>
<button onClick={switchToDeutsch}>
Deutsch
</button>
</React.Fragment>
)}
</IntlContext.Consumer>
);
// with hooks
const LanguageSwitch2 = () => {
const { switchToEnglish, switchToDeutsch } = React.useContext(IntlContext);
return (
<>
<button onClick={switchToEnglish}>English</button>
<button onClick={switchToDeutsch}>Deutsch</button>
</>
);
};
export default LanguageSwitch;
Example on CodeSandbox
Here is the relevant repository with a more general solution.
Note: at the moment react-intl is still using an old context API but in the future solution like this might work out-of-the-box.
you can use redux manage your locale and localeMessage. just add a key in IntlProvider.
import React, { Component } from 'react';
import { IntlProvider } from 'react-intl';
class Inter extends Component {
render() {
let { locale, localeMessage, children } = this.props;
return (
<IntlProvider key={locale} locale={locale} messages={localeMessage}>
{children}
</IntlProvider>
)
}
};
export default Inter;