The following doesn't give you a step-by-step guide of what to do. It gives you a number of individual pieces and you need to put them together in order to meet the main feature requirement of the Input Blade:
When the user has entered text and clicks the send button it should be sent to the other users.
The Input Blade uses two services which will enable you to achieve this functionality:
We're nearly ready to start coding. But before we do let's do a little bit of setup.
As we said, a nice team have put together some fake services to help us develop our Blade's functionality. In order to configure the services you need to know the require path to these implementations.
Open the aliases.xml configuration file for the Workbench,
blades/input/workbench/resources/aliases.xml, and set the content as:
<aliases xmlns="http://schema.caplin.com/CaplinTrader/aliases" useScenario="dev">
<alias name="chat.service" class="chatservice.FakeChatService"/>
<alias name="user.service" class="userservice.FakeUserService"/>
</aliases>
Yes, it's XML. What can we say! Anyway, we've now actually configured:
chat.service to use a fake chat service implementationuser.service to use a fake user service implementationNow that the helper services are in place, we can get to work.
In order to send a message you need to know who is sending the message. You do this by getting the current user from the User Service.
To get the User Service we first need to get the ServiceRegistry.
Update your InputViewModel accordingly e.g.:
var ServiceRegistry = require( 'br/ServiceRegistry' );
Next, we want to get the User Service instance that's been set up:
function InputViewModel() {
this.message = ko.observable( '' );
this._userService = ServiceRegistry.getService( 'user.service' );
}
Finally, we need to get hold of the current user:
this._userService.getCurrentUser( listener );
The fact that there's a listener object being passed in tells you that this is
an asynchronous call. In JavaScript there are a number of ways to listen for callbacks.
We've found that although passing in listeners that have to implement contracts
(in the same way as services do) can be a bit more effort, it can result in
a much more robust solution. There are of course times where passing in a function
will be fine.
Anyway, in this case we need to pass in an object that implements two functions:
userRetrieved( user ) called when the user is successfully retrieveduserRetrievalFailed( code, message ) called if there is a problem - we don't expect thisOne way of achieving this is by making InputViewModel a listener by implementing these functions
and passing a reference to this in as the listener.
InputViewModel.prototype.userRetrieved = function( user ) {
// store the current user
};
InputViewModel.prototype.userRetrievalFailed = function( code, message ) {
// Something has gone wrong. How do we feed this back to the user?
};
Once your code is making this call you're ready to move on to the next part of the exercise.
Ensure you call getCurrentUser after your View Model has been instantiated
(in the constructor or an instance method)
There's lots to do here. You can add more tests if you like but we'd recommend that you just make sure your existing tests still pass, or update them if required.
The first time you call getCurrentUser the userRetrievalFailed callback is likely
to trigger because the FakeUserService (set up in aliases.xml) hasn't had a
user set on it via setCurrentUser.
You can fix this by setting the user in your Workbench. This is achieved as follows:
var userService = ServiceRegistry.getService( 'user.service' );
userService.setCurrentUser( { userId: 'some-user-id' } );
Until a reference to the current user has been retrieved it shouldn't be possible
to enter text into the textarea or click the button.
Add an enabled Knockout Observable that has a value of either true or false and where
the textarea and button elements will be disabled if the enabled property
is set to false.
Sometimes things go wrong. If that happens then the user will be left with an Input feature that won't let them input anything. In case of error add an area to the Input Blade that can give the user feedback.
Create a Knockout Observable called feedbackMessage in case of error (userRetrievalFailed)
set the contents of this so the user knows something has gone wrong.
feedbackMessage
is not empty.userRetrievalFailed on the View Model to simulate failure. Or you can ensure you haven't set a current user on the service.The buttonClicked function gets the text from the UI so it makes sense to
interact with the Chat Service at this point. In this exercise you need to:
ServiceRegistryuserId, text and timestampsendMessage chat service function to send the message object that has just been constructedmessage property so that the textarea is clearedBefore we start, you need to copy the contents of aliases.xml from the Workbench
into input/test-unit/resources/aliases.xml.
In the services overview we talked about how using MVVM and Services allows us to test full features in isolation. In this part of the exercise we're going to do exactly that. We'll demonstrate how to achieve two types of test:
This way we're testing how UI interactions result in service interactions and how service events are reflected in UI state.
Let's start by creating a new file called InputFeatureTest.js in input/test-unit/tests/
and update it to look as follows to add the Test Suite:
'use strict';
require( 'jasmine' );
var InputViewModel = require( 'modularapp/input/InputViewModel' );
var ServiceRegistry = require( 'br/ServiceRegistry' );
describe( 'The Input', function() {
} );
Note: You could call this file InputSpecTest.js.
The first thing to notice is that we're using Jasmine, and specifically we're using Jasmine 1.3 as it ships with BRJS.
As with anything in software it's possible to achieve the same thing in multiple ways. For example, we can test service interactions by:
ServiceRegistry with a Mock objectSince we're using Jasmine, we'll use Spies, but we'll also demonstrate how the Fake service, that was developed really to help our development within the Workbench, is also useful here.
Exactly how you put this test together depends on your InputViewModel implementation.
For this test we're going to assume that the current application user is requested
from the User Service in the InputViewModel constructor.
Add the following spec to the The Input suite:
describe( 'The Input', function() {
it( 'Requests a user from the UserService', function() {
var userService = ServiceRegistry.getService( 'user.service' );
spyOn( userService, 'getCurrentUser' );
var inputViewModel = new InputViewModel();
expect( userService.getCurrentUser ).toHaveBeenCalled();
} );
} );
A better test is to interact with the View Model and then verify that a service interaction has occurred. So, over to you:
Verify that when valid text has been entered into message and the send button
is clicked (don't touch that DOM!) that the sendMessage function is called with
an appropriately formed message object.
it( 'Sends a Message using the ChatService when the Button is clicked', function() {
// TODO: Implement test as described above
} );
FakeUserService has a setCurrentUser function.getCurrentUser is asynchronous - you have two options:InputViewModel.userRetrieved with the user this is probably the easiestexpect( someFunction ).toHaveBeenCalledWith( args... ) to verify what parameters were passedtimestamp of the message you can use the jasmine.any( Date ) to at least
check a Date object was passedThe next thing we want to do is see how services can impact the state of the View Model.
The only time that the Input View Model is affected by a service is if the call to
getCurrentUser fails and the userRetrievalFailed callback is executed.
You could call userRetrievalFailed on the InputViewModel and check that the
enabled property is false. However, this is much more of a class unit test
and doesn't test the service interaction. Instead, we can use the FakeUserService
and set it up to act as if the user retrieval failed. We do this by using a helper.
By calling the following we tell the service to fail and ultimately call
userRetrievalFailed on the InputViewModel:
userService.setUserDataFetcher( 'failing', { count: 1 } );
The second parameter tells the service to only fail with user retrieval once.
Now we know how to set up the fake service it should be easy to test that if the user retrieval fails:
So, complete the following tests:
it( 'Disables the interface when the current user is not available', function() {
// TODO
} );
it( 'Provides the user with a feedback messages when the current user is not available', function() {
// TODO
} );
userService.setUserDataFetcher makes an instant call to userRetrievalFailed
so you don't need to worry about creating an asynchronous test.Our Blade is now interacting with two services and we've tested both UI through to Services and Services through to the UI; full feature testing.
It's time to commit those changes and push them to github:
git add blades/inputgit commit -m 'integrating input blade with services'git pull origin mastergit push origin masterSee how the other teams in your company are getting on. Can you help them out?
Then, we'll see how our application looks.