* Create .rspec * Update .rubocop.yml * Update .travis.yml * Update hanami-helpers.gemspec * Update Rakefile * Create escape_helper_spec.rb * Create escape_helper_spec.rb * Create escape_helper_spec.rb * Delete escape_helper_spec.rb * Create fixtures.rb * Create version_spec.rb * Update escape_helper_spec.rb * Create html_helper_spec.rb * create form_helper_spec in progress * Create link_to_helper_spec.rb * Create routing_helper_spec.rb * Create number_formatting_helper_spec.rb * Update escape_helper_spec.rb * Create html_helper_spec * Create number_formatter_helper_spec * Create routing_helper_spec * Rename number_formatter_helper_spec to number_formatter_helper_spec.rb * Rename html_helper_spec to html_helper_spec.rb * Rename routing_helper_spec to routing_helper_spec.rb * Create form_helper_spec.rb * Create link_to_helper_spec.rb * Update .travis.yml * Update fixtures.rb * Create new.html.erb * Add files via upload * Create spec_helper.rb * Create html_builder_spec.rb * Update html_helper_spec.rb * Update escape_helper_spec.rb * Update form_helper_spec.rb * Update html_helper_spec.rb * Update escape_helper_spec.rb * Update version_spec.rb * Update * Update form_helper_spec.rb * Update html_helper_spec.rb * Update number_formatting_helper_spec.rb * clear trailing whitespace * Update spec_helper.rb * Update html_helper_spec.rb * Update hanami-helpers.gemspec * Update escape_helper_spec.rb * fix indentation issues * Update hanami-helpers.gemspec * Update Rakefile * Update form_helper_spec.rb * Update form_helper_spec.rb * Fix to skip errors about array and indent * Fix to clear CI issue This should fix this issue: test/fixtures.rb:58:5: W: Lint/AmbiguousBlockAssociation: Parenthesize the param html to make sure that the block will be associated with the + method call. (https://github.com/bbatsov/ruby-style-guide#syntax) html { div 'Hello' } + html { div 'Hanami' } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Update form_helper_spec.rb * Update fixtures.rb * Update fixtures.rb * Update form_helper_spec.rb * Update fixtures.rb
This commit is contained in:
parent
ed852dd633
commit
3425b08a2f
|
@ -5,5 +5,10 @@ inherit_from:
|
|||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- "test/**/*_test.rb" # Minitest syntax generates this false positive
|
||||
- 'spec/**/*'
|
||||
Style/MethodMissing:
|
||||
Enabled: false
|
||||
Style/IndentHeredoc:
|
||||
Enabled: false
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
|
|
@ -5,7 +5,7 @@ before_install:
|
|||
- gem update --system
|
||||
- rvm @global do gem uninstall bundler -a -x
|
||||
- rvm @global do gem install bundler -v 1.13.7
|
||||
script: 'bundle exec rake test:coverage --trace && bundle exec rubocop'
|
||||
script: 'bundle exec rake spec:coverage --trace && bundle exec rubocop'
|
||||
rvm:
|
||||
- 2.3.3
|
||||
- 2.4.0
|
||||
|
|
18
Rakefile
18
Rakefile
|
@ -1,17 +1,25 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rspec/core/rake_task'
|
||||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.libs.push 'test'
|
||||
end
|
||||
|
||||
namespace :test do
|
||||
namespace :spec do
|
||||
RSpec::Core::RakeTask.new(:unit) do |task|
|
||||
file_list = FileList['spec/**/*_spec.rb']
|
||||
file_list = file_list.exclude("spec/{integration,isolation}/**/*_spec.rb")
|
||||
|
||||
task.pattern = file_list
|
||||
end
|
||||
|
||||
task :coverage do
|
||||
ENV['COVERALL'] = 'true'
|
||||
Rake::Task['test'].invoke
|
||||
ENV['COVERAGE'] = 'true'
|
||||
Rake::Task['spec:unit'].invoke
|
||||
end
|
||||
end
|
||||
|
||||
task default: :test
|
||||
task default: 'spec:unit'
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# coding: utf-8
|
||||
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'hanami/helpers/version'
|
||||
|
||||
|
@ -25,6 +27,6 @@ Gem::Specification.new do |spec|
|
|||
spec.add_development_dependency 'bundler', '~> 1.6'
|
||||
spec.add_development_dependency 'rake', '~> 11'
|
||||
spec.add_development_dependency 'minitest', '~> 5.5'
|
||||
|
||||
spec.add_development_dependency 'rspec', '~> 3.5'
|
||||
spec.add_development_dependency 'dry-struct', '~> 0.1'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
RSpec.describe Hanami::Helpers::EscapeHelper do
|
||||
before do
|
||||
@view = EscapeView.new
|
||||
end
|
||||
|
||||
it 'has a private escape html method' do
|
||||
expect { @view.escape_html }.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it 'has a private escape html attribute method' do
|
||||
expect { @view.escape_html_attribute }.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it 'has a private escape url method' do
|
||||
expect { @view.escape_url }.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it 'has a private raw method' do
|
||||
expect { @view.raw }.to raise_error(NoMethodError)
|
||||
end
|
||||
it 'autoscape evil string' do
|
||||
expect(@view.evil_string).to eq(%(<script>alert('xss')</script>))
|
||||
end
|
||||
|
||||
it "don't autoscape safe string" do
|
||||
expect(@view.good_string).to eq(%(this is a good string))
|
||||
end
|
||||
|
||||
it 'autoscape attributes evil string' do
|
||||
expect(@view.good_attributes_string).to eq(%(<a title='foo'>link</a>))
|
||||
end
|
||||
|
||||
it "don't autoscape attributes safe string" do
|
||||
expect(@view.evil_attributes_string).to eq(%(<a title='<script>alert('xss')</script>'>link</a>))
|
||||
end
|
||||
|
||||
it 'autoscape url evil string' do
|
||||
expect(@view.good_url_string).to eq(%(http://hanamirb.org))
|
||||
end
|
||||
|
||||
it "don't autoscape url evil string" do
|
||||
expect(@view.evil_url_string).to be_empty
|
||||
end
|
||||
|
||||
it 'raw string is returned' do
|
||||
expect(@view.raw_string).to eq(%(<div>I'm a raw string</div>))
|
||||
end
|
||||
|
||||
it 'raw string is a Hanami::Helpers::Escape::SafeString class' do
|
||||
expect(@view.raw_string.class).to eq(Hanami::Utils::Escape::SafeString)
|
||||
end
|
||||
|
||||
it 'html helper alias' do
|
||||
expect(@view.html_string_alias).to eq(%(this is a good string))
|
||||
end
|
||||
|
||||
it 'html attribute helper alias' do
|
||||
expect(@view.html_attribute_string_alias).to eq(%(<a title='foo'>link</a>))
|
||||
end
|
||||
|
||||
it 'url helper alias' do
|
||||
expect(@view.url_string_alias).to eq(%(http://hanamirb.org))
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,293 @@
|
|||
RSpec.describe Hanami::Helpers::HtmlHelper::HtmlBuilder do
|
||||
before do
|
||||
@builder = Hanami::Helpers::HtmlHelper::HtmlBuilder.new
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# TAGS #
|
||||
##############################################################################
|
||||
|
||||
describe 'unknown tag' do
|
||||
it 'generates it' do
|
||||
result = @builder.tag(:custom, 'Foo', id: 'content').to_s
|
||||
expect(result).to eq(%(<custom id="content">Foo</custom>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<a>' do
|
||||
it 'generates a link' do
|
||||
result = @builder.a('Hanami', href: 'http://hanamirb.org').to_s
|
||||
expect(result).to eq(%(<a href="http://hanamirb.org">Hanami</a>))
|
||||
end
|
||||
|
||||
it 'generates a link with target' do
|
||||
result = @builder.a('Hanami', href: 'http://hanamirb.org', target: '_blank').to_s
|
||||
expect(result).to eq(%(<a href="http://hanamirb.org" target="_blank">Hanami</a>))
|
||||
end
|
||||
|
||||
it 'generates a link with image' do
|
||||
result = @builder.a('Hanami', href: 'http://hanamirb.org') do
|
||||
img(src: '/images/logo.png')
|
||||
end.to_s
|
||||
|
||||
expect(result).to eq(%(<a href="http://hanamirb.org">\n<img src="/images/logo.png">\n</a>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<abbr>' do
|
||||
it 'generates an abbreviation' do
|
||||
result = @builder.abbr('MVC', title: 'Model View Controller').to_s
|
||||
expect(result).to eq(%(<abbr title="Model View Controller">MVC</abbr>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<addr>' do
|
||||
it 'generates an address' do
|
||||
content = Hanami::Utils::Escape::SafeString.new(
|
||||
<<-CONTENT
|
||||
Mozilla Foundation<br>
|
||||
1981 Landings Drive<br>
|
||||
Building K<br>
|
||||
Mountain View, CA 94043-0801<br>
|
||||
USA
|
||||
CONTENT
|
||||
)
|
||||
|
||||
result = @builder.address(content).to_s
|
||||
expect(result).to eq(%(<address>#{content}</address>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<script>' do
|
||||
it 'generates a script tag with a link to a javascript' do
|
||||
result = @builder.script(src: '/assets/application.js').to_s
|
||||
expect(result).to eq(%(<script src="/assets/application.js"></script>))
|
||||
end
|
||||
|
||||
it 'generates a script tag with javascript code' do
|
||||
result = @builder.script { Hanami::Utils::Escape::SafeString.new(%(alert("hello"))) }.to_s
|
||||
expect(result).to eq(%(<script>\nalert("hello")\n</script>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<template>' do
|
||||
it 'generates a template tag' do
|
||||
result = @builder.template(id: 'product') do
|
||||
div 'Computer'
|
||||
end.to_s
|
||||
|
||||
expect(result).to eq(%(<template id="product">\n<div>Computer</div>\n</template>))
|
||||
end
|
||||
|
||||
it 'generates a script tag with javascript code' do
|
||||
result = @builder.script { Hanami::Utils::Escape::SafeString.new(%(alert("hello"))) }.to_s
|
||||
expect(result).to eq(%(<script>\nalert("hello")\n</script>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<title>' do
|
||||
it 'generates a title' do
|
||||
result = @builder.title('Welcome to Foo').to_s
|
||||
expect(result).to eq(%(<title>Welcome to Foo</title>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<dialog>' do
|
||||
it 'generates a dialog' do
|
||||
result = @builder.dialog('Greetings, one and all!').to_s
|
||||
expect(result).to eq(%(<dialog>Greetings, one and all!</dialog>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<hgroup>' do
|
||||
it 'generates a hgroup' do
|
||||
result = @builder.hgroup do
|
||||
h1 "Hello"
|
||||
end.to_s
|
||||
|
||||
expect(result).to eq(%(<hgroup>\n<h1>Hello</h1>\n</hgroup>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<rtc>' do
|
||||
it 'generates a rtc' do
|
||||
result = @builder.rtc("Rome").to_s
|
||||
expect(result).to eq(%(<rtc>Rome</rtc>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<slot>' do
|
||||
it 'generates a slot' do
|
||||
result = @builder.slot("Need description").to_s
|
||||
expect(result).to eq(%(<slot>Need description</slot>))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<var>' do
|
||||
it 'generates a var' do
|
||||
result = @builder.var("x").to_s
|
||||
expect(result).to eq(%(<var>x</var>))
|
||||
end
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# EMPTY TAGS #
|
||||
##############################################################################
|
||||
|
||||
describe 'empty tag' do
|
||||
it 'generates it' do
|
||||
result = @builder.empty_tag(:xr, id: 'foo').to_s
|
||||
expect(result).to eq(%(<xr id="foo">))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<img>' do
|
||||
it 'generates an image' do
|
||||
result = @builder.img(src: '/images/logo.png', alt: 'Hanami logo').to_s
|
||||
expect(result).to eq(%(<img src="/images/logo.png" alt="Hanami logo">))
|
||||
end
|
||||
|
||||
it 'generates an image with size' do
|
||||
result = @builder.img(src: '/images/logo.png', height: 128, width: 128).to_s
|
||||
expect(result).to eq(%(<img src="/images/logo.png" height="128" width="128">))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<link>' do
|
||||
it 'generates a link to a stylesheet' do
|
||||
result = @builder.link(href: '/assets/application.css', rel: 'stylesheet').to_s
|
||||
expect(result).to eq(%(<link href="/assets/application.css" rel="stylesheet">))
|
||||
end
|
||||
end
|
||||
|
||||
describe '<meta>' do
|
||||
it 'generates HTML4 content type' do
|
||||
# RUBY_VERSION >= 2.2
|
||||
# result = @builder.meta('http-equiv': 'Content-Type', content: 'text/html; charset=utf-8').to_s
|
||||
result = @builder.meta(:'http-equiv' => 'Content-Type', content: 'text/html; charset=utf-8').to_s
|
||||
expect(result).to eq(%(<meta http-equiv="Content-Type" content="text/html; charset=utf-8">))
|
||||
end
|
||||
|
||||
it 'generates HTML5 content type' do
|
||||
result = @builder.meta(charset: 'utf-8').to_s
|
||||
expect(result).to eq(%(<meta charset="utf-8">))
|
||||
end
|
||||
|
||||
it 'generates a page refresh' do
|
||||
# RUBY_VERSION >= 2.2
|
||||
# result = @builder.meta('http-equiv': 'refresh', content: '23;url=http://hanamirb.org').to_s
|
||||
result = @builder.meta(:'http-equiv' => 'refresh', content: '23;url=http://hanamirb.org').to_s
|
||||
expect(result).to eq(%(<meta http-equiv="refresh" content="23;url=http://hanamirb.org">))
|
||||
end
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# FRAGMENTS
|
||||
##############################################################################
|
||||
|
||||
describe 'fragment' do
|
||||
it 'generates a html fragment' do
|
||||
result = @builder.fragment do
|
||||
span 'Hello'
|
||||
span 'Hanami'
|
||||
end.to_s
|
||||
|
||||
expect(result).to eq(%(<span>Hello</span>\n<span>Hanami</span>))
|
||||
end
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# ATTRIBUTES #
|
||||
##############################################################################
|
||||
|
||||
describe 'attributes' do
|
||||
it 'handles no attribute list' do
|
||||
result = @builder.input.to_s
|
||||
expect(result).to eq('<input>')
|
||||
end
|
||||
|
||||
it 'handles empty attribute list' do
|
||||
result = @builder.input({}).to_s
|
||||
expect(result).to eq('<input>')
|
||||
end
|
||||
|
||||
it 'handles nil attribute list' do
|
||||
result = @builder.input(nil).to_s
|
||||
expect(result).to eq('<input>')
|
||||
end
|
||||
|
||||
it 'does not render boolean attribute when its value is false' do
|
||||
result = @builder.input(required: false).to_s
|
||||
expect(result).to eq('<input>')
|
||||
end
|
||||
|
||||
it 'does not render boolean attribute when its value is nil' do
|
||||
result = @builder.input(required: nil).to_s
|
||||
expect(result).to eq('<input>')
|
||||
end
|
||||
|
||||
it 'does render boolean attribute when its value is true' do
|
||||
result = @builder.input(required: true).to_s
|
||||
expect(result).to eq('<input required="required">')
|
||||
end
|
||||
|
||||
it 'does render boolean attribute when its value is trueish' do
|
||||
result = @builder.input(required: 'yes').to_s
|
||||
expect(result).to eq('<input required="required">')
|
||||
end
|
||||
|
||||
it 'also handles strings for detection of boolean attributes' do
|
||||
result = @builder.input('required' => true).to_s
|
||||
expect(result).to eq('<input required="required">')
|
||||
end
|
||||
|
||||
it 'renders multiple attributes' do
|
||||
result = @builder.input('required' => true, 'value' => 'Title "book"', 'something' => 'bar').to_s
|
||||
expect(result).to eq('<input required="required" value="Title "book"" something="bar">')
|
||||
end
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# TEXT
|
||||
##############################################################################
|
||||
|
||||
describe 'plain text' do
|
||||
it 'renders plain text' do
|
||||
result = @builder.text('Foo').to_s
|
||||
expect(result).to eq('Foo')
|
||||
end
|
||||
|
||||
it 'accepts any object that respond to #to_s' do
|
||||
result = @builder.text(23).to_s
|
||||
expect(result).to eq('23')
|
||||
end
|
||||
|
||||
it 'renders plain text inside a tag' do
|
||||
result = @builder.p do
|
||||
span('Foo')
|
||||
text('Bar')
|
||||
end.to_s
|
||||
|
||||
expect(result).to eq(%(<p>\n<span>Foo</span>\nBar\n</p>))
|
||||
end
|
||||
|
||||
it 'ignores block' do
|
||||
result = @builder.text('Foo') { p 'Bar' }.to_s
|
||||
expect(result).to eq('Foo')
|
||||
end
|
||||
|
||||
it 'allows concatenation with raw string' do
|
||||
result = @builder.p do
|
||||
span('Foo') +
|
||||
'Bar'
|
||||
end.to_s
|
||||
|
||||
expect(result).to eq(%(<p>\n<span>Foo</span>\nBar\n</p>))
|
||||
end
|
||||
|
||||
it 'escapes HTML inside' do
|
||||
result = @builder.text(%(<p>Foo</p>)).to_s
|
||||
expect(result).to eq('<p>Foo</p>')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,107 @@
|
|||
RSpec.describe Hanami::Helpers::HtmlHelper do
|
||||
before do
|
||||
@view = HtmlView.new
|
||||
end
|
||||
|
||||
it 'has a private html builder' do
|
||||
expect { @view.html }.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it 'returns an empty tag' do
|
||||
expect(@view.empty_div.to_s).to eq(%(<div></div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with string content' do
|
||||
expect(@view.div_with_string_content.to_s).to eq(%(<div>hello world</div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with block content as string' do
|
||||
expect(@view.div_with_block_content_as_string.to_s).to eq(%(<div>\nhola\n</div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with block content as tag helper' do
|
||||
expect(@view.div_with_block_content_with_tag_helper.to_s).to eq(%(<div><p>inner</p></div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with block content with nested calls' do
|
||||
expect(@view.div_with_block_content_with_nested_calls.to_s).to eq(%(<div>\n<span>hello</span>\n</div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with block content with multiple nested calls' do
|
||||
expect(@view.div_with_block_content_and_multiple_nested_calls.to_s).to eq(%(<form action="/users" method="POST">\n<div>\n<label for="user-first-name">First name</label>\n<input type="text" id="user-first-name" name="user[first_name]" value="L">\n</div>\n<input type="submit" value="Save changes">\n</form>))
|
||||
end
|
||||
|
||||
it 'returns a concatenation of multiple divs' do
|
||||
expect(@view.concatenation_of_multiple_divs.to_s).to eq(%(<div>Hello</div>\n<div>Hanami</div>))
|
||||
end
|
||||
|
||||
it 'returns a concatenation of multiple fragments' do
|
||||
expect(@view.concatenation_of_multiple_fragments.to_s).to eq(%(<div>Hello</div>\n<div>Hanami</div>))
|
||||
end
|
||||
|
||||
it 'returns a concatenation of fragment and div' do
|
||||
expect(@view.concatenation_of_fragment_and_div.to_s).to eq(%(<div>Hello</div>\n<div>Hanami</div>))
|
||||
end
|
||||
|
||||
it 'returns a fragment with block content as string' do
|
||||
expect(@view.fragment_with_block_content.to_s).to eq(%(<div>Hello</div>\n<div>Hanami</div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with attribute' do
|
||||
expect(@view.div_with_attr.to_s).to eq(%(<div id="container"></div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with data attribute' do
|
||||
expect(@view.div_with_data_attr.to_s).to eq(%(<div data-where="up"></div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with attributes' do
|
||||
expect(@view.div_with_attrs.to_s).to eq(%(<div id="content" class="filled"></div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with string content and attributes' do
|
||||
expect(@view.div_with_string_content_and_attrs.to_s).to eq(%(<div id="greeting" class="blink">ciao</div>))
|
||||
end
|
||||
|
||||
it 'returns a tag with block content as string and attributes' do
|
||||
expect(@view.div_with_block_content_as_string_and_attrs.to_s).to eq(%(<div id="sidebar" class="blue">\nbonjour\n</div>))
|
||||
end
|
||||
|
||||
it 'returns a custom tag' do
|
||||
expect(@view.custom_tag.to_s).to eq(%(<custom id="next">Foo</custom>))
|
||||
end
|
||||
|
||||
it 'returns a custom empty tag' do
|
||||
expect(@view.custom_empty_tag.to_s).to eq(%(<xr id="next">))
|
||||
end
|
||||
|
||||
it 'autoescapes string contents' do
|
||||
expect(@view.evil_string_content.to_s).to eq(%(<div><script>alert('xss')</script></div>))
|
||||
end
|
||||
|
||||
it 'autoescapes block contents' do
|
||||
expect(@view.evil_block_content.to_s).to eq(%(<div>\n<script>alert('xss')</script>\n</div>))
|
||||
end
|
||||
|
||||
it 'autoescapes nested helpers contents' do
|
||||
expect(@view.evil_tag_helper.to_s).to eq(%(<div><p><script>alert('xss')</script></p></div>))
|
||||
end
|
||||
|
||||
it 'autoescapes nested blocks' do
|
||||
expect(@view.evil_nested_block_content.to_s).to eq(%(<div>\n<p><script>alert('xss')</script></p>\n</div>))
|
||||
end
|
||||
|
||||
describe 'with link_to helper' do
|
||||
before do
|
||||
@view = HtmlAndLinkTo.new
|
||||
end
|
||||
|
||||
it 'returns two links in div' do
|
||||
expect(@view.two_links_to_in_div.to_s).to eq(%(<div>\n<a href=\"/comments\">Comments</a>\n<a href=\"/posts\">Posts</a>\n</div>))
|
||||
end
|
||||
|
||||
it 'returns span and link in div' do
|
||||
expect(@view.span_and_link_to_in_div.to_s).to eq(%(<div>\n<span>hello</span>\n<a href=\"/comments\">Comments</a>\n</div>))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
RSpec.describe Hanami::Helpers::LinkToHelper do
|
||||
before do
|
||||
@view = LinkTo.new
|
||||
end
|
||||
|
||||
it 'returns a link to posts' do
|
||||
expect(@view.link_to_posts.to_s).to eq(%(<a href="/posts/">Posts</a>))
|
||||
end
|
||||
|
||||
it 'returns a link to a post' do
|
||||
expect(@view.link_to_post.to_s).to eq(%(<a href="/post/1">Post</a>))
|
||||
end
|
||||
|
||||
it 'returns a link with a class' do
|
||||
expect(@view.link_to_with_class.to_s).to eq(%(<a class="first" href="/posts/">Post</a>))
|
||||
end
|
||||
|
||||
it 'returns a link with id' do
|
||||
expect(@view.link_to_with_id.to_s).to eq(%(<a id="posts__link" href="/posts/">Post</a>))
|
||||
end
|
||||
|
||||
it 'returns a link relative link' do
|
||||
expect(@view.link_to_relative_posts.to_s).to eq(%(<a href="posts">Posts</a>))
|
||||
end
|
||||
|
||||
it 'returns a link with html content' do
|
||||
expect(@view.link_to_with_html_content.to_s).to eq(%(<a href="/posts/">\n<strong>Post</strong>\n</a>))
|
||||
end
|
||||
|
||||
it 'returns a link with html content, id and class' do
|
||||
expect(@view.link_to_with_html_content_id_and_class.to_s).to eq(%(<a id="posts__link" class="first" href="/posts/">\n<strong>Post</strong>\n</a>))
|
||||
end
|
||||
|
||||
it 'raises an exception link with content and html content' do
|
||||
expect { @view.link_to_with_content_and_html_content }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'raises an exception when link with content, html content, id and class' do
|
||||
expect { @view.link_to_with_content_html_content_id_and_class }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'raises an exception when have not arguments' do
|
||||
expect { @view.link_to_without_args }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'raises an exception when have not arguments and empty block' do
|
||||
expect { @view.link_to_without_args_and_empty_block }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'raises an exception when have only content' do
|
||||
expect { @view.link_to_with_only_content }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,85 @@
|
|||
RSpec.describe Hanami::Helpers::NumberFormattingHelper do
|
||||
before do
|
||||
@view = NumbersView.new
|
||||
end
|
||||
|
||||
it 'returns string representation of one' do
|
||||
expect(@view.single_digit).to eq('1')
|
||||
end
|
||||
|
||||
it 'returns string representation of one thousand' do
|
||||
expect(@view.thousand_digits).to eq('1,000')
|
||||
end
|
||||
|
||||
it 'returns string representation of one million' do
|
||||
expect(@view.million_digits).to eq('1,000,000')
|
||||
end
|
||||
|
||||
it 'returns string representation of point one' do
|
||||
expect(@view.zero_point_one).to eq('0.1')
|
||||
end
|
||||
|
||||
it 'returns string representation of 5 thousand and 2 point 007' do
|
||||
expect(@view.mixed_digits).to eq('5,002.007')
|
||||
end
|
||||
|
||||
it 'formats precision to 2dp by default' do
|
||||
expect(@view.precision_default_format).to eq('3.14')
|
||||
end
|
||||
|
||||
it 'returns string formatted to 4dp' do
|
||||
expect(@view.precision_format).to eq('3.1416')
|
||||
end
|
||||
|
||||
it 'returns string padded with zeros' do
|
||||
expect(@view.precision_higher_than_numbers_precision).to eq('3.1400')
|
||||
end
|
||||
|
||||
it 'returns string with no decimal part' do
|
||||
expect(@view.zero_precision).to eq('3')
|
||||
end
|
||||
|
||||
it 'returns string with "." delimiter and "," separator' do
|
||||
expect(@view.euro_format).to eq('1.234,12')
|
||||
end
|
||||
|
||||
it 'raises TypeError when nil is passed' do
|
||||
expect { @view.pass_nil }.to raise_error(TypeError)
|
||||
end
|
||||
|
||||
it 'raises a TypeError when a class name is passed' do
|
||||
expect { @view.pass_class_name }.to raise_error(TypeError)
|
||||
end
|
||||
|
||||
it 'raises a TypeError when a string cannot be coerced into a float' do
|
||||
expect { @view.pass_string }.to raise_error(TypeError)
|
||||
end
|
||||
|
||||
it 'returns string when passed a string that represent an integer' do
|
||||
expect(@view.pass_non_numeric_integer).to eq('1')
|
||||
end
|
||||
|
||||
it 'returns string when passed a string that represent a float' do
|
||||
expect(@view.pass_non_numeric_float).to eq('1.0')
|
||||
end
|
||||
|
||||
it 'returns string when passed BigDecimal' do
|
||||
expect(@view.big_decimal).to eq('0.0001')
|
||||
end
|
||||
|
||||
it 'returns string when passed Complex' do
|
||||
expect(@view.complex).to eq('1.0')
|
||||
end
|
||||
|
||||
it 'returns string when passed a Rational' do
|
||||
expect(@view.rational).to eq('1.0')
|
||||
end
|
||||
|
||||
it 'returns infinity representation' do
|
||||
expect(@view.infinity).to eq('Infinity')
|
||||
end
|
||||
|
||||
it 'returns NaN representation' do
|
||||
expect(@view.nan).to eq('NaN')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
RSpec.describe Hanami::Helpers::RoutingHelper do
|
||||
before do
|
||||
@template = Hanami::View::Template.new('test/fixtures/templates/full_stack/dashboard/index.html.erb', 'utf-8')
|
||||
end
|
||||
|
||||
describe 'when routing constant is defined' do
|
||||
before do
|
||||
@view = FullStack::Views::Dashboard::Index.new(@template, {})
|
||||
end
|
||||
|
||||
it 'has access to routing helper' do
|
||||
expect(@view.routing_helper_path).to eq(%(/dashboard))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when routing constant is not defined' do
|
||||
before do
|
||||
@view = ViewWithoutRoutingHelper.new(@template, {})
|
||||
end
|
||||
|
||||
it 'raises an exception when trying to access routing helper' do
|
||||
expect { @view.routing_helper_path }.to raise_error(NameError)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
RSpec.describe Hanami::Helpers::VERSION do
|
||||
it 'exposes version' do
|
||||
expect(Hanami::Helpers::VERSION).to eq('1.0.0.beta2')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
RSpec.describe 'Escape helper' do
|
||||
before do
|
||||
@user = User.new('MG', 'http://freud.org', %(<span>hello</span>))
|
||||
@actual = Users::Show.render(format: :html, user: @user)
|
||||
end
|
||||
|
||||
it 'renders the title' do
|
||||
expect(@actual).to match(%(<h1>#{@user.name}</h1>))
|
||||
end
|
||||
|
||||
it 'renders the details' do
|
||||
expect(@actual).to match(%(<div id="details">\n<ul>\n<li>\n<a href="#{@user.website}" title="#{@user.name}'s website">website</a>\n</li>\n<li>#{@user.snippet}</li>\n</ul>\n</div>))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,124 @@
|
|||
RSpec.describe 'Form helper' do
|
||||
before do
|
||||
Hanami::View.load!
|
||||
end
|
||||
|
||||
describe 'form with huge ERB block' do
|
||||
before do
|
||||
@params = Hanami::Action::BaseParams.new({})
|
||||
@session = Session.new(_csrf_token: 'l23')
|
||||
@actual = FullStack::Views::Sessions::New.render(format: :html, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form' do
|
||||
expect(@actual).to include(%(<form action="/sessions" method="POST" accept-charset="utf-8" id="session-form" class="form-horizontal">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<div class="form-group">\n<label for="session-email">Email</label>\n<input type="email" name="session[email]" id="session-email" value="" class="form-control" placeholder="Email address">\n</div>\n<div class="form-group">\n<label for="session-password">Password</label>\n<input type="password" name="session[password]" id="session-password" value="" class="form-control">\n</div>\n<input type="hidden" name="session[remember][me]" value="0">\n<input type="checkbox" name="session[remember][me]" id="session-remember-me" value="1">\n<button type="submit" class="btn btn-default">Sign in</button>\n</form>))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'form to create a new resource' do
|
||||
describe 'first page load' do
|
||||
before do
|
||||
@params = DeliveryParams.new({})
|
||||
@session = Session.new(_csrf_token: 'm15')
|
||||
@actual = FullStack::Views::Deliveries::New.render(format: :html, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form' do
|
||||
expect(@actual).to include(%(<form action="/deliveries" method="POST" accept-charset="utf-8" id="delivery-form" class="form-horizontal">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<div class="form-group">\n<label for="delivery-customer">Customer</label>\n<input type="text" name="" id="delivery-customer" value="" class="form-control" placeholder="Customer">\n<input type="hidden" name="delivery[customer_id]" id="delivery-customer-id" value="">\n</div>\n<fieldset>\n<legend>Address</legend>\n<div class="form-group">\n<label for="delivery-address-street">Street</label>\n<input type="text" name="delivery[address][street]" id="delivery-address-street" value="" class="form-control" placeholder="Street">\n</div>\n</fieldset>\n<button type="submit" class="btn btn-default">Create</button>\n</form>))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after a failed form submission' do
|
||||
before do
|
||||
@params = DeliveryParams.new(delivery: { address: { street: '5th Ave' } })
|
||||
@session = Session.new(_csrf_token: 'm15')
|
||||
|
||||
@actual = FullStack::Views::Deliveries::New.render(format: :html, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form with previous values' do
|
||||
expect(@actual).to include(%(<form action="/deliveries" method="POST" accept-charset="utf-8" id="delivery-form" class="form-horizontal">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<div class="form-group">\n<label for="delivery-customer">Customer</label>\n<input type="text" name="" id="delivery-customer" value="" class="form-control" placeholder="Customer">\n<input type="hidden" name="delivery[customer_id]" id="delivery-customer-id" value="">\n</div>\n<fieldset>\n<legend>Address</legend>\n<div class="form-group">\n<label for="delivery-address-street">Street</label>\n<input type="text" name="delivery[address][street]" id="delivery-address-street" value="5th Ave" class="form-control" placeholder="Street">\n</div>\n</fieldset>\n<button type="submit" class="btn btn-default">Create</button>\n</form>))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'form to update a resource' do
|
||||
describe 'first page load' do
|
||||
before do
|
||||
@address = Address.new(street: '5th Ave')
|
||||
@delivery = Delivery.new(id: 1, customer_id: 23, address: @address)
|
||||
@params = DeliveryParams.new({})
|
||||
@session = Session.new(_csrf_token: 's14')
|
||||
@actual = FullStack::Views::Deliveries::Edit.render(format: :html, delivery: @delivery, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form' do
|
||||
expect(@actual).to include(%(<form action="/deliveries/#{@delivery.id}" method="POST" accept-charset="utf-8" id="delivery-form" class="form-horizontal">\n<input type="hidden" name="_method" value="PATCH">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<div class="form-group">\n<label for="delivery-customer">Customer</label>\n<input type="text" name="" id="delivery-customer" value="" class="form-control" placeholder="Customer">\n<input type="hidden" name="delivery[customer_id]" id="delivery-customer-id" value="#{@delivery.customer_id}">\n</div>\n<fieldset>\n<legend>Address</legend>\n<div class="form-group">\n<label for="delivery-address-street">Street</label>\n<input type="text" name="delivery[address][street]" id="delivery-address-street" value="#{@address.street}" class="form-control" placeholder="Street">\n</div>\n</fieldset>\n<button type="submit" class="btn btn-default">Update</button>\n</form>))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after a failed submission' do
|
||||
before do
|
||||
@address = Address.new(street: '5th Ave')
|
||||
@delivery = Delivery.new(id: 1, customer_id: 23, address: @address)
|
||||
@params = DeliveryParams.new(delivery: { address: { street: 'Mulholland Drive' } })
|
||||
@session = Session.new(_csrf_token: 's14')
|
||||
|
||||
@actual = FullStack::Views::Deliveries::Edit.render(format: :html, delivery: @delivery, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form' do
|
||||
expect(@actual).to include(%(<form action="/deliveries/#{@delivery.id}" method="POST" accept-charset="utf-8" id="delivery-form" class="form-horizontal">\n<input type="hidden" name="_method" value="PATCH">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<div class="form-group">\n<label for="delivery-customer">Customer</label>\n<input type="text" name="" id="delivery-customer" value="" class="form-control" placeholder="Customer">\n<input type="hidden" name="delivery[customer_id]" id="delivery-customer-id" value="#{@delivery.customer_id}">\n</div>\n<fieldset>\n<legend>Address</legend>\n<div class="form-group">\n<label for="delivery-address-street">Street</label>\n<input type="text" name="delivery[address][street]" id="delivery-address-street" value="#{@params[:delivery][:address][:street]}" class="form-control" placeholder="Street">\n</div>\n</fieldset>\n<button type="submit" class="btn btn-default">Update</button>\n</form>))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after a failed submission with blank values' do
|
||||
before do
|
||||
@address = Address.new(street: '5th Ave')
|
||||
@delivery = Delivery.new(id: 1, customer_id: 23, address: @address)
|
||||
@params = DeliveryParams.new(delivery: { address: { street: '' } })
|
||||
@session = Session.new(_csrf_token: 's14')
|
||||
|
||||
@actual = FullStack::Views::Deliveries::Edit.render(format: :html, delivery: @delivery, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form' do
|
||||
expect(@actual).to include(%(<form action="/deliveries/#{@delivery.id}" method="POST" accept-charset="utf-8" id="delivery-form" class="form-horizontal">\n<input type="hidden" name="_method" value="PATCH">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<div class="form-group">\n<label for="delivery-customer">Customer</label>\n<input type="text" name="" id="delivery-customer" value="" class="form-control" placeholder="Customer">\n<input type="hidden" name="delivery[customer_id]" id="delivery-customer-id" value="#{@delivery.customer_id}">\n</div>\n<fieldset>\n<legend>Address</legend>\n<div class="form-group">\n<label for="delivery-address-street">Street</label>\n<input type="text" name="delivery[address][street]" id="delivery-address-street" value="" class="form-control" placeholder="Street">\n</div>\n</fieldset>\n<button type="submit" class="btn btn-default">Update</button>\n</form>))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'form with nested structures' do
|
||||
describe 'first page load' do
|
||||
before do
|
||||
@address1 = Address.new(street: '5th Ave')
|
||||
@address2 = Address.new(street: '4th Ave')
|
||||
@bill = Bill.new(id: 1, addresses: [@address1, @address2])
|
||||
@params = BillParams.new({})
|
||||
@session = Session.new(_csrf_token: 's14')
|
||||
|
||||
@actual = FullStack::Views::Bills::Edit.render(format: :html, bill: @bill, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form' do
|
||||
expect(@actual).to include(%(<form action="/bills/#{@bill.id}" method="POST" accept-charset="utf-8" id="bill-form" class="form-horizontal">\n<input type="hidden" name="_method" value="PATCH">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<fieldset>\n<legend>Addresses</legend>\n<div class="form-group">\n<label for="bill-addresses-0-street">Street</label>\n<input type="text" name="bill[addresses][][street]" id="bill-addresses-0-street" value="#{@address1.street}" class="form-control" placeholder="Street" data-funky="id-0">\n</div>\n<div class="form-group">\n<label for="bill-addresses-1-street">Street</label>\n<input type="text" name="bill[addresses][][street]" id="bill-addresses-1-street" value="#{@address2.street}" class="form-control" placeholder="Street" data-funky="id-1">\n</div>\n<label for="bill-ensure-names">Ensure names</label>\n</fieldset>\n<button type="submit" class="btn btn-default">Update</button>\n</form>\n))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after a failed submission' do
|
||||
before do
|
||||
@address1 = Address.new(street: '5th Ave')
|
||||
@address2 = Address.new(street: '4th Ave')
|
||||
@bill = Bill.new(id: 1, addresses: [@address1, @address2])
|
||||
@params = BillParams.new(bill: { addresses: [{ street: 'Mulholland Drive' }, { street: 'Quaint Edge' }] })
|
||||
@session = Session.new(_csrf_token: 's14')
|
||||
|
||||
@actual = FullStack::Views::Bills::Edit.render(format: :html, bill: @bill, params: @params, session: @session)
|
||||
end
|
||||
|
||||
it 'renders the form' do
|
||||
expect(@actual).to include(%(<form action="/bills/#{@bill.id}" method="POST" accept-charset="utf-8" id="bill-form" class="form-horizontal">\n<input type="hidden" name="_method" value="PATCH">\n<input type="hidden" name="_csrf_token" value="#{@session[:_csrf_token]}">\n<fieldset>\n<legend>Addresses</legend>\n<div class="form-group">\n<label for="bill-addresses-0-street">Street</label>\n<input type="text" name="bill[addresses][][street]" id="bill-addresses-0-street" value="#{@params[:bill][:addresses][0][:street]}" class="form-control" placeholder="Street" data-funky="id-0">\n</div>\n<div class="form-group">\n<label for="bill-addresses-1-street">Street</label>\n<input type="text" name="bill[addresses][][street]" id="bill-addresses-1-street" value="#{@params[:bill][:addresses][1][:street]}" class="form-control" placeholder="Street" data-funky="id-1">\n</div>\n<label for="bill-ensure-names">Ensure names</label>\n</fieldset>\n<button type="submit" class="btn btn-default">Update</button>\n</form>\n))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
RSpec.describe 'Html helper' do
|
||||
before do
|
||||
@book = Book.new(title: 'The Work of Art in the Age of Mechanical Reproduction')
|
||||
@actual = Books::Show.render(format: :html, book: @book)
|
||||
end
|
||||
|
||||
it 'renders the generated html' do
|
||||
expect(@actual).to match("<div>\n<h1>The Work of Art in the Age of Mechanical Reproduction</h1>\n</div>")
|
||||
end
|
||||
|
||||
it 'raises an error when referencing an unknown local variable' do
|
||||
expect do
|
||||
Books::Error.render(format: :html, book: @book)
|
||||
end.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
Rspec.describe 'Escape helper' do
|
||||
before do
|
||||
@user = LinkTo.new
|
||||
@actual = LinkTo::Index.render(format: :html)
|
||||
end
|
||||
|
||||
it 'renders the title' do
|
||||
expect(@actual).to match(%(<a href="/">Home</a>))
|
||||
end
|
||||
|
||||
it 'renders relative link' do
|
||||
expect(@actual).to match(%(<a href="relative">Relative</a>))
|
||||
end
|
||||
|
||||
it 'renders link using html content' do
|
||||
expect(@actual).to match(%(<a href="/">\n<p>Home with html content</p>\n</a>))
|
||||
end
|
||||
|
||||
it 'renders link using html content, id and class' do
|
||||
expect(@actual).to match(%(<a id="home__link" class="first" href="/">\n<p>Home with html content, id and class</p>\n</a>))
|
||||
end
|
||||
|
||||
it 'renders link using content' do
|
||||
expect(@actual).to match(%(<a href="http://external.com">External</a>))
|
||||
end
|
||||
|
||||
it 'renders link using html content' do
|
||||
expect(@actual).to match(%(<a href="http://external.com">\n<strong>External with html content</strong>\n</a>))
|
||||
end
|
||||
|
||||
it 'escapes content' do
|
||||
expect(@actual).to match(%(<a href="/"><script>alert('xss')</script></a>))
|
||||
end
|
||||
|
||||
it 'escapes raw block content' do
|
||||
expect(@actual).to match(%(<a href="/">\n<script>alert('xss2')</script>\n</a>))
|
||||
end
|
||||
|
||||
it 'escapes html builder block content' do
|
||||
expect(@actual).to match(%(<a href="/">\n<p><script>alert('xss3')</script></p>\n</a>))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
RSpec.describe 'Number formatting helper' do
|
||||
before do
|
||||
@rendered = FullStack::Views::Cart::Show.render(format: :html, total: 1234.56)
|
||||
end
|
||||
|
||||
it 'formats number' do
|
||||
expect(@rendered).to include('1,234.56')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
Rspec.describe 'Routing helper' do
|
||||
before do
|
||||
@actual = FullStack::Views::Dashboard::Index.render(format: :html)
|
||||
end
|
||||
|
||||
it 'uses helper' do
|
||||
expect(@actual).to include(%(/dashboard))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
if ENV['COVERALL']
|
||||
require 'coveralls'
|
||||
Coveralls.wear!
|
||||
end
|
||||
|
||||
require 'hanami/utils'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.expect_with :rspec do |expectations|
|
||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||
end
|
||||
|
||||
config.mock_with :rspec do |mocks|
|
||||
mocks.verify_partial_doubles = true
|
||||
end
|
||||
|
||||
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||
|
||||
config.filter_run_when_matching :focus
|
||||
config.disable_monkey_patching!
|
||||
|
||||
config.warnings = true
|
||||
|
||||
config.default_formatter = 'doc' if config.files_to_run.one?
|
||||
|
||||
config.profile_examples = 10
|
||||
|
||||
config.order = :random
|
||||
Kernel.srand config.seed
|
||||
end
|
||||
|
||||
$LOAD_PATH.unshift 'lib'
|
||||
require 'hanami/helpers'
|
||||
require_relative './support/fixtures'
|
||||
|
||||
Hanami::View.load!
|
|
@ -0,0 +1,699 @@
|
|||
require 'hanami/view'
|
||||
require 'hanami/controller'
|
||||
require 'hanami/helpers/html_helper'
|
||||
require 'hanami/helpers/escape_helper'
|
||||
require 'dry/struct'
|
||||
|
||||
module Types
|
||||
include Dry::Types.module
|
||||
end
|
||||
|
||||
Store = Struct.new(:code, :label) do
|
||||
def to_ary
|
||||
[label, code]
|
||||
end
|
||||
end
|
||||
|
||||
class Signup < Dry::Struct
|
||||
attribute :password, Types::String.optional
|
||||
end
|
||||
|
||||
class HtmlView
|
||||
include Hanami::Helpers::HtmlHelper
|
||||
|
||||
def empty_div
|
||||
html.div
|
||||
end
|
||||
|
||||
def div_with_string_content
|
||||
html.div('hello world')
|
||||
end
|
||||
|
||||
def div_with_block_content_as_string
|
||||
html.div { 'hola' }
|
||||
end
|
||||
|
||||
def div_with_block_content_with_tag_helper
|
||||
html.div(html.p('inner'))
|
||||
end
|
||||
|
||||
def div_with_block_content_with_nested_calls
|
||||
html.div do
|
||||
span 'hello'
|
||||
end
|
||||
end
|
||||
|
||||
def div_with_block_content_and_multiple_nested_calls
|
||||
html.form(action: '/users', method: 'POST') do
|
||||
div do
|
||||
label 'First name', for: 'user-first-name'
|
||||
input type: 'text', id: 'user-first-name', name: 'user[first_name]', value: 'L'
|
||||
end
|
||||
|
||||
input type: 'submit', value: 'Save changes'
|
||||
end
|
||||
end
|
||||
|
||||
def concatenation_of_multiple_fragments
|
||||
hello = html { div 'Hello' }
|
||||
hanami = html { div 'Hanami' }
|
||||
|
||||
hello + hanami
|
||||
end
|
||||
|
||||
def concatenation_of_multiple_divs
|
||||
html.div('Hello') + html.div('Hanami')
|
||||
end
|
||||
|
||||
def concatenation_of_fragment_and_div
|
||||
html { div 'Hello' } + html.div('Hanami')
|
||||
end
|
||||
|
||||
def fragment_with_block_content
|
||||
html do
|
||||
div 'Hello'
|
||||
div 'Hanami'
|
||||
end
|
||||
end
|
||||
|
||||
def div_with_attr
|
||||
html.div(id: 'container')
|
||||
end
|
||||
|
||||
# RUBY_VERSION >= '2.2'
|
||||
# def div_with_data_attr
|
||||
# html.div('data-where': 'up')
|
||||
# end
|
||||
def div_with_data_attr
|
||||
html.div(:'data-where' => 'up')
|
||||
end
|
||||
|
||||
def div_with_attrs
|
||||
html.div(id: 'content', class: 'filled')
|
||||
end
|
||||
|
||||
def div_with_string_content_and_attrs
|
||||
html.div('ciao', id: 'greeting', class: 'blink')
|
||||
end
|
||||
|
||||
def div_with_block_content_as_string_and_attrs
|
||||
html.div(id: 'sidebar', class: 'blue') { 'bonjour' }
|
||||
end
|
||||
|
||||
def custom_tag
|
||||
html.tag(:custom, 'Foo', id: 'next')
|
||||
end
|
||||
|
||||
def custom_empty_tag
|
||||
html.empty_tag(:xr, id: 'next')
|
||||
end
|
||||
|
||||
def evil_string_content
|
||||
html.div("<script>alert('xss')</script>")
|
||||
end
|
||||
|
||||
def evil_block_content
|
||||
html.div { "<script>alert('xss')</script>" }
|
||||
end
|
||||
|
||||
def evil_tag_helper
|
||||
html.div(html.p("<script>alert('xss')</script>"))
|
||||
end
|
||||
|
||||
def evil_nested_block_content
|
||||
html.div do
|
||||
p "<script>alert('xss')</script>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NumbersView
|
||||
include Hanami::Helpers::NumberFormattingHelper
|
||||
|
||||
def single_digit
|
||||
format_number 1
|
||||
end
|
||||
|
||||
def thousand_digits
|
||||
format_number 1_000
|
||||
end
|
||||
|
||||
def million_digits
|
||||
format_number 1_000_000
|
||||
end
|
||||
|
||||
def zero_point_one
|
||||
format_number 0.1
|
||||
end
|
||||
|
||||
def precision_default_format
|
||||
format_number 3.141592
|
||||
end
|
||||
|
||||
def precision_format
|
||||
format_number 3.141592, precision: 4
|
||||
end
|
||||
|
||||
def precision_higher_than_numbers_precision
|
||||
format_number 3.14, precision: 4
|
||||
end
|
||||
|
||||
def zero_precision
|
||||
format_number 3.14, precision: 0
|
||||
end
|
||||
|
||||
def mixed_digits
|
||||
format_number 5002.007, precision: 3
|
||||
end
|
||||
|
||||
def euro_format
|
||||
format_number 1234.12, delimiter: '.', separator: ','
|
||||
end
|
||||
|
||||
def pass_nil
|
||||
format_number nil
|
||||
end
|
||||
|
||||
def pass_class_name
|
||||
format_number Object
|
||||
end
|
||||
|
||||
def pass_string
|
||||
format_number 'string'
|
||||
end
|
||||
|
||||
def pass_non_numeric_integer
|
||||
format_number '1'
|
||||
end
|
||||
|
||||
def pass_non_numeric_float
|
||||
format_number '1.0'
|
||||
end
|
||||
|
||||
def big_decimal
|
||||
format_number BigDecimal.new('0.0001'), precision: 4
|
||||
end
|
||||
|
||||
def complex
|
||||
format_number Complex(1)
|
||||
end
|
||||
|
||||
def rational
|
||||
format_number Rational(1)
|
||||
end
|
||||
|
||||
def string
|
||||
format_number Rational(1)
|
||||
end
|
||||
|
||||
def infinity
|
||||
format_number Float::INFINITY
|
||||
end
|
||||
|
||||
def nan
|
||||
format_number 0.0 / 0
|
||||
end
|
||||
end
|
||||
|
||||
class EscapeView
|
||||
include Hanami::Helpers::EscapeHelper
|
||||
|
||||
def good_string
|
||||
escape_html 'this is a good string'
|
||||
end
|
||||
|
||||
def evil_string
|
||||
escape_html "<script>alert('xss')</script>"
|
||||
end
|
||||
|
||||
def good_attributes_string
|
||||
"<a title='#{escape_html_attribute('foo')}'>link</a>"
|
||||
end
|
||||
|
||||
def evil_attributes_string
|
||||
"<a title='#{escape_html_attribute('<script>alert(\'xss\')</script>')}'>link</a>"
|
||||
end
|
||||
|
||||
def good_url_string
|
||||
escape_url 'http://hanamirb.org'
|
||||
end
|
||||
|
||||
def evil_url_string
|
||||
escape_url "javascript:alert('xss')"
|
||||
end
|
||||
|
||||
def raw_string
|
||||
raw "<div>I'm a raw string</div>"
|
||||
end
|
||||
|
||||
def html_string_alias
|
||||
h 'this is a good string'
|
||||
end
|
||||
|
||||
def html_attribute_string_alias
|
||||
"<a title='#{ha('foo')}'>link</a>"
|
||||
end
|
||||
|
||||
def url_string_alias
|
||||
hu 'http://hanamirb.org'
|
||||
end
|
||||
end
|
||||
|
||||
class Book < Dry::Struct
|
||||
constructor_type :weak
|
||||
|
||||
attribute :title, Types::String.optional
|
||||
attribute :search_title, Types::String.optional
|
||||
attribute :description, Types::String.optional
|
||||
attribute :author_id, Types::Form::Int.optional
|
||||
attribute :category, Types::String.optional
|
||||
attribute :cover, Types::String.optional
|
||||
attribute :image_cover, Types::String.optional
|
||||
attribute :percent_read, Types::Form::Int.optional
|
||||
attribute :discount_percentage, Types::Form::Int.optional
|
||||
attribute :published_at, Types::String.optional
|
||||
attribute :website, Types::String.optional
|
||||
attribute :publisher_email, Types::String.optional
|
||||
attribute :publisher_telephone, Types::String.optional
|
||||
attribute :release_date, Types::Form::Date.optional
|
||||
attribute :release_hour, Types::String.optional
|
||||
attribute :release_week, Types::String.optional
|
||||
attribute :release_month, Types::Form::Date.optional
|
||||
attribute :store, Types::String.optional
|
||||
end
|
||||
|
||||
User = Struct.new(:name, :website, :snippet)
|
||||
|
||||
module TestView
|
||||
def self.included(view)
|
||||
view.class_eval do
|
||||
include Hanami::View
|
||||
include Hanami::Helpers
|
||||
root __dir__ + '/fixtures/templates'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Books
|
||||
class Show
|
||||
include TestView
|
||||
|
||||
def title_widget
|
||||
html.div do
|
||||
h1 book.title
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Error
|
||||
include TestView
|
||||
|
||||
def error_widget
|
||||
html.div do
|
||||
unknown_local_variable
|
||||
end
|
||||
end
|
||||
|
||||
def render
|
||||
error_widget.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Users
|
||||
class Show
|
||||
include TestView
|
||||
|
||||
def title
|
||||
html.h1(user.name)
|
||||
end
|
||||
|
||||
def details
|
||||
html.div(id: 'details') do
|
||||
ul do
|
||||
li do
|
||||
a('website', href: hu(user.website), title: "#{ha(user.name)}'s website")
|
||||
end
|
||||
|
||||
li raw(user.snippet)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FormHelperView
|
||||
include Hanami::Helpers::FormHelper
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params)
|
||||
@params = _build_params(params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _build_params(params)
|
||||
parameters = params.to_h
|
||||
|
||||
# Randomly use Hanami::Action::BaseParams or the given raw Hash in order to
|
||||
# simulate Hash usage during the test setup unit tests in Hanami projects.
|
||||
if parameters.respond_to?(:dig)
|
||||
[true, false].sample ? Hanami::Action::BaseParams.new(parameters) : parameters
|
||||
else
|
||||
Hanami::Action::BaseParams.new(parameters)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class SessionFormHelperView < FormHelperView
|
||||
def initialize(params, csrf_token)
|
||||
super(params)
|
||||
@csrf_token = csrf_token
|
||||
end
|
||||
|
||||
def session
|
||||
{ _csrf_token: @csrf_token }
|
||||
end
|
||||
end
|
||||
|
||||
class Address
|
||||
attr_reader :street
|
||||
|
||||
def initialize(attributes = {})
|
||||
@street = attributes[:street]
|
||||
end
|
||||
end
|
||||
|
||||
class Delivery
|
||||
attr_reader :id, :customer_id, :address
|
||||
|
||||
def initialize(attributes = {})
|
||||
@id = attributes[:id]
|
||||
@customer_id = attributes[:customer_id]
|
||||
@address = attributes[:address]
|
||||
end
|
||||
end
|
||||
|
||||
class Bill
|
||||
attr_reader :id, :addresses
|
||||
|
||||
def initialize(attributes = {})
|
||||
@id = attributes[:id]
|
||||
@addresses = attributes[:addresses]
|
||||
end
|
||||
end
|
||||
|
||||
class DeliveryParams < Hanami::Action::Params
|
||||
params do
|
||||
required(:delivery).schema do
|
||||
required(:customer_id, :int).filled
|
||||
required(:address).schema do
|
||||
required(:street, :string).filled
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class BillParams < Hanami::Action::Params
|
||||
params do
|
||||
required(:bill).schema do
|
||||
required(:addresses).each do
|
||||
schema do
|
||||
required(:street, :string).filled
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Session
|
||||
def initialize(values)
|
||||
@values = values.to_h
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@values[key]
|
||||
end
|
||||
end
|
||||
|
||||
module FullStack
|
||||
class Routes
|
||||
def path(name)
|
||||
_escape "/#{name}"
|
||||
end
|
||||
|
||||
def sessions_path
|
||||
_escape '/sessions'
|
||||
end
|
||||
|
||||
def deliveries_path
|
||||
_escape '/deliveries'
|
||||
end
|
||||
|
||||
def delivery_path(attrs = {})
|
||||
_escape "/deliveries/#{attrs.fetch(:id)}"
|
||||
end
|
||||
|
||||
def bill_path(attrs = {})
|
||||
_escape "/bills/#{attrs.fetch(:id)}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _escape(string)
|
||||
Hanami::Utils::Escape::SafeString.new(string)
|
||||
end
|
||||
end
|
||||
|
||||
def self.routes
|
||||
Routes.new
|
||||
end
|
||||
|
||||
module Views
|
||||
module Dashboard
|
||||
class Index
|
||||
include TestView
|
||||
root __dir__ + '/fixtures/templates/full_stack'
|
||||
template 'dashboard/index'
|
||||
|
||||
def routing_helper_path
|
||||
routes.path(:dashboard)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Sessions
|
||||
class New
|
||||
include TestView
|
||||
template 'sessions/new'
|
||||
end
|
||||
end
|
||||
|
||||
module Cart
|
||||
class Show
|
||||
include TestView
|
||||
template 'cart/show'
|
||||
|
||||
def total
|
||||
format_number locals[:total]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Deliveries
|
||||
class New
|
||||
include TestView
|
||||
template 'deliveries/new'
|
||||
|
||||
def form
|
||||
Form.new(:delivery, routes.deliveries_path)
|
||||
end
|
||||
|
||||
def submit_label
|
||||
'Create'
|
||||
end
|
||||
end
|
||||
|
||||
class Edit
|
||||
include TestView
|
||||
template 'deliveries/edit'
|
||||
|
||||
def form
|
||||
Form.new(:delivery,
|
||||
routes.delivery_path(id: delivery.id),
|
||||
{ delivery: delivery }, method: :patch)
|
||||
end
|
||||
|
||||
def submit_label
|
||||
'Update'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Bills
|
||||
class Edit
|
||||
include TestView
|
||||
template 'bills/edit'
|
||||
|
||||
def form
|
||||
Form.new(:bill, routes.bill_path(id: bill.id), { bill: bill }, method: :patch)
|
||||
end
|
||||
|
||||
def submit_label
|
||||
'Update'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ViewWithoutRoutingHelper
|
||||
include TestView
|
||||
root __dir__ + '/fixtures/templates/full_stack'
|
||||
template 'dashboard/index'
|
||||
|
||||
def routing_helper_path
|
||||
routes.path(:dashboard)
|
||||
end
|
||||
end
|
||||
|
||||
class LinkTo
|
||||
include Hanami::Helpers::LinkToHelper
|
||||
|
||||
class Index
|
||||
include TestView
|
||||
|
||||
def link_to_home
|
||||
link_to('Home', '/')
|
||||
end
|
||||
|
||||
def link_to_relative
|
||||
link_to('Relative', 'relative')
|
||||
end
|
||||
|
||||
def link_to_home_with_html_content
|
||||
link_to('/') do
|
||||
p 'Home with html content'
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_home_with_html_content_id_and_class
|
||||
link_to('/', id: 'home__link', class: 'first') do
|
||||
p 'Home with html content, id and class'
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_external_url_with_content
|
||||
link_to('External', 'http://external.com')
|
||||
end
|
||||
|
||||
def link_to_external_url_with_html_content
|
||||
link_to('http://external.com') do
|
||||
strong 'External with html content'
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_xss_content
|
||||
link_to(%(<script>alert('xss')</script>), '/')
|
||||
end
|
||||
|
||||
def link_to_xss_raw_content_block
|
||||
link_to('/') do
|
||||
%(<script>alert('xss2')</script>)
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_xss_html_builder_content_block
|
||||
link_to('/') do
|
||||
p %(<script>alert('xss3')</script>)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Routes
|
||||
def self.path(name, id = nil)
|
||||
"/#{name}" << "/#{id}"
|
||||
end
|
||||
end
|
||||
|
||||
def routes
|
||||
Routes
|
||||
end
|
||||
|
||||
def link_to_relative_posts
|
||||
link_to('Posts', 'posts')
|
||||
end
|
||||
|
||||
def link_to_posts
|
||||
link_to('Posts', routes.path(:posts))
|
||||
end
|
||||
|
||||
def link_to_post
|
||||
link_to('Post', routes.path(:post, 1))
|
||||
end
|
||||
|
||||
def link_to_with_class
|
||||
link_to('Post', routes.path(:posts), class: 'first')
|
||||
end
|
||||
|
||||
def link_to_with_id
|
||||
link_to('Post', routes.path(:posts), id: 'posts__link')
|
||||
end
|
||||
|
||||
def link_to_with_html_content
|
||||
link_to(routes.path(:posts)) do
|
||||
strong 'Post'
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_with_content_and_html_content
|
||||
link_to('Post', routes.path(:posts)) do
|
||||
strong 'Post'
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_with_html_content_id_and_class
|
||||
link_to(routes.path(:posts), id: 'posts__link', class: 'first') do
|
||||
strong 'Post'
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_without_args
|
||||
link_to
|
||||
end
|
||||
|
||||
def link_to_without_args_and_empty_block
|
||||
link_to do
|
||||
# this block was left intentionally blank ;)
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_with_only_content
|
||||
link_to 'Post'
|
||||
end
|
||||
|
||||
def link_to_with_content_html_content_id_and_class
|
||||
link_to('Post', routes.path(:posts), id: 'posts__link', class: 'first') do
|
||||
strong 'Post'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HtmlAndLinkTo
|
||||
include Hanami::Helpers::HtmlHelper
|
||||
include Hanami::Helpers::LinkToHelper
|
||||
|
||||
def two_links_to_in_div
|
||||
html.div do
|
||||
link_to('Comments', '/comments') +
|
||||
link_to('Posts', '/posts')
|
||||
end
|
||||
end
|
||||
|
||||
def span_and_link_to_in_div
|
||||
html.div do
|
||||
span('hello') +
|
||||
link_to('Comments', '/comments')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
<%= album_form %>
|
|
@ -0,0 +1,18 @@
|
|||
<%=
|
||||
form_for(form, class: 'form-horizontal') do
|
||||
fieldset do
|
||||
legend 'Addresses'
|
||||
|
||||
fields_for_collection :addresses do |i|
|
||||
div class: 'form-group' do
|
||||
label :street
|
||||
input_text :street, class: 'form-control', placeholder: 'Street', 'data-funky': "id-#{i}"
|
||||
end
|
||||
end
|
||||
|
||||
label :ensure_names
|
||||
end
|
||||
|
||||
submit submit_label, class: 'btn btn-default'
|
||||
end
|
||||
%>
|
|
@ -0,0 +1,3 @@
|
|||
<div id="content">
|
||||
<%= title_widget %>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
<%=
|
||||
form_for(form, class: 'form-horizontal') do
|
||||
div class: 'form-group' do
|
||||
label :customer
|
||||
input_text :customer, class: 'form-control', placeholder: 'Customer', name: nil
|
||||
|
||||
hidden_field :customer_id
|
||||
end
|
||||
|
||||
fieldset do
|
||||
legend 'Address'
|
||||
|
||||
fields_for :address do
|
||||
div class: 'form-group' do
|
||||
label :street
|
||||
input_text :street, class: 'form-control', placeholder: 'Street'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
submit submit_label, class: 'btn btn-default'
|
||||
end
|
||||
%>
|
|
@ -0,0 +1 @@
|
|||
<%= render partial: 'test/fixtures/templates/deliveries/form' %>
|
|
@ -0,0 +1 @@
|
|||
<%= render partial: 'test/fixtures/templates/deliveries/form' %>
|
|
@ -0,0 +1 @@
|
|||
<%= total %>
|
|
@ -0,0 +1 @@
|
|||
<%= routes.path(:dashboard) %>
|
|
@ -0,0 +1,9 @@
|
|||
<%= link_to_home %>
|
||||
<%= link_to_relative %>
|
||||
<%= link_to_home_with_html_content %>
|
||||
<%= link_to_home_with_html_content_id_and_class %>
|
||||
<%= link_to_external_url_with_content %>
|
||||
<%= link_to_external_url_with_html_content %>
|
||||
<%= link_to_xss_content %>
|
||||
<%= link_to_xss_raw_content_block %>
|
||||
<%= link_to_xss_html_builder_content_block %>
|
|
@ -0,0 +1,19 @@
|
|||
<%=
|
||||
form_for :session, routes.sessions_path, class: 'form-horizontal' do
|
||||
div class: 'form-group' do
|
||||
label :email
|
||||
email_field :email, class: 'form-control', placeholder: 'Email address'
|
||||
end
|
||||
|
||||
div class: 'form-group' do
|
||||
label :password
|
||||
password_field :password, class: 'form-control'
|
||||
end
|
||||
|
||||
fields_for :remember do
|
||||
check_box :me
|
||||
end
|
||||
|
||||
submit 'Sign in', class: 'btn btn-default'
|
||||
end
|
||||
%>
|
|
@ -0,0 +1,2 @@
|
|||
<%= title %>
|
||||
<%= details %>
|
|
@ -55,7 +55,10 @@ class HtmlView
|
|||
end
|
||||
|
||||
def concatenation_of_multiple_fragments
|
||||
html { div 'Hello' } + html { div 'Hanami' }
|
||||
hello = html { div 'Hello' }
|
||||
hanami = html { div 'Hanami' }
|
||||
|
||||
hello + hanami
|
||||
end
|
||||
|
||||
def concatenation_of_multiple_divs
|
||||
|
|
Loading…
Reference in New Issue