Configuration for Auto-hiding Panel in Meilix with LXQt desktop

In the new LXQt desktop, we can intelligently hide the panel. For that purpose, we’ll just need to patch a new file in a location under the meilix-default-settings metapackage.
Originally the file lies in the lxqt folder of the .config of the OS with the name panel.conf like .config/lxqt/panel.conf but since we have to make changes in the metapackage, we need to patch it here meilix-default-settings/etc/skel/.config/lxqt/panel.conf. Files in etc/skel/ will be put in each users’ new home folder, a folder which does not exist yet when we build the ISO.

panel.conf

1. [panel1]
2. alignment=-1
3. animation-duration=0
4. background-color=@Variant(\0\0\0\x43\0\xff\xff\0\0\0\0\0\0\0\0)
5. background-image=
6. desktop=0
7. font-color=@Variant(\0\0\0\x43\0\xff\xff\0\0\0\0\0\0\0\0)
8. hidable=true
9. iconSize=22
10. lineCount=1
11. lockPanel=false
12. opacity=100
13.panelSize=32
14. plugins=mainmenu, desktopswitch, quicklaunch, taskbar, tray, statusnotifier, mount, volume, clock, showdesktop
15. position=Bottom
16. show-delay=0
17. width=100
18. width-percent=true

In the line number 8 , hidable=true is doing all the jobs. It is the only line which hides the panel by default.

How we find this approach?
Originally LXQt panel is not hidden, they are shown by default. I first try to locate panel.conf file which will carry out the configuration for the panel. I try to find the code responsible for hiding the panel, but I can’t find that. Then I copied the panel.conf in a file and then by GUI I hide the panel and reopen the config file. Then I compare the changes between this file and the old config.panel file in which I found that the new file has a new line hidable=true. We introduced the changes in this PR.

How this approach actually work?
We are using meilix-default-settings metapackage to make the things work. We made an .config file which contains the configuration file. And the .config file is present under skel folder which gets copied under the home folder of the user. Thus ultimately we get a configuration file which will overwrite the original one to get the desired changes.

Other Uses of panel.conf
The file panel.conf could be used to customize all related settings to the LXQT panel, like its alignment, volume bar, quick launch, show desktop, etc.

References:
LXQt panel hiding
Customize LXQt desktop

Continue ReadingConfiguration for Auto-hiding Panel in Meilix with LXQt desktop

Debuilding the meilix-default-settings Metapackage

In the Meilix code repository you find a metapackage named meilix-default-settings which contains custom settings in directories as debian, etc, and user. In these directories one can make changes to make them be included in the build ISO. As Meilix runs on Debian we package our custom user settings in a Debian package to be installed along all the other software packages. The process and utility to make a Debian package is called debuild.

Directories in the meilix-default-settings:

What is debuilding?

It’s Debian slang for “making a deb package” and that stirred quite some confusion in our communications. Debuild is actually a rebuilding of the metapackage. But as to rebuild the Debian package you usually type debuild -uc -us therefore I stick to the language

Suppose someone has edited a configuration file in the metapackage according to its desires to achieve a specific result in the ISO it won’t get in unless he rebuilds the metapackage.He has not only to edit the metapackage but also to rebuild it to get the desired output in the ISO. To make the process automated, we have made a tiny script which will debuild the metapackages during each and every build, we only need to modify the metapackage.

Actually the first meilix-default-settings folder is the only metapackage and inside of it is the sub-metapackage which is responsible to get the changes applied in the ISO. To see a change in the ISO, we only need to edit the meilix-default-settings usr or etc folder in the first layer. Then, we need to debuild the metapackages.

Code-Base:

This file is present here

1. #!/bin/bash
2. rm meilix-default-settings_*                                    
3. cd meilix-default-settings                                      
4. debuild -uc -us

Let’s go through the whole code base line by line:
Line 2 deletes the previous meilix-default-settings binary packages.
Line 3 in this we changed our directory to the metapackage folder that is of our concern.
Line 4 is the most important line, it builds the whole metapackage and brings back all the binary packages and metapackages after making the desired changes.

Follow the example below to know that actually how it works:

This pull request is responsible to turn off system sounds by default in the generated ISO. Pull Requests files in which I only edited the this file and rest of the files get changes in the process of debuilding the metapackage (ignore .travis.yml file).

