volt 0.8.27.beta3 → 0.8.27.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +11 -0
  4. data/CONTRIBUTING.md +3 -2
  5. data/{Readme.md → README.md} +9 -12
  6. data/Rakefile +2 -9
  7. data/VERSION +1 -1
  8. data/app/volt/models/user.rb +8 -0
  9. data/app/volt/tasks/live_query/data_store.rb +13 -5
  10. data/app/volt/tasks/live_query/live_query.rb +45 -3
  11. data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
  12. data/app/volt/tasks/query_tasks.rb +20 -2
  13. data/app/volt/tasks/store_tasks.rb +37 -20
  14. data/app/volt/tasks/user_tasks.rb +15 -13
  15. data/lib/volt/boot.rb +5 -3
  16. data/lib/volt/cli/console.rb +1 -0
  17. data/lib/volt/cli/generate.rb +15 -0
  18. data/lib/volt/cli.rb +19 -12
  19. data/lib/volt/config.rb +1 -1
  20. data/lib/volt/controllers/model_controller.rb +13 -3
  21. data/lib/volt/extra_core/extra_core.rb +1 -0
  22. data/lib/volt/extra_core/hash.rb +26 -0
  23. data/lib/volt/extra_core/object.rb +5 -1
  24. data/lib/volt/models/array_model.rb +86 -35
  25. data/lib/volt/models/associations.rb +53 -0
  26. data/lib/volt/models/buffer.rb +22 -10
  27. data/lib/volt/models/dirty.rb +88 -0
  28. data/lib/volt/models/errors.rb +21 -0
  29. data/lib/volt/models/field_helpers.rb +2 -2
  30. data/lib/volt/models/listener_tracker.rb +17 -0
  31. data/lib/volt/models/model.rb +213 -69
  32. data/lib/volt/models/model_helpers.rb +27 -17
  33. data/lib/volt/models/permissions.rb +246 -0
  34. data/lib/volt/models/persistors/array_store.rb +149 -81
  35. data/lib/volt/models/persistors/base.rb +16 -0
  36. data/lib/volt/models/persistors/cookies.rb +14 -9
  37. data/lib/volt/models/persistors/flash.rb +3 -0
  38. data/lib/volt/models/persistors/local_store.rb +0 -16
  39. data/lib/volt/models/persistors/model_store.rb +1 -2
  40. data/lib/volt/models/persistors/query/normalizer.rb +51 -0
  41. data/lib/volt/models/persistors/query/query_listener.rb +21 -5
  42. data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
  43. data/lib/volt/models/persistors/store.rb +8 -0
  44. data/lib/volt/models/persistors/store_state.rb +4 -27
  45. data/lib/volt/models/state_helpers.rb +11 -0
  46. data/lib/volt/models/state_manager.rb +43 -0
  47. data/lib/volt/models/url.rb +5 -5
  48. data/lib/volt/models/validations.rb +38 -41
  49. data/lib/volt/models/validators/email_validator.rb +4 -9
  50. data/lib/volt/models/validators/format_validator.rb +23 -8
  51. data/lib/volt/models/validators/length_validator.rb +2 -2
  52. data/lib/volt/models/validators/numericality_validator.rb +7 -3
  53. data/lib/volt/models/validators/phone_number_validator.rb +4 -9
  54. data/lib/volt/models/validators/presence_validator.rb +2 -2
  55. data/lib/volt/models/validators/unique_validator.rb +2 -2
  56. data/lib/volt/models/validators/user_validation.rb +6 -0
  57. data/lib/volt/models.rb +8 -3
  58. data/lib/volt/page/bindings/attribute_binding.rb +10 -4
  59. data/lib/volt/page/bindings/content_binding.rb +9 -5
  60. data/lib/volt/page/bindings/if_binding.rb +25 -2
  61. data/lib/volt/page/bindings/template_binding.rb +19 -1
  62. data/lib/volt/page/bindings/yield_binding.rb +31 -0
  63. data/lib/volt/page/page.rb +11 -16
  64. data/lib/volt/reactive/class_eventable.rb +71 -0
  65. data/lib/volt/reactive/computation.rb +79 -10
  66. data/lib/volt/reactive/dependency.rb +27 -8
  67. data/lib/volt/reactive/eventable.rb +36 -22
  68. data/lib/volt/reactive/reactive_array.rb +2 -3
  69. data/lib/volt/reactive/reactive_hash.rb +8 -3
  70. data/lib/volt/router/routes.rb +2 -1
  71. data/lib/volt/server/component_templates.rb +0 -2
  72. data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
  73. data/lib/volt/server/html_parser/view_handler.rb +3 -0
  74. data/lib/volt/server/html_parser/view_parser.rb +1 -0
  75. data/lib/volt/server/html_parser/view_scope.rb +17 -41
  76. data/lib/volt/server/rack/component_paths.rb +1 -10
  77. data/lib/volt/server/rack/index_files.rb +9 -4
  78. data/lib/volt/server/rack/opal_files.rb +22 -14
  79. data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
  80. data/lib/volt/server/socket_connection_handler.rb +4 -0
  81. data/lib/volt/spec/setup.rb +26 -0
  82. data/lib/volt/tasks/dispatcher.rb +11 -0
  83. data/lib/volt/utils/event_counter.rb +29 -0
  84. data/lib/volt/utils/generic_pool.rb +12 -0
  85. data/lib/volt/utils/modes.rb +40 -0
  86. data/lib/volt/utils/promise_patch.rb +66 -0
  87. data/lib/volt/utils/timers.rb +33 -0
  88. data/lib/volt/volt/users.rb +48 -5
  89. data/lib/volt.rb +4 -0
  90. data/spec/apps/kitchen_sink/Gemfile +3 -1
  91. data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
  92. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
  93. data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
  94. data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
  95. data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
  96. data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
  97. data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
  98. data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
  99. data/spec/extra_core/logger_spec.rb +4 -2
  100. data/spec/integration/user_spec.rb +42 -42
  101. data/spec/integration/yield_spec.rb +18 -0
  102. data/spec/models/associations_spec.rb +37 -0
  103. data/spec/models/dirty_spec.rb +102 -0
  104. data/spec/models/model_spec.rb +64 -8
  105. data/spec/models/model_state_spec.rb +24 -0
  106. data/spec/models/permissions_spec.rb +96 -0
  107. data/spec/models/user_spec.rb +8 -5
  108. data/spec/models/user_validation_spec.rb +24 -0
  109. data/spec/models/validations_spec.rb +44 -5
  110. data/spec/models/validators/email_validator_spec.rb +109 -82
  111. data/spec/models/validators/format_validator_spec.rb +4 -107
  112. data/spec/models/validators/length_validator_spec.rb +9 -9
  113. data/spec/models/validators/phone_number_validator_spec.rb +60 -103
  114. data/spec/models/validators/shared_examples_for_validators.rb +123 -0
  115. data/spec/reactive/class_eventable_spec.rb +37 -0
  116. data/spec/reactive/computation_spec.rb +68 -3
  117. data/spec/reactive/dependency_spec.rb +71 -0
  118. data/spec/reactive/eventable_spec.rb +21 -0
  119. data/spec/reactive/reactive_hash_spec.rb +12 -0
  120. data/spec/router/routes_spec.rb +50 -50
  121. data/spec/server/html_parser/view_parser_spec.rb +0 -3
  122. data/spec/server/rack/component_paths_spec.rb +11 -0
  123. data/spec/server/rack/quite_common_logger_spec.rb +3 -4
  124. data/spec/spec_helper.rb +7 -3
  125. data/templates/component/config/dependencies.rb +1 -7
  126. data/templates/component/config/routes.rb +1 -1
  127. data/templates/component/controllers/main_controller.rb.tt +20 -0
  128. data/templates/component/views/{index → main}/index.html.tt +0 -0
  129. data/templates/newgem/lib/newgem.rb.tt +1 -3
  130. data/templates/project/app/main/config/routes.rb +3 -3
  131. data/templates/project/app/main/views/main/main.html.tt +4 -4
  132. data/templates/project/config/app.rb.tt +6 -0
  133. data/volt.gemspec +11 -7
  134. metadata +96 -42
  135. data/lib/volt/models/model_state.rb +0 -21
  136. data/templates/component/controllers/main_controller.rb +0 -18
