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:
Semyon Pupkov 2017-04-10 18:50:41 +05:00 committed by Luca Guidi
parent c0b2845170
commit 53303305a4
47 changed files with 7178 additions and 39 deletions

2
.rspec Normal file
View File

@ -0,0 +1,2 @@
--color
--require spec_helper

View File

@ -6,6 +6,7 @@ Metrics/BlockLength:
Exclude:
- 'test/**/*'
- 'tmp/**/*'
- 'spec/**/*'
Style/DoubleNegation:
Enabled: false
Style/SpecialGlobalVars:

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

438
spec/hanami/logger_spec.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 '&amp;'
end
it "escapes '<'" do
result = mod.html('<'.encode(encoding))
expect(result).to eq '&lt;'
end
it "escapes '>'" do
result = mod.html('>'.encode(encoding))
expect(result).to eq '&gt;'
end
it %(escapes '"') do
result = mod.html('"'.encode(encoding))
expect(result).to eq '&quot;'
end
it %(escapes "'") do
result = mod.html("'".encode(encoding))
expect(result).to eq '&apos;'
end
it "escapes '/'" do
result = mod.html('/'.encode(encoding))
expect(result).to eq '&#x2F;'
end
it "escapes '<script>'" do
result = mod.html('<script>'.encode(encoding))
expect(result).to eq '&lt;script&gt;'
end
it "escapes '<scr<script>ipt>'" do
result = mod.html('<scr<script>ipt>'.encode(encoding))
expect(result).to eq '&lt;scr&lt;script&gt;ipt&gt;'
end
it "escapes '&lt;script&gt;'" do
result = mod.html('&lt;script&gt;'.encode(encoding))
expect(result).to eq '&amp;lt;script&amp;gt;'
end
it %(escapes '""><script>xss(5)</script>') do
result = mod.html('""><script>xss(5)</script>'.encode(encoding))
expect(result).to eq '&quot;&quot;&gt;&lt;script&gt;xss(5)&lt;&#x2F;script&gt;'
end
it %(escapes '><script>xss(6)</script>') do
result = mod.html('><script>xss(6)</script>'.encode(encoding))
expect(result).to eq '&gt;&lt;script&gt;xss(6)&lt;&#x2F;script&gt;'
end
it %(escapes '# onmouseover="xss(7)" ') do
result = mod.html('# onmouseover="xss(7)" '.encode(encoding))
expect(result).to eq '# onmouseover=&quot;xss(7)&quot; '
end
it %(escapes '/" onerror="xss(9)">') do
result = mod.html('/" onerror="xss(9)">'.encode(encoding))
expect(result).to eq '&#x2F;&quot; onerror=&quot;xss(9)&quot;&gt;'
end
it %(escapes '/ onerror="xss(10)"') do
result = mod.html('/ onerror="xss(10)"'.encode(encoding))
expect(result).to eq '&#x2F; onerror=&quot;xss(10)&quot;'
end
it %(escapes '<<script>xss(14);//<</script>') do
result = mod.html('<<script>xss(14);//<</script>'.encode(encoding))
expect(result).to eq '&lt;&lt;script&gt;xss(14);&#x2F;&#x2F;&lt;&lt;&#x2F;script&gt;'
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 '&amp;'
end
it "escapes '<'" do
result = mod.html_attribute('<'.encode(encoding))
expect(result).to eq '&lt;'
end
it "escapes '>'" do
result = mod.html_attribute('>'.encode(encoding))
expect(result).to eq '&gt;'
end
it %(escapes '"') do
result = mod.html_attribute('"'.encode(encoding))
expect(result).to eq '&quot;'
end
it %(escapes "'") do
result = mod.html_attribute("'".encode(encoding))
expect(result).to eq '&#x27;'
end
it "escapes '/'" do
result = mod.html_attribute('/'.encode(encoding))
expect(result).to eq '&#x2f;'
end
it "escapes '<script>'" do
result = mod.html_attribute('<script>'.encode(encoding))
expect(result).to eq '&lt;script&gt;'
end
it "escapes '<scr<script>ipt>'" do
result = mod.html_attribute('<scr<script>ipt>'.encode(encoding))
expect(result).to eq '&lt;scr&lt;script&gt;ipt&gt;'
end
it "escapes '&lt;script&gt;'" do
result = mod.html_attribute('&lt;script&gt;'.encode(encoding))
expect(result).to eq '&amp;lt&#x3b;script&amp;gt&#x3b;'
end
it %(escapes '""><script>xss(5)</script>') do
result = mod.html_attribute('""><script>xss(5)</script>'.encode(encoding))
expect(result).to eq '&quot;&quot;&gt;&lt;script&gt;xss&#x28;5&#x29;&lt;&#x2f;script&gt;'
end
it %(escapes '><script>xss(6)</script>') do
result = mod.html_attribute('><script>xss(6)</script>'.encode(encoding))
expect(result).to eq '&gt;&lt;script&gt;xss&#x28;6&#x29;&lt;&#x2f;script&gt;'
end
it %(escapes '# onmouseover="xss(7)" ') do
result = mod.html_attribute('# onmouseover="xss(7)" '.encode(encoding))
expect(result).to eq '&#x23;&#x20;onmouseover&#x3d;&quot;xss&#x28;7&#x29;&quot;&#x20;'
end
it %(escapes '/" onerror="xss(9)">') do
result = mod.html_attribute('/" onerror="xss(9)">'.encode(encoding))
expect(result).to eq '&#x2f;&quot;&#x20;onerror&#x3d;&quot;xss&#x28;9&#x29;&quot;&gt;'
end
it %(escapes '/ onerror="xss(10)"') do
result = mod.html_attribute('/ onerror="xss(10)"'.encode(encoding))
expect(result).to eq '&#x2f;&#x20;onerror&#x3d;&quot;xss&#x28;10&#x29;&quot;'
end
it %(escapes '<<script>xss(14);//<</script>') do
result = mod.html_attribute('<<script>xss(14);//<</script>'.encode(encoding))
expect(result).to eq '&lt;&lt;script&gt;xss&#x28;14&#x29;&#x3b;&#x2f;&#x2f;&lt;&lt;&#x2f;script&gt;'
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 '&#x9;'
end
it 'escapes return carriage' do
result = mod.html_attribute("\r")
expect(result).to eq '&#xd;'
end
it 'escapes new line' do
result = mod.html_attribute("\n")
expect(result).to eq '&#xa;'
end
it 'escapes unicode char' do
result = mod.html_attribute('Ā')
expect(result).to eq '&#x100;'
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

1
spec/isolation/.rspec Normal file
View File

@ -0,0 +1 @@
--color

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

9
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,9 @@
if ENV['COVERALL']
require 'coveralls'
Coveralls.wear!
end
$LOAD_PATH.unshift 'lib'
require 'hanami/utils'
Hanami::Utils.require!("spec/support")

View File

@ -0,0 +1,2 @@
class A
end

View File

@ -0,0 +1,2 @@
class Aa
end

View File

@ -0,0 +1,2 @@
class Ab
end

View File

@ -0,0 +1,2 @@
class C
end

View File

@ -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

View File

@ -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

25
spec/support/rspec.rb Normal file
View File

@ -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

19
spec/support/stdout.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)