Capybara: Test for Content in CSS PsuedoElements - ruby-on-rails-4

I'm working on an app where text conditionally appears in a ::before pseudo element's content property and is rendered on the page. After a code change caused this important text to accidentally disappear, I wanted to be able to write tests that would capture that error if it happened again, but there are challenges grabbing the content from pseudo-selectors. I was looking for something like:
#scss
.content-div {
&.condition-true {
&:before {
content: "conditional text";
}
}
}
#coffeescript
if #someCondition
$('content-div').addClass('condition-true')
else
$('content-div').removeClass('condition-true')
#spec
context "when true" do
it "should have the conditional text" do
# do thing that makes it true
expect( page ).to have_content("conditional text")
end
end
The solution wasn't so easy, and I thought I'd share here and let others comment, or provide other solutions.
I'm using Capybara 2.3.0 and Poltergeist 1.5.1.

The key was passing a block of code to page.evaluate_script, as well as Javascript's getComputedStyle() function.
content_array = page.evaluate_script <<-SCRIPT.strip.gsub(/\s+/,' ')
(function () {
var elementArray = $('.desired-css-selector');
var contentArray = [];
for (var i = 0, tot=elementArray.length; i < tot; i++) {
var content = window.getComputedStyle( elementArray[i], ':before' ).getPropertyValue('content');
contentArray.push(content);
}
return contentArray;
})()
SCRIPT
content_array.each { |c| c.gsub!(/\A'|'\Z/, '') }
expect( content_array ).to include("conditional text")
UPDATE - SIMPLE EXAMPLE:
I've recently had to do a much simpler version of this:
color = page.evaluate_script <<-SCRIPT
(function () {
var element = document.getElementById('hoverme');
var color = window.getComputedStyle( element, ':hover' ).getPropertyValue('color');
return color;
})()
SCRIPT

Related

Changing image based on location data output

I am trying to show/echo users location on a webpage using maxmind geoip2 paid plan, I also want to show different images based on the state/city names output.
For example, if my webpage shows the user is from New York, I would like to show a simple picture of New York, if the script detects the user is from Washington, the image should load for Washington.
This is the snippet I have tried but doesn't work.
<script type="text/javascript">
if
$('span#region=("New York")') {
// Display your image for New York
document.write("<img src='./images/NY.jpg'>");
}
else {
document.write("<img src='./images/different.jpg'>");
}
</script>
This is the code in the header.
<script src="https://js.maxmind.com/js/apis/geoip2/v2.1/geoip2.js" type="text/javascript"></script>
<script>
var onSuccess = function(geoipResponse) {
var cityElement = document.getElementById('city');
if (cityElement) {
cityElement.textContent = geoipResponse.city.names.en || 'Unknown city';
}
var countryElement = document.getElementById('country');
if (countryElement) {
countryElement.textContent = geoipResponse.country.names.en || 'Unknown country';
}
var regionElement = document.getElementById('region');
if (regionElement) {
regionElement.textContent = geoipResponse.most_specific_subdivision.names.en || 'Unknown region';
}
};
var onError = function(error) {
window.console.log("something went wrong: " + error.error)
};
var onLoad = function() {
geoip2.city(onSuccess, onError);
};
// Run the lookup when the document is loaded and parsed. You could
// also use something like $(document).ready(onLoad) if you use jQuery.
document.addEventListener('DOMContentLoaded', onLoad);
</script>
And this simple span shows the state name in body text of the Html when the page loads.
<span id="region"></span>
now the only issue is the image doesn't change based on users location, what am i doing wrong here?
Your example is missing some code, but it looks like you are running some code immediately and some code in a callback, a better way to do it is to have all the code in the callback:
// whitelist of valid image names
var validImages = ["NJ", "NY"];
// get the main image you want to replace
var mainImage = document.getElementById('mainImage');
if (mainImage) {
// ensure there is a subdivision detected, or load the default
if(geoipResponse.subdivisions[0].iso_code && validImages.includes( && geoipResponse.subdivisions[0].iso_code)){
mainImage.src = "./images/" + geoipResponse.subdivisions[0].iso_code + ".jpg";
} else {
mainImage.src = "./images/different.jpg";
}
}
Then just have the image you want to replace be:
<img src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D" id="mainImage" />
Notes:
If you are using a responsive image, make sure your transparent gif is the same ratio height of to width as your final image to avoid page reflows.
You will have to load the different.jpg in the onError callback as well.

