enki-engine 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE +284 -0
- data/README.textile +112 -0
- data/Rakefile +13 -0
- data/TODO.textile +8 -0
- data/app/assets/images/admin/flash_bg.gif +0 -0
- data/app/assets/images/admin/future_bg.png +0 -0
- data/app/assets/images/admin/gray_bg.gif +0 -0
- data/app/assets/images/admin/new_button.png +0 -0
- data/app/assets/images/admin/silver_bg.gif +0 -0
- data/app/assets/images/admin/submit_bg.gif +0 -0
- data/app/assets/images/admin/subnav_bg.gif +0 -0
- data/app/assets/images/openid_icon.png +0 -0
- data/app/assets/images/rails.png +0 -0
- data/app/assets/images/silk/arrow_undo.png +0 -0
- data/app/assets/images/silk/delete.png +0 -0
- data/app/assets/images/silk/pencil.png +0 -0
- data/app/assets/javascripts/admin/actions.js +18 -0
- data/app/assets/javascripts/admin/comments.js +3 -0
- data/app/assets/javascripts/admin/common.js +109 -0
- data/app/assets/javascripts/admin/dashboard.js +33 -0
- data/app/assets/javascripts/admin/edit-preview.js +40 -0
- data/app/assets/javascripts/admin/pages.js +1 -0
- data/app/assets/javascripts/admin/posts.js +1 -0
- data/app/assets/javascripts/admin/shortcut.js +223 -0
- data/app/assets/javascripts/admin.js +17 -0
- data/app/assets/javascripts/application.js +15 -0
- data/app/assets/javascripts/common.js +22 -0
- data/app/assets/javascripts/enki.js +13 -0
- data/app/assets/javascripts/live-comment-preview.js +36 -0
- data/app/assets/stylesheets/admin.css +304 -0
- data/app/assets/stylesheets/application.css.scss +486 -0
- data/app/assets/stylesheets/humanmsg.css +112 -0
- data/app/assets/stylesheets/login.css +65 -0
- data/app/controllers/enki/admin/base_controller.rb +15 -0
- data/app/controllers/enki/admin/comments_controller.rb +52 -0
- data/app/controllers/enki/admin/dashboard_controller.rb +12 -0
- data/app/controllers/enki/admin/health_controller.rb +20 -0
- data/app/controllers/enki/admin/pages_controller.rb +97 -0
- data/app/controllers/enki/admin/posts_controller.rb +104 -0
- data/app/controllers/enki/admin/undo_items_controller.rb +43 -0
- data/app/controllers/enki/application_controller.rb +21 -0
- data/app/controllers/enki/archives_controller.rb +7 -0
- data/app/controllers/enki/base_controller.rb +5 -0
- data/app/controllers/enki/comments_controller.rb +43 -0
- data/app/controllers/enki/pages_controller.rb +7 -0
- data/app/controllers/enki/posts_controller.rb +27 -0
- data/app/helpers/enki/admin/navigation_helper.rb +10 -0
- data/app/helpers/enki/application_helper.rb +35 -0
- data/app/helpers/enki/date_helper.rb +15 -0
- data/app/helpers/enki/form_helper.rb +7 -0
- data/app/helpers/enki/host_helper.rb +19 -0
- data/app/helpers/enki/navigation_helper.rb +23 -0
- data/app/helpers/enki/page_title_helper.rb +33 -0
- data/app/helpers/enki/posts_helper.rb +9 -0
- data/app/helpers/enki/tag_helper.rb +7 -0
- data/app/helpers/enki/url_helper.rb +25 -0
- data/app/models/enki/base/post.rb +153 -0
- data/app/models/enki/comment.rb +75 -0
- data/app/models/enki/comment_activity.rb +33 -0
- data/app/models/enki/delete_comment_undo.rb +33 -0
- data/app/models/enki/delete_page_undo.rb +32 -0
- data/app/models/enki/delete_post_undo.rb +36 -0
- data/app/models/enki/page.rb +42 -0
- data/app/models/enki/post.rb +4 -0
- data/app/models/enki/stats.rb +19 -0
- data/app/models/enki/tag.rb +19 -0
- data/app/models/enki/tagging.rb +7 -0
- data/app/models/enki/undo_item.rb +10 -0
- data/app/views/enki/admin/comments/_comment.html.erb +12 -0
- data/app/views/enki/admin/comments/index.html.erb +30 -0
- data/app/views/enki/admin/comments/show.html.erb +9 -0
- data/app/views/enki/admin/dashboard/show.html.erb +61 -0
- data/app/views/enki/admin/health/index.html.erb +3 -0
- data/app/views/enki/admin/pages/_form.html.erb +3 -0
- data/app/views/enki/admin/pages/_page.html.erb +12 -0
- data/app/views/enki/admin/pages/index.html.erb +25 -0
- data/app/views/enki/admin/pages/new.html.erb +8 -0
- data/app/views/enki/admin/pages/show.html.erb +8 -0
- data/app/views/enki/admin/posts/_form.html.erb +11 -0
- data/app/views/enki/admin/posts/_post.html.erb +12 -0
- data/app/views/enki/admin/posts/_taggings_form.html.erb +4 -0
- data/app/views/enki/admin/posts/_upload_form.html.erb +0 -0
- data/app/views/enki/admin/posts/index.html.erb +25 -0
- data/app/views/enki/admin/posts/new.html.erb +8 -0
- data/app/views/enki/admin/posts/show.html.erb +8 -0
- data/app/views/enki/admin/undo_items/index.html.erb +24 -0
- data/app/views/enki/archives/index.html.erb +17 -0
- data/app/views/enki/comments/_comment.html.erb +4 -0
- data/app/views/enki/pages/_page.html.erb +3 -0
- data/app/views/enki/pages/show.html.erb +5 -0
- data/app/views/enki/posts/_post.html.erb +13 -0
- data/app/views/enki/posts/index.atom.builder +27 -0
- data/app/views/enki/posts/index.html.erb +15 -0
- data/app/views/enki/posts/show.html.erb +37 -0
- data/autotest/discover.rb +2 -0
- data/config/cucumber.yml +8 -0
- data/config/enki.yml.sample +16 -0
- data/config/initializers/enki_ext.rb +3 -0
- data/config/initializers/set_chronic_timezone.rb +1 -0
- data/config/initializers/verification.rb +135 -0
- data/config/routes.rb +34 -0
- data/db/migrate/20110709024316_initialize_db.rb +97 -0
- data/db/seeds.rb +8 -0
- data/enki-engine.gemspec +47 -0
- data/features/admin_dashboard.feature +10 -0
- data/features/admin_health.feature +10 -0
- data/features/admin_undo.feature +20 -0
- data/features/browsing.feature +16 -0
- data/features/step_definitions/admin.rb +27 -0
- data/features/step_definitions/browsing.rb +3 -0
- data/features/step_definitions/posts.rb +11 -0
- data/features/step_definitions/web_steps.rb +1 -0
- data/features/support/env.rb +59 -0
- data/features/support/paths.rb +35 -0
- data/features/support/selectors.rb +39 -0
- data/lib/core_extensions/object.rb +9 -0
- data/lib/core_extensions/string.rb +22 -0
- data/lib/enki/config.rb +44 -0
- data/lib/enki/engine.rb +19 -0
- data/lib/enki/html5_tags.rb +8 -0
- data/lib/enki/pagination_shim.rb +25 -0
- data/lib/enki/version.rb +3 -0
- data/lib/enki.rb +14 -0
- data/lib/enki_formatter.rb +11 -0
- data/lib/tag_list.rb +2 -0
- data/lib/tags_helper.rb +13 -0
- data/lib/tasks/cucumber.rake +65 -0
- data/lib/tasks/enki.rake +29 -0
- data/lib/undo_failed.rb +2 -0
- data/script/cucumber +10 -0
- data/spec/controllers/admin/comments_controller_spec.rb +140 -0
- data/spec/controllers/admin/dashboard_controller_spec.rb +47 -0
- data/spec/controllers/admin/health_controller_spec.rb +49 -0
- data/spec/controllers/admin/pages_controller_spec.rb +136 -0
- data/spec/controllers/admin/posts_controller_spec.rb +183 -0
- data/spec/controllers/admin/undo_items_controller_spec.rb +93 -0
- data/spec/controllers/archives_controller_spec.rb +37 -0
- data/spec/controllers/comments_controller_spec.rb +126 -0
- data/spec/controllers/pages_controller_spec.rb +46 -0
- data/spec/controllers/posts_controller_spec.rb +168 -0
- data/spec/dummy/Gemfile +5 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/config/application.rb +34 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +6 -0
- data/spec/dummy/config/enki.yml +20 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +34 -0
- data/spec/dummy/config/environments/test.rb +32 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +14 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/factories/factories.rb +36 -0
- data/spec/helpers/page_title_helper_spec.rb +54 -0
- data/spec/helpers/url_helper_spec.rb +23 -0
- data/spec/lib/slugorize_spec.rb +44 -0
- data/spec/models/comment_activity_spec.rb +60 -0
- data/spec/models/comment_spec.rb +125 -0
- data/spec/models/delete_comment_undo_spec.rb +52 -0
- data/spec/models/delete_post_undo_spec.rb +18 -0
- data/spec/models/page_spec.rb +75 -0
- data/spec/models/post_spec.rb +257 -0
- data/spec/models/stats_spec.rb +28 -0
- data/spec/models/tag_spec.rb +13 -0
- data/spec/models/tagging_spec.rb +30 -0
- data/spec/rcov.opts +2 -0
- data/spec/routing/admin/pages_routing_spec.rb +29 -0
- data/spec/routing/archives_routing_spec.rb +9 -0
- data/spec/routing/comments_routing_spec.rb +17 -0
- data/spec/routing/pages_routing_spec.rb +9 -0
- data/spec/routing/posts_routing_spec.rb +26 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/support/be_valid_html5.rb +150 -0
- data/spec/support/be_valid_xhtml.rb +148 -0
- data/spec/support/routes_override_helper.rb +12 -0
- data/spec/views/admin/comments/index.html_spec.rb +26 -0
- data/spec/views/admin/comments/show.html_spec.rb +28 -0
- data/spec/views/admin/dashboard/show.html_spec.rb +37 -0
- data/spec/views/admin/pages/index.html_spec.rb +23 -0
- data/spec/views/admin/pages/new.html_spec.rb +16 -0
- data/spec/views/admin/pages/show.html_spec.rb +16 -0
- data/spec/views/admin/posts/index.html_spec.rb +24 -0
- data/spec/views/admin/posts/new.html_spec.rb +16 -0
- data/spec/views/admin/posts/show.html_spec.rb +16 -0
- data/spec/views/admin/undo_items/index.html_spec.rb +19 -0
- data/spec/views/archives/index.html_spec.rb +34 -0
- data/spec/views/pages/show.html_spec.rb +23 -0
- data/spec/views/posts/index.atom.builder_spec.rb +36 -0
- data/spec/views/posts/index.html_spec.rb +39 -0
- data/spec/views/posts/show.html_spec.rb +49 -0
- data/vendor/assets/javascripts/humanmsg.js +86 -0
- data/vendor/assets/javascripts/jquery.easing.1.3.js +205 -0
- data/vendor/assets/javascripts/jquery.form.js +869 -0
- data/vendor/assets/javascripts/jquery.jfeed.js +143 -0
- data/vendor/assets/javascripts/jquery.livequery.js +250 -0
- metadata +464 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'PostsController routes' do
|
4
|
+
|
5
|
+
it "should route /code to posts#index with tag code" do
|
6
|
+
get("/code").should route_to("enki/posts#index", :tag => 'code')
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should route /posts.atmo to posts#index format atom" do
|
10
|
+
get("/posts.atom").should route_to("enki/posts#index", :format => 'atom')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should route /pages to posts#index with tag pages" do
|
14
|
+
get("/pages").should route_to('enki/posts#index', :tag => 'pages')
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should route /code.atmo to posts#index tag: code and format: atom" do
|
18
|
+
get("/code.atom").should route_to("enki/posts#index", :tag => "code", :format => 'atom')
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should route /2008/02/01/a-post to posts#show with year: 2008, month: 02, day: 01, slug: a-post" do
|
22
|
+
get("/2008/02/01/a-post").should route_to("enki/posts#show", :year => '2008', :month => '02', :day => '01', :slug => 'a-post')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
|
+
ENV["RAILS_ENV"] ||= 'test'
|
3
|
+
|
4
|
+
# See http://reinteractive.net/posts/2-start-your-engines for useful info regarding testing Engines.
|
5
|
+
require File.expand_path("../dummy/config/environment.rb", __FILE__)
|
6
|
+
require 'rspec/rails'
|
7
|
+
require 'factory_girl_rails'
|
8
|
+
|
9
|
+
# Add engine url_helpers to the base test app as we are testing the
|
10
|
+
# engine works not whether rails routing proxies work.
|
11
|
+
::ApplicationController.send :include, Enki::Engine.routes.url_helpers
|
12
|
+
|
13
|
+
ENGINE_RAILS_ROOT = File.expand_path('../../', __FILE__)
|
14
|
+
|
15
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
16
|
+
# in spec/support/ and its subdirectories.
|
17
|
+
Dir[File.join(ENGINE_RAILS_ROOT, "spec/support/**/*.rb")].each {|f| require f }
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
require 'rspec/expectations'
|
21
|
+
|
22
|
+
config.include RSpec::Matchers
|
23
|
+
# == Mock Framework
|
24
|
+
#
|
25
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
26
|
+
#
|
27
|
+
# config.mock_with :mocha
|
28
|
+
# config.mock_with :flexmock
|
29
|
+
# config.mock_with :rr
|
30
|
+
config.mock_with :rspec
|
31
|
+
|
32
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
33
|
+
# config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
34
|
+
|
35
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
36
|
+
# examples within a transaction, remove the following line or assign false
|
37
|
+
# instead of true.
|
38
|
+
config.use_transactional_fixtures = true
|
39
|
+
|
40
|
+
config.include FactoryGirl::Syntax::Methods
|
41
|
+
|
42
|
+
# Ensure the routes used by Rspec are the engine ones
|
43
|
+
config.include RoutesOverrideHelper
|
44
|
+
# Make the engines route helpers available to Rspec.
|
45
|
+
config.include Enki::Engine.routes.url_helpers
|
46
|
+
|
47
|
+
config.before(:all) do
|
48
|
+
ActiveRecord::Migration.verbose = false
|
49
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
50
|
+
ActiveRecord::Migrator.up([File.expand_path('../../db/migrate', __FILE__)]) { |migration| true }
|
51
|
+
end
|
52
|
+
|
53
|
+
config.render_views
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
module DisableFlashSweeping
|
58
|
+
def sweep
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# Paste me into spec_helper.rb, or save me somewhere else and require me in.
|
2
|
+
require 'net/http'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
class BeValidHtml5
|
6
|
+
|
7
|
+
def initialize(base, options)
|
8
|
+
@base = base
|
9
|
+
@fragment = options[:fragment]
|
10
|
+
end
|
11
|
+
|
12
|
+
# Assert that markup is valid according the W3C validator web service.
|
13
|
+
# By default, it validates the contents of @response.body, which is set after calling
|
14
|
+
# one of the get/post/etc helper methods. You can also pass it a string to be validated.
|
15
|
+
# Validation errors, if any, will be included in the output. The input fragment and
|
16
|
+
# response from the validator service will be cached in the $RAILS_ROOT/tmp directory to
|
17
|
+
# minimize network calls.
|
18
|
+
#
|
19
|
+
# For example, if you have a FooController with an action Bar, put this in foo_controller_test.rb:
|
20
|
+
#
|
21
|
+
# def test_bar_valid_markup
|
22
|
+
# get :bar
|
23
|
+
# assert_valid_markup
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
MARKUP_VALIDATOR_HOST = ENV['MARKUP_VALIDATOR_HOST'] || 'validator.w3.org'
|
27
|
+
MARKUP_VALIDATOR_PATH = ENV['MARKUP_VALIDATOR_PATH'] || '/check'
|
28
|
+
CSS_VALIDATOR_HOST = ENV['CSS_VALIDATOR_HOST'] || 'jigsaw.w3.org'
|
29
|
+
CSS_VALIDATOR_PATH = ENV['CSS_VALIDATOR_PATH'] || '/css-validator/validator'
|
30
|
+
|
31
|
+
@@display_invalid_content = false
|
32
|
+
cattr_accessor :display_invalid_content
|
33
|
+
|
34
|
+
@@auto_validate = false
|
35
|
+
cattr_accessor :auto_validate
|
36
|
+
|
37
|
+
class_attribute :auto_validate_excludes
|
38
|
+
class_attribute :auto_validate_includes
|
39
|
+
|
40
|
+
|
41
|
+
def matches?(rendered)
|
42
|
+
fn = @base
|
43
|
+
fragment = rendered
|
44
|
+
fragment = wrap_with_html5_header(fragment) if @fragment
|
45
|
+
return true if validity_checks_disabled?
|
46
|
+
base_filename = cache_resource('markup',fragment,fn)
|
47
|
+
|
48
|
+
return false unless base_filename
|
49
|
+
results_filename = base_filename + '-results.yml'
|
50
|
+
|
51
|
+
begin
|
52
|
+
response = File.open(results_filename) do |f| Marshal.load(f) end
|
53
|
+
rescue
|
54
|
+
response = http.start(MARKUP_VALIDATOR_HOST).post2(MARKUP_VALIDATOR_PATH, "fragment=#{CGI.escape(fragment)}&output=xml")
|
55
|
+
File.open(results_filename, 'w+') do |f| Marshal.dump(response, f) end
|
56
|
+
end
|
57
|
+
markup_is_valid = response['x-w3c-validator-status'] == 'Valid'
|
58
|
+
unless markup_is_valid
|
59
|
+
fragment.split($/).each_with_index{|line, index| message << "#{'%04i' % (index+1)} : #{line}#{$/}"} if @@display_invalid_content
|
60
|
+
@message = "Invalid markup:\n"
|
61
|
+
@elements = Nokogiri::HTML(response.body.force_encoding('utf-8')).css("li.msg_err > span.msg")
|
62
|
+
(@elements).each { |span| @message << CGI.unescapeHTML(span.inner_html) + "\n" }
|
63
|
+
end
|
64
|
+
if markup_is_valid
|
65
|
+
return true
|
66
|
+
else
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def wrap_with_html5_header(fragment)
|
72
|
+
ret = <<-EOS
|
73
|
+
<!DOCTYPE html>
|
74
|
+
<html dir="ltr" lang="en-US">
|
75
|
+
<head>
|
76
|
+
<meta charset="utf-8">
|
77
|
+
<title>Test</title>
|
78
|
+
</head>
|
79
|
+
<body>
|
80
|
+
#{fragment}
|
81
|
+
</body>
|
82
|
+
</html>
|
83
|
+
EOS
|
84
|
+
end
|
85
|
+
|
86
|
+
def description
|
87
|
+
"be valid html5"
|
88
|
+
end
|
89
|
+
|
90
|
+
def failure_message
|
91
|
+
" expected html5 to be valid, but validation produced these errors:\n #{@message}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def negative_failure_message
|
95
|
+
" expected to not be valid, but was (missing validation?)"
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def validity_checks_disabled?
|
100
|
+
ENV["NET"] != 'true'
|
101
|
+
end
|
102
|
+
|
103
|
+
def text_to_multipart(key,value)
|
104
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(key)}\"\r\n\r\n#{value}\r\n"
|
105
|
+
end
|
106
|
+
|
107
|
+
def file_to_multipart(key,filename,mime_type,content)
|
108
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(key)}\"; filename=\"#{filename}\"\r\n" +
|
109
|
+
"Content-Transfer-Encoding: binary\r\nContent-Type: #{mime_type}\r\n\r\n#{content}\r\n"
|
110
|
+
end
|
111
|
+
|
112
|
+
def cache_resource(base,resource,fn)
|
113
|
+
resource_md5 = Digest::MD5.hexdigest(resource).to_s
|
114
|
+
file_md5 = nil
|
115
|
+
|
116
|
+
output_dir = "#{Rails.root}/tmp/#{base}"
|
117
|
+
base_filename = File.join(output_dir, fn)
|
118
|
+
filename = base_filename
|
119
|
+
|
120
|
+
parent_dir = File.dirname(filename)
|
121
|
+
FileUtils.mkdir_p(parent_dir) unless File.exists?(parent_dir)
|
122
|
+
|
123
|
+
File.open(filename, 'r') do |f|
|
124
|
+
file_md5 = Digest::MD5.hexdigest(f.read(f.stat.size)).to_s
|
125
|
+
end if File.exists?(filename)
|
126
|
+
|
127
|
+
if file_md5 != resource_md5
|
128
|
+
Dir["#{base_filename}[^.]*"] .each {|f| File.delete(f)}
|
129
|
+
File.open(filename, 'w+') do |f| f.write(resource); end
|
130
|
+
end
|
131
|
+
base_filename
|
132
|
+
end
|
133
|
+
|
134
|
+
def http
|
135
|
+
if Module.constants.include?("ApplicationConfig") && ApplicationConfig.respond_to?(:proxy_config)
|
136
|
+
Net::HTTP::Proxy(ApplicationConfig.proxy_config['host'], ApplicationConfig.proxy_config['port'])
|
137
|
+
else
|
138
|
+
Net::HTTP
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
def be_valid_html5
|
145
|
+
BeValidhtml5.new(subject)
|
146
|
+
end
|
147
|
+
|
148
|
+
def be_valid_html5_fragment
|
149
|
+
BeValidHtml5.new(subject, :fragment => true)
|
150
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# Paste me into spec_helper.rb, or save me somewhere else and require me in.
|
2
|
+
|
3
|
+
class BeValidXhtml
|
4
|
+
require 'net/http'
|
5
|
+
require 'digest/md5'
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@fragment = options[:fragment]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Assert that markup (html/xhtml) is valid according the W3C validator web service.
|
12
|
+
# Validation errors, if any, will be included in the output. The input fragment and
|
13
|
+
# response from the validator service will be cached in the #{Rails.root}/tmp directory to
|
14
|
+
# minimize network calls.
|
15
|
+
#
|
16
|
+
# For example, if you have a FooController with an action Bar, put this in foo_controller_test.rb:
|
17
|
+
#
|
18
|
+
# def test_bar_valid_markup
|
19
|
+
# get :bar
|
20
|
+
# assert_valid_markup
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
MARKUP_VALIDATOR_HOST = ENV['MARKUP_VALIDATOR_HOST'] || 'validator.w3.org'
|
24
|
+
MARKUP_VALIDATOR_PATH = ENV['MARKUP_VALIDATOR_PATH'] || '/check'
|
25
|
+
CSS_VALIDATOR_HOST = ENV['CSS_VALIDATOR_HOST'] || 'jigsaw.w3.org'
|
26
|
+
CSS_VALIDATOR_PATH = ENV['CSS_VALIDATOR_PATH'] || '/css-validator/validator'
|
27
|
+
|
28
|
+
@@display_invalid_content = false
|
29
|
+
cattr_accessor :display_invalid_content
|
30
|
+
|
31
|
+
@@auto_validate = false
|
32
|
+
cattr_accessor :auto_validate
|
33
|
+
|
34
|
+
class_attribute :auto_validate_excludes
|
35
|
+
class_attribute :auto_validate_includes
|
36
|
+
|
37
|
+
|
38
|
+
def matches?(rendered)
|
39
|
+
fragment = rendered
|
40
|
+
fragment = wrap_with_xhtml_header(fragment) if @fragment
|
41
|
+
return true if validity_checks_disabled?
|
42
|
+
base_filename = cache_resource('markup',fragment,'html')
|
43
|
+
|
44
|
+
return false unless base_filename
|
45
|
+
results_filename = base_filename + '-results.yml'
|
46
|
+
|
47
|
+
begin
|
48
|
+
response = File.open(results_filename) do |f| Marshal.load(f) end
|
49
|
+
rescue
|
50
|
+
response = http.start(MARKUP_VALIDATOR_HOST).post2(MARKUP_VALIDATOR_PATH, "fragment=#{CGI.escape(fragment)}&output=xml")
|
51
|
+
File.open(results_filename, 'w+') do |f| Marshal.dump(response, f) end
|
52
|
+
end
|
53
|
+
markup_is_valid = response['x-w3c-validator-status'] == 'Valid'
|
54
|
+
@message = ''
|
55
|
+
unless markup_is_valid
|
56
|
+
fragment.split($/).each_with_index{|line, index| message << "#{'%04i' % (index+1)} : #{line}#{$/}"} if @@display_invalid_content
|
57
|
+
@message << XmlSimple.xml_in(response.body)['messages'][0]['msg'].collect{ |m| "Invalid markup: line #{m['line']}: #{CGI.unescapeHTML(m['content'])}" }.join("\n")
|
58
|
+
end
|
59
|
+
if markup_is_valid
|
60
|
+
return true
|
61
|
+
else
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def wrap_with_xhtml_header(fragment)
|
67
|
+
ret = <<-EOS
|
68
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
69
|
+
<!DOCTYPE html PUBLIC
|
70
|
+
"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"
|
71
|
+
"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd">
|
72
|
+
|
73
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
74
|
+
<head>
|
75
|
+
<title>Test</title>
|
76
|
+
</head>
|
77
|
+
<body>
|
78
|
+
#{fragment}
|
79
|
+
</body>
|
80
|
+
</html>
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
|
84
|
+
def description
|
85
|
+
"be valid xhtml"
|
86
|
+
end
|
87
|
+
|
88
|
+
def failure_message
|
89
|
+
" expected xhtml to be valid, but validation produced these errors:\n #{@message}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def negative_failure_message
|
93
|
+
" expected to not be valid, but was (missing validation?)"
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def validity_checks_disabled?
|
98
|
+
ENV["NET"] != 'true'
|
99
|
+
end
|
100
|
+
|
101
|
+
def text_to_multipart(key,value)
|
102
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(key)}\"\r\n\r\n#{value}\r\n"
|
103
|
+
end
|
104
|
+
|
105
|
+
def file_to_multipart(key,filename,mime_type,content)
|
106
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(key)}\"; filename=\"#{filename}\"\r\n" +
|
107
|
+
"Content-Transfer-Encoding: binary\r\nContent-Type: #{mime_type}\r\n\r\n#{content}\r\n"
|
108
|
+
end
|
109
|
+
|
110
|
+
def cache_resource(base,rendered,extension)
|
111
|
+
rendered_md5 = Digest::MD5.hexdigest(rendered).to_s
|
112
|
+
file_md5 = nil
|
113
|
+
|
114
|
+
output_dir = "#{Rails.root}/tmp/#{base}"
|
115
|
+
base_filename = File.join(output_dir, rendered_md5)
|
116
|
+
filename = base_filename + '.' + extension
|
117
|
+
|
118
|
+
parent_dir = File.dirname(filename)
|
119
|
+
FileUtils.mkdir_p(parent_dir) unless File.exists?(parent_dir)
|
120
|
+
|
121
|
+
File.open(filename, 'r') do |f|
|
122
|
+
file_md5 = Digest::MD5.hexdigest(f.read(f.stat.size)).to_s
|
123
|
+
end if File.exists?(filename)
|
124
|
+
|
125
|
+
if file_md5 != rendered_md5
|
126
|
+
Dir["#{base_filename}[^.]*"] .each {|f| File.delete(f)}
|
127
|
+
File.open(filename, 'w+') do |f| f.write(rendered); end
|
128
|
+
end
|
129
|
+
base_filename
|
130
|
+
end
|
131
|
+
|
132
|
+
def http
|
133
|
+
if Module.constants.include?("ApplicationConfig") && ApplicationConfig.respond_to?(:proxy_config)
|
134
|
+
Net::HTTP::Proxy(ApplicationConfig.proxy_config['host'], ApplicationConfig.proxy_config['port'])
|
135
|
+
else
|
136
|
+
Net::HTTP
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
def be_valid_xhtml
|
143
|
+
BeValidXhtml.new
|
144
|
+
end
|
145
|
+
|
146
|
+
def be_valid_xhtml_fragment
|
147
|
+
BeValidXhtml.new(:fragment => true)
|
148
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/comments/index.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
comments = [mock_model(Comment,
|
12
|
+
:author => 'Don Alias',
|
13
|
+
:body => 'Hello I am a post',
|
14
|
+
:created_at => Time.now,
|
15
|
+
:post_title => 'A Post',
|
16
|
+
:post => mock_model(Post,
|
17
|
+
:slug => 'a-post',
|
18
|
+
:published_at => Time.now
|
19
|
+
)
|
20
|
+
)].extend PaginationShim
|
21
|
+
assign :comments, comments
|
22
|
+
render :template => '/enki/admin/comments/index', :formats => [:html]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/comments/show.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
assign :comment, mock_model(Comment,
|
12
|
+
:author => 'Don Alias',
|
13
|
+
:author_url => 'http://enkiblog.com',
|
14
|
+
:author_email => 'donalias@enkiblog.com',
|
15
|
+
:body => 'Hello I am a post',
|
16
|
+
:created_at => Time.now
|
17
|
+
)
|
18
|
+
allow_message_expectations_on_nil
|
19
|
+
assigns[:comment].stub!(:post).and_return(mock_model(Post,
|
20
|
+
:title => 'A post',
|
21
|
+
:slug => 'a-post',
|
22
|
+
:published_at => Time.now
|
23
|
+
))
|
24
|
+
render :template => '/enki/admin/comments/show', :formats => [:html]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/dashboard/show.html" do
|
6
|
+
before(:each) do
|
7
|
+
view.stub!(:enki_config).and_return(Enki::Config.default)
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
rendered.should be_valid_html5_fragment
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should render' do
|
15
|
+
assign :posts, [mock_model(Post,
|
16
|
+
:title => 'A Post',
|
17
|
+
:published_at => Time.now,
|
18
|
+
:slug => 'a-post',
|
19
|
+
:approved_comments => []
|
20
|
+
)]
|
21
|
+
assign :pages, [create(:page)]
|
22
|
+
assign :comment_activity, [mock("comment-activity-1",
|
23
|
+
:post => mock_model(Post,
|
24
|
+
:published_at => Time.now,
|
25
|
+
:title => "A Post",
|
26
|
+
:slug => 'a-post',
|
27
|
+
:approved_comments => []
|
28
|
+
),
|
29
|
+
:comments => [mock_model(Comment, :author => 'Don', :body_html => 'Hello')],
|
30
|
+
:most_recent_comment => mock_model(Comment, :created_at => Time.now, :author => 'Don')
|
31
|
+
)]
|
32
|
+
assign :stats, Struct.new(:post_count, :page_count, :comment_count, :tag_count).new(3,4,2,1)
|
33
|
+
render :template => '/enki/admin/dashboard/show', :formats => [:html]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/pages/index.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
pages = [mock_model(Page,
|
12
|
+
:title => 'A page',
|
13
|
+
:body => 'Hello I am a page',
|
14
|
+
:slug => 'a-page',
|
15
|
+
:created_at => Time.now
|
16
|
+
)]
|
17
|
+
pages.stub!(:total_pages).and_return(1)
|
18
|
+
assign :pages, pages
|
19
|
+
render :template => '/enki/admin/pages/index', :formats => [:html]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/pages/new.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
assign :page, Page.new
|
12
|
+
render :template => '/enki/admin/pages/new', :formats => [:html]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/pages/show.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
assign :page, create(:page)
|
12
|
+
render :template => '/enki/admin/pages/show', :formats => [:html]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/posts/index.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
posts = [mock_model(Post,
|
12
|
+
:published_at => Time.now,
|
13
|
+
:title => 'A post',
|
14
|
+
:body => 'Hello I am a post',
|
15
|
+
:slug => 'a-post',
|
16
|
+
:approved_comments => []
|
17
|
+
)]
|
18
|
+
posts.stub!(:total_pages).and_return(1)
|
19
|
+
assign :posts, posts
|
20
|
+
render :template => '/enki/admin/posts/index', :formats => [:html]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/posts/new.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
assign :post, Post.new
|
12
|
+
render :template => '/enki/admin/posts/new', :formats => [:html]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/posts/show.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
assign :post, create(:post)
|
12
|
+
render :template => '/enki/admin/posts/show', :formats => [:html]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/admin/undo_items/index.html" do
|
6
|
+
after(:each) do
|
7
|
+
rendered.should be_valid_html5_fragment
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should render' do
|
11
|
+
assign :undo_items, [mock_model(UndoItem,
|
12
|
+
:created_at => Time.now,
|
13
|
+
:description => 'Deleted a comment'
|
14
|
+
)]
|
15
|
+
render :template => '/enki/admin/undo_items/index', :formats => [:html]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
module Enki
|
4
|
+
|
5
|
+
describe "/archives/index.html" do
|
6
|
+
def tag(name)
|
7
|
+
mock_model(Tag, :name => name)
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
view.stub!(:enki_config).and_return(Enki::Config.default)
|
12
|
+
|
13
|
+
month = Struct.new(:date, :posts)
|
14
|
+
assign :months, [
|
15
|
+
month.new(1.month.ago.utc.beginning_of_month, [
|
16
|
+
mock_model(Post, :title => 'Post A', :published_at => 3.weeks.ago.utc, :slug => 'post-a', :tags => [tag("Code")])
|
17
|
+
]),
|
18
|
+
month.new(2.months.ago.utc.beginning_of_month, [
|
19
|
+
mock_model(Post, :title => 'Post B', :published_at => 6.weeks.ago.utc, :slug => 'post-b', :tags => [tag("Code"), tag("Ruby")]),
|
20
|
+
mock_model(Post, :title => 'Post C', :published_at => 7.weeks.ago.utc, :slug => 'post-c', :tags => [])
|
21
|
+
])
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:each) do
|
26
|
+
rendered.should be_valid_html5_fragment
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'renders posts grouped by month' do
|
30
|
+
render :template => "/enki/archives/index", :formats => [:html]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|