volt 0.8.27.beta2 → 0.8.27.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -0
  4. data/CHANGELOG.md +7 -2
  5. data/CONTRIBUTING.md +133 -0
  6. data/Gemfile +0 -1
  7. data/Rakefile +11 -3
  8. data/Readme.md +4 -4
  9. data/VERSION +1 -1
  10. data/app/volt/models/user.rb +6 -3
  11. data/lib/volt/cli/console.rb +21 -12
  12. data/lib/volt/cli/runner.rb +1 -1
  13. data/lib/volt/cli.rb +5 -4
  14. data/lib/volt/config.rb +9 -11
  15. data/lib/volt/controllers/model_controller.rb +16 -0
  16. data/lib/volt/data_stores/data_store.rb +1 -1
  17. data/lib/volt/extra_core/array.rb +1 -6
  18. data/lib/volt/extra_core/blank.rb +1 -3
  19. data/lib/volt/extra_core/class.rb +1 -1
  20. data/lib/volt/extra_core/extra_core.rb +0 -1
  21. data/lib/volt/extra_core/logger.rb +78 -1
  22. data/lib/volt/extra_core/object.rb +4 -4
  23. data/lib/volt/models/array_model.rb +2 -3
  24. data/lib/volt/models/buffer.rb +1 -2
  25. data/lib/volt/models/field_helpers.rb +4 -5
  26. data/lib/volt/models/model.rb +27 -1
  27. data/lib/volt/models/model_hash_behaviour.rb +3 -4
  28. data/lib/volt/models/persistors/array_store.rb +6 -7
  29. data/lib/volt/models/persistors/cookies.rb +2 -2
  30. data/lib/volt/models/persistors/model_store.rb +5 -6
  31. data/lib/volt/models/validations.rb +5 -7
  32. data/lib/volt/models/validators/email_validator.rb +8 -29
  33. data/lib/volt/models/validators/format_validator.rb +116 -0
  34. data/lib/volt/models/validators/numericality_validator.rb +2 -2
  35. data/lib/volt/models/validators/phone_number_validator.rb +8 -29
  36. data/lib/volt/models/validators/unique_validator.rb +2 -2
  37. data/lib/volt/page/bindings/content_binding.rb +1 -1
  38. data/lib/volt/page/bindings/each_binding.rb +1 -1
  39. data/lib/volt/page/bindings/template_binding/view_lookup_for_path.rb +92 -0
  40. data/lib/volt/page/bindings/template_binding.rb +10 -85
  41. data/lib/volt/page/channel.rb +0 -1
  42. data/lib/volt/page/page.rb +5 -7
  43. data/lib/volt/page/sub_context.rb +1 -1
  44. data/lib/volt/page/targets/base_section.rb +2 -2
  45. data/lib/volt/page/targets/helpers/comment_searchers.rb +2 -2
  46. data/lib/volt/reactive/reactive_accessors.rb +1 -1
  47. data/lib/volt/reactive/reactive_array.rb +6 -6
  48. data/lib/volt/router/routes.rb +4 -4
  49. data/lib/volt/server/rack/asset_files.rb +1 -2
  50. data/lib/volt/server/rack/component_code.rb +0 -2
  51. data/lib/volt/server/rack/component_paths.rb +2 -2
  52. data/lib/volt/server/rack/quiet_common_logger.rb +2 -2
  53. data/lib/volt/spec/capybara.rb +1 -1
  54. data/lib/volt/spec/sauce_labs.rb +6 -6
  55. data/lib/volt/spec/setup.rb +8 -7
  56. data/lib/volt/tasks/dispatcher.rb +12 -10
  57. data/lib/volt/tasks/task_handler.rb +1 -1
  58. data/lib/volt/utils/generic_pool.rb +2 -2
  59. data/lib/volt/volt/users.rb +7 -9
  60. data/lib/volt.rb +2 -4
  61. data/spec/apps/file_loading/app/missing_deps/config/dependencies.rb +1 -1
  62. data/spec/apps/kitchen_sink/Gemfile +2 -2
  63. data/spec/apps/kitchen_sink/app/main/config/dependencies.rb +1 -1
  64. data/spec/apps/kitchen_sink/app/main/config/routes.rb +0 -1
  65. data/spec/apps/kitchen_sink/app/main/models/user.rb +1 -1
  66. data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -1
  67. data/spec/apps/kitchen_sink/config/app.rb +1 -1
  68. data/spec/extra_core/array_spec.rb +4 -2
  69. data/spec/extra_core/blank_spec.rb +11 -0
  70. data/spec/extra_core/class_spec.rb +2 -2
  71. data/spec/extra_core/logger_spec.rb +50 -0
  72. data/spec/extra_core/string_transformations_spec.rb +0 -1
  73. data/spec/integration/cookies_spec.rb +1 -2
  74. data/spec/integration/flash_spec.rb +2 -3
  75. data/spec/integration/list_spec.rb +1 -1
  76. data/spec/integration/templates_spec.rb +0 -1
  77. data/spec/integration/url_spec.rb +1 -2
  78. data/spec/integration/user_spec.rb +3 -3
  79. data/spec/models/field_helpers_spec.rb +2 -2
  80. data/spec/models/model_spec.rb +21 -2
  81. data/spec/models/user_spec.rb +69 -0
  82. data/spec/models/validations_spec.rb +69 -78
  83. data/spec/models/validators/email_validator_spec.rb +3 -3
  84. data/spec/models/validators/format_validator_spec.rb +144 -0
  85. data/spec/models/validators/length_validator_spec.rb +82 -0
  86. data/spec/models/validators/phone_number_validator_spec.rb +3 -3
  87. data/spec/page/bindings/template_binding/view_lookup_for_path_spec.rb +149 -0
  88. data/spec/page/bindings/template_binding_spec.rb +0 -151
  89. data/spec/reactive/computation_spec.rb +46 -0
  90. data/spec/reactive/dependency_spec.rb +0 -1
  91. data/spec/reactive/reactive_array_spec.rb +0 -1
  92. data/spec/router/routes_spec.rb +0 -4
  93. data/spec/server/html_parser/view_parser_spec.rb +0 -4
  94. data/spec/server/rack/asset_files_spec.rb +1 -1
  95. data/spec/server/rack/quite_common_logger_spec.rb +55 -0
  96. data/spec/spec_helper.rb +2 -5
  97. data/spec/tasks/dispatcher_spec.rb +16 -5
  98. data/spec/tasks/live_query_spec.rb +0 -1
  99. data/spec/tasks/query_tasks.rb +0 -1
  100. data/spec/tasks/query_tracker_spec.rb +0 -3
  101. data/spec/templates/targets/binding_document/component_node_spec.rb +0 -1
  102. data/spec/utils/generic_counting_pool_spec.rb +0 -1
  103. data/spec/utils/generic_pool_spec.rb +0 -1
  104. data/templates/component/assets/images/.empty_directory +0 -0
  105. data/templates/project/README.md.tt +3 -2
  106. data/templates/project/app/main/assets/images/.empty_directory +0 -0
  107. data/templates/project/config/base/index.html +6 -7
  108. data/volt.gemspec +3 -5
  109. metadata +27 -9
  110. data/lib/volt/extra_core/numeric.rb +0 -9
