rails31-evergreen 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. data/README.rdoc +193 -0
  2. data/bin/evergreen +18 -0
  3. data/config/routes.rb +3 -0
  4. data/lib/evergreen.rb +41 -0
  5. data/lib/evergreen/application.rb +43 -0
  6. data/lib/evergreen/cli.rb +23 -0
  7. data/lib/evergreen/rails.rb +12 -0
  8. data/lib/evergreen/reports.rb +79 -0
  9. data/lib/evergreen/resources/evergreen.css +64 -0
  10. data/lib/evergreen/resources/evergreen.js +80 -0
  11. data/lib/evergreen/resources/jasmine-html.js +190 -0
  12. data/lib/evergreen/resources/jasmine.css +166 -0
  13. data/lib/evergreen/resources/jasmine.js +2477 -0
  14. data/lib/evergreen/resources/json2.js +478 -0
  15. data/lib/evergreen/resources/version.rb +6 -0
  16. data/lib/evergreen/runner.rb +148 -0
  17. data/lib/evergreen/server.rb +22 -0
  18. data/lib/evergreen/spec.rb +52 -0
  19. data/lib/evergreen/suite.rb +44 -0
  20. data/lib/evergreen/tasks.rb +6 -0
  21. data/lib/evergreen/template.rb +32 -0
  22. data/lib/evergreen/version.rb +3 -0
  23. data/lib/evergreen/views/layout.erb +19 -0
  24. data/lib/evergreen/views/list.erb +9 -0
  25. data/lib/evergreen/views/spec.erb +40 -0
  26. data/lib/jasmine/Gemfile +6 -0
  27. data/lib/jasmine/MIT.LICENSE +20 -0
  28. data/lib/jasmine/README.markdown +28 -0
  29. data/lib/jasmine/Rakefile +182 -0
  30. data/lib/jasmine/cruise_config.rb +21 -0
  31. data/lib/jasmine/example/SpecRunner.html +27 -0
  32. data/lib/jasmine/example/spec/PlayerSpec.js +58 -0
  33. data/lib/jasmine/example/spec/SpecHelper.js +9 -0
  34. data/lib/jasmine/example/src/Player.js +22 -0
  35. data/lib/jasmine/example/src/Song.js +7 -0
  36. data/lib/jasmine/images/fail-16.png +0 -0
  37. data/lib/jasmine/images/fail.png +0 -0
  38. data/lib/jasmine/images/go-16.png +0 -0
  39. data/lib/jasmine/images/go.png +0 -0
  40. data/lib/jasmine/images/pending-16.png +0 -0
  41. data/lib/jasmine/images/pending.png +0 -0
  42. data/lib/jasmine/images/question-bk.png +0 -0
  43. data/lib/jasmine/images/questionbk-16.png +0 -0
  44. data/lib/jasmine/images/spinner.gif +0 -0
  45. data/lib/jasmine/jsdoc-template/allclasses.tmpl +17 -0
  46. data/lib/jasmine/jsdoc-template/allfiles.tmpl +56 -0
  47. data/lib/jasmine/jsdoc-template/class.tmpl +646 -0
  48. data/lib/jasmine/jsdoc-template/index.tmpl +39 -0
  49. data/lib/jasmine/jsdoc-template/publish.js +184 -0
  50. data/lib/jasmine/jsdoc-template/static/default.css +162 -0
  51. data/lib/jasmine/jsdoc-template/static/header.html +2 -0
  52. data/lib/jasmine/jsdoc-template/static/index.html +19 -0
  53. data/lib/jasmine/jsdoc-template/symbol.tmpl +35 -0
  54. data/lib/jasmine/lib/jasmine-html.js +188 -0
  55. data/lib/jasmine/lib/jasmine.css +166 -0
  56. data/lib/jasmine/lib/jasmine.js +2421 -0
  57. data/lib/jasmine/lib/json2.js +478 -0
  58. data/lib/jasmine/spec/runner.html +80 -0
  59. data/lib/jasmine/spec/suites/BaseSpec.js +27 -0
  60. data/lib/jasmine/spec/suites/CustomMatchersSpec.js +97 -0
  61. data/lib/jasmine/spec/suites/EnvSpec.js +158 -0
  62. data/lib/jasmine/spec/suites/ExceptionsSpec.js +107 -0
  63. data/lib/jasmine/spec/suites/JsApiReporterSpec.js +103 -0
  64. data/lib/jasmine/spec/suites/MatchersSpec.js +795 -0
  65. data/lib/jasmine/spec/suites/MockClockSpec.js +38 -0
  66. data/lib/jasmine/spec/suites/MultiReporterSpec.js +45 -0
  67. data/lib/jasmine/spec/suites/NestedResultsSpec.js +54 -0
  68. data/lib/jasmine/spec/suites/PrettyPrintSpec.js +93 -0
  69. data/lib/jasmine/spec/suites/QueueSpec.js +23 -0
  70. data/lib/jasmine/spec/suites/ReporterSpec.js +56 -0
  71. data/lib/jasmine/spec/suites/RunnerSpec.js +267 -0
  72. data/lib/jasmine/spec/suites/SpecRunningSpec.js +1253 -0
  73. data/lib/jasmine/spec/suites/SpecSpec.js +124 -0
  74. data/lib/jasmine/spec/suites/SpySpec.js +201 -0
  75. data/lib/jasmine/spec/suites/SuiteSpec.js +120 -0
  76. data/lib/jasmine/spec/suites/TrivialReporterSpec.js +238 -0
  77. data/lib/jasmine/spec/suites/UtilSpec.js +40 -0
  78. data/lib/jasmine/spec/suites/WaitsForBlockSpec.js +87 -0
  79. data/lib/jasmine/src/Block.js +22 -0
  80. data/lib/jasmine/src/Env.js +264 -0
  81. data/lib/jasmine/src/JsApiReporter.js +102 -0
  82. data/lib/jasmine/src/Matchers.js +354 -0
  83. data/lib/jasmine/src/MultiReporter.js +35 -0
  84. data/lib/jasmine/src/NestedResults.js +80 -0
  85. data/lib/jasmine/src/PrettyPrinter.js +122 -0
  86. data/lib/jasmine/src/Queue.js +99 -0
  87. data/lib/jasmine/src/Reporter.js +31 -0
  88. data/lib/jasmine/src/Runner.js +77 -0
  89. data/lib/jasmine/src/Spec.js +242 -0
  90. data/lib/jasmine/src/Suite.js +82 -0
  91. data/lib/jasmine/src/WaitsBlock.js +13 -0
  92. data/lib/jasmine/src/WaitsForBlock.js +52 -0
  93. data/lib/jasmine/src/base.js +589 -0
  94. data/lib/jasmine/src/html/TrivialReporter.js +188 -0
  95. data/lib/jasmine/src/html/jasmine.css +166 -0
  96. data/lib/jasmine/src/mock-timeout.js +183 -0
  97. data/lib/jasmine/src/util.js +67 -0
  98. data/lib/jasmine/src/version.json +5 -0
  99. data/lib/tasks/evergreen.rake +54 -0
  100. data/spec/evergreen_spec.rb +29 -0
  101. data/spec/meta_spec.rb +50 -0
  102. data/spec/runner_spec.rb +33 -0
  103. data/spec/spec_helper.rb +49 -0
  104. data/spec/spec_spec.rb +28 -0
  105. data/spec/suite1/public/jquery.js +152 -0
  106. data/spec/suite1/public/styles.css +3 -0
  107. data/spec/suite1/spec/javascripts/bar_spec.js +0 -0
  108. data/spec/suite1/spec/javascripts/coffeescript_spec.coffee +7 -0
  109. data/spec/suite1/spec/javascripts/failing_spec.js +12 -0
  110. data/spec/suite1/spec/javascripts/foo_spec.js +0 -0
  111. data/spec/suite1/spec/javascripts/invalid_coffee_spec.coffee +1 -0
  112. data/spec/suite1/spec/javascripts/libs/lucid_spec.js +0 -0
  113. data/spec/suite1/spec/javascripts/models/game_spec.js +0 -0
  114. data/spec/suite1/spec/javascripts/slow_spec.coffee +8 -0
  115. data/spec/suite1/spec/javascripts/spec_helper.coffee +1 -0
  116. data/spec/suite1/spec/javascripts/spec_helper.js +1 -0
  117. data/spec/suite1/spec/javascripts/templates/another_template.html +1 -0
  118. data/spec/suite1/spec/javascripts/templates/escape.html +1 -0
  119. data/spec/suite1/spec/javascripts/templates/one_template.html +1 -0
  120. data/spec/suite1/spec/javascripts/templates/script_tags.html +3 -0
  121. data/spec/suite1/spec/javascripts/templates_spec.js +55 -0
  122. data/spec/suite1/spec/javascripts/testing_spec.js +11 -0
  123. data/spec/suite1/spec/javascripts/transactions_spec.js +14 -0
  124. data/spec/suite1/spec/javascripts/with_helper_spec.js +9 -0
  125. data/spec/suite2/config/evergreen.rb +5 -0
  126. data/spec/suite2/public_html/foo.js +1 -0
  127. data/spec/suite2/spec/awesome_spec.js +12 -0
  128. data/spec/suite2/spec/failing_spec.js +5 -0
  129. data/spec/suite2/templates/foo.html +1 -0
  130. data/spec/suite_spec.rb +26 -0
  131. data/spec/template_spec.rb +32 -0
  132. metadata +296 -0
