Unit Testing Nested Components UI elements in Angular using Jasmine and Karma - jasmine

pseudo-code of my parentComponent html:
<mat-tab-group class="mat-elevation-z4">
<mat-tab label="OneBox">
<host-configuration [hostTemplate]="shareNodes">
</host-configuration>
</mat-tab>
</mat-tab-group>
I am creating an instance of child component using it's selector(host-configuration) to create form fields where user can give the values.
Code in childComponent(host-configuration) which will create UI form fields
#Input()
set hostTemplate(value: HostTemplate) {
this.loadedTemplate = value ?? new HostTemplate();
this.templateNameConfigItem.formControl.setValue(value.TemplateName);
this.vmStubNameConfigItem.formControl.setValue(value.VMStubName);
this.virtualApplianceConfigItem.formControl.setValue(value.VirtualAppliance);
this.cpuCountConfigItem.formControl.setValue(value.Sizing.cpu_count);
this.numOfCoresPerSocketConfigItem.formControl.setValue(value.Sizing.num_cores_per_socket);
this.ramInMBConfigItem.formControl.setValue(value.Sizing.ram_mb);
if (value.VMs.length > 0) {
this.hostNameConfigItem.formControl.setValue(value.VMs[0]?.ComputerName);
this.ipAddressConfigItem.formControl.setValue(value.VMs[0]?.Ip);
this.macAddressConfigItem.formControl.setValue(value.VMs[0]?.Mac);
}
this.diskConfigurationItems.length = 0;
for (const d of value.Disks) {
this.diskConfigurationItems.push(new DiskConfigurationItems(d));
}
}
Now I want to test this particular UI form elements of child component (host-configuration) in parent component spec file?
Is there a way to do this , if yes could you please help me on how to do it ?

Related

Dynamically adding custom elements to DOM Aurelia [duplicate]