References:
Required files under debian directory
Debian directory guideline

Continue ReadingDebuilding the meilix-default-settings Metapackage

Hiding the Scrollbar in HTML in the loklak Media Wall

Loklak media wall needs to provide an appealing view to the user. The issue of visibility of scrollbars appeared on the browsers which uses webkit as a browser engine, for example, Google chrome and some others. This issue caused problems like shifting of elements when scrollbars are visible, bad UI. The task was to hide the scrollbars completely from the application while still making overflow of web page resources available by scrolling.

In this blog, I explain how to hide scrollbars from a webpage and still making scrolling available for the user.

Procedure

 

  • Removing scrollbars from the body division: By default, the <body> tag has the style property overflow:auto which makes overflow available automatically if web page resources exceed the current page dimensions. Therefore, to hide scrollbar from the body, we need to hide the overflow using overflow:hidden.

body {
overflow: hidden;
}

 

  • Creating a child wrapper division for the body: Since body now doesn’t provide scrolling available for the web page, we need to create a wrapper division for the web page for which scrolling would be available, however, scrollbars would not be visible. The idea behind this step is that wrapper division would now act as a wrapper for the web page and if there is some overflow of web page resources, scrolling can be performed from the child division (for which scrollbar can be hidden) instead of parent division.

 

The wrapper division needs to be a block which should occupy total web page available and CSS property overflow should be set to auto.

div.wrapper {
display: block;
width: 100%;
height: 100%;
overflow: auto;
}

 

  • Hiding scrollbars from the wrapper division: The browsers which uses webkit as the browser engines provides scrollbar to every DOM element as webkit-scrollbar which can be customized according to our need. We can now turn scrollbar background to transparent or either set width to 0. Now, since problem of shifting of DOM elements exists, we can need to set width to 0.

.wrapper::-webkitscrollbar {
width: 0px;
}

 

  • Blocking Scroll Blocks: For Angular Material Dialog box, same problem exists since Scroll blocks sets the CSS property of HTML to scroll. This causes the whole  html element to have a scroll. For the same, we can set overflow to hidden by using \deep\ tag to change CSS property deeply of different component of Angular project.

/deep/ html.cdkglobalscrollblock {
overflow: hidden;
}

References

Continue ReadingHiding the Scrollbar in HTML in the loklak Media Wall

Implementing Predefined Color Themes in loklak Media Wall

Loklak media wall provides predefined color theme buttons which can be used to directly switch to day or night mode. It is important that the colors of the components are updated instantly with a click of a button. To implement pre-defined color options, we should, first, choose a set of color combinations which should be updated on the concerned divisions of the templates. These set of colors should be stored as an object (same interface) and the current state should be updated with this object when another theme is requested.

In this blog, I will explain how to implement predefined theme options and how to add a new theme in media wall.

Working

Media Wall can provide plenty of themes to help the user to choose a theme of their choice. Loklak media wall currently provides two themes, i.e.,  dark and light themes to provide a contrasting variety of themes at first. Ngrx structure makes it easy to add a predefined themes to the media wall. Let’s see how to add a theme to media wall and see it in action.

Adding ngrx structure

The first task is to create actions which will be dispatched from the Angular components to update the media wall. Depending on the action dispatched, state properties will change and when passed to the template, will update the media wall with the requested theme. There is no need of payload since the color options for all the themes are stored already as a reducer variable which will be updated directly to the media wall state.

export class WallLightThemeChangeAction implements Action {
type = ActionTypes.WALL_LIGHT_THEME_CHANGE;constructor(public payload: ) { }
}export class WallDarkThemeChangeAction implements Action {
type = ActionTypes.WALL_DARK_THEME_CHANGE;constructor(public payload: ) { }
}

Next, we have to update reducer functions for the corresponding actions so that the state properties change according to the actions and wall is updated. For color options, we have an interface defined for color options. For a particular type of theme, we have to adjust interface and just have to update state with the personalised theme state. As the default theme is set to light theme, we have to update state to the initial state when user requests for  light theme

