Tuesday, June 15, 2010

NDC 2010: Bookmarklet to export full day plan as single calendar file for Outlook / Google Cal

This is probably just relevant for people attending the Norwegian Developer Conference in Oslo this year .. Unless you've got some wicked obsession with iCal or JavaScript lunacy.

Anyhoo: the agenda page at ndc2010.no doesn't seem to allow you to export an entire day in a single iCal file, so I decided to write a quick jQuery based bookmarklet to properly export a full day. Here's a video demonstration:


Click the video above to watch it embedded (best in full screen), or Watch in new window


In a nutshell: just go to the agenda page, log in, make sure your sessions for the target day are selected, then hit the bookmarklet to generate an aggregate iCal file.

And here's the bookmarklet. Drag it to your bookmark toolbar (if that works in your browser), or right click and save it as a bookmark from the context menu.

Update: Worth noting for Google Calendar (and possibly others) is that it will not import "duplicate" entries. This means that if you export a day plan, delete a few entries, then try to import the file again - it will throw a big bad error. At this point you can go back to the agenda page and export a new iCal file, which will have new event timestamp. Importing this will succeed without errors.
Update2 (June 15th, 3:11pm): Fixed a padding issue with dates when imported into Google Calendar, and replaced padding approach as a whole.

If you're curious about the source code, here it is in non-minified form. Some less-than pretty constructs are used, such as while-shift rather than join for Array expansion - to save processing memory, and avoid errors caused by such.

