How do I call inputs and buttons in my EmberJS acceptance tests? - javascript

This is maddening, as there is little to no help on google/the internet for this. https://guides.emberjs.com/v2.4.0/testing/acceptance/ is also not very helpful, even though it tries. I am basically learning this from scratch. I know a modest amount of HTML, handlebars, and javascript, but emphasis on the modest.
Here is my template, much of it is copied code from my architect's design who doesn't have time to help me :
<form {{action "login" on="submit"}} class="col-md-4 col-md-offset-4">
{{#if loginFailed}}
<div class="alert">Invalid username or password.</div>
{{/if}}
<div class="form-group">
<label for="inputEmail">Email address</label>
{{input value=username type="email" class="form-control" id="inputEmail1" placeholder="Email"}}
</div>
<div class="form-group">
<label for="inputPassword">Password</label>
{{input value=password type="password" class="form-control" id="inputPassword" placeholder="Password"}}
</div>
<button type="submit" class="btn btn-default" disabled={{isProcessing}}>Log in!</button>
</form>
Note the application runs correctly (I'm able to generate a login screen which connects to my local database, and I am able to log in correctly when the credentials are correct and not login when they aren't).
There is also a large .js file for the route which has an ajax call and corresponding promise from it, which I can sort of understand, but bottom line, it works :
import Ember from 'ember';
import ajax from 'ic-ajax';
export default Ember.Route.extend({
loginFailed: false,
isProcessing: false,
beforeModel: function(){
this.store.unloadAll('security-setting');
this.store.unloadAll('org');
var user = this.modelFor('application').user;
user.setProperties({email: '', auth: ''});
},
actions: {
login: function() {
this.setProperties({
loginFailed: false,
isProcessing: true
});
var _this = this;
ajax({
url: _this.modelFor('application').url + '/signin.json/',
type: 'post',
data: {session: {email: this.controller.get("username"), password: this.controller.get("password")}},
}).then(
function(result) {
// proprietary stuff, it all works
},
function(error){
alert(error.jqXHR.responseText);
this.set('isProcessing', false);
_this.set("loginFailed", true);
}
);
},
},
reset: function() {
this.set('isProcessing', false);
this.controller.set('password', '');
this.controller.set('username', '');
}
});
Here is the acceptance test I am trying to write :
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'ember-super-user/tests/helpers/start-app';
module('Acceptance | login', {
beforeEach: function() {
this.application = startApp();
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('visiting /login and fail a login attempt', function(assert) {
visit('/login');
fillIn('input.username', 'insert-username-here');
fillIn('input.password', 'insert-password-here');
click('button.submit');
// I know this assert is wrong but I haven't even gotten this far yet so I'm // not thinking about it; basically what happens is a popup appears and says // wrong-username-or-password-etc
andThen(function() {
assert.equal(currentURL(), '/login');
});
});
Execution dies on the fillIn lines of code. I really don't know what to do here, I've tried all combinations of 'input.username', 'input.inputEmail1', 'input.inputEmail'... I'm just not sure what I'm supposed to do, at all. I'm also pretty sure that 'button.submit' will not just magically work either. Then, I know I'll be even more lost when I try to fill in the andThen promise to acknowledge the fact that a popup appeared saying wrong-password-etc.
Please help; thanks very much for your time.
EDIT: I have been able to fix the fillIn parts of the test, but the click (probably the click, anyway, as the error messages are unclear as to which line is the problem) is producing some errors that I am unable to diagnose. Error messages appear in the output of the QUnit test suites that don't make sense to me --
TypeError: Cannot read property 'set' of undefined# 4286 ms
Expected:
true
Result:
false
Diff:
trufalse
at http://localhost:7357/assets/test-support.js:3592:13
at exports.default._emberTestingAdaptersAdapter.default.extend.exception (http://localhost:7357/assets/vendor.js:52460:7)
at onerrorDefault (http://localhost:7357/assets/vendor.js:43162:24)
at Object.exports.default.trigger (http://localhost:7357/assets/vendor.js:67346:11)
at Promise._onerror (http://localhost:7357/assets/vendor.js:68312:22)
at publishRejection (http://localhost:7357/assets/vendor.js:66619:15)
EDIT 2: The latest change for changing 'button' to 'submit' still doesn't work. Current error message :
Error: Element input[type='submit'] not found.# 166 ms
Expected:
true
Result:
false
Diff:
trufalse
Source:
at http://localhost:7357/assets/test-support.js:3592:13
at exports.default._emberTestingAdaptersAdapter.default.extend.exception (http://localhost:7357/assets/vendor.js:52460:7)
at onerrorDefault (http://localhost:7357/assets/vendor.js:43162:24)
at Object.exports.default.trigger (http://localhost:7357/assets/vendor.js:67346:11)
at Promise._onerror (http://localhost:7357/assets/vendor.js:68312:22)
at publishRejection (http://localhost:7357/assets/vendor.js:66619:15)

Your selector for each input is wrong. Since you gave each one an id, you can do this:
fillIn('#inputEmail1', 'insert-username-here');
fillIn('#inputPassword', 'insert-password-here');
Remember that you are using CSS selectors for the first argument of fillIn, IDs use # prefix and classes use ..
For the submit button, you did not add a class or ID, but if it is the only submit button on the page you can target it like this:
click('button[type="submit"]');

The fillIn and click functions takes in css selectors. so for example, clicking on the submit would look like,
click("input[type='submit']");

Related

Laravel Ajax 419 Error on Production but not Local Development and Nothing In /storage/logs/laravel.log

So I've run into this issue where I'm having a 419 Error code when submitting my AJAX request through my project. I know that this error is due to the CSRF Token not being passed, or not valid.
Story behind this: I've created a "maintenance mode" on my project. This maintenance mode restricts access to the front end by displaying the 503 error page, but still allows my administrators access to the backend to update the site, etc. This is done using some middleware. See the code here on my github page for more information on how I accomplish this functionality.
https://github.com/JDsWebService/ModelAWiki/commit/263a59ebba42688d4a232a5838334b9ee419504c
So maybe this is an issue with the 503 error page on my production environment? I'm not too sure.
I've taken a look at this Question and Answer on SOF, but it doesnt seem to be helping me any.
Laravel 5.5 ajax call 419 (unknown status)
Here is the production site, take a look at the console for more information: http://modelawiki.com/
Here is my code pertaining to the 419 error:
Javascript
$(document).ready(function() {
// CSRF Ajax Token
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
// Add Section Post Button
$('#subscribeButton').click(function(event) {
/* Act on the event */
// Clear Feedback Boxes
$('#valid-feedback').css("display", "none");
$('#invalid-feedback').css("display", "none");
// Input Field
var email = $('#email').val();
var token = $('meta[name="csrf-token"]').attr('content');
console.log(email);
console.log(token);
// Post
$.ajax({
type: 'POST',
url: '/email/subscribe/',
dataType: 'json',
data: {
email: email,
"_token": token,
},
success: function (data) {
// Check Server Side validation
if($.isEmptyObject(data.errors)){
// Show the Feedback Div
$('#valid-feedback').css("display", "block");
// Add the Bootsrapt Is Invalid Class
$('#email').addClass('is-valid');
// Validation Failed Display Error Message
$('#valid-feedback').text(data['success']);
// Animate the Object
$('#email').animateCss('tada', function() {});
console.log(data['success']);
}else{
// Show the Feedback Div
$('#invalid-feedback').css("display", "block");
// Add the Bootsrapt Is Invalid Class
$('#email').addClass('is-invalid');
// Validation Failed Display Error Message
$('#invalid-feedback').text(data.errors[0]);
// Animate the Object
$('#email').animateCss('shake', function() {});
console.log(data.errors);
}
},
error: function (data) {
console.log(data);
}
}); // End Ajax POST function
}); // End Click Event
// On Focus of the Email Box
$('#email').focus(function(event) {
/* Act on the event */
$('#valid-feedback').css("display", "none");
$('#invalid-feedback').css("display", "none");
});
}); // End Document Ready
HTML Form
<div class="input-group input-group-newsletter">
<input type="email" class="form-control" placeholder="Enter email..." aria-label="Enter email..." aria-describedby="basic-addon" id="email">
<div class="input-group-append">
<button class="btn btn-secondary" type="button" id="subscribeButton">Notify Me!</button>
</div>
<div id="invalid-feedback" class="invalid-feedback"></div>
<div id="valid-feedback" class="valid-feedback"></div>
</div>
Header (This shows that the CSRF token is actually on the 503 error page)
<meta name="csrf-token" content="{{ csrf_token() }}">
Again, this code works on my local environment, but not on my production environment. (I also know that AJAX requests can and are being handled in other parts of my site just fine on the production environment, so I know it's not a server issue and has to do with code)
Just in case here is my controller code as well.
// Store the Email into the database
public function subscribe(Request $request) {
// Validate the request
$validator = Validator::make($request->all(), [
'email' => 'required|email|max:255|unique:email_subscribers,email',
]);
// If the validation fails
if($validator->fails()) {
return response()->json([
'errors' => $validator->errors()->all(),
]);
}
// New Section Object
$subscription = new EmailSubscription;
// Add Name into Section Object
$subscription->email = $request->email;
// Save the Section
$subscription->save();
// Return The Request
return response()->json([
'success' => 'You have successfully subscribed! Check your email!'
]);
}
And my route
// Email Routes
Route::prefix('email')->group(function() {
// Create Post Route for subscribing
Route::post('/subscribe', 'EmailSubscriptionsController#subscribe')->name('email.subscribe');
});
Just in case it can help someone, we experienced the same issue and it was due to a server space disk issue. Therefore, Laravel was unable to write files (sessions.php notably).

Vuejs Axios, When adding error to vee validate, error not showing (I explain more)

In my Vue project I use Vee-Validate and Axios.
I have an API call to register a user and when I email is already used, it throws an error. With axios I catch that error and want to display the error message. Very simple right. Just doing this:
this.loading = true;
this.$http.post('v1/auth/register', {
first_name: this.first_name,
last_name: this.last_name,
email: this.email,
phone: this.phone,
password: this.password
}).then((response) => {
this.registration_card = 2;
}).catch(error => {
if (error.data.error.message === "email_already_exists") {
let input = this.$refs['email'].$children[0];
input.errors.add({
field: 'email',
msg: 'email already in use'
});
// So the error is added and will be showing
// Now disable the loading icon
this.loading = false;
}
});
So in the catch I add the error and I disable the loading icon within the button. Here is the button component:
<template>
<button type="button">
<span v-if="!loading">{{label}}</span>
<loading v-if="loading"/>
</button>
</template>
<script>
import loading from 'vue-loading-spinner/src/components/Circle'
export default {
name: 'jellow-button',
props: [
'label',
'loading'
],
components: {
loading
}
}
</script>
But right now the error message is not showing or even added to the error bag. When doing {{this.errors}} the array is empty. I already tackled the problem but I still want to found out why this doesn't work and why it works with my solution.
Solution in the button component
Change this:
<loading v-if="loading"/>
To this:
<loading v-show="loading"/>
Now {{this.errors}} does have error items and the error is displayed.
This is not really an ugly workaround for the problem so I am OK with this but I still want to know why it doesn't work with v-if for displaying the loading icon. What does this button have anything to do with the error bag?
v-show toggles display:none whereas v-if removes the element from the DOM.
when you do run the following bit of code
let input = this.$refs['email'].$children[0];
$refs would be unable to find the email element on the DOM if you were using v-if.

Meteor entering route when calling a method

Can someone explain to me why when I have collections code inside router will cause the route to be called when a method is called?
Consider the following code:
home.html
<template name="home">
{{ duplicate }}
<form>
<input type="text" name="test" value="somevalue">
<input type="submit" value="Submit">
</form>
</template>
script.js
Template.home.events({
'submit form': function (e) {
e.preventDefault();
console.log('Enter Meteor call');
Meteor.call('createDoc', { 'test': e.target.test.value });
}
});
route.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', function () {
console.log('Enter action');
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
this.render('home', {
data: {
'duplicate' : foo
}
});
Template.home.rendered = function () {
console.log('Enter rendered');
$('#loading').hide();
};
});
methods.js
collection = new Mongo.Collection('collection');
Meteor.methods({
createDoc: function (data) {
console.log('Enter createDoc');
collection.insert(data);
}
});
The problem is that if I press submit on the form, after the method is called the router will activate, even though e.preventDefault() presents. The console log shows this behaviour clearly:
"Enter Meteor call" script.js:4:3
"Enter createDoc" methods.js:5:3
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
Furthermore, you can see that the router is called twice and that it never enters Template.home.rendered. This causes the loading div to appear and never leaves. I can confirm that data are being inserted correctly.
If I remove collection.findOne() in routes.js, however, this behaviour will disappear and everything works as expected.
Questions
Why is the route being called only when I have collection.findOne() inside the route?
Why collection.findOne({ test: 'somevalue' }) never returns anything inside the route? (I know how I can get around this by using Session variables and helpers in script.js, but I want to know exactly why)
This is causing a lot of unexpected behaviour in my app. Thank you very much in advance.
As answered by others the problem you have arises from the fact that Meteor will reactively re-run code that runs in a reactive context, if and only if, that code issues a call to a reactive data source.
In your case, the call to findOne is a call to a reactive data source and the context in Router.route('/', function () { // context }); is a reactive context.
There are two important tools that let you control this behavior: one is good design. Be aware of the reactivity and try to design your code around it.
The other is checking Tracker.active and using Tracker.nonreactive to avoid reactivity inside a reactive data context.
This should answer your first question. As to why your findOne query never finds anything: have you published the data from the server to the client? Please check out Publish-Subscribe. You basically need:
// on the server
Meteor.publish('myPublication', function(author) {
return collection.find();
});
// on the client
Meteor.subscribe('myPublication');
The call to collection.findOne() inside the route is listening to any new changes on the database, every time text is saved on the database the query is run.
A possible solution:
Router.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', {
template: 'home',
waitOn: function() {
return Meteor.subscribe('collection');
},
data: function() {
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
return {
'duplicate': foo
};
},
action: function() {
this.render();
}
});
And a publish file on server/publish.js
Meteor.publish('collection', function () {
return collection.find();
});
I hope this can help you solving your problem.
Best.

computed property not working on controller accessed though the needs-property

Somehow the computed property of a controller I'm accessing through the needs property won't work in the template. Other regular string properties work as expected. Below is my code.
To be clear, what I'm trying to achieve is to access properties of the userController and userModel in actually all of my templates/routes (some of which are computed). However, 'user' itself doesn't have a page, so that's why it's not added in the Router.map. It's just a really important class that handles everything user-related and handles access to the user-model.
I hope somebody with a bit more ember experience knows what I'm doing wrong. Or maybe got some advice on how to do this the ember-way? Any help is greatly appreciated!
PS: I tried to be as complete as possible, if I'm forgetting smt let me know, I'll add it.
App.Router.map(function() {
this.resource('signup');
this.resource('login');
this.resource('profile');
this.resource('practice');
this.resource('overview');
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
App.LoginRoute = Ember.Route.extend({
controllerName: 'application',
model: function () {}
});
App.UserRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
//UserController
App.UserController = Ember.ArrayController.extend({
//pagetitle property to test. Working.
pageTitle: 'Usercontroller',
//userArray property to test, but didn't work.
// Makes sense since the Arraycontroller return an array, so you'll have to use #each-helper
userArray: function(){
return this.get('content');
},
//particularUser computed property to test, but also didn't work.
// Neither did looping #each through the userArray property
particularUser : Ember.computed.filterBy('content' , 'username', 'Sunchild')
});
//LoginController
App.LoginController = Ember.ArrayController.extend({
needs: ['user'],
pageTitle: 'test-title loginController'
});
// Login template feeded into an outlet in the application template
<script type="text/x-handlebars" id="login">
<div class="content">
<form class="input-group">
<div class="input-row">
<label>Username</label>
<input type="text" placeholder="Username">
</div>
<div class="input-row">
<label>Email</label>
<input type="email" placeholder="ratchetframework#gmail.com">
</div>
<button class="btn btn-primary btn-block btn-outlined">Login</button>
</form>
<h3> test1:{{controllers.user.pageTitle}}</h3>
<h3> test2:{{controllers.user.userArray}}</h3>
{{#each user in controllers.user.particularUser}}
<div class="card_wrapper">
<p><h3>Username: {{{user.username}}}</h3><p>
<p>email: {{user.email}}</p>
</div>
{{/each}}
</div>
</script>
Without a user route in your router, the model hook in your UserRoute will never run. That means that the model of your UserController will always be empty. So the way you've set things up won't work for your use case. But, you have the same model on your ApplicationRoute, so why not use that controller instead?
App.ApplicationController = Ember.ArrayController.extend({
// Never use the `content` property, always use the `model` property, they are different
userArray: Ember.computed.alias('model'),
particularUser: function() {
// Be sure to only grab the first item, not an array with the first item in it
return this.get('userArray').filterBy('username', 'Sunchild').get('firstObject');
}.property('userArray.#each.username')
});
This is nice because your application route runs before any other route, so this data will always be available. So in your LoginController:
App.LoginController = Ember.ArrayController.extend({
needs: ['application'],
userArray: Ember.comptued.alias('controllers.application.userArray']
});
Hopefully that clears things up a bit.

View not updating bindings in SPA with Durandal/Knockout

I am building a SPA app with the default Durandal setup. I have multiple views returning data with ajax calls however, it is not working perfectly. I created my shell page with a search box so I can search through a list of employees shown here.
Shell.js
define(['require', 'durandal/plugins/router', 'durandal/app', 'config'],
function (require, router, app, config) {
var shell = {
router: router,
searchData: searchData,
employees: ko.observable(),
search: search,
activate: activate,
};
var searchData = ko.observable('');
function search(searchData) {
var url = '#/employeeSearch/' + searchData.searchData;
router.navigateTo(url);
}
return shell;
function activate() {
router.map(config.routes);
return router.activate(config.startModule);
}
});
shell.html
<div class="input-group">
<input type="text" class="form-control" placeholder="Search Employees" data-bind="value: searchData" />
<span class="input-group-btn">
<button type="submit" class="btn btn-default" data-bind="click: search">Search</button>
</span>
</div>
The user puts in a search and when they click the search button the view below navigates to the employeeSearch page. This does work and return the data and view I need it to here.
define(['require', 'durandal/plugins/router', 'durandal/app', 'config', 'services/logger'],
function(require, router, app, config, logger) {
var goBack = function() {
router.navigateBack();
};
function details(employee) {
var url = '#/employee/' + employee.Id + '/profile';
router.navigateTo(url);
}
var vm = {
goBack: goBack,
employees: ko.observable(),
details: details,
};
return {
activate: function (route) {
var self = this;
return self.getEmployees(route.q);
},
getEmployees: function (query) {
return $.ajax(app.url('/employees?q=' + query),
{
type: "GET",
contentType: 'application/json',
dataType: 'json',
}).then(querySucceeded).promise;
function querySucceeded(result) {
self.employees = result;
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
},
};
});
So then, if I try to search for another name, the url will navigate, the logger will show the value I searched for, however the view itself will not show the new results. Exploring in the Chrome debugger, I view the employees object to contain the new result set, but the view has still not updated. If I refresh the page however, the view does properly show up I have viewed multiple questions on here with similar issues about keeping your data calls in the activate method, make sure the data returns a promise before completing the activate method, and putting DOM manipulation in the viewAttached.
Javascript is not rendering in my SPA
How to use observables in Durandal?
HotTowel: Viewmodel lose mapping when navigating between pages
After putting those practices in my code, I am still having problems getting the view to update correctly.
Are there any other Durandal/Knockout properties I need to be aware of? How can I get the bindings to update every time I navigate to a view with (router.navigateTo(url);). I have set my shell properties (cacheViews: true) to true and false but nothing seems to change.
This is one of many problems I have been having with building SPA apps with Durandal. Trying not to give up yet.
I cant test this quick but a think you handle the observable wrong.
I suspect the "result" var is an array of employees. In this case you might handle this with an observableArray (http://knockoutjs.com/examples/collections.html)
And you cant set the value directly like self.employees
You must call the observable function to set the value like
function querySucceeded(result) {
self.employees(result)
logger.log(query + ' Search Activated!', null, 'employeeSearch', true);
}
In your samples you have not shown/mentioned where knockout is being loaded. If you are using Durandal 2.0 then add the following line to the top of your main.js file above your existing define statement
define('knockout', ko);

Categories

Resources