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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +3 -2
- data/{Readme.md → README.md} +9 -12
- data/Rakefile +2 -9
- data/VERSION +1 -1
- data/app/volt/models/user.rb +8 -0
- data/app/volt/tasks/live_query/data_store.rb +13 -5
- data/app/volt/tasks/live_query/live_query.rb +45 -3
- data/app/volt/tasks/live_query/live_query_pool.rb +9 -1
- data/app/volt/tasks/query_tasks.rb +20 -2
- data/app/volt/tasks/store_tasks.rb +37 -20
- data/app/volt/tasks/user_tasks.rb +15 -13
- data/lib/volt/boot.rb +5 -3
- data/lib/volt/cli/console.rb +1 -0
- data/lib/volt/cli/generate.rb +15 -0
- data/lib/volt/cli.rb +19 -12
- data/lib/volt/config.rb +1 -1
- data/lib/volt/controllers/model_controller.rb +13 -3
- data/lib/volt/extra_core/extra_core.rb +1 -0
- data/lib/volt/extra_core/hash.rb +26 -0
- data/lib/volt/extra_core/object.rb +5 -1
- data/lib/volt/models/array_model.rb +86 -35
- data/lib/volt/models/associations.rb +53 -0
- data/lib/volt/models/buffer.rb +22 -10
- data/lib/volt/models/dirty.rb +88 -0
- data/lib/volt/models/errors.rb +21 -0
- data/lib/volt/models/field_helpers.rb +2 -2
- data/lib/volt/models/listener_tracker.rb +17 -0
- data/lib/volt/models/model.rb +213 -69
- data/lib/volt/models/model_helpers.rb +27 -17
- data/lib/volt/models/permissions.rb +246 -0
- data/lib/volt/models/persistors/array_store.rb +149 -81
- data/lib/volt/models/persistors/base.rb +16 -0
- data/lib/volt/models/persistors/cookies.rb +14 -9
- data/lib/volt/models/persistors/flash.rb +3 -0
- data/lib/volt/models/persistors/local_store.rb +0 -16
- data/lib/volt/models/persistors/model_store.rb +1 -2
- data/lib/volt/models/persistors/query/normalizer.rb +51 -0
- data/lib/volt/models/persistors/query/query_listener.rb +21 -5
- data/lib/volt/models/persistors/query/query_listener_pool.rb +0 -9
- data/lib/volt/models/persistors/store.rb +8 -0
- data/lib/volt/models/persistors/store_state.rb +4 -27
- data/lib/volt/models/state_helpers.rb +11 -0
- data/lib/volt/models/state_manager.rb +43 -0
- data/lib/volt/models/url.rb +5 -5
- data/lib/volt/models/validations.rb +38 -41
- data/lib/volt/models/validators/email_validator.rb +4 -9
- data/lib/volt/models/validators/format_validator.rb +23 -8
- data/lib/volt/models/validators/length_validator.rb +2 -2
- data/lib/volt/models/validators/numericality_validator.rb +7 -3
- data/lib/volt/models/validators/phone_number_validator.rb +4 -9
- data/lib/volt/models/validators/presence_validator.rb +2 -2
- data/lib/volt/models/validators/unique_validator.rb +2 -2
- data/lib/volt/models/validators/user_validation.rb +6 -0
- data/lib/volt/models.rb +8 -3
- data/lib/volt/page/bindings/attribute_binding.rb +10 -4
- data/lib/volt/page/bindings/content_binding.rb +9 -5
- data/lib/volt/page/bindings/if_binding.rb +25 -2
- data/lib/volt/page/bindings/template_binding.rb +19 -1
- data/lib/volt/page/bindings/yield_binding.rb +31 -0
- data/lib/volt/page/page.rb +11 -16
- data/lib/volt/reactive/class_eventable.rb +71 -0
- data/lib/volt/reactive/computation.rb +79 -10
- data/lib/volt/reactive/dependency.rb +27 -8
- data/lib/volt/reactive/eventable.rb +36 -22
- data/lib/volt/reactive/reactive_array.rb +2 -3
- data/lib/volt/reactive/reactive_hash.rb +8 -3
- data/lib/volt/router/routes.rb +2 -1
- data/lib/volt/server/component_templates.rb +0 -2
- data/lib/volt/server/html_parser/component_view_scope.rb +59 -0
- data/lib/volt/server/html_parser/view_handler.rb +3 -0
- data/lib/volt/server/html_parser/view_parser.rb +1 -0
- data/lib/volt/server/html_parser/view_scope.rb +17 -41
- data/lib/volt/server/rack/component_paths.rb +1 -10
- data/lib/volt/server/rack/index_files.rb +9 -4
- data/lib/volt/server/rack/opal_files.rb +22 -14
- data/lib/volt/server/rack/quiet_common_logger.rb +1 -1
- data/lib/volt/server/socket_connection_handler.rb +4 -0
- data/lib/volt/spec/setup.rb +26 -0
- data/lib/volt/tasks/dispatcher.rb +11 -0
- data/lib/volt/utils/event_counter.rb +29 -0
- data/lib/volt/utils/generic_pool.rb +12 -0
- data/lib/volt/utils/modes.rb +40 -0
- data/lib/volt/utils/promise_patch.rb +66 -0
- data/lib/volt/utils/timers.rb +33 -0
- data/lib/volt/volt/users.rb +48 -5
- data/lib/volt.rb +4 -0
- data/spec/apps/kitchen_sink/Gemfile +3 -1
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +9 -8
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +9 -0
- data/spec/apps/kitchen_sink/app/main/controllers/yield_component_controller.rb +5 -0
- data/spec/apps/kitchen_sink/app/main/views/main/cookie_test.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/index.html +1 -1
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +2 -1
- data/spec/apps/kitchen_sink/app/main/views/main/yield.html +18 -0
- data/spec/apps/kitchen_sink/app/main/views/yield-component/index.html +4 -0
- data/spec/extra_core/logger_spec.rb +4 -2
- data/spec/integration/user_spec.rb +42 -42
- data/spec/integration/yield_spec.rb +18 -0
- data/spec/models/associations_spec.rb +37 -0
- data/spec/models/dirty_spec.rb +102 -0
- data/spec/models/model_spec.rb +64 -8
- data/spec/models/model_state_spec.rb +24 -0
- data/spec/models/permissions_spec.rb +96 -0
- data/spec/models/user_spec.rb +8 -5
- data/spec/models/user_validation_spec.rb +24 -0
- data/spec/models/validations_spec.rb +44 -5
- data/spec/models/validators/email_validator_spec.rb +109 -82
- data/spec/models/validators/format_validator_spec.rb +4 -107
- data/spec/models/validators/length_validator_spec.rb +9 -9
- data/spec/models/validators/phone_number_validator_spec.rb +60 -103
- data/spec/models/validators/shared_examples_for_validators.rb +123 -0
- data/spec/reactive/class_eventable_spec.rb +37 -0
- data/spec/reactive/computation_spec.rb +68 -3
- data/spec/reactive/dependency_spec.rb +71 -0
- data/spec/reactive/eventable_spec.rb +21 -0
- data/spec/reactive/reactive_hash_spec.rb +12 -0
- data/spec/router/routes_spec.rb +50 -50
- data/spec/server/html_parser/view_parser_spec.rb +0 -3
- data/spec/server/rack/component_paths_spec.rb +11 -0
- data/spec/server/rack/quite_common_logger_spec.rb +3 -4
- data/spec/spec_helper.rb +7 -3
- data/templates/component/config/dependencies.rb +1 -7
- data/templates/component/config/routes.rb +1 -1
- data/templates/component/controllers/main_controller.rb.tt +20 -0
- data/templates/component/views/{index → main}/index.html.tt +0 -0
- data/templates/newgem/lib/newgem.rb.tt +1 -3
- data/templates/project/app/main/config/routes.rb +3 -3
- data/templates/project/app/main/views/main/main.html.tt +4 -4
- data/templates/project/config/app.rb.tt +6 -0
- data/volt.gemspec +11 -7
- metadata +96 -42
- data/lib/volt/models/model_state.rb +0 -21
- 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/{{
|
4
|
-
get '/bindings',
|
5
|
-
get '/store',
|
6
|
-
get '/cookie_test',
|
7
|
-
get '/flash',
|
8
|
-
get '/
|
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',
|
12
|
-
get '/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
|
@@ -20,6 +20,6 @@
|
|
20
20
|
|
21
21
|
<ul>
|
22
22
|
{{ cookies.keys.each do |key| }}
|
23
|
-
<li>{{ key }}: {{ cookies.
|
23
|
+
<li>{{ key }}: {{ cookies.get(key) }} <button class="cookieDelete" e-click="cookies.delete(key)">X</button></li>
|
24
24
|
{{ end }}
|
25
25
|
</ul>
|
@@ -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>© Company
|
25
|
+
<p>© 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 }}
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'should fail to create an account without a valid email and password' do
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
data/spec/models/model_spec.rb
CHANGED
@@ -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?(:
|
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(
|
211
|
+
expect(count).to eq(2)
|
206
212
|
|
207
213
|
a._blue = 22
|
208
214
|
Volt::Computation.flush!
|
209
|
-
expect(count).to eq(
|
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(
|
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 << {
|
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 << {
|
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
|