Geharnast JavaScript

Dit artikel is eerder geplaatst in de adventskalender 2011 van Fronteers. De discussie speelt zich daar af.

Het belang van JavaScript op internet

Het belang van JavaScript op het web is de laatste jaren enorm toegenomen. Ten eerste heeft JavaScript deels de animatierol van Flash overgenomen, ten tweede is het web applicatiever geworden, waardoor JavaScript (bijvoorbeeld in ajax-communicatie) een grote vlucht genomen heeft. De rol JavaScript wordt groter en tegelijkertijd neemt de professionalisering toe. Het is opvallend te zien dat veel best practices uit de back-end-wereld gemeengoed aan het worden zijn bij JavaScript-development. Testen is zo’n belangrijk onderdeel.

Testen

Het testen van JavaScript kan op meerdere niveaus gedaan worden:

  1. Lint testen. Het testen op correcte syntax door tools als JsHint, JsLint of Closure Linter. Dit kan deels door je editor gedaan worden.
  2. Functioneel testen. Het testen van bijvoorbeeld klikscenario’s in je website of app met een tool als Selenium.
  3. Unittesten. Daar gaan we in dit artikel dieper op in.
  4. Last but not least: handmatig testen. Idealiter stel je hiervoor schriftelijke testscripts op, zodat je gestructureerd dezelfde scenario’s kunt testen bij opeenvolgende software releases.

De eerste drie soorten testtools kunnen geautomatiseerd worden, zodat je continu op de hoogte gebracht kunt worden van de staat van je applicatie.

Unittesten

Wikipedia zegt over unittesten: “Unittesten is een methode om softwaremodulen of stukjes broncode (units) afzonderlijk te testen. Bij unittesten zal voor iedere unit een of meerdere tests ontwikkeld worden. Hierbij worden dan verschillende testcases doorlopen. In het ideale geval zijn alle testcases onafhankelijk van andere tests.” Verschillende units samen worden getest in een integratietest.

Voor JavaScript zijn er diverse unittest frameworks beschikbaar. Enkele bekende zijn:

In dit artikel kijken we verder naar unittesten met Jasmine. Jasmine kan onafhankelijk van een JavaScript-library gebruikt worden en heeft ook geen DOM nodig om zijn testen uit te voeren. Verder is Jasmine goed te automatiseren. In syntax en mogelijkheden verschilt Jasmine niet veel van andere unittesting tools.

TDD – Assume your code will fail

Eén stap verder nog dan systematisch testen is het testen als uitgangspunt te nemen in je software-ontwikkelproces: “Test-Driven Development” (TDD). Test-Driven Development is een ontwikkelmethode voor software waarbij eerst tests worden geschreven en daarna pas de code. De testcases worden beschreven vanuit het oogpunt van de gebruiker. Hoewel TDD (een methodiek) en Jasmine (een tool) niet per definitie een combinatie vormen, werpen we hier een korte, inleidende blik op TDD met Jasmine.

Jasmine installeren

  1. De voorbeeldcode bij dit artikel is te downloaden vanaf github.
  2. Op het hoogste niveau zie je de directory’s “lib”, “spec” en “src”. “lib” bevat de core-bestanden van Jasmine, in “src” komen de JavaScriptbestanden van je project en in “spec” zitten de bestanden die de sourcecode gaan testen.
  3. Open SpecRunner.html in je favoriete browser. Er worden direct enkele testen uitgevoerd. En met succes, want de SpecRunner kleurt groen.

Een eerste Jasmine unit test maken

Jasmine is opgebouwd uit suites, specs en expectations. Eén JavaScript-project bestaat normaal gesproken uit meerdere Jasmine testsuites. Eén testsuite, die vaak een component of een class omvat, kan op zijn beurt een geneste suite of meerdere specs bevatten. Een spec test gerelateerde functionaliteit. In een spec kunnen één of meerdere test cases (expectations) gedefinieerd zijn. Met de standaard matchers van Jasmine (functies zoals toEqual(), toBe(), toMatch(), toBeUndefined() etc.) kun je verschillende scenario’s testen.