(function(){
function padl(s,n,c)
{
return new Array(1 + n - s.length).join(c) + s;
}

function makeCalendar()
{
var str = "";
while(events.length > 0)
{ str += (str == "" ? "" : "\r\n") + events.shift(); }
return "BEGIN:VCALENDAR" + "\r\n" +
"PRODID:-//Google Inc//Google Calendar 70.9054//EN" + "\r\n" +
"VERSION:2.0" + "\r\n" +
"CALSCALE:GREGORIAN" + "\r\n" +
"BEGIN:VTIMEZONE" + "\r\n" +
"TZID:Europe/Oslo" + "\r\n" +
"LAST-MODIFIED:20040526T134920Z" + "\r\n" +
"BEGIN:DAYLIGHT" + "\r\n" +
"DTSTART:20040328T010000" + "\r\n" +
"TZOFFSETTO:+0200" + "\r\n" +
"TZOFFSETFROM:+0000" + "\r\n" +
"TZNAME:CEST" + "\r\n" +
"END:DAYLIGHT" + "\r\n" +
"BEGIN:STANDARD" + "\r\n" +
"DTSTART:20041031T030000" + "\r\n" +
"TZOFFSETTO:+0100" + "\r\n" +
"TZOFFSETFROM:+0200" + "\r\n" +
"TZNAME:CET" + "\r\n" +
"END:STANDARD" + "\r\n" +
"END:VTIMEZONE" + "\r\n" +
str + "\r\n" +
"END:VCALENDAR";
};

function makeEvent(day, start, end, summary, description)
{
return "BEGIN:VEVENT" + "\r\n" +
"DTSTART;TZID=Europe/Oslo:201006" + day + "T" + start + "00Z" + "\r\n" +
"DTEND;TZID=Europe/Oslo:201006" + day + "T" + end + "00Z" + "\r\n" +
"DTSTAMP;TZID=Europe/Oslo:" + stamp + "\r\n" +
"DESCRIPTION:" + description + "\r\n" +
"LOCATION:Oslo Spektrum" + "\r\n" +
"SEQUENCE:0" + "\r\n" +
"STATUS:CONFIRMED" + "\r\n" +
"SUMMARY:" + summary + "\r\n" +
"TRANSP:OPAQUE" + "\r\n" +
"END:VEVENT";
};

var events = [];
var nowdt = new Date();
var stamp = nowdt.getFullYear() +
padl(nowdt.getMonth(), 2, "0") +
padl(nowdt.getDate(), 2, "0") + "T" +
padl(nowdt.getHours(), 2, "0") +
padl(nowdt.getMinutes(), 2, "0") +
padl(nowdt.getSeconds(), 2, "0") + "Z";
$(":checked").each(function()
{
var n = $(this).parents("tbody:first").find("div[id] h2.agenda:first");
var roughTime = n.html();
n = n.parents("tbody:first");
var e =
{
day: 15 + parseInt($(".agenda2:first").text().replace(/.*(\d).*/, "$1")),
track: roughTime.replace(/.*\((.*)\)/, "$1"),
time: roughTime.replace(/Time:\s*(.*) \(.*/, "$1").split(" - "),
subject: n.find(".ingress_agenda").text(),
author: n.find("tr td:nth-child(2) h2:first").text()
};
events.push(makeEvent(e.day, padl(e.time[0].replace(/:/, ""), 4, "0"), padl(e.time[1].replace(/:/, ""), 4, "0"), e.track + ": " + e.subject, e.author));
});
window.location = "data:text/calendar;," + escape(makeCalendar(events));
})()

Monday, June 7, 2010

No-fuss mocking of extension methods (and more) with Microsoft Moles

The last few months I've been working off and on with a pet project of mine, a real-time collaborative text & sketching app, using C# and Silverlight 4. It's TDD'd to the rim, and as such I'm not writing a line of code if it hasn't already been described by a test.

That latter point brought me to a complete halt a few weeks back, when I began testing a part of my framework in charge of building property objects. To build an understanding of what I was actually doing, and how I failed, I'll briefly explain the setup.

I rely on Autofac for IOC, so that's doing the actual type resolving. For Mocking I had exclusively used the brilliant Moq framework. A test for my PropertyFactory.Create() method could therefore be something like:

[TestMethod]
public void Create_ofT_uses_builder()
{
var expected = new Mock<IDummy>();
var mockBuilder = new Mock<IContainer>();
mockBuilder.Setup(x => x.Resolve<IDummy>()).Returns(() => expected.Object);
var factory = new PropertyFactory(mockBuilder.Object);
var returned = factory.Create<IDummy>();
Assert.AreSame(expected.Object, returned);
}

I'd use Moq to gain control of the Resolve method, and be able to return my own dummy property object - and thus verify that the Create method was doing what it was supposed to be doing.

.. or so I thought. Test failed. In big, bold, red letters. What I didn't think of at that point, is that Resolve isn't actually a method brought by Autofac's IContainer; it's an extension method. Moq can't do extension methods, as they are really just glorified statics.

I pondered this for quite some time. Among my options were switching to TypeMock (which would cost me major $), wrapping IContainer in a construct of my own - which would use extension methods (and that would just delay the problem) or switching IOC containers (which would work for a while, but be of no use the next time I was trying to test something with an extension method in it). As such, I was at a complete loss.

Until yesterday.

The Microsoft Moles project has completely eluded me until now, and I'm somewhat ashamed of that. In all its simplicity (a two minute download and install), it solved all my problems - without messing with any of my production code, forcing me to turn away from any of the other frameworks I use, or even push Moq to the sideline. I can still use Moq for most of the heavy lifting, and then just turn to Moles for the obscure stuff.

Here's what my Create test looks like now:

[TestMethod]
[HostType("Moles")]
public void Create_ofT_uses_builder()
{
var expected = new Mock<IDummy>();
Autofac.Moles.MResolutionExtensions.ResolveIComponentContext(x => expected.Object);
var mockBuilder = new Mock<IContainer>();
var factory = new PropertyFactory(mockBuilder.Object);
var returned = factory.Create<IDummy>();
Assert.AreSame(expected.Object, returned);
}

Test Passed!

All I had to to, apart from the single moles delegate used to return my expected dummy object, was to add a new .moles file in my test project. I called this Autofac.moles, and the content would be:

<?xml version="1.0" encoding="utf-8" ?>
<Moles xmlns="http://schemas.microsoft.com/moles/2010/">
<Assembly Name="Autofac" />
</Moles>

In a nutshell, the Moles framework will take the assembly referenced in the .moles file, and generate a Mole assembly for it. For all types TypeX in the referenced assembly, there will be another type MTypeX (or STypeX for interfaces) in a .Moles sub-namespace of TypeX' original namespace. As you go on to run a test using the Moles framework, any .moles-referenced type down the call tree from a method with the [HostType("Moles")] attribute will get its implementation replaced by its generated Mole twin. Thus you can mock just about anything; non-static and static alike.

Brilliant.

Here's the page for Microsoft Moles, at Microsoft Research.