@@ -0,0 +1,50 @@
1
+ if RUBY_PLATFORM != 'opal'
2
+ describe Volt::VoltLogger do
3
+ let(:args) { [5, :arg2] }
4
+ let(:class_name) { 'ClassName' }
5
+ let(:method_name) { 'method_name' }
6
+ let(:run_time) { 50 }
7
+
8
+ let(:logger) { Volt::VoltLogger.new }
9
+
10
+ let(:logger_with_opts) do
11
+ Volt::VoltLogger.new({
12
+ args: args,
13
+ class_name: class_name,
14
+ method_name: method_name,
15
+ run_time: run_time
16
+ })
17
+ end
18
+
19
+ it 'should log only severity and message wrapped in line breaks' do
20
+ expect(STDOUT).to receive(:write).with("\n\n[INFO] message\n")
21
+ logger.log(Logger::INFO, "message")
22
+ end
23
+
24
+ it 'should convert an array of arguments into a string' do
25
+ expect(logger_with_opts.args).to eq([5, :arg2])
26
+ end
27
+
28
+ context 'when STDOUT is a TTY' do
29
+ it 'should return a blue class name' do
30
+ expect(logger_with_opts.class_name).to eq("\e[1;34m#{class_name}\e[0;37m")
31
+ end
32
+
33
+ it 'should return a green method name' do
34
+ expect(logger_with_opts.method_name).to eq("\e[0;32m#{method_name}\e[0;37m")
35
+ end
36
+
37
+ it 'should return a green run time in milliseconds' do
38
+ expect(logger_with_opts.run_time).to eq("\e[0;32m#{run_time}ms\e[0;37m")
39
+ end
40
+ end
41
+
42
+ context 'when STDOUT is not a TTY' do
43
+ before { allow(STDOUT).to receive(:tty?).and_return(false) }
44
+
45
+ it 'should not add any terminal color codes' do
46
+ expect(logger_with_opts.class_name).to eq(class_name)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -44,5 +44,4 @@ describe '#camelize' do
44
44
  expect('HTMLTidy'.underscore).to eq('html_tidy')