case mediaWallCustomAction.ActionTypes.WALL_DARK_THEME_CHANGE: {
state = {
wallHeader: {
backgroundColor: ‘#243447’,
fontColor: ‘#FFFFFF’
},
wallBackground: {
backgroundColor: ‘#2C4158’
},
wallCard: {
fontColor: ‘#FFFFFF’,
backgroundColor: ‘#1B2836’,
accentColor: ‘#1c94e0’
}
}
return state;
}case mediaWallCustomAction.ActionTypes.WALL_LIGHT_THEME_CHANGE: {
state = initialState;return state;
}

Component and Template

Component

Now, we need to define an array of the string value of colors corresponding to a particular theme. These corresponding theme colors will be displayed in a form of color picker to the user through looping in the template. Whenever user requests for a particular theme, at first, the variable currentTheme is updated with the theme color. Next, the action is dispatched according to the selected theme from the method installTheme().

export class MediaWallMenuComponent implements OnInit, OnDestroy {
.
.
public currentTheme: string;
public themes = [ ‘#FFFFFF’, ‘#333’ ];public installTheme() {
if (this.currentTheme === this.themes[0]) {
this.store.dispatch( new mediaWallCustomAction.WallLightThemeChangeAction());
this.store.dispatch(new mediaWallDirectUrlAction.WallGenerateDirectUrlAction());
}
else if (this.currentTheme === this.themes[1]) {
this.store.dispatch( new mediaWallCustomAction.WallDarkThemeChangeAction());
this.store.dispatch(new mediaWallDirectUrlAction.WallGenerateDirectUrlAction());
}
}
.
.
}

Template

Now, we have to provide a menu for color themes in media wall template to make it easier for user to select the theme. Any interaction with the menu buttons will update the current chosen color and calls a method installTheme() and the corresponding action is dispatched and theme will be updated. Also, the check should show the updated theme for the media wall. For this, a check icon is put up based on condition *ngIf=”currentTheme === theme”.

<mdmenu class=“docs-theme-picker-menu” overlapTrigger=“false” #themeMenu=“mdMenu” yposition=“above”>
<mdgridlist cols=“2”>
<mdgridtile *ngFor=“let theme of themes”>
<div mdmenuitem (click)=“currentTheme = theme; installTheme();”>
<div class=“docs-theme-picker-swatch”>
<mdicon class=“docs-theme-chosen-icon” *ngIf=“currentTheme === theme”>check_circle</md-icon>
<div class=”docs-theme-picker-primary” [style.background]=”theme”></div>
</div>
</div>
</md-grid-tile>
</mdgridlist>
</mdmenu>

Now, The swatch menu looks like this and user can select any predefined theme from the menu and now, the wall is updated with the selected color option.

Reference

Continue ReadingImplementing Predefined Color Themes in loklak Media Wall

Adding Zooming Functionality For Gallery Images

Phimpme is an Android app which contains the camera, editing, sharing on different platforms and displaying local images. To display local images with swipe feature we also allow the user to zoom images with pinch and double tap action on an image. So in this post, I will be explaining how I added zooming functionality for images in phimpme.

To zoom an image we are using Photoview that is similar to an imageview means it support all option that imageview supports.

Step 1

The first step is to add the following dependency in your Gradle.build file of your project.

dependencies {
   compile 'com.github.chrisbanes:PhotoView:latest.release.here'
}

Step 2

Now replace your imageview with PhotoView and it can be done by following way.

<com.github.chrisbanes.photoview.PhotoView
   android:id="@+id/photo_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>

Now we can use this photoview as imageview and it will enable zoom functionality easily.

Step 3

Now next step is to load image in PhotoView. As phimpme allow to display local images of a device so we are using glide library to load image in PhotoView. We are using URI to load image in Glide.

PhotoView imageView = new PhotoView(ActivitySwitchHelper.context);


Glide.with(getContext())
       .load(media.get(position).getUri())
       .diskCacheStrategy(DiskCacheStrategy.SOURCE)
       .thumbnail(0.5f)
       .into(imageView);

Now your image view which is actually a PhotoView is ready to provide zoom functionality by double tap on and image and by pan-zoom.

PhotoView in Phimpme Android

Resources

Continue ReadingAdding Zooming Functionality For Gallery Images

Creating Dynamic Forms Using Custom-Form API in Open Event Front-end