@@ -11,7 +11,9 @@ gem 'volt-bootstrap'
11
11
  gem 'volt-bootstrap-jumbotron-theme'
12
12
 
13
13
  gem 'volt-fields'
14
- gem 'volt-user-templates'
14
+ gem 'volt-user-templates', path: '/Users/ryanstout/Sites/volt/apps/volt-user-templates'
15
+
16
+ gem 'opal'
15
17
 
16
18
  # Server for MRI
17
19
  platform :mri do
@@ -1,15 +1,16 @@
1
1
  # See https://github.com/voltrb/volt#routes for more info on routes
2
2
 
3
- get '/bindings/{{_route_test}}', _action: 'bindings'
4
- get '/bindings', _action: 'bindings'
5
- get '/store', _action: 'store'
6
- get '/cookie_test', _action: 'cookie_test'
7
- get '/flash', _action: 'flash'
8
- get '/todos', _controller: 'todos'
3
+ get '/bindings/{{ route_test }}', action: 'bindings'
4
+ get '/bindings', action: 'bindings'
5
+ get '/store', action: 'store'
6
+ get '/cookie_test', action: 'cookie_test'
7
+ get '/flash', action: 'flash'
8
+ get '/yield', action: 'yield'
9
+ get '/todos', controller: 'todos'
9
10
 