45
45
  expect('HTMLTidyGenerator'.underscore).to eq('html_tidy_generator')
46
46
  end
47
-
48
47
  end
@@ -42,7 +42,6 @@ if ENV['BROWSER']
42
42
  page.evaluate_script('document.location.reload()')
43
43
 
44
44
  expect(page).to_not have_content('two: two')
45
-
46
45
  end
47
46
  end
48
- end
47
+ end
@@ -2,7 +2,7 @@
2
2
  if ENV['BROWSER'] && ENV['BROWSER'] != 'sauce'
3
3
  require 'spec_helper'
4
4
 
5
- describe 'flash messages', type: :feature, :sauce => true do
5
+ describe 'flash messages', type: :feature, sauce: true do
6
6
  it 'should flash on sucesses, notices, warnings, and errors' do
7
7
  visit '/'
8
8
 
@@ -27,7 +27,6 @@ if ENV['BROWSER'] && ENV['BROWSER'] != 'sauce'
27
27
  expect(page).to have_content('An error message')
28
28
  find('.alert').click
29
29
  expect(page).to_not have_content('An error message')
30
-
31
30
  end
32
31
  end
33
- end
32
+ end
@@ -1,7 +1,7 @@
1
1
  if ENV['BROWSER'] == 'firefox'
2
2
  require 'spec_helper'
3
3
 
4
- describe 'todo example spec', type: :feature, :sauce => true do
4
+ describe 'todo example spec', type: :feature, sauce: true do
5
5
  before do
6
6
  visit '/'
7
7
 
@@ -9,7 +9,6 @@ if ENV['BROWSER']
9
9
  click_link 'Bindings'
10
10
 
11
11
  expect(page).to have_title 'Bindings - KitchenSink'
12
-
13
12
  end
14
13
  end
15
14
  end
@@ -26,6 +26,5 @@ if ENV['BROWSER'] && ENV['BROWSER'] != 'phantom'
26
26
 
27
27
  expect(current_url).to match(/\/$/)
28
28
  end
29
-
30
29
  end
31
- end
30
+ end
@@ -1,7 +1,7 @@
1
1
  if ENV['BROWSER']
2
2
  require 'spec_helper'
3
3
 
4
- describe "user accounts", type: :feature, sauce: true do
4
+ describe 'user accounts', type: :feature, sauce: true do
5
5
  before(:each) do
6
6
  # Clear out db
7
7
  DataStore.new.drop_database
@@ -38,7 +38,7 @@ if ENV['BROWSER']
38
38
  visit '/'
39
39
 
40
40
  # Add the user