In Open Event Front-end allows the the event creators to customise the sessions & speakers forms which are implemented on the Orga server using custom-form API. While event creation the organiser can select the forms fields which will be placed in the speaker & session forms.

In this blog we will see how we created custom forms for sessions & speakers using the custom-form API. Lets see how we did it.

Retrieving all the form fields

Each event has custom form fields which can be enabled on the sessions-speakers page, where the organiser can include/exclude the fields for speakers & session forms which are used by the organiser and speakers.

return this.modelFor('events.view').query('customForms', {});

We pass return the result of the query to the new session route where we will create a form using the forms included in the event.

Creating form using custom form API

The model returns an array of all the fields related to the event, however we need to group them according to the type of the field i.e session & speaker. We use lodash groupBy.

allFields: computed('fields', function() {
  return groupBy(this.get('fields').toArray(), field => field.get('form'));
})

For session form we run a loop allFields.session which is an array of all the fields related to session form. We check if the field is included and render the field.

{{#each allFields.session as |field|}}
  {{#if field.isIncluded}}
    <div class="field">
      <label class="{{if field.isRequired 'required'}}" for="name">{{field.name}}</label>
      {{#if (or (eq field.type 'text') (eq field.type 'email'))}}
        {{#if field.isLongText}}
          {{widgets/forms/rich-text-editor textareaId=(if field.isRequired (concat 'session_' field.fieldIdentifier '_required'))}}
        {{else}}
          {{input type=field.type id=(if field.isRequired (concat 'session_' field.fieldIdentifier '_required'))}}
        {{/if}}
      {{/if}}
    </div>
  {{/if}}
{{/each}}

We also use a unique id for all the fields for form validation. If the field is required we create a unique id as `session_fieldName_required` for which we add a validation in the session-speaker-form component. We also use different components for different types of fields eg. for a long text field we make use of the rich-text-editor component.

Thank you for reading the blog, you can check the source code for the example here.

Resources

Continue ReadingCreating Dynamic Forms Using Custom-Form API in Open Event Front-end

Controlling Motors using PSLab Device

PSLab device is capable of building up a complete science lab almost anywhere. While the privilege is mostly taken by high school students and teachers to perform scientific experiments, electronic hobbyists can greatly be influenced from the device. One of the usages is to test and debug sensors and other electronic components before actually using them in their projects. In this blog it will be explained how hobbyist motors are made functional with the use of the PSLab device.

There are four types of motors generally used by hobbyists in their DIY(Do-It-Yourself) projects. They are;

  • DC Gear Motor
  • DC Brushless Motor
  • Servo Motor
  • Stepper Motor

DC motors do not require much of a control as their internal structure is simply a magnet and a shaft which was made rotatable around the magnetic field. The following image from slideshare illustrates the cross section of a motor. These motors require high currents and PSLab device as it is powered from a USB port from a PC or a mobile phone, cannot provide such high current. Hence these type of motors are not recommended to use with the device as there is a very high probability it might burn something.

In the current context, we are concerned about stepper motors and servo motors. They cannot be powered up using direct currents to them. Inside these motors, the structure is different and they require a set of controlled signals to function. The following diagram from electronics-tutorials illustrates the feedback loop inside a servo motor. A servo motor is functional using a PWM wave. Depending on the duty cycle, the rotational angle will be determined. PSLab device is capable of generating four different square waves at any duty cycle varying from 0% to 100%. This gives us freedom to acquire any angle we desire from a servo motor. The experiment “Servo Motors” implement the following method where it accepts four angles.

public void servo4(double angle1, double angle2, double angle3, double angle4)

The experiment supports control of four different servo motors at independant angles. Most of the servos available in the market support only 180 degree rotation where some servos can rotate indefinitely. In such a case, the servo will rotate one cycle and reach its initial position.

The last type of motor is stepper motor. As the name says it, this motor can produce steps. Inside of the motor, there are four coils and and five wires coming out of the motor body connecting these coils. The illustration from Wikipedia shows how four steps are acquired by powering up the respective coil in order. This powering up process needs to be controlled and hard to do manually. Using PSLab device experiment “Stepper Motor”, a user can acquire any number of steps just by entering the step value in the text box. The implementation consists of a set of method calls;

scienceLab.stepForward(steps, 100);

scienceLab.stepBackward(steps, 100);

A delay of 100 milliseconds is provided so that there is enough time to produce a step. Otherwise the shaft will not experience enough resultant force to move and will remain in the same position.

These two experiments are possible with PSLab because the amount of current drawn is quite small which can be delivered through a general USB port. It is worth mentioning that as industry grade servo and stepper motors may draw high current as they were built to interact with heavy loads, they are not suitable for this type of experiments.

Resources:

Continue ReadingControlling Motors using PSLab Device

Improving Loklak apps site

In this blog I will be describing some of the recent improvements made to the Loklak apps site. A new utility script has been added to automatically update the loklak app wall after a new app has been made. Invalid app query in app details page has been handled gracefully.

A proper message is shown when a user enters an invalid app name in the url of the details page. Tests has been added for details page.

Developing updatewall script

This is a small utility script to update Loklak wall in order to expose a newly created app or update an existing app. Before moving into the working of this script let us discuss how Loklak apps site tracks all the apps and their details. In the root of the project there is a file names apps.json. This file contains an aggregation of all the app.json files present in the individual apps. Now when the site is loaded, index.html loads the Javascript code present in app_list.js. This app_list.js file makes an ajax call to root apps.json files, loads all the app details in a list and attaches this list to the AngularJS scope variable. After this the app wall consisting of various app details is rendered using html. So whenever a new app is created, in order to expose the app on the wall, the developer needs to copy the contents of the application’s app.json and paste it in the root apps.json file. This is quite tedious on the part of the developer as for making a new app he will first have to know how the site works which is not all directly related to his development work. Next, whenever he updates the app.json of his app, he needs to again update apps.json file with the new data.

This newly added script (updatewall) automates this entire process. After creating a new app all that the developer needs to do is run this script from within his app directory and the app wall will be updated automatically.

Now, let us move into the working of this script. The basic workflow of the updatewall script can be described as follows. The script loads the json data present in the app.json file of the app under consideration. Next it loads the json data present in the root apps.json file.

if __name__ == '__main__':

    #open file containg json object
    json_list_file = open(PATH_TO_ROOT_JSON, 'r')

    #load json object
    json_list = json.load(json_list_file,  object_pairs_hook=OrderedDict)
    json_list_file.close()

    app_json_file = open(PATH_TO_APP_JSON, 'r')
    app_json = json.load(app_json_file,  object_pairs_hook=OrderedDict)
    app_json_file.close()

    #method to update Loklak app wall
    expose_app(json_list, app_json)

When we are loading the json data we are using object_pairs_hook in order to load the data into an OrderedDict rather than a normal python dictionary. We are doing this so that the order of the dictionary items are maintained. Once the data is loaded we invoke the expose method.

def expose_app(json_list, app_json):
    #if app is already present in list then fetch that app
    app = getAppIfPesent(json_list, app_json)

    #if app is not present then add a new entry
    if app == None:
        json_list['apps'].append(app_json)
        update_list_file(json_list)
        print colors.BOLD + colors.OKGREEN + 'App exposed on app wall' + colors.ENDC

    #else update the existing app entry
    else:
        for key in app_json:
            app[key] = app_json[key]
        update_list_file(json_list)
        print colors.BOLD + colors.OKGREEN + 'App updated on app wall' + colors.ENDC

The apps.json file contain a key called apps. This value of this key is a list of json objects, each object being the json data of an individual app’s app.json file. In the above function we iterate over all the json objects present in the list. If we are unable to find a json object whose name value is same as that of the newly created app then we simply append the new app’s app.json object to that list. However if we find an object containing the same name value as that of the newly created app, then we simply update its properties. In short, if the app is a new one, its data gets added to apps.json otherwise the corresponding app data is updated.

Handling invalid app names in the URL of details page

The url of the app details page takes the app name as parameter. If any user wants to see the store listing of an app then he has to use the following url.

https://apps.loklak.org/details.html?q=<app_name>

Here app name is a url parameter used to load the store listing information. Now if anyone enters an invalid app name, that is an app which does not exists, then a proper error message has to be shown to the user. This can be done by checking whether the given app name is present in the root apps.json file or not. If not present if simply set a flag so that the error message can be conditionally rendered.

$scope.getSelectedApp = function() {
        for (var i = 0; i < $scope.apps.length; i++) {
            if ($scope.apps[i].name === $scope.appName) {
                $scope.selectedApp = $scope.apps[i];
                $scope.found = true;
                $("nav").show();
                break;
            }
        }
        if ($scope.found == false) {
            $scope.notFound = true;
        }
    }

In the above snippet if the app is not found then we set notFound to true. This causes the error message to appear on the page.

<div ng-if="notFound" class="not-found">
        <span class="brand-and-image">
          <img src="images/loklak_icon.png">
          <span class="loklak-brand"> <span class="loklak-header">
            loklak </span> <span>apps</span>
          </span>
        </span>
        <span class="error-404">
          Error: Requested app not found
        </span>
        <span class="go-back">
          <a href="/"> Go Back to Home Page >> </a>
        </span>
      </div>

The code renders the error message if notFound is set to true.

Writing tests for store listing page

Almost the entire content of the store listing is loaded dynamically by Javascript logic. So it is very important to write tests for store listing page. Protractor framework has been used to write automated browser test. The tests make sure that for a given app, the content of the middle section is loaded correctly.

it("should have basic information", function() {
    expect(element(by.css(".app-name")).getText()).toEqual("MultiLinePlotter");
    expect(element(by.css(".app-headline")).getText()).toEqual("App to plot tweet aggregations and statistics");
    expect(element(by.css(".author")).getText()).toEqual("by Deepjyoti Mondal");
    expect(element(by.css(".short-desc")).getText()).toEqual("An applicaton to visually compare tweet statistics");
  });

The above tests make sure that the top section is loaded properly. Next we check that getting started section and app use section are not empty.

it("main content should not be empty", function() {
    expect(element(by.css(".get-started-md")).getText()).not.toBe("");
    expect(element(by.css(".app-use-md")).getText()).not.toBe("");
  });

Apart from these, two more tests are performed to check the behaviour of the side bar menu items on click event and the functionality of the Try now button.

Future roadmap

There is still a lot of scope for the site’s improvement and enhancement. Some of the features which can be implemented next are given below.

  • Add more tests to make the site stable and add tests to travis build.
  • Make the apps independent. Work on this has already been started and can be viewed here – issue, PR
  • Optimise the site for mobile using services workers and caching (making a progressive web app).
  • Add a splash screen and home screen icon for mobile.

Important resources

Continue ReadingImproving Loklak apps site

Using Protractor for UI Tests in Angular JS for Loklak Apps Site

Loklak apps site’s home page and app details page have sections where data is dynamically loaded from external javascript and json files. Data is fetched from json files using angular js, processed and then rendered to the corresponding views by controllers. Any erroneous modification to the controller functions might cause discrepancies in the frontend. Since Loklak apps is a frontend project, any bug in the home page or details page will lead to poor UI/UX. How do we deal with this? One way is to write unit tests for the various controller functions and check their behaviours. Now how do we test the behaviours of the site. Most of the controller functions render something on the view. One thing we can do is simulate the various browser actions and test site against known, accepted behaviours with Protractor.

What is Protractor

Protractor is end to end test framework for Angular and AngularJS apps. It runs tests against our app running in browser as if a real user is interacting with our browser. It uses browser specific drivers to interact with our web application as any user would.

Using Protractor to write tests for Loklak apps site

First we need to install Protractor and its dependencies. Let us begin by creating an empty json file in the project directory using the following command.

echo {} > package.json

Next we will have to install Protractor.

The above command installs protractor and webdriver-manager. After this we need to get the necessary binaries to set up our selenium server. This can be done using the following.

./node_modules/protractor/bin/webdriver-manager update
./node_modules/protractor/bin/webdriver-manager start

Let us tidy up things a bit. We will include these commands in package.json file under scripts section so that we can shorten our commands.

Given below is the current state of package.json

{
    "scripts": {
        "start": "./node_modules/http-server/bin/http-server",
        "update-driver": "./node_modules/protractor/bin/webdriver-manager update",
        "start-driver": "./node_modules/protractor/bin/webdriver-manager start",
        "test": "./node_modules/protractor/bin/protractor conf.js"
    },
    "dependencies": {
        "http-server": "^0.10.0",
        "protractor": "^5.1.2"
    }
}

The package.json file currently holds our dependencies and scripts. It contains command for starting development server, updating webdriver and starting webdriver (mentioned just before this) and command to run test.

Next we need to include a configuration file for protractor. The configuration file should contain the test framework to be used, the address at which selenium is running and path to specs file.

// conf.js
exports.config = {
    framework: "jasmine",
    seleniumAddress: "http://localhost:4444/wd/hub",
    specs: ["tests/home-spec.js"]
};

We have set the framework as jasmine and selenium address as http://localhost:4444/wd/hub. Next we need to define our actual file. But before writing tests we need to find out what are the things that we need to test. We will mostly be testing dynamic content loaded by Javascript files. Let us define a spec. A spec is a collection of tests. We will start by testing the category name. Initially when the page loads it should be equal to All apps. Next we test the top right hand side menu which is loaded by javascript using topmenu.json file.

it("should have a category name", function() {
    expect(element(by.id("categoryName")).getText()).toEqual("All apps");
  });

  it("should have top menu", function() {
    let list = element.all(by.css(".topmenu li a"));
    expect(list.count()).toBe(5);
  });

As mentioned earlier, we are using jasmine framework for writing our specs. In the above code snippet ‘it’ describes a particular test. It takes a test description and a callback function thereby providing a very efficient way to document our tests white write the test code itself. In the first test we use expect function to check whether the category name is equal to All apps or not. Here we select the div containing the category name by its id.

Next we write a test for top menu. There should be five menu options in total for the top menu. We select all the list items that are supposed to contain the top menu items and check whether the number of such items are five or not using expect function. As it can be seen from the snippet, the process of selecting a node is almost similar to that of Jquery library.

Next we test the left hand side category list. This list is loaded by AngularJS controller from apps,json file. We should make sure the list is loaded properly and all the options are present.

it("should have a category list", function() {
    let categoryIds = ["All", "Scraper", "Search", "Visualizer", "LoklakLibraries", "InternetOfThings", "Misc"];
    let categoryNames = ["All", "Scraper", "Search", "Visualizer", "Loklak Libraries", "Internet Of Things", "Misc"];

    expect(element(by.css("#catTitle")).getText()).toBe("Categories");

    let categoryList = element.all(by.css(".category-main"));
    expect(categoryList.count()).toBe(7);

    categoryIds.forEach(function(id, index) {
      element(by.css("#" + id)).isPresent().then(function(present) {
        expect(present).toBe(true);
      });

      element(by.css("#" + id)).getText().then(function(text) {
        expect(text).toBe(categoryNames[index]);
      });
    });
  });

At first we maintain two lists of category id and category names. We begin by confirming that Category title is equal to Categories. Next we get the list of categories and iterate over them, For each category we check whether the corresponding id is present in the DOM or not. After confirming this, we match the names of the categories with the expected names. Elements.all function allows us to get a list of selected nodes.

Finally we check the click functionality of the left side menu. Expected behaviour is, on clicking a menu item, the category name should get replaced with the selected category name. For this we need to simulate the click event. Protractor allows us to do it very easily using click function.

it("category list should respond to click", function() {
    let categoryIds = ["All", "Scraper", "Search", "Visualizer", "LoklakLibraries", "InternetOfThings", "Misc"];
    let categoryNames = ["All apps", "Scraper", "Search", "Visualizer", "Loklak Libraries", "Internet Of Things", "Misc"];

    categoryIds.forEach(function(id, index) {
      element(by.id(categoryIds[index])).click().then(function() {
        browser.getCurrentUrl().then(function(url) {
          expect(url).toBe("http://127.0.0.1:8080/#/" + categoryIds[index]);
        });
        element(by.id("categoryName")).getText().then(function(text) {
          expect(text).toBe(categoryNames[index]);
        });
      });
    });
  });



Once again we maintain two lists, category id and category names. We obtain the present list of categories and iterate over them. For each category link we simulate a click event. For each click event we check two values. We check the new browser URL which should now contain the category id. Next we check the value of category name. It should be equal to the category selected.
FInally after all the tests are over we get the final report on our terminal.
In order to run the tests, use the following command.

npm test

This will start executing the tests.

Important resources

Continue ReadingUsing Protractor for UI Tests in Angular JS for Loklak Apps Site