Showing posts with label semantic-ui. Show all posts
Showing posts with label semantic-ui. Show all posts

Saturday, September 22, 2018

Semantic UI Dropdown 'setup menu' fails

Leave a Comment

I'm trying to change values of semantic drodown by using setup menu(values) method, but it fails when I use useLabels: false.

Ex. without useLabels: false

$(document).ready(function() {    $('.ui.dropdown').dropdown({      //useLabels: false,      onChange: function(value, text, $selectedItem) {        console.clear();        console.log(value);      }    });            $('.ui.button').on('click', function() {      $('#select').dropdown('setup menu', {        values: [{            name: 'Alaska',            value: 'AK'          },          {            name: 'Arizona',            value: 'AZ'          },          {            name: 'Arkansas',            value: 'AR'          },          {            name: 'California',            value: 'CA'          }        ]      });    })  })
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" rel="stylesheet" />  <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.js"></script>      <select name="gender" class="ui  dropdown" multiple id="select">    <option value="">Gender</option>    <option value="male">Male</option>    <option value="female">Female</option>  </select>  <p></p>  <button class="ui button" type="button">Reset values</button>


Ex. with useLabels: false

The problem is, that is shows that it selects value but actually does not.

$(document).ready(function() {    $('.ui.dropdown').dropdown({      useLabels: false,      onChange: function(value, text, $selectedItem) {        console.clear();        console.log(value);      }    });    $('.ui.button').on('click', function() {      $('#select').dropdown('setup menu', {        values: [{            name: 'Alaska',            value: 'AK'          },          {            name: 'Arizona',            value: 'AZ'          },          {            name: 'Arkansas',            value: 'AR'          },          {            name: 'California',            value: 'CA'          }        ]      });    })  })
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" rel="stylesheet" />  <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.js"></script>      <select name="gender" class="ui  dropdown" multiple id="select">    <option value="">Gender</option>    <option value="male">Male</option>    <option value="female">Female</option>  </select>  <p></p>  <button class="ui button" type="button">Reset values</button>

Or maybe problem is something else, but whatever it is I'm not getting multiple values (Array of chosen values).

Any help will be appreciated.

Note: I'm using .net sever controls for select, therefore I cannot use div structured dropdown

Ex.

@Html.DropDownList("", new {     @id = "ddPage",     @class="ui fluid dropdown search",     @multiple=""  }) 

2 Answers

Answers 1

Check this snippet, i tried various ways, especially the documented methods, but nothing seemed to work... but if you try a more javascript/jQuery based approach, you can destroy the dropdown, reconstruct the select element with either jQuery or plain javascript and then reinitialize the dropdown, i also added a set placeholder textcall, i tried inserting an empty option but didn't made the trick, so i used that function instead...

$(document).ready(function() {      $('.ui.dropdown').dropdown({          //useLabels: false,          onChange: function(value, text, $selectedItem) {              console.clear();              console.log(value);          }      });                  $('.ui.button').on('click', function() {                    $('#select').dropdown("clear"); // clear dropdown          $('#select').html(""); // Empty the select          $('#select').dropdown("destroy"); // Destroy dropdown          $('#select').dropdown("set placeholder text", 'New Placeholder'); // Set new placeholder                    var selectValues = { "AK": "Alaska", "AZ": "Arizona", "CA": "California" }; // define new values          // populate select          $.each(selectValues, function(key, value) {                 $('#select')              .append($("<option></option>")              .attr("value",key)              .text(value));           });                    // call your original dropdown definition again          $('.ui.dropdown').dropdown({              //useLabels: false,              onChange: function(value, text, $selectedItem) {                  console.clear();                  console.log(value);              }          });            console.clear(); // CLEAR CONSOLE          console.log($('#select').dropdown('get value'));  // GET CURRENT SELECTED VALUE WHICH SHOULD BE NULL        });  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" rel="stylesheet"/>  <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.js"></script>      <select name="gender" class="ui  dropdown" multiple id="select">    <option value="">Gender</option>    <option value="male">Male</option>    <option value="female">Female</option>  </select>  <p></p>  <button class="ui button" type="button">Reset values</button>

Answers 2

3 things:

  1. The CDN seems to be outdated. I found the latest while looking at this GitHub issue and clicking on the fiddle right at the end.
  2. In the latest version, you no longer need to use 'setup menu' to update the select.
  3. It had nothing to do with useLabels. The problem that you're facing seems solvable only when I created a dropdown in the format shown in the Examples.

$(document).ready(function() {    $('.ui.dropdown').dropdown({      //useLabels: false,      onChange: function(value, text, $selectedItem) {        console.clear();        console.log(value);      }    });    $('.ui.button').on('click', function() {      $('#select').dropdown({        useLabels : false,        onChange: function(value, text, $selectedItem) {          console.clear();          console.log(value);        },        values: [{            name: 'Alaska',            value: 'AK'          },          {            name: 'Arizona',            value: 'AZ'          },          {            name: 'Arkansas',            value: 'AR'          },          {            name: 'California',            value: 'CA'          }        ]      });    })  })
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>  <link href="https://semantic-ui.com/dist/semantic.css" rel="stylesheet" />  <script src="https://rawgit.com/Semantic-Org/Semantic-UI/next/dist/semantic.js"></script>        <button class="ui button" type="button">Reset values</button>      <div id="select" class="ui fluid multiple special selection dropdown">  <i class="dropdown icon"></i>  <div class="default text">Select Country</div>  <div class="menu">  <div class="item" data-value="af"><i class="af flag"></i>Afghanistan</div>  <div class="item" data-value="ad"><i class="af flag"></i>ad</div>  <div class="item" data-value="as"><i class="af flag"></i>as</div>  </div>  </div>

Read More

Wednesday, August 8, 2018

Datepicker with React on Safari

Leave a Comment

My application uses the Form.Input from Semantic UI React library to insert dates. It shows a date-picker on both Chrome and Firefox but not on Safari. I've tried to use the react-datepicker library, but it has different styling and it's difficult to align its inputs with the others from Semantic UI React's Forms. What can I do?

This is an example of Form.Input type that does not work on Safari.

<Form.Input     label='From'     type='date'     min={this.state.filters.data_inizio}     value={moment(this.state.filters.data_fine).format('YYYY-MM-DD')}     onChange={         (e) => this.setState({             ...this.state,             filters: {                 ...this.state.filters,                 data_fine: moment(e.target.value).format('YYYY-MM-DD')             }         }, this.filter)     } /> 

2 Answers

Answers 1

Bad news.

Semantic UI React does not support the input date type.

What are you seeing in Chrome & Firefox is the default browser versions of input with type="date".

Input with type="date" is not supported in Safari.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#Browser_compatibility

I tried Semantic UI React and plain side-by-side

  <Container>     <Form>       <Form.Input       label='From'       type='date'        min={data_inizio}       value={moment(data_fine).format('YYYY-MM-DD')}       onChange={           (e) => this.setState({             filters: {                 ...filters,                 data_fine: moment(e.target.value).format('YYYY-MM-DD')             }       }, this.filter)     } />     </Form>     <span><strong>Plain version</strong></span><br/>     <input type="date" />   </Container> 

Full example: https://codepen.io/anon/pen/GBdoQW

First picker is same as the plain one below. The first only gets some Semantic CSS.

Try in Safari. They are just regular text inputs. :(

Answers 2

You can try this cool date picker called 'react-dates' made by airbnb...

Github: airbnb / react-dates (for documentation)

Official Live Demo : click here

Code sandbox demo (made by me to help you get started) : https://codesandbox.io/s/l5oo5r4pxl

Read More

Wednesday, June 20, 2018

Use query result from semantic UI inside Django URL Template tag

Leave a Comment

I'm using Django and semantic UI to create a search box. Everything works fine except for the URL parameters.

$('.ui.search').search({          type          : 'standard',         minCharacters : 2,                       apiSettings   : {             onResponse: function(parcelleResponse) {                                         //DO Something                 return response;             },             url: "/myUrl/{query}"         }            }); 

I would like to use the URL template tag system to specify the URL :

{% url 'searchParcelle' {query} %}   

But as the results returned by Semantic UI are stored inside a variable {query}, I've got a template error :

Could not parse the remainder: '{query}' from '{query}' 

Do you know how I could resolve that? I could keep it like this, but as my Prod URL (virtual host) is a bit different, I have to change it every time.

Thanks for your help

2 Answers

Answers 1

There are two problems with your code:

  1. you do not have quotes around {query}, so Django does not treat it like a string
  2. the curly brackets in the URL will be escaped, resulting in a URL like this: /myUrl/%7Bquery%7D/

Additionally, your URL definition might only allow alphanumeric characters and not match with the argument {query}.

The proper way to do this would be to get the URL pattern and transform it into the template URL format, but that would require diving deep into the internals of Django's URL resolving and is probably not worth the hassle.

A more pragmatic approach would be to reverse the URL with a placeholder, which you then replace in JavaScript.

var url = "{% url 'searchParcelle' 'QUERYPLACEHOLDER' %}".replace(     'QUERYPLACEHOLDER', '{query}' ) $('.ui.search').search({      type          : 'standard',     minCharacters : 2,                   apiSettings   : {         onResponse: function(parcelleResponse) {                                     //DO Something             return response;         },         url: url     }        }); 

Answers 2

A more pragmatic approach would be to reverse the URL with a placeholder, which you then replace in JavaScript.

In fact I did exactly what you have suggested and it working as expected now ! Thanks for your help Daniel Hepper.

_urlSearch  = "{% url 'search'  '--MySearchValue--' %}"; _urlDisplay = "{% url 'display' '--MySearchValue--' %}";     $('.ui.search').search({   ...  url: _urlSearch.replace('--MySearchValue--', '{query}') }); 
Read More

Wednesday, November 22, 2017

Semantic UI autocomplete responses arrive out of order

Leave a Comment

I am using the following Semantic UI autocomplete dropdown:

$('.ui.dropdown').dropdown({     minCharacters: 1,     apiSettings: {         url: '/api/people?q={query}'     } }); 

It works, except that when I type 'abc' the responses from my server are returned in the order 'abc', 'ab', 'a' and so the final rendered result is the set of suggestions for 'a', while the field contains 'abc'.

Does Semantic UI provide a standard way to deal with this problem or do I need to implement a fix manually?

2 Answers

Answers 1

The solution would be to cancel previous request whenever new request is made. Semantic UI provides a flag for this (interruptRequests).

$('.ui.dropdown').dropdown({      minCharacters: 1,      apiSettings: {          url: '/api/people?q={query}',          interruptRequests: true      }  });

Ref: https://github.com/Semantic-Org/Semantic-UI/blob/master/RELEASE-NOTES.md

Search for interruptRequest in above link.

Answers 2

You can use match attribute

When using search selection specifies how to match values.

both Matches against text and value

value matches against value only

text matches against text only

The default setting is both

Read More

Monday, June 26, 2017

Table not fully visible when screen size changes in semantic ui

Leave a Comment

I have created a table using html and semantic ui (see html code below).

The problem with the following table is when the screen size changes to a smaller format the last columns are not visible anymore (see browser image below: header 9 is not visible anymore). There is also no horizontal scrolling available. Is there a fix for this behavior?

HTML

<html>     <head>         <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">         <meta charset="utf-8" />          <link href="semantic.min.css" rel="stylesheet" type="text/css" />          <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>         <script type="text/javascript" src="semantic.min.js"></script>         <title></title>     </head>     <body>         <div class='ui container'>             <table class="ui striped selectable celled table" id="overviewtable">                 <thead>                     <tr>                         <th>header 1</th>                         <th>header 2</th>                         <th>header 3</th>                         <th>header 4</th>                         <th>header 5</th>                         <th>header 6</th>                         <th>header 7</th>                         <th>header 8</th>                         <th>header 9</th>                     </tr>                 </thead>                  <tbody>                     <tr>                         <td>sdfsdqfqdsf qsfqsf qs dqsfdqfdq</td>                         <td>qsdfdqsfdqsfdfqsf qs dfsq</td>                         <td>dqfdqsfdsq</td>                         <td>dsqfqsdf</td>                         <td>sqdfsqdf</td>                         <td>sdfsdfqsf</td>                         <td>dsfqsdfqsdfqsdf</td>                         <td>sdqfsqdfsd</td>                         <td>dsqfqsfdqsfsqfqsfdqsf</td>                     </tr>                 </tbody>             </table>         </div>     </body> </html> 

Browser result enter image description here

If you change the screen width further than the table will flip to vertical mode.

PS: same behavior in firefox, chrome and safari.

4 Answers

Answers 1

Give the body tag the style overflow-x:scroll !important;.

That is

body{   overflow-x:scroll !important; } 

The !important is used because if an overflow-x:hidden is set anywhere, this rule will override it.

Note that set it to body. setting it to container will make a scroll bar just below the header. Since you want a scroll bar for the page, set it to body

**Note : you can give scroll or auto

body{    overflow-x:scroll !important;  }
<html>      <head>          <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">          <meta charset="utf-8" />            <link href="semantic.min.css" rel="stylesheet" type="text/css" />            <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>          <script type="text/javascript" src="semantic.min.js"></script>          <title></title>      </head>      <body>          <div class='ui container'>              <table class="ui striped selectable celled table" id="overviewtable">                  <thead>                      <tr>                          <th>header 1</th>                          <th>header 2</th>                          <th>header 3</th>                          <th>header 4</th>                          <th>header 5</th>                          <th>header 6</th>                          <th>header 7</th>                          <th>header 8</th>                          <th>header 9</th>                      </tr>                  </thead>                    <tbody>                      <tr>                          <td>sdfsdqfqdsf qsfqsf qs dqsfdqfdq</td>                          <td>qsdfdqsfdqsfdfqsf qs dfsq</td>                          <td>dqfdqsfdsq</td>                          <td>dsqfqsdf</td>                          <td>sqdfsqdf</td>                          <td>sdfsdfqsf</td>                          <td>dsfqsdfqsdfqsdf</td>                          <td>sdqfsqdfsd</td>                          <td>dsqfqsfdqsfsqfqsfdqsf</td>                      </tr>                  </tbody>              </table>          </div>      </body>  </html>

Answers 2

Try in your css:

.ui.container { overflow-x: auto; } 

Answers 3

Here is solution I have used to display table properly.

<html>    <head>    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">    <meta charset="utf-8" />      <link href="https://cdn.jsdelivr.net/semantic-ui/2.2.10/semantic.min.css" rel="stylesheet" type="text/css" />      <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>    <script type="text/javascript" src="https://cdn.jsdelivr.net/semantic-ui/2.2.10/semantic.min.js"></script>    <style>      .container {        width: 90% !important;      }    </style>    <title></title>  </head>    <body>    <div class='ui container'>      <table class="ui striped selectable celled table" id="overviewtable">        <thead>          <tr>            <th>header 1</th>            <th>header 2</th>            <th>header 3</th>            <th>header 4</th>            <th>header 5</th>            <th>header 6</th>            <th>header 7</th>            <th>header 8</th>            <th>header 9</th>          </tr>        </thead>          <tbody>          <tr>            <td>sdfsdqfqdsf qsfqsf qs dqsfdqfdq</td>            <td>qsdfdqsfdqsfdfqsf qs dfsq</td>            <td>dqfdqsfdsq</td>            <td>dsqfqsdf</td>            <td>sqdfsqdf</td>            <td>sdfsdfqsf</td>            <td>dsfqsdfqsdfqsdf</td>            <td>sdqfsqdfsd</td>            <td>dsqfqsfdqsfsqfqsfdqsf</td>          </tr>        </tbody>      </table>    </div>  </body>    </html>

Answers 4

The overflow property specifies what happens if content overflows an element's box.

This property specifies whether to clip content or to add scrollbars when an element's content is too big to fit in a specified area.

Note: The overflow property only works for block elements with a specified height.

Therefor in your css:

body{   overflow-x:scroll; } 

*Note: Also try not to use !important as much as possible since it gets you in problems later on.

Read More

Monday, April 10, 2017

How do I properly mock third party libraries (like jQuery and Semantic UI) using Jest?

Leave a Comment

I have been learning React, Babel, Semantic UI, and Jest over the last couple of weeks. I haven't really run into too many issues with my components not rendering in the browser, but I have run into issues with rendering when writing unit tests with Jest.

The SUT is as follows:

EditUser.jsx

var React = require('react'); var { browserHistory, Link } = require('react-router'); var $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 

The associated test file is as follows:

EditUser.test.js

var React = require('react'); var Renderer = require('react-test-renderer'); var jQuery = require('jquery'); require('../../../semantic/dist/components/dropdown');  import EditUser from '../../../app/components/Users/EditUser';  it('renders correctly', () => {     const component = Renderer.create(         <EditUser />     ).toJSON();     expect(component).toMatchSnapshot(); }); 

The issue that I am seeing when I run jest:

 FAIL  test/components/Users/EditUser.test.js   ● Test suite failed to run      ReferenceError: jQuery is not defined        at Object.<anonymous> (semantic/dist/components/dropdown.min.js:11:21523)       at Object.<anonymous> (test/components/Users/EditUser.test.js:6:370)       at process._tickCallback (node.js:369:9) 

1 Answers

Answers 1

You are doing it in right way but one simple mistake.

You have to tell jest not to mock jquery

To be clear,

from https://www.phpied.com/jest-jquery-testing-vanilla-app/ under 4th subtitle Testing Vanilla

[It talks about testing a Vanilla app, but it perfectly describe about Jest]

The thing about Jest is that it mocks everything. Which is priceless for unit testing. But it also means you need to declare when you don't want something mocked.

That is

jest.unmock(moduleName) 

From Facebook's documentation
unmock Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module).

The most common use of this API is for specifying the module a given test intends to be testing (and thus doesn't want automatically mocked).

It returns the jest object for chaining.

Note : Previously it was dontMock.

When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block. Use dontMock if you want to explicitly avoid this behavior.
You can see the full documentation here Facebook's Documentation Page in Github .

Also use const instead of var in require. That is

const $ = require('jquery'); 

So the code looks like

jest.unmock('jquery'); // unmock it. In previous versions, use dontMock instead var React = require('react'); var { browserHistory, Link } = require('react-router'); const $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 
Read More

Monday, March 6, 2017

How do I properly mock third party libraries (like jQuery and Semantic UI) using Jest?

Leave a Comment

I have been learning React, Babel, Semantic UI, and Jest over the last couple of weeks. I haven't really run into too many issues with my components not rendering in the browser, but I have run into issues with rendering when writing unit tests with Jest.

The SUT is as follows:

EditUser.jsx

var React = require('react'); var { browserHistory, Link } = require('react-router'); var $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 

The associated test file is as follows:

EditUser.test.js

var React = require('react'); var Renderer = require('react-test-renderer'); var jQuery = require('jquery'); require('../../../semantic/dist/components/dropdown');  import EditUser from '../../../app/components/Users/EditUser';  it('renders correctly', () => {     const component = Renderer.create(         <EditUser />     ).toJSON();     expect(component).toMatchSnapshot(); }); 

The issue that I am seeing when I run jest:

 FAIL  test/components/Users/EditUser.test.js   ● Test suite failed to run      ReferenceError: jQuery is not defined        at Object.<anonymous> (semantic/dist/components/dropdown.min.js:11:21523)       at Object.<anonymous> (test/components/Users/EditUser.test.js:6:370)       at process._tickCallback (node.js:369:9) 

3 Answers

Answers 1

You are doing it in right way but one simple mistake.

You have to tell jest not to mock jquery

To be clear,

from https://www.phpied.com/jest-jquery-testing-vanilla-app/ under 4th subtitle Testing Vanilla

[It talks about testing a Vanilla app, but it perfectly describe about Jest]

The thing about Jest is that it mocks everything. Which is priceless for unit testing. But it also means you need to declare when you don't want something mocked.

That is

jest.unmock(moduleName) 

From Facebook's documentation
unmock Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module).

The most common use of this API is for specifying the module a given test intends to be testing (and thus doesn't want automatically mocked).

It returns the jest object for chaining.

Note : Previously it was dontMock.

When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block. Use dontMock if you want to explicitly avoid this behavior.
You can see the full documentation here Facebook's Documentation Page in Github .

Also use const instead of var in require. That is

const $ = require('jquery'); 

So the code looks like

jest.unmock('jquery'); // unmock it. In previous versions, use dontMock instead var React = require('react'); var { browserHistory, Link } = require('react-router'); const $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 

Answers 2

From your error stack, it seems like the semantic dropdown is looking for jQuery, which has not been previously loaded. I think that if you change:

var jQuery = require('jquery'); 

To

require('jquery'); 

That would load it for the tests and not place it in a variable, making it available for the semantic dropdown as well.

Answers 3

You probably don't need to import jquery in your test file, since you are already importing in the EditUser component.

You should also have a look at enzyme. You can do shallow rendering, or full DOM rendering. More info here. Whatever frameworks you are using within your React components, you can easily test your components output with enzyme.

Some simple examples are below:

import React from 'react'; import { mount, shallow, render } from 'enzyme'; import EditUser from './EditUser';  describe('EditUser', function() {    // Ensure component mounts OK   it('EditUser should mount', function() {     const user = shallow(       <EditUser />     );     expect(user).not.toBe.undefined;   });    // Ensure error is thrown when invalid data is passed in to a prop   it('Prop with invalid data throw error', function() {     expect(() => {       shallow(<EditUser prop="invalid data" />);     }).toThrow();   });    // Ensure that a specific prop is of type function   it('Should ensure that some prop is a function', () => {     const propClick = function() {console.log('click')};     const item = mount(       <EditUser someProp={propClick} />     );     expect(typeof(item.props().someProp) === "function");   }); }); 

I use enzyme(with Jest) in all my React projects, and it seems to be the easiest to work with, and supports modern test runners.

Read More

Tuesday, April 12, 2016

Changing Laravel's Gulp/Elixir `watch` task

Leave a Comment

I want to use Laravel's Elixir along with Semantic UI in my new project.

In Semantic UI docs, they suggest how to include their gulp tasks to your current project's gulpfile. In Laravel, they suggest (briefly) how to extend Elixir. But how can I include another gulp task to the watch command?

Currently I'm running gulp watch watch-ui, but I wanted to include the watch-ui task inside watch. Is it possible?

This is my current gulpfile.js:

var gulp     = require('gulp'); var elixir   = require('laravel-elixir'); var semantic = {   watch: require('./resources/assets/semantic/tasks/watch'),   build: require('./resources/assets/semantic/tasks/build') };  gulp.task('watch-ui', semantic.watch); gulp.task('build-ui', semantic.build);  elixir(function(mix) {   mix.task('build-ui'); }); 

2 Answers

Answers 1

If I understand your question correctly, you want to add something to the Semantic UI task. However they never really define a task, but only a function that you can assign to a task.

You can't really include anything in the semantic watch task, unless you want to patch files, but you can add two watch tasks and make sure they both run.

The following code should enable you to do what you need:

var gulp     = require('gulp'); var elixir   = require('laravel-elixir'); var semantic = {   watch: require('./resources/assets/semantic/tasks/watch'),   build: require('./resources/assets/semantic/tasks/build') };  // Define a task for the semantic watch function. gulp.task('semantic-watch', semantic.watch);  // Define the main watch task, that is depended on the semantic-watch, this will run both watch tasks when you run this one. gulp.task('watch', ['semantic-watch'], function() {     // Do your own custom watch logic in here. });  gulp.task('build-ui', semantic.build);  elixir(function(mix) {   mix.task('build-ui'); }); 

You can see how Semantic UI actually defines that their watch tasks should use the watch function here: https://github.com/Semantic-Org/Semantic-UI/blob/master/gulpfile.js#L46

Answers 2

If you mean is it possible to add custom watcher logic through the Elixir API then the short answer is no.

In this case, you could simply rename the watch task that Elixir defines to something else, then create your own watch task that runs both Elixir's watch and the Semantic UI watch:

gulp.tasks['watch-elixir'] = gulp.tasks.watch;  gulp.task('watch', ['watch-elixir', 'watch-ui']); 


But in terms of Elixir extensions, the closest you get to custom watchers is a second argument to mix.task() that takes a set of file paths to watch and retriggers the task on change. So, although you could do something like...

mix.task('build-ui', './resources/assets/semantic/src/**/*') 

... that would trigger a full rebuild on every change, which isn't the same as the more granular watch task provided by semantic.

Read More