10
11
  # Signup/login routes
11
- get '/signup', _controller: 'user-templates', _action: 'signup'
12
- get '/login', _controller: 'user-templates', _action: 'login'
12
+ get '/signup', controller: 'user-templates', action: 'signup'
13
+ get '/login', controller: 'user-templates', action: 'login'
13
14
 
14
15
  # The main route, this should be last. It will match any params not previously matched.
15
16
  get '/', {}
@@ -1,6 +1,11 @@
1
1
  class MainController < Volt::ModelController
2
2
  model :page
3
3
 
4
+ def index
5
+ a = {}
6
+ a[{}] = 5
7
+ end
8
+
4
9
  def flash_notice
5
10
  flash._notices << 'A notice message'
6
11
  end
@@ -27,6 +32,10 @@ class MainController < Volt::ModelController
27
32
  self.model = page._new_cookie.buffer
28
33
  end
29
34
 
35
+ def content_string
36
+ 'content'
37
+ end
38
+
30
39
  private
31
40
 
32
41
  # the main template contains a #template binding that shows another
@@ -0,0 +1,5 @@
1
+ class YieldComponentController < Volt::ModelController
2
+ def content_string
3
+ 'wrong'
4
+ end
5
+ end
@@ -20,6 +20,6 @@
20
20
 
21
21
  <ul>
22
22
  {{ cookies.keys.each do |key| }}
23
- <li>{{ key }}: {{ cookies.read_attribute(key) }} <button class="cookieDelete" e-click="cookies.delete(key)">X</button></li>
23
+ <li>{{ key }}: {{ cookies.get(key) }} <button class="cookieDelete" e-click="cookies.delete(key)">X</button></li>
24
24
  {{ end }}
25
25
  </ul>
@@ -4,4 +4,4 @@
4
4
  <:Body>
5
5
  <h2>Kitchen Sink</h2>
6
6
 
