Jest snapshots testing - jestjs

I use jest snapshot testing for one of my component and the generated snap file is huge (199Kb and 4310 lines). All snapshot file is print to the console (that's 3-4 secs of rendering) when the snapshot test fails and it gave me this "you're doing something wrong" feeling.
So my question is : Am i using snapshot testing correctly ?
component code :
import _ = require('lodash');
import React = require('react');
import {TranslatedMessage} from 'translator';
import {UserProfile} from './user-profile';
import {ICustomerProfile} from '../customer/customer-profile';
interface IUserProfile {
firstName: string;
lastName: string;
id: string;
customer: ICustomerProfile;
job: string;
email: string;
contacts: string;
phoneNumber: string;
}
interface IUserProfileProps {
contact: IUserProfile;
}
interface IUserProfileState {}
export class UserProfile extends React.Component<IUserProfileProps, IUserProfileState> {
constructor(props: IUserProfileProps) {
super(props);
}
public render(): JSX.Element {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<h3>{this.props.contact.firstName } {this.props.contact.lastName}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.id}</span>
</p>
</div>
</div>
<div className="row">
<div className="col-md-8">
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.customer" />
</h6>
<ul>
<li>{this.props.contact.customer.name}</li>
</ul>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="admin.contact.infos.job" />
</h6>
<ul>
<li>{this.props.contact.job}</li>
</ul>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="admin.contact.infos.email" />
</h6>
<ul>
<li>{this.props.contact.email}</li>
</ul>
</div>
</div>
<div className="col-md-4">
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.followed" />
</h6>
<ol>
{this.renderContacts(this.props.contact.contacts)}
</ol>
</div>
<div className="ext-admin-user-infos-card">
<h6>
<TranslatedMessage messageKey="common.labels.phone" />
</h6>
<ul>
<li>{this.props.contact.phoneNumber}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
protected renderContacts(contacts: IUserProfile[]): JSX.Element[] {
let contacts= [];
if (sales) {
_.map(sales, function(contact: IUserProfile): void {
salesContact.push(
<li>
{ contact.firstName}
{ contact.lastName}
</li>
);
});
}
return contacts;
}
}
And the test file
jest.mock('TranslatedMessage');
import React = require('react');
import {render} from 'enzyme';
import {user} from '../../../tests/tools';
import {UserProfile} from '../../../app/components/user-profile/user-profile';
describe('UserProfile', () => {
it('should match the snapshot', () => {
const tree = render(<UserProfile user={user} />);
expect(tree).toMatchSnapshot();
});
});

Trust that feeling.
You're using snapshot testing correctly, but you've reached the point where you need to break down large components into smaller components. Breaking them apart will allow you to mock the children components, which will cut down on your snapshot size (per snapshot, not in aggregate) and make your diffs easier to see and fix.
For example, instead of:
export class UserProfile extends Component {
public render() {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<h3>{this.props.contact.firstName } {this.props.contact.lastName}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.id}</span>
</p>
</div>
</div>
// ...
</div>
</div>
)
}
}
You do:
export class UserProfile extends Component {
public render() {
return (
<div className="ext-admin-user-infos-details">
<div className="ext-admin-user-infos-details-content">
<div className="row">
<div className="col-md-12">
<UserProfileName
first={this.props.contact.firstName}
last={this.props.contact.firstName}
contactId={this.props.contact.id}
/>
</div>
</div>
// ...
</div>
</div>
)
}
}
export class UserProfileName extends Component {
public render() {
return (
<div>
<h3>{this.props.contact.first} {this.props.contact.last}</h3>
<p className="ext-subtitle">
<span className="ext-minor">{this.props.contact.contactId}</span>
</p>
</div>
);
}
}
Notice that I've moved the logic for rendering the user's name to another component. Here's the updated test, mocking the child component:
jest.mock('TranslatedMessage');
jest.mock('UserProfileName'); // Mock this and other children
import React = require('react');
import {render} from 'enzyme';
import {user} from '../../../tests/tools';
import {UserProfile} from '../../../app/components/user-profile/user-profile';
describe('UserProfile', () => {
it('should match the snapshot', () => {
const tree = render(<UserProfile user={user} />);
expect(tree).toMatchSnapshot();
});
});
The snapshot for this will be much smaller than if it was all in one component. Of course you would have tests for the children components as well:
import React = require('react');
import {render} from 'enzyme';
import {UserProfileName} from '../../../app/components/user-profile/user-profile-name';
describe('UserProfileName', () => {
it('should match the snapshot with all props', () => {
const tree = render(<UserProfile first="Test" last="Testerson" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
it('should render without a first name', () => {
const tree = render(<UserProfile last="Testerson" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
it('should render without a last name', () => {
const tree = render(<UserProfile first="Test" contactId="test-id" />);
expect(tree).toMatchSnapshot();
});
});
Notice that in these tests I added two more cases at the end. When you break components down like this, it's a lot easier to understand and test for specific use-cases of the children components!
Finally, an added benefit of this approach is that now you have a re-usable component that knows how to render a user name! You could generalize this and plop it in whenever you need it.

You're doing the testing right, but you should definitely divide your component into multiple smaller ones, as it's too big at the moment.
When using enzyme, you might not want always to use render for testing, as it's what makes an output this big. When snapshot testing pure components, for example, you should use shallow.
We're using react-test-rendered for snapshot testing, it's more lightweight than enzyme.

If you're using enzyme, you should be using a shallow render.
import { shallow } from 'enzyme';
const component = shallow(<UserProfile user={user} />);
expect(component.text()).toMatchSnapshot();
You can also use react-test-renderer as well:
import renderer from 'react-test-renderer';
const component = renderer.create(<UserProfile user={user} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();

Related

Error occurs in the template of component EmployeeCreateComponent

I'm new to Angular and working on httpclient and httpservice project of CRUD application. While compiling with ng serve in VS code I get the following error:
error: ERROR in src/app/employee-create/employee-create.component.html:18:65 - error TS2554: Expected 1 arguments, but got 0.
employee-create.component.html
<div class="container custom-container">
<div class="col-md-12">
<h3 class="mb-3 text-center">Create Employee</h3>
<div class="form-group">
<input type="text" [(ngModel)]="employeeDetails.name" class="form-control" placeholder="Name">
</div>
<div class="form-group">
<input type="text" [(ngModel)]="employeeDetails.email" class="form-control" placeholder="Email">
</div>
<div class="form-group">
<input type="text" [(ngModel)]="employeeDetails.phone" class="form-control" placeholder="Phone">
</div>
<div class="form-group">
<button class="btn btn-success btn-lg btn-block" (click)="addEmployee()">Create Employee</button>
</div>
</div>
</div>
employee-create component.ts
import { Component, OnInit, Input } from '#angular/core';
import { Router } from '#angular/router';
import { RestApiService } from "../shared/rest-api.service";
#Component({
selector: 'app-employee-create',
templateUrl: './employee-create.component.html',
styleUrls: ['./employee-create.component.css']
})
export class EmployeeCreateComponent implements OnInit {
#Input() employeeDetails = { name: '', email: '', phone: 0 }
constructor(
public restApi: RestApiService,
public router: Router
) { }
ngOnInit() { }
addEmployee() {
this.restApi.createEmployee(this.employeeDetails).subscribe((data: {}) => {
this.router.navigate(['/employees-list'])
})
}
}
The error is obvious in your template addEmployee() method on button click doesnt have any param.
(click)="addEmployee()"
But in your component you are passing dataEmployee param in addEmployee(dataEmployee) method (which looks like not in use).
Hence you can remove dataEmployee param from addEmployee() method.
addEmployee() { // <=== no dataEmployee param
this.restApi.createEmployee(this.employeeDetails).subscribe((data: {}) => {
this.router.navigate(['/employees-list'])
})
}
addEmployee() { // <=== dataEmployee Parameter this.restApi.createEmployee(this.employeeDetails).subscribe((data: {}) => {
this.router.navigate(['/employees-list'])
})
}
you are not passing a Parameter in Html file but you are trying to get parameter in ts that's why you are getting Error.

Why is this react component not displaying content loaded?

I'm working on this project and I want to display the content I got from the backend routes via axios to Showcase component. But the code doesn't give the output as expected the updated state console.log(cont) is working and no issue but it doesn't rendering contents.The app.js state is received by the component. I want to display the names. The child functional component as follows.
import React from 'react';
import {
Table,
Button
} from 'reactstrap';
function Showcase(props) {
const title = props.title;
const contents = props.contents;
let items_body = [];
items_body = contents.map(cont => {
console.log(cont)
if(cont.category === 'Men') {
return (
<div className="item_card" key={cont._id}>
<div className="itemC_right">
<div className="itemCR_topA">
<div className="itemCR_topA_title">{cont.name}</div>
</div>
</div>
</div>
)
}
else if(cont.category === 'Women') {
return (
<div className="lead content d-flex d-flex justify-content-center mb-3" key={cont.id}>
<div>Name : {cont.name}</div>
</div>
)
}
else if(cont.category === 'Kids') {
return (
<div className="lead content d-flex d-flex justify-content-center mb-3" key={cont.id}>
<div>Name : {cont.name}</div>
</div>
)
}
else
return (
null
)
})
return (
<div id="showcase">
<div id="showcase_card">
<div className="row">
<div className="col-sm-6 d-flex flex-row mt-1">
<h1 className="display-3 txt_secondary text-left" id="showcase_title">{title}</h1>
</div>
<div className="col-sm-6 d-flex flex-row-reverse mt-4">
<small className="txt_secondary text-right">Oreo is a online shopping store made just for you.</small>
</div>
</div>
<div>
{items_body}
</div>
</div>
</div>
)
}
export default Showcase;
The App.js class component
class App extends React.Component {
state = {
title: 'Oreo',
contents: []
}
changeState = (category,data) => {
this.setState({
title: category,
contents: data
})
}
handleNavigation = (e) => {
const option = e.target.innerHTML;
switch(option) {
case "Men":
axios.get('/api/items/men/2')
.then(res => {
this.changeState('Men',res.data);
// console.log(res.data)
})
break;
}
}
render() {
return (
<div>
<NavigationBar handleNavigation={this.handleNavigation} />
<Showcase title={this.state.title} contents={this.state.contents} />
<ItemWindow />
<BottomBar />
</div>
);
}
}
export default App;
In NavigationComponent.js when clicked on Men I'm sending it to App.js then it handles the click event. Why doesn't Showcase.js cannot show/render results? Help.
So I got rid of the if statements under the Showcase.js and could get my results. It seems that I was checking the category twice(in handleNavigation and here). I also added async and await as tonkalata's way.

Force preact-router to reload a page completely

I have a page that contains a link to a secondary page that creates a record. Here is the problem I'm running into: If I fill out the fields on the secondary page, and return back to create another item, the previous data is still inside my text boxes.
I don't know if this is just how preact works. I thought that by calling route it would unmount the component, thus clearing state. I even tried adding unique keys to my routes (which I heard forces them to unmount).
I really am at wits end.
app.jsx
const App = () => (
<div>
<Header/>
<Router history={createHashHistory()}>
<Home path="/" />
<DisplayUsers key="displayUsers" path="/display-users"/>
<CreateUser key="createUser" path="/create-user"/>
</Router>
</div>
);
create-item.jsx
import { h, Component } from "preact";
import { route } from 'preact-router';
import { $post } from "app/services/ajax.jsx";
import Section from "app/components/section/section.jsx";
import UserList from "app/components/user-list/user-list.jsx";
class CreateUser extends Component {
constructor(props) {
super(props);
this.state = {
userName: "",
route: ""
};
}
handleSubmit = (event) => {
event.preventDefault();
$post("/api/users", this.state, () =>
{
route('/display-users');
}
);
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
});
}
render() {
return (
<Section title="New User">
<form onSubmit={this.handleSubmit}>
<div className="mat-field">
<label
htmlFor="userName"
className="mat-field__label">
User Name:
</label>
<input
type="text"
id="userName"
name="userName"
className="mat-field__input"
autoComplete="off"
autoFocus="autoFocus"
maxlength="30"
required
onChange={this.handleChange}/>
</div>
<div className="mat-field">
<label
htmlFor="route"
className="mat-field__label">
Route To:
</label>
<UserList
name="route"
onChange={this.handleChange}/>
</div>
{/* Buttons */ }
<div>
<input
type="submit"
value="Create"
className="mat-button mat-button--secondary mat-button--raised"/>
<a
href="/display-users"
className="mat-button">Cancel</a>
</div>
</form>
</Section>
);
}
}
export default CreateUser;

Updating the data in angular view that is bound to a function in .ts file everytime a change is detected

I'm building an ecommerce app in MEAN stack. For making the app real-time, I'm using pusher-js. To display the number of a particular item in the cart, I've used a function in the ts file that iterates over all the items in a shopping-cart(which is an input property to the product-card component) and finds the number of that item in the cart and then displays it.
<div *ngIf="product.name" class="card" style="width: 23rem;">
<img *ngIf="product.url" class="card-img-top" [src]="product.url" alt="{{ product.name }}">
<div class="card-body">
<h5 class="card-title">{{ product.name }}</h5>
<p class="card-text">{{ product.price | currency:'USD':true }}</p>
</div>
<div class="card-footer" *ngIf="showActions">
<div class="row">
<div class="col-4">
<button
class="btn btn-secondary btn-block active"
[disabled]="productQuantity === 0"
(click)="removeFromCart(product)">
Remove
</button>
</div>
<div class="col quantity">{{ getQuantity() }} in Cart</div>
<div class="col-4">
<button
class="btn btn-secondary btn-block"
(click)="addToCart(product)">
Add
</button>
</div>
</div>
</div>
This is the html file of the component. 10th line from the last indicates the use of getQuantity() function. That function is defined in the following file:
import { ShoppingCartService } from './../shopping-cart.service';
import { Product } from './../models/Product';
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '#angular/core';
#Component({
selector: 'product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.css'],
changeDetection: ChangeDetectionStrategy.Default
})
export class ProductCardComponent implements OnInit, OnChanges {
#Input('product') product;
#Input('show-actions') showActions = true;
#Input('shopping-cart') shoppingCart;
constructor(private cartService: ShoppingCartService) {
}
addToCart(product: Product) {
this.cartService.addToCart(product);
}
removeFromCart(product: Product) {
this.cartService.removeFromCart(product);
}
getQuantity() {
if (!this.shoppingCart) {
return 0;
} else {
const itemsArray = this.shoppingCart.items;
for (let i = 0; i < itemsArray.length; i++) {
if (itemsArray[i]._id === this.product._id) {
return itemsArray[i].quantity;
}
}
return 0;
}
}
ngOnInit() {
}
}
Whenever I add a product to the shopping-cart, pusher-js emits an event from the server-side. I can't figure out as to when to catch the event in the angular side and then call the getQuantity() function again so that the view gets updated in real time.
You don't need to listen to the pusher-js event, you should have everything in your code.
Firstly, I would define a property quantity, and use that in the template instead of getQuantity(). getQuantity() will then only update this.quantity.
Further, I would call getQuantity() after cartService's addToCart() or removeFromCart() methods are finished. Now it depends on whether the methods return a Promise or an Observable. In case it's a Promise (which it probably is, since you're not calling a subscribe method), it would look like:
this.cartService.addToCart(product)
.then(data => {
getQuantity();
});

ReduxForm handleSubmit refreshes page with fields assigned

Environment
ReduxForm: v6.5.0
Node: v8.1.2
Browser: Google Chrome
I've gone through all the existing issues on handleSubmit page refreshing, but none of them seems to solve my problem.
LoginForm
import React, { Component } from 'react'
import { reduxForm, Field, propTypes } from 'redux-form'
import classNames from 'classnames'
import loginValidation from './validation'
#reduxForm({
form: 'loginForm',
validate: loginValidation
})
export default class LoginForm extends Component {
static propTypes = {
...propTypes
}
inputField = ({ input, label, type, meta: { touched, error } }) => (
<fieldset className="form__fieldset login-form__fieldset">
<div className="form__field">
<input {...input}
type={type}
placeholder={touched && error ? error : label}
className={classNames('form__input login-form__input',
touched && error ? 'ng-invalid' : ''
)}
/> {/* .ng-invalid */}
</div>
{type === 'password' &&
<div className="form__helper login-form__helper">
<div className="small">
<a>I've forgotten my password</a>
</div>
</div>
}
</fieldset>
)
render() {
const { inputField, props } = this
const { handleSubmit, submitting } = props
return (
<div className="habbo-login-form">
<form className="login-form__form" onSubmit={handleSubmit}>
<Field name="username" type="text" component={inputField} label="Username" />
<Field name="password" type="password" component={inputField} label="Password" />
<button className="login-form__button" type="submit" disabled={submitting}>Let's go!</button>
</form>
<div className="login-form__social">
<div className="habbo-facebook-connect" type="large">
<button className="facebook-connect">Login with Facebook</button>
</div>
<div className="habbo-facebook-connect" type="small">
<button className="facebook-connect"></button>
</div>
</div>
</div>
)
}
}
And the HOC LoginHeader:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import classNames from 'classnames'
import LoginForm from './LoginForm'
import { authActions } from '../redux/modules/auth'
#connect(
state => ({
user: state.auth.user,
...state.form.loginForm
}),
{ ...authActions }
)
export default class LoginHeader extends Component {
static propTypes = {
user: PropTypes.object,
login: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired
}
static contextTypes = {
router: PropTypes.object
}
onSubmit = (data) => this.props.login(data).then(console.log)
render() {
const { submitFailed } = this.props
const headerLoginForm = classNames(
'header__login-form',
submitFailed ? 'animated shake' : ''
)
return (
<div className="header__top sticky-header sticky-header--top">
<div className="wrapper">
<div className="header__top__content">
<div className={headerLoginForm}>
<LoginForm onSubmit={this.onSubmit} />
</div>
</div>
</div>
</div>
)
}
}
Even when I try to replace <form onSubmit={handleSubmit}> with <form onSubmit={(e) => e.preventDefault()}> my page still refreshes.
I'm unsure whether or not this is a problem with my coding, browser, version, or whatever else because practically yesterday it worked without any problem.

Resources