I'm using Jest to test my React component but I've encountered an error I had never seen before.
here's my <Row/> component:
class Row extends React.Component {
constructor() {
super();
this.state = { userId: '', showDetails: false, showModal: false, status: '', value: '' }
this.handleStatusChange = this.handleStatusChange.bind(this)
this.handleModalCancel = this.handleModalCancel.bind(this)
this.handleModalConfirm = this.handleModalConfirm.bind(this)
this.showDetailsPanel = this.showDetailsPanel.bind(this)
}
showDetailsPanel() {
if(!this.state.showDetails) {
this.setState({
showDetails: true
});
} else {
this.setState({
showDetails: false
});
}
}
handleModalCancel() {
this.setState({
showModal: false
});
}
handleStatusChange(e) {
this.setState({
showModal: true,
value: e.target.value,
});
}
handleModalConfirm() {
this.setState({
showModal: false
});
this.props.model.status = this.state.value;
}
componentWillMount() {
this.props.model.fullName = `${this.props.model.firstName} ${this.props.model.lastName}`
}
render() {
const { model, modelProps, actions } = this.props;
return (
<div className="c-table__row">
{this.state.showModal ?
<Modal
title="Are you sure?"
copy={`Are you sure that you want to change the staus of this user to ${this.state.value}?`}
confirmText="Yes I'm sure"
cancel={this.handleModalCancel}
submit={this.handleModalConfirm}
/>
: null
}
<div className="c-table__row-wrapper">
{modelProps.map((p, i) =>
<div className={`c-table__item ${model[p] === "Active" || model[p] === "Suspended" || model[p] === "Locked" ? model[p] : ""}`} key={i}>
{model[p]}
</div>
)}
<div className="c-table__item c-table__item-sm">
<a onClick={this.showDetailsPanel} className={this.state.showDetails ? "info showing" : "info"}><Icon yicon="Expand_Cross_30_by_30" /></a>
</div>
</div>
{this.state.showDetails ?
<Details
tel={model.telephoneNumber}
dept={model.department}
role={model.role}
username={model.username}
status={model.status}
statusToggle={this.handleStatusChange}
/>
: null }
</div>
);
}
}
export default Row;
And here's my test:
import React from 'react';
import {shallow, mount} from 'enzyme';
import { shallowToJson } from 'enzyme-to-json'
import renderer from 'react-test-renderer';
import Row from '../../../src/components/tables/row.jsx';
test('clicking the info button should call showDetailsPanel', () => {
const component = mount(<Row model={{a: 1, b: 2}} modelProps={['a', 'b']}/>)
const showDetailsPanel = jest.spyOn(component.instance(), 'showDetailsPanel');
component.update();
const button = component.find('.info')
button.simulate('click')
expect(showDetailsPanel).toBeCalled();
})
So I'm simply trying to check that when the info button is clicked the showDetailsPanel is called, but it fails with TypeError: jest.spyOn is not a function.
I'm using "jest": "^18.1.0","jest-cli": "^18.1.0","jest-junit": "^1.5.1".
Any idea?
Thanks in advance
You're using Jest version 18.1.0 but jest.spyOn has been added in 19.0.0.
If you want to use it, you need to upgrade Jest.
npm install --save-dev jest#latest
Or if you're using Yarn:
yarn upgrade jest --latest
Related
I try to deploy Vue app for uploading on GitHub Pages, but got such parse error:
95: </script>
^
96: <template>
97: <Navbar
error during build:
Error: Parse error #:95:10
at parse$b (file:///C:/Users/User/vContact/vContact/node_modules/vite/dist/node/chunks/dep-a713b95d.js:33668:355)
at Object.transform (file:///C:/Users/User/vContact/vContact/node_modules/vite/dist/node/chunks/dep-a713b95d.js:42856:27)
My code:
<script >
import { RouterLink, RouterView } from "vue-router";
import Navbar from "./components/Navbar.vue";
import Contacts from "./components/Contacts.vue";
import Modal from "./components/Modal.vue";
import AddButton from "./components/AddButton.vue";
export default{
components: {
Navbar,
Contacts,
Modal,
AddButton
},
data(){
return {
currentId: 1,
modalOpen: false,
contacts: [],
edit: false,
editContact: {},
search: ''
}
},
created(){
this.getContacts()
},
computed: {
// filterContacts(){
// return this.search ? this.contacts.filter(contact =>
// contact.fullName.toLowerCase().includes(this.search.toLowerCase())) :
this.contacts;
// },
filterContacts(){
return this.search ?
this.contacts.filter(contact => {
for(let key in contact){
if(String(contact[key]).toLowerCase().includes(this.search.toLowerCase())){
return contact;
}
}
})
: this.contacts;
},
// filterContactsCategory(){
// return this.search ? this.contacts.filter(contact =>
contact.category.toLowerCase().includes(this.search.toLowerCase())) : this.contacts;
// }
},
methods: {
openModal(){
this.modalOpen = true
},
closeModal(){
this.modalOpen = false
this.edit = false
},
addContact(item){
this.contacts.push(item)
this.modalOpen = false
},
deleteContact(id){
let index = this.contacts.findIndex(contact => contact.id == id)
this.contacts.splice(index, 1)
},
changeContact(id){
this.edit = this.modalOpen = true
let pickedContact = this.contacts.find(contact => contact.id == id)
this.editContact = pickedContact
},
editedContact(contactEdited){
this.contacts.forEach(contact => {
if(contact.id == contactEdited.id) {
contact.fullName = contactEdited.fullName
contact.phoneNumber = contactEdited.phoneNumber
contact.email = contactEdited.email
contact.category = contactEdited.category
}
})
},
getContacts(){
const localContacts = localStorage.contacts
if(localContacts){
this.contacts = JSON.parse(localContacts)
}
}
},
watch: {
contacts: {
handler(newContacts){
localStorage.contacts = JSON.stringify(this.contacts)
},
deep: true
}
}
}
</script>
<template>
<Navbar
#searchValue="search = $event"
/>
<Contacts
:contacts="filterContacts"
#delContact="deleteContact"
#changeContact="changeContact"
:search="search"
/>
<Modal
:edit="edit"
:editContact="editContact"
#addContact="addContact"
:currentId="currentId"
v-show="modalOpen"
#closeModal="closeModal"
#editedContact="editedContact"
/>
<AddButton
#openModal="openModal"
/>
<RouterView />
</template>
did you put
import vue from '#vitejs/plugin-vue';
in your vite.config.js file, after (of course) installing it via npm?
Expanding on Stephan Franks answer, I had the exact same error as OP, and putting this in vite.config.ts did the trick for me.
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()], // <-- This is where the magic happens
})
For more information, please have a look at the documentation for #vitejs/plugin-vue.
Sandbox link : Link to This CodeSandbox.io
Please help me understand the sequence of execution in this code.
When an error is thrown in child component ( Display ), the getDerivedStateFromError() runs and sets hasError state to true. After that, as expected render() runs....
What I didn't understand is.... Why is the constructor being called again ? This is counter-intuitive... Please explain the reason for its calling...
What did React achieve by restarting everything from the constructor ? It should've simply displayed the fallback UI..But it ran the constructor again and only after facing the same error again did it show the fallback UI. Why did this happen ?
This is my App.js file
import React from "react";
export const Button = (props) => {
return (
<button className={props.className} onClick={props.onClick}>
{props.children}
</button>
);
};
function Display(props) {
throw new Error();
return <h1>Counter {props.i}</h1>;
}
class Counter extends React.Component {
constructor() {
super();
console.log("constr ran");
this.state = {
count: 0,
updated: false,
firstRender: false,
hasError: false,
};
this.incHandler = this.incHandler.bind(this);
}
static getDerivedStateFromError() {
console.log("getDerivedStateFromError Ran...");
return {
hasError: true,
};
}
incHandler() {
this.setState((prevState) => {
return {
count: prevState.count + 1,
updated: true,
};
});
console.log(this.state.count);
}
decHandler = () => {
this.setState((prevState) => {
return {
count: prevState.count - 1,
updated: true,
};
});
console.log(this.state.count);
};
render() {
console.log("render() ran... hasError: ", this.state.hasError);
if (this.state.hasError) {
return (
<div className="App">
<h1>Error happened!</h1>;
</div>
);
}
return (
<div className="App">
<Display i={this.state.count} />
<Button className="button" onClick={this.incHandler}>
Increment
</Button>
<Button className="button" onClick={this.decHandler}>
Decrement
</Button>
<Button
className="button"
onClick={() => {
throw new Error();
}}
>
This Will Throw Error
</Button>
</div>
);
}
}
export default Counter;
This is my index.js file
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
reportWebVitals();
Just wanted to add more to this....
Even when componentDidCatch() is used to catch errors in child components,
Behaviour of React is same...
Please see below image and code
In App.js I made this small modification. This is also causing re-running of constructor.... Why ?
// static getDerivedStateFromError() {
// console.log("getDerivedStateFromError Ran...");
// return {
// hasError: true,
// };
// }
componentDidCatch() {
console.log("componentDidCatch running");
this.setState({ hasError: true });
}
So, apparently, whenever React encounters an error it tries to start everything from constructor...(with the hope of eliminating it...) It sure looks like that....
I am newbie to react, redux-saga, I have a dropdown in page Display, when I select it move to respective component (eg. policy, score), In Component Pages, I have a button Add New, on clicking it will navigate to a page as mentioned in link url , which is a page having cancel button, on cancelling it returns to the Display.js but no dropdown selected,
I would like to keep the state articleMode, when navigating to a page and returning back to same page,
articleMode returns to state -1 instead of selected component Policy or Score
actions.js
export const updateArticleMode = data => {
console.log(data.body);
return {
type: CONSTANTS.UPDATE_ARTICLE_MODE,
data: data.body
};
};
queryReducer.js
import * as CONSTANTS from "../constants/constants";
const initialState = {
articleMode: ""
}
case CONSTANTS.UPDATE_ARTICLE_MODE: {
return {
...state,
articleMode: data.mode
};
}
export default queryReducer;
constants.js
export const UPDATE_ARTICLE_MODE = "UPDATE_ARTICLE_MODE";
Display.js
import React from "react";
import { connect } from "react-redux";
import Policy from "../policy";
import Score from "./../score";
import { updateArticleMode } from "../../../../actions/actions";
const articleMode = [
{ name: "Select", id: "-1" },
{ name: "Score", id: "Score" },
{ name: "Policy", id: "Policy" }
]
class Display extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
articleMode: "-1"
};
}
componentWillMount = () => {
this.setState({ articleMode: this.props.queryData.articleMode || "-1" });
};
_getComponent = () => {
const { articleMode } = this.state;
if (articleMode === "Policy") {
return <DisplayPolicy></DisplayPolicy>;
}
if (articleMode === "Score") {
return <DisplayScore></DisplayScore>;
}
}
render() {
return (
<React.Fragment>
<select name="example"
className="simpleSearchSelect1"
value={this.state.articleMode}
onChange={event => {
this.setState({ articleMode: event.target.value });
this.props.dispatch(
updateArticleMode({ body: { mode: event.target.value } })
);
}}
style={{ marginLeft: "2px" }}
>
{articleMode.length != 0 &&
articleMode.map((option, index) => {
const { name, id } = option;
return (
<option key={index} value={id}>
{name}
</option>
);
})}
</select>
{this.state.articleMode === "-1"
? this._renderNoData()
: this._getComponent()}
</React.Fragment>
)}
const mapStateToProps = state => {
return {
queryData: state.queryData
};
};
export default connect(mapStateToProps)(Display);
}
DisplayPolicy.js
import React from "react";
class DisplayPrivacyPolicy extends React.Component {
constructor(props) {
super(props);
}<Link
to={{
pathname: "/ui/privacy-policy/addNew",
state: {
language: "en"
}
}}
>
<button className="page-header-btn icon_btn display-inline">
<img
title="edit"
className="tableImage"
src={`${process.env.PUBLIC_URL}/assets/icons/ic_addstore.svg`}
/>
{`Add New`}
</button>
</Link>
AddNew.js
<Link
to =pathname: "/ui/display",
className="btn btn-themes btn-rounded btn-sec link-sec-btn"
>
Cancel
</Link>
I'm using Material-UI components to build my website. I have a header component with a search field which uses mui InputBase under the hood. When user enters empty input (that is they don't enter anything and just click enter) I want to display a mui Snackbar which will warn the user the no meaningful input was entered.
I can't get the combination of the two components to work together. In addition because search field state doesn't really change when user enters nothing it doesn't reload so if user repeatedly presses Enter the snackbar won't appear. I use this.forceUpdate(); but is there a more elegant way to implement such logic?
This is my code:
for the search input field:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InputBase from '#material-ui/core/InputBase';
import { withStyles } from '#material-ui/core/styles';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { getAppInfo } from '../../actions/appActions.js';
import constants from '../../constants.js';
import { AppSearchBarInputStyles } from '../styles/Material-UI/muiStyles.js';
import AppNotFound from './AppNotFound.js';
class AppSearchBarInput extends Component {
state = {
appId: ''
}
onChange = e => {
this.setState({ appId: e.target.value });
}
onKeyDown = e => {
const { appId } = this.state;
if (e.keyCode === constants.ENTER_KEY && appId !== '') {
this.props.getAppInfo({ appId });
this.setState({
appId: ''
});
}
this.props.history.push('/app/appInfo');
this.forceUpdate();
}
render() {
const { classes } = this.props;
const { appId } = this.state;
console.log(`appId from AppSearchInput=${appId === ''}`);
return (
<div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
value={this.state.appId} />
{ appId === '' ? <AppNotFound message={constants.MESSAGES.APP_BLANK()}/> : ''}
</div>
)
}
}
AppSearchBarInput.propTypes = {
classes: PropTypes.object.isRequired
}
const AppSearchBarWithStyles = withStyles(AppSearchBarInputStyles)(AppSearchBarInput);
const AppSearchBarWithStylesWithRouter = withRouter(AppSearchBarWithStyles);
export default connect(null, { getAppInfo })(AppSearchBarWithStylesWithRouter);
for the snackbar:
import React from 'react';
import Snackbar from '#material-ui/core/Snackbar';
import constants from '../../constants.js';
import SnackbarMessage from './SnackbarMessage.js';
class AppNotFound extends React.Component {
state = {
open: true,
};
handleClose = event => {
this.setState({ open: false });
};
render() {
const { message } = this.props;
return (
<Snackbar
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
open={this.state.open}
autoHideDuration={6000}
onClose={this.handleClose}
>
<SnackbarMessage
onClose={this.handleClose}
variant="warning"
message={message}
/>
</Snackbar>
);
}
}
export default AppNotFound;
I think the good way to achieve what You want is by adding another state property called snackBarOpen which will help You to determine if user has entered empty value or something meaningful:
AppSearchBarInput Component
state = {
appId: '',
snackBarOpen: false
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && e.target.value === '') {
this.setState({
appId: e.target.value,
snackBarOpen: true
});
} else {
this.setState({
appId: e.target.value
})
}
}
handleCloseSnackBar = () => {
this.setState({
snackBarOpen: false
});
}
And then just render also <AppNotFound /> in render() method(it will be hidden by default because it will depend on open prop):
render() {
const { snackBarOpen } = this.state;
return(
<div>
/* <InputBase /> here */
<AppNotFound message={/* Your message here */} open={snackBarOpen} onClose={this.handleCloseSnackBar} />
</div>
)
}
AppNotFound Component
You can remove all methods now and leave only render() one which will look next:
render() {
const { message, open, onClose } = this.props;
return (
<Snackbar
// ...
open={open}
// ...
onClose={onClose}
>
<SnackbarMessage
onClose={onClose}
// ...
message={message}
/>
</Snackbar>
);
}
Hope that my answer will be useful :)
I'm working on an app that keeps track of salespeople's availability based on being either "Available" or "With Client".
Here's the bug I'm having. I'll use an example:
2 salespeople have been added to the app. The order in which they have been added to the app seems to matter in a way I don't expect to. For example, if the first salesperson I've added is James and the second is Rick, If I click on the button next to Rick that reads "Helped A Customer", James will now populate both the "Available" and the "With Client" tables, and Rick will have disappeared.
However if I click on them in a certain order, it works fine. For example, in the same situation as the example above, if I click on James' "Helped A Customer" first, then Rick's "Helped A Customer", then James' "No Longer With Customer", then Rick's "No Longer With Customer", it behaves as expected.
Here's the github project, you can clone it and try it out yourselves:
https://github.com/jackson-lenhart/salesperson-queue
I'll post what I think is the most relevant code here as well:
main.js:
import React from "react";
import { render } from "react-dom";
import shortid from "shortid";
import deepCopy from "deep-copy";
import AddForm from "./add-form";
import Available from "./available";
import WithClient from "./with-client";
class Main extends React.Component {
constructor() {
super();
this.state = {
queue: {
available: [],
withClient: [],
unavailable: []
},
currName: ""
};
this.addToQueue = this.addToQueue.bind(this);
this.removeFromQueue = this.removeFromQueue.bind(this);
this.handleInput = this.handleInput.bind(this);
this.move = this.move.bind(this);
}
addToQueue(name) {
let newQueue = deepCopy(this.state.queue);
newQueue.available = this.state.queue.available.concat({
name,
id: shortid.generate()
});
this.setState({
queue: newQueue
});
}
removeFromQueue(id) {
let newQueue = deepCopy(this.state.queue);
for (let k in this.state.queue) {
newQueue[k] = this.state.queue[k].filter(x =>
x.id !== id
);
}
this.setState({
queue: newQueue
});
}
move(id, from, to) {
this.setState(prevState => {
let newQueue = deepCopy(prevState.queue);
let temp = newQueue[from].find(x => x.id === id);
newQueue[from] = prevState.queue[from].filter(x =>
x.id !== id
);
newQueue[to] = prevState.queue[to].concat(temp);
return {
queue: newQueue
};
});
}
handleInput(event) {
this.setState({
currName: event.target.value
});
}
render() {
return (
<div>
<AddForm
addToQueue={this.addToQueue}
handleInput={this.handleInput}
currName={this.state.currName}
/>
<Available
available={this.state.queue.available}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
<WithClient
withClient={this.state.queue.withClient}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
</div>
);
}
}
render(
<Main />,
document.body
);
add-form.js:
import React from "react";
class AddForm extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.addToQueue(this.props.currName);
}
render() {
return (
<div>
<input
type="text"
onChange={this.props.handleInput}
/>
<button onClick={this.clickWrapper}>
<strong>Add To Queue</strong>
</button>
</div>
);
}
}
export default AddForm;
available.js:
import React from "react";
import Salesperson from "./salesperson";
class Available extends React.Component {
render() {
const style = {
item: {
padding: "10px"
},
available: {
padding: "20px"
}
};
let available;
this.props.available.length === 0 ?
available = (
<p>None available.</p>
) : available = this.props.available.map(x =>
<div key={x.id} style={style.item}>
<Salesperson
key={x.id}
id={x.id}
name={x.name}
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
parent={"available"}
/>
</div>
);
return (
<div style={style.available}>
<h1>Available</h1>
{available}
</div>
);
}
}
export default Available;
salesperson.js:
import React from "react";
import DeleteButton from "./delete-button";
import HelpedButton from "./helped-button";
import NlwcButton from "./nlwc-button";
class Salesperson extends React.Component {
render() {
const style = {
name: {
padding: "10px"
},
button: {
padding: "5px"
}
};
let moveButton;
switch(this.props.parent) {
case "available":
moveButton = (
<HelpedButton
move={this.props.move}
id={this.props.id}
style={style.button}
/>
);
break;
case "withClient":
moveButton = (
<NlwcButton
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
id={this.props.id}
style={style.button}
/>
);
break;
default:
console.error("Invalid parent:", this.props.parent);
}
return (
<div>
<span style={style.name}>{this.props.name}</span>
{moveButton}
<DeleteButton
removeFromQueue={this.props.removeFromQueue}
name={this.props.name}
id={this.props.id}
style={style.button}
/>
</div>
);
}
}
export default Salesperson;
helped-button.js:
import React from "react";
class HelpedButton extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.move(this.props.id, "available", "withClient");
}
render() {
return (
<span style={this.props.style}>
<button onClick={this.clickWrapper}>
<strong>Helped A Customer</strong>
</button>
</span>
);
}
}
export default HelpedButton;
This was just a typo on my part. No longer an issue. Here's the commit that fixed the typo:
https://github.com/jackson-lenhart/salesperson-queue/commit/b86271a20ac8b700bec1e15e001b0c6ef57adb8b