I am trying to unit test an angular dart decorator but cannot pass the component compile phase.
I am trying to follow this example : https://github.com/vsavkin/angulardart-sample-app/blob/master/test/unit/agenda_item_component_test.dart
The problem is that karma doesn't seem to wait for the compile phase to finish and just skips the test.
part of webpuzzle_spec;
digest() {
inject((TestBed tb) {
tb.rootScope.apply();
});
}
compileComponent(String html, Map scope, callback) {
print("This logs");
return async(() {
inject((TestBed tb) {
print("this should log but doesn't");
final s = tb.rootScope.createChild(scope);
final el = tb.compile(html, scope: s);
microLeap();
digest();
callback(el.shadowRoot);
});
});
}
testWpDropdownMenu() {
group("[WpDropdownMenu]", () {
setUp(setUpInjector);
tearDown(tearDownInjector);
setUp((){
module((Module _) => _..type(TestBed)..type(WpDropdownMenu));
});
html() => '''<span class="dropdown-toggle">
<button type="button" class="btn btn-default">menu to click</button>
<ul class="dropdown-menu">
<li>menu item</li>
</ul>
</span>''';
test("should open menu on click", compileComponent(html(), {}, (shadowRoot) {
print("this never logs :(");
shadowRoot.click();
digest();
var toggleableMenu = shadowRoot.querySelector('.dropdown-menu');
expect(toggleableMenu.style.display, equals('none'));
expect(true, equals(false)); // this should make the test fail
}));
});
}
What is the problem with this code ? Is there a nice example somewhere that explains how to test a component/decorator, only using angular.dart and karma ?
Related
I have a renderer file that has the sole purpose of opening a dialog box to select files from. I have tried rewriting this so many times, and each time I get a different error. What am I doing wrong?
Exact code:
const { ipcRenderer, shell, remote } = require('electron')
const dialog = remote.dialog;
function openFileBrowser() {
dialog.showOpenDialog(remote.getCurrentWindow(), {
properties: ["openFile", "multiSelections"]
}).then(result => {
if (result.canceled === false) {
console.log("Selected file paths:")
console.log(result.filePaths)
}
}).catch(err => {
console.log(err)
})
}
Related HTML:
<div id="button-container">
<nav>
<ul class="buttons">
<li id="Open" onclick="openFileBrowser()">Proxies</li>
</ul>
</nav>
</div>
Error Code
renderer.js:37 Uncaught ReferenceError: Cannot access 'dialog' before initialization
at openFileBrowser (renderer.js:37)
at HTMLLIElement.onclick (proxies.html:16)
Using Electron:
"7.1.7"
Since Electron 6.0.0, the functions dialog.showMessageBox(), dialog.showOpenDialog() and dialog.showSaveDialog() return Promises and no longer take callback functions.
There are synchronous counterparts dialog.showMessageBoxSync(), dialog.showOpenDialogSync() and dialog.showSaveDialogSync().
Check out the following code examples showing the asynchronous and the synchronous way of displaying an open dialog:
Asynchronous: dialog.showOpenDialog()
const remote = require("electron").remote
const dialog = remote.dialog
dialog.showOpenDialog(remote.getCurrentWindow(), {
properties: ["openFile", "multiSelections"]
}).then(result => {
if (result.canceled === false) {
console.log("Selected file paths:")
console.log(result.filePaths)
}
}).catch(err => {
console.log(err)
})
Synchronous: dialog.showOpenDialogSync()
const remote = require("electron").remote
const dialog = remote.dialog
let result = dialog.showOpenDialogSync(remote.getCurrentWindow(), {
properties: ["openFile", "multiSelections"]
})
if (typeof result === "object") {
console.log("Selected file paths:")
console.log(result)
}
Both versions can optionally take a BrowserWindow as the first element. If one is provided, the dialog is shown as a modal window.
Check the Electron dialog documentation for detailed usage information.
I have a stenciljs component deployed in an nginx server behind an authentication service. In order to get anything the request must include a cookie containing an access_token. the component is dipslyaed with no preoblem on android devices and on chrome/firfox/IE11/ in desktop devices. the problem is with microsoft edge and on ipad (any navigator) and its due to the browser not sending the cookie to the server. Any hint ?
header.tsx
import { Component, Prop, State, Element } from '#stencil/core';
#Component({
tag: 'pm-header',
styleUrl: 'pm-header.scss',
shadow: true
})
export class PmHeader {
...
render() {
return (
<nav>
<ul>
<li id="menu-icon" class="left menu-icon"
onClick={() => this.toggleFocus('menu-icon')} >
<a>
<ion-icon name="md-apps"></ion-icon>
</a>
</li>
<li id="user-icon" class="right menu-icon"
onClick={() => this.toggleFocus('user-icon')} >
<a>
<ion-icon name="md-contact"></ion-icon>
</a>
</li>
</ul>
</nav>
);
}
}
PS: I'm using stencil/core v0.15.2
So after some digging it turned out that the issue is with ionicons implementation.
They fetch the svgs without sending the credentials which result in an authenticated request. Of course some navigator such as chrome and firefox and even IE11 manages to send the cookies even though it's not explicitly specified that they should.
Anyway, to solve this I had to create a script file that run after the build. This script adds credentials: "include" option to the fetch call so that the cookie get sent.
fix-icons-script.js
/**
* Workaround to fix this ion-icons bug https://github.com/ionic-team/ionicons/issues/640
* To be removed when this bug is fixed
*/
const fs = require('fs');
const workDir = 'dist/component-dir';
fs.readdir(workDir, function(err, items) {
if (err) { return console.log(err); }
for (var i=0; i<items.length; i++) {
if (items[i].endsWith('.entry.js')) {
insertString(workDir + '/' + items[i], '"force-cache"', ',credentials:"include"');
}
}
});
function insertString(file, searchValue, insertValue){
fs.readFile(file, 'utf8', function (err, content) {
if (err) { return console.log(err); }
let newContent = content.substr(0, content.indexOf(searchValue) + searchValue.length);
content = content.substr(newContent.length, content.length);
newContent += insertValue + content;
fs.writeFile(file, newContent, function (err) {
if (err) { return console.log(err); }
console.log('Successfully rewrote ' + file);
});
});
}
This is my react js file code to render media player.
componentDidMount() I imported js file on a load of this page and at the time of render call 'amp' function with options parameter.
componentDidMount () {
const script = document.createElement("script");
script.src = "//amp.azure.net/libs/amp/2.1.5/azuremediaplayer.min.js";
script.async = true;
document.body.appendChild(script);
}
render() {
var myOptions = {
"nativeControlsForTouch": false,
controls: true,
autoplay: true,
width: "640",
height: "400",
}
var myPlayer = amp("azuremediaplayer", myOptions);
myPlayer.src([
{
"src": "//amssamples.streaming.mediaservices.windows.net/91492735-c523-432b-ba01-faba6c2206a2/AzureMediaServicesPromo.ism/manifest",
"type": "application/vnd.ms-sstr+xml"
}
]);
return (
<div className="form-horizontal">
<div className="form-group">
<div className="col-sm-4">Azure Media Player</div>
<div className="col-sm-6">
<video id="azuremediaplayer" class="azuremediaplayer amp-default-skin amp-big-play-centered" tabindex="0"></video>
</div>
</div>
</div>
);
}
}
export default AddItemForm;
And console gives this error
** Line 26: 'amp' is not defined no-undef**
I think if you load the player's script in a sync way like script.async = false;, it would work properly .. I know it would harm the performance, but unfortunately this is the way to go with loading this weirdly packaged player! .. I hate the fact that they didn't make an npm package for it!!
You could provide onload callback for your script like this:
script.onload = () => this.setState({libraryLoaded: true})
And then you can react on the state inside render method
render() {
if (!this.state.libraryLoaded) {
return <span>Loading...</span>
} else {
return ... // your component
}
}
Do not forget to initiate the state with libraryLoaded: false
You can also check my package which is basically doing this loading under the hood.
I have deployed my app on Azure, I have a C# backend and AngularJS front end.
I am using a custom directive (called bgSrc) which sets the image source based on the given url (either a background-image or a src) depending on which element the directive is used.
Here is the directive:
app.directive('bgSrc', ['preloadService', function (preloadService) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.hide();
preloadService(attrs.bgSrc).then(function (url) {
if (element[0].tagName === 'DIV' || element[0].tagName === 'MD-CONTENT' || element[0].tagName === 'MAIN' || element[0].tagName === 'FORM') {
element.css({
"background-image": "url('" + url + "')"
});
element.fadeIn();
}
else if (element[0].tagName === 'IMG') {
attrs.$set('src', url);
element.css({
'display': 'block'
});
element.fadeIn();
}
});
}
};
}]);
Here is my preloadService:
app.factory('preloadService', ['$q', '$rootScope', function ($q, $rootScope) {
return function preload(url) {
var deffered = $q.defer(),
image = new Image();
image.src = url;
if (image.complete) {
deffered.resolve(image.src);
$rootScope.loading = false;
} else {
$rootScope.loading = true;
image.addEventListener('load', function () {
deffered.resolve(image.src);
$rootScope.loading = false;
});
image.addEventListener('error', function () {
deffered.reject();
$rootScope.loading = true;
});
}
return deffered.promise;
};
}]);
Here is an example of how I use it on html.
<div ng-if="!loading" bg-src="assets/build/img/ocean_bg.png">
<form name="model.form" ng-submit="login()" bg-src="assets/build/img/scroll.png">
<h1>Log In</h1>
...
</form>
</div>
It works perfectly well on Chrome and Android but keeps failing on iOS devices. I have pinpointed the issue to be my custom directive, if I remove it the page loads fine, if I include it the page is caught in an endless loop not loading my images and stick in the "$rootScope.loading" state which simply displays a circular progress bar.
Any help on the matter is much appreciated
The issue was in my preloadeService, where the loading of the image was stuck in an infinite loop.
My viewModel has an array called 'Items'. I want to display the contents of 'Items' using a foreach binding. Everything works fine when I use regular HTML. But does not work with a dialogue box which I created using jQueryUI.
HTML:
<div id="skus0">
<div id="skus1">
<ul data-bind="foreach: Items">
<li data-bind="text:Name"></li>
</ul>
</div>
<input type="button" id="openQryItems" class="btn btn-info" value="Open" data-bind="click:openQueryItems" />
</div>
JavaScript:
// my view model
var viewModel = {
Items: [{Name:'Soap'},{Name:'Toothpaste'}]
};
// JS to configure dialogue
$("#skus1").dialog({
autoOpen: false,
width: 500,
modal: true,
buttons: {
"OK": function () {
$(this).dialog("close");
},
"Cancel": function () {
$(this).dialog("close");
}
}
});
// for mapping my model using ko.mapping plugin
var zub = zub || {};
zub.initModel = function (model) {
zub.cycleCountModel = ko.mapping.fromJS(model);
zub.cycleCountModel.openQueryItems = function () {
$("#skus1").dialog("open");
}
ko.applyBindings(zub.cycleCountModel, $("#skus0")[0]);
}
zub.initModel(viewModel);
I have created a fiddle here my fiddle
$.fn.dialog removes the element from its place in the DOM and places it in a new container; this is how it can create a floating window. The problem with this happening is that it breaks data binding, since the dialog DOM is no-longer nested within the top-level data-bound DOM.
Moving the dialog initialization to after ko.applyBindings will enable dialog to yank stuff out of the DOM after the list is populated. Of course, this means that after that point, future changes will still not be reflected, which may be important if you're wanting the opened dialog to change automatically.
If you are wanting the dialog contents to be fully dynamic, you could create a binding handler; we did this in our project. Here's a rough outline of how we did this:
ko.bindingHandlers.dialog = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingCtx) {
var bindingValues = valueAccessor();
var hasAppliedBindings = false;
var elem = $(element);
var options = {
id: ko.utils.unwrapObservable(bindingValues.id),
title: ko.utils.unwrapObservable(bindingValues.title),
// etc...
onOpen: function () {
if (!hasAppliedBindings) {
hasAppliedBindings = true;
var childCtx = bindingCtx.createChildContext(viewModel);
ko.applyBindingsToDescendants(childCtx, element);
}
}
};
elem.dialog(options);
}
return { controlsDescendantBindings: true };
}
...which we used like this:
<div data-bind="dialog: { title: 'some title', id: 'foo', ... }">
<!-- dialog contents -->
</div>
What return { controlsDescendantBindings: true } does is makes sure that outer bindings do not affect anything using the dialog binding handler. Then we create our own Knockout binding "island" after it is pulled out of the DOM, based on the original view model.
Although in our project we also used hybrid jQuery+Knockout, I would highly recommend you avoid this whenever possible. There were so many hacks we had to employ to sustain this type of application. The very best thing you should do is prefer Knockout binding handlers (and I think it has a "component" concept now which I haven't played with) over DOM manipulations to avoid buggy UI management.