├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Changes.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib └── rspec │ ├── hanami.rb │ └── hanami │ ├── form_parser.rb │ ├── form_presenter.rb │ ├── match_status.rb │ ├── match_type.rb │ ├── matchers.rb │ ├── matchers │ ├── be_status.rb │ ├── form_matchers.rb │ ├── have_http_status.rb │ ├── include_json.rb │ ├── match_entity_schema.rb │ ├── match_in_body.rb │ └── redirect_to.rb │ ├── request_helpers.rb │ └── version.rb ├── rspec-hanami.gemspec └── spec ├── rspec ├── hanami │ ├── form_parser_spec.rb │ ├── form_presenter_spec.rb │ ├── match_status_spec.rb │ ├── matchers │ │ ├── be_status_spec.rb │ │ ├── form_matchers_spec.rb │ │ ├── have_http_status_spec.rb │ │ ├── include_json_spec.rb │ │ ├── match_entity_schema_spec.rb │ │ ├── match_in_body_spec.rb │ │ └── redirect_to_spec.rb │ └── request_spec.rb └── hanami_spec.rb ├── spec_helper.rb └── support ├── actions.rb ├── entities.rb ├── templates └── main.html.erb └── views.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /spec/examples.txt 10 | /tmp/ 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | cache: bundler 4 | install: true 5 | env: 6 | global: 7 | - JRUBY_OPTS=--dev 8 | script: 9 | - 'if [[ "$TRAVIS_RUBY_VERSION" =~ "jruby" ]]; then rvm get head && rvm reload && rvm use --install $TRAVIS_RUBY_VERSION && gem install bundler; fi' 10 | - 'bundle install' 11 | rvm: 12 | - 2.3.1 13 | - jruby-9.0.5.0 14 | - jruby-head 15 | - ruby-head 16 | 17 | matrix: 18 | allow_failures: 19 | - rvm: ruby-head 20 | - rvm: jruby-head 21 | - rvm: jruby-9.0.5.0 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at antondavydov.o@gmail.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 45 | version 1.3.0, available at 46 | [http://contributor-covenant.org/version/1/3/0/][version] 47 | 48 | [homepage]: http://contributor-covenant.org 49 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /Changes.md: -------------------------------------------------------------------------------- 1 | # Rspec Hanami Changes 2 | 3 | HEAD 4 | ----------- 5 | 6 | - Add request helpers (`get`, `post`, etc) [#5] 7 | - Add new matcher `match_in_body` [#3] 8 | 9 | 0.2.0 10 | ----------- 11 | 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rspec-hanami.gemspec 4 | gemspec 5 | 6 | gem 'coveralls', require: false 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Anton Davydov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSpec::Hanami 2 | 3 | [![Build Status](https://travis-ci.org/davydovanton/rspec-hanami.svg?branch=master)](https://travis-ci.org/davydovanton/rspec-hanami) [![Coverage Status](https://coveralls.io/repos/github/davydovanton/rspec-hanami/badge.svg?branch=master)](https://coveralls.io/github/davydovanton/rspec-hanami?branch=master) 4 | 5 | **rspec-hanami** is a testing framework for [hanami](http://hanamirb.org) 6 | 7 | ## Installation 8 | Add this line to your application's Gemfile: 9 | 10 | ```ruby 11 | group :test do 12 | gem 'rspec-hanami' 13 | end 14 | ``` 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install rspec-hanami 23 | 24 | After that require gem to `spec_helper.rb` and include matchers to rspec: 25 | 26 | ```ruby 27 | require 'rspec/hanami' 28 | 29 | RSpec.configure do |config| 30 | config.include RSpec::Hanami::Matchers 31 | 32 | # ... 33 | end 34 | ``` 35 | 36 | ### Capybara 37 | Check your `spec/features_helper.rb` and `spec/support/Capybara.rb` files. If you find something like this: 38 | 39 | ```ruby 40 | Capybara.app = Hanami::Container.new 41 | # or 42 | Capybara.app = Hanami::App.new 43 | ``` 44 | 45 | Please change this line to: 46 | ```ruby 47 | Capybara.app = ::Hanami::Container.new 48 | # or 49 | Capybara.app = ::Hanami::App.new 50 | ``` 51 | 52 | For more information see [this issue](https://github.com/davydovanton/rspec-hanami/issues/1) 53 | 54 | ## Supported matchers 55 | ### Request helpers 56 | You can use familiar request helpers like `#get`, `#post`, etc. 57 | These methods make full hanami app request and return env (array with 3 elements). 58 | 59 | For using these helpers include `RSpec::Hanami::RequestHelpers` to your `spec_helper.rb` file: 60 | 61 | ```ruby 62 | config.include RSpec::Hanami::RequestHelpers 63 | ``` 64 | 65 | After that you can call any method: 66 | ```ruby 67 | it { expect(get('/')).to be_success } 68 | it { expect(post('/tasks')).to redirect_to('/tasks') } 69 | ``` 70 | 71 | ### Controller Specs 72 | #### `have_http_status` 73 | Passes if `response` has a matching HTTP status code. 74 | 75 | The following symbolic status codes are allowed: 76 | - `:error` 77 | - `:missing` 78 | - `:redirect` 79 | - `:success` 80 | - `Rack::Utils::SYMBOL_TO_STATUS_CODE` 81 | 82 | ``` ruby 83 | response = action.call(params) 84 | expect(response).to have_http_status(404) 85 | expect(response).to have_http_status(:created) 86 | expect(response).to have_http_status(:success) 87 | expect(response).to have_http_status(:error) 88 | expect(response).to have_http_status(:missing) 89 | expect(response).to have_http_status(:redirect) 90 | ``` 91 | 92 | #### `be_success` 93 | Passes if `response` has a not 4xx and 5xx error code. 94 | 95 | ``` ruby 96 | response = action.call(params) 97 | expect(response).to be_success 98 | ```` 99 | 100 | #### `redirect_to` 101 | Passes if `response` has a redirect to special url 102 | 103 | ``` ruby 104 | response = action.call(params) 105 | expect(response).to redirect_to('site.com') 106 | ``` 107 | 108 | #### `match_in_body` 109 | Passes if `body` matches with argument 110 | 111 | ``` ruby 112 | response = action.call(params) 113 | expect(response).to match_in_body('Tittle') 114 | expect(response).to match_in_body(/Tittle\s\d+/) 115 | ``` 116 | 117 | #### `include_json` 118 | Passes if `json` string in the body matches with hash arg 119 | 120 | ``` ruby 121 | response = action.call(params) 122 | expect(response).to include_json(name: 'Anton') 123 | expect(response).to include_json(user: { name: 'Anton }) 124 | ``` 125 | 126 | ### Views Specs 127 | #### `have_form_action` 128 | Passes if form object has an action 129 | 130 | ``` ruby 131 | expect(view.form).to have_form_action('/users') 132 | expect(view.form).to_not have_form_action('/books') 133 | ``` 134 | 135 | #### `have_method` 136 | Passes if form object has a method 137 | 138 | ``` ruby 139 | expect(view.form).to have_method('POST') 140 | expect(view.form).to have_method(:post) 141 | expect(view.form).to_not have_method(:put) 142 | ``` 143 | 144 | #### `have_form_field` 145 | Passes if form object has a field with wanted params 146 | 147 | ``` ruby 148 | expect(view.form).to have_form_field(node: 'input', type: 'text', id: 'user-first-name') 149 | ``` 150 | 151 | ### Entity specs 152 | Passes if argument type has a matching with type. 153 | You can use `Hanami::Entity::Types` for compare. 154 | 155 | #### `have_attribute` 156 | Passes if `:name` has `Types::String` type: 157 | 158 | ``` ruby 159 | it { expect(User).to have_attribute(:name, Types::String) } 160 | ``` 161 | 162 | #### `have_attributes` 163 | Passes if `:name` has `Types::String` type and `:age` has `Types::Int` type: 164 | 165 | ``` ruby 166 | it { expect(User).to have_attributes(name: Types::String, age: Types::Int) } 167 | ``` 168 | 169 | ## Also see 170 | 171 | * 172 | * 173 | * 174 | * 175 | 176 | ## Feature Requests & Bugs 177 | 178 | See 179 | 180 | ## License 181 | 182 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 183 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList['test/**/*_test.rb'] 8 | end 9 | 10 | task default: :spec 11 | desc 'run Rspec specs' 12 | task :spec do 13 | sh 'rspec spec' 14 | end 15 | -------------------------------------------------------------------------------- /lib/rspec/hanami.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/hanami/version' 2 | require 'rspec/hanami/request_helpers' 3 | 4 | module RSpec 5 | module Hanami 6 | autoload :Matchers, 'rspec/hanami/matchers' 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rspec/hanami/form_parser.rb: -------------------------------------------------------------------------------- 1 | require 'hanami/utils/hash' 2 | 3 | module RSpec 4 | module Hanami 5 | class FormParser 6 | def call(html) 7 | meta_list = select_inputs(html).map! { |input| input_data_hash(input) } 8 | meta_list.map! { |hash| ::Hanami::Utils::Hash.new(hash).symbolize! } 9 | end 10 | 11 | private 12 | 13 | def select_inputs(html) 14 | html.scan(/|||||||/) 15 | end 16 | 17 | def input_data_hash(input) 18 | # trim all '<' and '>' chars. After that I add data 19 | # attr 'node' for first element (html tag name). And 20 | # in the end I split string on key-value pairs, trim '"' 21 | # and split key-value to key and value array. 22 | Hash[ 23 | input 24 | .tr('<', '') 25 | .tr('>', '') 26 | .prepend('node=') 27 | .split(' ') 28 | .map! { |key_value| key_value.tr('"', '').split('=') } 29 | ] 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rspec/hanami/form_presenter.rb: -------------------------------------------------------------------------------- 1 | require 'hanami/utils/hash' 2 | 3 | module RSpec 4 | module Hanami 5 | class FormPresenter 6 | def call(form_data) 7 | stringify = form_data.map(&:to_h).map(&:to_s) 8 | indentify = stringify.map.with_index do |value, index| 9 | value = " " + value if index > 0 10 | value = " " + value if value.match(/option/) 11 | value 12 | end 13 | indentify.join("\n") 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rspec/hanami/match_status.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | class MatchStatus 4 | def call(actual, expected) 5 | case expected 6 | when Numeric 7 | actual == expected 8 | when Symbol 9 | chec_symbol_status(actual, expected) 10 | else 11 | false 12 | end 13 | end 14 | 15 | private 16 | 17 | TYPE_CODES = { 18 | error: /(4|5)../, 19 | success: /2../, 20 | missing: /404/, 21 | redirect: /3../, 22 | }.freeze 23 | 24 | def chec_symbol_status(actual, expected) 25 | status = TYPE_CODES[expected] 26 | 27 | if status 28 | !!actual.to_s.match(status) 29 | else 30 | actual == Rack::Utils::SYMBOL_TO_STATUS_CODE[expected] 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/rspec/hanami/match_type.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | class MatchType 4 | attr_reader :actual, :expected 5 | 6 | def initialize(actual, expected) 7 | @actual = actual 8 | @expected = expected 9 | end 10 | 11 | def call 12 | match_types? 13 | end 14 | 15 | private 16 | 17 | def match_types? 18 | return match_if_sum? if expected.class == Dry::Types::Sum 19 | 20 | actual.is_a?(expected.primitive) 21 | end 22 | 23 | def match_if_sum? 24 | expected.primitive?(actual) 25 | end 26 | end 27 | end 28 | end 29 | 30 | # Syntactic shortcut to reference Hanami::Entity::Types in custom matchers 31 | # 32 | Types = ::Hanami::Entity::Types 33 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/core/warnings' 2 | require 'rspec/expectations' 3 | require 'rspec/hanami/form_parser' 4 | require 'rspec/hanami/form_presenter' 5 | require 'rspec/hanami/match_status' 6 | require 'rspec/hanami/match_type' 7 | require 'rspec/hanami/matchers/have_http_status' 8 | require 'rspec/hanami/matchers/match_in_body' 9 | require 'rspec/hanami/matchers/be_status' 10 | require 'rspec/hanami/matchers/form_matchers' 11 | require 'rspec/hanami/matchers/redirect_to' 12 | require 'rspec/hanami/matchers/match_entity_schema' 13 | require 'rspec/hanami/matchers/include_json' 14 | 15 | module RSpec 16 | module Hanami 17 | module Matchers 18 | extend ::RSpec::Matchers::DSL 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers/be_status.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | module Matchers 4 | extend ::RSpec::Matchers::DSL 5 | 6 | # @api public 7 | # Passes if `response` has a not 4xx and 5xx error code. 8 | # 9 | # @example Accepts numeric and symbol statuses 10 | # response = action.call(params) 11 | # expect(response).to be_success 12 | # 13 | matcher :be_success do 14 | attr_reader :actual, :object 15 | 16 | description { "have response success" } 17 | match do |object| 18 | @object = object 19 | @actual = object.first 20 | !RSpec::Hanami::MatchStatus.new.call(actual, :error) 21 | end 22 | 23 | failure_message { |actual| "expect #{object} to have status success" } 24 | diffable 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers/form_matchers.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | module Matchers 4 | extend ::RSpec::Matchers::DSL 5 | 6 | # @api public 7 | # Passes if form object has a action 8 | # 9 | # @example Accepts numeric and symbol statuses 10 | # 11 | # expect(view.form).to have_action('/users') 12 | # 13 | matcher :have_action do |action| 14 | attr_reader :form_spec 15 | 16 | description { "have correct action param" } 17 | match do |form| 18 | form_data = RSpec::Hanami::FormParser.new.call(form.to_s)[0..0] 19 | @form_spec = RSpec::Hanami::FormPresenter.new.call(form_data) 20 | attributes = form.instance_variable_get('@attributes') 21 | 22 | attributes[:action] == action 23 | end 24 | 25 | failure_message { |actual| "expect\n#{form_spec}\n\nto have\n\n{:action=>\"#{action}\"} param" } 26 | # diffable 27 | end 28 | 29 | # @api public 30 | # Passes if form object has a method 31 | # 32 | # @example Accepts numeric and symbol statuses 33 | # 34 | # expect(view.form).to have_action('/users') 35 | # 36 | matcher :have_method do |method| 37 | attr_reader :form_spec 38 | 39 | description { "have correct method param" } 40 | match do |form| 41 | form_data = RSpec::Hanami::FormParser.new.call(form.to_s)[0..0] 42 | @form_spec = RSpec::Hanami::FormPresenter.new.call(form_data) 43 | attributes = form.instance_variable_get('@attributes') 44 | 45 | attributes[:method].downcase.to_sym == method.downcase.to_sym 46 | end 47 | 48 | failure_message { |actual| "expect\n#{form_spec}\n\nto have\n\n{:method=>\"#{method}\"} param" } 49 | # diffable 50 | end 51 | 52 | # @api public 53 | # Passes if form object has a field with wanted params 54 | # 55 | # @example Accepts numeric and symbol statuses 56 | # 57 | # expect(view.form).to have_field(node: 'input', type: 'text', id: 'user-first-name') 58 | # 59 | matcher :have_field do |params| 60 | require 'hanami/utils/hash' 61 | attr_reader :params, :form_spec 62 | 63 | description { "have field with params" } 64 | match do |form| 65 | @params = ::Hanami::Utils::Hash.new(params).symbolize! 66 | form_data = RSpec::Hanami::FormParser.new.call(form.to_s) 67 | @form_spec = RSpec::Hanami::FormPresenter.new.call(form_data) 68 | 69 | form_data.any? do |input| 70 | input.merge(params) == params.merge(input) 71 | end 72 | end 73 | 74 | failure_message { |actual| "expect\n#{form_spec}\n\nto have field\n\n#{params}" } 75 | # diffable 76 | end 77 | 78 | # @api public 79 | # Alias for: have_action 80 | # 81 | alias have_form_action have_action 82 | 83 | # @api public 84 | # Alias for: have_method 85 | # 86 | alias have_form_method have_method 87 | 88 | # @api public 89 | # Alias for: have_field 90 | # 91 | alias have_form_field have_field 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers/have_http_status.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | module Matchers 4 | extend ::RSpec::Matchers::DSL 5 | 6 | # @api public 7 | # Passes if `response` has a matching HTTP status code. 8 | # 9 | # The following symbolic status codes are allowed: 10 | # - `:error` 11 | # - `:missing` 12 | # - `:redirect` 13 | # - `:success` 14 | # 15 | # @example Accepts numeric and symbol statuses 16 | # response = action.call(params) 17 | # expect(response).to have_http_status(404) 18 | # expect(response).to have_http_status(:created) 19 | # expect(response).to have_http_status(:success) 20 | # expect(response).to have_http_status(:error) 21 | # expect(response).to have_http_status(:missing) 22 | # expect(response).to have_http_status(:redirect) 23 | # 24 | matcher :have_http_status do |status| 25 | attr_reader :actual, :object 26 | 27 | description { "have response #{status}" } 28 | match do |object| 29 | @object = object 30 | @actual = object.first 31 | RSpec::Hanami::MatchStatus.new.call(actual, status) 32 | end 33 | 34 | failure_message { |actual| "expect #{object} to have status #{status}" } 35 | diffable 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers/include_json.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module RSpec 4 | module Hanami 5 | module Matchers 6 | extend ::RSpec::Matchers::DSL 7 | 8 | # @api public 9 | # Passes if `json` string in the body matches with hash arg 10 | # 11 | # 12 | # @example Accepts numeric and symbol statuses 13 | # response = action.call(params) 14 | # expect(response).to include_json(name: 'Anton') 15 | # expect(response).to include_json(user: { name: 'Anton }) 16 | # 17 | matcher :include_json do |json_object| 18 | attr_reader :actual, :object 19 | 20 | description { "include json #{json_object.to_json}" } 21 | 22 | match do |object| 23 | @object = object 24 | @actual = JSON.parse(object.last.last, symbolize_names: true) 25 | actual == json_object 26 | end 27 | 28 | failure_message { |actual| "expect #{object} to include #{json_object} json" } 29 | diffable 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers/match_entity_schema.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | module Matchers 4 | extend ::RSpec::Matchers::DSL 5 | 6 | # @api public 7 | # Passes if `parameter` has an passed attribute of expected type 8 | # Hanami::Entity::Types are allowed 9 | # 10 | # expect(User).to have_attribute(:name, Type::String) 11 | # 12 | matcher :have_attribute do |parameter, type| 13 | attr_reader :expected_type 14 | 15 | description { "have #{expected_type.name} type" } 16 | match do |object| 17 | @attributes = object.instance_variable_get(:@attributes) 18 | @actual = @attributes[parameter] 19 | @expected_type = type 20 | 21 | RSpec::Hanami::MatchType.new(actual, expected_type).call 22 | end 23 | 24 | failure_message do |actual| 25 | "expected that #{actual.class} should match with #{expected_type.name}"\ 26 | "\nDiff: #{differ.diff_as_string(actual.class.to_s, expected_type.name)}" 27 | end 28 | 29 | failure_message_when_negated do |actual| 30 | "expected that #{actual.class} should not match with #{expected_type.name}" 31 | end 32 | 33 | def differ 34 | RSpec::Support::Differ.new( 35 | color: RSpec::Matchers.configuration.color? 36 | ) 37 | end 38 | end 39 | 40 | # @api public 41 | # Passes if all `parameters` has an passed attribute of expected types. 42 | # Hanami::Entity::Types are allowed 43 | # 44 | # expect(User).to have_attributes(name: Type::String, ...) 45 | # 46 | matcher :have_attributes do |**parameters| 47 | attr_reader :attributes, :actual_types, :expected_types 48 | 49 | description { "have #{expected_types} types" } 50 | match do |object| 51 | @attributes = object.instance_variable_get(:@attributes) 52 | @actual_types = attributes.map { |_attr, type| type.class }.join(', ') 53 | @expected_types = parameters.map { |_attr, type| type.name }.join(', ') 54 | 55 | parameters.all? do |attribute, type| 56 | actual = attributes[attribute] 57 | RSpec::Hanami::MatchType.new(actual, type).call 58 | end 59 | end 60 | 61 | failure_message do |_actual| 62 | "expected that #{actual_types} should match with #{expected_types}"\ 63 | "\nDiff: #{differ.diff_as_string(actual_types, expected_types)}" 64 | end 65 | 66 | failure_message_when_negated do |_actual| 67 | "expected that #{actual_types} should not match with #{expected_types}" 68 | end 69 | 70 | def differ 71 | RSpec::Support::Differ.new( 72 | color: RSpec::Matchers.configuration.color? 73 | ) 74 | end 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers/match_in_body.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | module Matchers 4 | extend ::RSpec::Matchers::DSL 5 | 6 | # @api public 7 | # Passes if `response` has a not 4xx and 5xx error code. 8 | # 9 | # @example Accepts numeric and symbol statuses 10 | # response = action.call(params) 11 | # expect(response).to be_success 12 | # 13 | matcher :match_in_body do |regexp| 14 | attr_reader :actual, :object 15 | 16 | description { "match #{regexp} in body" } 17 | match do |object| 18 | @object = object 19 | @actual = object.last.first 20 | !!actual.match(Regexp.new(regexp)) 21 | end 22 | 23 | failure_message { |actual| "expect #{object} to have text #{regexp} in body" } 24 | diffable 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rspec/hanami/matchers/redirect_to.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | module Matchers 4 | extend ::RSpec::Matchers::DSL 5 | 6 | # @api public 7 | # Passes if `response` has a redirect to special url 8 | # 9 | # @example Accepts numeric and symbol statuses 10 | # response = action.call(params) 11 | # expect(response).to redirect_to('site.com') 12 | # 13 | matcher :redirect_to do |url| 14 | attr_reader :actual, :object, :location 15 | 16 | description { "have redirect to url" } 17 | match do |object| 18 | @object = object 19 | @location = object[1]['Location'] 20 | @actual = object.first 21 | RSpec::Hanami::MatchStatus.new.call(actual, :redirect) && 22 | location == url 23 | end 24 | 25 | failure_message { |actual| "expect #{object} to redirect #{url}" } 26 | diffable 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/rspec/hanami/request_helpers.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | module RequestHelpers 4 | class Request 5 | def initialize(method, path, options) 6 | @path, @query_string = path.split('?', 2) 7 | @method = method 8 | @params = options[:params] 9 | @headers = options[:headers] || {} 10 | end 11 | 12 | def env 13 | default_env.tap do |env| 14 | env['PATH_INFO'] = @path 15 | env['REQUEST_METHOD'] = @method 16 | env['QUERY_STRING'] = "?#{@query_string}" 17 | # TODO: Doesn't work correctly 18 | # Need to use something like this: 19 | # ::Rack::Lint::InputWrapper.new(StringIO.new(params.env['rack.input'].read)) 20 | env['rack.input'] = StringIO.new(@params.to_json) if @params 21 | # also, we need to use rack-test here instead self written stuff 22 | 23 | @headers.each do |key, value| 24 | rack_name = key.to_s.upcase.tr('-', '_') 25 | env["HTTP_#{rack_name}"] = value 26 | end 27 | if env.key?("HTTP_CONTENT_TYPE") 28 | env["CONTENT_TYPE"] = env.delete("HTTP_CONTENT_TYPE") 29 | end 30 | end 31 | end 32 | 33 | def default_env 34 | { 35 | 'SCRIPT_NAME' => '', 36 | 'SERVER_NAME' => 'localhost', 37 | 'SERVER_PORT' => '800613', 38 | 'rack.version' => [1, 3], 39 | 'rack.url_scheme' => 'http', 40 | 'rack.input' => StringIO.new, 41 | 'rack.errors' => StringIO.new, 42 | 'rack.multithread' => false, 43 | 'rack.multiprocess' => false, 44 | 'rack.run_once' => false, 45 | 'rack.hijack?' => false 46 | } 47 | end 48 | end 49 | 50 | def self.included(klass) 51 | klass.class_eval do 52 | attr_reader :response 53 | end 54 | end 55 | 56 | def request(request) 57 | @response = ::Hanami.app.call(request.env) 58 | end 59 | 60 | def get(path, options = {}) 61 | request(Request.new('GET', path, options)) 62 | end 63 | 64 | def post(path, options = {}) 65 | request(Request.new('POST', path, options)) 66 | end 67 | 68 | def patch(path, options = {}) 69 | request(Request.new('PATCH', path, options)) 70 | end 71 | 72 | def put(path, options = {}) 73 | request(Request.new('PUT', path, options)) 74 | end 75 | 76 | def delete(path, options = {}) 77 | request(Request.new('DELETE', path, options)) 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/rspec/hanami/version.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Hanami 3 | VERSION = "0.4.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /rspec-hanami.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'rspec/hanami/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "rspec-hanami" 8 | spec.version = RSpec::Hanami::VERSION 9 | spec.authors = ["Anton Davydov"] 10 | spec.email = ["antondavydov.o@gmail.com"] 11 | 12 | spec.summary = %q{RSpec Matchers for Hanami} 13 | spec.description = %q{RSpec Matchers for Hanami} 14 | spec.homepage = "https://github.com/davydovanton/rspec-hanami" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.bindir = "exe" 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_dependency "rspec" 23 | spec.add_dependency "hanami", "~> 1.0" 24 | spec.add_dependency "hanami-model" 25 | 26 | spec.add_development_dependency "bundler", "~> 1.11" 27 | spec.add_development_dependency "rake", "~> 10.0" 28 | spec.add_development_dependency "minitest", "~> 5.0" 29 | end 30 | -------------------------------------------------------------------------------- /spec/rspec/hanami/form_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe RSpec::Hanami::FormParser do 5 | let(:parser) { RSpec::Hanami::FormParser.new } 6 | let(:form_html_string) do 7 | <<~FORM 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 |
24 | FORM 25 | end 26 | 27 | describe '#call' do 28 | it 'parses form html string to inputs hash list' do 29 | result = parser.(form_html_string) 30 | expect(result.count).to eq 13 31 | expect(result[2]).to eq(node: "input", type: "text", id: "user-first-name", name: "user[first_name]", value: "L") 32 | expect(result.map{ |i| i[:node] }).to eq(%w[form label input input input textarea datalist option option select option option button]) 33 | expect(result.map{ |i| i[:type] }).to eq([nil, nil, 'text', 'hidden', 'checkbox', nil, nil, nil, nil, nil, nil, nil, "submit"]) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/rspec/hanami/form_presenter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe RSpec::Hanami::FormPresenter do 5 | let(:form_html_string) { 6 | <<~FORM 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 |
23 | FORM 24 | } 25 | let(:form_data) { RSpec::Hanami::FormParser.new.(form_html_string) } 26 | let(:form_presenter) { RSpec::Hanami::FormPresenter.new } 27 | let(:expectation) { 28 | <<~EXP.chomp 29 | {:node=>"form", :action=>"/users", :method=>"POST", :"accept-charset"=>"utf-8", :id=>"user-form"} 30 | {:node=>"label", :for=>"password-reset-email"} 31 | {:node=>"input", :type=>"text", :id=>"user-first-name", :name=>"user[first_name]", :value=>"L"} 32 | {:node=>"input", :type=>"hidden", :name=>"user[free_shipping]", :value=>"0"} 33 | {:node=>"input", :type=>"checkbox", :name=>"user[free_shipping]", :id=>"user-free-shipping", :value=>"1"} 34 | {:node=>"textarea", :name=>"user[description]", :id=>"desc"} 35 | {:node=>"datalist", :id=>"places"} 36 | {:node=>"option", :value=>"Moscow"} 37 | {:node=>"option", :value=>"Rome"} 38 | {:node=>"select", :name=>"password_reset[places]", :id=>"password-reset-places"} 39 | {:node=>"option", :value=>"it"} 40 | {:node=>"option", :value=>"us"} 41 | {:node=>"button", :type=>"submit"} 42 | EXP 43 | } 44 | 45 | describe '#call' do 46 | it 'presents form_data in a nice way' do 47 | result = form_presenter.(form_data) 48 | 49 | expect(result).to eq expectation 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/rspec/hanami/match_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/match_status' 3 | 4 | RSpec.describe RSpec::Hanami::MatchStatus do 5 | SUCCESS_STATUSES = [200, 201, 202, 203, 204] 6 | ERROR_STATUSES = [400, 401, 402, 403, 404] 7 | SERVER_ERROR_STATUSES = [500, 501, 502, 503, 502, 503] 8 | REDIRECTION_STATUSES = [301, 302, 303, 304] 9 | 10 | let(:service) { RSpec::Hanami::MatchStatus.new } 11 | 12 | describe 'when expecting symbol status' do 13 | context 'for success statuses' do 14 | it 'match status code and symbol' do 15 | SUCCESS_STATUSES.each do |status| 16 | expect(service.(status, :success)).to be true 17 | expect(service.(status, :redirect)).to be false 18 | expect(service.(status, :missing)).to be false 19 | expect(service.(status, :error)).to be false 20 | end 21 | end 22 | end 23 | 24 | context 'for redirection statuses' do 25 | it 'match status code and symbol' do 26 | REDIRECTION_STATUSES.each do |status| 27 | expect(service.(status, :success)).to be false 28 | expect(service.(status, :redirect)).to be true 29 | expect(service.(status, :missing)).to be false 30 | expect(service.(status, :error)).to be false 31 | end 32 | end 33 | end 34 | 35 | context 'for error statuses' do 36 | it 'match status code and symbol' do 37 | ERROR_STATUSES.each do |status| 38 | expect(service.(status, :success)).to be false 39 | expect(service.(status, :redirect)).to be false 40 | expect(service.(status, :error)).to be true 41 | end 42 | end 43 | end 44 | 45 | context 'for missing status' do 46 | it 'match status code and symbol' do 47 | expect(service.(404, :success)).to be false 48 | expect(service.(404, :redirect)).to be false 49 | expect(service.(404, :missing)).to be true 50 | expect(service.(404, :error)).to be true 51 | end 52 | end 53 | 54 | context 'for server error statuses' do 55 | it 'match status code and symbol' do 56 | SERVER_ERROR_STATUSES.each do |status| 57 | expect(service.(status, :success)).to be false 58 | expect(service.(status, :redirect)).to be false 59 | expect(service.(status, :missing)).to be false 60 | expect(service.(status, :error)).to be true 61 | end 62 | end 63 | end 64 | end 65 | 66 | describe 'when expecting rack symbol status' do 67 | Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| 68 | it "returns true for #{status.inspect} status" do 69 | expect(service.(code, status)).to be true 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/rspec/hanami/matchers/be_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe "be matchers" do 5 | include RSpec::Hanami::Matchers 6 | 7 | let(:params) { {} } 8 | 9 | describe '#be_success' do 10 | context 'for SuccessfulAction' do 11 | let(:action) { SuccessfulAction.new } 12 | 13 | it "return true" do 14 | response = action.call(params) 15 | expect(response).to be_success 16 | end 17 | end 18 | 19 | context 'for FailedAction' do 20 | let(:action) { FailedAction.new } 21 | 22 | it "return false" do 23 | response = action.call(params) 24 | expect(response).to_not be_success 25 | end 26 | end 27 | 28 | context 'for RedirectedAction' do 29 | let(:action) { RedirectedAction.new } 30 | 31 | it "return false" do 32 | response = action.call(params) 33 | expect(response).to be_success 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/rspec/hanami/matchers/form_matchers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe 'form matchers' do 5 | include RSpec::Hanami::Matchers 6 | 7 | let(:view) { MainView.new } 8 | 9 | describe '#have_form_action' do 10 | context 'for form with right action' do 11 | it 'return true' do 12 | expect(view.form).to have_form_action('/users') 13 | end 14 | end 15 | 16 | context 'for form with other action' do 17 | it 'return false' do 18 | expect(view.form).to_not have_form_action('/books') 19 | end 20 | end 21 | end 22 | 23 | describe '#have_method' do 24 | context 'for form with right method' do 25 | it 'return true' do 26 | expect(view.form).to have_method('POST') 27 | expect(view.form).to have_method(:post) 28 | end 29 | end 30 | 31 | context 'for form with other method' do 32 | it 'return false' do 33 | expect(view.form).to_not have_method('PUT') 34 | expect(view.form).to_not have_method(:put) 35 | end 36 | end 37 | end 38 | 39 | describe '#have_method' do 40 | context 'for form with right method' do 41 | it 'return true' do 42 | expect(view.form).to have_method('POST') 43 | expect(view.form).to have_method(:post) 44 | end 45 | end 46 | 47 | context 'for form with other method' do 48 | it 'return false' do 49 | expect(view.form).to_not have_method('PUT') 50 | expect(view.form).to_not have_method(:put) 51 | end 52 | end 53 | end 54 | 55 | describe '#have_form_field' do 56 | context 'for form with right field' do 57 | it 'return true' do 58 | expect(view.form).to have_form_field(node: 'input', type: 'text', id: 'user-first-name') 59 | end 60 | end 61 | 62 | context 'for form with invalid field' do 63 | it 'return false' do 64 | expect(view.form).to_not have_form_field(node: 'input', type: 'hidden', value: '100') 65 | end 66 | end 67 | end 68 | 69 | describe 'aliases' do 70 | it { expect(method(:have_form_action).original_name).to eq(:have_action) } 71 | it { expect(method(:have_form_method).original_name).to eq(:have_method) } 72 | it { expect(method(:have_form_field).original_name).to eq(:have_field) } 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/rspec/hanami/matchers/have_http_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe "have_http_status" do 5 | include RSpec::Hanami::Matchers 6 | 7 | let(:params) { {} } 8 | 9 | describe 'with a successful number status' do 10 | context 'for SuccessfulAction' do 11 | let(:action) { SuccessfulAction.new } 12 | 13 | it "return true" do 14 | response = action.call(params) 15 | expect(response).to have_http_status(200) 16 | end 17 | end 18 | 19 | context 'for FailedAction' do 20 | let(:action) { FailedAction.new } 21 | 22 | it "return false" do 23 | response = action.call(params) 24 | expect(response).to_not have_http_status(200) 25 | end 26 | end 27 | 28 | context 'for RedirectedAction' do 29 | let(:action) { RedirectedAction.new } 30 | 31 | it "return false" do 32 | response = action.call(params) 33 | expect(response).to_not have_http_status(200) 34 | end 35 | end 36 | end 37 | 38 | describe 'with a successful symbol status' do 39 | context 'for SuccessfulAction' do 40 | let(:action) { SuccessfulAction.new } 41 | 42 | it "return true" do 43 | response = action.call(params) 44 | expect(response).to have_http_status(:success) 45 | end 46 | end 47 | 48 | context 'for FailedAction' do 49 | let(:action) { FailedAction.new } 50 | 51 | it 'return true' do 52 | response = action.call(params) 53 | expect(response).to_not have_http_status(:success) 54 | end 55 | end 56 | 57 | context 'for RedirectedAction' do 58 | let(:action) { RedirectedAction.new } 59 | 60 | it "return false" do 61 | response = action.call(params) 62 | expect(response).to_not have_http_status(:success) 63 | end 64 | end 65 | end 66 | 67 | describe 'with a redirected number status' do 68 | context 'for SuccessfulAction' do 69 | let(:action) { SuccessfulAction.new } 70 | 71 | it "return true" do 72 | response = action.call(params) 73 | expect(response).to_not have_http_status(302) 74 | end 75 | end 76 | 77 | context 'for FailedAction' do 78 | let(:action) { FailedAction.new } 79 | 80 | it "return false" do 81 | response = action.call(params) 82 | expect(response).to_not have_http_status(302) 83 | end 84 | end 85 | 86 | context 'for RedirectedAction' do 87 | let(:action) { RedirectedAction.new } 88 | 89 | it "return false" do 90 | response = action.call(params) 91 | expect(response).to have_http_status(302) 92 | end 93 | end 94 | end 95 | 96 | describe 'with a redirected symbol status' do 97 | context 'for SuccessfulAction' do 98 | let(:action) { SuccessfulAction.new } 99 | 100 | it "return true" do 101 | response = action.call(params) 102 | expect(response).to_not have_http_status(:redirect) 103 | end 104 | end 105 | 106 | context 'for FailedAction' do 107 | let(:action) { FailedAction.new } 108 | 109 | it 'return true' do 110 | response = action.call(params) 111 | expect(response).to_not have_http_status(:redirect) 112 | end 113 | end 114 | 115 | context 'for RedirectedAction' do 116 | let(:action) { RedirectedAction.new } 117 | 118 | it "return false" do 119 | response = action.call(params) 120 | expect(response).to have_http_status(:redirect) 121 | end 122 | end 123 | end 124 | 125 | describe 'with a error number status' do 126 | context 'for SuccessfulAction' do 127 | let(:action) { SuccessfulAction.new } 128 | 129 | it "return true" do 130 | response = action.call(params) 131 | expect(response).to_not have_http_status(404) 132 | end 133 | end 134 | 135 | context 'for FailedAction' do 136 | let(:action) { FailedAction.new } 137 | 138 | it "return false" do 139 | response = action.call(params) 140 | expect(response).to have_http_status(404) 141 | end 142 | end 143 | 144 | context 'for RedirectedAction' do 145 | let(:action) { RedirectedAction.new } 146 | 147 | it "return false" do 148 | response = action.call(params) 149 | expect(response).to_not have_http_status(404) 150 | end 151 | end 152 | end 153 | 154 | describe 'with a error symbol status' do 155 | context 'for SuccessfulAction' do 156 | let(:action) { SuccessfulAction.new } 157 | 158 | it "return true" do 159 | response = action.call(params) 160 | expect(response).to_not have_http_status(:error) 161 | expect(response).to_not have_http_status(:missing) 162 | end 163 | end 164 | 165 | context 'for FailedAction' do 166 | let(:action) { FailedAction.new } 167 | 168 | it 'return true' do 169 | response = action.call(params) 170 | expect(response).to have_http_status(:error) 171 | expect(response).to have_http_status(:missing) 172 | end 173 | end 174 | 175 | context 'for RedirectedAction' do 176 | let(:action) { RedirectedAction.new } 177 | 178 | it "return false" do 179 | response = action.call(params) 180 | expect(response).to_not have_http_status(:error) 181 | expect(response).to_not have_http_status(:missing) 182 | end 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /spec/rspec/hanami/matchers/include_json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe "include_json" do 5 | include RSpec::Hanami::Matchers 6 | 7 | let(:params) { {} } 8 | 9 | context 'for JsonAction' do 10 | let(:action) { JsonAction.new } 11 | 12 | it "return true" do 13 | response = action.call(params) 14 | expect(response).to include_json(user: { name: 'Anton' }) 15 | end 16 | 17 | it "return false" do 18 | response = action.call(params) 19 | expect(response).to_not include_json(user: 'Anton') 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/rspec/hanami/matchers/match_entity_schema_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe 'match_entity_schema' do 5 | include RSpec::Hanami::Matchers 6 | 7 | let(:entity) { UserEntity.new(id: 1, name: 'whatever', gender: true) } 8 | 9 | describe 'pass with success' do 10 | context 'for UserEntity single parameter' do 11 | it { expect(entity).to have_attribute(:name, Types::String) } 12 | end 13 | 14 | context 'for UserEntity multiple parameters' do 15 | it { expect(entity).to have_attributes(id: Types::Int, name: Types::String, gender: Types::Bool) } 16 | end 17 | end 18 | 19 | describe 'pass with failure' do 20 | context 'for UserEntity single parameter' do 21 | it { expect(entity).not_to have_attribute(:name, Types::Int) } 22 | end 23 | 24 | context 'for UserEntity multiple parameters' do 25 | it { expect(entity).not_to have_attributes(id: Types::Int, name: Types::Int, gender: Types::Bool) } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/rspec/hanami/matchers/match_in_body_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe 'match_in_body' do 5 | include RSpec::Hanami::Matchers 6 | 7 | let(:params) { {} } 8 | 9 | context 'for SuccessfulAction' do 10 | let(:action) { SuccessfulAction.new } 11 | it { expect(action.call(params)).to match_in_body('Hi') } 12 | it { expect(action.call(params)).to match_in_body('successful') } 13 | end 14 | 15 | context 'for FailedAction' do 16 | let(:action) { FailedAction.new } 17 | it { expect(action.call(params)).to match_in_body('Hi') } 18 | it { expect(action.call(params)).to match_in_body('failed') } 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/rspec/hanami/matchers/redirect_to_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/matchers' 3 | 4 | RSpec.describe "be matchers" do 5 | include RSpec::Hanami::Matchers 6 | 7 | let(:params) { {} } 8 | let(:site) { 'http://example.com/' } 9 | 10 | describe '#be_success' do 11 | context 'for SuccessfulAction' do 12 | let(:action) { SuccessfulAction.new } 13 | 14 | it "return true" do 15 | response = action.call(params) 16 | expect(response).to_not redirect_to(site) 17 | end 18 | end 19 | 20 | context 'for FailedAction' do 21 | let(:action) { FailedAction.new } 22 | 23 | it "return false" do 24 | response = action.call(params) 25 | expect(response).to_not redirect_to(site) 26 | end 27 | end 28 | 29 | context 'for RedirectedAction' do 30 | let(:action) { RedirectedAction.new } 31 | 32 | it "return false" do 33 | response = action.call(params) 34 | expect(response).to redirect_to(site) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/rspec/hanami/request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'rspec/hanami/request_helpers' 3 | 4 | RSpec.describe RSpec::Hanami::RequestHelpers::Request do 5 | describe '#env' do 6 | [['GET', '/', {}], 7 | ['POST', '/users', {}], 8 | ['PATCH', '/posts', {}], 9 | ['PUT', '/comments', {}], 10 | ['DELETE', '/tags', {}]].each do |method, path, options| 11 | it "returns hash for #{path} #{method}" do 12 | request = RSpec::Hanami::RequestHelpers::Request.new(method, path, options).env 13 | expect(request["REQUEST_METHOD"]).to eq method 14 | expect(request["PATH_INFO"]).to eq path 15 | expect(request["QUERY_STRING"]).to eq "?" 16 | end 17 | end 18 | 19 | let(:headers) { { "Content-type": "application/json", "auth" => "whatever" } } 20 | let(:params) { { "whatever": "whatever" } } 21 | 22 | let(:options) { { headers: headers, params: params } } 23 | 24 | it "returns hash for options" do 25 | request = RSpec::Hanami::RequestHelpers::Request.new("GET", "", options).env 26 | expect(request["CONTENT_TYPE"]).to eq "application/json" 27 | expect(request["HTTP_CONTENT_TYPE"]).to be_nil 28 | expect(request["HTTP_AUTH"]).to eq "whatever" 29 | expect(request["rack.input"].string).to eq params.to_json 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/rspec/hanami_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/support/spec/library_wide_checks' 2 | require 'spec_helper' 3 | 4 | RSpec.describe "RSpec::Hanami" do 5 | include RSpec::Support::WhitespaceChecks 6 | 7 | # Pasted from rspec-support lib/rspec/support/spec/library_wide_checks.rb:134 8 | # Easier to do that here than to extract it out 9 | RSpec::Matchers.define :be_well_formed do 10 | match do |actual| 11 | actual.empty? 12 | end 13 | 14 | failure_message do |actual| 15 | actual.join("\n") 16 | end 17 | end 18 | 19 | it "has no malformed whitespace", :slow do 20 | error_messages = [] 21 | `git ls-files -z`.split("\x0").each do |filename| 22 | error_messages << check_for_tab_characters(filename) 23 | error_messages << check_for_extra_spaces(filename) 24 | end 25 | expect(error_messages.compact).to be_well_formed 26 | end 27 | 28 | it 'has a version number' do 29 | expect(RSpec::Hanami::VERSION).not_to be nil 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | 4 | # $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 5 | require 'rspec/hanami' 6 | require 'rspec/support/spec' 7 | 8 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 9 | -------------------------------------------------------------------------------- /spec/support/actions.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'hanami-controller' 3 | 4 | class SuccessfulAction 5 | include Hanami::Action 6 | 7 | def call(params) 8 | self.status = 200 9 | self.body = "Hi! It's successful" 10 | end 11 | end 12 | 13 | class FailedAction 14 | include Hanami::Action 15 | 16 | def call(params) 17 | self.status = 404 18 | self.body = "Hi! It's failed" 19 | end 20 | end 21 | 22 | class RedirectedAction 23 | include Hanami::Action 24 | 25 | def call(params) 26 | redirect_to 'http://example.com/' 27 | end 28 | end 29 | 30 | class JsonAction 31 | include Hanami::Action 32 | 33 | def call(params) 34 | self.status = 200 35 | self.body = JSON.generate(user: { name: 'Anton' }) 36 | end 37 | end 38 | 39 | -------------------------------------------------------------------------------- /spec/support/entities.rb: -------------------------------------------------------------------------------- 1 | require 'hanami/model' 2 | 3 | class UserEntity < Hanami::Entity 4 | attributes do 5 | attribute :id, Types::Int 6 | attribute :name, Types::String 7 | attribute :gender, Types::Bool 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/templates/main.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davydovanton/rspec-hanami/77d0044ce2e3cf14643b57191ec31153489db0a8/spec/support/templates/main.html.erb -------------------------------------------------------------------------------- /spec/support/views.rb: -------------------------------------------------------------------------------- 1 | require 'hanami/controller' 2 | require 'hanami/helpers' 3 | require 'hanami/view' 4 | 5 | class MainView 6 | include Hanami::Helpers::FormHelper 7 | 8 | def params 9 | {} 10 | end 11 | 12 | def form 13 | form_for :user, '/users' do 14 | input type: 'text', id: 'user-first-name', name: 'user[first_name]', value: 'L' 15 | check_box :free_shipping 16 | color_field :cover, name: 'cover' 17 | date_field :release_date, class: 'form-control' 18 | email_field :publisher_email 19 | file_field :image_cover 20 | hidden_field :author_id, id: 'author-id' 21 | number_field :percent_read, step: 5 22 | text_area :description, nil, id: 'desc' 23 | text_field :title, value: 'DDD' 24 | 25 | submit 'Create' 26 | end 27 | end 28 | end 29 | --------------------------------------------------------------------------------