nesta 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitmodules +6 -0
- data/.travis.yml +9 -4
- data/CHANGES +18 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +70 -54
- data/LICENSE +1 -1
- data/RELEASING.md +5 -6
- data/Rakefile +20 -3
- data/lib/nesta/app.rb +4 -8
- data/lib/nesta/commands/command.rb +1 -2
- data/lib/nesta/commands/demo/content.rb +23 -5
- data/lib/nesta/commands/theme/install.rb +9 -7
- data/lib/nesta/helpers.rb +14 -0
- data/lib/nesta/models.rb +26 -22
- data/lib/nesta/navigation.rb +1 -1
- data/lib/nesta/version.rb +1 -1
- data/nesta.gemspec +10 -11
- data/templates/config/config.yml +1 -1
- data/{spec → test}/fixtures/nesta-plugin-test/Gemfile +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/Rakefile +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/lib/nesta-plugin-test.rb +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/lib/nesta-plugin-test/init.rb +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/lib/nesta-plugin-test/version.rb +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/nesta-plugin-test.gemspec +0 -0
- data/test/integration/atom_feed_test.rb +178 -0
- data/test/integration/commands/demo/content_test.rb +31 -0
- data/test/integration/commands/edit_test.rb +21 -0
- data/test/integration/commands/new_test.rb +120 -0
- data/test/integration/commands/plugin/create_test.rb +128 -0
- data/test/integration/commands/theme/create_test.rb +35 -0
- data/test/integration/commands/theme/enable_test.rb +22 -0
- data/test/integration/commands/theme/install_test.rb +62 -0
- data/test/integration/default_theme_test.rb +220 -0
- data/test/integration/overrides_test.rb +118 -0
- data/test/integration/route_handlers_test.rb +96 -0
- data/test/integration/sitemap_test.rb +85 -0
- data/test/integration_test_helper.rb +61 -0
- data/test/support/model_factory.rb +169 -0
- data/test/support/silence_commands_during_tests.rb +5 -0
- data/test/support/temporary_files.rb +33 -0
- data/test/support/test_configuration.rb +19 -0
- data/test/test_helper.rb +26 -0
- data/test/unit/commands_test.rb +23 -0
- data/test/unit/config_test.rb +138 -0
- data/test/unit/file_model_test.rb +71 -0
- data/test/unit/menu_test.rb +82 -0
- data/test/unit/page_test.rb +571 -0
- data/test/unit/path_test.rb +41 -0
- data/test/unit/plugin_test.rb +47 -0
- data/views/master.sass +1 -1
- metadata +81 -85
- data/smoke-test.sh +0 -107
- data/spec/atom_spec.rb +0 -141
- data/spec/commands/demo/content_spec.rb +0 -65
- data/spec/commands/edit_spec.rb +0 -27
- data/spec/commands/new_spec.rb +0 -88
- data/spec/commands/plugin/create_spec.rb +0 -97
- data/spec/commands/system_spec.rb +0 -25
- data/spec/commands/theme/create_spec.rb +0 -41
- data/spec/commands/theme/enable_spec.rb +0 -44
- data/spec/commands/theme/install_spec.rb +0 -56
- data/spec/config_spec.rb +0 -127
- data/spec/model_factory.rb +0 -92
- data/spec/models_spec.rb +0 -700
- data/spec/overrides_spec.rb +0 -132
- data/spec/page_spec.rb +0 -560
- data/spec/path_spec.rb +0 -28
- data/spec/plugin_spec.rb +0 -51
- data/spec/sitemap_spec.rb +0 -105
- data/spec/spec_helper.rb +0 -114
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'integration_test_helper'
|
2
|
+
|
3
|
+
describe 'Routing' do
|
4
|
+
include Nesta::IntegrationTest
|
5
|
+
|
6
|
+
it 'redirects requests with trailing slash' do
|
7
|
+
with_temp_content_directory do
|
8
|
+
model = create(:page)
|
9
|
+
visit model.path + '/'
|
10
|
+
assert_equal model.abspath, page.current_path
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'not_found handler' do
|
15
|
+
it 'returns HTTP code 404' do
|
16
|
+
with_temp_content_directory do
|
17
|
+
visit '/no-such-page'
|
18
|
+
assert_equal 404, page.status_code
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'default route' do
|
24
|
+
it 'provides access to helper methods in Haml pages' do
|
25
|
+
with_temp_content_directory do
|
26
|
+
model = create(:category,
|
27
|
+
ext: 'haml',
|
28
|
+
content: '%div= format_date(Date.new(2010, 11, 23))')
|
29
|
+
visit model.path
|
30
|
+
assert_has_css 'div', text: '23 November 2010'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should access helpers when rendering articles on a category page" do
|
35
|
+
with_temp_content_directory do
|
36
|
+
category = create(:page)
|
37
|
+
markup = "%h1 Heading\n\n%div= format_date(Date.new(2010, 11, 23))"
|
38
|
+
article = create(:article,
|
39
|
+
ext: 'haml',
|
40
|
+
metadata: { 'categories' => category.path },
|
41
|
+
content: markup)
|
42
|
+
visit category.path
|
43
|
+
assert_has_css 'div', text: '23 November 2010'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_attachment_in(directory)
|
49
|
+
path = File.join(directory, 'test.txt')
|
50
|
+
FileUtils.mkdir_p(directory)
|
51
|
+
File.open(path, 'w') { |file| file.write("I'm a test attachment") }
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'attachments route' do
|
55
|
+
it 'returns HTTP code 200' do
|
56
|
+
with_temp_content_directory do
|
57
|
+
create_attachment_in(Nesta::Config.attachment_path)
|
58
|
+
visit '/attachments/test.txt'
|
59
|
+
assert_equal 200, page.status_code
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'serves attachment to the client' do
|
64
|
+
with_temp_content_directory do
|
65
|
+
create_attachment_in(Nesta::Config.attachment_path)
|
66
|
+
visit '/attachments/test.txt'
|
67
|
+
assert_equal "I'm a test attachment", page.body
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'sets the appropriate MIME type' do
|
72
|
+
with_temp_content_directory do
|
73
|
+
create_attachment_in(Nesta::Config.attachment_path)
|
74
|
+
visit '/attachments/test.txt'
|
75
|
+
assert_match %r{^text/plain}, page.response_headers['Content-Type']
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'refuses to serve files outside the attachments directory' do
|
80
|
+
# On earlier versions of Sinatra this test would have been handed
|
81
|
+
# to the attachments handler. On the current version (1.4.5) it
|
82
|
+
# appears as though the request is no longer matched by the
|
83
|
+
# attachments route handler, which means we're not in danger of
|
84
|
+
# serving serving files to attackers via `#send_file`.
|
85
|
+
#
|
86
|
+
# I've left the test in anyway, as without it we wouldn't become
|
87
|
+
# aware of a security hole if there was a regression in Sinatra.
|
88
|
+
#
|
89
|
+
with_temp_content_directory do
|
90
|
+
model = create(:page)
|
91
|
+
visit "/attachments/../pages/#{File.basename(model.filename)}"
|
92
|
+
assert_equal 404, page.status_code
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'integration_test_helper'
|
2
|
+
|
3
|
+
describe 'XML sitemap' do
|
4
|
+
include Nesta::IntegrationTest
|
5
|
+
|
6
|
+
def visit_sitemap
|
7
|
+
visit '/sitemap.xml'
|
8
|
+
end
|
9
|
+
|
10
|
+
def for_site_with_page(&block)
|
11
|
+
with_temp_content_directory do
|
12
|
+
model = create(:page)
|
13
|
+
visit_sitemap
|
14
|
+
yield(model)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def for_site_with_article(&block)
|
19
|
+
with_temp_content_directory do
|
20
|
+
article = create(:article)
|
21
|
+
visit_sitemap
|
22
|
+
yield(article)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'renders successfully' do
|
27
|
+
for_site_with_page do
|
28
|
+
assert_equal 200, page.status_code
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has a urlset tag' do
|
33
|
+
for_site_with_page do
|
34
|
+
namespace = 'http://www.sitemaps.org/schemas/sitemap/0.9'
|
35
|
+
assert_has_xpath "//urlset[@xmlns='#{namespace}']"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'references the home page' do
|
40
|
+
for_site_with_page do
|
41
|
+
assert_has_xpath '//urlset/url/loc', text: 'http://www.example.com/'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'configures home page to be checked frequently' do
|
46
|
+
for_site_with_page do
|
47
|
+
assert_has_xpath '//urlset/url/loc', text: "http://www.example.com/"
|
48
|
+
assert_has_xpath '//urlset/url/changefreq', text: "daily"
|
49
|
+
assert_has_xpath '//urlset/url/priority', text: "1.0"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sets homepage lastmod from timestamp of most recently modified page" do
|
54
|
+
for_site_with_article do |article|
|
55
|
+
timestamp = article.last_modified
|
56
|
+
assert_has_xpath '//urlset/url/loc', text: "http://www.example.com/"
|
57
|
+
assert_has_xpath '//urlset/url/lastmod', text: timestamp.xmlschema
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def site_url(path)
|
62
|
+
"http://www.example.com/#{path}"
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'references category pages' do
|
66
|
+
for_site_with_page do |model|
|
67
|
+
assert_has_xpath '//urlset/url/loc', text: site_url(model.path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'references article pages' do
|
72
|
+
for_site_with_article do |article|
|
73
|
+
assert_has_xpath '//urlset/url/loc', text: site_url(article.path)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'omits pages that have the skip-sitemap flag set' do
|
78
|
+
with_temp_content_directory do
|
79
|
+
create(:category)
|
80
|
+
omitted = create(:page, metadata: { 'flags' => 'skip-sitemap' })
|
81
|
+
visit_sitemap
|
82
|
+
assert_has_no_xpath '//urlset/url/loc', text: site_url(omitted.path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'capybara/dsl'
|
2
|
+
|
3
|
+
require_relative 'test_helper'
|
4
|
+
|
5
|
+
module Nesta
|
6
|
+
class App < Sinatra::Base
|
7
|
+
set :environment, :test
|
8
|
+
set :reload_templates, true
|
9
|
+
end
|
10
|
+
|
11
|
+
module IntegrationTest
|
12
|
+
include Capybara::DSL
|
13
|
+
|
14
|
+
include ModelFactory
|
15
|
+
include TestConfiguration
|
16
|
+
|
17
|
+
def setup
|
18
|
+
Capybara.app = App.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
Capybara.reset_sessions!
|
23
|
+
Capybara.use_default_driver
|
24
|
+
|
25
|
+
remove_temp_directory
|
26
|
+
Nesta::FileModel.purge_cache
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_has_xpath(query, options = {})
|
30
|
+
if ! page.has_xpath?(query, options)
|
31
|
+
message = "not found in page: '#{query}'"
|
32
|
+
message << ", #{options.inspect}" unless options.empty?
|
33
|
+
fail message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert_has_no_xpath(query, options = {})
|
38
|
+
if page.has_xpath?(query, options)
|
39
|
+
message = "found in page: '#{query}'"
|
40
|
+
message << ", #{options.inspect}" unless options.empty?
|
41
|
+
fail message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_has_css(query, options = {})
|
46
|
+
if ! page.has_css?(query, options)
|
47
|
+
message = "not found in page: '#{query}'"
|
48
|
+
message << ", #{options.inspect}" unless options.empty?
|
49
|
+
fail message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def assert_has_no_css(query, options = {})
|
54
|
+
if ! page.has_no_css?(query, options)
|
55
|
+
message = "found in page: '#{query}'"
|
56
|
+
message << ", #{options.inspect}" unless options.empty?
|
57
|
+
fail message
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module ModelFactory
|
2
|
+
class FileModelWriter
|
3
|
+
def initialize(type, data = {})
|
4
|
+
@type = type
|
5
|
+
@data = data
|
6
|
+
end
|
7
|
+
|
8
|
+
def model_class
|
9
|
+
Nesta::FileModel
|
10
|
+
end
|
11
|
+
|
12
|
+
def instantiate_model
|
13
|
+
model_class.new(filename)
|
14
|
+
end
|
15
|
+
|
16
|
+
def extension
|
17
|
+
data.fetch(:ext, 'mdown')
|
18
|
+
end
|
19
|
+
|
20
|
+
def filename
|
21
|
+
File.join(model_class.model_path, "#{data[:path]}.#{extension}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def sequence_prefix
|
25
|
+
'file-'
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_data
|
29
|
+
{ path: default_path }
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_path
|
33
|
+
sequence_prefix + ModelFactory.next_sequence_number.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def data
|
37
|
+
if @memoized_data.nil?
|
38
|
+
@memoized_data = default_data
|
39
|
+
if @memoized_data.has_key?(:metadata)
|
40
|
+
@memoized_data[:metadata].merge!(@data.delete(:metadata) || {})
|
41
|
+
end
|
42
|
+
@memoized_data.merge!(@data)
|
43
|
+
end
|
44
|
+
@memoized_data
|
45
|
+
end
|
46
|
+
|
47
|
+
def write
|
48
|
+
metadata = data[:metadata] || {}
|
49
|
+
metatext = metadata.map { |key, value| "#{key}: #{value}" }.join("\n")
|
50
|
+
contents =<<-EOF
|
51
|
+
#{metatext}
|
52
|
+
|
53
|
+
#{heading}#{data[:content]}
|
54
|
+
EOF
|
55
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
56
|
+
File.open(filename, 'w') { |file| file.write(contents) }
|
57
|
+
yield(filename) if block_given?
|
58
|
+
end
|
59
|
+
|
60
|
+
def heading
|
61
|
+
return '' unless data[:heading]
|
62
|
+
prefix = {
|
63
|
+
'haml' => "%div\n %h1",
|
64
|
+
'textile' => "<div>\nh1."
|
65
|
+
}.fetch(data[:ext], '# ')
|
66
|
+
"#{prefix} #{data[:heading]}\n\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class PageWriter < FileModelWriter
|
71
|
+
def model_class
|
72
|
+
Nesta::Page
|
73
|
+
end
|
74
|
+
|
75
|
+
def sequence_prefix
|
76
|
+
'page-'
|
77
|
+
end
|
78
|
+
|
79
|
+
def default_data
|
80
|
+
path = default_path
|
81
|
+
{ path: path, heading: heading_from_path(path) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def heading_from_path(path)
|
85
|
+
File.basename(path).sub('-', ' ').capitalize
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class ArticleWriter < PageWriter
|
90
|
+
def sequence_prefix
|
91
|
+
'articles/page-'
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_data
|
95
|
+
path = default_path
|
96
|
+
{
|
97
|
+
path: path,
|
98
|
+
heading: heading_from_path(path),
|
99
|
+
content: 'Content goes here',
|
100
|
+
metadata: {
|
101
|
+
'date' => '29 December 2008'
|
102
|
+
}
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class CategoryWriter < PageWriter
|
108
|
+
def sequence_prefix
|
109
|
+
'categories/page-'
|
110
|
+
end
|
111
|
+
|
112
|
+
def default_data
|
113
|
+
path = default_path
|
114
|
+
{
|
115
|
+
path: path,
|
116
|
+
heading: heading_from_path(path),
|
117
|
+
content: 'Content goes here'
|
118
|
+
}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
@@sequence = 0
|
123
|
+
def self.next_sequence_number
|
124
|
+
@@sequence += 1
|
125
|
+
end
|
126
|
+
|
127
|
+
def before_setup
|
128
|
+
# This is a minitest hook. We reset file sequence number at the
|
129
|
+
# start of each test so that we can automatically generate a unique
|
130
|
+
# path for each file we create within a test.
|
131
|
+
@@sequence = 0
|
132
|
+
end
|
133
|
+
|
134
|
+
def create(type, data = {}, &block)
|
135
|
+
file_writer = writer_class(type).new(type, data)
|
136
|
+
file_writer.write(&block)
|
137
|
+
file_writer.instantiate_model
|
138
|
+
end
|
139
|
+
|
140
|
+
def write_menu_item(indent, file, menu_item)
|
141
|
+
if menu_item.is_a?(Array)
|
142
|
+
indent.sub!(/^/, ' ')
|
143
|
+
menu_item.each { |path| write_menu_item(indent, file, path) }
|
144
|
+
indent.sub!(/^ /, '')
|
145
|
+
else
|
146
|
+
file.write("#{indent}#{menu_item}\n")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def create_menu(menu_text)
|
151
|
+
file = filename(Nesta::Config.content_path, 'menu', :txt)
|
152
|
+
File.open(file, 'w') { |file| file.write(menu_text) }
|
153
|
+
end
|
154
|
+
|
155
|
+
def create_content_directories
|
156
|
+
FileUtils.mkdir_p(Nesta::Config.page_path)
|
157
|
+
FileUtils.mkdir_p(Nesta::Config.attachment_path)
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
def filename(directory, basename, extension = :mdown)
|
162
|
+
File.join(directory, "#{basename}.#{extension}")
|
163
|
+
end
|
164
|
+
|
165
|
+
def writer_class(type)
|
166
|
+
camelcased_type = type.to_s.gsub(/(?:^|_)(\w)/) { $1.upcase }
|
167
|
+
ModelFactory.const_get(camelcased_type + 'Writer')
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module TemporaryFiles
|
2
|
+
TEMP_DIR = File.expand_path('tmp', File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
def remove_temp_directory
|
5
|
+
if File.exist?(TemporaryFiles::TEMP_DIR)
|
6
|
+
FileUtils.rm_r(TemporaryFiles::TEMP_DIR)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def temp_path(base)
|
11
|
+
File.join(TemporaryFiles::TEMP_DIR, base)
|
12
|
+
end
|
13
|
+
|
14
|
+
def project_root
|
15
|
+
temp_path('mysite.com')
|
16
|
+
end
|
17
|
+
|
18
|
+
def project_path(path)
|
19
|
+
File.join(project_root, path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def in_temporary_project(*args, &block)
|
23
|
+
FileUtils.mkdir_p(File.join(project_root, 'config'))
|
24
|
+
File.open(File.join(project_root, 'config', 'config.yml'), 'w').close
|
25
|
+
Dir.chdir(project_root) { yield }
|
26
|
+
ensure
|
27
|
+
remove_temp_directory
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_exists_in_project(path)
|
31
|
+
assert File.exist?(project_path(path)), "#{path} should exist"
|
32
|
+
end
|
33
|
+
end
|