<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-6294118673654365284</atom:id><lastBuildDate>Sat, 21 Feb 2009 01:45:56 +0000</lastBuildDate><title>Nothing to see here...</title><description>Things in John's world...</description><link>http://johnwedgwood.blogspot.com/</link><managingEditor>noreply@blogger.com (John Wedgwood)</managingEditor><generator>Blogger</generator><openSearch:totalResults>5</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/blogspot/johnwedgwood" type="application/rss+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><item><guid isPermaLink="false">tag:blogger.com,1999:blog-6294118673654365284.post-6351353627449662020</guid><pubDate>Mon, 13 Aug 2007 16:40:00 +0000</pubDate><atom:updated>2007-08-13T10:14:39.735-07:00</atom:updated><title>Validation Nirvana</title><description>&lt;p&gt;I've been researching content management systems, and in the process was  reminded of some of the headaches that I've seen in the past when dealing with  content on a site. Having just created a test to ensure that my javascript code  was well formed, I started to wonder if it might be possible to validate all of  my content -- javascript, css and html. The answer turns out to be "yes," and  due to some good work done by others, the process turned out to be a lot less  painful than I expected.&lt;/p&gt; &lt;p&gt;My goals were that as part of my automated tests, I would be able to ensure  that:&lt;/p&gt; &lt;ul&gt;&lt;li&gt;The site javascript contained no obvious errors  &lt;/li&gt;&lt;li&gt;The site css was correctly formed, and valid  &lt;/li&gt;&lt;li&gt;Pages served from the site had valid XHTML &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;In addition, to avoid some misery I had encountered in earlier projects, I  wanted to check a few other things:&lt;/p&gt; &lt;ul&gt;&lt;li&gt;All link (a) tags had valid href attributes  &lt;/li&gt;&lt;li&gt;All img tags had valid src attributes  &lt;/li&gt;&lt;li&gt;All url() attributes in my CSS were valid &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;I do use the Firefox extensions Firebug and HTMLTidy, but those only operate  on the pages loaded in the browser, so they aren't really a substitute for  automated validation.&lt;/p&gt; &lt;p&gt;Let's take these in order ...&lt;/p&gt; &lt;h2&gt;Javascript&lt;/h2&gt; &lt;p&gt;For how javascript checking is done, see my other posting  (&lt;a href="http://johnwedgwood.blogspot.com/2007/08/javascript-lint-and-assetpackager.html"&gt;http://johnwedgwood.blogspot.com/2007/08/javascript-lint-and-assetpackager.html&lt;/a&gt;)  about using a javascript lint program to automatically check javascript code for  the most obvious errors.&lt;/p&gt; &lt;h2&gt;CSS&lt;/h2&gt; &lt;p&gt;I really wanted to use a command line tool for this, but could not find one  that validated the CSS (there are a set of interesting tools for compacting css  though). The solution everyone refers to is to use the w3c online validation  tool (&lt;a href="http://jigsaw.w3.org/css-validator/"&gt;http://jigsaw.w3.org/css-validator/&lt;/a&gt;). It turns out that there is already a  terrific plugin for doing exactly this job -- assert_valid_asset  (&lt;a href="http://www.realityforge.org/articles/2006/03/15/rails-plugin-to-validate-x-html-and-css"&gt;http://www.realityforge.org/articles/2006/03/15/rails-plugin-to-validate-x-html-and-css&lt;/a&gt;).  It even has a method assert_valid_css_files which is almost perfect. It  creates test methods for each CSS file, and these test methods submit the file  contents to the w3c site to be checked.&lt;/p&gt; &lt;p&gt;Unfortunately for me I have CSS files located in sub-directories, and this  method uses the file name as part of the method name for the test, which doesn't  work so well when the file name has a "/" in it. So I took the idea, and rolled  my own method that handles this case, producing a file  test/unit/css_validation_test.rb which processes each CSS file to create  a test method for that CSS file, but which munges the name so that it can handle  CSS files located in sub-folders.&lt;/p&gt; &lt;p&gt;And of course it didn't work -- failing to catch the errors that I'd created  to test it. After hunting a bit, I noticed that the check that is made for  errors from the w3c in assert_valid_asset doesn't appear to work (for me at  least). The line #128 in assert_valid_asset.rb was referring to a set of nested  div elements that contained the errors. In my testing it appeared as though  errors were actually coming back in a table row marked with the error class, so  I changed that line to reflect the return value that I was seeing:&lt;/p&gt; &lt;table class="CodeRay"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"&gt;&lt;pre&gt;1&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt; &lt;td class="code"&gt;&lt;pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"&gt;&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;REXML&lt;/span&gt;::&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;XPath&lt;/span&gt;.each( &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;REXML&lt;/span&gt;::&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Document&lt;/span&gt;.new(response.body).root, &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;//tr[@class='error']&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;) &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;do&lt;/span&gt; |element|&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;With that done, the code did work for me. All of the errors that I tossed at  it were caught, and my CSS was being validated automatically. Here's the  contents of test/unit/css_validation_test.rb:&lt;/p&gt; &lt;table class="CodeRay"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;br /&gt;26&lt;br /&gt;27&lt;br /&gt;28&lt;br /&gt;29&lt;br /&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;br /&gt;31&lt;br /&gt;32&lt;br /&gt;33&lt;br /&gt;34&lt;br /&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;br /&gt;36&lt;br /&gt;37&lt;br /&gt;38&lt;br /&gt;39&lt;br /&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;br /&gt;41&lt;br /&gt;42&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt; &lt;td class="code"&gt;&lt;pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# A test for valid css. At the moment the calls to the w3c are failing, so this&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# is commented out until I figure out a better approach.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;require &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;File&lt;/span&gt;.dirname(&lt;span style="font-weight: bold; color: rgb(0, 51, 136);"&gt;__FILE__&lt;/span&gt;) + &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/../test_helper&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;class&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(187, 0, 102);"&gt;CSSValidationTest&lt;/span&gt; &lt; &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Test&lt;/span&gt;::&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Unit&lt;/span&gt;::&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;TestCase&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Where our css is located&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;CSS_PATH&lt;/span&gt; = &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;File&lt;/span&gt;.dirname(&lt;span style="font-weight: bold; color: rgb(0, 51, 136);"&gt;__FILE__&lt;/span&gt;) + &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/../../public/stylesheets&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# CSS we don't want to process&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;EXCEPTIONS&lt;/span&gt; = [&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;CSS_PATH&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/invalid.css&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;]&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Process the files, generating individual tests for each of them&lt;/span&gt;&lt;br /&gt;  (&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Dir&lt;/span&gt;::glob(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;CSS_PATH&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/**/*.css&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;) - &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;EXCEPTIONS&lt;/span&gt;).each &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;do&lt;/span&gt; |file_name|&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Create a test name by converting "/.../stylesheets/foo.css" to "foo"&lt;/span&gt;&lt;br /&gt;    test_name = file_name.scan(&lt;span style="background-color: rgb(255, 240, 255);"&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;^.*&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;public&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;stylesheets&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;(.*)&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\.&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;css&lt;/span&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;/span&gt;)[&lt;span style="font-weight: bold; color: rgb(0, 0, 221);"&gt;0&lt;/span&gt;][&lt;span style="font-weight: bold; color: rgb(0, 0, 221);"&gt;0&lt;/span&gt;].gsub(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;_&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Create our test case&lt;/span&gt;&lt;br /&gt;    class_eval &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;&lt;&lt;-EOS&lt;/span&gt;&lt;/span&gt;&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;&lt;br /&gt;      def test_&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;test_name&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;_valid_css&lt;br /&gt;        file_contents = File.open('&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;file_name&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;','rb').read&lt;br /&gt;&lt;br /&gt;        # Check the contents as valid css&lt;br /&gt;        assert_valid_css file_contents&lt;br /&gt;&lt;br /&gt;        # Check that the URL links are valid&lt;br /&gt;        assert_css_has_valid_urls file_contents, '&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;file_name&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;'&lt;br /&gt;      end&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;&lt;br /&gt;    EOS&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Check the CSS contents for valid links&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;def&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 102, 187);"&gt;assert_css_has_valid_urls&lt;/span&gt;(css, source)&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Find the contents of all the "url(...)" strings and check them&lt;/span&gt;&lt;br /&gt;    urls = css.scan(&lt;span style="background-color: rgb(255, 240, 255);"&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;url&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\(&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;(.*)&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\)&lt;/span&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;/span&gt;).flatten.each &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;do&lt;/span&gt; |url|&lt;br /&gt;      assert_url_valid url, source&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;Note that there is a set of exceptions listed (files we don't want to check).  I moved all my invalid CSS into the file invalid.css and mark this file as one  not to be validated. The CSS in there is all related to setting opacity filters  for dialog boxes, which is apparently supported, but not valid. If you have a  set of CSS files from another source and you can't really change them, the  exception list might be the right place to put them if they are generating  errors.&lt;/p&gt; &lt;p&gt;There is also a call to assert_url_valid in there, which I'll cover  separately under link checking.&lt;/p&gt; &lt;h2&gt;HTML&lt;/h2&gt; &lt;p&gt;I wanted to be somewhat aggressive -- validating every single response from  my code during testing. There is a great slideshow and a small body of test  helper code located here -- &lt;a href="http://blog.spotstory.com/category/plugin/"&gt;http://blog.spotstory.com/category/plugin/&lt;/a&gt; -- The  test helpers expect that you will have the assert_valid_markup plugin installed,  but it turns out to work just fine with the assert_valid_asset plugin as well.  What this code does is override the normal "get" and "post" methods used during  testing, and performs validation on the results of those calls, checking HTML  and RSS responses. It uses the assert_valid_markup method defined in the  assert_valid_asset plugin to validate the HTML.&lt;/p&gt; &lt;p&gt;Unfortunately the code in assert_valid_asset package to check HTML sends it off  to the w3c, just like it does for CSS, But for some reason I'm less comfortable  about sending my HTML off to the w3c. What I wanted instead was to be able to  validate my HTML locally, and I wanted to be able to use this tool on every  response during testing. To do this I hunted down rails-tidy (a plugin),  ruby-tidy (a gem), and the tidy library (a native library).&lt;/p&gt;&lt;pre&gt;&lt;code&gt;rails-tidy: &lt;a href="http://blog.cosinux.org/pages/rails-tidy"&gt;http://blog.cosinux.org/pages/rails-tidy&lt;/a&gt;&lt;br /&gt;ruby-tidy: &lt;a href="http://blog.cosinux.org/pages/rails-tidy"&gt;http://rubyforge.org/projects/tidy&lt;/a&gt;&lt;br /&gt;tidy library: &lt;a href="http://tidy.sourceforge.net/"&gt;http://tidy.sourceforge.net/&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;I'm running on Windows, so I downloaded the tidy DLL and put it somewhere  that I could find it, and then configured rails-tidy with the path to the tidy  library (see the readme file). With these in place, you can run tidy on HTML  files if you wish (from the command line). You can also run a rake command to  validate each of your views. In addition you get the nice assert_tidy call which you can use to validate your html in your tests. I used that assert_tidy call to override the behavior that was sending my HTML off to the w3c by redefining the assert_html_valid method that was being used by the test helper code:&lt;/p&gt; &lt;table class="CodeRay"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt; &lt;td class="code"&gt;&lt;pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"&gt;&lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;class&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(187, 0, 102);"&gt;Test::Unit::TestCase&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# &lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Override the test_validation_helper code so it uses tidy instead of a web service &lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# &lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;def&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 102, 187);"&gt;assert_html_valid&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Check for valid XHTML &lt;/span&gt;&lt;br /&gt;    assert_tidy&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;p&gt;At this point, I was feeling pretty great. I actually found a bunch of errors  (including a few in my html-generating helpers) which reminded me that there was  actually some value in this. When I was done, all the pages served as part of my  functional tests were valid, my CSS was validating correctly, and the javascript  was lint free. There was only one more thing I wanted to do.&lt;/p&gt; &lt;h2&gt;Links and Images&lt;/h2&gt; &lt;p&gt;I wanted to make sure that my links were valid, and that my images were  valid. Normally these are a pain to check, since they require checking by hand,  or at least loading the page in the browser and looking for errors. On a content  rich site, this might be troublesome.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;There was one thing working in my favor  for links -- I won't have images linking to external sites (so no fully  qualified URLs for images, ever). This means that for images, an external link  is an error. This accomplishes two things: (1) it simplifies my checking and (2)  I don't have to worry about fully qualified URLs producing a "mixed content"  warning in IE when the image URL includes "http" but the page is being accessed  via https.&lt;/p&gt; &lt;p&gt;There are existing (built-in) rails extensions to Test::Unit::TestCase for  finding specific elements in an HTML tree. My code to check links was based on  these, and was all placed into a test_asset_validation_helper.rb file. Note that I  moved assert_html_valid from test_helper.rb and extended it to provide link  checking. The assert_url_valid method is the same one that is called by the CSS  checking code.&lt;/p&gt; &lt;p&gt;Summarizing the tests for valid links. A link is valid if any one of these is  true:&lt;/p&gt; &lt;ul&gt;&lt;li&gt;There is a file in public which matches the link&lt;/li&gt;&lt;li&gt;It is recognized by the  routing engine, and the route refers to a view&lt;/li&gt;&lt;li&gt;It is recognized by the routing  engine, and the action is a method that the controller responds to&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;From the  file test_asset_validation_helper.rb:&lt;/p&gt; &lt;table class="CodeRay"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;br /&gt;26&lt;br /&gt;27&lt;br /&gt;28&lt;br /&gt;29&lt;br /&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;br /&gt;31&lt;br /&gt;32&lt;br /&gt;33&lt;br /&gt;34&lt;br /&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;br /&gt;36&lt;br /&gt;37&lt;br /&gt;38&lt;br /&gt;39&lt;br /&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;br /&gt;41&lt;br /&gt;42&lt;br /&gt;43&lt;br /&gt;44&lt;br /&gt;&lt;strong&gt;45&lt;/strong&gt;&lt;br /&gt;46&lt;br /&gt;47&lt;br /&gt;48&lt;br /&gt;49&lt;br /&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;br /&gt;51&lt;br /&gt;52&lt;br /&gt;53&lt;br /&gt;54&lt;br /&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;br /&gt;56&lt;br /&gt;57&lt;br /&gt;58&lt;br /&gt;59&lt;br /&gt;&lt;strong&gt;60&lt;/strong&gt;&lt;br /&gt;61&lt;br /&gt;62&lt;br /&gt;63&lt;br /&gt;64&lt;br /&gt;&lt;strong&gt;65&lt;/strong&gt;&lt;br /&gt;66&lt;br /&gt;67&lt;br /&gt;68&lt;br /&gt;69&lt;br /&gt;&lt;strong&gt;70&lt;/strong&gt;&lt;br /&gt;71&lt;br /&gt;72&lt;br /&gt;73&lt;br /&gt;74&lt;br /&gt;&lt;strong&gt;75&lt;/strong&gt;&lt;br /&gt;76&lt;br /&gt;77&lt;br /&gt;78&lt;br /&gt;79&lt;br /&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;br /&gt;81&lt;br /&gt;82&lt;br /&gt;83&lt;br /&gt;84&lt;br /&gt;&lt;strong&gt;85&lt;/strong&gt;&lt;br /&gt;86&lt;br /&gt;87&lt;br /&gt;88&lt;br /&gt;89&lt;br /&gt;&lt;strong&gt;90&lt;/strong&gt;&lt;br /&gt;91&lt;br /&gt;92&lt;br /&gt;93&lt;br /&gt;94&lt;br /&gt;&lt;strong&gt;95&lt;/strong&gt;&lt;br /&gt;96&lt;br /&gt;97&lt;br /&gt;98&lt;br /&gt;99&lt;br /&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;br /&gt;101&lt;br /&gt;102&lt;br /&gt;103&lt;br /&gt;104&lt;br /&gt;&lt;strong&gt;105&lt;/strong&gt;&lt;br /&gt;106&lt;br /&gt;107&lt;br /&gt;108&lt;br /&gt;109&lt;br /&gt;&lt;strong&gt;110&lt;/strong&gt;&lt;br /&gt;111&lt;br /&gt;112&lt;br /&gt;113&lt;br /&gt;114&lt;br /&gt;&lt;strong&gt;115&lt;/strong&gt;&lt;br /&gt;116&lt;br /&gt;117&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt; &lt;td class="code"&gt;&lt;pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"&gt;&lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;class&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(187, 0, 102);"&gt;Test::Unit::TestCase&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# &lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Override the test_validation_helper code so it uses tidy instead of a web service &lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# &lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;def&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 102, 187);"&gt;assert_html_valid&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Check for valid XHTML &lt;/span&gt;&lt;br /&gt;    assert_tidy&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Check for valid a and img tags&lt;/span&gt;&lt;br /&gt;    assert_a_tags_valid&lt;br /&gt;    assert_img_tags_valid&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Check that the a tags in the response are valid links&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;def&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 102, 187);"&gt;assert_a_tags_valid&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Validate a links&lt;/span&gt;&lt;br /&gt;    find_all_tag( &lt;span style="color: rgb(170, 102, 0);"&gt;:tag&lt;/span&gt; =&gt; &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;a&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt; ).each &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;do&lt;/span&gt; |a_tag|&lt;br /&gt;      href = a_tag.attributes[&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;href&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;]&lt;br /&gt;      name = a_tag.attributes[&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;name&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;]&lt;br /&gt;      line = a_tag.line&lt;br /&gt;&lt;br /&gt;      &lt;span style="color: rgb(136, 136, 136);"&gt;# One of these must exist&lt;/span&gt;&lt;br /&gt;      flunk(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;a tag on line &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;line&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; of response must have href or name attribute&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;) &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;unless&lt;/span&gt; (href || name)&lt;br /&gt;&lt;br /&gt;      &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;if&lt;/span&gt; href == &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;#&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;then&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(136, 136, 136);"&gt;# This is a placeholder -- we allow these&lt;/span&gt;&lt;br /&gt;      &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;elsif&lt;/span&gt; (href =~ &lt;span style="background-color: rgb(255, 240, 255);"&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;^#.+$&lt;/span&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;        &lt;span style="color: rgb(136, 136, 136);"&gt;# If the href is of the form "#name" then we check that the name exists&lt;/span&gt;&lt;br /&gt;        assert_select &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;a[name=&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;href[&lt;span style="font-weight: bold; color: rgb(0, 0, 221);"&gt;1&lt;/span&gt;..&lt;span style="font-weight: bold; color: rgb(0, 0, 221);"&gt;-1&lt;/span&gt;]&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;]&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;, {&lt;span style="color: rgb(170, 102, 0);"&gt;:count&lt;/span&gt; =&gt; &lt;span style="font-weight: bold; color: rgb(0, 0, 221);"&gt;1&lt;/span&gt;}, &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;Hash link '&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;href&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;' has no anchor on this page&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;else&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(136, 136, 136);"&gt;# Assume it's a real link&lt;/span&gt;&lt;br /&gt;        assert_url_valid(href, &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;Line &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;line&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; of response&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;      &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Check that the img tags in the response are valid&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;def&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 102, 187);"&gt;assert_img_tags_valid&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Validate img links&lt;/span&gt;&lt;br /&gt;    find_all_tag( &lt;span style="color: rgb(170, 102, 0);"&gt;:tag&lt;/span&gt; =&gt; &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;img&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt; ).each &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;do&lt;/span&gt; |img_tag|&lt;br /&gt;      src  = img_tag.attributes[&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;src&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;]&lt;br /&gt;      line = img_tag.line&lt;br /&gt;&lt;br /&gt;      &lt;span style="color: rgb(136, 136, 136);"&gt;# There must be a src&lt;/span&gt;&lt;br /&gt;      flunk(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;img tag on line &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;line&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; of response must have src attribute&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;) &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;unless&lt;/span&gt; src&lt;br /&gt;&lt;br /&gt;      &lt;span style="color: rgb(136, 136, 136);"&gt;# We do not allow img tags to point to things off-site&lt;/span&gt;&lt;br /&gt;      &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;if&lt;/span&gt; is_fully_qualified_url? src &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;then&lt;/span&gt;&lt;br /&gt;        flunk(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;img tag on line &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;line&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; of response has a fully qualified url&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;      &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span style="color: rgb(136, 136, 136);"&gt;# Assume it's a real link&lt;/span&gt;&lt;br /&gt;      assert_url_valid(src, &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;Line &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;img_tag.line&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; of response&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Check that a URL is valid by seeing if it is a public file, or if our routing&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# scheme recognizes it. If our routing scheme does recognize it. If the routing&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# scheme does recognize it, then we make sure that the action is handled either&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# by a view, or by a method in the controller class.&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;def&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 102, 187);"&gt;assert_url_valid&lt;/span&gt;(url, source)&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Ignore anything that looks like a fully qualified URL&lt;/span&gt;&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;return&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;if&lt;/span&gt; is_fully_qualified_url? url&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Check for empty url&lt;/span&gt;&lt;br /&gt;    flunk(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;Empty url in &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;source&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;) &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;if&lt;/span&gt; url.empty?&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Strip query parameters&lt;/span&gt;&lt;br /&gt;    possible_file_name = url.gsub(&lt;span style="background-color: rgb(255, 240, 255);"&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\?&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;.*$&lt;/span&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;/span&gt;, &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Check for things in the public folder&lt;/span&gt;&lt;br /&gt;    file_path = &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;File&lt;/span&gt;.expand_path(&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;File&lt;/span&gt;.dirname(&lt;span style="font-weight: bold; color: rgb(0, 51, 136);"&gt;__FILE__&lt;/span&gt;) + &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/../public/&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt; + possible_file_name)&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;return&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;if&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;FileTest&lt;/span&gt;::exist? file_path&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Check for a valid route&lt;/span&gt;&lt;br /&gt;    route_info = &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;ActionController&lt;/span&gt;::&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Routing&lt;/span&gt;::&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Routes&lt;/span&gt;.recognize_path url&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# If the route parses, then check to make sure that a view exists&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# for that controller + action. Assume that any suffix is good enough.&lt;/span&gt;&lt;br /&gt;    controller_name  = route_info[&lt;span style="color: rgb(170, 102, 0);"&gt;:controller&lt;/span&gt;]&lt;br /&gt;    action_name      = route_info[&lt;span style="color: rgb(170, 102, 0);"&gt;:action&lt;/span&gt;]&lt;br /&gt;    controller_class = &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;controller_name.camelize&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;Controller&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;.constantize&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Construct the path to the view&lt;/span&gt;&lt;br /&gt;    view_path = &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;app/views/&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;controller_name&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;action_name&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Get the set of possible views&lt;/span&gt;&lt;br /&gt;    possible_views = &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Dir&lt;/span&gt;::glob(&lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;File&lt;/span&gt;.dirname(&lt;span style="font-weight: bold; color: rgb(0, 51, 136);"&gt;__FILE__&lt;/span&gt;) + &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/../&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;view_path&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;.*&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# If any view exists, that's good enough&lt;/span&gt;&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;return&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;if&lt;/span&gt; possible_views.length &gt; &lt;span style="font-weight: bold; color: rgb(0, 0, 221);"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# There are no views ... that means that the controller must respond to the action directly&lt;/span&gt;&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;unless&lt;/span&gt; (controller_class.method_defined? action_name)&lt;br /&gt;      flunk(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;Controller &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;controller_name&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; does not respond to &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;action_name&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;, and there is no view either&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;rescue&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 51, 102);"&gt;Exception&lt;/span&gt; =&gt; ex&lt;br /&gt;    &lt;span style="color: rgb(136, 136, 136);"&gt;# Other failures, probably in mapping the route&lt;/span&gt;&lt;br /&gt;    flunk(&lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;source&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; - Path not recognized: &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;url&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; -- &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;ex.message&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;# Returns true if the passed string represents a fully qualified URL&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;def&lt;/span&gt; &lt;span style="font-weight: bold; color: rgb(0, 102, 187);"&gt;is_fully_qualified_url?&lt;/span&gt;(url)&lt;br /&gt;    url =~ &lt;span style="background-color: rgb(255, 240, 255);"&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;^(http|https|ftp|mailto|callto).*$&lt;/span&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 136, 0);"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6294118673654365284-6351353627449662020?l=johnwedgwood.blogspot.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/johnwedgwood/~4/vTe8Wnln1a8" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/blogspot/johnwedgwood/~3/vTe8Wnln1a8/validation-nirvana.html</link><author>noreply@blogger.com (John Wedgwood)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://johnwedgwood.blogspot.com/2007/08/validation-nirvana.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-6294118673654365284.post-5665323925252356438</guid><pubDate>Fri, 03 Aug 2007 02:36:00 +0000</pubDate><atom:updated>2007-07-31T20:13:03.340-07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">tools</category><category domain="http://www.blogger.com/atom/ns#">testing</category><category domain="http://www.blogger.com/atom/ns#">lint</category><category domain="http://www.blogger.com/atom/ns#">assetpackager</category><title>Javascript Lint and AssetPackager</title><description>The AssetPackager plugin is a configurable tool which allows you to package multiple javascript (or css) files, in whatever order you want, into a single file. This speeds up your page loads by reducing the number of times the users browser needs to fetch files from your server. So instead of a dozen separate javascript and css fetches, you can get down to two if you want. In addition, it includes tools to compact the javascript and css, making the files even smaller.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://synthesis.sbecker.net/pages/asset_packager"&gt;http://synthesis.sbecker.net/pages/asset_packager&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You install it as a plugin, add a capistrano deployment task, and really you're good to go. For my needs, it's perfect.&lt;br /&gt;&lt;br /&gt;The only problem that I ran into was something I've seen in other projects that used javascript compactors. If you have things like a missing semi-colon, the compacted code ends up producing errors in the browser, where the uncompacted code does not. This can be a real headache because typically you never actually run the compacted code locally, only when it is finally deployed onto your server.&lt;br /&gt;&lt;br /&gt;What I wanted was a way to check for this problem, and ideally check for it as part of my normal deployment process. Since I run all my tests before installing, it seemed natural to include a check of the javascript syntax as part of my tests, so that's what I did.&lt;br /&gt;&lt;br /&gt;The first part of this is finding a javascript lint program. I found the "JavaScript Lint" tool:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.javascriptlint.com/"&gt;http://www.javascriptlint.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This lint tool was created by Matthias Miller. It's great, and very configurable. It also is available as an executable, which was convenient for automation.&lt;br /&gt;&lt;br /&gt;Having downloaded and installed it, I ran into another roadblock. The prototype, scriptaculous and behavior libraries that I was using did not pass lint. Actually, some of my own code didn't pass lint either, but at least there wasn't much of it, so it was easy to fix.&lt;br /&gt;&lt;br /&gt;When it comes to lint, you really want to start early, and keep your code clean. If you have a ton of code that is dirty, it tends to just linger because most people don't want to go make 200 changes (even if they are as simple as adding a semicolon or braces), and since hunting for new issues in a sea of old lint errors isn't any fun, the end result is that you just stop using lint entirely. I am lucky enough to be at the start of a project, so fixing the dozen warnings took minutes, and it means that any new warnings are indicators of something that I can (and should) really pay attention to.&lt;br /&gt;&lt;br /&gt;With my own code clean, what I wanted was for my tests to run lint on any javascript files which I hadn't specifically exempted, so that new files would be caught in the lint trap, rather than being omitted until I remembered to add them.&lt;br /&gt;&lt;br /&gt;That led to the test you see below, which was created in test/unit/javascript_validation_test.rb in my rails application. It runs through every file in the public/javascript folder, removes the ones that I've specifically exempted, removes anything matching a pattern (which was useful for removing files generated by the AssetPackager) and then runs lint on the remaining files, creating output only if the test fails.&lt;br /&gt;&lt;br /&gt;The end result is automated javascript lint checking, which runs as part of my standard install process. I'm sure that there will be cases that won't get caught, but at least I feel pretty comfortable that issues like missing semicolons and extra commas in array definitions will be caught and fixed before any code gets installed.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;table class="CodeRay"&gt;&lt;tbody&gt;&lt;tr&gt;   &lt;td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"&gt;&lt;pre&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;br /&gt;11&lt;br /&gt;12&lt;br /&gt;13&lt;br /&gt;14&lt;br /&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;br /&gt;16&lt;br /&gt;17&lt;br /&gt;18&lt;br /&gt;19&lt;br /&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;br /&gt;21&lt;br /&gt;22&lt;br /&gt;23&lt;br /&gt;24&lt;br /&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;br /&gt;26&lt;br /&gt;27&lt;br /&gt;28&lt;br /&gt;29&lt;br /&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;br /&gt;31&lt;br /&gt;32&lt;br /&gt;33&lt;br /&gt;34&lt;br /&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;br /&gt;36&lt;br /&gt;37&lt;br /&gt;38&lt;br /&gt;39&lt;br /&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;br /&gt;41&lt;br /&gt;42&lt;br /&gt;43&lt;br /&gt;44&lt;br /&gt;&lt;strong&gt;45&lt;/strong&gt;&lt;br /&gt;46&lt;br /&gt;47&lt;br /&gt;48&lt;br /&gt;49&lt;br /&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;br /&gt;51&lt;br /&gt;52&lt;br /&gt;53&lt;br /&gt;54&lt;br /&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;br /&gt;56&lt;br /&gt;57&lt;br /&gt;58&lt;br /&gt;59&lt;br /&gt;&lt;strong&gt;60&lt;/strong&gt;&lt;br /&gt;61&lt;br /&gt;62&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td class="code"&gt;&lt;pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# A test for valid javascript. Every javascript file is passed through&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# jsl, except for the ones which we know will fail, but we feel good about&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# because they come from standard libraries.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;#&lt;/span&gt;&lt;br /&gt;require &lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;File&lt;/span&gt;.dirname(&lt;span style="color: rgb(0, 51, 136); font-weight: bold;"&gt;__FILE__&lt;/span&gt;) + &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/../test_helper&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 136, 0); font-weight: bold;"&gt;class&lt;/span&gt; &lt;span style="color: rgb(187, 0, 102); font-weight: bold;"&gt;JavascriptValidationTest&lt;/span&gt; &amp;lt; &lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;Test&lt;/span&gt;::&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;Unit&lt;/span&gt;::&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;TestCase&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Where to find the JSL program&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;JSL_EXECUTABLE&lt;/span&gt; = &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;c:/jsl-0.3.0/jsl&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Where to find the config file with all our lint settings&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;JSL_CONFIG_FILE&lt;/span&gt; = &lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;File&lt;/span&gt;.dirname(&lt;span style="color: rgb(0, 51, 136); font-weight: bold;"&gt;__FILE__&lt;/span&gt;) + &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/../jsl.conf&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Where our javascript is located&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;JAVASCRIPT_PATH&lt;/span&gt; = &lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;File&lt;/span&gt;.dirname(&lt;span style="color: rgb(0, 51, 136); font-weight: bold;"&gt;__FILE__&lt;/span&gt;) + &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;/../../public/javascripts&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Files we don't want to process because they are either not actually javascript&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# files, or because we tested them with the asset packer once, they worked fine,&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# and we don't expect them to change.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;EXCEPTION_LIST&lt;/span&gt; = &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;%w{&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt;&lt;br /&gt;    .&lt;br /&gt;    ..&lt;br /&gt;    .svn&lt;br /&gt;    behaviour.js&lt;br /&gt;    builder.js&lt;br /&gt;    controls.js&lt;br /&gt;    dragdrop.js&lt;br /&gt;    effects.js&lt;br /&gt;    prototype.js&lt;br /&gt;    redbox.js&lt;br /&gt;    scriptaculous.js&lt;br /&gt;    slider.js&lt;br /&gt;    tiny_mce&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Pattern for stuff we don't want to process&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;EXCEPTION_PATTERN&lt;/span&gt; = &lt;span style="background-color: rgb(255, 240, 255);"&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;(^base_&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\d&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;+&lt;/span&gt;&lt;span style="color: rgb(0, 68, 221);"&gt;\.&lt;/span&gt;&lt;span style="color: rgb(136, 0, 136);"&gt;js$)&lt;/span&gt;&lt;span style="color: rgb(68, 0, 68);"&gt;/&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 136, 0); font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: rgb(0, 102, 187); font-weight: bold;"&gt;test_for_valid_javascript&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# For each javascript file in the folder&lt;/span&gt;&lt;br /&gt;all_files_in_path = &lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;Dir&lt;/span&gt;::entries(&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;JAVASCRIPT_PATH&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Figure out which files match our pattern of things not to process&lt;/span&gt;&lt;br /&gt;files_matching_exception_pattern = all_files_in_path.grep(&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;EXCEPTION_PATTERN&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# The set of files to process does not include our exceptions&lt;/span&gt;&lt;br /&gt;files_to_process = all_files_in_path - (&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;EXCEPTION_LIST&lt;/span&gt; + files_matching_exception_pattern)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Process the files -- checking for exception patterns&lt;/span&gt;&lt;br /&gt;files_to_process.each &lt;span style="color: rgb(0, 136, 0); font-weight: bold;"&gt;do&lt;/span&gt; |file|&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Capture the output&lt;/span&gt;&lt;br /&gt;jsl_output = &lt;span style="background-color: rgb(240, 255, 240);"&gt;&lt;span style="color: rgb(17, 102, 17);"&gt;`&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;JSL_EXECUTABLE&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(34, 187, 34);"&gt; -conf &lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;JSL_CONFIG_FILE&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(34, 187, 34);"&gt; -nologo -process "&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;&lt;span style="color: rgb(0, 51, 102); font-weight: bold;"&gt;JAVASCRIPT_PATH&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(34, 187, 34);"&gt;/&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;file&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(34, 187, 34);"&gt;"&lt;/span&gt;&lt;span style="color: rgb(17, 102, 17);"&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# Printing it only if something went wrong&lt;/span&gt;&lt;br /&gt;print jsl_output &lt;span style="color: rgb(0, 136, 0); font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: rgb(221, 119, 0); font-weight: bold;"&gt;$?&lt;/span&gt; != &lt;span style="color: rgb(0, 0, 221); font-weight: bold;"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(136, 136, 136);"&gt;# And the obligatory assert, so our tests can fail if need be&lt;/span&gt;&lt;br /&gt;assert_equal &lt;span style="color: rgb(221, 119, 0); font-weight: bold;"&gt;$?&lt;/span&gt;, &lt;span style="color: rgb(0, 0, 221); font-weight: bold;"&gt;0&lt;/span&gt;, &lt;span style="background-color: rgb(255, 240, 240);"&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;span style="background: rgb(238, 238, 238) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"&gt;&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;#{&lt;/span&gt;file&lt;span style="font-weight: bold; color: rgb(136, 136, 136);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(221, 34, 0);"&gt; fails to pass lint&lt;/span&gt;&lt;span style="color: rgb(119, 17, 0);"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 136, 0); font-weight: bold;"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 136, 0); font-weight: bold;"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 136, 0); font-weight: bold;"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/td&gt; &lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6294118673654365284-5665323925252356438?l=johnwedgwood.blogspot.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/johnwedgwood/~4/38HL71jZ66M" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/blogspot/johnwedgwood/~3/38HL71jZ66M/javascript-lint-and-assetpackager.html</link><author>noreply@blogger.com (John Wedgwood)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://johnwedgwood.blogspot.com/2007/08/javascript-lint-and-assetpackager.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-6294118673654365284.post-2465816024002754580</guid><pubDate>Thu, 02 Aug 2007 04:00:00 +0000</pubDate><atom:updated>2007-07-31T14:17:40.768-07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">tools</category><title>My Toolkit</title><description>This reflects my current toolkit as of August 1st, 2007.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Ruby and Ruby on Rails&lt;/h2&gt; &lt;p&gt;There's enough on the web about these. I'm almost embarrassed by how much I  love them.&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;code&gt;&lt;a href="http://www.ruby-lang.org/en/downloads/"&gt;http://www.ruby-lang.org/en/downloads/&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.rubyonrails.org/down"&gt;http://www.rubyonrails.org/down&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have ruby installed into C:\ruby, and rails installed as a gem there.&lt;/p&gt; &lt;h2&gt;Cygwin&lt;/h2&gt; &lt;p&gt;Using cygwin is a blessing and a curse. I love the unix command line tools,  but the way that cygwin munges paths makes it sometimes painful to use tools  which are expecting DOS paths. Pretty much everything installed in cygwin into  /usr/bin works great, but as soon as you attempt to use something outside of the  cygwin bubble, things start to break down a bit. For me this has mostly affected  ruby-specific tools (e.g. autotest, capistrano, etc) which are installed as gems  in the non-cygwin ruby, and fail to run when executed directly from cygwin  because the ruby interpreter receives the cygwin path to the script when the  script is run from the cygwin command line.&lt;/p&gt; &lt;p&gt;I suppose that I could have made my life a bit easier and done all my work  using the cygwin installation of ruby, but I had ruby installed before I decided  to do more work inside of cygwin, so as a result when I'm working in cygwin I've  had to specifically defeat cygwin's attempts to use its own version of ruby. As  a result my .bashrc contains a set of aliases which feel like hacks, but have  worked just fine.&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# Force the c:/ruby version to be found first&lt;br /&gt;PATH=/cygdrive/c/ruby/bin:$PATH&lt;br /&gt;&lt;br /&gt;alias autotest='ruby /ruby/bin/autotest'&lt;br /&gt;alias cap='ruby /ruby/bin/cap'&lt;br /&gt;alias gem='ruby /ruby/bin/gem'&lt;br /&gt;alias rake='ruby /ruby/bin/rake'&lt;br /&gt;alias rcov='ruby /ruby/bin/rcov'&lt;br /&gt;alias ruby='/cygdrive/c/ruby/bin/ruby'&lt;br /&gt;alias specrb='ruby /ruby/bin/specrb'&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;In the interests of cleanliness I'm sure that I will end up doing something  about these, but having set them up, I haven't had to revisit them in a long  time. &lt;h2&gt;Gems&lt;/h2&gt; &lt;p&gt;Other than the site specific gems, there are a few gems that I have installed  which I think are terrific. They are:&lt;/p&gt; &lt;ul&gt;&lt;li&gt; &lt;p&gt;Capistrano (site deployment) &lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;a href="http://wiki.rubyonrails.org/rails/pages/Capistrano"&gt;http://wiki.rubyonrails.org/rails/pages/Capistrano&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Capistrano rocks. Deploying a new build is almost too easy and seems to take  about 30 seconds. It really has completely removed all the misery and guesswork  from deploying an update to the site.&lt;/p&gt; &lt;ul&gt;&lt;li&gt; &lt;p&gt;TestSpec (readable tests, integrated well with the existing rails testing  structure) &lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;a href="http://chneukirchen.org/blog/category/ruby.html#x-20070124-121211"&gt;http://chneukirchen.org/blog/category/ruby.html#x-20070124-121211&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;This fits nicely with the auto-generated rails tests, and allows for some  really readable test code:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;code&gt;context "A stream owner" do&lt;br /&gt;...&lt;br /&gt;specify "should be able to create a picture stream" do&lt;br /&gt;...&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;It's been great. I initially valued the fact that I could add test-spec tests  to existing tests and have it all sort of work together, but after a little bit  I just went in and replaced all of my old tests with these because I liked the  format.&lt;/p&gt; &lt;ul&gt;&lt;li&gt; &lt;p&gt;Autotest + Snarl + RedGreen (continuously running tests + notification of  problems)&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;a href="http://nubyonrails.com/articles/2006/04/19/autotest-rails"&gt;http://nubyonrails.com/articles/2006/04/19/autotest-rails&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.fullphat.net/"&gt;http://www.fullphat.net/&lt;/a&gt;&lt;br /&gt;&lt;a href="http://on-ruby.blogspot.com/2006/05/red-and-green-for-ruby.html"&gt;http://on-ruby.blogspot.com/2006/05/red-and-green-for-ruby.html&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Autotest provides continuous testing of your code. It notices which files  have changed, and runs the appropriate tests. If you have snarl configured, when  a problem is encountered you get a brief and unobtrusive notification dialog  that tells you that some tests have failed, which is a nice clue that you need  to go fix something.&lt;/p&gt; &lt;ul&gt;&lt;li&gt; &lt;p&gt;RCov&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;a href="http://eigenclass.org/hiki.rb?rcov"&gt;http://eigenclass.org/hiki.rb?rcov&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Another fantastic tool. I run my tests using rcov, and it tells me how well  the tests cover the code. It also produces some phenomenally cool looking output  (viewable in your browser) that tells you specifically which lines were covered  and weren't, allowing you to browse the code in your browser, spot the areas  that were not tested, and write tests for those areas. You really have to see it  to believe it. Check the screenshots in the summary section on the  eigenclass.org link.&lt;/p&gt; &lt;h2&gt;Editors, Source Control, etc...&lt;/h2&gt; &lt;ul&gt;&lt;li&gt; &lt;p&gt;Komodo Editor&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;a href="http://www.activestate.com/Products/komodo_edit/"&gt;http://www.activestate.com/Products/komodo_edit/&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;It's free, and it is really good. There are the cool things I had expected (syntax highlighting, auto completion, etc) and then the occasional nice touch I had not expected, like typing Dir::entries and getting tooltip help for the method.&lt;br /&gt;&lt;/p&gt; &lt;ul&gt;&lt;li&gt; &lt;p&gt;SVN and TortoiseSVN&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;a href="http://tortoisesvn.tigris.org/"&gt;http://tortoisesvn.tigris.org/&lt;/a&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;I am ambivalent about Tortoise. I spent about 2 hours getting it to work  nicely with ssh-agent so that I didn't have to type my password over and over  again when accessing an svn depot via svn+ssh (that deserves a separate writeup)  but other than that little bit of misery, it's been working nicely. I like the  fact that it integrates itself as a Windows Explorer extension, allowing me to  use the right-click menu in the explorer to do SVN operations. It makes SVN feel well  integrated, which is a nice touch.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6294118673654365284-2465816024002754580?l=johnwedgwood.blogspot.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/johnwedgwood/~4/sRZyH5iavaE" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/blogspot/johnwedgwood/~3/sRZyH5iavaE/my-toolkit.html</link><author>noreply@blogger.com (John Wedgwood)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://johnwedgwood.blogspot.com/2007/07/my-toolkit.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-6294118673654365284.post-5737129420290576647</guid><pubDate>Thu, 02 Aug 2007 03:30:00 +0000</pubDate><atom:updated>2007-07-21T10:48:48.640-07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">blogger</category><category domain="http://www.blogger.com/atom/ns#">coderay</category><category domain="http://www.blogger.com/atom/ns#">windows clipboard</category><title>Getting CodeRay formatting into Blogger</title><description>This is the approach I settled on for publishing code snippets in Blogger. If there is a better way, I'd love to hear about it.&lt;br /&gt;&lt;br /&gt;First, about CodeRay (&lt;a href="http://coderay.rubychan.de/"&gt;http://coderay.rubychan.de/&lt;/a&gt;) -- CodeRay is a tool that takes snippets of code, and formats the result as HTML, using CSS styles to create an attractive result. Here's an example.&lt;br /&gt;&lt;br /&gt;&lt;table class="CodeRay"&gt;&lt;tr&gt;   &lt;td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"&gt;&lt;pre&gt;1&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;2&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;3&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;4&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;6&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;7&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;8&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;9&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td class="code"&gt;&lt;pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"&gt;  &lt;span style="color:#888"&gt;#&lt;/span&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;  &lt;span style="color:#888"&gt;# Handle login requests&lt;/span&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;  &lt;span style="color:#888"&gt;#&lt;/span&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;  &lt;span style="color:#080; font-weight:bold"&gt;def&lt;/span&gt; &lt;span style="color:#06B; font-weight:bold"&gt;login&lt;/span&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;    &lt;span style="color:#888"&gt;# Attempt to do the login&lt;/span&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;    &lt;span style="color:#038; font-weight:bold"&gt;self&lt;/span&gt;.current_user = &lt;span style="color:#036; font-weight:bold"&gt;User&lt;/span&gt;.authenticate(params[&lt;span style="color:#A60"&gt;:login&lt;/span&gt;], params[&lt;span style="color:#A60"&gt;:password&lt;/span&gt;])&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;    &lt;span style="color:#080; font-weight:bold"&gt;if&lt;/span&gt; logged_in?&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;      &lt;span style="color:#080; font-weight:bold"&gt;if&lt;/span&gt; params[&lt;span style="color:#A60"&gt;:remember_me&lt;/span&gt;] == &lt;span style="background-color:#fff0f0"&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#D20"&gt;1&lt;/span&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt;        &lt;span style="color:#038; font-weight:bold"&gt;self&lt;/span&gt;.current_user.remember_me&lt;tt&gt;&lt;br /&gt;&lt;/tt&gt; &lt;/pre&gt;&lt;/td&gt; &lt;/tr&gt;&lt;/table&gt; &lt;br /&gt;Some blogging engines have plugins that make it easy to generate results like this, but I haven't found one for Blogger. The approach I chose has two elements:&lt;br /&gt;&lt;br /&gt;1) The CSS styles.&lt;br /&gt;&lt;br /&gt;There are a lot of CodeRay stylesheets out there. I suggest you find one that you like and start from there. You need to paste the CSS into your template, and from there you can see how it looks on your blog, and then tweak it until you get the result you like.&lt;br /&gt;&lt;br /&gt;You will want to save the CSS somewhere (outside of Blogger). I've noticed that when you change templates, you lose the custom CSS that you stuck in the template, so any carefully tweaked values will be lost.&lt;br /&gt;&lt;br /&gt;2) Actually generating the formatted code.&lt;br /&gt;&lt;br /&gt;This second part turns out to not be terribly difficult, but I wanted a simple tool that would allow me to copy code from my editor, and then produce a formatted result that I can paste into the "Edit Html" tab in Blogger. Here is the script:&lt;br /&gt;&lt;br /&gt;&lt;table class="CodeRay"&gt;&lt;tr&gt;   &lt;td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"&gt;&lt;pre&gt;1&lt;br /&gt; 2&lt;br /&gt; 3&lt;br /&gt; 4&lt;br /&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;br /&gt; 6&lt;br /&gt; 7&lt;br /&gt; 8&lt;br /&gt; 9&lt;br /&gt; &lt;strong&gt;10&lt;/strong&gt;&lt;br /&gt; 11&lt;br /&gt; 12&lt;br /&gt; 13&lt;br /&gt; 14&lt;br /&gt; &lt;strong&gt;15&lt;/strong&gt;&lt;br /&gt; 16&lt;br /&gt; 17&lt;br /&gt; 18&lt;br /&gt; 19&lt;br /&gt; &lt;strong&gt;20&lt;/strong&gt;&lt;br /&gt; 21&lt;br /&gt; 22&lt;br /&gt; 23&lt;br /&gt; 24&lt;br /&gt; &lt;strong&gt;25&lt;/strong&gt;&lt;br /&gt; 26&lt;br /&gt; 27&lt;br /&gt; 28&lt;br /&gt; 29&lt;br /&gt; &lt;strong&gt;30&lt;/strong&gt;&lt;br /&gt; 31&lt;br /&gt; 32&lt;br /&gt; 33&lt;br /&gt; 34&lt;br /&gt; &lt;strong&gt;35&lt;/strong&gt;&lt;br /&gt; 36&lt;br /&gt; 37&lt;br /&gt; 38&lt;br /&gt; 39&lt;br /&gt; &lt;/pre&gt;&lt;/td&gt;   &lt;td class="code"&gt;&lt;pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"&gt;&lt;span style="color:#888"&gt;#!c:/ruby/bin/ruby.exe -rubygems&lt;/span&gt;&lt;br /&gt; require &lt;span style="background-color:#fff0f0"&gt;&lt;span style="color:#710"&gt;'&lt;/span&gt;&lt;span style="color:#D20"&gt;coderay&lt;/span&gt;&lt;span style="color:#710"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt; require &lt;span style="background-color:#fff0f0"&gt;&lt;span style="color:#710"&gt;'&lt;/span&gt;&lt;span style="color:#D20"&gt;win32/clipboard&lt;/span&gt;&lt;span style="color:#710"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt; include &lt;span style="color:#036; font-weight:bold"&gt;Win32&lt;/span&gt;&lt;br /&gt; &lt;br /&gt; &lt;span style="color:#888"&gt;# Fix the formatted code to work nicely in blogger.&lt;/span&gt;&lt;br /&gt; &lt;span style="color:#080; font-weight:bold"&gt;def&lt;/span&gt; &lt;span style="color:#06B; font-weight:bold"&gt;format_for_blogger&lt;/span&gt;(str)&lt;br /&gt;   &lt;span style="color:#888"&gt;# Blogger has this quirk where it transforms newlines to &amp;lt;br /&amp;gt;&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#888"&gt;# tags. This is great, except when you don't want it, for instance&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#888"&gt;# the HTML &amp;lt;tr&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;td&amp;gt; is illegal. This code finds all of the&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#888"&gt;# places where things like &amp;lt;tr&amp;gt; exist with a newline after them,&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#888"&gt;# and removes the newline&lt;/span&gt;&lt;br /&gt;   blogger_result = str.gsub(&lt;span style="background-color:#fff0ff"&gt;&lt;span style="color:#404"&gt;/&lt;/span&gt;&lt;span style="color:#808"&gt;(&lt;/span&gt;&lt;span style="color:#04D"&gt;\s&lt;/span&gt;&lt;span style="color:#808"&gt;*&lt;/span&gt;&lt;span style="color:#04D"&gt;\n&lt;/span&gt;&lt;span style="color:#04D"&gt;\s&lt;/span&gt;&lt;span style="color:#808"&gt;*)(&amp;lt;|&amp;lt;&lt;/span&gt;&lt;span style="color:#04D"&gt;\/&lt;/span&gt;&lt;span style="color:#808"&gt;)(table|tr|td)&lt;/span&gt;&lt;span style="color:#404"&gt;/&lt;/span&gt;&lt;span style="color:#C2C"&gt;i&lt;/span&gt;&lt;/span&gt;) { |x| x.gsub(&lt;span style="background-color:#fff0f0"&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#04D"&gt;\n&lt;/span&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,&lt;span style="background-color:#fff0f0"&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#D20"&gt; &lt;/span&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) }&lt;br /&gt; &lt;br /&gt;   &lt;span style="color:#888"&gt;# CodeRay produces &amp;lt;tt&amp;gt; elements that have nothing but a newline in them.&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#888"&gt;# Replacing these with simple \n (which blogger transforms to &amp;lt;br /&amp;gt;)&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#888"&gt;# also helps in making things line up nicely. Note the space after the&lt;/span&gt;&lt;br /&gt;   &lt;span style="color:#888"&gt;# newline. That turns out to be important to get nice results in IE.&lt;/span&gt;&lt;br /&gt;   blogger_result.gsub(&lt;span style="background-color:#fff0ff"&gt;&lt;span style="color:#404"&gt;/&lt;/span&gt;&lt;span style="color:#808"&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;&lt;span style="color:#04D"&gt;\s&lt;/span&gt;&lt;span style="color:#808"&gt;*&lt;/span&gt;&lt;span style="color:#04D"&gt;\n&lt;/span&gt;&lt;span style="color:#04D"&gt;\s&lt;/span&gt;&lt;span style="color:#808"&gt;*&amp;lt;&lt;/span&gt;&lt;span style="color:#04D"&gt;\/&lt;/span&gt;&lt;span style="color:#808"&gt;tt&amp;gt;&lt;/span&gt;&lt;span style="color:#404"&gt;/&lt;/span&gt;&lt;span style="color:#C2C"&gt;i&lt;/span&gt;&lt;/span&gt;, &lt;span style="background-color:#fff0f0"&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#04D"&gt;\n&lt;/span&gt;&lt;span style="color:#D20"&gt; &lt;/span&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt; &lt;span style="color:#080; font-weight:bold"&gt;end&lt;/span&gt;&lt;br /&gt; &lt;br /&gt; &lt;br /&gt; &lt;span style="color:#888"&gt;# Quit if there's no clipboard&lt;/span&gt;&lt;br /&gt; print &lt;span style="background-color:#fff0f0"&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#D20"&gt;The clipboard was empty&lt;/span&gt;&lt;span style="color:#710"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span style="color:#080; font-weight:bold"&gt;and&lt;/span&gt; exit &lt;span style="color:#080; font-weight:bold"&gt;unless&lt;/span&gt; &lt;span style="color:#036; font-weight:bold"&gt;Clipboard&lt;/span&gt;.data&lt;br /&gt; &lt;br /&gt; &lt;span style="color:#888"&gt;# Format it via CodeRay, and process the result specially for blogger&lt;/span&gt;&lt;br /&gt; formatted_code = &lt;span style="color:#036; font-weight:bold"&gt;CodeRay&lt;/span&gt;.scan(&lt;span style="color:#036; font-weight:bold"&gt;Clipboard&lt;/span&gt;.data, &lt;span style="color:#A60"&gt;:ruby&lt;/span&gt;).div({&lt;br /&gt;           &lt;span style="color:#A60"&gt;:line_numbers&lt;/span&gt; =&amp;gt; &lt;span style="color:#A60"&gt;:table&lt;/span&gt;,&lt;br /&gt;           &lt;span style="color:#A60"&gt;:bold_every&lt;/span&gt; =&amp;gt; &lt;span style="color:#00D; font-weight:bold"&gt;5&lt;/span&gt;,&lt;br /&gt;           &lt;span style="color:#A60"&gt;:line_number_start&lt;/span&gt; =&amp;gt; &lt;span style="color:#00D; font-weight:bold"&gt;1&lt;/span&gt;&lt;br /&gt;         }&lt;br /&gt;   )&lt;br /&gt;   &lt;br /&gt; blogger_code = format_for_blogger(formatted_code)&lt;br /&gt;   &lt;br /&gt; &lt;span style="color:#888"&gt;# Set the clipboard contents and show the user the result&lt;/span&gt;&lt;br /&gt; &lt;span style="color:#036; font-weight:bold"&gt;Clipboard&lt;/span&gt;.set_data blogger_code&lt;br /&gt; &lt;br /&gt; print blogger_code&lt;/pre&gt;&lt;/td&gt; &lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;This relies on two gems, which you can install with:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;gem install coderay&lt;br /&gt;gem install win32-clipboard&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;The script takes the contents of the clipboard, runs it through the CodeRay formatter (passing options to get a table with line numbers) and then puts the result back onto the clipboard. This makes it possible to copy code, run the script and then paste into the "Edit Html" tab of the editor in Blogger.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6294118673654365284-5737129420290576647?l=johnwedgwood.blogspot.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/johnwedgwood/~4/mKTWWsn-kRw" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/blogspot/johnwedgwood/~3/mKTWWsn-kRw/testing-coderay-formatting.html</link><author>noreply@blogger.com (John Wedgwood)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://johnwedgwood.blogspot.com/2007/07/testing-coderay-formatting.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-6294118673654365284.post-8251872332605834802</guid><pubDate>Thu, 02 Aug 2007 03:23:00 +0000</pubDate><atom:updated>2007-07-31T20:14:38.310-07:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Engineering Culture</category><title>The Manifesto</title><description>&lt;p&gt;Here I am, starting a new company. I have a great partner, but he's not writing code, which leaves me free to try to articulate some of the things I think are important in an engineering culture, and to enforce them ruthlessly ... on myself.&lt;/p&gt; &lt;p&gt;Here is the manifesto. Since we plan on building a site using Ruby on Rails, it has a bit of a focus on those technical underpinnings. I've tried to call out the key items in bullet points.&lt;/p&gt; &lt;h2&gt;The big picture&lt;/h2&gt; &lt;p&gt;Stay grounded in the core questions:&lt;/p&gt; &lt;ul&gt;&lt;li&gt;What is the service are we offering?&lt;/li&gt;&lt;li&gt;What specifically are we trying to enable?&lt;/li&gt;&lt;li&gt;Are we making it easy for the people we serve?&lt;/li&gt;&lt;li&gt;Are we making it fun for them?&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;Why have conventions?&lt;/h2&gt; &lt;p&gt;Formal conventions set the expectations that we have for ourselves, and for each other. They make explicit the things that are most important, with the hope that the details will take care of themselves. They provide tools by which we can hold each other accountable, in what is hopefully a positive way, to the shared task of creating great products built on a solid foundation of design and code.&lt;/p&gt; &lt;ul&gt;&lt;li&gt;Define conventions for things that matter&lt;/li&gt;&lt;li&gt;Keep to the conventions&lt;/li&gt;&lt;li&gt;Hold each other to high standards&lt;/li&gt;&lt;li&gt;Take pride in the work&lt;/li&gt;&lt;li&gt;Keep raising the bar&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;Design&lt;/h2&gt; &lt;p&gt;We do our initial designs on paper or using simple design tools. The goal is to turn around designs quickly, and to focus on basic usability questions before focusing on beauty, graphics or color schemes.&lt;/p&gt; &lt;p&gt;We write documentation that describes use cases, starting with the simple and moving to the complex. As much as possible, we do this before we start writing code. The goal is to evaluate the quality of the user experience by attempting to document it up front. We iterate on documentation and design until we feel that we are describing the user experience that we want to offer.&lt;/p&gt; &lt;p&gt;We write code when we think that we have a design and documentation that is mostly complete and which fulfills our goals for the user. We expect to iterate after the code is written. We try to evaluate and iterate quickly, rather than letting mediocre results become accepted fact.&lt;/p&gt; &lt;ul&gt;&lt;li&gt;Use paper or simple graphics tools&lt;/li&gt;&lt;li&gt;Focus on flow and usability&lt;/li&gt;&lt;li&gt;Keep the big picture questions in mind&lt;/li&gt;&lt;li&gt;Think about error handling or odd use cases&lt;/li&gt;&lt;li&gt;Write documentation&lt;/li&gt;&lt;li&gt;Expect to make changes after the code is written&lt;/li&gt;&lt;li&gt;Evaluate and iterate quickly&lt;/li&gt;&lt;li&gt;Don't let mediocre aspects become an accepted fact&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;Code&lt;/h2&gt; &lt;p&gt;The goal is to create a service built on a foundation of an elegant code base that is small and clean. We build tools and libraries. We avoid repetition in the code. The difference between 500 and 1000 lines of code isn't that much, but the difference between 50,000 and 100,000 lines is huge. The path to a bad outcome involves a series of cumulative decisions involving small bodies of code. Paying attention to the small pieces pays off in the aggregate.&lt;/p&gt; &lt;p&gt;We subscribe to the "broken windows" approach to maintaining a clean code base. Once a body of code starts to become "broken", it is easier to add new elements that are broken. By keeping the code clean, it becomes harder to add hacks later.&lt;/p&gt; &lt;p&gt;We write documentation including method and class headers, and we use tools to generate code documentation. We do this from the start, because while it's not very important in a small code base, it is difficult to go back and do later. In addition creating documentation requires some introspection on the part of the engineer, which hopefully leads to better code.&lt;/p&gt; &lt;p&gt;We try to keep controllers and views simple. It is easy to dump code into both places, but it produces code that is harder to understand and maintain. The effort to keep these clean results in helpers, which may not be immediately useful, but the simple act of creating libraries of code at least pushes us toward introspection and reuse.&lt;/p&gt; &lt;p&gt;We think in terms of APIs. When writing our controllers we think in terms of the API that we are providing to outside developers -- is it consistent? is it elegant? is it easy to understand? When we write library and helper methods, we look to achieve the same goals.&lt;/p&gt; &lt;ul&gt;&lt;li&gt;Write class and method headers suitable for automatically generating code  documentation&lt;/li&gt;&lt;li&gt;Keep methods short, pulling out code into libraries if needed&lt;/li&gt;&lt;li&gt;Keep Controllers and Views as simple as possible&lt;/li&gt;&lt;li&gt;Do not repeat code, create libraries or helpers&lt;/li&gt;&lt;li&gt;Strive for elegance&lt;/li&gt;&lt;li&gt;Always look for simpler ways to do things&lt;/li&gt;&lt;li&gt;When you learn something new, look back at old code and apply that knowledge&lt;/li&gt;&lt;li&gt;Think in terms of APIs, and imagine other developers needing to understand  how to use your code&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;Testing&lt;/h2&gt; &lt;p&gt;We design with testing in mind. Where possible, we design features and write code which can tested using automated testing tools.&lt;/p&gt; &lt;p&gt;We write automated tests, and we write them as part of developing new features. When a feature is installed, it is expected that there is a valid and complete set of automated tests that will test the feature. This is particularly true with unit and functional tests (for models and controllers). For integration testing, we write stories that describe user behavior, and build tests around those stories.&lt;/p&gt; &lt;p&gt;We use these tests to help ensure that new changes haven't broken existing code. Testing by hand is hugely expensive in terms of time and morale. Automated tests don't remove the requirement for testing by hand, but they can dramatically reduce the amount of time required, and dramatically increase confidence in the stability of the code base.&lt;/p&gt; &lt;p&gt;As we find bugs, we decide whether the bug is a simple coding error, or whether it is rooted in a deeper logic or design mistake. If it is not a simple error, then we write a test to detect the error before we fix it. The goal is to ensure that the bug is fixed, but also to ensure that the same sort of bug won't be reintroduced later.&lt;/p&gt; &lt;p&gt;We write test code which is readable and documented. There may eventually be as much test code written as there is code in the site itself, and we should treat the test code as if it were part of the product.&lt;/p&gt; &lt;p&gt;For tools, we use &lt;i&gt;test-spec&lt;/i&gt; to help write readable tests. We use the  &lt;i&gt;autotest&lt;/i&gt; testing library to warn us of tests which are failing due to changes we've made. No code should be installed that does not pass the existing suite of tests.&lt;/p&gt; &lt;p&gt;We use &lt;i&gt;rcov&lt;/i&gt; before we install new code to evaluate how completely our tests are covering our code base. We evaluate the results to decide what tests we should add. Our goal is 100% coverage. When we install, we want to know that every line of code at least executes correctly in a basic usage testing model.&lt;/p&gt; &lt;ul&gt;&lt;li&gt;Design with testing in mind&lt;/li&gt;&lt;li&gt;Run autotest when you begin development&lt;/li&gt;&lt;li&gt;Write tests as part of developing new features&lt;/li&gt;&lt;li&gt;Use test-spec to make tests readable&lt;/li&gt;&lt;li&gt;Use RCov to evaluate the effectiveness the tests&lt;/li&gt;&lt;li&gt;Do not install code until tests exist, and produce 100% coverage&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;HTML, CSS and Javascript&lt;/h2&gt; &lt;p&gt;We attempt to keep our HTML clean by separating out behavior into javascript files, and binding that behavior to DOM objects via the &lt;i&gt;behavior&lt;/i&gt;  library. This makes it easier for us to read and maintain HTML code, and easier  for designers to work in our templates.&lt;/p&gt; &lt;p&gt;Where possible, we use CSS names that describe what things are, not how they  look. So we prefer class names like "&lt;span style="font-style: italic;"&gt;emailaddress&lt;/span&gt;&lt;i&gt;" over "grayanditalic&lt;/i&gt;".  We used nested CSS rules to affect differences in appearance in different  contexts.&lt;/p&gt; &lt;p&gt;The goal is that the same CSS name will apply to the same type of object, regardless of where that object is placed on the site. This creates simple and self-documentating HTML code that is readable and can be maintained by both engineers and design staff.&lt;/p&gt; &lt;p&gt;We use partials to avoid duplication of HTML. All the same issues which apply to source code apply to HTML, CSS and javascript. It should be clean, elegant, and should not include duplication.&lt;/p&gt; &lt;ul&gt;&lt;li&gt;Keep the HTML clean&lt;/li&gt;&lt;li&gt;Use CSS class names that describe what something is, not how it should look&lt;/li&gt;&lt;li&gt;Use the same CSS class name to describe the same thing, no matter where it  is&lt;/li&gt;&lt;li&gt;Use nested CSS class definitions to alter the look of similar items across  the site&lt;/li&gt;&lt;li&gt;Where possible, put javascript in javascript files, not in HTML&lt;/li&gt;&lt;li&gt;Use behavior to bind javascript to HTML&lt;/li&gt;&lt;li&gt;Make it clean, elegant, and avoid duplication&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;Javascript&lt;/h2&gt; &lt;p&gt;We try to minimize the amount of site logic built around javascript. Javascript is hard to test with automated testing tools, which means that to test large bodies of javascript code requires human testing across a broad range of browsers, which is expensive, time consuming and prone to error.&lt;/p&gt; &lt;p&gt;We prefer to have a hit on the server to fetch a small amount of data instead of having a separate body of code that we need to debug in javascript.&lt;/p&gt; &lt;p&gt;This does not mean that we won't render content in javascript (as the result of an ajax request to the server for data), but we avoid writing complex logic in javascript.&lt;/p&gt; &lt;ul&gt;&lt;li&gt;Keep the javascript as simple as possible&lt;/li&gt;&lt;li&gt;Prefer a small hit on the server over complex javascript logic&lt;/li&gt;&lt;li&gt;Focus javascript around simple effects or rendering simple UI&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6294118673654365284-8251872332605834802?l=johnwedgwood.blogspot.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/blogspot/johnwedgwood/~4/m8yvuqBZFjY" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/blogspot/johnwedgwood/~3/m8yvuqBZFjY/manifesto.html</link><author>noreply@blogger.com (John Wedgwood)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://johnwedgwood.blogspot.com/2007/08/manifesto.html</feedburner:origLink></item></channel></rss>