@@ -0,0 +1,193 @@
1
+ = Evergreen
2
+
3
+ "Because green is the new Blue(Ridge)"
4
+
5
+ Evergreen is a tool to run javascript unit tests for client side JavaScript. It
6
+ combines a server which allows you to serve up and run your specs in a browser,
7
+ as well as a runner which uses Capybara and any of its drivers to run your
8
+ specs. Evergreen uses the Jasmine unit testing framework for JavaScript.
9
+
10
+ http://github.com/jnicklas/evergreen
11
+
12
+ == Philosophy
13
+
14
+ Evergreen is a unit testing tool. Its purpose is to test JavaScript in
15
+ isolation from your application. If you need a tool that tests how your
16
+ JavaScript integrates with your application you should use an integration
17
+ testing framework, such as {Capybara}[http://github.com/jnicklas/capybara].
18
+
19
+ == Installation
20
+
21
+ Install as a Ruby gem:
22
+
23
+ gem install evergreen
24
+
25
+ == Usage
26
+
27
+ Evergreen assumes a file and directory structure, place all your javascript
28
+ code inside ./public and all spec files inside ./spec/javascripts. All spec
29
+ files should end in _spec.js. For example:
30
+
31
+ public/widget.js
32
+ spec/javascripts/widget_spec.js
33
+
34
+ You can require files from the public directory inside your spec file:
35
+
36
+ require('/widget.js')
37
+
38
+ describe('a widget', function() {
39
+ ...
40
+ });
41
+
42
+ You can now look at your spec files inside a browser by starting up the
43
+ Evergreen server:
44
+
45
+ evergreen serve
46
+
47
+ Alternatively you can run the specs headlessly by running:
48
+
49
+ evergreen run
50
+
51
+ == Integrating with Rails 3
52
+
53
+ Add Evergreen to your Gemfile:
54
+
55
+ gem 'evergreen', :require => 'evergreen/rails'
56
+
57
+ Start your rails application and navigate to /evergreen. You should now see a
58
+ list of all spec files, click on one to run it.
59
+
60
+ There's a rake task provided for you that you can use to run your specs:
61
+
62
+ rake spec:javascripts
63
+
64
+ == Integrating with Rails 2
65
+
66
+ Add the following line to your Rakefile:
67
+
68
+ require 'evergreen/tasks'
69
+
70
+ This will give you the `spec:javascripts` rake task. Note that mounting is not
71
+ possible under Rails 2 and that `require 'evergreen/rails'` will fail.
72
+
73
+ == Configuration
74
+
75
+ By default, Evergreen uses Selenium to run your specs and assumes a certain
76
+ directory structure. If this standard is fine for you, then you don't need to
77
+ do anything else. If you need to configure Evergreen to suit your needs,
78
+ Evergreen will automatically look for and load the following files:
79
+
80
+ config/evergreen.rb
81
+ .evergreen
82
+ ~/.evergreen
83
+
84
+ The content of these files could look like this:
85
+
86
+ require 'capybara/envjs'
87
+
88
+ Evergreen.configure do |config|
89
+ config.driver = :envjs
90
+ config.public_dir = 'public_html'
91
+ config.template_dir = 'templates'
92
+ config.spec_dir = 'spec'
93
+ end
94
+
95
+ == Transactions
96
+
97
+ One problem often faced when writing unit tests for client side code is that
98
+ changes to the page are not reverted for the next example, so that successive
99
+ examples become dependent on each other. Evergreen adds a special div to your
100
+ page with an id of test. This div is automatically emptied before each example.
101
+ You should avoid appending markup to the page body and instead append it to
102
+ this test div:
103
+
104
+ describe('transactions', function() {
105
+ it("should add stuff in one test...", function() {
106
+ $('#test').append('<h1 id="added">New Stuff</h1>');
107
+ expect($('#test h1#added').length).toEqual(1);
108
+ });
109
+
110
+ it("... should have been removed before the next starts", function() {
111
+ expect($('#test h1#added').length).toEqual(0);
112
+ });
113
+ });
114
+
115
+ == Templates
116
+
117
+ Even more powerful than that, Evergreen allows you to create HTML templates to
118
+ go along with your specs. Put the templates in their own folder like this:
119
+
120
+ spec/javascripts/templates/one_template.html
121
+ spec/javascripts/templates/another_template.html
122
+
123
+ You can then load the template into the test div, by calling the template
124
+ function in your specs:
125
+
126
+ describe('transactions', function() {
127
+ template('one_template.html')
128
+
129
+ it("should load the template in this test", function() {
130
+ ...
131
+ });
132
+ });
133
+
134
+ == Spec Helper
135
+
136
+ If you add a spec_helper file like so:
137
+
138
+ spec/javascripts/spec_helper.js
139
+
140
+ It will automatically be loaded. This is a great place for adding custom
141
+ matchers and the like.
142
+
143
+ == CoffeeScript
144
+
145
+ Evergreen supports specs written in
146
+ {CoffeeScript}[http://github.com/jashkenas/coffee-script]. Just name your spec
147
+ file _spec.coffee and it will automatically be translated for you.
148
+
149
+ You can also add a CoffeeScript spec helper, but remember that CoffeeScript
150
+ encloses individual files in a closure, if you need something you define in the
151
+ spec helper to be available in your spec files, attach it to the window object:
152
+
153
+ # spec/javascripts/spec_helper.coffee
154
+
155
+ MyThing: "foo" # local to spec helper
156
+ window.MyThing: "foo" # global
157
+
158
+ == Development
159
+
160
+ If you plan to work on Evergreen, you need to checkout the Jasmine gem, which
161
+ is added as a git submodule. Run the following command:
162
+
163
+ git submodule update --init
164
+
165
+ If you're using a version of Evergreen from git with bundler, you need to tell
166
+ bundler to use submodules, this can be achieved with the following command:
167
+
168
+ gem 'evergreen', :submodules => true, :git => 'git://github.com/jnicklas/evergreen.git'
169
+
170
+ == License:
171
+
172
+ (The MIT License)
173
+
174
+ Copyright (c) 2009 Jonas Nicklas
175
+
176
+ Permission is hereby granted, free of charge, to any person obtaining
177
+ a copy of this software and associated documentation files (the
178
+ 'Software'), to deal in the Software without restriction, including
179
+ without limitation the rights to use, copy, modify, merge, publish,
180
+ distribute, sublicense, and/or sell copies of the Software, and to
181
+ permit persons to whom the Software is furnished to do so, subject to
182
+ the following conditions:
183
+
184
+ The above copyright notice and this permission notice shall be
185
+ included in all copies or substantial portions of the Software.
186
+
187
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
188
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
189
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
190
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
191
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
192
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
193
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require 'evergreen'
6
+
7
+ begin
8
+ # The dup is to keep ARGV intact, so that tools like ruby-debug can respawn.
9
+ success = Evergreen::Cli.execute(ARGV.dup)
10
+ Kernel.exit(success ? 0 : 1)
11
+ rescue SystemExit => e
12
+ Kernel.exit(e.status)
13
+ rescue Exception => e
14
+ STDERR.puts("#{e.message} (#{e.class})")
15
+ STDERR.puts(e.backtrace.join("\n"))
16
+ Kernel.exit(1)
17
+ end
18
+
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ mount Evergreen.rails, :at => '/evergreen'
3
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'sinatra/base'
3
+ require 'capybara'
4
+ require 'launchy'
5
+ require 'evergreen/version'
6
+ require 'evergreen/application'
7
+ require 'json'
8
+
9
+ module Evergreen
10
+ autoload :Cli, 'evergreen/cli'
11
+ autoload :Server, 'evergreen/server'
12
+ autoload :Runner, 'evergreen/runner'
13
+ autoload :Suite, 'evergreen/suite'
14
+ autoload :Spec, 'evergreen/spec'
15
+ autoload :Template, 'evergreen/template'
16
+ autoload :Reports, 'evergreen/reports'
17
+
18
+ class << self
19
+ attr_accessor :driver, :public_dir, :template_dir, :spec_dir
20
+
21
+ def configure
22
+ yield self
23
+ end
24
+
25
+ def extensions(&block)
26
+ @extensions = block if block
27
+ @extensions
28
+ end
29
+
30
+ def use_defaults!
31
+ configure do |config|
32
+ config.driver = :selenium
33
+ config.public_dir = 'public'
34
+ config.spec_dir = 'spec/javascripts'
35
+ config.template_dir = 'spec/javascripts/templates'
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ Evergreen.use_defaults!
@@ -0,0 +1,43 @@
1
+ module Evergreen
2
+ def self.application(suite)
3
+ Rack::Builder.new do
4
+ instance_eval(&Evergreen.extensions) if Evergreen.extensions
5
+
6
+ map "/resources" do
7
+ use Rack::Static, :urls => ["/"], :root => File.expand_path('resources', File.dirname(__FILE__))
8
+ run lambda { |env| [404, {}, "No such file"]}
9
+ end
10
+
11
+ map "/" do
12
+ app = Class.new(Sinatra::Base).tap do |app|
13
+ app.reset!
14
+ app.class_eval do
15
+ set :static, true
16
+ set :root, File.expand_path('.', File.dirname(__FILE__))
17
+ set :public, File.expand_path(File.join(suite.root, Evergreen.public_dir), File.dirname(__FILE__))
18
+
19
+ helpers do
20
+ def url(path)
21
+ request.env['SCRIPT_NAME'].to_s + path.to_s
22
+ end
23
+ end
24
+
25
+ get '/' do
26
+ @suite = suite
27
+ erb :list
28
+ end
29
+
30
+ get '/run/*' do |name|
31
+ @suite = suite
32
+ @spec = suite.get_spec(name)
33
+ @js_spec_helper = suite.get_spec('spec_helper.js')
34
+ @coffee_spec_helper = suite.get_spec('spec_helper.coffee')
35
+ erb :spec
36
+ end
37
+ end
38
+ end
39
+ run app
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ module Evergreen
2
+ class Cli
3
+ def self.execute(argv)
4
+ new.execute(argv)
5
+ end
6
+
7
+ def execute(argv)
8
+ command = argv.shift
9
+ root = File.expand_path(argv.shift || '.', Dir.pwd)
10
+
11
+ case command
12
+ when "serve"
13
+ Evergreen::Suite.new(root).serve
14
+ return true
15
+ when "run"
16
+ return Evergreen::Suite.new(root).run
17
+ else
18
+ puts "no such command '#{command}'"
19
+ return false
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ require 'evergreen'
2
+ require 'rails'
3
+
4
+ module Evergreen
5
+ def self.rails
6
+ Evergreen::Suite.new(Rails.root).application
7
+ end
8
+
9
+ class Railtie < Rails::Engine
10
+ end
11
+ end
12
+
@@ -0,0 +1,79 @@
1
+ require 'builder'
2
+
3
+ # generates reports in junit xml for ci/jenkins
4
+ module Evergreen
5
+ module Reports
6
+ def self.filename(spec_result, index)
7
+ File.join(dir, "#{spec_result[:name]}.#{index}.xml")
8
+ end
9
+
10
+ def self.dir
11
+ ENV['CI_REPORTS'] || File.expand_path("#{Dir.getwd}/reports")
12
+ end
13
+
14
+ def self.collect_data(suite)
15
+ # open new reporter
16
+ runners = suite.runner.instance_eval('spec_runners')
17
+ result_data = runners.map do |specrunner|
18
+ {
19
+ :name => specrunner.spec.name,
20
+ :passed => specrunner.spec.passed?,
21
+ :results => specrunner.examples.map { |r|
22
+ row = r.instance_variable_get("@row")
23
+ hash = {}
24
+ row.each {|k,v| hash[k] = v}
25
+ hash
26
+ }
27
+ }
28
+ end
29
+ end
30
+
31
+ def self.find_filename(spec_result)
32
+ index = 0
33
+ while File.exists?(filename(spec_result, index))
34
+ index = index + 1
35
+ end
36
+ filename(spec_result, index)
37
+ end
38
+
39
+ def self.publish_spec(spec_result)
40
+ File.open(find_filename(spec_result), "w") do |f|
41
+ builder = Builder::XmlMarkup.new(:target => f, :indent => 2, :escape_attrs => true)
42
+ builder.instruct!
43
+
44
+ tests = 0
45
+ failures = 0
46
+ spec_result[:results].each do |result|
47
+ tests = tests + 1
48
+ failures = failures + 1 unless result['passed']
49
+ end
50
+
51
+ builder.testsuite({:name => spec_result[:name], :tests => tests, :failures => failures}) do
52
+ spec_result[:results].each do |result|
53
+ builder.testcase({'name' => "#{spec_result[:name]} - #{result['name']}"}) do
54
+ unless result['passed']
55
+ builder.failure(:message => result['message']) do
56
+ builder.text!('Failed: '+result['name']+' '+result['message'])
57
+ if result['trace']
58
+ builder.text!(" -> in #{result['trace']['fileName']}:#{result['trace']['lineNumber']}")
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def self.publish!(suite)
69
+ result_data = collect_data(suite)
70
+
71
+ FileUtils.rm_rf(dir)
72
+ FileUtils.mkdir_p(dir)
73
+
74
+ result_data.each do |spec_result|
75
+ publish_spec(spec_result)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,64 @@
1
+ body {
2
+ font-family: 'Lucida grande', 'sans-serif';
3
+ background: #f0f0f0;
4
+ }
5
+
6
+ a {
7
+ color: #00A500;
8
+ }
9
+
10
+ #page, #test, .jasmine_reporter {
11
+ width: 800px;
12
+ background: white;
13
+ -moz-border-radius: 3px;
14
+ padding: 20px;
15
+ margin: 50px auto 30px;
16
+ border: 1px solid #ddd
17
+ }
18
+
19
+ #test {
20
+ min-height: 50px;
21
+ max-height: 300px;
22
+ overflow: auto;
23
+ margin: 30px auto 30px;
24
+ }
25
+
26
+ .jasmine_reporter {
27
+ margin: 30px auto 30px;
28
+ }
29
+
30
+ #page h1 {
31
+ font-size: 24px;
32
+ margin: 0 0 15px;
33
+ }
34
+
35
+ #page a.back {
36
+ font-size: 12px;
37
+ }
38
+
39
+ #page ul {
40
+ margin: 0;
41
+ padding: 0;
42
+ }
43
+
44
+ #page ul li {
45
+ list-style: none;
46
+ border: 1px solid #ddd;
47
+ -moz-border-radius: 3px;
48
+ margin: 10px 0;
49
+ background: #f5f5f5;
50
+ }
51
+
52
+ #page ul li a {
53
+ padding: 7px 10px;
54
+ display: block;
55
+ text-decoration: none;
56
+ }
57
+
58
+ #footer {
59
+ margin: 0 auto 30px;
60
+ width: 600px;
61
+ text-align: center;
62
+ font-size: 13px;
63
+ color: #aaa;
64
+ }