volt 0.8.27.beta2 → 0.8.27.beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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