Migrate from Minitest to RSpec (#200)
* Migrate from Minitest to RSpec Closes https://github.com/hanami/utils/issues/198 * run tests with script/ci * Fix rubocop errors * run isolation tests without bundler load * Add isolation spec helper
This commit is contained in:
parent
c0b2845170
commit
53303305a4
|
@ -6,6 +6,7 @@ Metrics/BlockLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'test/**/*'
|
- 'test/**/*'
|
||||||
- 'tmp/**/*'
|
- 'tmp/**/*'
|
||||||
|
- 'spec/**/*'
|
||||||
Style/DoubleNegation:
|
Style/DoubleNegation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/SpecialGlobalVars:
|
Style/SpecialGlobalVars:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
language: ruby
|
language: ruby
|
||||||
sudo: false
|
sudo: false
|
||||||
cache: bundler
|
cache: bundler
|
||||||
script: 'script/ci && bundle exec rubocop'
|
script: ./script/ci
|
||||||
before_install:
|
before_install:
|
||||||
- rvm @global do gem uninstall bundler -a -x
|
- rvm @global do gem uninstall bundler -a -x
|
||||||
- rvm @global do gem install bundler -v 1.13.7
|
- rvm @global do gem install bundler -v 1.13.7
|
||||||
|
|
18
Rakefile
18
Rakefile
|
@ -1,7 +1,8 @@
|
||||||
require 'rake'
|
require 'rake'
|
||||||
require 'rake/testtask'
|
|
||||||
require 'bundler/gem_tasks'
|
require 'bundler/gem_tasks'
|
||||||
|
require 'rspec/core/rake_task'
|
||||||
|
|
||||||
|
require 'rake/testtask'
|
||||||
Rake::TestTask.new do |t|
|
Rake::TestTask.new do |t|
|
||||||
t.test_files = Dir['test/**/*_test.rb'].reject do |path|
|
t.test_files = Dir['test/**/*_test.rb'].reject do |path|
|
||||||
path.include?('isolation')
|
path.include?('isolation')
|
||||||
|
@ -10,11 +11,18 @@ Rake::TestTask.new do |t|
|
||||||
t.libs.push 'test'
|
t.libs.push 'test'
|
||||||
end
|
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
|
task :coverage do
|
||||||
ENV['COVERALL'] = 'true'
|
ENV['COVERAGE'] = 'true'
|
||||||
Rake::Task['test'].invoke
|
Rake::Task['spec:unit'].invoke
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
task default: :test
|
task default: 'spec:unit'
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# coding: utf-8
|
|
||||||
lib = File.expand_path('../lib', __FILE__)
|
lib = File.expand_path('../lib', __FILE__)
|
||||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||||
require 'hanami/utils/version'
|
require 'hanami/utils/version'
|
||||||
|
@ -21,4 +20,5 @@ Gem::Specification.new do |spec|
|
||||||
|
|
||||||
spec.add_development_dependency 'bundler', '~> 1.6'
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
||||||
spec.add_development_dependency 'rake', '~> 11'
|
spec.add_development_dependency 'rake', '~> 11'
|
||||||
|
spec.add_development_dependency 'rspec', '~> 3.5'
|
||||||
end
|
end
|
||||||
|
|
|
@ -110,7 +110,7 @@ module Hanami
|
||||||
|
|
||||||
# @since 1.0.0.beta1
|
# @since 1.0.0.beta1
|
||||||
# @api private
|
# @api private
|
||||||
RESERVED_KEYS = [:app, :severity, :time].freeze
|
RESERVED_KEYS = %i(app severity time).freeze
|
||||||
|
|
||||||
include Utils::ClassAttribute
|
include Utils::ClassAttribute
|
||||||
|
|
||||||
|
|
21
script/ci
21
script/ci
|
@ -7,16 +7,16 @@ run_code_quality_checks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
run_unit_tests() {
|
run_unit_tests() {
|
||||||
bundle exec rake test:coverage
|
bundle exec rake spec:coverage
|
||||||
}
|
}
|
||||||
|
|
||||||
run_integration_tests() {
|
run_isolation_tests() {
|
||||||
local pwd=$PWD
|
local pwd=$PWD
|
||||||
local root="$pwd/test/isolation"
|
local root="$pwd/spec/isolation"
|
||||||
|
|
||||||
for test in $(find $root -name '*_test.rb')
|
for test in $(find $root -name '*_spec.rb')
|
||||||
do
|
do
|
||||||
run_test $test
|
run_isolation_test $test
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
|
@ -26,17 +26,24 @@ run_integration_tests() {
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_isolation_test() {
|
||||||
|
local test=$1
|
||||||
|
|
||||||
|
printf "\n\n\nRunning: $test\n"
|
||||||
|
ruby $test --options spec/isolation/.rspec
|
||||||
|
}
|
||||||
|
|
||||||
run_test() {
|
run_test() {
|
||||||
local test=$1
|
local test=$1
|
||||||
|
|
||||||
printf "\n\n\nRunning: $test\n"
|
printf "\n\n\nRunning: $test\n"
|
||||||
ruby -Itest $test
|
COVERAGE=true bundle exec rspec $test
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
run_code_quality_checks &&
|
run_code_quality_checks &&
|
||||||
run_unit_tests &&
|
run_unit_tests &&
|
||||||
run_integration_tests
|
run_isolation_tests
|
||||||
}
|
}
|
||||||
|
|
||||||
main
|
main
|
||||||
|
|
|
@ -0,0 +1,447 @@
|
||||||
|
require 'hanami/interactor'
|
||||||
|
|
||||||
|
class InteractorWithoutInitialize
|
||||||
|
include Hanami::Interactor
|
||||||
|
|
||||||
|
def call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class InteractorWithoutCall
|
||||||
|
include Hanami::Interactor
|
||||||
|
end
|
||||||
|
|
||||||
|
class User
|
||||||
|
def initialize(attributes = {})
|
||||||
|
@attributes = attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
@attributes.fetch(:name, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def name=(value)
|
||||||
|
@attributes[:name] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def persist!
|
||||||
|
raise if name.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_hash
|
||||||
|
{ name: name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Signup
|
||||||
|
include Hanami::Interactor
|
||||||
|
expose :user, :params
|
||||||
|
|
||||||
|
def initialize(params)
|
||||||
|
@params = params
|
||||||
|
@user = User.new(params)
|
||||||
|
@__foo = 23
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
@user.persist!
|
||||||
|
rescue
|
||||||
|
fail!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
!@params[:force_failure]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ErrorInteractor
|
||||||
|
include Hanami::Interactor
|
||||||
|
expose :operations
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@operations = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
prepare!
|
||||||
|
persist!
|
||||||
|
log!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def prepare!
|
||||||
|
@operations << __method__
|
||||||
|
error 'There was an error while preparing data.'
|
||||||
|
end
|
||||||
|
|
||||||
|
def persist!
|
||||||
|
@operations << __method__
|
||||||
|
error 'There was an error while persisting data.'
|
||||||
|
end
|
||||||
|
|
||||||
|
def log!
|
||||||
|
@operations << __method__
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ErrorBangInteractor
|
||||||
|
include Hanami::Interactor
|
||||||
|
expose :operations
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@operations = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
persist!
|
||||||
|
sync!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def persist!
|
||||||
|
@operations << __method__
|
||||||
|
error! 'There was an error while persisting data.'
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync!
|
||||||
|
@operations << __method__
|
||||||
|
error 'There was an error while syncing data.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class PublishVideo
|
||||||
|
include Hanami::Interactor
|
||||||
|
|
||||||
|
def call
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
owns?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def owns?
|
||||||
|
# fake failed ownership check
|
||||||
|
1 == 0 ||
|
||||||
|
error("You're not owner of this video")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CreateUser
|
||||||
|
include Hanami::Interactor
|
||||||
|
expose :user
|
||||||
|
|
||||||
|
def initialize(params)
|
||||||
|
@user = User.new(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
persist
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def persist
|
||||||
|
@user.persist!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class UpdateUser < CreateUser
|
||||||
|
def initialize(_user, params)
|
||||||
|
super(params)
|
||||||
|
@user.name = params.fetch(:name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Interactor do
|
||||||
|
describe '#initialize' do
|
||||||
|
it "works when it isn't overridden" do
|
||||||
|
InteractorWithoutInitialize.new
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows to override it' do
|
||||||
|
Signup.new({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#call' do
|
||||||
|
it 'returns a result' do
|
||||||
|
result = Signup.new(name: 'Luca').call
|
||||||
|
expect(result.class).to eq Hanami::Interactor::Result
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is successful by default' do
|
||||||
|
result = Signup.new(name: 'Luca').call
|
||||||
|
expect(result).to be_successful
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the payload' do
|
||||||
|
result = Signup.new(name: 'Luca').call
|
||||||
|
|
||||||
|
expect(result.user.name).to eq 'Luca'
|
||||||
|
expect(result.params).to eq(name: 'Luca')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't include private ivars" do
|
||||||
|
result = Signup.new(name: 'Luca').call
|
||||||
|
|
||||||
|
expect { result.__foo }.to raise_error NoMethodError
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'exposes a convenient API for handling failures' do
|
||||||
|
result = Signup.new({}).call
|
||||||
|
expect(result).to be_failure
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't invoke it if the preconditions are failing" do
|
||||||
|
result = Signup.new(force_failure: true).call
|
||||||
|
expect(result).to be_failure
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error when #call isn't implemented" do
|
||||||
|
expect { InteractorWithoutCall.new.call }.to raise_error NoMethodError
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'inheritance' do
|
||||||
|
it 'is successful for super class' do
|
||||||
|
result = CreateUser.new(name: 'L').call
|
||||||
|
|
||||||
|
expect(result).to be_successful
|
||||||
|
expect(result.user.name).to eq 'L'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is successful for sub class' do
|
||||||
|
user = User.new(name: 'L')
|
||||||
|
result = UpdateUser.new(user, name: 'MG').call
|
||||||
|
|
||||||
|
expect(result).to be_successful
|
||||||
|
expect(result.user.name).to eq 'MG'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#error' do
|
||||||
|
it "isn't successful" do
|
||||||
|
result = ErrorInteractor.new.call
|
||||||
|
expect(result).to be_failure
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accumulates errors' do
|
||||||
|
result = ErrorInteractor.new.call
|
||||||
|
expect(result.errors).to eq [
|
||||||
|
'There was an error while preparing data.',
|
||||||
|
'There was an error while persisting data.'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't interrupt the flow" do
|
||||||
|
result = ErrorInteractor.new.call
|
||||||
|
expect(result.operations).to eq %i(prepare! persist! log!)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See https://github.com/hanami/utils/issues/69
|
||||||
|
it 'returns false as control flow for caller' do
|
||||||
|
interactor = PublishVideo.new
|
||||||
|
expect(interactor).not_to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#error!' do
|
||||||
|
it "isn't successful" do
|
||||||
|
result = ErrorBangInteractor.new.call
|
||||||
|
expect(result).to be_failure
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'stops at the first error' do
|
||||||
|
result = ErrorBangInteractor.new.call
|
||||||
|
expect(result.errors).to eq [
|
||||||
|
'There was an error while persisting data.'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'interrupts the flow' do
|
||||||
|
result = ErrorBangInteractor.new.call
|
||||||
|
expect(result.operations).to eq [:persist!]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Interactor::Result do
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'allows to skip payload' do
|
||||||
|
Hanami::Interactor::Result.new
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts a payload' do
|
||||||
|
result = Hanami::Interactor::Result.new(foo: 'bar')
|
||||||
|
expect(result.foo).to eq 'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#successful?' do
|
||||||
|
it 'is successful by default' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
expect(result).to be_successful
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when it has errors' do
|
||||||
|
it "isn't successful" do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
result.add_error 'There was a problem'
|
||||||
|
expect(result).to be_failure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#fail!' do
|
||||||
|
it 'causes a failure' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
result.fail!
|
||||||
|
|
||||||
|
expect(result).to be_failure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#prepare!' do
|
||||||
|
it 'merges the current payload' do
|
||||||
|
result = Hanami::Interactor::Result.new(foo: 'bar')
|
||||||
|
result.prepare!(foo: 23)
|
||||||
|
|
||||||
|
expect(result.foo).to eq 23
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns self' do
|
||||||
|
result = Hanami::Interactor::Result.new(foo: 'bar')
|
||||||
|
returning = result.prepare!(foo: 23)
|
||||||
|
|
||||||
|
expect(returning).to eq result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#errors' do
|
||||||
|
it 'empty by default' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
expect(result.errors).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns all the errors' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
result.add_error ['Error 1', 'Error 2']
|
||||||
|
|
||||||
|
expect(result.errors).to eq ['Error 1', 'Error 2']
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents information escape' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
result.add_error ['Error 1', 'Error 2']
|
||||||
|
|
||||||
|
result.errors.clear
|
||||||
|
|
||||||
|
expect(result.errors).to eq ['Error 1', 'Error 2']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#error' do
|
||||||
|
it 'nil by default' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
expect(result.error).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns only the first error' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
result.add_error ['Error 1', 'Error 2']
|
||||||
|
|
||||||
|
expect(result.error).to eq 'Error 1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#respond_to?' do
|
||||||
|
it 'returns true for concrete methods' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
|
||||||
|
expect(result).to respond_to(:successful?)
|
||||||
|
expect(result).to respond_to('successful?')
|
||||||
|
|
||||||
|
expect(result).to respond_to(:failure?)
|
||||||
|
expect(result).to respond_to('failure?')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true for methods derived from payload' do
|
||||||
|
result = Hanami::Interactor::Result.new(foo: 1)
|
||||||
|
|
||||||
|
expect(result).to respond_to(:foo)
|
||||||
|
expect(result).to respond_to('foo')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true for methods derived from merged payload' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
result.prepare!(bar: 2)
|
||||||
|
|
||||||
|
expect(result).to respond_to(:bar)
|
||||||
|
expect(result).to respond_to('bar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#inspect' do
|
||||||
|
let(:result) { Hanami::Interactor::Result.new(id: 23, user: User.new) }
|
||||||
|
|
||||||
|
it 'reports the class name and the object_id' do
|
||||||
|
expect(result.inspect).to match %(#<Hanami::Interactor::Result)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'reports the object_id' do
|
||||||
|
object_id = format('%x', (result.__id__ << 1))
|
||||||
|
expect(result.inspect).to match object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'reports @success' do
|
||||||
|
expect(result.inspect).to match %(@success=true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'reports @payload' do
|
||||||
|
expect(result.inspect).to match %(@payload={:id=>23, :user=>#<User:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'payload' do
|
||||||
|
it 'returns all the values passed in the payload' do
|
||||||
|
result = Hanami::Interactor::Result.new(a: 1, b: 2)
|
||||||
|
expect(result.a).to eq 1
|
||||||
|
expect(result.b).to eq 2
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns hash values passed in the payload' do
|
||||||
|
result = Hanami::Interactor::Result.new(a: { 100 => 3 })
|
||||||
|
expect(result.a).to eq(100 => 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns all the values after a merge' do
|
||||||
|
result = Hanami::Interactor::Result.new(a: 1, b: 2)
|
||||||
|
result.prepare!(a: 23, c: 3)
|
||||||
|
|
||||||
|
expect(result.a).to eq 23
|
||||||
|
expect(result.b).to eq 2
|
||||||
|
expect(result.c).to eq 3
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't ignore forwarded messages" do
|
||||||
|
result = Hanami::Interactor::Result.new(params: { name: 'Luca' })
|
||||||
|
expect(result.params[:name]).to eq 'Luca'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an error when unknown message is passed' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
expect { result.unknown }.to raise_error NoMethodError
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an error when unknown message is passed with args' do
|
||||||
|
result = Hanami::Interactor::Result.new
|
||||||
|
expect { result.unknown(:foo) }.to raise_error NoMethodError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,438 @@
|
||||||
|
require 'hanami/logger'
|
||||||
|
require 'rbconfig'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Logger do
|
||||||
|
before do
|
||||||
|
# clear defined class
|
||||||
|
Object.send(:remove_const, :TestLogger) if Object.constants.include?(:TestLogger)
|
||||||
|
|
||||||
|
allow(Time).to receive(:now).and_return(Time.parse("2017-01-15 16:00:23 +0100"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'like std logger, sets log level to info by default' do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
expect(TestLogger.new.info?).to eq true
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'uses STDOUT by default' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
logger = TestLogger.new
|
||||||
|
logger.info('foo')
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(output).to match(/foo/)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'custom level option' do
|
||||||
|
it 'takes a integer' do
|
||||||
|
logger = Hanami::Logger.new(level: 3)
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a integer more than 5' do
|
||||||
|
logger = Hanami::Logger.new(level: 99)
|
||||||
|
expect(logger.level).to eq Hanami::Logger::DEBUG
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a symbol' do
|
||||||
|
logger = Hanami::Logger.new(level: :error)
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a string' do
|
||||||
|
logger = Hanami::Logger.new(level: 'error')
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a string with strange value' do
|
||||||
|
logger = Hanami::Logger.new(level: 'strange')
|
||||||
|
expect(logger.level).to eq Hanami::Logger::DEBUG
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a uppercased string' do
|
||||||
|
logger = Hanami::Logger.new(level: 'ERROR')
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a constant' do
|
||||||
|
logger = Hanami::Logger.new(level: Hanami::Logger::ERROR)
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'contains debug level by default' do
|
||||||
|
logger = Hanami::Logger.new
|
||||||
|
expect(logger.level).to eq ::Logger::DEBUG
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'custom stream' do
|
||||||
|
describe 'file system' do
|
||||||
|
before do
|
||||||
|
Pathname.new(stream).dirname.mkpath
|
||||||
|
end
|
||||||
|
|
||||||
|
Hash[
|
||||||
|
Pathname.new(Dir.pwd).join('tmp', 'logfile.log').to_s => 'absolute path (string)',
|
||||||
|
Pathname.new('tmp').join('logfile.log').to_s => 'relative path (string)',
|
||||||
|
Pathname.new(Dir.pwd).join('tmp', 'logfile.log') => 'absolute path (pathname)',
|
||||||
|
Pathname.new('tmp').join('logfile.log') => 'relative path (pathname)',
|
||||||
|
].each do |dev, desc|
|
||||||
|
describe "when #{desc}" do
|
||||||
|
let(:stream) { dev }
|
||||||
|
|
||||||
|
after do
|
||||||
|
File.delete(stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'and it does not exist' do
|
||||||
|
before do
|
||||||
|
File.delete(stream) if File.exist?(stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'writes to file' do
|
||||||
|
logger = Hanami::Logger.new(stream: stream)
|
||||||
|
logger.info('newline')
|
||||||
|
|
||||||
|
contents = File.read(stream)
|
||||||
|
expect(contents).to match(/newline/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'and it already exists' do
|
||||||
|
before do
|
||||||
|
File.open(stream, File::WRONLY | File::TRUNC | File::CREAT, permissions) { |f| f.write('existing') }
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:permissions) { 0o664 }
|
||||||
|
|
||||||
|
it 'appends to file' do
|
||||||
|
logger = Hanami::Logger.new(stream: stream)
|
||||||
|
logger.info('appended')
|
||||||
|
|
||||||
|
contents = File.read(stream)
|
||||||
|
expect(contents).to match(/existing/)
|
||||||
|
expect(contents).to match(/appended/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not change permissions' do
|
||||||
|
logger = Hanami::Logger.new(stream: stream)
|
||||||
|
logger.info('appended')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end # end loop
|
||||||
|
|
||||||
|
describe 'when file' do
|
||||||
|
let(:stream) { Pathname.new('tmp').join('logfile.log') }
|
||||||
|
let(:log_file) { File.new(stream, 'w+', permissions) }
|
||||||
|
let(:permissions) { 0o644 }
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
log_file.write('hello')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'and already written' do
|
||||||
|
it 'appends to file' do
|
||||||
|
logger = Hanami::Logger.new(stream: log_file)
|
||||||
|
logger.info('world')
|
||||||
|
|
||||||
|
logger.close
|
||||||
|
|
||||||
|
contents = File.read(log_file)
|
||||||
|
expect(contents).to match(/hello/)
|
||||||
|
expect(contents).to match(/world/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not change permissions'
|
||||||
|
# it 'does not change permissions' do
|
||||||
|
# logger = Hanami::Logger.new(stream: log_file)
|
||||||
|
# logger.info('appended')
|
||||||
|
# logger.close
|
||||||
|
|
||||||
|
# stat = File.stat(log_file)
|
||||||
|
# mode = stat.mode.to_s(8)
|
||||||
|
|
||||||
|
# require 'hanami/utils'
|
||||||
|
# if Hanami::Utils.jruby?
|
||||||
|
# expect(mode).to eq('100664')
|
||||||
|
# else
|
||||||
|
# expect(mode).to eq('100644')
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
end # end File
|
||||||
|
|
||||||
|
describe 'when IO' do
|
||||||
|
let(:stream) { Pathname.new('tmp').join('logfile.log').to_s }
|
||||||
|
|
||||||
|
it 'appends' do
|
||||||
|
fd = IO.sysopen(stream, 'w')
|
||||||
|
io = IO.new(fd, 'w')
|
||||||
|
|
||||||
|
logger = Hanami::Logger.new(stream: io)
|
||||||
|
logger.info('in file')
|
||||||
|
logger.close
|
||||||
|
|
||||||
|
contents = File.read(stream)
|
||||||
|
expect(contents).to match(/in file/)
|
||||||
|
end
|
||||||
|
end # end IO
|
||||||
|
end # end FileSystem
|
||||||
|
|
||||||
|
describe 'when StringIO' do
|
||||||
|
let(:stream) { StringIO.new }
|
||||||
|
|
||||||
|
it 'appends' do
|
||||||
|
logger = Hanami::Logger.new(stream: stream)
|
||||||
|
logger.info('in file')
|
||||||
|
|
||||||
|
stream.rewind
|
||||||
|
expect(stream.read).to match(/in file/)
|
||||||
|
end
|
||||||
|
end # end StringIO
|
||||||
|
end # end #initialize
|
||||||
|
|
||||||
|
describe '#close' do
|
||||||
|
it 'does not close STDOUT output for other code' do
|
||||||
|
logger = Hanami::Logger.new(stream: STDOUT)
|
||||||
|
logger.close
|
||||||
|
|
||||||
|
expect { print 'in STDOUT' }.to output('in STDOUT').to_stdout
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not close $stdout output for other code' do
|
||||||
|
logger = Hanami::Logger.new(stream: $stdout)
|
||||||
|
logger.close
|
||||||
|
|
||||||
|
expect { print 'in $stdout' }.to output('in $stdout').to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#level=' do
|
||||||
|
subject(:logger) { Hanami::Logger.new }
|
||||||
|
|
||||||
|
it 'takes a integer' do
|
||||||
|
logger.level = 3
|
||||||
|
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a integer more than 5' do
|
||||||
|
logger.level = 99
|
||||||
|
|
||||||
|
expect(logger.level).to eq Hanami::Logger::DEBUG
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a symbol' do
|
||||||
|
logger.level = :error
|
||||||
|
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a string' do
|
||||||
|
logger.level = 'error'
|
||||||
|
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a string with strange value' do
|
||||||
|
logger.level = 'strange'
|
||||||
|
|
||||||
|
expect(logger.level).to eq Hanami::Logger::DEBUG
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a uppercased string' do
|
||||||
|
logger.level = 'ERROR'
|
||||||
|
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'takes a constant' do
|
||||||
|
logger.level = Hanami::Logger::ERROR
|
||||||
|
|
||||||
|
expect(logger.level).to eq Hanami::Logger::ERROR
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has application_name when log' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
module App; class TestLogger < Hanami::Logger; end; end
|
||||||
|
logger = App::TestLogger.new
|
||||||
|
logger.info('foo')
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(output).to match(/App/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has default app tag when not in any namespace' do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
expect(TestLogger.new.application_name).to eq 'hanami'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'infers apptag from namespace' do
|
||||||
|
module App2
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
class Bar
|
||||||
|
def hoge
|
||||||
|
TestLogger.new.application_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(App2::Bar.new.hoge).to eq 'App2'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses custom application_name from override class' do
|
||||||
|
class TestLogger < Hanami::Logger
|
||||||
|
def application_name
|
||||||
|
'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
TestLogger.new.info('')
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(output).to match(/bar/)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'with nil formatter' do
|
||||||
|
it 'falls back to Formatter' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new(formatter: nil).info('foo')
|
||||||
|
end
|
||||||
|
expect(output).to eq "[hanami] [INFO] [2017-01-15 16:00:23 +0100] foo\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'with JSON formatter' do
|
||||||
|
if Hanami::Utils.jruby?
|
||||||
|
it 'when passed as a symbol, it has JSON format for string messages'
|
||||||
|
else
|
||||||
|
it 'when passed as a symbol, it has JSON format for string messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new(formatter: :json).info('foo')
|
||||||
|
end
|
||||||
|
expect(output).to eq %({"app":"hanami","severity":"INFO","time":"2017-01-15T15:00:23Z","message":"foo"}\n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Hanami::Utils.jruby?
|
||||||
|
it 'has JSON format for string messages'
|
||||||
|
else
|
||||||
|
it 'has JSON format for string messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new(formatter: Hanami::Logger::JSONFormatter.new).info('foo')
|
||||||
|
end
|
||||||
|
expect(output).to eq %({"app":"hanami","severity":"INFO","time":"2017-01-15T15:00:23Z","message":"foo"}\n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Hanami::Utils.jruby?
|
||||||
|
it 'has JSON format for error messages'
|
||||||
|
else
|
||||||
|
it 'has JSON format for error messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new(formatter: Hanami::Logger::JSONFormatter.new).error(Exception.new('foo'))
|
||||||
|
end
|
||||||
|
expect(output).to eq %({"app":"hanami","severity":"ERROR","time":"2017-01-15T15:00:23Z","message":"foo","backtrace":[],"error":"Exception"}\n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Hanami::Utils.jruby?
|
||||||
|
it 'has JSON format for hash messages'
|
||||||
|
else
|
||||||
|
it 'has JSON format for hash messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new(formatter: Hanami::Logger::JSONFormatter.new).info(foo: :bar)
|
||||||
|
end
|
||||||
|
expect(output).to eq %({"app":"hanami","severity":"INFO","time":"2017-01-15T15:00:23Z","foo":"bar"}\n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Hanami::Utils.jruby?
|
||||||
|
it 'has JSON format for not string messages'
|
||||||
|
else
|
||||||
|
it 'has JSON format for not string messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new(formatter: Hanami::Logger::JSONFormatter.new).info(['foo'])
|
||||||
|
end
|
||||||
|
expect(output).to eq %({"app":"hanami","severity":"INFO","time":"2017-01-15T15:00:23Z","message":["foo"]}\n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'with default formatter' do
|
||||||
|
it 'when passed as a symbol, it has key=value format for string messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new(formatter: :default).info('foo')
|
||||||
|
end
|
||||||
|
expect(output).to eq "[hanami] [INFO] [2017-01-15 16:00:23 +0100] foo\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has key=value format for string messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new.info('foo')
|
||||||
|
end
|
||||||
|
expect(output).to eq "[hanami] [INFO] [2017-01-15 16:00:23 +0100] foo\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has key=value format for error messages' do
|
||||||
|
exception = nil
|
||||||
|
output = with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
begin
|
||||||
|
raise StandardError.new('foo')
|
||||||
|
rescue => e
|
||||||
|
exception = e
|
||||||
|
end
|
||||||
|
TestLogger.new.error(exception)
|
||||||
|
end
|
||||||
|
expectation = "[hanami] [ERROR] [2017-01-15 16:00:23 +0100] StandardError: foo\n"
|
||||||
|
exception.backtrace.each do |line|
|
||||||
|
expectation << "from #{line}\n"
|
||||||
|
end
|
||||||
|
expect(output).to eq expectation
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has key=value format for hash messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new.info(foo: :bar)
|
||||||
|
end
|
||||||
|
expect(output).to eq "[hanami] [INFO] [2017-01-15 16:00:23 +0100] bar\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has key=value format for not string messages' do
|
||||||
|
output =
|
||||||
|
with_captured_stdout do
|
||||||
|
class TestLogger < Hanami::Logger; end
|
||||||
|
TestLogger.new.info(%(foo bar))
|
||||||
|
end
|
||||||
|
expect(output).to eq "[hanami] [INFO] [2017-01-15 16:00:23 +0100] foo bar\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,42 @@
|
||||||
|
require 'hanami/utils/basic_object'
|
||||||
|
require 'pp'
|
||||||
|
|
||||||
|
class TestClass < Hanami::Utils::BasicObject
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::BasicObject do
|
||||||
|
describe '#respond_to_missing?' do
|
||||||
|
it 'raises an exception if respond_to? method is not implemented' do
|
||||||
|
expect { TestClass.new.respond_to?(:no_existing_method) }
|
||||||
|
.to raise_error(NotImplementedError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true given respond_to? method was implemented' do
|
||||||
|
TestCase = Class.new(TestClass) do
|
||||||
|
def respond_to?(_method_name, _include_all = false)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(TestCase.new).to respond_to(:no_existing_method)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#class' do
|
||||||
|
it 'returns TestClass' do
|
||||||
|
expect(TestClass.new.class).to eq TestClass
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#inspect' do
|
||||||
|
it 'returns the inspect message' do
|
||||||
|
inspect_msg = TestClass.new.inspect
|
||||||
|
expect(inspect_msg).to match(/\A#<TestClass:\w+>\z/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# See https://github.com/hanami/hanami/issues/629
|
||||||
|
it 'is pretty printable' do
|
||||||
|
pp TestClass.new
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,42 @@
|
||||||
|
require 'hanami/utils/kernel'
|
||||||
|
require 'hanami/utils/string'
|
||||||
|
require 'hanami/utils/hash'
|
||||||
|
require 'hanami/utils/blank'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Blank do
|
||||||
|
describe '.blank?' do
|
||||||
|
[nil, false, '', ' ', " \n\t \r ", ' ', "\u00a0", [], {}, Set.new,
|
||||||
|
Hanami::Utils::Kernel.Boolean(0), Hanami::Utils::String.new(''),
|
||||||
|
Hanami::Utils::Hash.new({})].each do |v|
|
||||||
|
it 'returns true' do
|
||||||
|
expect(Hanami::Utils::Blank.blank?(v)).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[Object.new, true, 0, 1, 'a', :book, DateTime.now, Time.now, Date.new, [nil], { nil => 0 }, Set.new([1]),
|
||||||
|
Hanami::Utils::Kernel.Symbol(:hello), Hanami::Utils::String.new('foo'),
|
||||||
|
Hanami::Utils::Hash.new(foo: :bar)].each do |v|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(Hanami::Utils::Blank.blank?(v)).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.filled?' do
|
||||||
|
[nil, false, '', ' ', " \n\t \r ", ' ', "\u00a0", [], {}, Set.new,
|
||||||
|
Hanami::Utils::Kernel.Boolean(0), Hanami::Utils::String.new(''),
|
||||||
|
Hanami::Utils::Hash.new({})].each do |v|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(Hanami::Utils::Blank.filled?(v)).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[Object.new, true, 0, 1, 'a', :book, DateTime.now, Time.now, Date.new, [nil], { nil => 0 }, Set.new([1]),
|
||||||
|
Hanami::Utils::Kernel.Symbol(:hello), Hanami::Utils::String.new('foo'),
|
||||||
|
Hanami::Utils::Hash.new(foo: :bar)].each do |v|
|
||||||
|
it 'returns true' do
|
||||||
|
expect(Hanami::Utils::Blank.filled?(v)).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,333 @@
|
||||||
|
require 'hanami/utils/callbacks'
|
||||||
|
|
||||||
|
Hanami::Utils::Callbacks::Chain.class_eval do
|
||||||
|
def size
|
||||||
|
@chain.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def first
|
||||||
|
@chain.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def last
|
||||||
|
@chain.last
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(&blk)
|
||||||
|
@chain.each(&blk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Callable
|
||||||
|
def call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Action
|
||||||
|
attr_reader :logger
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@logger = []
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def authenticate!
|
||||||
|
logger.push 'authenticate!'
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_article(params) # rubocop:disable Style/AccessorMethodName
|
||||||
|
logger.push "set_article: #{params[:id]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Callbacks::Chain do
|
||||||
|
before do
|
||||||
|
@chain = Hanami::Utils::Callbacks::Chain.new
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#append' do
|
||||||
|
it 'wraps the given callback with a callable object' do
|
||||||
|
@chain.append :symbolize!
|
||||||
|
|
||||||
|
cb = @chain.last
|
||||||
|
expect(cb).to respond_to(:call)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'appends the callbacks at the end of the chain' do
|
||||||
|
@chain.append(:foo)
|
||||||
|
|
||||||
|
@chain.append(:bar)
|
||||||
|
expect(@chain.first.callback).to eq(:foo)
|
||||||
|
expect(@chain.last.callback).to eq(:bar)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a callable object is passed' do
|
||||||
|
before do
|
||||||
|
@chain.append callback
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { Callable.new }
|
||||||
|
|
||||||
|
it 'includes the given callback' do
|
||||||
|
cb = @chain.last
|
||||||
|
expect(cb.callback).to eq(callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a Symbol is passed' do
|
||||||
|
before do
|
||||||
|
@chain.append callback
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { :upcase }
|
||||||
|
|
||||||
|
it 'includes the given callback' do
|
||||||
|
cb = @chain.last
|
||||||
|
expect(cb.callback).to eq(callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'guarantees unique entries' do
|
||||||
|
# append the callback again, see before block
|
||||||
|
@chain.append callback
|
||||||
|
expect(@chain.size).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a block is passed' do
|
||||||
|
before do
|
||||||
|
@chain.append(&callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { proc {} }
|
||||||
|
|
||||||
|
it 'includes the given callback' do
|
||||||
|
cb = @chain.last
|
||||||
|
expect(cb.callback).to eq(callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when multiple callbacks are passed' do
|
||||||
|
before do
|
||||||
|
@chain.append(*callbacks)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callbacks) { [:upcase, Callable.new, proc {}] }
|
||||||
|
|
||||||
|
it 'includes all the given callbacks' do
|
||||||
|
expect(@chain.size).to eq(callbacks.size)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'all the included callbacks are callable' do
|
||||||
|
@chain.each do |callback|
|
||||||
|
expect(callback).to respond_to(:call)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#prepend' do
|
||||||
|
it 'wraps the given callback with a callable object' do
|
||||||
|
@chain.prepend :symbolize!
|
||||||
|
|
||||||
|
cb = @chain.first
|
||||||
|
expect(cb).to respond_to(:call)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prepends the callbacks at the beginning of the chain' do
|
||||||
|
@chain.append(:foo)
|
||||||
|
|
||||||
|
@chain.prepend(:bar)
|
||||||
|
expect(@chain.first.callback).to eq(:bar)
|
||||||
|
expect(@chain.last.callback).to eq(:foo)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a callable object is passed' do
|
||||||
|
before do
|
||||||
|
@chain.prepend callback
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { Callable.new }
|
||||||
|
|
||||||
|
it 'includes the given callback' do
|
||||||
|
cb = @chain.first
|
||||||
|
expect(cb.callback).to eq(callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a Symbol is passed' do
|
||||||
|
before do
|
||||||
|
@chain.prepend callback
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { :upcase }
|
||||||
|
|
||||||
|
it 'includes the given callback' do
|
||||||
|
cb = @chain.first
|
||||||
|
expect(cb.callback).to eq(callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'guarantees unique entries' do
|
||||||
|
# append the callback again, see before block
|
||||||
|
@chain.prepend callback
|
||||||
|
expect(@chain.size).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a block is passed' do
|
||||||
|
before do
|
||||||
|
@chain.prepend(&callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { proc {} }
|
||||||
|
|
||||||
|
it 'includes the given callback' do
|
||||||
|
cb = @chain.first
|
||||||
|
expect(cb.callback).to eq callback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when multiple callbacks are passed' do
|
||||||
|
before do
|
||||||
|
@chain.prepend(*callbacks)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callbacks) { [:upcase, Callable.new, proc {}] }
|
||||||
|
|
||||||
|
it 'includes all the given callbacks' do
|
||||||
|
expect(@chain.size).to eq(callbacks.size)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'all the included callbacks are callable' do
|
||||||
|
@chain.each do |callback|
|
||||||
|
expect(callback).to respond_to(:call)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#run' do
|
||||||
|
let(:action) { Action.new }
|
||||||
|
let(:params) { Hash[id: 23] }
|
||||||
|
|
||||||
|
describe 'when symbols are passed' do
|
||||||
|
before do
|
||||||
|
@chain.append :authenticate!, :set_article
|
||||||
|
@chain.run action, params
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'executes the callbacks' do
|
||||||
|
authenticate = action.logger.shift
|
||||||
|
expect(authenticate).to eq 'authenticate!'
|
||||||
|
|
||||||
|
set_article = action.logger.shift
|
||||||
|
expect(set_article).to eq "set_article: #{params[:id]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when procs are passed' do
|
||||||
|
before do
|
||||||
|
@chain.append do
|
||||||
|
logger.push 'authenticate!'
|
||||||
|
end
|
||||||
|
|
||||||
|
@chain.append do |params|
|
||||||
|
logger.push "set_article: #{params[:id]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@chain.run action, params
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'executes the callbacks' do
|
||||||
|
authenticate = action.logger.shift
|
||||||
|
expect(authenticate).to eq 'authenticate!'
|
||||||
|
|
||||||
|
set_article = action.logger.shift
|
||||||
|
expect(set_article).to eq "set_article: #{params[:id]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#freeze' do
|
||||||
|
before do
|
||||||
|
@chain.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'must be frozen' do
|
||||||
|
expect(@chain).to be_frozen
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an error if try to add a callback when frozen' do
|
||||||
|
expect { @chain.append :authenticate! }.to raise_error RuntimeError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Callbacks::Factory do
|
||||||
|
describe '.fabricate' do
|
||||||
|
before do
|
||||||
|
@callback = Hanami::Utils::Callbacks::Factory.fabricate(callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a callable is passed' do
|
||||||
|
let(:callback) { Callable.new }
|
||||||
|
|
||||||
|
it 'fabricates a Callback' do
|
||||||
|
expect(@callback).to be_kind_of(Hanami::Utils::Callbacks::Callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'wraps the given callback' do
|
||||||
|
expect(@callback.callback).to eq(callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when a symbol is passed' do
|
||||||
|
let(:callback) { :symbolize! }
|
||||||
|
|
||||||
|
it 'fabricates a MethodCallback' do
|
||||||
|
expect(@callback).to be_kind_of(Hanami::Utils::Callbacks::MethodCallback)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'wraps the given callback' do
|
||||||
|
expect(@callback.callback).to eq(callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Callbacks::Callback do
|
||||||
|
before do
|
||||||
|
@callback = Hanami::Utils::Callbacks::Callback.new(callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { proc { |params| logger.push("set_article: #{params[:id]}") } }
|
||||||
|
|
||||||
|
it 'executes self within the given context' do
|
||||||
|
context = Action.new
|
||||||
|
@callback.call(context, id: 23)
|
||||||
|
|
||||||
|
invokation = context.logger.shift
|
||||||
|
expect(invokation).to eq('set_article: 23')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Callbacks::MethodCallback do
|
||||||
|
before do
|
||||||
|
@callback = Hanami::Utils::Callbacks::MethodCallback.new(callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:callback) { :set_article }
|
||||||
|
|
||||||
|
it 'executes self within the given context' do
|
||||||
|
context = Action.new
|
||||||
|
@callback.call(context, id: 23)
|
||||||
|
|
||||||
|
invokation = context.logger.shift
|
||||||
|
expect(invokation).to eq('set_article: 23')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'implements #hash' do
|
||||||
|
cb = Hanami::Utils::Callbacks::MethodCallback.new(callback)
|
||||||
|
expect(cb.send(:hash)).to eq(@callback.send(:hash))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,147 @@
|
||||||
|
require 'hanami/utils/class_attribute'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::ClassAttribute do
|
||||||
|
before do
|
||||||
|
class ClassAttributeTest
|
||||||
|
include Hanami::Utils::ClassAttribute
|
||||||
|
class_attribute :callbacks, :functions, :values
|
||||||
|
self.callbacks = [:a]
|
||||||
|
self.values = [1]
|
||||||
|
end
|
||||||
|
|
||||||
|
class SubclassAttributeTest < ClassAttributeTest
|
||||||
|
class_attribute :subattribute
|
||||||
|
self.functions = %i(x y)
|
||||||
|
self.subattribute = 42
|
||||||
|
end
|
||||||
|
|
||||||
|
class SubSubclassAttributeTest < SubclassAttributeTest
|
||||||
|
end
|
||||||
|
|
||||||
|
class Vehicle
|
||||||
|
include Hanami::Utils::ClassAttribute
|
||||||
|
class_attribute :engines, :wheels
|
||||||
|
|
||||||
|
self.engines = 0
|
||||||
|
self.wheels = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
class Car < Vehicle
|
||||||
|
self.engines = 1
|
||||||
|
self.wheels = 4
|
||||||
|
end
|
||||||
|
|
||||||
|
class Airplane < Vehicle
|
||||||
|
self.engines = 4
|
||||||
|
self.wheels = 16
|
||||||
|
end
|
||||||
|
|
||||||
|
class SmallAirplane < Airplane
|
||||||
|
self.engines = 2
|
||||||
|
self.wheels = 8
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
%i(ClassAttributeTest
|
||||||
|
SubclassAttributeTest
|
||||||
|
SubSubclassAttributeTest
|
||||||
|
Vehicle
|
||||||
|
Car
|
||||||
|
Airplane
|
||||||
|
SmallAirplane).each do |const|
|
||||||
|
Object.send :remove_const, const
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets the given value' do
|
||||||
|
expect(ClassAttributeTest.callbacks).to eq([:a])
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'inheritance' do
|
||||||
|
around do |example|
|
||||||
|
@debug = $DEBUG
|
||||||
|
$DEBUG = true
|
||||||
|
|
||||||
|
example.run
|
||||||
|
|
||||||
|
$DEBUG = @debug
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'the value it is inherited by subclasses' do
|
||||||
|
expect(SubclassAttributeTest.callbacks).to eq([:a])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'if the superclass value changes it does not affects subclasses' do
|
||||||
|
ClassAttributeTest.functions = [:y]
|
||||||
|
expect(SubclassAttributeTest.functions).to eq(%i(x y))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'if the subclass value changes it does not affects superclass' do
|
||||||
|
SubclassAttributeTest.values = [3, 2]
|
||||||
|
expect(ClassAttributeTest.values).to eq([1])
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when the subclass is defined in a different namespace' do
|
||||||
|
before do
|
||||||
|
module Lts
|
||||||
|
module Routing
|
||||||
|
class Resource
|
||||||
|
class Action
|
||||||
|
include Hanami::Utils::ClassAttribute
|
||||||
|
class_attribute :verb
|
||||||
|
end
|
||||||
|
|
||||||
|
class New < Action
|
||||||
|
self.verb = :get
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Resources < Resource
|
||||||
|
class New < Resource::New
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'refers to the superclass value' do
|
||||||
|
expect(Lts::Routing::Resources::New.verb).to eq :get
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# it 'if the subclass value changes it affects subclasses' do
|
||||||
|
# values = [3,2]
|
||||||
|
# SubclassAttributeTest.values = values
|
||||||
|
# expect(SubclassAttributeTest.values).to eq(values)
|
||||||
|
# expect(SubSubclassAttributeTest.values).to eq(values)
|
||||||
|
# end
|
||||||
|
|
||||||
|
it 'if the subclass defines an attribute it should not be available for the superclass' do
|
||||||
|
$DEBUG = @debug
|
||||||
|
expect { ClassAttributeTest.subattribute }.to raise_error(NoMethodError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'if the subclass defines an attribute it should be available for its subclasses' do
|
||||||
|
expect(SubSubclassAttributeTest.subattribute).to eq 42
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'preserves values within the inheritance chain' do
|
||||||
|
expect(Vehicle.engines).to eq 0
|
||||||
|
expect(Vehicle.wheels).to eq 0
|
||||||
|
|
||||||
|
expect(Car.engines).to eq 1
|
||||||
|
expect(Car.wheels).to eq 4
|
||||||
|
|
||||||
|
expect(Airplane.engines).to eq 4
|
||||||
|
expect(Airplane.wheels).to eq 16
|
||||||
|
|
||||||
|
expect(SmallAirplane.engines).to eq 2
|
||||||
|
expect(SmallAirplane.wheels).to eq 8
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't print warnings when it gets inherited" do
|
||||||
|
expect { Class.new(Vehicle) }.not_to output.to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,127 @@
|
||||||
|
require 'hanami/utils/class'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Class do
|
||||||
|
before do
|
||||||
|
class Bar
|
||||||
|
def level
|
||||||
|
'top'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
class Bar
|
||||||
|
def level
|
||||||
|
'nested'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module App
|
||||||
|
module Layer
|
||||||
|
class Step
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Service
|
||||||
|
class Point
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ServicePoint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.load!' do
|
||||||
|
it 'loads the class from the given static string' do
|
||||||
|
expect(Hanami::Utils::Class.load!('App::Layer::Step')).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from the given static string and namespace' do
|
||||||
|
expect(Hanami::Utils::Class.load!('Step', App::Layer)).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from the given class name' do
|
||||||
|
expect(Hanami::Utils::Class.load!(App::Layer::Step)).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an error in case of missing class' do
|
||||||
|
expect { Hanami::Utils::Class.load!('Missing') }.to raise_error(NameError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.load' do
|
||||||
|
it 'loads the class from the given static string' do
|
||||||
|
expect(Hanami::Utils::Class.load('App::Layer::Step')).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from the given static string and namespace' do
|
||||||
|
expect(Hanami::Utils::Class.load('Step', App::Layer)).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from the given class name' do
|
||||||
|
expect(Hanami::Utils::Class.load(App::Layer::Step)).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil in case of missing class' do
|
||||||
|
expect(Hanami::Utils::Class.load('Missing')).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.load_from_pattern!' do
|
||||||
|
it 'loads the class within the given namespace' do
|
||||||
|
klass = Hanami::Utils::Class.load_from_pattern!('(Hanami|Foo)::Bar')
|
||||||
|
expect(klass.new.level).to eq 'nested'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class within the given namespace, when first namespace does not exist' do
|
||||||
|
klass = Hanami::Utils::Class.load_from_pattern!('(NotExisting|Foo)::Bar')
|
||||||
|
expect(klass.new.level).to eq 'nested'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class within the given namespace when first namespace in pattern is correct one' do
|
||||||
|
klass = Hanami::Utils::Class.load_from_pattern!('(Foo|Hanami)::Bar')
|
||||||
|
expect(klass.new.level).to eq 'nested'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from the given static string' do
|
||||||
|
expect(Hanami::Utils::Class.load_from_pattern!('App::Layer::Step')).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error for missing constant' do
|
||||||
|
expect { Hanami::Utils::Class.load_from_pattern!('MissingConstant') }
|
||||||
|
.to raise_error(NameError, 'uninitialized constant MissingConstant')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error for missing constant with multiple alternatives' do
|
||||||
|
expect { Hanami::Utils::Class.load_from_pattern!('Missing(Constant|Class)') }
|
||||||
|
.to raise_error(NameError, 'uninitialized constant Missing(Constant|Class)')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error with full constant name' do
|
||||||
|
expect { Hanami::Utils::Class.load_from_pattern!('Step', App) }
|
||||||
|
.to raise_error(NameError, 'uninitialized constant App::Step')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error with full constant name and multiple alternatives' do
|
||||||
|
expect { Hanami::Utils::Class.load_from_pattern!('(Step|Point)', App) }
|
||||||
|
.to raise_error(NameError, 'uninitialized constant App::(Step|Point)')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from given string, by interpolating tokens' do
|
||||||
|
expect(Hanami::Utils::Class.load_from_pattern!('App::Service(::Point|Point)')).to eq(App::Service::Point)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from given string, by interpolating string tokens and respecting their order' do
|
||||||
|
expect(Hanami::Utils::Class.load_from_pattern!('App::Service(Point|::Point)')).to eq(App::ServicePoint)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads the class from given string, by interpolating tokens and not stopping after first fail' do
|
||||||
|
expect(Hanami::Utils::Class.load_from_pattern!('App::(Layer|Layer::)Step')).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'loads class from given string and namespace' do
|
||||||
|
expect(Hanami::Utils::Class.load_from_pattern!('(Layer|Layer::)Step', App)).to eq(App::Layer::Step)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,39 @@
|
||||||
|
require 'hanami/utils/deprecation'
|
||||||
|
|
||||||
|
class DeprecationTest
|
||||||
|
def old_method
|
||||||
|
Hanami::Utils::Deprecation.new('old_method is deprecated, please use new_method')
|
||||||
|
new_method
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_method
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DeprecationWrapperTest
|
||||||
|
def initialize
|
||||||
|
@engine = DeprecationTest.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
@engine.old_method
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Deprecation do
|
||||||
|
it 'prints a deprecation warning for direct call' do
|
||||||
|
stack = if Hanami::Utils.jruby?
|
||||||
|
"#{__FILE__}:31:in `block in (root)'"
|
||||||
|
else
|
||||||
|
"#{__FILE__}:31:in `block (3 levels) in <top (required)>'"
|
||||||
|
end
|
||||||
|
|
||||||
|
expect { DeprecationTest.new.old_method }
|
||||||
|
.to output(include("old_method is deprecated, please use new_method - called from: #{stack}.")).to_stderr
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prints a deprecation warning for nested call' do
|
||||||
|
expect { DeprecationWrapperTest.new.run }
|
||||||
|
.to output(include("old_method is deprecated, please use new_method - called from: #{__FILE__}:19:in `run'.")).to_stderr
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,99 @@
|
||||||
|
require 'set'
|
||||||
|
require 'bigdecimal'
|
||||||
|
require 'hanami/utils/duplicable'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Duplicable do
|
||||||
|
describe '#dup' do
|
||||||
|
describe 'non duplicable types' do
|
||||||
|
before do
|
||||||
|
@debug = $DEBUG
|
||||||
|
$DEBUG = true
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
$DEBUG = @debug
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup nil" do
|
||||||
|
assert_same_duped_object nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup false" do
|
||||||
|
assert_same_duped_object false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup true" do
|
||||||
|
assert_same_duped_object true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup symbol" do
|
||||||
|
assert_same_duped_object :hanami
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup integer" do
|
||||||
|
assert_same_duped_object 23
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup float" do
|
||||||
|
assert_same_duped_object 3.14
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup bigdecimal" do
|
||||||
|
assert_same_duped_object BigDecimal.new(42)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't dup bignum" do
|
||||||
|
assert_same_duped_object 70_207_105_185_500**64
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'duplicable types' do
|
||||||
|
it 'duplicates array' do
|
||||||
|
assert_different_duped_object [2, [3, 'L']]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'duplicates set' do
|
||||||
|
assert_different_duped_object Set.new(['L'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'duplicates hash' do
|
||||||
|
assert_different_duped_object Hash['L' => 23]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'duplicates string' do
|
||||||
|
assert_different_duped_object 'Hanami'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'duplicates date' do
|
||||||
|
assert_different_duped_object Date.today
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'duplicates time' do
|
||||||
|
assert_different_duped_object Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'duplicates datetime' do
|
||||||
|
assert_different_duped_object DateTime.now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def assert_same_duped_object(object)
|
||||||
|
actual = nil
|
||||||
|
|
||||||
|
expect { actual = Hanami::Utils::Duplicable.dup(object) }
|
||||||
|
.to output(be_empty).to_stderr
|
||||||
|
|
||||||
|
expect(actual).to eq object
|
||||||
|
expect(actual.object_id).to eq object.object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_different_duped_object(object)
|
||||||
|
actual = Hanami::Utils::Duplicable.dup(object)
|
||||||
|
|
||||||
|
expect(actual).to eq object
|
||||||
|
expect(actual.object_id).not_to eq object.object_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,378 @@
|
||||||
|
require 'hanami/utils'
|
||||||
|
require 'hanami/utils/escape'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Escape do
|
||||||
|
let(:mod) { Hanami::Utils::Escape }
|
||||||
|
|
||||||
|
TEST_ENCODINGS = Encoding.name_list.each_with_object(['UTF-8']) do |encoding, result|
|
||||||
|
test_string = '<script>'.encode(Encoding::UTF_8)
|
||||||
|
|
||||||
|
string = begin
|
||||||
|
test_string.encode(encoding)
|
||||||
|
rescue
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
result << encoding if !string.nil? && string != test_string
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.html' do
|
||||||
|
TEST_ENCODINGS.each do |encoding|
|
||||||
|
describe encoding.to_s do
|
||||||
|
it "doesn't escape safe string" do
|
||||||
|
input = Hanami::Utils::Escape::SafeString.new('&')
|
||||||
|
result = mod.html(input.encode(encoding))
|
||||||
|
expect(result).to eq '&'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes nil' do
|
||||||
|
result = mod.html(nil)
|
||||||
|
expect(result).to eq ''
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'test'" do
|
||||||
|
result = mod.html('test'.encode(encoding))
|
||||||
|
expect(result).to eq 'test'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '&'" do
|
||||||
|
result = mod.html('&'.encode(encoding))
|
||||||
|
expect(result).to eq '&'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<'" do
|
||||||
|
result = mod.html('<'.encode(encoding))
|
||||||
|
expect(result).to eq '<'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '>'" do
|
||||||
|
result = mod.html('>'.encode(encoding))
|
||||||
|
expect(result).to eq '>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '"') do
|
||||||
|
result = mod.html('"'.encode(encoding))
|
||||||
|
expect(result).to eq '"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes "'") do
|
||||||
|
result = mod.html("'".encode(encoding))
|
||||||
|
expect(result).to eq '''
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '/'" do
|
||||||
|
result = mod.html('/'.encode(encoding))
|
||||||
|
expect(result).to eq '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<script>'" do
|
||||||
|
result = mod.html('<script>'.encode(encoding))
|
||||||
|
expect(result).to eq '<script>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<scr<script>ipt>'" do
|
||||||
|
result = mod.html('<scr<script>ipt>'.encode(encoding))
|
||||||
|
expect(result).to eq '<scr<script>ipt>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<script>'" do
|
||||||
|
result = mod.html('<script>'.encode(encoding))
|
||||||
|
expect(result).to eq '&lt;script&gt;'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '""><script>xss(5)</script>') do
|
||||||
|
result = mod.html('""><script>xss(5)</script>'.encode(encoding))
|
||||||
|
expect(result).to eq '""><script>xss(5)</script>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '><script>xss(6)</script>') do
|
||||||
|
result = mod.html('><script>xss(6)</script>'.encode(encoding))
|
||||||
|
expect(result).to eq '><script>xss(6)</script>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '# onmouseover="xss(7)" ') do
|
||||||
|
result = mod.html('# onmouseover="xss(7)" '.encode(encoding))
|
||||||
|
expect(result).to eq '# onmouseover="xss(7)" '
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '/" onerror="xss(9)">') do
|
||||||
|
result = mod.html('/" onerror="xss(9)">'.encode(encoding))
|
||||||
|
expect(result).to eq '/" onerror="xss(9)">'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '/ onerror="xss(10)"') do
|
||||||
|
result = mod.html('/ onerror="xss(10)"'.encode(encoding))
|
||||||
|
expect(result).to eq '/ onerror="xss(10)"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '<<script>xss(14);//<</script>') do
|
||||||
|
result = mod.html('<<script>xss(14);//<</script>'.encode(encoding))
|
||||||
|
expect(result).to eq '<<script>xss(14);//<</script>'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes word with different encoding' do
|
||||||
|
skip 'There is no ASCII-8BIT encoding' unless Encoding.name_list.include?('ASCII-8BIT')
|
||||||
|
|
||||||
|
# rubocop:disable Style/AsciiComments
|
||||||
|
# 'тест' means test in russian
|
||||||
|
string = 'тест'.force_encoding('ASCII-8BIT')
|
||||||
|
encoding = string.encoding
|
||||||
|
|
||||||
|
result = mod.html(string)
|
||||||
|
expect(result).to eq 'тест'
|
||||||
|
expect(result.encoding).to eq Encoding::UTF_8
|
||||||
|
|
||||||
|
expect(string.encoding).to eq encoding
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.html_attribute' do
|
||||||
|
TEST_ENCODINGS.each do |encoding|
|
||||||
|
describe encoding.to_s do
|
||||||
|
it "doesn't escape safe string" do
|
||||||
|
input = Hanami::Utils::Escape::SafeString.new('&')
|
||||||
|
result = mod.html_attribute(input.encode(encoding))
|
||||||
|
expect(result).to eq '&'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes nil' do
|
||||||
|
result = mod.html_attribute(nil)
|
||||||
|
expect(result).to eq ''
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'test'" do
|
||||||
|
result = mod.html_attribute('test'.encode(encoding))
|
||||||
|
expect(result).to eq 'test'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '&'" do
|
||||||
|
result = mod.html_attribute('&'.encode(encoding))
|
||||||
|
expect(result).to eq '&'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<'" do
|
||||||
|
result = mod.html_attribute('<'.encode(encoding))
|
||||||
|
expect(result).to eq '<'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '>'" do
|
||||||
|
result = mod.html_attribute('>'.encode(encoding))
|
||||||
|
expect(result).to eq '>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '"') do
|
||||||
|
result = mod.html_attribute('"'.encode(encoding))
|
||||||
|
expect(result).to eq '"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes "'") do
|
||||||
|
result = mod.html_attribute("'".encode(encoding))
|
||||||
|
expect(result).to eq '''
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '/'" do
|
||||||
|
result = mod.html_attribute('/'.encode(encoding))
|
||||||
|
expect(result).to eq '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<script>'" do
|
||||||
|
result = mod.html_attribute('<script>'.encode(encoding))
|
||||||
|
expect(result).to eq '<script>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<scr<script>ipt>'" do
|
||||||
|
result = mod.html_attribute('<scr<script>ipt>'.encode(encoding))
|
||||||
|
expect(result).to eq '<scr<script>ipt>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes '<script>'" do
|
||||||
|
result = mod.html_attribute('<script>'.encode(encoding))
|
||||||
|
expect(result).to eq '&lt;script&gt;'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '""><script>xss(5)</script>') do
|
||||||
|
result = mod.html_attribute('""><script>xss(5)</script>'.encode(encoding))
|
||||||
|
expect(result).to eq '""><script>xss(5)</script>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '><script>xss(6)</script>') do
|
||||||
|
result = mod.html_attribute('><script>xss(6)</script>'.encode(encoding))
|
||||||
|
expect(result).to eq '><script>xss(6)</script>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '# onmouseover="xss(7)" ') do
|
||||||
|
result = mod.html_attribute('# onmouseover="xss(7)" '.encode(encoding))
|
||||||
|
expect(result).to eq '# onmouseover="xss(7)" '
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '/" onerror="xss(9)">') do
|
||||||
|
result = mod.html_attribute('/" onerror="xss(9)">'.encode(encoding))
|
||||||
|
expect(result).to eq '/" onerror="xss(9)">'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '/ onerror="xss(10)"') do
|
||||||
|
result = mod.html_attribute('/ onerror="xss(10)"'.encode(encoding))
|
||||||
|
expect(result).to eq '/ onerror="xss(10)"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes '<<script>xss(14);//<</script>') do
|
||||||
|
result = mod.html_attribute('<<script>xss(14);//<</script>'.encode(encoding))
|
||||||
|
expect(result).to eq '<<script>xss(14);//<</script>'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end # tests with encodings
|
||||||
|
|
||||||
|
TEST_INVALID_CHARS.each do |char, _entity|
|
||||||
|
it "escapes '#{char}'" do
|
||||||
|
result = mod.html_attribute(char)
|
||||||
|
expect(result).to eq "&#x#{TEST_REPLACEMENT_CHAR};"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes tab' do
|
||||||
|
result = mod.html_attribute("\t")
|
||||||
|
expect(result).to eq '	'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes return carriage' do
|
||||||
|
result = mod.html_attribute("\r")
|
||||||
|
expect(result).to eq '
'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes new line' do
|
||||||
|
result = mod.html_attribute("\n")
|
||||||
|
expect(result).to eq '
'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes unicode char' do
|
||||||
|
result = mod.html_attribute('Ā')
|
||||||
|
expect(result).to eq 'Ā'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't escape ','" do
|
||||||
|
result = mod.html_attribute(',')
|
||||||
|
expect(result).to eq ','
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't escape '.'" do
|
||||||
|
result = mod.html_attribute('.')
|
||||||
|
expect(result).to eq '.'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't escape '-'" do
|
||||||
|
result = mod.html_attribute('-')
|
||||||
|
expect(result).to eq '-'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't escape '_'" do
|
||||||
|
result = mod.html_attribute('_')
|
||||||
|
expect(result).to eq '_'
|
||||||
|
end
|
||||||
|
|
||||||
|
TEST_HTML_ENTITIES.each do |char, entity|
|
||||||
|
test_name = Hanami::Utils.jruby? ? char.ord : char
|
||||||
|
|
||||||
|
it "escapes #{test_name}" do
|
||||||
|
result = mod.html_attribute(char)
|
||||||
|
expect(result).to eq "&#{entity};"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end # .html_attribute
|
||||||
|
|
||||||
|
describe '.url' do
|
||||||
|
TEST_ENCODINGS.each do |encoding|
|
||||||
|
describe encoding.to_s do
|
||||||
|
it "doesn't escape safe string" do
|
||||||
|
input = Hanami::Utils::Escape::SafeString.new('javascript:alert(0);')
|
||||||
|
result = mod.url(input.encode(encoding))
|
||||||
|
expect(result).to eq 'javascript:alert(0);'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes nil' do
|
||||||
|
result = mod.url(nil)
|
||||||
|
expect(result).to eq ''
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'test'" do
|
||||||
|
result = mod.url('test'.encode(encoding))
|
||||||
|
expect(result).to eq ''
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'http://hanamirb.org'" do
|
||||||
|
result = mod.url('http://hanamirb.org'.encode(encoding))
|
||||||
|
expect(result).to eq 'http://hanamirb.org'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'https://hanamirb.org'" do
|
||||||
|
result = mod.url('https://hanamirb.org'.encode(encoding))
|
||||||
|
expect(result).to eq 'https://hanamirb.org'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'https://hanamirb.org#introduction'" do
|
||||||
|
result = mod.url('https://hanamirb.org#introduction'.encode(encoding))
|
||||||
|
expect(result).to eq 'https://hanamirb.org#introduction'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'https://hanamirb.org/guides/index.html'" do
|
||||||
|
result = mod.url('https://hanamirb.org/guides/index.html'.encode(encoding))
|
||||||
|
expect(result).to eq 'https://hanamirb.org/guides/index.html'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'mailto:user@example.com'" do
|
||||||
|
result = mod.url('mailto:user@example.com'.encode(encoding))
|
||||||
|
expect(result).to eq 'mailto:user@example.com'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'mailto:user@example.com?Subject=Hello'" do
|
||||||
|
result = mod.url('mailto:user@example.com?Subject=Hello'.encode(encoding))
|
||||||
|
expect(result).to eq 'mailto:user@example.com?Subject=Hello'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "escapes 'javascript:alert(1);'" do
|
||||||
|
result = mod.url('javascript:alert(1);'.encode(encoding))
|
||||||
|
expect(result).to eq ''
|
||||||
|
end
|
||||||
|
|
||||||
|
# See https://github.com/mzsanford/twitter-text-rb/commit/cffce8e60b7557e9945fc0e8b4383e5a66b1558f
|
||||||
|
it %(escapes 'http://x.xx/@"style="color:pink"onmouseover=alert(1)//') do
|
||||||
|
result = mod.url('http://x.xx/@"style="color:pink"onmouseover=alert(1)//'.encode(encoding))
|
||||||
|
expect(result).to eq 'http://x.xx/@'
|
||||||
|
end
|
||||||
|
|
||||||
|
it %{escapes 'http://x.xx/("style="color:red"onmouseover="alert(1)'} do
|
||||||
|
result = mod.url('http://x.xx/("style="color:red"onmouseover="alert(1)'.encode(encoding))
|
||||||
|
expect(result).to eq 'http://x.xx/('
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(escapes 'http://x.xx/@%22style=%22color:pink%22onmouseover=alert(1)//') do
|
||||||
|
result = mod.url('http://x.xx/@%22style=%22color:pink%22onmouseover=alert(1)//'.encode(encoding))
|
||||||
|
expect(result).to eq 'http://x.xx/@'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'encodes non-String objects that respond to `.to_s`' do
|
||||||
|
TEST_ENCODINGS.each do |enc|
|
||||||
|
describe enc.to_s do
|
||||||
|
it 'escapes a Date' do
|
||||||
|
result = mod.html(Date.new(2016, 0o1, 27))
|
||||||
|
expect(result).to eq '2016-01-27'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes a Time' do
|
||||||
|
time_string = Hanami::Utils.jruby? ? '2016-01-27 12:00:00 UTC' : '2016-01-27 12:00:00 +0000'
|
||||||
|
result = mod.html(Time.new(2016, 0o1, 27, 12, 0, 0, 0))
|
||||||
|
expect(result).to eq time_string
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'escapes a DateTime' do
|
||||||
|
result = mod.html(DateTime.new(2016, 0o1, 27, 12, 0, 0, 0))
|
||||||
|
expect(result).to eq '2016-01-27T12:00:00+00:00'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
require 'hanami/utils/file_list'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::FileList do
|
||||||
|
describe '.[]' do
|
||||||
|
it 'returns consistent file list across operating systems' do
|
||||||
|
list = Hanami::Utils::FileList['test/fixtures/file_list/*.rb']
|
||||||
|
expect(list).to eq [
|
||||||
|
'test/fixtures/file_list/a.rb',
|
||||||
|
'test/fixtures/file_list/aa.rb',
|
||||||
|
'test/fixtures/file_list/ab.rb'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,487 @@
|
||||||
|
require 'bigdecimal'
|
||||||
|
require 'hanami/utils/hash'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Hash do
|
||||||
|
describe '#initialize' do
|
||||||
|
let(:input_to_hash) do
|
||||||
|
Class.new do
|
||||||
|
def to_hash
|
||||||
|
Hash[foo: 'bar']
|
||||||
|
end
|
||||||
|
end.new
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:input_to_h) do
|
||||||
|
Class.new do
|
||||||
|
def to_h
|
||||||
|
Hash[head: 'tail']
|
||||||
|
end
|
||||||
|
end.new
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'holds values passed to the constructor' do
|
||||||
|
hash = Hanami::Utils::Hash.new('foo' => 'bar')
|
||||||
|
expect(hash['foo']).to eq('bar')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assigns default via block' do
|
||||||
|
hash = Hanami::Utils::Hash.new { |h, k| h[k] = [] }
|
||||||
|
hash['foo'].push 'bar'
|
||||||
|
|
||||||
|
expect(hash).to eq('foo' => ['bar'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts a Hanami::Utils::Hash' do
|
||||||
|
arg = Hanami::Utils::Hash.new('foo' => 'bar')
|
||||||
|
hash = Hanami::Utils::Hash.new(arg)
|
||||||
|
|
||||||
|
expect(hash.to_h).to be_kind_of(::Hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts object that implements #to_hash' do
|
||||||
|
hash = Hanami::Utils::Hash.new(input_to_hash)
|
||||||
|
|
||||||
|
expect(hash.to_h).to eq(input_to_hash.to_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error when object doesn't implement #to_hash" do
|
||||||
|
expect { Hanami::Utils::Hash.new(input_to_h) }
|
||||||
|
.to raise_error(NoMethodError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#symbolize!' do
|
||||||
|
it 'symbolize keys' do
|
||||||
|
hash = Hanami::Utils::Hash.new('fub' => 'baz')
|
||||||
|
hash.symbolize!
|
||||||
|
|
||||||
|
expect(hash['fub']).to be_nil
|
||||||
|
expect(hash[:fub]).to eq('baz')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not symbolize nested hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new('nested' => { 'key' => 'value' })
|
||||||
|
hash.symbolize!
|
||||||
|
|
||||||
|
expect(hash[:nested].keys).to eq(['key'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#deep_symbolize!' do
|
||||||
|
it 'symbolize keys' do
|
||||||
|
hash = Hanami::Utils::Hash.new('fub' => 'baz')
|
||||||
|
hash.deep_symbolize!
|
||||||
|
|
||||||
|
expect(hash['fub']).to be_nil
|
||||||
|
expect(hash[:fub]).to eq('baz')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'symbolizes nested hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new('nested' => { 'key' => 'value' })
|
||||||
|
hash.deep_symbolize!
|
||||||
|
|
||||||
|
expect(hash[:nested]).to be_kind_of Hanami::Utils::Hash
|
||||||
|
expect(hash[:nested][:key]).to eq('value')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'symbolizes deep nested hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new('nested1' => { 'nested2' => { 'nested3' => { 'key' => 1 } } })
|
||||||
|
hash.deep_symbolize!
|
||||||
|
|
||||||
|
expect(hash.keys).to eq([:nested1])
|
||||||
|
|
||||||
|
hash1 = hash[:nested1]
|
||||||
|
expect(hash1.keys).to eq([:nested2])
|
||||||
|
|
||||||
|
hash2 = hash1[:nested2]
|
||||||
|
expect(hash2.keys).to eq([:nested3])
|
||||||
|
|
||||||
|
hash3 = hash2[:nested3]
|
||||||
|
expect(hash3.keys).to eq([:key])
|
||||||
|
|
||||||
|
expect(hash3[:key]).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'symbolize nested Hanami::Utils::Hashes' do
|
||||||
|
nested = Hanami::Utils::Hash.new('key' => 'value')
|
||||||
|
hash = Hanami::Utils::Hash.new('nested' => nested)
|
||||||
|
hash.deep_symbolize!
|
||||||
|
|
||||||
|
expect(hash[:nested]).to be_kind_of Hanami::Utils::Hash
|
||||||
|
expect(hash[:nested][:key]).to eq('value')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'symbolize nested object that responds to to_hash' do
|
||||||
|
nested = Hanami::Utils::Hash.new('metadata' => WrappingHash.new('coverage' => 100))
|
||||||
|
nested.deep_symbolize!
|
||||||
|
|
||||||
|
expect(nested[:metadata]).to be_kind_of Hanami::Utils::Hash
|
||||||
|
expect(nested[:metadata][:coverage]).to eq(100)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't try to symbolize nested objects" do
|
||||||
|
hash = Hanami::Utils::Hash.new('foo' => ['bar'])
|
||||||
|
hash.deep_symbolize!
|
||||||
|
|
||||||
|
expect(hash[:foo]).to eq(['bar'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#stringify!' do
|
||||||
|
it 'covert keys to strings' do
|
||||||
|
hash = Hanami::Utils::Hash.new(fub: 'baz')
|
||||||
|
hash.stringify!
|
||||||
|
|
||||||
|
expect(hash[:fub]).to be_nil
|
||||||
|
expect(hash['fub']).to eq('baz')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'stringifies nested hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new(nested: { key: 'value' })
|
||||||
|
hash.stringify!
|
||||||
|
|
||||||
|
expect(hash['nested']).to be_kind_of Hanami::Utils::Hash
|
||||||
|
expect(hash['nested']['key']).to eq('value')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'stringifies nested Hanami::Utils::Hashes' do
|
||||||
|
nested = Hanami::Utils::Hash.new(key: 'value')
|
||||||
|
hash = Hanami::Utils::Hash.new(nested: nested)
|
||||||
|
hash.stringify!
|
||||||
|
|
||||||
|
expect(hash['nested']).to be_kind_of Hanami::Utils::Hash
|
||||||
|
expect(hash['nested']['key']).to eq('value')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'stringifies nested object that responds to to_hash' do
|
||||||
|
nested = Hanami::Utils::Hash.new(metadata: WrappingHash.new(coverage: 100))
|
||||||
|
nested.stringify!
|
||||||
|
|
||||||
|
expect(nested['metadata']).to be_kind_of Hanami::Utils::Hash
|
||||||
|
expect(nested['metadata']['coverage']).to eq(100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#deep_dup' do
|
||||||
|
it 'returns an instance of Utils::Hash' do
|
||||||
|
duped = Hanami::Utils::Hash.new('foo' => 'bar').deep_dup
|
||||||
|
expect(duped).to be_kind_of(Hanami::Utils::Hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a hash with duplicated values' do
|
||||||
|
hash = Hanami::Utils::Hash.new('foo' => 'bar', 'baz' => 'x')
|
||||||
|
duped = hash.deep_dup
|
||||||
|
|
||||||
|
duped['foo'] = nil
|
||||||
|
duped['baz'].upcase!
|
||||||
|
|
||||||
|
expect(hash['foo']).to eq('bar')
|
||||||
|
expect(hash['baz']).to eq('x')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't try to duplicate value that can't perform this operation" do
|
||||||
|
original = {
|
||||||
|
'nil' => nil,
|
||||||
|
'false' => false,
|
||||||
|
'true' => true,
|
||||||
|
'symbol' => :symbol,
|
||||||
|
'fixnum' => 23,
|
||||||
|
'bignum' => 13_289_301_283**2,
|
||||||
|
'float' => 1.0,
|
||||||
|
'complex' => Complex(0.3),
|
||||||
|
'bigdecimal' => BigDecimal.new('12.0001'),
|
||||||
|
'rational' => Rational(0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash = Hanami::Utils::Hash.new(original)
|
||||||
|
duped = hash.deep_dup
|
||||||
|
|
||||||
|
expect(duped).to eq(original)
|
||||||
|
expect(duped.object_id).not_to eq(original.object_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a hash with nested duplicated values' do
|
||||||
|
hash = Hanami::Utils::Hash.new('foo' => { 'bar' => 'baz' }, 'x' => Hanami::Utils::Hash.new('y' => 'z'))
|
||||||
|
duped = hash.deep_dup
|
||||||
|
|
||||||
|
duped['foo']['bar'].reverse!
|
||||||
|
duped['x']['y'].upcase!
|
||||||
|
|
||||||
|
expect(hash['foo']['bar']).to eq('baz')
|
||||||
|
expect(hash['x']['y']).to eq('z')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'preserves original class' do
|
||||||
|
duped = Hanami::Utils::Hash.new('foo' => {}, 'x' => Hanami::Utils::Hash.new).deep_dup
|
||||||
|
|
||||||
|
expect(duped['foo']).to be_kind_of(::Hash)
|
||||||
|
expect(duped['x']).to be_kind_of(Hanami::Utils::Hash)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'hash interface' do
|
||||||
|
it 'returns a new Hanami::Utils::Hash for methods which return a ::Hash' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
result = hash.clear
|
||||||
|
|
||||||
|
expect(hash).to be_empty
|
||||||
|
expect(result).to be_kind_of(Hanami::Utils::Hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a value that is compliant with ::Hash return value' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
result = hash.assoc('a')
|
||||||
|
|
||||||
|
expect(result).to eq ['a', 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds to whatever ::Hash responds to' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
|
||||||
|
expect(hash).to respond_to :rehash
|
||||||
|
expect(hash).not_to respond_to :unknown_method
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts blocks for methods' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
result = hash.delete_if { |k, _| k == 'a' }
|
||||||
|
|
||||||
|
expect(result).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_h' do
|
||||||
|
it 'returns a ::Hash' do
|
||||||
|
actual = Hanami::Utils::Hash.new('a' => 1).to_h
|
||||||
|
expect(actual).to eq('a' => 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nested ::Hash' do
|
||||||
|
hash = {
|
||||||
|
tutorial: {
|
||||||
|
instructions: [
|
||||||
|
{ title: 'foo', body: 'bar' },
|
||||||
|
{ title: 'hoge', body: 'fuga' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils_hash = Hanami::Utils::Hash.new(hash)
|
||||||
|
expect(utils_hash).not_to be_kind_of(::Hash)
|
||||||
|
|
||||||
|
actual = utils_hash.to_h
|
||||||
|
expect(actual).to eq(hash)
|
||||||
|
|
||||||
|
expect(actual[:tutorial]).to be_kind_of(::Hash)
|
||||||
|
expect(actual[:tutorial][:instructions]).to all(be_kind_of(::Hash))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nested ::Hash (when symbolized)' do
|
||||||
|
hash = {
|
||||||
|
'tutorial' => {
|
||||||
|
'instructions' => [
|
||||||
|
{ 'title' => 'foo', 'body' => 'bar' },
|
||||||
|
{ 'title' => 'hoge', 'body' => 'fuga' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils_hash = Hanami::Utils::Hash.new(hash).deep_symbolize!
|
||||||
|
expect(utils_hash).not_to be_kind_of(::Hash)
|
||||||
|
|
||||||
|
actual = utils_hash.to_h
|
||||||
|
expect(actual).to eq(hash)
|
||||||
|
|
||||||
|
expect(actual[:tutorial]).to be_kind_of(::Hash)
|
||||||
|
expect(actual[:tutorial][:instructions]).to all(be_kind_of(::Hash))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents information escape' do
|
||||||
|
actual = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
hash = actual.to_h
|
||||||
|
hash['b'] = 2
|
||||||
|
|
||||||
|
expect(actual.to_h).to eq('a' => 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents information escape for nested hash'
|
||||||
|
# it 'prevents information escape for nested hash' do
|
||||||
|
# actual = Hanami::Utils::Hash.new({'a' => {'b' => 2}})
|
||||||
|
# hash = actual.to_h
|
||||||
|
# subhash = hash['a']
|
||||||
|
# subhash.merge!('c' => 3)
|
||||||
|
|
||||||
|
# expect(actual.to_h).to eq({'a' => {'b' => 2}})
|
||||||
|
# end
|
||||||
|
|
||||||
|
it 'serializes nested objects that respond to to_hash' do
|
||||||
|
nested = Hanami::Utils::Hash.new(metadata: WrappingHash.new(coverage: 100))
|
||||||
|
expect(nested.to_h).to eq(metadata: { coverage: 100 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_hash' do
|
||||||
|
it 'returns a ::Hash' do
|
||||||
|
actual = Hanami::Utils::Hash.new('a' => 1).to_hash
|
||||||
|
expect(actual).to eq('a' => 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nested ::Hash' do
|
||||||
|
hash = {
|
||||||
|
tutorial: {
|
||||||
|
instructions: [
|
||||||
|
{ title: 'foo', body: 'bar' },
|
||||||
|
{ title: 'hoge', body: 'fuga' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils_hash = Hanami::Utils::Hash.new(hash)
|
||||||
|
expect(utils_hash).not_to be_kind_of(::Hash)
|
||||||
|
|
||||||
|
actual = utils_hash.to_h
|
||||||
|
expect(actual).to eq(hash)
|
||||||
|
|
||||||
|
expect(actual[:tutorial]).to be_kind_of(::Hash)
|
||||||
|
expect(actual[:tutorial][:instructions]).to all(be_kind_of(::Hash))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nested ::Hash (when symbolized)' do
|
||||||
|
hash = {
|
||||||
|
'tutorial' => {
|
||||||
|
'instructions' => [
|
||||||
|
{ 'title' => 'foo', 'body' => 'bar' },
|
||||||
|
{ 'title' => 'hoge', 'body' => 'fuga' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils_hash = Hanami::Utils::Hash.new(hash).deep_symbolize!
|
||||||
|
expect(utils_hash).not_to be_kind_of(::Hash)
|
||||||
|
|
||||||
|
actual = utils_hash.to_h
|
||||||
|
expect(actual).to eq(hash)
|
||||||
|
|
||||||
|
expect(actual[:tutorial]).to be_kind_of(::Hash)
|
||||||
|
expect(actual[:tutorial][:instructions]).to all(be_kind_of(::Hash))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents information escape' do
|
||||||
|
actual = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
hash = actual.to_hash
|
||||||
|
hash['b'] = 2
|
||||||
|
|
||||||
|
expect(actual.to_hash).to eq('a' => 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_a' do
|
||||||
|
it 'returns an ::Array' do
|
||||||
|
actual = Hanami::Utils::Hash.new('a' => 1).to_a
|
||||||
|
expect(actual).to eq([['a', 1]])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents information escape' do
|
||||||
|
actual = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
array = actual.to_a
|
||||||
|
array.push(['b', 2])
|
||||||
|
|
||||||
|
expect(actual.to_a).to eq([['a', 1]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'equality' do
|
||||||
|
it 'has a working equality' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
other = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
|
||||||
|
expect(hash == other).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working equality with raw hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
expect(hash == { 'a' => 1 }).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'case equality' do
|
||||||
|
it 'has a working case equality' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
other = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
|
||||||
|
expect(hash === other).to be_truthy # rubocop:disable Style/CaseEquality
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working case equality with raw hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
expect(hash === { 'a' => 1 }).to be_truthy # rubocop:disable Style/CaseEquality
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'value equality' do
|
||||||
|
it 'has a working value equality' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
other = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
|
||||||
|
expect(hash).to eql(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working value equality with raw hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
expect(hash).to eql('a' => 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'identity equality' do
|
||||||
|
it 'has a working identity equality' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
expect(hash).to equal(hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working identity equality with raw hashes' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
expect(hash).not_to equal('a' => 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#hash' do
|
||||||
|
it 'returns the same hash result of ::Hash' do
|
||||||
|
expected = { 'l' => 23 }.hash
|
||||||
|
actual = Hanami::Utils::Hash.new('l' => 23).hash
|
||||||
|
|
||||||
|
expect(actual).to eq expected
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#inspect' do
|
||||||
|
it 'returns the same output of ::Hash' do
|
||||||
|
expected = { 'l' => 23, l: 23 }.inspect
|
||||||
|
actual = Hanami::Utils::Hash.new('l' => 23, l: 23).inspect
|
||||||
|
|
||||||
|
expect(actual).to eq expected
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'unknown method' do
|
||||||
|
it 'raises error' do
|
||||||
|
begin
|
||||||
|
Hanami::Utils::Hash.new('l' => 23).party!
|
||||||
|
rescue NoMethodError => e
|
||||||
|
expect(e.message).to eq %(undefined method `party!' for {\"l\"=>23}:Hanami::Utils::Hash)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# See: https://github.com/hanami/utils/issues/48
|
||||||
|
it 'returns the correct object when a NoMethodError is raised' do
|
||||||
|
hash = Hanami::Utils::Hash.new('a' => 1)
|
||||||
|
|
||||||
|
if RUBY_VERSION == '2.4.0' # rubocop:disable Style/ConditionalAssignment
|
||||||
|
exception_message = "undefined method `foo' for 1:Integer"
|
||||||
|
else
|
||||||
|
exception_message = "undefined method `foo' for 1:Fixnum"
|
||||||
|
end
|
||||||
|
|
||||||
|
expect { hash.all? { |_, v| v.foo } }.to raise_error(NoMethodError, include(exception_message))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,140 @@
|
||||||
|
require 'hanami/utils/inflector'
|
||||||
|
require 'hanami/utils/string'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Inflector do
|
||||||
|
describe '.inflections' do
|
||||||
|
it 'adds exception for singular rule' do
|
||||||
|
actual = Hanami::Utils::Inflector.singularize('analyses') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'analysis'
|
||||||
|
|
||||||
|
actual = Hanami::Utils::Inflector.singularize('algae') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'alga'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds exception for plural rule' do
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize('analysis') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'analyses'
|
||||||
|
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize('alga') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'algae'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds exception for uncountable rule' do
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize('music') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'music'
|
||||||
|
|
||||||
|
actual = Hanami::Utils::Inflector.singularize('music') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'music'
|
||||||
|
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize('butter') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'butter'
|
||||||
|
|
||||||
|
actual = Hanami::Utils::Inflector.singularize('butter') # see spec/support/fixtures.rb
|
||||||
|
expect(actual).to eq 'butter'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.pluralize' do
|
||||||
|
it 'returns nil when nil is given' do
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize(nil)
|
||||||
|
expect(actual).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns empty string when empty string is given' do
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize('')
|
||||||
|
expect(actual).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns empty string when empty string is given (multiple chars)' do
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize(string = ' ')
|
||||||
|
expect(actual).to eq string
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns instance of String' do
|
||||||
|
result = Hanami::Utils::Inflector.pluralize('Hanami')
|
||||||
|
expect(result.class).to eq ::String
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't modify original string" do
|
||||||
|
string = 'application'
|
||||||
|
result = Hanami::Utils::Inflector.pluralize(string)
|
||||||
|
|
||||||
|
expect(result.object_id).not_to eq(string.object_id)
|
||||||
|
expect(string).to eq('application')
|
||||||
|
end
|
||||||
|
|
||||||
|
TEST_PLURALS.each do |singular, plural|
|
||||||
|
it %(pluralizes "#{singular}" to "#{plural}") do
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize(singular)
|
||||||
|
expect(actual).to eq plural
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(pluralizes titleized "#{Hanami::Utils::String.new(singular).titleize}" to "#{plural}") do
|
||||||
|
actual = Hanami::Utils::Inflector.pluralize(Hanami::Utils::String.new(singular).titleize)
|
||||||
|
expect(actual).to eq Hanami::Utils::String.new(plural).titleize
|
||||||
|
end
|
||||||
|
|
||||||
|
# it %(doesn't pluralize "#{ plural }" as it's already plural) do
|
||||||
|
# actual = Hanami::Utils::Inflector.pluralize(plural)
|
||||||
|
# expect(actual).to eq plural
|
||||||
|
# end
|
||||||
|
|
||||||
|
# it %(doesn't pluralize titleized "#{ Hanami::Utils::String.new(singular).titleize }" as it's already plural) do
|
||||||
|
# actual = Hanami::Utils::Inflector.pluralize(Hanami::Utils::String.new(plural).titleize)
|
||||||
|
# expect(actual).to eq Hanami::Utils::String.new(plural).titleize
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.singularize' do
|
||||||
|
it 'returns nil when nil is given' do
|
||||||
|
actual = Hanami::Utils::Inflector.singularize(nil)
|
||||||
|
expect(actual).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns empty string when empty string is given' do
|
||||||
|
actual = Hanami::Utils::Inflector.singularize('')
|
||||||
|
expect(actual).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns empty string when empty string is given (multiple chars)' do
|
||||||
|
actual = Hanami::Utils::Inflector.singularize(string = ' ')
|
||||||
|
expect(actual).to eq string
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns instance of String' do
|
||||||
|
result = Hanami::Utils::Inflector.singularize('application')
|
||||||
|
expect(result.class).to eq ::String
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't modify original string" do
|
||||||
|
string = 'applications'
|
||||||
|
result = Hanami::Utils::Inflector.singularize(string)
|
||||||
|
|
||||||
|
expect(result.object_id).not_to eq(string.object_id)
|
||||||
|
expect(string).to eq('applications')
|
||||||
|
end
|
||||||
|
|
||||||
|
TEST_SINGULARS.each do |singular, plural|
|
||||||
|
it %(singularizes "#{plural}" to "#{singular}") do
|
||||||
|
actual = Hanami::Utils::Inflector.singularize(plural)
|
||||||
|
expect(actual).to eq singular
|
||||||
|
end
|
||||||
|
|
||||||
|
it %(singularizes titleized "#{Hanami::Utils::String.new(plural).titleize}" to "#{singular}") do
|
||||||
|
actual = Hanami::Utils::Inflector.singularize(Hanami::Utils::String.new(plural).titleize)
|
||||||
|
expect(actual).to eq Hanami::Utils::String.new(singular).titleize
|
||||||
|
end
|
||||||
|
|
||||||
|
# it %(doesn't singularizes "#{ singular }" as it's already singular) do
|
||||||
|
# actual = Hanami::Utils::Inflector.singularize(singular)
|
||||||
|
# expect(actual).to eq singular
|
||||||
|
# end
|
||||||
|
|
||||||
|
# it %(doesn't singularizes titleized "#{ Hanami::Utils::String.new(plural).titleize }" as it's already singular) do
|
||||||
|
# actual = Hanami::Utils::Inflector.singularize(Hanami::Utils::String.new(singular).titleize)
|
||||||
|
# expect(actual).to Hanami::Utils::String.new(singular).titleize
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
require 'hanami/utils/io'
|
||||||
|
|
||||||
|
class IOTest
|
||||||
|
TEST_CONSTANT = 'initial'.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::IO do
|
||||||
|
describe '.silence_warnings' do
|
||||||
|
it 'lowers verbosity of stdout' do
|
||||||
|
expect do
|
||||||
|
Hanami::Utils::IO.silence_warnings do
|
||||||
|
IOTest::TEST_CONSTANT = 'redefined'.freeze
|
||||||
|
end
|
||||||
|
end.to output(eq('')).to_stderr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,216 @@
|
||||||
|
require 'hanami/utils/load_paths'
|
||||||
|
|
||||||
|
Hanami::Utils::LoadPaths.class_eval do
|
||||||
|
def empty?
|
||||||
|
@paths.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def include?(object)
|
||||||
|
@paths.include?(object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::LoadPaths do
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'can be initialized with zero paths' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new
|
||||||
|
expect(paths).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can be initialized with one path' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '..'
|
||||||
|
expect(paths).to include('..')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can be initialized with more paths' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '..', '../..'
|
||||||
|
|
||||||
|
expect(paths).to include('..')
|
||||||
|
expect(paths).to include('../..')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#each' do
|
||||||
|
it 'coerces the given paths to pathnames and yields a block' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '..', '../..'
|
||||||
|
|
||||||
|
paths.each do |path|
|
||||||
|
expect(path).to be_kind_of Pathname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'remove duplicates' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '..', '..'
|
||||||
|
expect(paths.each(&proc {}).size).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an error if a path is unknown' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new 'unknown/path'
|
||||||
|
|
||||||
|
expect { paths.each {} }.to raise_error(Errno::ENOENT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#push' do
|
||||||
|
it 'adds the given path' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths.push '..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths).to include '..'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds the given paths' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths.push '..', '../..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths).to include '..'
|
||||||
|
expect(paths).to include '../..'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes duplicates' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths.push '.', '.'
|
||||||
|
expect(paths.each(&proc {}).size).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes nil' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths.push nil
|
||||||
|
expect(paths.each(&proc {}).size).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns self so multiple operations can be performed' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new
|
||||||
|
|
||||||
|
returning = paths.push('.')
|
||||||
|
expect(returning).to equal(paths)
|
||||||
|
|
||||||
|
paths.push('..').push('../..')
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths).to include '..'
|
||||||
|
expect(paths).to include '../..'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#<< (alias of #push)' do
|
||||||
|
it 'adds the given path' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths << '..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths).to include '..'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds the given paths' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths << ['..', '../..']
|
||||||
|
|
||||||
|
expect(paths == ['.', '..', '../..']).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns self so multiple operations can be performed' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new
|
||||||
|
|
||||||
|
returning = paths << '.'
|
||||||
|
expect(returning).to equal(paths)
|
||||||
|
|
||||||
|
paths << '..' << '../..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths).to include '..'
|
||||||
|
expect(paths).to include '../..'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#dup' do
|
||||||
|
it 'returns a copy of self' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths2 = paths.dup
|
||||||
|
|
||||||
|
paths << '..'
|
||||||
|
paths2 << '../..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths).to include '..'
|
||||||
|
expect(paths).not_to include '../..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths2).to include '../..'
|
||||||
|
expect(paths2).not_to include '..'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#clone' do
|
||||||
|
it 'returns a copy of self' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new '.'
|
||||||
|
paths2 = paths.clone
|
||||||
|
|
||||||
|
paths << '..'
|
||||||
|
paths2 << '../..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths).to include '..'
|
||||||
|
expect(paths).not_to include '../..'
|
||||||
|
|
||||||
|
expect(paths).to include '.'
|
||||||
|
expect(paths2).to include '../..'
|
||||||
|
expect(paths2).not_to include '..'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#freeze' do
|
||||||
|
it 'freezes the object' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new
|
||||||
|
paths.freeze
|
||||||
|
|
||||||
|
expect(paths).to be_frozen
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't allow to push paths" do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new
|
||||||
|
paths.freeze
|
||||||
|
|
||||||
|
expect { paths.push '.' }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#==' do
|
||||||
|
it 'checks equality with LoadPaths' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new('.', '.')
|
||||||
|
other = Hanami::Utils::LoadPaths.new('.')
|
||||||
|
|
||||||
|
expect(paths == other).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "it returns false if the paths aren't equal" do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new('.', '..')
|
||||||
|
other = Hanami::Utils::LoadPaths.new('.')
|
||||||
|
|
||||||
|
expect(paths == other).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'checks equality with Array' do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new('.', '.')
|
||||||
|
other = ['.']
|
||||||
|
|
||||||
|
expect(paths == other).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "it returns false if given array isn't equal" do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new('.', '..')
|
||||||
|
other = ['.']
|
||||||
|
|
||||||
|
expect(paths == other).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it "it returns false the type isn't matchable" do
|
||||||
|
paths = Hanami::Utils::LoadPaths.new('.', '..')
|
||||||
|
other = nil
|
||||||
|
|
||||||
|
expect(paths == other).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,188 @@
|
||||||
|
require 'hanami/utils/path_prefix'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::PathPrefix do
|
||||||
|
it 'exposes itself as a string' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new
|
||||||
|
expect(prefix).to eq('')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds root prefix only when needed' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('/fruits')
|
||||||
|
expect(prefix).to eq('/fruits')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#join' do
|
||||||
|
it 'returns a PathPrefix' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('orders', '?').join('new')
|
||||||
|
expect(prefix).to be_kind_of(Hanami::Utils::PathPrefix)
|
||||||
|
expect(prefix.__send__(:separator)).to eq('?')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a string' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect(prefix.join('peaches')).to eq '/fruits/peaches'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a prefixed string' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect(prefix.join('/cherries')).to eq '/fruits/cherries'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a string that is the same as the prefix' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect(prefix.join('fruits')).to eq '/fruits/fruits'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a string when the root is blank' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new
|
||||||
|
expect(prefix.join('tea')).to eq '/tea'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a prefixed string when the root is blank' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new
|
||||||
|
expect(prefix.join('/tea')).to eq '/tea'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins multiple strings' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new
|
||||||
|
expect(prefix.join('assets', 'application.js')).to eq '/assets/application.js'
|
||||||
|
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('myapp')
|
||||||
|
expect(prefix.join('assets', 'application.js')).to eq '/myapp/assets/application.js'
|
||||||
|
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('/myapp')
|
||||||
|
expect(prefix.join('/assets', 'application.js')).to eq '/myapp/assets/application.js'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rejects entries that are matching separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('/assets')
|
||||||
|
expect(prefix.join('/')).to eq '/assets'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes trailing occurrences of separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('curcuma')
|
||||||
|
expect(prefix.join(nil)).to eq '/curcuma'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#relative_join' do
|
||||||
|
it 'returns a PathPrefix' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('orders', '&').relative_join('new')
|
||||||
|
expect(prefix).to be_kind_of(Hanami::Utils::PathPrefix)
|
||||||
|
expect(prefix.__send__(:separator)).to eq('&')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a string without prefixing with separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect(prefix.relative_join('peaches')).to eq 'fruits/peaches'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a prefixed string without prefixing with separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect(prefix.relative_join('/cherries')).to eq 'fruits/cherries'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a string when the root is blank without prefixing with separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new
|
||||||
|
expect(prefix.relative_join('tea')).to eq 'tea'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a prefixed string when the root is blank and removes the prefix' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new
|
||||||
|
expect(prefix.relative_join('/tea')).to eq 'tea'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a string with custom separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect(prefix.relative_join('cherries', '_')).to eq 'fruits_cherries'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'joins a prefixed string without prefixing with custom separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect(prefix.relative_join('_cherries', '_')).to eq 'fruits_cherries'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'changes all the occurences of the current separator with the given one' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('?fruits', '?')
|
||||||
|
expect(prefix.relative_join('cherries', '_')).to eq 'fruits_cherries'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes trailing occurrences of separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('jojoba')
|
||||||
|
expect(prefix.relative_join(nil)).to eq 'jojoba'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rejects entries that are matching separator' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('assets')
|
||||||
|
expect(prefix.relative_join('/')).to eq 'assets'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error if the given separator is nil' do
|
||||||
|
prefix = Hanami::Utils::PathPrefix.new('fruits')
|
||||||
|
expect { prefix.relative_join('_cherries', nil) }.to raise_error(TypeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'string interface' do
|
||||||
|
describe 'equality' do
|
||||||
|
it 'has a working equality' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
other = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
|
||||||
|
expect(string).to eq(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working equality with raw strings' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
expect(string).to eq('hanami')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'case equality' do
|
||||||
|
it 'has a working case equality' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
other = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
expect(string === other).to be_truthy # rubocop:disable Style/CaseEquality
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working case equality with raw strings' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
expect(string === 'hanami').to be_truthy # rubocop:disable Style/CaseEquality
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'value equality' do
|
||||||
|
it 'has a working value equality' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
other = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
expect(string).to eql(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working value equality with raw strings' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
expect(string).to eql('hanami')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'identity equality' do
|
||||||
|
it 'has a working identity equality' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
expect(string).to equal(string)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working identity equality with raw strings' do
|
||||||
|
string = Hanami::Utils::PathPrefix.new('hanami')
|
||||||
|
expect(string).not_to equal('hanami')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#hash' do
|
||||||
|
it 'returns the same hash result of ::String' do
|
||||||
|
expected = 'hello'.hash
|
||||||
|
actual = Hanami::Utils::PathPrefix.new('hello').hash
|
||||||
|
|
||||||
|
expect(actual).to eq expected
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,423 @@
|
||||||
|
require 'hanami/utils/string'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::String do
|
||||||
|
describe '#titleize' do
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
expect(Hanami::Utils::String.new('hanami').titleize).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not mutate self' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
string.titleize
|
||||||
|
expect(string).to eq('hanami')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an titleized string' do
|
||||||
|
expect(Hanami::Utils::String.new('hanami').titleize).to eq('Hanami')
|
||||||
|
expect(Hanami::Utils::String.new('HanamiUtils').titleize).to eq('Hanami Utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami utils').titleize).to eq('Hanami Utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami_utils').titleize).to eq('Hanami Utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami-utils').titleize).to eq('Hanami Utils')
|
||||||
|
expect(Hanami::Utils::String.new("hanami' utils").titleize).to eq("Hanami' Utils")
|
||||||
|
expect(Hanami::Utils::String.new('hanami’ utils').titleize).to eq('Hanami’ Utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami` utils').titleize).to eq('Hanami` Utils')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#capitalize' do
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
expect(Hanami::Utils::String.new('hanami').capitalize).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't mutate self" do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
string.capitalize
|
||||||
|
expect(string).to eq('hanami')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an capitalized string' do
|
||||||
|
expect(Hanami::Utils::String.new('hanami').capitalize).to eq('Hanami')
|
||||||
|
expect(Hanami::Utils::String.new('HanamiUtils').capitalize).to eq('Hanami utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami utils').capitalize).to eq('Hanami utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami_utils').capitalize).to eq('Hanami utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami-utils').capitalize).to eq('Hanami utils')
|
||||||
|
expect(Hanami::Utils::String.new("hanami' utils").capitalize).to eq("Hanami' utils")
|
||||||
|
expect(Hanami::Utils::String.new('hanami’ utils').capitalize).to eq('Hanami’ utils')
|
||||||
|
expect(Hanami::Utils::String.new('hanami` utils').capitalize).to eq('Hanami` utils')
|
||||||
|
expect(Hanami::Utils::String.new('OneTwoThree').capitalize).to eq('One two three')
|
||||||
|
expect(Hanami::Utils::String.new('one Two three').capitalize).to eq('One two three')
|
||||||
|
expect(Hanami::Utils::String.new('one_two_three').capitalize).to eq('One two three')
|
||||||
|
expect(Hanami::Utils::String.new('one-two-three').capitalize).to eq('One two three')
|
||||||
|
|
||||||
|
expect(Hanami::Utils::String.new(:HanamiUtils).capitalize).to eq('Hanami utils')
|
||||||
|
expect(Hanami::Utils::String.new(:'hanami utils').capitalize).to eq('Hanami utils')
|
||||||
|
expect(Hanami::Utils::String.new(:hanami_utils).capitalize).to eq('Hanami utils')
|
||||||
|
expect(Hanami::Utils::String.new(:'hanami-utils').capitalize).to eq('Hanami utils')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#classify' do
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
expect(Hanami::Utils::String.new('hanami').classify).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a classified string' do
|
||||||
|
expect(Hanami::Utils::String.new('hanami').classify).to eq('Hanami')
|
||||||
|
expect(Hanami::Utils::String.new('hanami_router').classify).to eq('HanamiRouter')
|
||||||
|
expect(Hanami::Utils::String.new('hanami-router').classify).to eq('Hanami::Router')
|
||||||
|
expect(Hanami::Utils::String.new('hanami/router').classify).to eq('Hanami::Router')
|
||||||
|
expect(Hanami::Utils::String.new('hanami::router').classify).to eq('Hanami::Router')
|
||||||
|
expect(Hanami::Utils::String.new('hanami::router/base_object').classify).to eq('Hanami::Router::BaseObject')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a classified string from symbol' do
|
||||||
|
expect(Hanami::Utils::String.new(:hanami).classify).to eq('Hanami')
|
||||||
|
expect(Hanami::Utils::String.new(:hanami_router).classify).to eq('HanamiRouter')
|
||||||
|
expect(Hanami::Utils::String.new(:'hanami-router').classify).to eq('Hanami::Router')
|
||||||
|
expect(Hanami::Utils::String.new(:'hanami/router').classify).to eq('Hanami::Router')
|
||||||
|
expect(Hanami::Utils::String.new(:'hanami::router').classify).to eq('Hanami::Router')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not remove capital letter in string' do
|
||||||
|
expect(Hanami::Utils::String.new('AwesomeProject').classify).to eq('AwesomeProject')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#underscore' do
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
expect(Hanami::Utils::String.new('Hanami').underscore).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not mutate itself' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami')
|
||||||
|
string.underscore
|
||||||
|
expect(string).to eq('Hanami')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes all the upcase characters' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami')
|
||||||
|
expect(string.underscore).to eq('hanami')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'transforms camel case class names' do
|
||||||
|
string = Hanami::Utils::String.new('HanamiView')
|
||||||
|
expect(string.underscore).to eq('hanami_view')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'substitutes double colons with path separators' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami::Utils::String')
|
||||||
|
expect(string.underscore).to eq('hanami/utils/string')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles acronyms' do
|
||||||
|
string = Hanami::Utils::String.new('APIDoc')
|
||||||
|
expect(string.underscore).to eq('api_doc')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles numbers' do
|
||||||
|
string = Hanami::Utils::String.new('Lucky23Action')
|
||||||
|
expect(string.underscore).to eq('lucky23_action')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles dashes' do
|
||||||
|
string = Hanami::Utils::String.new('hanami-utils')
|
||||||
|
expect(string.underscore).to eq('hanami_utils')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles spaces' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami Utils')
|
||||||
|
expect(string.underscore).to eq('hanami_utils')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles accented letters' do
|
||||||
|
string = Hanami::Utils::String.new('è vero')
|
||||||
|
expect(string.underscore).to eq('è_vero')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#dasherize' do
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
expect(Hanami::Utils::String.new('Hanami').dasherize).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not mutate itself' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami')
|
||||||
|
string.dasherize
|
||||||
|
expect(string).to eq('Hanami')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes all the upcase characters' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami')
|
||||||
|
expect(string.dasherize).to eq('hanami')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'transforms camel case class names' do
|
||||||
|
string = Hanami::Utils::String.new('HanamiView')
|
||||||
|
expect(string.dasherize).to eq('hanami-view')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles acronyms' do
|
||||||
|
string = Hanami::Utils::String.new('APIDoc')
|
||||||
|
expect(string.dasherize).to eq('api-doc')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles numbers' do
|
||||||
|
string = Hanami::Utils::String.new('Lucky23Action')
|
||||||
|
expect(string.dasherize).to eq('lucky23-action')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles underscores' do
|
||||||
|
string = Hanami::Utils::String.new('hanami_utils')
|
||||||
|
expect(string.dasherize).to eq('hanami-utils')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles spaces' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami Utils')
|
||||||
|
expect(string.dasherize).to eq('hanami-utils')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles accented letters' do
|
||||||
|
string = Hanami::Utils::String.new('è vero')
|
||||||
|
expect(string.dasherize).to eq('è-vero')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#demodulize' do
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
expect(Hanami::Utils::String.new('Hanami').demodulize).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the class name without the namespace' do
|
||||||
|
expect(Hanami::Utils::String.new('String').demodulize).to eq('String')
|
||||||
|
expect(Hanami::Utils::String.new('Hanami::Utils::String').demodulize).to eq('String')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#namespace' do
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
expect(Hanami::Utils::String.new('Hanami').namespace).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the top level module name' do
|
||||||
|
expect(Hanami::Utils::String.new('String').namespace).to eq('String')
|
||||||
|
expect(Hanami::Utils::String.new('Hanami::Utils::String').namespace).to eq('Hanami')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#tokenize' do
|
||||||
|
before do
|
||||||
|
@logger = []
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an instance of Hanami::Utils::String' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami::(Utils|App)')
|
||||||
|
string.tokenize do |token|
|
||||||
|
@logger.push token
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(@logger).to all(be_kind_of(Hanami::Utils::String))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls the given block for each token occurrence' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami::(Utils|App)')
|
||||||
|
string.tokenize do |token|
|
||||||
|
@logger.push token
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(@logger).to eq(['Hanami::Utils', 'Hanami::App'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'guarantees the block to be called even when the token conditions are not met' do
|
||||||
|
string = Hanami::Utils::String.new('Hanami')
|
||||||
|
string.tokenize do |token|
|
||||||
|
@logger.push token
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(@logger).to eq(['Hanami'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
result = Hanami::Utils::String.new('Hanami::(Utils|App)').tokenize {}
|
||||||
|
expect(result).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#pluralize' do
|
||||||
|
before do
|
||||||
|
@singular, @plural = *TEST_PLURALS.to_a.sample
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a Hanami::Utils::String instance' do
|
||||||
|
result = Hanami::Utils::String.new(@singular).pluralize
|
||||||
|
expect(result).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'pluralizes string' do
|
||||||
|
result = Hanami::Utils::String.new(@singular).pluralize
|
||||||
|
expect(result).to eq(@plural)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not modify the original string' do
|
||||||
|
string = Hanami::Utils::String.new(@singular)
|
||||||
|
|
||||||
|
expect(string.pluralize).to eq(@plural)
|
||||||
|
expect(string).to eq(@singular)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#singularize' do
|
||||||
|
before do
|
||||||
|
@singular, @plural = *TEST_SINGULARS.to_a.sample
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a Hanami::Utils::String instance' do
|
||||||
|
result = Hanami::Utils::String.new(@plural).singularize
|
||||||
|
expect(result).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'singularizes string' do
|
||||||
|
result = Hanami::Utils::String.new(@plural).singularize
|
||||||
|
expect(result).to eq(@singular)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not modify the original string' do
|
||||||
|
string = Hanami::Utils::String.new(@plural)
|
||||||
|
|
||||||
|
expect(string.singularize).to eq(@singular)
|
||||||
|
expect(string).to eq(@plural)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#rsub' do
|
||||||
|
it 'returns a Hanami::Utils::String instance' do
|
||||||
|
result = Hanami::Utils::String.new('authors/books/index').rsub(//, '')
|
||||||
|
expect(result).to be_kind_of(Hanami::Utils::String)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not mutate original string' do
|
||||||
|
string = Hanami::Utils::String.new('authors/books/index')
|
||||||
|
string.rsub(%r{/}, '#')
|
||||||
|
|
||||||
|
expect(string).to eq('authors/books/index')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'replaces rightmost instance (regexp)' do
|
||||||
|
result = Hanami::Utils::String.new('authors/books/index').rsub(%r{/}, '#')
|
||||||
|
expect(result).to eq('authors/books#index')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'replaces rightmost instance (string)' do
|
||||||
|
result = Hanami::Utils::String.new('authors/books/index').rsub('/', '#')
|
||||||
|
expect(result).to eq('authors/books#index')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts Hanami::Utils::String as replacement' do
|
||||||
|
replacement = Hanami::Utils::String.new('#')
|
||||||
|
result = Hanami::Utils::String.new('authors/books/index').rsub(%r{/}, replacement)
|
||||||
|
|
||||||
|
expect(result).to eq('authors/books#index')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the initial string no match' do
|
||||||
|
result = Hanami::Utils::String.new('index').rsub(%r{/}, '#')
|
||||||
|
expect(result).to eq('index')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'string interface' do
|
||||||
|
it 'responds to ::String methods and returns a new Hanami::Utils::String' do
|
||||||
|
string = Hanami::Utils::String.new("Hanami\n").chomp
|
||||||
|
expect(string).to eq('Hanami')
|
||||||
|
expect(string).to be_kind_of Hanami::Utils::String
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds to ::String methods and only returns a new Hanami::Utils::String when the return value is a string' do
|
||||||
|
string = Hanami::Utils::String.new('abcdef')
|
||||||
|
expect(string.casecmp('abcde')).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'responds to whatever ::String responds to' do
|
||||||
|
string = Hanami::Utils::String.new('abcdef')
|
||||||
|
|
||||||
|
expect(string).to respond_to :reverse
|
||||||
|
expect(string).not_to respond_to :unknown_method
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'equality' do
|
||||||
|
it 'has a working equality' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
other = Hanami::Utils::String.new('hanami')
|
||||||
|
|
||||||
|
expect(string.==(other)).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working equality with raw strings' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
expect(string.==('hanami')).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'case equality' do
|
||||||
|
it 'has a working case equality' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
other = Hanami::Utils::String.new('hanami')
|
||||||
|
expect(string.===(other)).to be_truthy # rubocop:disable Style/CaseEquality
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working case equality with raw strings' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
expect(string.===('hanami')).to be_truthy # rubocop:disable Style/CaseEquality
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'value equality' do
|
||||||
|
it 'has a working value equality' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
other = Hanami::Utils::String.new('hanami')
|
||||||
|
expect(string).to eql(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working value equality with raw strings' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
expect(string).to eql('hanami')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'identity equality' do
|
||||||
|
it 'has a working identity equality' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
expect(string).to equal(string)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a working identity equality with raw strings' do
|
||||||
|
string = Hanami::Utils::String.new('hanami')
|
||||||
|
expect(string).not_to equal('hanami')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#hash' do
|
||||||
|
it 'returns the same hash result of ::String' do
|
||||||
|
expected = 'hello'.hash
|
||||||
|
actual = Hanami::Utils::String.new('hello').hash
|
||||||
|
|
||||||
|
expect(actual).to eq(expected)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'unknown method' do
|
||||||
|
it 'raises error' do
|
||||||
|
expect { Hanami::Utils::String.new('one').yay! }
|
||||||
|
.to raise_error(NoMethodError, %(undefined method `yay!' for "one":Hanami::Utils::String))
|
||||||
|
end
|
||||||
|
|
||||||
|
# See: https://github.com/hanami/utils/issues/48
|
||||||
|
it 'returns the correct object when a NoMethodError is raised' do
|
||||||
|
string = Hanami::Utils::String.new('/path/to/something')
|
||||||
|
exception_message = %(undefined method `boom' for "/":String)
|
||||||
|
|
||||||
|
expect { string.gsub(%r{/}, &:boom) }
|
||||||
|
.to raise_error(NoMethodError, exception_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
RSpec.describe Hanami::Utils do
|
||||||
|
describe '.jruby?' do
|
||||||
|
it 'introspects the current platform' do
|
||||||
|
if RUBY_PLATFORM == 'java'
|
||||||
|
expect(Hanami::Utils.jruby?).to eq(true)
|
||||||
|
else
|
||||||
|
expect(Hanami::Utils.jruby?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.rubinius?' do
|
||||||
|
it 'introspects the current platform' do
|
||||||
|
if RUBY_ENGINE == 'rbx'
|
||||||
|
expect(Hanami::Utils.rubinius?).to eq(true)
|
||||||
|
else
|
||||||
|
expect(Hanami::Utils.rubinius?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
RSpec.describe Hanami::Utils::VERSION do
|
||||||
|
it 'exposes version' do
|
||||||
|
expect(Hanami::Utils::VERSION).to eq('1.0.0.beta3')
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
--color
|
|
@ -0,0 +1,29 @@
|
||||||
|
require_relative '../../support/isolation_spec_helper'
|
||||||
|
require 'hanami/utils/json'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Json do
|
||||||
|
describe 'with JSON' do
|
||||||
|
it 'uses JSON engine' do
|
||||||
|
expect(Hanami::Utils::Json.class_variable_get(:@@engine)).to eq(JSON)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.parse' do
|
||||||
|
it 'loads given payload' do
|
||||||
|
actual = Hanami::Utils::Json.parse %({"a":1})
|
||||||
|
expect(actual).to eq('a' => 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error if given payload is malformed' do
|
||||||
|
expect { Hanami::Utils::Json.parse %({"a:1}) }.to raise_error(Hanami::Utils::Json::ParserError)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See: https://github.com/hanami/utils/issues/169
|
||||||
|
it "doesn't eval payload" do
|
||||||
|
actual = Hanami::Utils::Json.parse %({"json_class": "Foo"})
|
||||||
|
expect(actual).to eq('json_class' => 'Foo')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Support::Runner.run
|
|
@ -0,0 +1,40 @@
|
||||||
|
require_relative '../../support/isolation_spec_helper'
|
||||||
|
|
||||||
|
Bundler.require(:default, :development, :multi_json)
|
||||||
|
|
||||||
|
require 'gson' if Hanami::Utils.jruby?
|
||||||
|
require 'hanami/utils/json'
|
||||||
|
|
||||||
|
RSpec.describe Hanami::Utils::Json do
|
||||||
|
describe 'with MultiJson' do
|
||||||
|
it 'uses MultiJson engine' do
|
||||||
|
expect(Hanami::Utils::Json.class_variable_get(:@@engine)).to be_kind_of(Hanami::Utils::Json::MultiJsonAdapter)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.parse' do
|
||||||
|
it 'loads given payload' do
|
||||||
|
actual = Hanami::Utils::Json.parse %({"a":1})
|
||||||
|
expect(actual).to eq('a' => 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error if given payload is malformed' do
|
||||||
|
expect { Hanami::Utils::Json.parse %({"a:1}) }.to raise_error(Hanami::Utils::Json::ParserError)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See: https://github.com/hanami/utils/issues/169
|
||||||
|
it "doesn't eval payload" do
|
||||||
|
actual = Hanami::Utils::Json.parse %({"json_class": "Foo"})
|
||||||
|
expect(actual).to eq('json_class' => 'Foo')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.generate' do
|
||||||
|
it 'dumps given Hash' do
|
||||||
|
actual = Hanami::Utils::Json.generate(a: 1)
|
||||||
|
expect(actual).to eq %({"a":1})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Support::Runner.run
|
|
@ -0,0 +1,44 @@
|
||||||
|
require_relative '../support/isolation_spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Hanami::Utils.reload!' do
|
||||||
|
before do
|
||||||
|
FileUtils.rm_rf(root) if root.exist?
|
||||||
|
root.mkpath
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
FileUtils.rm_rf(root.parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:root) { Pathname.new(Dir.pwd).join('tmp', 'reload') }
|
||||||
|
|
||||||
|
it 'reloads the files set of files' do
|
||||||
|
File.open(root.join('user.rb'), 'w+') do |f|
|
||||||
|
f.write <<-EOF
|
||||||
|
class User
|
||||||
|
def greet
|
||||||
|
"Hi"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
|
||||||
|
Hanami::Utils.reload!(root)
|
||||||
|
expect(User.new.greet).to eq "Hi"
|
||||||
|
|
||||||
|
File.open(root.join('user.rb'), 'w+') do |f|
|
||||||
|
f.write <<-EOF
|
||||||
|
class User
|
||||||
|
def greet
|
||||||
|
"Ciao"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
|
||||||
|
Hanami::Utils.reload!(root)
|
||||||
|
expect(User.new.greet).to eq "Ciao"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Support::Runner.run
|
|
@ -0,0 +1,17 @@
|
||||||
|
require_relative '../../support/isolation_spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Hanami::Utils.require!' do
|
||||||
|
describe 'with absolute path' do
|
||||||
|
it 'requires ordered set of files' do
|
||||||
|
directory = Pathname.new(Dir.pwd).join('spec', 'support', 'fixtures', 'file_list')
|
||||||
|
Hanami::Utils.require!(directory)
|
||||||
|
|
||||||
|
expect(defined?(A)).to be_truthy, 'expected A to be defined'
|
||||||
|
expect(defined?(Aa)).to be_truthy, 'expected Aa to be defined'
|
||||||
|
expect(defined?(Ab)).to be_truthy, 'expected Ab to be defined'
|
||||||
|
expect(defined?(C)).to be_truthy, 'expected C to be defined'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Support::Runner.run
|
|
@ -0,0 +1,22 @@
|
||||||
|
require_relative '../../support/isolation_spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Hanami::Utils.require!' do
|
||||||
|
describe 'with file separator' do
|
||||||
|
it 'applies current operating system file separator' do
|
||||||
|
# Invert the file separator for the current operating system:
|
||||||
|
#
|
||||||
|
# * on *NIX systems, instead of having /, we get \
|
||||||
|
# * on Windows systems, instead of having \, we get /
|
||||||
|
separator = File::SEPARATOR == '/' ? '\\' : '/'
|
||||||
|
directory = %w(spec support fixtures file_list).join(separator)
|
||||||
|
Hanami::Utils.require!(directory)
|
||||||
|
|
||||||
|
expect(defined?(A)).to be_truthy, 'expected A to be defined'
|
||||||
|
expect(defined?(Aa)).to be_truthy, 'expected Aa to be defined'
|
||||||
|
expect(defined?(Ab)).to be_truthy, 'expected Ab to be defined'
|
||||||
|
expect(defined?(C)).to be_truthy, 'expected C to be defined'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Support::Runner.run
|
|
@ -0,0 +1,17 @@
|
||||||
|
require_relative '../../support/isolation_spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Hanami::Utils.require!' do
|
||||||
|
describe 'with file separator' do
|
||||||
|
it 'requires ordered set of files' do
|
||||||
|
directory = %w(spec support fixtures file_list ** *.rb).join(File::SEPARATOR)
|
||||||
|
Hanami::Utils.require!(directory)
|
||||||
|
|
||||||
|
expect(defined?(A)).to be_truthy, 'expected A to be defined'
|
||||||
|
expect(defined?(Aa)).to be_truthy, 'expected Aa to be defined'
|
||||||
|
expect(defined?(Ab)).to be_truthy, 'expected Ab to be defined'
|
||||||
|
expect(defined?(C)).to be_truthy, 'expected C to be defined'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Support::Runner.run
|
|
@ -0,0 +1,17 @@
|
||||||
|
require_relative '../../support/isolation_spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Hanami::Utils.require!' do
|
||||||
|
describe 'with relative path' do
|
||||||
|
it 'requires ordered set of files' do
|
||||||
|
directory = Pathname.new('spec').join('support', 'fixtures', 'file_list')
|
||||||
|
Hanami::Utils.require!(directory)
|
||||||
|
|
||||||
|
expect(defined?(A)).to be_truthy, 'expected A to be defined'
|
||||||
|
expect(defined?(Aa)).to be_truthy, 'expected Aa to be defined'
|
||||||
|
expect(defined?(Ab)).to be_truthy, 'expected Ab to be defined'
|
||||||
|
expect(defined?(C)).to be_truthy, 'expected C to be defined'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Support::Runner.run
|
|
@ -0,0 +1,9 @@
|
||||||
|
if ENV['COVERALL']
|
||||||
|
require 'coveralls'
|
||||||
|
Coveralls.wear!
|
||||||
|
end
|
||||||
|
|
||||||
|
$LOAD_PATH.unshift 'lib'
|
||||||
|
require 'hanami/utils'
|
||||||
|
|
||||||
|
Hanami::Utils.require!("spec/support")
|
|
@ -0,0 +1,2 @@
|
||||||
|
class A
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
class Aa
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
class Ab
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
class C
|
||||||
|
end
|
|
@ -0,0 +1,628 @@
|
||||||
|
TEST_REPLACEMENT_CHAR = 'fffd'.freeze
|
||||||
|
TEST_INVALID_CHARS = ((0x0..0x8).to_a + (0x11..0x12).to_a + (0x14..0x1f).to_a).flatten.each_with_object({}) do |char, result|
|
||||||
|
char = char.chr(Encoding::UTF_8)
|
||||||
|
result[char] = TEST_REPLACEMENT_CHAR
|
||||||
|
end
|
||||||
|
|
||||||
|
TEST_HTML_ENTITIES = {
|
||||||
|
'"' => 'quot',
|
||||||
|
'&' => 'amp',
|
||||||
|
'<' => 'lt',
|
||||||
|
'>' => 'gt',
|
||||||
|
' ' => 'nbsp',
|
||||||
|
'¡' => 'iexcl',
|
||||||
|
'¢' => 'cent',
|
||||||
|
'£' => 'pound',
|
||||||
|
'¤' => 'curren',
|
||||||
|
'¥' => 'yen',
|
||||||
|
'¦' => 'brvbar',
|
||||||
|
'§' => 'sect',
|
||||||
|
'¨' => 'uml',
|
||||||
|
'©' => 'copy',
|
||||||
|
'ª' => 'ordf',
|
||||||
|
'«' => 'laquo',
|
||||||
|
'¬' => 'not',
|
||||||
|
'' => 'shy',
|
||||||
|
'®' => 'reg',
|
||||||
|
'¯' => 'macr',
|
||||||
|
'°' => 'deg',
|
||||||
|
'±' => 'plusmn',
|
||||||
|
'²' => 'sup2',
|
||||||
|
'³' => 'sup3',
|
||||||
|
'´' => 'acute',
|
||||||
|
'µ' => 'micro',
|
||||||
|
'¶' => 'para',
|
||||||
|
'·' => 'middot',
|
||||||
|
'¸' => 'cedil',
|
||||||
|
'¹' => 'sup1',
|
||||||
|
'º' => 'ordm',
|
||||||
|
'»' => 'raquo',
|
||||||
|
'¼' => 'frac14',
|
||||||
|
'½' => 'frac12',
|
||||||
|
'¾' => 'frac34',
|
||||||
|
'¿' => 'iquest',
|
||||||
|
'À' => 'Agrave',
|
||||||
|
'Á' => 'Aacute',
|
||||||
|
'Â' => 'Acirc',
|
||||||
|
'Ã' => 'Atilde',
|
||||||
|
'Ä' => 'Auml',
|
||||||
|
'Å' => 'Aring',
|
||||||
|
'Æ' => 'AElig',
|
||||||
|
'Ç' => 'Ccedil',
|
||||||
|
'È' => 'Egrave',
|
||||||
|
'É' => 'Eacute',
|
||||||
|
'Ê' => 'Ecirc',
|
||||||
|
'Ë' => 'Euml',
|
||||||
|
'Ì' => 'Igrave',
|
||||||
|
'Í' => 'Iacute',
|
||||||
|
'Î' => 'Icirc',
|
||||||
|
'Ï' => 'Iuml',
|
||||||
|
'Ð' => 'ETH',
|
||||||
|
'Ñ' => 'Ntilde',
|
||||||
|
'Ò' => 'Ograve',
|
||||||
|
'Ó' => 'Oacute',
|
||||||
|
'Ô' => 'Ocirc',
|
||||||
|
'Õ' => 'Otilde',
|
||||||
|
'Ö' => 'Ouml',
|
||||||
|
'×' => 'times',
|
||||||
|
'Ø' => 'Oslash',
|
||||||
|
'Ù' => 'Ugrave',
|
||||||
|
'Ú' => 'Uacute',
|
||||||
|
'Û' => 'Ucirc',
|
||||||
|
'Ü' => 'Uuml',
|
||||||
|
'Ý' => 'Yacute',
|
||||||
|
'Þ' => 'THORN',
|
||||||
|
'ß' => 'szlig',
|
||||||
|
'à' => 'agrave',
|
||||||
|
'á' => 'aacute',
|
||||||
|
'â' => 'acirc',
|
||||||
|
'ã' => 'atilde',
|
||||||
|
'ä' => 'auml',
|
||||||
|
'å' => 'aring',
|
||||||
|
'æ' => 'aelig',
|
||||||
|
'ç' => 'ccedil',
|
||||||
|
'è' => 'egrave',
|
||||||
|
'é' => 'eacute',
|
||||||
|
'ê' => 'ecirc',
|
||||||
|
'ë' => 'euml',
|
||||||
|
'ì' => 'igrave',
|
||||||
|
'í' => 'iacute',
|
||||||
|
'î' => 'icirc',
|
||||||
|
'ï' => 'iuml',
|
||||||
|
'ð' => 'eth',
|
||||||
|
'ñ' => 'ntilde',
|
||||||
|
'ò' => 'ograve',
|
||||||
|
'ó' => 'oacute',
|
||||||
|
'ô' => 'ocirc',
|
||||||
|
'õ' => 'otilde',
|
||||||
|
'ö' => 'ouml',
|
||||||
|
'÷' => 'divide',
|
||||||
|
'ø' => 'oslash',
|
||||||
|
'ù' => 'ugrave',
|
||||||
|
'ú' => 'uacute',
|
||||||
|
'û' => 'ucirc',
|
||||||
|
'ü' => 'uuml',
|
||||||
|
'ý' => 'yacute',
|
||||||
|
'þ' => 'thorn',
|
||||||
|
'ÿ' => 'yuml',
|
||||||
|
'Œ' => 'OElig',
|
||||||
|
'œ' => 'oelig',
|
||||||
|
'Š' => 'Scaron',
|
||||||
|
'š' => 'scaron',
|
||||||
|
'Ÿ' => 'Yuml',
|
||||||
|
'ƒ' => 'fnof',
|
||||||
|
'ˆ' => 'circ',
|
||||||
|
'˜' => 'tilde',
|
||||||
|
'Α' => 'Alpha',
|
||||||
|
'Β' => 'Beta',
|
||||||
|
'Γ' => 'Gamma',
|
||||||
|
'Δ' => 'Delta',
|
||||||
|
'Ε' => 'Epsilon',
|
||||||
|
'Ζ' => 'Zeta',
|
||||||
|
'Η' => 'Eta',
|
||||||
|
'Θ' => 'Theta',
|
||||||
|
'Ι' => 'Iota',
|
||||||
|
'Κ' => 'Kappa',
|
||||||
|
'Λ' => 'Lambda',
|
||||||
|
'Μ' => 'Mu',
|
||||||
|
'Ν' => 'Nu',
|
||||||
|
'Ξ' => 'Xi',
|
||||||
|
'Ο' => 'Omicron',
|
||||||
|
'Π' => 'Pi',
|
||||||
|
'Ρ' => 'Rho',
|
||||||
|
'Σ' => 'Sigma',
|
||||||
|
'Τ' => 'Tau',
|
||||||
|
'Υ' => 'Upsilon',
|
||||||
|
'Φ' => 'Phi',
|
||||||
|
'Χ' => 'Chi',
|
||||||
|
'Ψ' => 'Psi',
|
||||||
|
'Ω' => 'Omega',
|
||||||
|
'α' => 'alpha',
|
||||||
|
'β' => 'beta',
|
||||||
|
'γ' => 'gamma',
|
||||||
|
'δ' => 'delta',
|
||||||
|
'ε' => 'epsilon',
|
||||||
|
'ζ' => 'zeta',
|
||||||
|
'η' => 'eta',
|
||||||
|
'θ' => 'theta',
|
||||||
|
'ι' => 'iota',
|
||||||
|
'κ' => 'kappa',
|
||||||
|
'λ' => 'lambda',
|
||||||
|
'μ' => 'mu',
|
||||||
|
'ν' => 'nu',
|
||||||
|
'ξ' => 'xi',
|
||||||
|
'ο' => 'omicron',
|
||||||
|
'π' => 'pi',
|
||||||
|
'ρ' => 'rho',
|
||||||
|
'ς' => 'sigmaf',
|
||||||
|
'σ' => 'sigma',
|
||||||
|
'τ' => 'tau',
|
||||||
|
'υ' => 'upsilon',
|
||||||
|
'φ' => 'phi',
|
||||||
|
'χ' => 'chi',
|
||||||
|
'ψ' => 'psi',
|
||||||
|
'ω' => 'omega',
|
||||||
|
'ϑ' => 'thetasym',
|
||||||
|
'ϒ' => 'upsih',
|
||||||
|
'ϖ' => 'piv',
|
||||||
|
"\u2002" => 'ensp',
|
||||||
|
"\u2003" => 'emsp',
|
||||||
|
"\u2009" => 'thinsp',
|
||||||
|
"\u200C" => 'zwnj',
|
||||||
|
"\u200D" => 'zwj',
|
||||||
|
"\u200E" => 'lrm',
|
||||||
|
"\u200F" => 'rlm',
|
||||||
|
'–' => 'ndash',
|
||||||
|
'—' => 'mdash',
|
||||||
|
'‘' => 'lsquo',
|
||||||
|
'’' => 'rsquo',
|
||||||
|
'‚' => 'sbquo',
|
||||||
|
'“' => 'ldquo',
|
||||||
|
'”' => 'rdquo',
|
||||||
|
'„' => 'bdquo',
|
||||||
|
'†' => 'dagger',
|
||||||
|
'‡' => 'Dagger',
|
||||||
|
'•' => 'bull',
|
||||||
|
'…' => 'hellip',
|
||||||
|
'‰' => 'permil',
|
||||||
|
'′' => 'prime',
|
||||||
|
'″' => 'Prime',
|
||||||
|
'‹' => 'lsaquo',
|
||||||
|
'›' => 'rsaquo',
|
||||||
|
'‾' => 'oline',
|
||||||
|
'⁄' => 'frasl',
|
||||||
|
'€' => 'euro',
|
||||||
|
'ℑ' => 'image',
|
||||||
|
'℘' => 'weierp',
|
||||||
|
'ℜ' => 'real',
|
||||||
|
'™' => 'trade',
|
||||||
|
'ℵ' => 'alefsym',
|
||||||
|
'←' => 'larr',
|
||||||
|
'↑' => 'uarr',
|
||||||
|
'→' => 'rarr',
|
||||||
|
'↓' => 'darr',
|
||||||
|
'↔' => 'harr',
|
||||||
|
'↵' => 'crarr',
|
||||||
|
'⇐' => 'lArr',
|
||||||
|
'⇑' => 'uArr',
|
||||||
|
'⇒' => 'rArr',
|
||||||
|
'⇓' => 'dArr',
|
||||||
|
'⇔' => 'hArr',
|
||||||
|
'∀' => 'forall',
|
||||||
|
'∂' => 'part',
|
||||||
|
'∃' => 'exist',
|
||||||
|
'∅' => 'empty',
|
||||||
|
'∇' => 'nabla',
|
||||||
|
'∈' => 'isin',
|
||||||
|
'∉' => 'notin',
|
||||||
|
'∋' => 'ni',
|
||||||
|
'∏' => 'prod',
|
||||||
|
'∑' => 'sum',
|
||||||
|
'−' => 'minus',
|
||||||
|
'∗' => 'lowast',
|
||||||
|
'√' => 'radic',
|
||||||
|
'∝' => 'prop',
|
||||||
|
'∞' => 'infin',
|
||||||
|
'∠' => 'ang',
|
||||||
|
'∧' => 'and',
|
||||||
|
'∨' => 'or',
|
||||||
|
'∩' => 'cap',
|
||||||
|
'∪' => 'cup',
|
||||||
|
'∫' => 'int',
|
||||||
|
'∴' => 'there4',
|
||||||
|
'∼' => 'sim',
|
||||||
|
'≅' => 'cong',
|
||||||
|
'≈' => 'asymp',
|
||||||
|
'≠' => 'ne',
|
||||||
|
'≡' => 'equiv',
|
||||||
|
'≤' => 'le',
|
||||||
|
'≥' => 'ge',
|
||||||
|
'⊂' => 'sub',
|
||||||
|
'⊃' => 'sup',
|
||||||
|
'⊄' => 'nsub',
|
||||||
|
'⊆' => 'sube',
|
||||||
|
'⊇' => 'supe',
|
||||||
|
'⊕' => 'oplus',
|
||||||
|
'⊗' => 'otimes',
|
||||||
|
'⊥' => 'perp',
|
||||||
|
'⋅' => 'sdot',
|
||||||
|
'⌈' => 'lceil',
|
||||||
|
'⌉' => 'rceil',
|
||||||
|
'⌊' => 'lfloor',
|
||||||
|
'⌋' => 'rfloor',
|
||||||
|
"\u2329" => 'lang', # rubocop:disable Style/AsciiComments "〈"
|
||||||
|
"\u232A" => 'rang', # rubocop:disable Style/AsciiComments "〉"
|
||||||
|
'◊' => 'loz',
|
||||||
|
'♠' => 'spades',
|
||||||
|
'♣' => 'clubs',
|
||||||
|
'♥' => 'hearts',
|
||||||
|
'♦' => 'diams'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
TEST_PLURALS = {
|
||||||
|
# um => a
|
||||||
|
'bacterium' => 'bacteria',
|
||||||
|
'agendum' => 'agenda',
|
||||||
|
'desideratum' => 'desiderata',
|
||||||
|
'erratum' => 'errata',
|
||||||
|
'stratum' => 'strata',
|
||||||
|
'datum' => 'data',
|
||||||
|
'ovum' => 'ova',
|
||||||
|
'extremum' => 'extrema',
|
||||||
|
'candelabrum' => 'candelabra',
|
||||||
|
'curriculum' => 'curricula',
|
||||||
|
'millennium' => 'millennia',
|
||||||
|
'referendum' => 'referenda',
|
||||||
|
'stadium' => 'stadia',
|
||||||
|
'medium' => 'media',
|
||||||
|
'memorandum' => 'memoranda',
|
||||||
|
'criterium' => 'criteria',
|
||||||
|
'perihelium' => 'perihelia',
|
||||||
|
'aphelium' => 'aphelia',
|
||||||
|
# on => a
|
||||||
|
'phenomenon' => 'phenomena',
|
||||||
|
'prolegomenon' => 'prolegomena',
|
||||||
|
'noumenon' => 'noumena',
|
||||||
|
'organon' => 'organa',
|
||||||
|
# o => os
|
||||||
|
'albino' => 'albinos',
|
||||||
|
'archipelago' => 'archipelagos',
|
||||||
|
'armadillo' => 'armadillos',
|
||||||
|
'commando' => 'commandos',
|
||||||
|
'crescendo' => 'crescendos',
|
||||||
|
'fiasco' => 'fiascos',
|
||||||
|
'ditto' => 'dittos',
|
||||||
|
'dynamo' => 'dynamos',
|
||||||
|
'embryo' => 'embryos',
|
||||||
|
'ghetto' => 'ghettos',
|
||||||
|
'guano' => 'guanos',
|
||||||
|
'inferno' => 'infernos',
|
||||||
|
'jumbo' => 'jumbos',
|
||||||
|
'lumbago' => 'lumbagos',
|
||||||
|
'magneto' => 'magnetos',
|
||||||
|
'manifesto' => 'manifestos',
|
||||||
|
'medico' => 'medicos',
|
||||||
|
'octavo' => 'octavos',
|
||||||
|
'photo' => 'photos',
|
||||||
|
'pro' => 'pros',
|
||||||
|
'quarto' => 'quartos',
|
||||||
|
'canto' => 'cantos',
|
||||||
|
'lingo' => 'lingos',
|
||||||
|
'generalissimo' => 'generalissimos',
|
||||||
|
'stylo' => 'stylos',
|
||||||
|
'rhino' => 'rhinos',
|
||||||
|
'casino' => 'casinos',
|
||||||
|
'auto' => 'autos',
|
||||||
|
'macro' => 'macros',
|
||||||
|
'zero' => 'zeros',
|
||||||
|
'todo' => 'todos',
|
||||||
|
'studio' => 'studios',
|
||||||
|
'avocado' => 'avocados',
|
||||||
|
'zoo' => 'zoos',
|
||||||
|
'banjo' => 'banjos',
|
||||||
|
'cargo' => 'cargos',
|
||||||
|
'flamingo' => 'flamingos',
|
||||||
|
'fresco' => 'frescos',
|
||||||
|
'halo' => 'halos',
|
||||||
|
'mango' => 'mangos',
|
||||||
|
'memento' => 'mementos',
|
||||||
|
'motto' => 'mottos',
|
||||||
|
'tornado' => 'tornados',
|
||||||
|
'tuxedo' => 'tuxedos',
|
||||||
|
'volcano' => 'volcanos',
|
||||||
|
# The correct form from italian is: o => i. (Eg. contralto => contralti)
|
||||||
|
# English dictionaries are reporting o => s as a valid rule
|
||||||
|
#
|
||||||
|
# We're sticking to the latter rule, in order to not introduce exceptions
|
||||||
|
# for words that end with "o". See the previous category.
|
||||||
|
'solo' => 'solos',
|
||||||
|
'soprano' => 'sopranos',
|
||||||
|
'basso' => 'bassos',
|
||||||
|
'alto' => 'altos',
|
||||||
|
'contralto' => 'contraltos',
|
||||||
|
'tempo' => 'tempos',
|
||||||
|
'piano' => 'pianos',
|
||||||
|
'virtuoso' => 'virtuosos',
|
||||||
|
# o => oes
|
||||||
|
'buffalo' => 'buffaloes',
|
||||||
|
'domino' => 'dominoes',
|
||||||
|
'echo' => 'echoes',
|
||||||
|
'embargo' => 'embargoes',
|
||||||
|
'hero' => 'heroes',
|
||||||
|
'mosquito' => 'mosquitoes',
|
||||||
|
'potato' => 'potatoes',
|
||||||
|
'tomato' => 'tomatoes',
|
||||||
|
'torpedo' => 'torpedos',
|
||||||
|
'veto' => 'vetos',
|
||||||
|
# a => ata
|
||||||
|
'anathema' => 'anathemata',
|
||||||
|
'enema' => 'enemata',
|
||||||
|
'oedema' => 'oedemata',
|
||||||
|
'bema' => 'bemata',
|
||||||
|
'enigma' => 'enigmata',
|
||||||
|
'sarcoma' => 'sarcomata',
|
||||||
|
'carcinoma' => 'carcinomata',
|
||||||
|
'gumma' => 'gummata',
|
||||||
|
'schema' => 'schemata',
|
||||||
|
'charisma' => 'charismata',
|
||||||
|
'lemma' => 'lemmata',
|
||||||
|
'soma' => 'somata',
|
||||||
|
'diploma' => 'diplomata',
|
||||||
|
'lymphoma' => 'lymphomata',
|
||||||
|
'stigma' => 'stigmata',
|
||||||
|
'dogma' => 'dogmata',
|
||||||
|
'magma' => 'magmata',
|
||||||
|
'stoma' => 'stomata',
|
||||||
|
'drama' => 'dramata',
|
||||||
|
'melisma' => 'melismata',
|
||||||
|
'trauma' => 'traumata',
|
||||||
|
'edema' => 'edemata',
|
||||||
|
'miasma' => 'miasmata',
|
||||||
|
# # is => es
|
||||||
|
# "axis" => "axes",
|
||||||
|
# "analysis" => "analyses",
|
||||||
|
# "basis" => "bases",
|
||||||
|
# "crisis" => "crises",
|
||||||
|
# "diagnosis" => "diagnoses",
|
||||||
|
# "ellipsis" => "ellipses",
|
||||||
|
# "hypothesis" => "hypotheses",
|
||||||
|
# "oasis" => "oases",
|
||||||
|
# "paralysis" => "paralyses",
|
||||||
|
# "parenthesis" => "parentheses",
|
||||||
|
# "synthesis" => "syntheses",
|
||||||
|
# "synopsis" => "synopses",
|
||||||
|
# "thesis" => "theses",
|
||||||
|
# us => uses
|
||||||
|
'apparatus' => 'apparatuses',
|
||||||
|
'impetus' => 'impetuses',
|
||||||
|
'prospectus' => 'prospectuses',
|
||||||
|
'cantus' => 'cantuses',
|
||||||
|
'nexus' => 'nexuses',
|
||||||
|
'sinus' => 'sinuses',
|
||||||
|
'coitus' => 'coituses',
|
||||||
|
'plexus' => 'plexuses',
|
||||||
|
'status' => 'statuses',
|
||||||
|
'hiatus' => 'hiatuses',
|
||||||
|
'bus' => 'buses',
|
||||||
|
'octopus' => 'octopuses',
|
||||||
|
#
|
||||||
|
# none => i
|
||||||
|
# "afreet" => true,
|
||||||
|
# "afrit" => true,
|
||||||
|
# "efreet" => true,
|
||||||
|
#
|
||||||
|
# none => im
|
||||||
|
# "cherub" => true,
|
||||||
|
# "goy" => true,
|
||||||
|
# "seraph" => true,
|
||||||
|
#
|
||||||
|
# man => mans
|
||||||
|
'human' => 'humans',
|
||||||
|
'Alabaman' => 'Alabamans',
|
||||||
|
'Bahaman' => 'Bahamans',
|
||||||
|
'Burman' => 'Burmans',
|
||||||
|
'German' => 'Germans',
|
||||||
|
'Hiroshiman' => 'Hiroshimans',
|
||||||
|
'Liman' => 'Limans',
|
||||||
|
'Nakayaman' => 'Nakayamans',
|
||||||
|
'Oklahoman' => 'Oklahomans',
|
||||||
|
'Panaman' => 'Panamans',
|
||||||
|
'Selman' => 'Selmans',
|
||||||
|
'Sonaman' => 'Sonamans',
|
||||||
|
'Tacoman' => 'Tacomans',
|
||||||
|
'Yakiman' => 'Yakimans',
|
||||||
|
'Yokohaman' => 'Yokohamans',
|
||||||
|
'Yuman' => 'Yumans',
|
||||||
|
# ch => es
|
||||||
|
'witch' => 'witches',
|
||||||
|
'church' => 'churches',
|
||||||
|
# ch => chs
|
||||||
|
'stomach' => 'stomachs',
|
||||||
|
'epoch' => 'epochs',
|
||||||
|
# e => es,
|
||||||
|
'mustache' => 'mustaches',
|
||||||
|
'horse' => 'horses',
|
||||||
|
'verse' => 'verses',
|
||||||
|
'universe' => 'universes',
|
||||||
|
'inverse' => 'inverses',
|
||||||
|
'price' => 'prices',
|
||||||
|
'advice' => 'advices',
|
||||||
|
'device' => 'devices',
|
||||||
|
# x => es
|
||||||
|
'box' => 'boxes',
|
||||||
|
'fox' => 'foxes',
|
||||||
|
# vowel + y => s
|
||||||
|
'boy' => 'boys',
|
||||||
|
'way' => 'ways',
|
||||||
|
'buy' => 'buys',
|
||||||
|
# consonant + y => ies
|
||||||
|
'baby' => 'babies',
|
||||||
|
'lorry' => 'lorries',
|
||||||
|
'entity' => 'entities',
|
||||||
|
'repository' => 'repositories',
|
||||||
|
'fly' => 'flies',
|
||||||
|
# f => ves
|
||||||
|
'leaf' => 'leaves',
|
||||||
|
'hoof' => 'hooves',
|
||||||
|
'self' => 'selves',
|
||||||
|
'elf' => 'elves',
|
||||||
|
'half' => 'halves',
|
||||||
|
'scarf' => 'scarves',
|
||||||
|
'dwarf' => 'dwarves',
|
||||||
|
# vocal + fe => ves
|
||||||
|
'knife' => 'knives',
|
||||||
|
'life' => 'lives',
|
||||||
|
'wife' => 'wives',
|
||||||
|
# eau => eaux
|
||||||
|
'beau' => 'beaux',
|
||||||
|
'bureau' => 'bureaux',
|
||||||
|
'tableau' => 'tableaux',
|
||||||
|
# ouse => ice
|
||||||
|
'louse' => 'lice',
|
||||||
|
'mouse' => 'mice',
|
||||||
|
# irregular
|
||||||
|
'cactus' => 'cacti',
|
||||||
|
'foot' => 'feet',
|
||||||
|
'tooth' => 'teeth',
|
||||||
|
'goose' => 'geese',
|
||||||
|
'child' => 'children',
|
||||||
|
'man' => 'men',
|
||||||
|
'woman' => 'women',
|
||||||
|
'person' => 'people',
|
||||||
|
'ox' => 'oxen',
|
||||||
|
'corpus' => 'corpora',
|
||||||
|
'genus' => 'genera',
|
||||||
|
'sex' => 'sexes',
|
||||||
|
'quiz' => 'quizzes',
|
||||||
|
'testis' => 'testes',
|
||||||
|
# uncountable
|
||||||
|
'deer' => 'deer',
|
||||||
|
'fish' => 'fish',
|
||||||
|
'money' => 'money',
|
||||||
|
'means' => 'means',
|
||||||
|
'offspring' => 'offspring',
|
||||||
|
'series' => 'series',
|
||||||
|
'sheep' => 'sheep',
|
||||||
|
'species' => 'species',
|
||||||
|
'equipment' => 'equipment',
|
||||||
|
'information' => 'information',
|
||||||
|
'rice' => 'rice',
|
||||||
|
'news' => 'news',
|
||||||
|
'police' => 'police',
|
||||||
|
# fallback (add s)
|
||||||
|
'giraffe' => 'giraffes',
|
||||||
|
'test' => 'tests',
|
||||||
|
'feature' => 'features',
|
||||||
|
'fixture' => 'fixtures',
|
||||||
|
'controller' => 'controllers',
|
||||||
|
'action' => 'actions',
|
||||||
|
'router' => 'routers',
|
||||||
|
'route' => 'routes',
|
||||||
|
'endpoint' => 'endpoints',
|
||||||
|
'string' => 'strings',
|
||||||
|
'view' => 'views',
|
||||||
|
'template' => 'templates',
|
||||||
|
'layout' => 'layouts',
|
||||||
|
'application' => 'applications',
|
||||||
|
'api' => 'apis',
|
||||||
|
'model' => 'models',
|
||||||
|
'mapper' => 'mappers',
|
||||||
|
'mapping' => 'mappings',
|
||||||
|
'table' => 'tables',
|
||||||
|
'attribute' => 'attributes',
|
||||||
|
'column' => 'columns',
|
||||||
|
'migration' => 'migrations',
|
||||||
|
'presenter' => 'presenters',
|
||||||
|
'wizard' => 'wizards',
|
||||||
|
'architecture' => 'architectures',
|
||||||
|
'cat' => 'cats',
|
||||||
|
'car' => 'cars',
|
||||||
|
'hive' => 'hives',
|
||||||
|
# https://github.com/hanami/utils/issues/106
|
||||||
|
'album' => 'albums',
|
||||||
|
# https://github.com/hanami/utils/issues/173
|
||||||
|
'kitten' => 'kittens'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
TEST_SINGULARS = {
|
||||||
|
# a => ae
|
||||||
|
'alumna' => 'alumnae',
|
||||||
|
'alga' => 'algae',
|
||||||
|
'vertebra' => 'vertebrae',
|
||||||
|
'persona' => 'personae',
|
||||||
|
'antenna' => 'antennae',
|
||||||
|
'formula' => 'formulae',
|
||||||
|
'nebula' => 'nebulae',
|
||||||
|
'vita' => 'vitae',
|
||||||
|
# is => ides
|
||||||
|
'iris' => 'irides',
|
||||||
|
'clitoris' => 'clitorides',
|
||||||
|
# us => i
|
||||||
|
'alumnus' => 'alumni',
|
||||||
|
'alveolus' => 'alveoli',
|
||||||
|
'bacillus' => 'bacilli',
|
||||||
|
'bronchus' => 'bronchi',
|
||||||
|
'locus' => 'loci',
|
||||||
|
'nucleus' => 'nuclei',
|
||||||
|
'stimulus' => 'stimuli',
|
||||||
|
'meniscus' => 'menisci',
|
||||||
|
'thesaurus' => 'thesauri',
|
||||||
|
# f => s
|
||||||
|
'chief' => 'chiefs',
|
||||||
|
'spoof' => 'spoofs',
|
||||||
|
# en => ina
|
||||||
|
'stamen' => 'stamina',
|
||||||
|
'foramen' => 'foramina',
|
||||||
|
'lumen' => 'lumina',
|
||||||
|
# s => es
|
||||||
|
'acropolis' => 'acropolises',
|
||||||
|
'chaos' => 'chaoses',
|
||||||
|
'lens' => 'lenses',
|
||||||
|
'aegis' => 'aegises',
|
||||||
|
'cosmos' => 'cosmoses',
|
||||||
|
'mantis' => 'mantises',
|
||||||
|
'alias' => 'aliases',
|
||||||
|
'dais' => 'daises',
|
||||||
|
'marquis' => 'marquises',
|
||||||
|
'asbestos' => 'asbestoses',
|
||||||
|
'digitalis' => 'digitalises',
|
||||||
|
'metropolis' => 'metropolises',
|
||||||
|
'atlas' => 'atlases',
|
||||||
|
'epidermis' => 'epidermises',
|
||||||
|
'pathos' => 'pathoses',
|
||||||
|
'bathos' => 'bathoses',
|
||||||
|
'ethos' => 'ethoses',
|
||||||
|
'pelvis' => 'pelvises',
|
||||||
|
'bias' => 'biases',
|
||||||
|
'gas' => 'gases',
|
||||||
|
'polis' => 'polises',
|
||||||
|
'caddis' => 'caddises',
|
||||||
|
'rhinoceros' => 'rhinoceroses',
|
||||||
|
'cannabis' => 'cannabises',
|
||||||
|
'glottis' => 'glottises',
|
||||||
|
'sassafras' => 'sassafrases',
|
||||||
|
'canvas' => 'canvases',
|
||||||
|
'ibis' => 'ibises',
|
||||||
|
'trellis' => 'trellises',
|
||||||
|
'kiss' => 'kisses',
|
||||||
|
# https://github.com/hanami/utils/issues/106
|
||||||
|
'album' => 'albums'
|
||||||
|
}.merge(TEST_PLURALS)
|
||||||
|
|
||||||
|
require 'hanami/utils/inflector'
|
||||||
|
Hanami::Utils::Inflector.inflections do
|
||||||
|
exception 'analysis', 'analyses'
|
||||||
|
exception 'alga', 'algae'
|
||||||
|
uncountable 'music', 'butter'
|
||||||
|
end
|
||||||
|
|
||||||
|
class WrappingHash
|
||||||
|
def initialize(hash)
|
||||||
|
@hash = hash.to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_hash
|
||||||
|
@hash
|
||||||
|
end
|
||||||
|
alias to_h to_hash
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
require 'rubygems'
|
||||||
|
require 'bundler'
|
||||||
|
Bundler.setup(:default, :development)
|
||||||
|
|
||||||
|
$LOAD_PATH.unshift 'lib'
|
||||||
|
require 'hanami/utils'
|
||||||
|
require_relative './rspec'
|
||||||
|
|
||||||
|
module RSpec
|
||||||
|
module Support
|
||||||
|
module Runner
|
||||||
|
def self.run
|
||||||
|
Core::Runner.autorun
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
require 'rspec'
|
||||||
|
|
||||||
|
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
|
|
@ -0,0 +1,19 @@
|
||||||
|
module RSpec
|
||||||
|
module Support
|
||||||
|
module Stdout
|
||||||
|
def with_captured_stdout
|
||||||
|
original = $stdout
|
||||||
|
captured = StringIO.new
|
||||||
|
$stdout = captured
|
||||||
|
yield
|
||||||
|
$stdout.string
|
||||||
|
ensure
|
||||||
|
$stdout = original
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.include RSpec::Support::Stdout
|
||||||
|
end
|
|
@ -12,7 +12,7 @@ describe Hanami::Utils::ClassAttribute do
|
||||||
|
|
||||||
class SubclassAttributeTest < ClassAttributeTest
|
class SubclassAttributeTest < ClassAttributeTest
|
||||||
class_attribute :subattribute
|
class_attribute :subattribute
|
||||||
self.functions = [:x, :y]
|
self.functions = %i(x y)
|
||||||
self.subattribute = 42
|
self.subattribute = 42
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -44,15 +44,15 @@ describe Hanami::Utils::ClassAttribute do
|
||||||
end
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
[:ClassAttributeTest,
|
%i(ClassAttributeTest
|
||||||
:SubclassAttributeTest,
|
SubclassAttributeTest
|
||||||
:SubSubclassAttributeTest,
|
SubSubclassAttributeTest
|
||||||
:Vehicle,
|
Vehicle
|
||||||
:Car,
|
Car
|
||||||
:Airplane,
|
Airplane
|
||||||
:SmallAirplane].each do |const|
|
SmallAirplane).each do |const|
|
||||||
Object.send :remove_const, const
|
Object.send :remove_const, const
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the given value' do
|
it 'sets the given value' do
|
||||||
|
@ -75,7 +75,7 @@ describe Hanami::Utils::ClassAttribute do
|
||||||
|
|
||||||
it 'if the superclass value changes it does not affects subclasses' do
|
it 'if the superclass value changes it does not affects subclasses' do
|
||||||
ClassAttributeTest.functions = [:y]
|
ClassAttributeTest.functions = [:y]
|
||||||
SubclassAttributeTest.functions.must_equal([:x, :y])
|
SubclassAttributeTest.functions.must_equal(%i(x y))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'if the subclass value changes it does not affects superclass' do
|
it 'if the subclass value changes it does not affects superclass' do
|
||||||
|
|
|
@ -242,7 +242,7 @@ describe Hanami::Interactor do
|
||||||
|
|
||||||
it "doesn't interrupt the flow" do
|
it "doesn't interrupt the flow" do
|
||||||
result = ErrorInteractor.new.call
|
result = ErrorInteractor.new.call
|
||||||
result.operations.must_equal [:prepare!, :persist!, :log!]
|
result.operations.must_equal %i(prepare! persist! log!)
|
||||||
end
|
end
|
||||||
|
|
||||||
# See https://github.com/hanami/utils/issues/69
|
# See https://github.com/hanami/utils/issues/69
|
||||||
|
|
|
@ -17,12 +17,12 @@ describe 'Hanami::Utils.reload!' do
|
||||||
it 'reloads the files set of files' do
|
it 'reloads the files set of files' do
|
||||||
File.open(root.join('user.rb'), 'w+') do |f|
|
File.open(root.join('user.rb'), 'w+') do |f|
|
||||||
f.write <<-EOF
|
f.write <<-EOF
|
||||||
class User
|
class User
|
||||||
def greet
|
def greet
|
||||||
"Hi"
|
"Hi"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
EOF
|
EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
Hanami::Utils.reload!(root)
|
Hanami::Utils.reload!(root)
|
||||||
|
@ -30,12 +30,12 @@ EOF
|
||||||
|
|
||||||
File.open(root.join('user.rb'), 'w+') do |f|
|
File.open(root.join('user.rb'), 'w+') do |f|
|
||||||
f.write <<-EOF
|
f.write <<-EOF
|
||||||
class User
|
class User
|
||||||
def greet
|
def greet
|
||||||
"Ciao"
|
"Ciao"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
EOF
|
EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
Hanami::Utils.reload!(root)
|
Hanami::Utils.reload!(root)
|
||||||
|
|
Loading…
Reference in New Issue