7
- <p>The kitchen sink app both showcases many of the features of Volt and acts as an app for integration testing.</p>
7
+ <p>The kitchen sink app both showcases many of the features of Volt and acts as an app for integration testing.</p>
@@ -11,6 +11,7 @@
11
11
  <:nav href="/flash" text="Flash" />
12
12
  <:nav href="/cookie_test" text="Cookies" />
13
13
  <:nav href="/store" text="Store" />
14
+ <:nav href="/yield" text="Yield" />
14
15
  <:user-templates:menu />
15
16
  </ul>
16
17
  <h3 class="text-muted">Project name</h3>
@@ -21,7 +22,7 @@
21
22
  {{ template main_path, 'body', {controller_group: 'main'} }}
22
23
 
23
24
  <div class="footer">
24
- <p>&copy; Company 2014</p>
25
+ <p>&copy; Company {{ Time.now.year }}</p>
25
26
  </div>
26
27
 
27
28
  </div>
@@ -0,0 +1,18 @@
1
+ <:Title>
2
+ Yield Binding
3
+
4
+ <:Body>
5
+ <:yield-test>My yielded content {{ @i ||= 0 ; @i += 1 }}</:yield-test>
6
+
7
+ <:yield-component>This is my {{ content_string }}</:yield-component>
8
+
9
+ <:Yield-test>
10
+ <h1>Yield Section</h1>
11
+
12
+ <p>{{ yield }}</p>
13
+ <p>{{ yield }}</p>
14
+
15
+
16
+ {{ if Volt.user }}
17
+ Email: {{ Volt.user._email }}
18
+ {{ end }}
@@ -0,0 +1,4 @@
1
+ <:Body>
2
+ <h1>Yield Component</h1>
3
+
4
+ {{ yield }}
@@ -25,7 +25,9 @@ if RUBY_PLATFORM != 'opal'
25
25
  expect(logger_with_opts.args).to eq([5, :arg2])
26
26
  end
27
27
 
28
- context 'when STDOUT is a TTY' do
28
+ describe 'when STDOUT is a TTY' do
29
+ before { allow(STDOUT).to receive(:tty?).and_return(true) }
30
+
29
31
  it 'should return a blue class name' do
30
32
  expect(logger_with_opts.class_name).to eq("\e[1;34m#{class_name}\e[0;37m")
31
33
  end
@@ -39,7 +41,7 @@ if RUBY_PLATFORM != 'opal'
39
41
  end
40
42
  end
41
43
 
42
- context 'when STDOUT is not a TTY' do
44
+ describe 'when STDOUT is not a TTY' do
43
45
  before { allow(STDOUT).to receive(:tty?).and_return(false) }
44
46
 
45
47
  it 'should not add any terminal color codes' do
@@ -33,48 +33,48 @@ if ENV['BROWSER']
33
33
 
34
34
  expect(page).to have_content('Test Account 9550')
35
35
  end