41
- $page.store._users << {email: 'test@test.com', password: 'awes0mesEcRet', name: 'Test Account 9550'}
41
+ $page.store._users << { email: 'test@test.com', password: 'awes0mesEcRet', name: 'Test Account 9550' }
42
42
 
43
43
  click_link 'Login'
44
44
 
@@ -77,4 +77,4 @@ if ENV['BROWSER']
77
77
  end
78
78
  end
79
79
 
80
- end
80
+ end
@@ -6,7 +6,7 @@ class ExampleModelWithField < Volt::Model
6
6
  field :value, Numeric
7
7
  end
8
8
 
9
- describe "field helpers" do
9
+ describe 'field helpers' do
10
10
  it 'should allow a user to setup a field that can be written to and read' do
11
11
  model = ExampleModelWithField.new
12
12
 
@@ -26,4 +26,4 @@ describe "field helpers" do
26
26
  ExampleModelWithField.field :awesome, Array
27
27
  end.to raise_error(FieldHelpers::InvalidFieldClass)
28
28
  end
29
- end
29
+ end
@@ -8,7 +8,6 @@ class Item < Volt::Model
8
8
  end
9
9
 
10
10
  describe Volt::Model do
11
-
12
11
  it 'should allow _ methods to be used to store values without predefining them' do
13
12
  a = Volt::Model.new
14
13
  a._stash = 'yes'
@@ -131,7 +130,6 @@ describe Volt::Model do
131
130
  Volt::Computation.flush!
132
131
 
133
132
  expect(values).to eq([nil, 'one'])
134
-
135
133
  end
136
134
 
137
135
  it 'should trigger changed for any indicies after a deleted index' do
@@ -401,6 +399,27 @@ describe Volt::Model do
401
399
  end
402
400
  end
403
401
 
402
+ describe 'reserved attributes' do
403
+ let(:model) { Volt::Model.new }
404
+
405
+ it 'should prevent reserved attributes from being read with underscores' do
406
+ [:attributes, :parent, :path, :options, :persistor].each do |attr_name|
407
+ expect do
408
+ model.send(:"_#{attr_name}")
409
+ end.to raise_error(Volt::InvalidFieldName, "`#{attr_name}` is reserved and can not be used as a field")
410
+ end
411
+
412
+ end
413
+
414
+ it 'should prevent reserved attributes from being assigned directly' do
415
+ [:attributes, :parent, :path, :options, :persistor].each do |attr_name|
416
+ expect do
417
+ model.send(:"_#{attr_name}=", 'assign val')
418
+ end.to raise_error(Volt::InvalidFieldName, "`#{attr_name}` is reserved and can not be used as a field")
419
+ end
420
+ end
421
+ end
422
+
404
423
  describe 'persistors' do
405
424
  it 'should setup a new instance of the persistor with self' do
406
425
  persistor = double('volt/persistor')
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'volt/models/user'
3
+
4
+ class FakeConfig
5
+ def public; self; end
6
+ def auth; self; end
7
+ def use_username; true; end
8
+ end
9
+
10
+ describe Volt::User do
11
+ describe '.login_field' do
12
+ subject { Volt::User.login_field }
13
+
14
+ context 'when use_username is set to true' do
15
+ before do
16
+ allow(Volt).to receive(:config).and_return FakeConfig.new
17
+ end
18
+
19
+ it "returns :username" do
20
+ expect(subject).to eq :username
21
+ end
22
+ end
23
+
24
+ context 'when use_username is not set' do
25
+ it "returns :email" do
26
+ expect(subject).to eq :email
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '#password=' do
32
+ let!(:user) { Volt::User.new }
33
+
34
+ subject { user.password = 'test' }
35
+
36
+ if RUBY_PLATFORM != 'opal'
37
+ context 'when it is a Volt server' do
38
+ before do
39
+ allow(BCrypt::Password).to receive(:create).with('test').
40
+ and_return 'hashed-password'
41
+ end
42
+
43
+ it "encrypts password" do
44
+ subject
45
+
46
+ expect(BCrypt::Password).to have_received :create
47
+ end
48
+
49
+ it 'sets _hashed_password to passed value' do
50
+ subject
51
+
52
+ expect(user._hashed_password).to eq "hashed-password"
53
+ end
54
+ end
55
+ end
56
+
57
+ context 'when it is not a Volt server' do
58
+ before do
59
+ allow(Volt).to receive(:server?).and_return false
60
+ end
61
+
62
+ it 'sets _password to passed value' do
63
+ subject
64
+
65
+ expect(user._password).to eq 'test'
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,17 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
- class TestModel < Volt::Model
4
- validate :count, numericality: { min: 5, max: 10 }
5
- validate :description, length: { message: 'needs to be longer', length: 50 }
6
- validate :email, email: true
7
- validate :name, length: 4
8
- validate :phone_number, phone_number: true
9
- validate :username, presence: true
10
- end
11
-
12
3
  describe Volt::Model do