Sitecore experience editor button triggering .aspx pop up page

Is it possible to make sitecore experience editor ribbon button trigger .aspx page in pop up window? It was possible before speak-ui was introduced by assigning a command to the button's click field.
There is a lot of tutorials describing how to use XML controls (e.g. http://jockstothecore.com/sitecore-8-ribbon-button-transfiguration/) but I can't find any information about triggering .aspx page.
My command looks like this:
<command name="item:showDashboard" type="Sitecore.Starterkit.customcode.Reports, MyProject" />
In the tutorial you posted I will just modify the code snippets to reflect what you need to do. (Considering you have done everything else). In the part Spell Two
In the command class you should do something like this (if you need to wait for postback):
public override void Execute(CommandContext context)
{
Assert.ArgumentNotNull((object) context, "context");
Context.ClientPage.Start((object) this, "Run", context.Parameters);
}
protected static void Run(ClientPipelineArgs args)
{
Assert.ArgumentNotNull((object) args, "args");
SheerResponse.ShowModalDialog(new UrlString("/YOURURL.aspx").ToString(), true);
args.WaitForPostBack();
}
If you just want to show something :
public override void Execute(CommandContext context)
{
Assert.ArgumentNotNull((object)context, "context");
if (context.Items.Length != 1)
return;
Item obj = context.Items[0];
UrlString urlString = new UrlString("/YOURURL.aspx");
urlString["fo"] = obj.ID.ToString();
urlString["la"] = obj.Language.ToString();
urlString["vs"] = obj.Version.ToString();
string str = "location=0,menubar=0,status=0,toolbar=0,resizable=1,getBestDialogSize:true";
SheerResponse.Eval("scForm.showModalDialog('" + (object)urlString + "', 'SitecoreWebEditEditor', '" + str + "');");
}
For the javascript:
define(["sitecore"], function (Sitecore) {
Sitecore.Commands.ScoreLanguageTools = {
canExecute: function (context) {
return true; // we will get back to this one
},
execute: function (context) {
var id = context.currentContext.itemId;
var lang = context.currentContext.language;
var ver = context.currentContext.version;
var path = "/YOURURL.aspx?id=" + id + "&lang=" + lang + "&ver=" + ver;
var features = "dialogHeight: 600px;dialogWidth: 500px;";
Sitecore.ExperienceEditor.Dialogs.showModalDialog(
path, '', features, null,
function (result) {
if (result) {
window.top.location.reload();
}
}
);
}
};
});

How do I return data to a template with Knockout and Requirejs modules?

I'm having a difficult time returning data from a module using RequireJS and Knockout to populate my markup for my single page app. Knockout can't seem to find my data binding observables.
I'm trying to keep each view in a separate js file, but I'm failing to identify where I've gone wrong. Here's what I have so far:
/app/app.js
define(function(require) {
require('simrou');
var $ = require('jQuery'),
ko = require('knockout'),
videoView = require('videoView');
var init = function() {
var viewModel = function() {
var self = this;
self.currentPage = ko.observable();
self.videoView = new videoView();
}
var view = new viewModel();
ko.applyBindings( view );
_router = new Simrou({
'/video/:id': [ view.videoView.getVideo ]
});
_router.start();
};
return {
init: init
};
});
/app/videoView.js
define(function(require) {
"use strict";
var $ = require('jQuery'),
ko = require('knockout');
return function() {
var self = this;
self.currentPage = ko.observable( 'showVideo' );
self.currentVideo = ko.observable();
self.videoData = ko.observableArray([]);
self.videoList = ko.observableArray([]);
var getVideo = function( event, params ) {
// ajax pseudo code
$.ajax({});
self.videoData( dataFromAjaxCall );
}
return {
getVideo: getVideo
};
};
});
index.html
When I browse to /#/video/14 I receive the following error:
Uncaught ReferenceError: Unable to parse bindings.
Bindings value: attr: { 'data-video-id': videoData().id }
Message: videoData is not defined
Here's the markup:
<section id="showVideo" data-bind="fadeVisible: currentPage()=='showVideo', with: $root">
<div class="video" data-bind="attr: { 'data-video-id': videoData().id }></div>
</section>
Like I said, I'm trying to keep each view separated, but I would love some enlightenment on what I'm doing wrong, or if this is even possible? Is there a better more efficient way?
videoData is a property of $root.videoView, not of the root model (the one you passed to applyBindings). It's also an observableArray, so videoData() is just a plain array and even if you get the context right, you won't be able to access its id property, since, being an array, it doesn't have.named properties.

How can I simulate blur when testing directives in angularjs?

The problem
I am trying to test some directives (code for both below). One of them is an "email" (called "epost" in the code(norwegian)) directive. The solution to this should work for all of them, so I am keeping it to this one for now.
Technologies: Angularjs, Jasmine, Requirejs, (grunt & karma running in Chrome)
The directive validates email addresses in two ways; on upshift and on blur. I can test the upshift without problems as you can see in the test below, but I can't figure out how to simulate a blur so the bind('blur') in the directive runs.
What I have done
I have tried to catch the compiled element like this:
elem = angular.element(html);
element = $compile(elem)($scope);
And then in the test i tried several permutations to trigger the blur with a console log just inside the bind function in the directive. None of the below works. It does not trigger.
elem.trigger('blur');
element.trigger('blur');
elem.triggerHandler('blur');
element.triggerHandler('blur');
element.blur();
elem.blur();
I based the injection and setup on this: To test a custom validation angularjs directive
The email directive in angularjs wrapped in requirejs
define(function() {
var Directive = function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var pattern = /^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
elem.bind('blur', function() {
scope.$apply(function () {
if (!elem.val() || pattern.test(elem.val())) {
ctrl.$setValidity('epost', true);
} else {
ctrl.$setValidity('epost', false);
}
});
});
ctrl.$parsers.unshift(function(viewValue) {
if (pattern.test(viewValue)) {
ctrl.$setValidity('epost', true);
return viewValue;
} else {
return undefined;
}
});
}
};
};
return Directive;
});
The test (using jasmine and requirejs)
define([
'Angular',
'AngularMocks',
], function () {
describe('Directives', function () {
var $scope;
var form;
beforeEach(module('common'));
beforeEach(function () {
var html = '<form name="form">';
html += '<input type="text" id="epost" name="epost" epost="" ng-model="model.epost"/>';
html += '</form>';
inject(function ($compile, $rootScope) {
$scope = $rootScope.$new();
$scope.model = {
epost: null
};
// Compile the element, run digest cycle
var elem = angular.element(html);
$compile(elem)($scope);
$scope.$digest();
form = $scope.form;
});
});
describe('(epost) Given an input field hooked up with the email directive', function () {
var validEmail = 'a#b.no';
var invalidEmail = 'asdf#asdf';
it('should bind data to model and be valid when email is valid on upshift', function () {
form.epost.$setViewValue(validEmail);
expect($scope.model.epost).toBe(validEmail);
expect(form.epost.$valid).toBe(true);
});
});
});
});
I have been able to figure out where I went wrong after some breakpoint debugging.
The "element" item I get out using the approach described in the top of the question is not actually the directive it self. It's an object which wraps the form and the directive.
Like this
{ 0: // The form
{ 0: // The directive (input element)
{
}
}
}
To actually simulate a blur on the directive it self, I did something like this
var directiveElement = $(element[0][0]);
directiveElement.blur();
After getting the element I wanted, and wrapping it in a jQuery object (may be optional), it worked like a charm. I then used the approach like in the test in the question with $setViewValue and checked the model value like this.
form.epost.$setViewValue('a#b.no');
directiveElement.blur();
expect($scope.model.epost).toBe('a#b.no');
expect($scope.form.epost.$valid).toBeTruthy();
Hope this could be of help to others trying to figure the directive testing out.
I too ran into a similar problem and it mystified me. My solution was to use JQuery to get the input and then use angular.element(input).triggerHandler('blur') to make it work. This is odd to me because I do not have to do this with the click event.
spyOn(controller, 'setRevenueIsInvalid');
var sugarRow = $(element).find('tr#ve_id_5')[0];
var amount = $(sugarRow).find('input.amount')[0];
angular.element(amount).triggerHandler('blur');
expect(controller.setRevenueIsInvalid).toHaveBeenCalled();