36
-
37
- it 'should login and logout' do
38
- visit '/'
39
-
40
- # Add the user
41
- $page.store._users << { email: 'test@test.com', password: 'awes0mesEcRet', name: 'Test Account 9550' }
42
-
43
- click_link 'Login'
44
-
45
- fields = all(:css, 'form .form-control')
46
- fields[0].set('test@test.com')
47
- fields[1].set('awes0mesEcRet')
48
- click_button 'Login'
49
-
50
- expect(page).to have_content('Test Account 9550')
51
-
52
- # Click the logout link
53
- click_link 'Test Account 9550'
54
- click_link 'Logout'
55
-
56
- expect(page).to_not have_content('Test Account 9550')
57
- end
58
-
59
- it 'should fail to create an account without a valid email and password' do
60
- visit '/'
61
-
62
- click_link 'Login'
63
- click_link 'Signup here'
64
-
65
- expect(page).to_not have_content('must be at least 8 characters')
66
-
67
- fields = all(:css, 'form .form-control')
68
-
69
- fields[0].set('test')
70
- fields[1].set('awe')
71
- fields[2].set('Tes')
72
-
73
- # some capybara drivers don't trigger blur correctly
74
- page.execute_script("$('.form-control').blur()")
75
-
76
- expect(page).to have_content('must be at least 8 characters')
77
- end
36
+ #
37
+ # it 'should login and logout' do
38
+ # visit '/'
39
+ #
40
+ # # Add the user
41
+ # $page.store._users << { email: 'test@test.com', password: 'awes0mesEcRet', name: 'Test Account 9550' }
42
+ #
43
+ # click_link 'Login'
44
+ #
45
+ # fields = all(:css, 'form .form-control')
46
+ # fields[0].set('test@test.com')
47
+ # fields[1].set('awes0mesEcRet')
48
+ # click_button 'Login'
49
+ #
50
+ # expect(page).to have_content('Test Account 9550')
51
+ #
52
+ # # Click the logout link
53
+ # click_link 'Test Account 9550'
54
+ # click_link 'Logout'
55
+ #
56
+ # expect(page).to_not have_content('Test Account 9550')
57
+ # end
58
+ #
59
+ # it 'should fail to create an account without a valid email and password' do
60
+ # visit '/'
61
+ #
62
+ # click_link 'Login'
63
+ # click_link 'Signup here'
64
+ #
65
+ # expect(page).to_not have_content('must be at least 8 characters')
66
+ #
67
+ # fields = all(:css, 'form .form-control')
68
+ #
69
+ # fields[0].set('test')
70
+ # fields[1].set('awe')
71
+ # fields[2].set('Tes')
72
+ #
73
+ # # some capybara drivers don't trigger blur correctly
74
+ # page.execute_script("$('.form-control').blur()")
75
+ #
76
+ # expect(page).to have_content('must be at least 8 characters')
77
+ # end
78
78
  end
79
79
 
80
80
  end
