Is there remotely any way to mock any SSE (Server Sent Event) from a Protractor test ?
That means mocking EventSource
Angular controller :
angular.module('app').controller('HomeController', function() {
var monitoringEvents = new window.EventSource('/streams/jobserveur');
monitoringEvents.addEventListener('monitoring-event', function(e) {
var json = JSON.parse(e.data);
...
});
});
Thank you for any insight
I managed to mock EventSource by the solution I mentionned (angular module/protractor addMockModule).
Externalize EventSource calls into a dedicated angular module
angular.module('app.sse', [])
.value('$sse', {
sources : [],
addEventSource : function(name, url) {
this.sources[name] = new window.EventSource(url);
},
addEventListener : function(name, eventName, callback) {
this.sources[name].addEventListener(eventName, callback);
}
});
Referencing the module in the app
angular.module('app', ['app.sse', ...])
Use the $sse module in the app
angular.module('app').controller('HomeController', ['$sse' , function($sse) {
$sse.addEventSource('jobserveur', '/streams/jobserveur');
$sse.addEventListener('jobserveur', 'monitoring-event', function(e) {
var js = JSON.parse(e.data);
}
}]);
From here, make sure your app still work before moving onto the testing
Mock the app.sse module in your test
describe('SSE Fixture', function() {
beforeEach(function() {
browser.addMockModule('app.sse', function() {
angular.module('app.sse', []).value('$sse', {
addEventSource: function(name, url) {
},
addEventListener: function(name, event, callback) {
}
});
});
}
And you're done ! Obviously, the two methods are not implemented here nor is the app.sse module in anyway robust but you get the picture.
Hope it helps anyone
Cheers
Related
I'm using generator-react-webpack to create a React web app. This web app relies on JSON feeds - one of which is hosted on a CDN that does not support JSONP and the CDN url is a subdomain of the webapp. Is there any way to return the JSON data from within the React Component?
Basic React Component:
var AppComponent = React.createClass({
loadData: function() {
jQuery.getJSON(jsonFile.json?callback=?)
.done(function(data) {
console.log(data);
}.bind(this));
},
render: function(){
return ( ... );
}
});
I've tried a few solutions, and have come to the conclusion that I need to define my own callback on the JSON file like so:
JSON:
handleData({
"data": "hello World"
})
Is there a way for the handleData callback to be defined in the react component, or the response accessed from the react component? Any thoughts as to how I can get this to work are much appreciated. Thanks!
This looks like an odd way to do things, especially the part where you're using jQuery. That's a client-side utility to overcome not knowing where everything is and not having direct access to your elements. It makes no sense to use it when you're using React weith Webpack for bundling: React already knows where everything is (using refs) and Webpack means you can just use regular universal Node modules for everything that you need to do.
I'd recommend using something like, using request or a similar universal fetch API:
// loadData.js
var request = require('request');
var loadData = function(urlYouNeed, handler) {
request(urlYouNeed, function(error, response, body) {
if (error) {
return handler(error, false);
}
// do anything processing you need on the body,
var data = process(body);
handler(false, data);
};
So: just a module you can require in any component you define with require('./loadData'). And then in your actual component you do this:
var loadData = require('./loadData');
var AppComponent = React.createClass({
getDefaultProps: function() {
return {
jsonURL: "cdn://whateverjson.json"
};
},
getInitialState: function() {
loadData(this.props.jsonURL, this.updateData);
return {
data: []
}
},
updateData: function(err, data) {
if (err) {
return console.error(err);
}
data = secondaryEnsureRightFormat(data);
this.setState({ data: data });
},
render: function(){
var actualThings = this.state.data.map((entry, pos) => {
return <Whatever content={entry} key={entry.dontUseThePosVariableUpThere}/>
});
return (
<div>
...
{actualThings}
...
</div>
);
}
});
Much cleaner.
If I understand correctly the question, you only have to change your loadData this way :
loadData: function() {
var c = this
jQuery.getJSON(jsonFile.json?callback=?)
.done(function(data) {
c.handleData(data)
});
},
handleData: function(data) {
/* Implement here the function to handle the data */
},
I'm building an E2E test of an Angular application using Protractor. The backend HTTP services are being mocked with $httpBackend. So far, the test looks like this:
describe('foo', function () {
it('bar', function () {
var backendMockModule = function () {
angular
.module('backendMock', [])
.run(['$httpBackend', function ($httpBackend) {
$httpBackend.whenPUT('http://localhost:8080/services/foo/bar')
.respond(function (method, url, data, header) {
return [200, {}, {}];
});
}]);
};
browser.addMockModule('backendMock', backendMockModule);
browser.get('http://localhost:8001/#/foo/bar');
element(by.id('baz')).click();
// here I would like to assert that the Angular app issued a PUT to '/foo/bar' with data = {...}
});
});
The test is a little more elaborated than this, it tests for optimistic update of the interface and other stuff. But I think this is not relevant to the this question, so I removed the other parts. The test in itself is working fine, I'm able to check that the elements on the interface are as expected. What I didn't find out is:
How to assert that the backend HTTP endpoint has been called with the correct data, method, headers, etc?
I have tried to do it like this (adding hasBeenCalled variable):
describe('foo', function () {
it('bar', function () {
var hasBeenCalled = false;
var backendMockModule = function () {
angular
.module('backendMock', [])
.run(['$httpBackend', function ($httpBackend) {
$httpBackend.whenPUT('http://localhost:8080/services/foo/bar')
.respond(function (method, url, data, header) {
hasBeenCalled = true;
return [200, {}, {}];
});
}]);
};
browser.addMockModule('backendMock', backendMockModule);
browser.get('http://localhost:8001/#/foo/bar');
element(by.id('baz')).click();
expect(hasBeenCalled).toBeTruthy();
});
});
But it does not work. I don't know exactly how the Protractor does the testing, but I imagine that it sends a serialized version of the function to the browser in the call addMockModule instead of running the test in the same process as the web page and so I'm not able to share state between the test and the browser (side question: is that correct?).
$httpBackend.flush() is needed before expect(...)...
I've inherited a Cordova/PhoneGap app running Cordova 3.4. My first task was to implement a Client-Side Routing framework to make it easier to navigate between pages. I chose Flatiron Director as my client-side router, but when I went to implement it I started to get weird functionality out of the app.
My first router setup:
var routing = {
testHandler: function(){
console.log('Route ran');
},
routes: function(){
return {
"/testhandler": testHandler
}
}
};
console.log('Routes added');
The routes are added (at least based on the console output). When I attempt to hit the /testhandler hash, I receive a "Failed to load resource: file:///testhandler" error when I set window.location.hash to "/testhandler". I noticed the "Route ran" statement was never printed.
My next attempt was just using the hashchange event with jQuery.
$(window).on('hashchange', function(){ console.log('Ran'); });
On this attempt, regardless of what I change the hash to, I see the 'Ran' output, but I still receive the "Failed to load resource: " error.
Is this a problem with PhoneGap/Cordova? Or our implementation? Is it just not possible to use client-side routing with Cordova? What am I doing wrong?
I know that this doesn't answer your question directly but you may consider making your own provisional router. This may help you to debug your app and to figure out what's the problem.
Something like this for example:
var router = (function (routes) {
var onRouteChange = function () {
// removes hash from the route
var route = location.hash.slice(1);
if (route in routes) {
routes[route]();
} else {
console.log('Route not defined');
}
};
window.addEventListener('hashchange', onRouteChange, false);
return {
addRoute: function (hashRoute, callback) {
routes[hashRoute] = callback;
},
removeRoute: function (hashRoute) {
delete routes[hashRoute];
}
};
})({
route1: function () {
console.log('Route 1');
document.getElementById('view').innerHTML = '<div><h1>Route 1</h1><p>Para 1</p><p>Para 2</p></div>';
},
route2: function () {
console.log('Route 2');
document.getElementById('view').innerHTML = '<div><h1>Route 1</h1><p>Para 1</p><p>Para 2</p></div>';
}
});
I am trying to implement a service inside of my WinJS Windows 8 App, the service needs to call winjs httpClient. I want my service to return a promise while it waits for the promise returned by httpClient . My service code is as follows
(function () {
"use strict";
var _httpClient = new Windows.Web.Http.HttpClient();
var _infoUri = new Windows.Foundation.Uri("https://news.google.com/");
var _getLatestInfo = function () {
return WinJS.Promise(function (completeCallback, errorCallback, progressCallback) {
// invoke the httpclient here
_httpClient.getAsync(_infoUri)
.then(function complete(result) {
completeCallback(result);
}, function error(result) {
errorCallback(result);
}, function progress(result) {
progressCallback(result);
});
});
};
WinJS.Namespace.define("DataService", {
getLatestInfo: _getLatestInfo
});
})();
And I call my service method as follows
(function () {
"use strict";
WinJS.UI.Pages.define("/pages/home/home.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
// TODO: Initialize the page here.
DataService.getLatestInfo().then(function () { }, function () { }, function () { });
}
});
})();
This does not work and I get an error like this
Exception was thrown at line 2018, column 13 in ms-appx://microsoft.winjs.2.0/js/base.js
0x800a01b6 - JavaScript runtime error: Object doesn't support property or method '_setState''
I tried simplifying my service as follows with no luck.
(function () {
"use strict";
var _getLatestInfo = function () {
return WinJS.Promise(function (a, b, c) { });
};
WinJS.Namespace.define("DataService", {
getLatestInfo: _getLatestInfo
});
})();
I don't know what the error is trying to tell me and how to correct this.
I just figured it out, I was missing the 'new' just before WinJS.Promise. Since WinJS.Promise is a class it apparently need to be newed. The error message only confused me more.
I've defined a module (module1) which is supposed to load the value of a property asynchronously. How can I use this property in my app as soon as it is defined and only after it is defined?
My setup (simplified)
v1
app.js
require(['module1'], function(mod) {
document.getElementById("greeting").value = mod.getPersonName();
});
module1.js
define(['jquery'], function($) {
_person;
$.get('values/person.json')
.done(function(data) {
_person = data
});
return {
getPersonName: function() { return _person.name; }
}
values/person.json
{ name: 'John Doe', age: 34 }
This only works if the GET happens nearly instantaneously, otherwise it fails because _person is undefined when getPersonName is called.
v2
To counter this, I figured I would register a callback to notify the app when person was loaded.
app.js
require(['module1'], function(mod) {
mod.onPersonLoaded(function() {
document.getElementById("greeting").value = mod.getPersonName();
});
});
module1.js
define(['jquery'], function($) {
_person;
_onLoaded;
$.get('values/person.json')
.done(function(data) {
_person = data;
_onLoaded();
});
return {
getPersonName: function() { return _person.name; },
onPersonLoaded: function(cb) { _onLoaded = cb; }
}
}
This works if the GET is slow, however, if it's quick _onLoaded is undefined when .done() is called.
Is there a good way to use _person values in app.js as soon as they are defined and only once they are defined?
I'm using RequireJS, but my question is generally applicable to AMD.
Edit
In simplifying my example, I removed a layer which may be important. I'm using RactiveJS for the UI.
Setup (slightly less simplified)
app.js
require(['Ractive', 'module1'], function(Ractive, mod) {
var ractive = new Ractive({
...
data : {
name: mod.getPersonName()
}
});
ractive.observe(...);
ractive.on(...);
});
Edit 2
My current solution, subject to change. Register a callback that notifies app.js when person is loaded. Callback is called immediately if person is already loaded when callback is registered.
app.js
require(['Ractive', 'module1'], function(Ractive, mod) {
var ractive = new Ractive({
...
data : {}
});
mod.watchPerson(function() {
ractive.set('person.name', mod.getPersonName());
});
ractive.observe(...);
ractive.on(...);
});
module1.js
define(['jquery'], function($) {
_person;
_onLoaded;
$.get('values/person.json')
.done(function(data) {
_person = data;
try {
_onLoaded();
} catch (e) {
// that was fast!
// callback will be called when it is registered
});
return {
getPersonName: function() { return _person.name; },
watchPerson: function(cb) {
_onLoaded = cb;
if(_person != null) {
_onLoaded();
}
}
}
}
Promises are a good choice here because callbacks are always called asynchronously - you never encounter that confusing situation where _onLoaded() gets called before it's designed.
Unfortunately, jQuery's promise implementation doesn't adhere to the Promises/A+ specification, so you don't get that guarantee. If possible, you could use an alternative AJAX library like Reqwest, and do something like
app.js
define(['module1'], function (mod) {
mod.then(function(person) {
// do something with person.name
}
});
module1.js
define(['reqwest'], function (reqwest) {
return reqwest('values/person.json');
});
Using the text loader plugin
Another option, since you're already using AMD, would be to use a loader plugin, such as text:
app.js
define(['module1'], function (person) {
// do something with person.name
});
module1.js
define(['text!values/person.json'], function (personJSON) {
return JSON.parse(personJSON);
});
Using the text loader plugin
In fact there's even a JSON plugin, so you could do away with module1 entirely in this example situation:
app.js
define(['json!values/person'], function (person) {
// do something with person.name
});
This is how I would do this. Basically, it is not much different from your V2, but it adds more incapsulation.
app.js
require(['module1'], function(mod) {
mod.loadPerson(function(person) {
document.getElementById("greeting").value = person.getPersonName();
});
});
module1.js
define(['jquery'], function($) {
return {
loadPerson : function(callback) {
$.get('values/person.json').done(function(data) {
_person = data;
callback({
getPersonName: function() { return _person.name; }
});
});
}
}
}
You may also use promises promises instead of simple callback.