How do you handle pluralisation in Ember?

Are there any helpers for making templates aware of when to use plural words?
In the example below, how do you make the template output "2 dogs have..."?
The code:
Ember.View.create({dog_count: 2})
The template:
{{dog_count}} (dog has)/(dogs have) gone for a walk.
I know this is old, but I needed it today, so here goes.
Ember.Handlebars.registerBoundHelper('pluralize', function(number, opts) {
var single = opts.hash['s'];
Ember.assert('pluralize requires a singular string (s)', single);
var plural = opts.hash['p'] || single + 's';
return (number == 1) ? single : plural;
});
Usage:
{{questions.length}} {{pluralize questions.length s="Question"}}
or
{{dog_count}} {{pluralize dog_count s="dog has" p="dogs have"}} gone for a walk.
The plural (p=) option is only necessary when you don't want the standard +s behavior.
There is a I18n library for Ember: zendesk/ember-i18n.
There is a handlebars helper t which handles the internationalization by looking up string from Em.I18n.translations:
Em.I18n.translations = {
'dog.walk.one': '1 dog has gone for a walk.',
'dog.walk.other': '{{count}} dogs have gone for a walk.'
};
And you can then use the string in your Handlebars template via:
{{t dog.walk countBinding="dogCount"}}
The code above is untested and just taken from the documentation in the README.
Another JS I18n library I found is Alex Sexton's messageformat.js.
It depends on the complexity of you app, but you can also use a computed property for that, see http://jsfiddle.net/pangratz666/pzg4c/:
Handlebars:
<script type="text/x-handlebars" data-template-name="dog" >
{{dogCountString}}
</script>​
JavaScript:
Ember.View.create({
templateName: 'dog',
dogCountString: function() {
var dogCount = this.get('dogCount');
var dogCountStr = (dogCount === 1) ? 'dog has' : 'dogs have';
return '%# %# gone for a walk.'.fmt(dogCount, dogCountStr);
}.property('dogCount')
}).append();
If you use Ember Data you can use Ember.Inflector.
var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
inflector.pluralize('person') //=> 'people'
You can register a new helper with:
Handlebars.registerHelper('pluralize', function(number, single) {
if (number === 1) { return single; }
else {
var inflector = new Ember.Inflector(Ember.Inflector.defaultRules);
return inflector.pluralize(single);
}
});
More details at http://emberjs.com/api/data/classes/Ember.Inflector.html
It looks like you got an answer from wycats himself, but I didn't see it mentioned in this thread, so here it is:
Handlebars.registerHelper('pluralize', function(number, single, plural) {
if (number === 1) { return single; }
else { return plural; }
});
I recently found this library http://slexaxton.github.com/Jed/ which seems to be a nice tool for JS i18n. I guess you can pretty easily create your own implementation by registering a handlebars helper using this library.
I do not know of any Ember specific functions that will do this for you. However, generally when you pluralize a word, the single version only shows up when the count is one.
See this for an example: http://jsfiddle.net/6VN56/
function pluralize(count, single, plural) {
return count + " " + (count == 1 ? single : plural);
}
pluralize(1, 'dog', 'dogs') // 1 dog
pluralize(10, 'dog', 'dogs') // 10 dogs
pluralize(0, 'dog', 'dogs') // 0 dogs