Hoe schrijf je een spec? Belangrijke leidraden voor TDD zijn:

  1. Schrijf eerst je test (dus niet eerst je functionele code)
  2. Zie de test falen
  3. Schrijf nu de code om de test te laten slagen, op de snelst mogelijke manier, dus niet rekening houdend met eventuele aanpassingen in de code
  4. Refactor (verbeter de code zonder de functionaliteit te wijzigen)
  5. Herhaal deze stappen

Voorbeeld

Stel dat je de Fronteers webshop beheert. Een product kan een variabele prijs hebben: De gewone bezoeker betaalt de volle mik, vaste klanten krijgen 20% korting, en Fronteersleden toucheren maar liefst 50%. Als je hier een functie voor wilt schrijven zou je dat volgens TDD met Jasmine als volgt kunnen aanpakken:

  1. Maak een suite aan (/spec/FronteersShopSpec.js) die het component FronteersShop en de nog te schrijven functie calcDiscount() gaat testen:
    describe("FronteersShop", function() {
    
      var fs;
      var CLIENTTYPE_MEMBER    = 'member',
          CLIENTTYPE_FRONTEERS = 'fronteers',
          CLIENTTYPE_NONMEMBER = 'other';
    
      // Is executed before each spec:
      beforeEach(function() {
        fs = new FronteersShop();
      });
    
    });
  2. De bijbehorende JavaScriptcode (/src/FronteersShop.js) ziet er dan nog als volgt uit:
    function FronteersShop() {
      // a lot TODO
    }
  3. Unittesten worden doorgaans opgeschreven in begrijpelijke taal, zie achtereenvolgens de beschrijving van een suite, een spec en een expectation:
    • describe “when the discount price is calculated”
    • it “should correctly validate function input”
    • expect(fs.calcDiscount(null)).toBeUndefined();

    Dit maakt enerzijds de Jasmine-code self-documenting en geeft anderszijds duidelijk aan waar in de testen fouten optreden.

  4. De functie calcDiscount() willen we twee input-parameters geven
    • price {Number} – de prijs van het product
    • customerType {String} – het soort klant: ‘member’, ‘fronteers’ of ‘other’.
  5. In de eerste stap van onze functie calcDiscount() kijken we (in een geneste suite) of de twee input-parameters van het verwachte datatype zijn:
    describe("FronteersShop", function() {
    
      var fs;
    
      var CLIENTTYPE_MEMBER    = 'member',
          CLIENTTYPE_FRONTEERS = 'fronteers',
          CLIENTTYPE_NONMEMBER = 'other';
    
      beforeEach(function() {
        fs = new FronteersShop();
      });
    
      describe("when the discount price is calculated", function() {
    
        it("should correctly validate function input", function() {
          // Wrong number of expected arguments
          expect(fs.calcDiscount(1)).toBeUndefined();
          // First argument of wrong data type
          expect(fs.calcDiscount(null, CLIENTTYPE_FRONTEERS)).toBeUndefined();
          // Second argument of wrong data type
          expect(fs.calcDiscount(100, null)).toBeUndefined();
          // Input should be accepted
          expect(fs.calcDiscount(100, CLIENTTYPE_FRONTEERS)).toBeDefined();
        });
    
      });
    
    });

De testen zullen falen, want de bijbehorende code ontbreekt. Hier volgt een eerste implementatie van calcDiscount():

FronteersShop.prototype.calcDiscount = function(price, customerType) {

	// Check if parameter "price" is correct, must be floating point number
	if (isNaN(parseFloat(price)) || (!isFinite(price)) ) {
		return;
	}
	// Check if parameter "customerType" is a String
	if (typeof customerType !== 'string') {
		return;
	}

}

(Normaal gesproken wil je waarschijnlijk geen kale return doen, maar handel je de fout af. Dat valt buiten de scope van dit artikel.)

Tenslotte moet calcDiscount() doen waarvoor het in het leven geroepen is, de juiste prijs teruggeven, al dan niet met korting. Eerst schrijven we de spec:

describe("FronteersShop", function() {

   // ...

  describe("when the discount price is calculated", function() {

    // ....

    it("should correctly calculate discount", function() {
      // Expect 50% of 100 => 50
      expect(fs.calcDiscount(100, CLIENTTYPE_FRONTEERS)).toEqual(50);
      // Expect 80% of 100 => 80
      expect(fs.calcDiscount(100, CLIENTTYPE_MEMBER)).toEqual(80);
      // Expect 100% of 100 => 100
      expect(fs.calcDiscount(100, CLIENTTYPE_NONMEMBER)).toEqual(100);
      // Check decimals, expect 50% of 17.1 => 8.55
      expect(fs.calcDiscount(17.1, CLIENTTYPE_FRONTEERS)).toEqual(8.55);
    });

  });

});

Met vallen en opstaan kunnen we dan de bijbehorende JavaScript afleveren:

FronteersShop.prototype.calcDiscount = function(price, customerType) {

	// ...

	if (customerType === 'member') {
		return price * 0.8;
	} else if (customerType === 'fronteers') {
		return price * 0.5;
	} else {
		return price;
	}
};

Dit is natuurlijk een simpel voorbeeld. Hoe complexer je code, hoe meer de waarde van unittesten toeneemt.

Wat verder?

Om verder up-speed te komen met Jasmine kan het inleidende artikel op Nettuts erg nuttig zijn. De Jasmine-wiki biedt alle noodzakelijke informatie. Er zijn veel plugins voor Jasmine beschikbaar, bijvoorbeeld om Jasmine in je IDE te integreren (JsTestDriver) of om met zogeheten fixtures de interactie met de DOM te testen (zie de Jasmine jQuery-plugin). Het is ook mogelijk om Jasminetesten automatisch uit te voeren in Maven en hen zo een onderdeel te maken van de Continuous Integration.

Mocht je offline zijn dan heb je wellicht iets aan het verhelderende boek van Christian Johansen Test-Driven JavaScript Development.

Tot slot

Wanneer moet je nu alles uit de kast halen met unittesten? Als je een JavaScript-library, plugins of herbruikbare componenten schrijft, zijn unittesten een must. Maar ook in langlopende projecten met een snelle release cycle zijn unittesten onmisbaar om mogelijke regressiebugs af te vangen. Zelfs als je korte toevoeging schrijft voor een kleine website kunnen unittesten erg nuttig blijken. Ze laten je anders tegen je code aankijken, waardoor je code robuuster, leesbaarder en makkelijker overdraagbaar wordt. Ook voor front-enders belangrijke waarden.

CSS code insight in Eclipse or Aptana

Lately I changed my editor from Aptana to Eclipse with the Aptana plugin. Suddenly my CSS code insight did not work any longer. With these steps I could get it working again:

  1. Goto Window / Preferences
  2. Type filter text “assoc”
  3. At “General – Editors – File Associations” select “*.css”
  4. Verify that the “CSS Source editor” is the default editor. If not select it and restart Eclipse.
  5. If that still does not give you CSS code insight, check the following:
  6. Goto the Project Explorer and right click on your project.
  7. Select “Properties” and then “Project Natures”.
  8. Select project nature “Web” and set it to primary. Eclipse should restart and refresh your project.
  9. Now you should have CSS code insight. If not, throw your computer out of the window. Btw, that does not necessarily give you code insight.

Run Selenium IDE tests with jQuery selectors

This took me a lot of time to find out, so here is a short and handy overview for everyone that wants to use jQuery selectors instead of Xpath selectors in their Selenium IDE tests. First the steps to achieve this, followed by some comments for the few readers that are still interested.

Add jQuery selectors to Selenium

  1. The selector engine of jQuery is called Sizzle. Download it from sizzlejs.com and store the file sizzle.js in your Selenium project. (I used the current version, 1.0.)
  2. Create a blank JavaScript file, rename it to “user-extensions.js” and save it to your Selenium project.
  3. Add the following code to “user-extensions.js”:
    PageBot.prototype.locateElementBySizzle = function(locator, inDocument) {
    var results = [];
    window.Sizzle(locator, inDocument, results);
    return results.length > 0 ? results[0] : null;
    }
  4. Open Selenium IDE from Firefox.
  5. Go to Options > Options > General.
  6. Click the “Browse …”-button of “Selenium Core extensions (user-extensions.js)”
  7. Multiple select “sizzle.js” and “user-selections.js”. They should be added comma-seperated to the input field (for example: “D:myProjectuser-extensions.js, D:myProjectsizzle.js“).
  8. Restart Selenium IDE.
  9. Execute the following test:

Command: verifyElementPresent
Target: sizzle=body
Value: true

This test should succeed on every normal HTML page. ;)

Comments

  • Why use Sizzle and not the complete  jQuery library? The same setup can also be used for jQuery, but I could not get it working. Some say that recent versions of jQuery (I use 1.4.4) don’t cope with Selenium, at the moment.
  • It’s still possible though to use jQuery functionality in your tests. If jQuery is available on the page you are testing, one could do a test similar to:

Command:assertEval
Target: selenium.browserbot.getUserWindow().jQuery("body").length
Value: 1

 

Helpful resources

 

Set text file encoding to UTF-8 in Aptana

Working in a web environment, it’s important to keep your character encoding clear. Serious websites set their character encoding to UTF-8 to be sure to be compatible with different language sets. Obviously the same accounts for the database.

Sometimes it’s also important to set the text file encoding of your script file (php etc.). In Aptana 2 the global text file encoding is set to Cp1252. You can change this to UTF-8 the following way:

  • In Aptana go to Window -> Preferences
  • Type “encoding” in the search box.
  • Go to General -> Workspace
  • Change the text file encoding to UTF-8 and apply.

In my case Aptana hung with the build settings so I had to uncheck them all:

Happy UTF-8 programming!

CSS and ellipsis: Cross-browser practices

Every now and then, say twice a year, I run into the same problem: what is the best cross-browser ellipsis solution? Because I end up searching the internet everytime, I decided the write down my prefererred solution.

The problem is as follows: You have a long sentence and you want to show only the first line and end up with … (the ellipsis) and trunk the rest of the sentence. Of course you can implement a server side solution, but today I prefer a client-side one.

IE6 and IE7 are simple, they support the CSS-property text-overflow, as do the Webkit based browsers Safari and Chrome. Text-overflow is part of the coming CSS3-recommendation, by the way. Because the property is not standardized yet Opera has it’s own implementation -o-text-overflow, while IE8 in standards mode supports -ms-text-overflow.

The only hickup we encouter with Firefox. The current version (Firefox 3.5) still does not support this property… This leaves us for several workarounds:

  • Do nothing. All browsers will show an ellipsis, Firefox just shows nothing. My prefered solution, because it involves less time and effort.
  • Create a Firefox-only selector in your CSS. This can be handy is you have something like Browserhawk installed on your server. Is not 100% waterproof though. Don’t just add the sometimes suggested “:after-content” option. (With something like myclass:after { content: “…” } we could easy add ellipsis to Firefox, but other modern browsers would render the ellipsis twice.
  • Use the “:after-content” setting for all modern browsers and show the ellipsis in IE6 and IE7 with conditional comments.
  • Use a JavaScript solution. Check for example this nice jQuery plugin.
  • Use XUL.

The CSS at last:

.ellipsis {
  display: block;
  white-space: nowrap;
  text-overflow: ellipsis;
  -o-text-overflow: ellipsis;
  -ms-text-overflow: ellipsis;
  width: 230px;
  overflow: hidden;
}

Bonus:

Toggle the ellipsis view with jQuery:

$(".ellipsis").click( function() { $(this).toggleClass(".ellipsis") });

Disclaimer:

Not all options are tested. Please comment.

IIS7 configuren voor Windows Vista Home Premium (NL)

Daar ging mijn halve avond in rook op: ik wilde even mijn oude ASP-site draaiend krijgen op mijn localhost op Windows Vista…

  1. Met Windows Vista wordt IIS7 meegeleverd. Deze staat standaard (natuurlijk) niet aan. IIS aanzetten gaat via het Configuratiescherm > Programma’s > Windows-onderdelen in- of uitschakelen (administrator-rechten vereist).
  2. Simpel Internet Information Services aanklikken is niet voldoende. Je moet ook kiezen voor World Wide Web-services > Toepassingsontwikkelingsfuncties > ASP.
  3. Je zou nu een ASP-site moeten kunnen draaien op http://localhost.

Iedereen – behalve de Goden op de Olympus – maakt wel eens fouten in hun software. Om gedetailleerde foutmeldingen te tonen moet je de halve wereld omdraaien, maar de volgende handelingen zouden ook kunnen helpen:

  1. Je kunt de IIS-manager (IIS-beheer geheten in het Nederlands) openen door in Vista te zoeken op “iis”.
  2. Selecteer de default website (of een andere als het daar om gaat…).
  3. Dubbelklik op het icoon Foutpagina’s. Klik op de actie Functie-instellingen bewerken…
  4. Selecteer een van de opties voor gedetailleerde foutmeldingen.
  5. Nu kreeg ik direct de weinig verhelderende foutmelding “An error occurred on the server when processing the URL. Please contact the system administrator” voor de kiezen.
  6. Nu kun je a) in je logfiles gaan neuzen (die standaard op onzichtbaar staan in de Verkenner, dus die moet je eerst tweaken).
  7. Fijner is het natuurlijk om de exacte foutmelding in je browser te zien. Daartoe moet je een command box openen (als administrator!!) en de volgende regel uitvoeren “%windir%system32inetsrvappcmd.exe set config -section:asp /scriptErrorSentToBrowser:true“. (Zie de comments op IIS.net)