@@ -0,0 +1,18 @@
1
+ if ENV['BROWSER']
2
+ require 'spec_helper'
3
+
4
+ describe 'yield binding', type: :feature, sauce: true do
5
+ before do
6
+ visit '/yield'
7
+ end
8
+
9
+ it 'should render the yielded content multiple times' do
10
+ expect(page).to have_content("My yielded content 1")
11
+ expect(page).to have_content("My yielded content 2")
12
+ end
13
+
14
+ it 'should render the content from the tag\'s controller when yielding' do
15
+ expect(page).to have_content('This is my content')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ class ::Person < Volt::Model
4
+ has_many :addresses
5
+ end
6
+
7
+ class ::Address < Volt::Model
8
+ belongs_to :person
9
+ end
10
+
11
+ describe Volt::Associations do
12
+ if RUBY_PLATFORM != 'opal'
13
+ before do
14
+ # DataStore.new.drop_database
15
+ # $page.instance_variable_set('@store', nil)
16
+
17
+ store._people << {name: 'Jimmy'}
18
+ @person = store._people[0]
19
+ @person._addresses << {city: 'Bozeman'}
20
+ @person._addresses << {city: 'Portland'}
21
+ end
22
+
23
+ it 'should associate via belongs_to' do
24
+ address = store._addresses.fetch_first.sync
25
+
26
+ expect(address.person.sync._id).to eq(@person._id)
27
+ end
28
+
29
+ it 'should associate via has_many' do
30
+ person = store._people.fetch_first.sync
31
+
32
+ addresses = person.addresses.fetch.sync
33
+ expect(addresses.size).to eq(2)
34
+ expect(addresses[0]._city).to eq('Bozeman')
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Volt::Dirty" do
4
+ let(:model) do
5
+ model = Volt::Model.new
6
+
7
+ # Run changed on the model will revert changes after each sync
8
+ allow(model).to receive(:run_changed)
9
+
10
+ model
11
+ end
12
+
13
+ it 'should track changed attributes' do
14
+ model._name = 'Bob'
15
+ expect(model.name_was).to eq(nil)
16
+
17
+ model._name = 'Jimmy'
18
+ expect(model.name_was).to eq(nil)
19
+ expect(model.name_changes).to eq([nil, 'Bob'])
20
+
21
+ model._name = 'Martin'
22
+ expect(model.name_was).to eq(nil)
23
+ expect(model.name_changes).to eq([nil, 'Bob', 'Jimmy'])
24
+
25
+ model._name = nil
26
+ expect(model.name_was).to eq(nil)
27
+ expect(model.name_changes).to eq([nil, 'Bob', 'Jimmy', 'Martin'])
28
+
29
+ model._name = 'Ryan'
30
+ expect(model.name_was).to eq(nil)
31
+ expect(model.name_changes).to eq([nil, 'Bob', 'Jimmy', 'Martin', nil])
32
+
33
+ expect(model.changed_attributes).to eq({:name=>[nil, "Bob", "Jimmy", "Martin", nil]})
34
+ end
35
+
36
+ it 'should say models are changed' do
37
+ expect(model.changed?(:name)).to eq(false)
38
+ model._name = 'Bob'
39
+
40
+ expect(model.changed?(:name)).to eq(true)
41
+ model._name = 'Jimmy'
42
+
43
+ expect(model.changed?(:name)).to eq(true)
44
+
45
+ model.clear_tracked_changes!
46
+
47
+ expect(model.changed?(:name)).to eq(false)
48
+ end
49
+
50
+ it 'should reset changes' do
51
+ expect(model.changed?(:name)).to eq(false)
52
+ model._name = 'Bob'
53
+ expect(model.changed?(:name)).to eq(true)
54
+ model._name = 'Jimmy'
55
+ expect(model.changed?(:name)).to eq(true)
56
+
57
+ expect(model.name_was).to eq(nil)
58
+ expect(model.name_changes).to eq([nil, 'Bob'])
59
+
60
+ model.clear_tracked_changes!
61
+
62
+ expect(model.name_was).to eq(nil)
63
+ end
64
+
65
+ it 'should not track when assigning the same value' do
66
+ expect(model.changed?(:name)).to eq(false)
67
+ model._name = nil
68
+ expect(model.changed?(:name)).to eq(false)
69
+
70
+ model._name = 'bob'
71
+ expect(model.changed?(:name)).to eq(true)
72
+ expect(model.name_changes).to eq([nil])
73
+
74
+ model._name = 'bob'
75
+ expect(model.name_changes).to eq([nil])
76
+ end
77
+
78
+ it 'should revert changes' do
79
+ expect(model.attributes).to eq({})
80
+ model.attributes = {first: 'Bob', last: 'Smith'}
81
+ expect(model.attributes).to eq({first: 'Bob', last: 'Smith'})
82
+
83
+ model.revert_changes!
84
+ expect(model.attributes).to eq({first: nil, last: nil})
85
+ end
86
+
87
+ it 'should revert changes after a clear_tracked_changed!' do
88
+ expect(model.attributes).to eq({})
89
+ model.attributes = {first: 'Bob', last: 'Smith'}
90
+ expect(model.attributes).to eq({first: 'Bob', last: 'Smith'})
91
+
92
+ model.clear_tracked_changes!
93
+ expect(model.changed_attributes).to eq({})
94
+
95
+ model._first = 'Jimmy'
96
+ model._last = 'Dean'
97
+ expect(model.attributes).to eq({first: 'Jimmy', last: 'Dean'})
98
+
99
+ model.revert_changes!
100
+ expect(model.attributes).to eq({first: 'Bob', last: 'Smith'})
101
+ end
102
+ end
@@ -7,6 +7,12 @@ end
7
7
  class Item < Volt::Model
8
8
  end
9
9
 
