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:
|
||||
- 'test/**/*'
|
||||
- 'tmp/**/*'
|
||||
- 'spec/**/*'
|
||||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
Style/SpecialGlobalVars:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
script: 'script/ci && bundle exec rubocop'
|
||||
script: ./script/ci
|
||||
before_install:
|
||||
- rvm @global do gem uninstall bundler -a -x
|
||||
- rvm @global do gem install bundler -v 1.13.7
|
||||
|
|
18
Rakefile
18
Rakefile
|
@ -1,7 +1,8 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
require 'rake/testtask'
|
||||
Rake::TestTask.new do |t|
|
||||
t.test_files = Dir['test/**/*_test.rb'].reject do |path|
|
||||
path.include?('isolation')
|
||||
|
@ -10,11 +11,18 @@ Rake::TestTask.new do |t|
|
|||
t.libs.push 'test'
|
||||
end
|
||||
|
||||
namespace :test do
|
||||
namespace :spec do
|
||||
RSpec::Core::RakeTask.new(:unit) do |task|
|
||||
file_list = FileList['spec/**/*_spec.rb']
|
||||
file_list = file_list.exclude("spec/{integration,isolation}/**/*_spec.rb")
|
||||
|
||||
task.pattern = file_list
|
||||
end
|
||||
|
||||
task :coverage do
|
||||
ENV['COVERALL'] = 'true'
|
||||
Rake::Task['test'].invoke
|
||||
ENV['COVERAGE'] = 'true'
|
||||
Rake::Task['spec:unit'].invoke
|
||||
end
|
||||
end
|
||||
|
||||
task default: :test
|
||||
task default: 'spec:unit'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# coding: utf-8
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'hanami/utils/version'
|
||||
|
@ -21,4 +20,5 @@ Gem::Specification.new do |spec|
|
|||
|
||||
spec.add_development_dependency 'bundler', '~> 1.6'
|
||||
spec.add_development_dependency 'rake', '~> 11'
|
||||
spec.add_development_dependency 'rspec', '~> 3.5'
|
||||
end
|
||||
|
|
|
@ -110,7 +110,7 @@ module Hanami
|
|||
|
||||
# @since 1.0.0.beta1
|
||||
# @api private
|
||||
RESERVED_KEYS = [:app, :severity, :time].freeze
|
||||
RESERVED_KEYS = %i(app severity time).freeze
|
||||
|
||||
include Utils::ClassAttribute
|
||||
|
||||
|
|
21
script/ci
21
script/ci
|
@ -7,16 +7,16 @@ run_code_quality_checks() {
|
|||
}
|
||||
|
||||
run_unit_tests() {
|
||||
bundle exec rake test:coverage
|
||||
bundle exec rake spec:coverage
|
||||
}
|
||||
|
||||
run_integration_tests() {
|
||||
run_isolation_tests() {
|
||||
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
|
||||
run_test $test
|
||||
run_isolation_test $test
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
local exit_code=$?
|
||||
|
@ -26,17 +26,24 @@ run_integration_tests() {
|
|||
done
|
||||
}
|
||||
|
||||
run_isolation_test() {
|
||||
local test=$1
|
||||
|
||||
printf "\n\n\nRunning: $test\n"
|
||||
ruby $test --options spec/isolation/.rspec
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local test=$1
|
||||
|
||||
printf "\n\n\nRunning: $test\n"
|
||||
ruby -Itest $test
|
||||
COVERAGE=true bundle exec rspec $test
|
||||
}
|
||||
|
||||
main() {
|
||||
run_code_quality_checks &&
|
||||
run_unit_tests &&
|
||||
run_integration_tests
|
||||
run_isolation_tests
|
||||
}
|
||||
|
||||
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_attribute :subattribute
|
||||
self.functions = [:x, :y]
|
||||
self.functions = %i(x y)
|
||||
self.subattribute = 42
|
||||
end
|
||||
|
||||
|
@ -44,15 +44,15 @@ describe Hanami::Utils::ClassAttribute do
|
|||
end
|
||||
|
||||
after do
|
||||
[:ClassAttributeTest,
|
||||
:SubclassAttributeTest,
|
||||
:SubSubclassAttributeTest,
|
||||
:Vehicle,
|
||||
:Car,
|
||||
:Airplane,
|
||||
:SmallAirplane].each do |const|
|
||||
Object.send :remove_const, const
|
||||
end
|
||||
%i(ClassAttributeTest
|
||||
SubclassAttributeTest
|
||||
SubSubclassAttributeTest
|
||||
Vehicle
|
||||
Car
|
||||
Airplane
|
||||
SmallAirplane).each do |const|
|
||||
Object.send :remove_const, const
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
ClassAttributeTest.functions = [:y]
|
||||
SubclassAttributeTest.functions.must_equal([:x, :y])
|
||||
SubclassAttributeTest.functions.must_equal(%i(x y))
|
||||
end
|
||||
|
||||
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
|
||||
result = ErrorInteractor.new.call
|
||||
result.operations.must_equal [:prepare!, :persist!, :log!]
|
||||
result.operations.must_equal %i(prepare! persist! log!)
|
||||
end
|
||||
|
||||
# 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
|
||||
File.open(root.join('user.rb'), 'w+') do |f|
|
||||
f.write <<-EOF
|
||||
class User
|
||||
def greet
|
||||
"Hi"
|
||||
end
|
||||
end
|
||||
EOF
|
||||
class User
|
||||
def greet
|
||||
"Hi"
|
||||
end
|
||||
end
|
||||
EOF
|
||||
end
|
||||
|
||||
Hanami::Utils.reload!(root)
|
||||
|
@ -30,12 +30,12 @@ EOF
|
|||
|
||||
File.open(root.join('user.rb'), 'w+') do |f|
|
||||
f.write <<-EOF
|
||||
class User
|
||||
def greet
|
||||
"Ciao"
|
||||
end
|
||||
end
|
||||
EOF
|
||||
class User
|
||||
def greet
|
||||
"Ciao"
|
||||
end
|
||||
end
|
||||
EOF
|
||||
end
|
||||
|
||||
Hanami::Utils.reload!(root)
|
||||
|
|
Loading…
Reference in New Issue