13
- it 'should validate the name' do
14
- expect(TestModel.new.errors).to eq(
4
+ let(:model) { test_model_class.new }
5
+
6
+ let(:test_model_class) do
7
+ Class.new(Volt::Model) do
8
+ validate :count, numericality: { min: 5, max: 10 }
9
+ validate :description, length: { message: 'needs to be longer',
10
+ length: 50 }
11
+ validate :email, email: true
12
+ validate :name, length: 4
13
+ validate :phone_number, phone_number: true
14
+ validate :username, presence: true
15
+ end
16
+ end
17
+
18
+ it 'should return errors for all failed validations' do
19
+ expect(model.errors).to eq(
15
20
  count: ['must be a number'],
16
21
  description: ['needs to be longer'],
17
22
  email: ['must be an email address'],
@@ -21,23 +26,7 @@ describe Volt::Model do
21
26
  )
22
27
  end
23
28
 
24
- it 'should show marked validations once they are marked' do
25
- model = TestModel.new
26
-
27
- expect(model.marked_errors).to eq({})
28
-
29
- model.mark_field!(:name)
30
-
31
- expect(model.marked_errors).to eq(
32
- name: ['must be at least 4 characters']
33
- )
34
- end
35
-
36
29
  it 'should show all fields in marked errors once saved' do
37
- model = TestModel.new
38
-
39
- expect(model.marked_errors).to eq({})
40
-
41
30
  model.save!
42
31
 
43
32
  expect(model.marked_errors.keys).to eq(
@@ -45,73 +34,75 @@ describe Volt::Model do
45
34
  )
46
35
  end
47
36
 
48
- describe 'length' do
49
- it 'should allow custom errors on length' do
50
- model = TestModel.new
51
-
52
- expect(model.marked_errors).to eq({})
53
-
54
- model.mark_field!(:description)
55
-
56
- expect(model.marked_errors).to eq(
57
- description: ['needs to be longer']
58
- )
37
+ describe 'builtin validations' do
38
+ shared_examples_for 'a built in validation' do |field, message|
39
+ specify do
40
+ expect { model.mark_field! field }
41
+ .to change { model.marked_errors }
42
+ .from({}).to({ field => [message ] })
43
+ end
59
44
  end
60
- end
61
-
62
- describe 'presence' do
63
- it 'should validate presence' do
64
- model = TestModel.new
65
-
66
- expect(model.marked_errors).to eq({})
67
45
 
68
- model.mark_field!(:username)
46
+ describe 'numericality' do
47
+ message = 'must be a number'
48
+ it_should_behave_like 'a built in validation', :count, message
49
+ end
69
50
 
70
- expect(model.marked_errors).to eq(
71
- username: ['must be specified']
72
- )
51
+ describe 'length' do
52
+ message = 'needs to be longer'
53
+ it_should_behave_like 'a built in validation', :description, message
73
54
  end
74
- end
75
55
 
76
- describe 'numericality' do
77
- it 'should validate numericality' do
78
- model = TestModel.new
56
+ describe 'email' do
57
+ message = 'must be an email address'
58
+ it_should_behave_like 'a built in validation', :email, message
59
+ end
79
60
 
80
- expect(model.marked_errors).to eq({})
61
+ describe 'name' do
62
+ message = 'must be at least 4 characters'
63
+ it_should_behave_like 'a built in validation', :name, message
64
+ end
81
65
 
82
- model.mark_field!(:count)
66
+ describe 'phone_number' do
67
+ message = 'must be a phone number with area or country code'
68
+ it_should_behave_like 'a built in validation', :phone_number, message
69
+ end
83
70
 
84
- expect(model.marked_errors).to eq(
85
- count: ['must be a number']
86
- )
71
+ describe 'presence' do
72
+ message = 'must be specified'
73
+ it_should_behave_like 'a built in validation', :username, message
87
74
  end
88
75
  end
89
76
 
90
- describe 'email' do
91
- it 'should validate email' do
92
- model = TestModel.new
93
-
94
- expect(model.marked_errors).to eq({})
95
-
96
- model.mark_field!(:email)
97
-
98
- expect(model.marked_errors).to eq(
99
- email: ['must be an email address']
100
- )
77
+ describe 'validators with multiple criteria' do
78
+ let(:regex_message) { 'regex failed' }
79
+ let(:proc_message) { 'proc failed' }
80
+
81
+ let(:test_model_class) do
82
+ Class.new(Volt::Model) do
83
+ validate :special_field, format: [
84
+ { with: /regex/, message: 'regex failed' },
85
+ { with: ->(x) {x == false}, message: 'proc failed' }
86
+ ]
87
+ end
101
88
  end
102
- end
103
89
 
104
- describe 'phone_number' do
105
- it 'should validate phone number' do
106
- model = TestModel.new
90
+ context 'when multiple fail' do
91
+ before { model._special_field = 'nope' }
107
92
 
108
- expect(model.marked_errors).to eq({})
93
+ it 'returns an array of errors' do
94
+ expect(model.errors).to eq({
95
+ special_field: [ regex_message, proc_message ]
96
+ })
97
+ end
98
+ end
109
99
 
110
- model.mark_field!(:phone_number)
100
+ context 'when one fails' do
101
+ before { model._special_field = 'regex' }
111
102
 
112
- expect(model.marked_errors).to eq(
113
- phone_number: ['must be a phone number with area or country code']
114
- )
103
+ it 'returns an array with a single error' do
104
+ expect(model.errors).to eq({ special_field: [ proc_message ] })
105
+ end
115
106
  end
116
107
  end
117
108
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Volt::EmailValidator do
4
4
  subject { Volt::EmailValidator.new(*params) }
5
- let(:params) { [ model, field_name, options ] }
5
+ let(:params) { [model, field_name, options] }
6
6
 
7
7
  let(:model) { Volt::Model.new email: email }
8
8
  let(:field_name) { :email }
@@ -105,14 +105,14 @@ describe Volt::EmailValidator do
105
105
  end
106
106
 
107
107
  context 'when provided a custom error message' do
108
- let(:options) { { error_message: custom_message } }
108
+ let(:options) { { message: custom_message } }
109
109
  let(:custom_message) { 'this is a custom message' }
110
110
 
111
111
  context 'and the email is invalid' do
112
112
  let(:email) { invalid_email }
113
113
 
114
114
  it 'returns errors with the custom message' do
115
- expect(subject.errors).to eq(email: [ custom_message ])
115
+ expect(subject.errors).to eq(email: [custom_message])
116
116
  end
117
117
  end
118
118
  end
@@ -0,0 +1,144 @@
1
+ require 'spec_helper'
2
+
3
+ describe Volt::FormatValidator do
4
+ subject { described_class.new(*init_params) }
5
+
6
+ let(:init_params) { [ model, field_name ] }
7
+ let(:validate_params) { [ model, nil, field_name, options ] }
8
+
9
+ let(:model) { Volt::Model.new field: field_content }
10
+ let(:field_name) { :field }
11
+ let(:options) { regex_opts }
12
+
13
+ let(:regex) { /^valid/ }
14
+ let(:proc_regex) { /^valid/ }
15
+ let(:test_proc) { ->(content) { proc_regex.match content } }
16
+
17
+ let(:proc_opts) { { with: test_proc, message: proc_message } }
18
+ let(:regex_opts) { { with: regex, message: regex_message } }
19
+
20
+ let(:proc_message) { 'proc is invalid' }
21
+ let(:regex_message) { 'regex is invalid' }
22
+
23
+ let(:field_content) { valid_content }
24
+ let(:invalid_content) { 'invalid_content' }
25
+ let(:valid_content) { 'valid_content' }
26
+
27
+ let(:validate) { described_class.validate(*validate_params) }
28
+
29
+ before do
30
+ allow(described_class).to receive(:new).and_return subject
31
+ end
32
+
33
+ context 'when no criteria is provided' do
34
+ before { validate }
35
+
36
+ it 'should have no errors' do
37
+ expect(subject.errors).to eq({})
38
+ end
39
+
40
+ specify { expect(subject).to be_valid }
41
+ end
42
+
43
+ context 'when the only criterion is a regex' do
44
+ let(:options) { regex_opts }
45
+
46
+ before { validate }
47
+
48
+ context 'and the field matches' do
49
+ let(:field_content) { valid_content }
50
+
51
+ it 'should have no errors' do
52
+ expect(subject.errors).to eq({})
53
+ end
54
+
55
+ specify { expect(subject).to be_valid }
56
+ end
57
+
58
+ context 'and the field does not match' do
59
+ let(:field_content) { invalid_content }
60
+
61
+ it 'should report the related error message' do
62
+ expect(subject.errors).to eq field_name => [regex_message]
63
+ end
64
+
65
+ specify { expect(subject).to_not be_valid }
66
+ end
67
+ end
68
+
69
+ context 'when the only criterion is a block' do
70
+ let(:options) { proc_opts }
71
+
72
+ before { validate }
73
+
74
+ context 'and the field passes the block' do
75
+ let(:field_content) { valid_content }
76
+
77
+ it 'should have no errors' do
78
+ expect(subject.errors).to eq({})
79
+ end
80
+
81
+ specify { expect(subject).to be_valid }
82
+ end
83
+
84
+ context 'and the field fails the block' do
85
+ let(:field_content) { invalid_content }
86
+
87
+ it 'should report the related error message' do
88
+ expect(subject.errors).to eq field_name => [proc_message]
89
+ end
90
+
91
+ specify { expect(subject).to_not be_valid }
92
+ end
93
+ end
94
+
95
+ context 'when there is both regex and block criteria' do
96
+ let(:options) { [ regex_opts, proc_opts ] }
97
+
98
+ before { validate }
99
+
100
+ context 'and the field passes all criteria' do
101
+ let(:field_content) { valid_content }
102
+
103
+ it 'should have no errors' do
104
+ expect(subject.errors).to eq({})
105
+ end
106
+
107
+ specify { expect(subject).to be_valid }
108
+ end
109
+
110
+ context 'and the field fails the regex' do
111
+ let(:regex) { /^invalid/ }
112
+
113
+ it 'should report the related error message' do
114
+ expect(subject.errors).to eq field_name => [regex_message]
115
+ end
116
+
117
+ specify { expect(subject).to_not be_valid }
118
+ end
119
+
120
+ context 'and the field fails the block' do
121
+ let(:proc_regex) { /^invalid/ }
122
+
123
+ it 'should report the related error message' do
124
+ expect(subject.errors).to eq field_name => [proc_message]
125
+ end
126
+
127
+ specify { expect(subject).to_not be_valid }
128
+ end
129
+
130
+ context 'and the field fails both the regex and the block' do
131
+ let(:field_content) { invalid_content }
132
+
133
+ it 'should report the regex error message' do
134
+ expect(subject.errors[field_name]).to include regex_message
135
+ end
136
+
137
+ it 'should report the proc error message' do
138
+ expect(subject.errors[field_name]).to include proc_message
139
+ end
140
+
141
+ specify { expect(subject).to_not be_valid }
142
+ end
143
+ end
144
+ end