Happy debugging!

IE7 and form submitting

Often, IE7 is regarded less standards compliant than its open source alternatives. Accidentally I ran into a – at least for me – unknown feature in IE7 which proves that in some cases IE7 can be rather strict. Take this, a FORM without a submit-button, but with a BUTTON-button:

<form>
  <!-- a lot of code -->
  <button>fire!</button>
</form>

Firefox just submits the form when you click the button. IE7 however does nothing! It appeared that IE7 only submits the form when the attribute type is specified as type=”submit”.

  <button type="submit">fire!</button>

Cool! Seems logically in the end. Where are the times that Microsoft interpreted all your sloppy code?

Tested with a XHTML 1.0 Strict page with a FORM-element which included three BUTTON-buttons. I encountered no form submitting problems without the type attribute specified  in FF 2.0.0.1 or in Opera 8.54 on Windows XP SP2. I didn’t test in IE6 or other exotic browsers. Continue reading

Jaiku

Loop je voortdurend rond met gedachten hoe je internetmiljonair kunt worden door mee te liften op de web2.0-hype? Probeer dan niet te focussen op slimme of handige apps waar men iets aan heeft. Mijn favoriete verpozingswebsite van de laatste tijd is Jaiku, een volstrekt nutteloos (Fins) initiatief dat jouw diverse feeds en die van je vrienden aan elkaar knoopt. Wel even om het uur je ‘presence‘ aanpassen!

Tags: , , , , ,

Play around with Microsoft Gadgets

Yahoo! came with Widgets, so now it’s Microsoft’s turn to introduce Gadgets. It’s the same idea: little, useless programs you install on your desktop and that eat up your resources. Interesting point: Microsoft Gadgets only run on Windows Vista!

However, Microsoft also has a web alternative: live gadgets. These gadgets are installed and run on your personal start page (as Microsoft sees it) live.com. Today I had to do a bit of a research on gadgets and walked through the Windows Live Gadget Developer’s Guide. In the guide I discovered two little errors, so if you want to play around with gadgets be aware:

  1. In section “Setting Up Your Environment” is stated: “In order to develop locally hosted Gadgets within IE, ensure that you have set the ‘Access data sources across domains’ setting to ‘prompt’. Inside IE, you can check this through Tools -> Internet Options… Click on the Security tab, select internet zone and click on ‘Custom Level’. Under ‘Miscellaneous’ select ‘prompt’ for ‘Access data sources across domains’.” Be sure that this accounts for the internet zone as well as for the trusted zone!
  2. In section “Defining Content” a code example is provided with the following line “Gadgets/htmlinliner/htmlcontent.xml”. On my station this only worked with a full address: “http://localhost/Gadgets/htmlinliner/htmlcontent.xml”.

Check my delicious-links on Microsoft Gadgets. Happy gadgetting!

Tags: , , , , ,