10
+ class TestAssignsMethod < Volt::Model
11
+ def name=(val)
12
+ self._name = val
13
+ end
14
+ end
15
+
10
16
  describe Volt::Model do
11
17
  it 'should allow _ methods to be used to store values without predefining them' do
12
18
  a = Volt::Model.new
@@ -196,22 +202,22 @@ describe Volt::Model do
196
202
  a = Volt::Model.new
197
203
 
198
204
  count = 0
199
- -> { a._blue && a._blue.respond_to?(:green) && a._blue._green; count += 1 }.watch!
205
+ -> { a._blue && a._blue.respond_to?(:_green) && a._blue._green; count += 1 }.watch!
200
206
  expect(count).to eq(1)
201
207
 
202
208
  a._blue._green = 5
203
209
  Volt::Computation.flush!
204
210
 
205
- expect(count).to eq(1)
211
+ expect(count).to eq(2)
206
212
 
207
213
  a._blue = 22
208
214
  Volt::Computation.flush!
209
- expect(count).to eq(2)
215
+ expect(count).to eq(3)
210
216
 
211
217
  a._blue = { green: 50 }
212
218
  expect(a._blue._green).to eq(50)
213
219
  Volt::Computation.flush!
214
- expect(count).to eq(3)
220
+ expect(count).to eq(4)
215
221
  end
216
222
 
217
223
  it 'should trigger changed when a value is deleted' do
@@ -381,13 +387,13 @@ describe Volt::Model do
381
387
  end
382
388
 
383
389
  it 'should set the model path for a sub array' do
384
- @model._items << { _name: 'Bob' }
390
+ @model._items << { name: 'Bob' }
385
391
  expect(@model._items.path).to eq([:items])
386
392
  expect(@model._items[0].path).to eq([:items, :[]])
387
393
  end
388
394
 
389
395
  it 'should set the model path for sub sub arrays' do
390
- @model._lists << { _name: 'List 1', _items: [] }
396
+ @model._lists << { name: 'List 1', items: [] }
391
397
  expect(@model._lists[0]._items.path).to eq([:lists, :[], :items])
392
398
  end
393
399
 
@@ -431,8 +437,6 @@ describe Volt::Model do
431
437
  if RUBY_PLATFORM != 'opal'
432
438
  describe 'class loading' do
433
439
  it 'should load classes for models' do
434
- $page.add_model('Item')
435
-
436
440
  @model = Volt::Model.new
437
441
 
438
442
  # Should return a buffer of the right type
@@ -444,4 +448,56 @@ describe Volt::Model do
444
448
  end
445
449
  end
446
450
  end
451
+
452
+ it 'should have assignments optionally go through a method' do
453
+ model = TestAssignsMethod.new
454
+
455
+ model._name = 'Jimmy'
456
+
457
+ expect(model._name).to eq('Jimmy')
458
+ end
459
+
460
+ describe "model state" do
461
+ it 'should be new when created, then false after a change' do
462
+ a = Volt::Model.new
463
+ expect(a.new?).to eq(true)
464
+
465
+ a._name = 'Ryan'
466
+ expect(a.new?).to eq(false)
467
+ end
468
+
469
+ it 'should allow multiple assignments with attributes, changing new? to false after' do
470
+ a = Volt::Model.new
471
+ expect(a.new?).to eq(true)
472
+
473
+ a.attributes = {first: 'Jimmy', last: 'Dean'}
474
+ expect(a.new?).to eq(false)
475
+ end
476
+
477
+ it 'should load store models ' do
478
+
479
+ end
480
+ end
481
+
482
+
483
+ if RUBY_PLATFORM != 'opal'
484
+ it 'should update other queries on the server when a new model is created' do
485
+ query1 = store._todos
486
+ query2 = store._todos.limit(1)
487
+
488
+ count = 0
489
+
490
+ # count the number of todos
491
+ query2.fetch {|v| count += v.size }
492
+
493
+ expect(count).to eq(0)
494
+
495
+ query1 << {label: 'One'}
496
+
497
+ count = 0
498
+ query2.fetch {|v| count += v.size }
499
+
500
+ expect(count).to eq(1)
501
+ end
502
+ end
447
503
  end