It seems Aurelia is not aware when I create and append an element in javascript and set a custom attribute (unless I am doing something wrong). For example,
const e = document.createElement('div');
e.setAttribute('custom-attr', 'some value');
body.appendChild(e);
Is there a way to make Aurelia aware of this custom attribute when it gets appended?
A little background: I am creating an app where the user can select their element type (e.g. input, select, checkbox etc.) and drag it around (the dragging is done in the custom attribute). I thought about creating a wrapper <div custom-attr repeat.for="e of elements"></div> and somehow render the elements array, but this seemed inefficient since the repeater will go through all the elements everytime I push a new one and I didn't not want to create a wrapper around something as simple as a text input that might be created.
You would have to manually trigger the Aurelia's enhance method for it to register the custom attributes or anything Aurelia related really. And you also have to pass in a ViewResources object containing the custom attribute.
Since this isn't as straight forward as you might think, I'll explain it a bit.
The enhance method requires the following parameters for this scenario:
Your HTML as plain text (string)
The binding context (in our scenario, it's just this)
A ViewResources object that has the required custom attribute
One way to get access to the ViewResources object that meets our requirements, is to require the custom attribute into your parent view and then use the parent view's ViewResources. To do that, require the view inside the parent view's HTML and then implement the created(owningView, thisView) callback in the controller. When it's fired, thisView will have a resources property, which is a ViewResources object that contains the require-d custom attribute.
Since I am HORRIBLE at explaining, please look into the example provided below.
Here is an example how to:
app.js
import { TemplatingEngine } from 'aurelia-framework';
export class App {
static inject = [TemplatingEngine];
message = 'Hello World!';
constructor(templatingEngine, viewResources) {
this._templatingEngine = templatingEngine;
}
created(owningView, thisView) {
this._viewResources = thisView.resources;
}
bind() {
this.createEnhanceAppend();
}
createEnhanceAppend() {
const span = document.createElement('span');
span.innerHTML = "<h5 example.bind=\"message\"></h5>";
this._templatingEngine.enhance({ element: span, bindingContext: this, resources: this._viewResources });
this.view.appendChild(span);
}
}
app.html
<template>
<require from="./example-custom-attribute"></require>
<div ref="view"></div>
</template>
Gist.run:
https://gist.run/?id=7b80d2498ed17bcb88f17b17c6f73fb9
Additional resources
Dwayne Charrington has written an excellent tutorial on this topic:
https://ilikekillnerds.com/2016/01/enhancing-at-will-using-aurelias-templating-engine-enhance-api/

Aurelia - multiple Enhance statements

Updated with solution (28.03.2017):
http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/app-configuration-and-startup/8
Have updated Aurelia docs with solution (scroll down a little).
Special thanks to Charleh for hint.
Question:
Aurelia has this nice feature calls enhance, which can help you enhancing specific parts of your application with Aurelia functional.
But can we have multiple enhance statements on the same page? It seems problematical.
Example:
Task: enhance first component on the page, then get some data from the server and enhance second component on the page with server data as binding context
HTML
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
</head>
<body>
<my-component1></my-component1>
<my-component2></my-component2>
</body>
</html>
JS
import { bootstrap } from 'aurelia-bootstrapper-webpack';
bootstrap(function(aurelia) {
aurelia.use
.standardConfiguration()
.globalResources("my-component1", "my-component2");
aurelia.start().then((app) => {
// Enhance first element
app.enhance(null, document.querySelector('my-component1'));
// Get some data from server and then enhance second element with binding context
getSomeDataFromServer().then((data) => {
app.enhance(data, document.querySelector('my-component2'));
});
});
});
Result:
In the result we will enhance first component, but when it's time for the second one, Aurelia will try to enhance first component one more time!
It happens because of aurelia-framework.js _configureHost method.
So basically when you start enhance it starts this method with your element as an application host:
Aurelia.prototype.enhance = function enhance() {
var _this2 = this;
var bindingContext = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var applicationHost = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
this._configureHost(applicationHost || _aureliaPal.DOM.querySelectorAll('body')[0]);
return new Promise(function (resolve) {
var engine = _this2.container.get(_aureliaTemplating.TemplatingEngine);
_this2.root = engine.enhance({ container: _this2.container, element: _this2.host, resources: _this2.resources, bindingContext: bindingContext });
_this2.root.attached();
_this2._onAureliaComposed();
resolve(_this2);
});
};
And inside the _configureHost we can see this if statement which is just checking if our app instance is already host configured then do nothing.
Aurelia.prototype._configureHost = function _configureHost(applicationHost) {
if (this.hostConfigured) {
return;
}
...
Problem
So the actual problem here is that any enhanced element automatically became an application host (root) and when you try to enhance another element with the same aurelia instance you will just end up enhancing the first element always.
Question
Is this some way around for the cases when I want to enhance several elements on the page?
There's a clue here:
this.root = engine.enhance({container: this.container, element: this.host, resources: this.resources, bindingContext: bindingContext});
this.root.attached();
The aurelia.enhance just wraps the TemplatingEngine instance's .enhance method.
You could just pull TemplatingEngine from the container and call .enhance on it passing the bindingContext since aurelia.enhance does just that (but adds the additional "host configure" step that you've already done via your first .enhance call).
So that bit might look like:
import { Container } from 'aurelia-dependency-injection';
let engine = Container.instance.get(TemplatingEngine);
engine.enhance({ container: Container.instance, element: document.querySelect('my-component2'), resources: (you might need to inject these too), bindingContext: someContext });
(disclaimer: I didn't test the above so it may not be accurate - also you probably need to pass the resources object in - you can inject it or pull it from the container - I believe the type is just Resources)
However - something to note: your my-component2 won't actually be a child of your host element my-component1. I'm not sure if that will cause issues further down the line but it's just a thought.
I'm still curious as to why you'd want to bootstrap an Aurelia instance and then have it enhance multiple elements on the same page instead of just wrapping all that server response logic inside the component's viewmodel itself?
Maybe you can give a bit more context to the reason behind this?
My workaround for this issue for now (thanks to Charleh for the clue):
import { bootstrap } from 'aurelia-bootstrapper-webpack';
import {TemplatingEngine} from "aurelia-framework";
let enhanceNode = function (app, node, bindingContext = null) {
let engine = app.container.get(TemplatingEngine);
let component = engine.enhance({container: app.container, element: node, resources: app.resources, bindingContext: bindingContext});
component.attached();
}
bootstrap(function(aurelia) {
aurelia.use
.standardConfiguration()
.globalResources("my-component1", "my-component2")
aurelia.start().then((app) => {
enhanceNode(document.querySelector('my-component1'));
enhanceNode(document.querySelector('my-component2'));
});
});
That way you can skip host configuration for the app and can enhance as many custom elements as you want on the page.

Angular2 selected option in SELECT removed from all other SELECT tags options

I have several dropdowns, all have the same initial options.
(they may be initialized with one value at the beggining).
I am looking for solution to a case when choosing one option in one dropdown - it will no longer show as an option in the other dropdowns.
I saw this solution in AngularJS and didn't succeed making it work in Angular 2 RC 4:
https://stackoverflow.com/a/28918935
In addition I saw that pipes are not recommended for filtering as of Angular.io:
The Angular team and many experienced Angular developers strongly
recommend that you move filtering and sorting logic into the component
itself.
I got a solution for this issue:
In html:
<select [ngModel]='currValue'
(change)="update($event.target.value, i, $event.target.options.selectedIndex)"
name="{{i}}"
#select>
<option *ngFor="let currItem of items | distinctValue: selectedList: select.value: i: currValue; let j=index"
value="{{currItem}}">{{currItem}}</option>
</select>
In ts:
selectedList: any = [];
when changing the selected value of select - push the item into the selectedList and remove the previous selected value of this element from the selectedList so it could be picked again.
In DistinctValue.pipe.ts:
transform(itemsList: any, filtersList: any, excludeItem: any, row: any, currItem: any) {
return itemsList.filter(function(option:any) {
let keepItem: boolean = true;
let currFilter: number = 0;
// Check if the option should be filtered
while ((keepItem) && (currFilter < filtersList.length)) {
// If option exists in filters list and not this item
if ( (option.fieldname != currItem.fieldname)
(option.fieldname != excludeItem.fieldname) &&
(option.fieldname == filtersList[currFilter].fieldname) ) {
keepItem = false;
}
currFilter++;
}
return keepItem;
});
}
you could write a custom filter pipe.
in html:
options | optionFilter:optionToRemove
js:
#Pipe({
name: 'optionFilter'
})
class OptionFilterPipe implements PipeTransform {
public transform(options, optionToRemove) {
return options.filter(function(option) {return option.id !== optionToRemove.id)};
}
}

VS2010 Coded UI Test - Test builder unable to map two checkboxes with same text

I'm trying to create a coded UI test (with VS2010 Ultimate) for a simple web form page with two checkboxes and a submit hyperlink. The checkboxes have the same text label; "I Agree".
Using the coded UI test builder to record actions, only one checkbox is captured because both checkboxes have the same text / same UIMap Name.
Using the crosshair tool to select the second checkbox, it replaces the previous checkbox instance because they have the same text / same UIMap Name.
When the test is run, the first checkbox is checked, the second is not, and the hyperlink is clicked to submitted the form (failing validation).
How can I add the second checkbox to the test map and differentiate between the two?
If there are no unique properties on the checkboxes themselves, specify the parent object of each checkbox to differentiate them.
Example:
For
<div id="box1Parent">
<input label="I Agree"/>
</div>
<div id=box2Parent">
<input label="I Agree"/>
</div>
You would define the object like this:
public HtmlCheckBox AgreementBox1()
{
HtmlDiv parent = new HtmlDiv(browser);
parent.SearchProperties["id"] = "box1Parent";
HtmlCheckBox target = new HtmlCheckBox(parent);
target.SearchProperties["label"] = "I Agree";
return target;
}
Then, do the same for the second box, but point the parent to box2Parent. This would be your code in the non-designer section of the .uitest class.
There are multiple ways to do this.
Try to find out unique property of object like id, name.
Try to find out parent control/container of checkbox, then use {TAB} or {UP}/{DOWN} keys.
Use {TAB} key of keyboard. find out previous control -> click on that control -> use {TAB} from that control to get focus on checkbox control and use {UP}/{DOWN} arrow key to navigate.
Find out text of document and click on first or second occurrence of that as per your need.
Code to find out document Text,
public string GetCurrentPageVisibleTexts()
{
var window = this.UIMap.<WindowObject>
UITestControlCollection c = window.GetChildren();
var pgContent = (string)c[0].GetProperty("OuterHtml");
var document = new HtmlAgilityPack.HtmlDocument();
document.LoadHtml(pgContent);
// We don't want these in our result
var exclusionText = new string[] { "<!--", "<![CDATA", "function()", "</form>" };
var visibleTexts = new List<string>();
//var nodes = document.DocumentNode.Descendants().Where(d => !d.Name.ToLower().Equals("span"));
foreach (var elem in document.DocumentNode.Descendants())
{
// Foreach element iterate its path back till root
// and look for "display: none" attribute in each one of its parent node
// to verify whether that element or any of its parent are marked as hidden
var tempElem = elem;
while (tempElem.ParentNode != null)
{
if (tempElem.Attributes["style"] != null)
{
// if hidden attribute found then break.
if (tempElem.Attributes["style"].Value.ToLower().Contains("display: none")) break;
}
tempElem = tempElem.ParentNode;
}
// If iteration reached to head and element is found clean of hidden property then proceed with text extraction.
if (tempElem.ParentNode == null)
{
if (!exclusionText.Any(e => elem.InnerText.Contains(e))
&& (!elem.InnerText.Trim().IsNullOrEmpty())
&& (!elem.HasChildNodes))
{
visibleTexts.Add(elem.InnerText);
}
}
} // Foreach close
var completeText = string.Join(" ", visibleTexts).Replace(" ", " ");
return Regex.Replace(completeText, #"\s+", " ");
}

How can i remove the expand arrow in kendo ui treeview if there are no child's to display

I am using kendo ui treeview. I am loading the treeview dynamically from the database. But my issue is i am getting the expand error if there are no child's to display. How can i remove the expand arrow.
Regards,
Sri
There is a configuration field of the HierarchicalDataSource schema.model object called hasChildren you can add a boolean property to your model which indicates if the your model has items.
This way when the TreeView creates its elements it will check that property (or call the function - you could for example return if the items.leght is greater than zero) and if the returned value is false it wont create the expand arrow in front of the item.
Check this demo.
for an example, I have declared my function like this in my Kendo Ui TreeView :
var inline = new kendo.data.HierarchicalDataSource({
data: #Html.Raw(dataSource),
schema: {
model: {
children: "Children",
hasChildren: function(e) {
var test = e.Children.length;
return test > 0;
}
}
}
});
And for my, it works perfectly !

Resources