@@ -0,0 +1,24 @@
1
+ # Models automatically unload if no dependencies are listening and they have not been .keep (kept)
2
+
3
+ if RUBY_PLATFORM != 'opal'
4
+ require 'spec_helper'
5
+
6
+ describe Volt::Model do
7
+ it 'should stay loaded while a computaiton is watching some data' do
8
+ expect(store._items.loaded_state).to eq(:not_loaded)
9
+
10
+ comp = -> { store._items.size }.watch!
11
+
12
+ # On the server models do a blocking load
13
+ expect(store._items.loaded_state).to eq(:loaded)
14
+
15
+ comp.stop
16
+
17
+ Volt::Timers.flush_next_tick_timers!
18
+
19
+ # Computation stopped listening, so the collection should unload and be set to
20
+ # a dirty state
21
+ expect(store._items.loaded_state).to eq(:dirty)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ class TestUserTodo < Volt::Model
5
+ own_by_user
6
+
7
+ permissions(:update) do
8
+ deny :user_id
9
+ end
10
+ end
11
+
12
+ class TestUserTodoWithCrudStates < Volt::Model
13
+ permissions(:create, :update) do |state|
14
+ # Name is set on create, then can not be changed
15
+ deny unless state == :create
16
+ end
17
+ end
18
+
19
+ class ::TestDenyDelete < Volt::Model
20
+ permissions(:delete) do
21
+ deny
22
+ end
23
+ end
24
+
25
+ class ::TestDenyReadName < Volt::Model
26
+ permissions(:read) do
27
+ deny :name
28
+ end
29
+ end
30
+
31
+ describe "model permissions" do
32
+ it 'should follow CRUD states when checking permissions' do
33
+ todo = TestUserTodoWithCrudStates.new.buffer
34
+
35
+ spec_err = nil
36
+
37
+ todo._name = 'Test Todo'
38
+ todo.save!.then do
39
+ # Don't allow it to change
40
+ todo._name = 'Jimmy'
41
+
42
+ todo.save!.then do
43
+ spec_err = "should not have saved"
44
+ end.fail do |err|
45
+ expect(err).to eq({:name=>["can not be changed"]})
46
+ end
47
+ end.fail do |err|
48
+ spec_err = "Did not save because: #{err.inspect}"
49
+ end
50
+
51
+ if spec_err
52
+ fail spec_err
53
+ end
54
+ end
55
+
56
+ # it 'should deny an insert/create if a deny without fields' do
57
+ # store._todos << {name: 'Ryan'}
58
+ # end
59
+
60
+
61
+ if RUBY_PLATFORM != 'opal'
62
+ describe "read permissions" do
63
+ it 'should deny read on a field' do
64
+ model = store._test_deny_read_names.buffer
65
+ model._name = 'Jimmy'
66
+ model._other = 'should be visible'
67
+
68
+ model.save!.sync
69
+
70
+ # Clear the identity map, so we can load up a fresh copy
71
+ model.save_to.persistor.clear_identity_map
72
+
73
+ reloaded = store._test_deny_read_names.fetch_first.sync
74
+
75
+ expect(reloaded._name).to eq(nil)
76
+ expect(reloaded._other).to eq('should be visible')
77
+ end
78
+ end
79
+
80
+ it 'should prevent delete if denied' do
81
+ model = store._test_deny_deletes.buffer
82
+
83
+ model.save!.then do
84
+ # Saved
85
+ count = 0
86
+
87
+ store._test_deny_deletes.delete(model).then do
88
+ # deleted
89
+ count += 1
90
+ end
91
+
92
+ expect(count).to eq(1)
93
+ end
94
+ end
95
+ end
96
+ end