volt 0.5.18 → 0.6.0
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.
- checksums.yaml +4 -4
- data/Readme.md +14 -0
- data/VERSION +1 -1
- data/app/volt/controllers/notices_controller.rb +9 -0
- data/app/volt/tasks/live_query/data_store.rb +12 -0
- data/app/volt/tasks/live_query/live_query.rb +86 -0
- data/app/volt/tasks/live_query/live_query_pool.rb +36 -0
- data/app/volt/tasks/live_query/query_tracker.rb +95 -0
- data/app/volt/tasks/query_tasks.rb +57 -0
- data/app/volt/tasks/store_tasks.rb +4 -17
- data/lib/volt.rb +2 -0
- data/lib/volt/console.rb +1 -1
- data/lib/volt/controllers/model_controller.rb +4 -0
- data/lib/volt/extra_core/array.rb +9 -0
- data/lib/volt/extra_core/extra_core.rb +1 -0
- data/lib/volt/extra_core/hash.rb +11 -0
- data/lib/volt/extra_core/object.rb +4 -0
- data/lib/volt/models/array_model.rb +56 -0
- data/lib/volt/models/model.rb +6 -11
- data/lib/volt/models/model_helpers.rb +12 -0
- data/lib/volt/models/persistors/array_store.rb +120 -21
- data/lib/volt/models/persistors/model_identity_map.rb +12 -0
- data/lib/volt/models/persistors/model_store.rb +20 -60
- data/lib/volt/models/persistors/query/query_listener.rb +87 -0
- data/lib/volt/models/persistors/query/query_listener_pool.rb +9 -0
- data/lib/volt/models/persistors/store.rb +11 -13
- data/lib/volt/models/url.rb +1 -1
- data/lib/volt/page/bindings/attribute_binding.rb +2 -2
- data/lib/volt/page/bindings/base_binding.rb +13 -1
- data/lib/volt/page/bindings/component_binding.rb +1 -1
- data/lib/volt/page/bindings/content_binding.rb +2 -2
- data/lib/volt/page/bindings/each_binding.rb +25 -21
- data/lib/volt/page/bindings/event_binding.rb +4 -6
- data/lib/volt/page/bindings/if_binding.rb +4 -5
- data/lib/volt/page/bindings/template_binding.rb +4 -4
- data/lib/volt/page/channel.rb +0 -1
- data/lib/volt/page/document.rb +7 -0
- data/lib/volt/page/page.rb +4 -4
- data/lib/volt/page/reactive_template.rb +2 -2
- data/lib/volt/page/targets/dom_section.rb +5 -0
- data/lib/volt/page/tasks.rb +10 -40
- data/lib/volt/page/template_renderer.rb +4 -4
- data/lib/volt/reactive/events.rb +14 -0
- data/lib/volt/reactive/reactive_array.rb +17 -7
- data/lib/volt/reactive/reactive_value.rb +65 -1
- data/lib/volt/server.rb +1 -1
- data/lib/volt/server/if_binding_setup.rb +3 -1
- data/lib/volt/server/socket_connection_handler.rb +7 -5
- data/lib/volt/server/template_parser.rb +7 -7
- data/lib/volt/tasks/dispatcher.rb +3 -0
- data/lib/volt/utils/ejson.rb +9 -0
- data/lib/volt/utils/generic_counting_pool.rb +44 -0
- data/lib/volt/utils/generic_pool.rb +88 -0
- data/spec/models/reactive_array_spec.rb +43 -0
- data/spec/models/reactive_generator_spec.rb +58 -0
- data/spec/models/reactive_value_spec.rb +6 -0
- data/spec/page/bindings/content_binding_spec.rb +36 -0
- data/spec/spec_helper.rb +13 -12
- data/spec/tasks/live_query_spec.rb +20 -0
- data/spec/tasks/query_tasks.rb +10 -0
- data/spec/tasks/query_tracker_spec.rb +120 -0
- data/spec/templates/template_binding_spec.rb +16 -10
- data/spec/utils/generic_counting_pool_spec.rb +36 -0
- data/spec/utils/generic_pool_spec.rb +50 -0
- metadata +29 -5
- data/app/volt/tasks/channel_tasks.rb +0 -55
- data/spec/tasks/channel_tasks_spec.rb +0 -74
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'volt/models'
|
2
|
+
|
3
|
+
describe ReactiveGenerator do
|
4
|
+
before do
|
5
|
+
@object = {
|
6
|
+
name: ReactiveValue.new('bob'),
|
7
|
+
location: {
|
8
|
+
town: ReactiveValue.new('Bozeman'),
|
9
|
+
places: [
|
10
|
+
ReactiveValue.new('The Garage'),
|
11
|
+
ReactiveValue.new('Ale Works')
|
12
|
+
]
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should find all reactive values in any object" do
|
18
|
+
values = ReactiveGenerator.find_reactives(@object)
|
19
|
+
|
20
|
+
expect(values.map(&:cur)).to eq(['bob', 'Bozeman', 'The Garage', 'Ale Works'])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return a reactive value that changes whenever a child reactive value changes" do
|
24
|
+
values = ReactiveGenerator.from_hash(@object)
|
25
|
+
|
26
|
+
count = 0
|
27
|
+
values.on('changed') { count += 1 }
|
28
|
+
expect(count).to eq(0)
|
29
|
+
|
30
|
+
@object[:name].cur = 'jim'
|
31
|
+
|
32
|
+
expect(count).to eq(1)
|
33
|
+
|
34
|
+
@object[:location][:places].last.cur = 'Starkies'
|
35
|
+
expect(count).to eq(2)
|
36
|
+
|
37
|
+
expect(values.to_h).to eq({
|
38
|
+
name: 'jim',
|
39
|
+
location: {
|
40
|
+
town: 'Bozeman',
|
41
|
+
places: [
|
42
|
+
'The Garage',
|
43
|
+
'Starkies'
|
44
|
+
]
|
45
|
+
}
|
46
|
+
})
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should optionally return a normal hash if there are no child reactive values" do
|
50
|
+
values = ReactiveGenerator.from_hash({name: 'bob'})
|
51
|
+
expect(values.reactive?).to eq(true)
|
52
|
+
expect(values.is_a?(Hash)).to eq(false)
|
53
|
+
|
54
|
+
values = ReactiveGenerator.from_hash({name: 'bob'}, true)
|
55
|
+
expect(values.reactive?).to eq(false)
|
56
|
+
expect(values.is_a?(Hash)).to eq(true)
|
57
|
+
end
|
58
|
+
end
|
@@ -344,4 +344,10 @@ describe ReactiveValue do
|
|
344
344
|
expect(count).to eq(2)
|
345
345
|
end
|
346
346
|
end
|
347
|
+
|
348
|
+
it "should give you back the object without any ReactiveValue's if you call .deep_cur on it." do
|
349
|
+
a = ReactiveValue.new({_names: [ReactiveValue.new('bob'), ReactiveValue.new('jim')]})
|
350
|
+
|
351
|
+
expect(a.deep_cur).to eq({_names: ['bob', 'jim']})
|
352
|
+
end
|
347
353
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'volt/page/bindings/content_binding'
|
3
|
+
require 'volt/page/targets/attribute_target'
|
4
|
+
require 'volt/page/template_renderer'
|
5
|
+
|
6
|
+
|
7
|
+
describe ContentBinding do
|
8
|
+
it "should render the content in a content binding" do
|
9
|
+
dom = AttributeTarget.new(0)
|
10
|
+
context = {:name => 'jimmy'}
|
11
|
+
binding = ContentBinding.new(nil, dom, context, 0, Proc.new { self[:name] })
|
12
|
+
|
13
|
+
expect(dom.to_html).to eq('jimmy')
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should render with a template" do
|
17
|
+
context = {:name => 'jimmy'}
|
18
|
+
binding = lambda {|page, target, context, id| ContentBinding.new(page, target, context, id, Proc.new { self[:name] }) }
|
19
|
+
|
20
|
+
templates = {
|
21
|
+
'home/index' => {
|
22
|
+
'html' => 'hello <!-- $1 --><!-- $/1 -->',
|
23
|
+
'bindings' => {1 => [binding]}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
page = double('page')
|
28
|
+
expect(page).to receive(:templates).and_return(templates)
|
29
|
+
|
30
|
+
dom = AttributeTarget.new(0)
|
31
|
+
|
32
|
+
TemplateRenderer.new(page, dom, context, 'main', 'home/index')
|
33
|
+
|
34
|
+
expect(dom.to_html).to eq('hello jimmy')
|
35
|
+
end
|
36
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,16 +5,17 @@
|
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
7
|
|
8
|
-
|
9
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
10
|
-
config.run_all_when_everything_filtered = true
|
11
|
-
config.filter_run :focus
|
8
|
+
require 'volt'
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
10
|
+
if RUBY_PLATFORM != 'opal'
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
|
15
|
+
# Run specs in random order to surface order dependencies. If you find an
|
16
|
+
# order dependency and want to debug it, you can fix the order by providing
|
17
|
+
# the seed, which is printed after each run.
|
18
|
+
# --seed 1234
|
19
|
+
config.order = 'random'
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
if RUBY_PLATFORM != 'opal'
|
2
|
+
describe "LiveQuery" do
|
3
|
+
before do
|
4
|
+
load File.join(File.dirname(__FILE__), "../../app/volt/tasks/live_query/live_query.rb")
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should run a query" do
|
8
|
+
pool = double('pool')
|
9
|
+
data_store = double('data store')
|
10
|
+
|
11
|
+
expect(data_store).to receive(:query).with('_items', {}).and_return([
|
12
|
+
{'_id' => 0, '_name' => 'one'}
|
13
|
+
])
|
14
|
+
|
15
|
+
live_query = LiveQuery.new(pool, data_store, '_items', {})
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
if RUBY_PLATFORM != 'opal'
|
2
|
+
describe "LiveQuery" do
|
3
|
+
before do
|
4
|
+
load File.join(File.dirname(__FILE__), "../../app/volt/tasks/live_query/live_query.rb")
|
5
|
+
end
|
6
|
+
|
7
|
+
# LiveQueryStub behaves as the front-end would with the changes to a
|
8
|
+
# live query. Instead of passing changes to the models to the front
|
9
|
+
# end, the changes are applied locally, then can be checked to see if
|
10
|
+
# the correct transitions have taken place.
|
11
|
+
class LiveQueryStub
|
12
|
+
attr_reader :collection, :query, :items
|
13
|
+
def initialize
|
14
|
+
@collection = '_items'
|
15
|
+
@query = {}
|
16
|
+
@items = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def notify_removed(ids, skip_channel)
|
20
|
+
# Remove the id's that need to be removed
|
21
|
+
@items.reject! {|item| ids.include?(item['_id']) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def notify_added(index, data, skip_channel)
|
25
|
+
@items.insert(index, data)
|
26
|
+
end
|
27
|
+
|
28
|
+
def notify_moved(id, index, skip_channel)
|
29
|
+
item = @items.find {|item| item['_id'] == id }
|
30
|
+
@items.delete(item)
|
31
|
+
|
32
|
+
@items.insert(index, item)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
before do
|
37
|
+
# Setup a live query stub
|
38
|
+
@live_query = LiveQueryStub.new
|
39
|
+
data_store = double('data store')
|
40
|
+
|
41
|
+
# return an empty collection
|
42
|
+
@items = []
|
43
|
+
expect(data_store).to receive(:query).at_least(:once) { @items.dup }
|
44
|
+
|
45
|
+
@query_tracker = QueryTracker.new(@live_query, data_store)
|
46
|
+
@query_tracker.run
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should add items" do
|
51
|
+
@items = [
|
52
|
+
{'_id' => 1, '_name' => 'one'}
|
53
|
+
]
|
54
|
+
|
55
|
+
expect(@live_query.items).to eq([])
|
56
|
+
|
57
|
+
@query_tracker.run
|
58
|
+
|
59
|
+
expect(@live_query.items).to eq(@items)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should remove items" do
|
63
|
+
@items = [
|
64
|
+
{'_id' => 1, '_name' => 'one'},
|
65
|
+
{'_id' => 2, '_name' => 'two'}
|
66
|
+
]
|
67
|
+
@query_tracker.run
|
68
|
+
expect(@live_query.items).to eq(@items)
|
69
|
+
|
70
|
+
@items = [
|
71
|
+
{'_id' => 2, '_name' => 'two'}
|
72
|
+
]
|
73
|
+
@query_tracker.run
|
74
|
+
expect(@live_query.items).to eq(@items)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should move items" do
|
78
|
+
@items = [
|
79
|
+
{'_id' => 1, '_name' => 'one'},
|
80
|
+
{'_id' => 2, '_name' => 'two'},
|
81
|
+
{'_id' => 3, '_name' => 'three'}
|
82
|
+
]
|
83
|
+
@query_tracker.run
|
84
|
+
expect(@live_query.items).to eq(@items)
|
85
|
+
|
86
|
+
@items = [
|
87
|
+
{'_id' => 2, '_name' => 'two'},
|
88
|
+
{'_id' => 3, '_name' => 'three'},
|
89
|
+
{'_id' => 1, '_name' => 'one'}
|
90
|
+
]
|
91
|
+
@query_tracker.run
|
92
|
+
expect(@live_query.items).to eq(@items)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should handle complex transforms" do
|
96
|
+
@items = [
|
97
|
+
{'_id' => 1, '_name' => 'one'},
|
98
|
+
{'_id' => 2, '_name' => 'two'},
|
99
|
+
{'_id' => 3, '_name' => 'three'},
|
100
|
+
{'_id' => 4, '_name' => 'four'},
|
101
|
+
{'_id' => 5, '_name' => 'five'}
|
102
|
+
]
|
103
|
+
@query_tracker.run
|
104
|
+
expect(@live_query.items).to eq(@items)
|
105
|
+
|
106
|
+
@items = [
|
107
|
+
{'_id' => 7, '_name' => 'seven'},
|
108
|
+
{'_id' => 4, '_name' => 'four'},
|
109
|
+
{'_id' => 1, '_name' => 'one'},
|
110
|
+
{'_id' => 5, '_name' => 'five'},
|
111
|
+
{'_id' => 3, '_name' => 'three'},
|
112
|
+
{'_id' => 6, '_name' => 'five'}
|
113
|
+
]
|
114
|
+
@query_tracker.run
|
115
|
+
expect(@live_query.items).to eq(@items)
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -9,19 +9,25 @@ end
|
|
9
9
|
|
10
10
|
describe TemplateBinding do
|
11
11
|
before do
|
12
|
-
|
12
|
+
@page = double('page')
|
13
|
+
expect(@page).to receive(:templates).at_least(1).times.and_return { @templates }
|
13
14
|
|
14
15
|
# TODO: We should decouple things so we don't need to allocate
|
15
16
|
@template_binding = TemplateBinding.allocate
|
17
|
+
@template_binding.instance_variable_set('@page', @page)
|
16
18
|
@template_binding.setup_path('home/index/index')
|
17
19
|
end
|
18
20
|
|
21
|
+
def set_template(templates)
|
22
|
+
@page.instance_variable_set('@templates', templates)
|
23
|
+
end
|
24
|
+
|
19
25
|
after do
|
20
26
|
$page = nil
|
21
27
|
end
|
22
28
|
|
23
29
|
it "should lookup sub-templates within its own file" do
|
24
|
-
|
30
|
+
@templates = {
|
25
31
|
'home/index/blog/nav' => '',
|
26
32
|
'home/index/index/nav' => '',
|
27
33
|
}
|
@@ -30,7 +36,7 @@ describe TemplateBinding do
|
|
30
36
|
end
|
31
37
|
|
32
38
|
it "should lookup sub-templates within another local view" do
|
33
|
-
|
39
|
+
@templates = {
|
34
40
|
'home/index/blog/nav' => '',
|
35
41
|
'home/index/index/nav' => '',
|
36
42
|
}
|
@@ -39,7 +45,7 @@ describe TemplateBinding do
|
|
39
45
|
end
|
40
46
|
|
41
47
|
it "should lookup in another view" do
|
42
|
-
|
48
|
+
@templates = {
|
43
49
|
'home/index/nav/body' => '',
|
44
50
|
}
|
45
51
|
|
@@ -47,7 +53,7 @@ describe TemplateBinding do
|
|
47
53
|
end
|
48
54
|
|
49
55
|
it "should lookup in a controller" do
|
50
|
-
|
56
|
+
@templates = {
|
51
57
|
'home/nav/index/body' => ''
|
52
58
|
}
|
53
59
|
|
@@ -55,7 +61,7 @@ describe TemplateBinding do
|
|
55
61
|
end
|
56
62
|
|
57
63
|
it "should lookup in a controller/view" do
|
58
|
-
|
64
|
+
@templates = {
|
59
65
|
'home/blog/nav/body' => ''
|
60
66
|
}
|
61
67
|
|
@@ -63,7 +69,7 @@ describe TemplateBinding do
|
|
63
69
|
end
|
64
70
|
|
65
71
|
it "should lookup in a controller" do
|
66
|
-
|
72
|
+
@templates = {
|
67
73
|
'home/nav/index/body' => ''
|
68
74
|
}
|
69
75
|
|
@@ -71,7 +77,7 @@ describe TemplateBinding do
|
|
71
77
|
end
|
72
78
|
|
73
79
|
it "should lookup in a component" do
|
74
|
-
|
80
|
+
@templates = {
|
75
81
|
'nav/index/index/body' => ''
|
76
82
|
}
|
77
83
|
|
@@ -79,7 +85,7 @@ describe TemplateBinding do
|
|
79
85
|
end
|
80
86
|
|
81
87
|
it "should lookup in a component/controller/view" do
|
82
|
-
|
88
|
+
@templates = {
|
83
89
|
'nav/index/index/body' => '',
|
84
90
|
'auth/login/new/body' => ''
|
85
91
|
}
|
@@ -88,7 +94,7 @@ describe TemplateBinding do
|
|
88
94
|
end
|
89
95
|
|
90
96
|
it "should let you force a sub template" do
|
91
|
-
|
97
|
+
@templates = {
|
92
98
|
'nav/index/index/title' => '',
|
93
99
|
'auth/login/new/title' => ''
|
94
100
|
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'volt/utils/generic_counting_pool'
|
2
|
+
|
3
|
+
class CountingPoolTest < GenericCountingPool
|
4
|
+
def create(id, name=nil)
|
5
|
+
return Object.new
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe GenericCountingPool do
|
10
|
+
before do
|
11
|
+
@count_pool = CountingPoolTest.new
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should lookup and retrieve" do
|
15
|
+
item1 = @count_pool.find('one')
|
16
|
+
|
17
|
+
item2 = @count_pool.find('one')
|
18
|
+
item3 = @count_pool.find('two')
|
19
|
+
|
20
|
+
expect(item1).to eq(item2)
|
21
|
+
expect(item2).to_not eq(item3)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should only remove items when the same number have been removed as have been added" do
|
25
|
+
item1 = @count_pool.find('_items', 'one')
|
26
|
+
item2 = @count_pool.find('_items', 'one')
|
27
|
+
expect(@count_pool.instance_variable_get('@pool')).to_not eq({})
|
28
|
+
|
29
|
+
@count_pool.remove('_items', 'one')
|
30
|
+
expect(@count_pool.instance_variable_get('@pool')).to_not eq({})
|
31
|
+
|
32
|
+
@count_pool.remove('_items', 'one')
|
33
|
+
expect(@count_pool.instance_variable_get('@pool')).to eq({})
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'volt/utils/generic_pool'
|
2
|
+
|
3
|
+
class PoolTest < GenericPool
|
4
|
+
def create(collection, query, other=nil)
|
5
|
+
return Object.new
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe GenericPool do
|
10
|
+
|
11
|
+
before do
|
12
|
+
@pool_test = PoolTest.new
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should insert nested for fast lookup at a path" do
|
16
|
+
item1 = @pool_test.lookup('_items', 'one')
|
17
|
+
expect(@pool_test.instance_variable_get('@pool')).to eq({'_items' => {'one' => item1}})
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should retrieve the same item both times" do
|
21
|
+
item1 = @pool_test.lookup('_items', {})
|
22
|
+
item2 = @pool_test.lookup('_items', {})
|
23
|
+
expect(item1.object_id).to eq(item2.object_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should recreate after being removed" do
|
27
|
+
item1 = @pool_test.lookup('_items', {})
|
28
|
+
item2 = @pool_test.lookup('_items', {})
|
29
|
+
expect(item1.object_id).to eq(item2.object_id)
|
30
|
+
|
31
|
+
@pool_test.remove('_items', {})
|
32
|
+
item3 = @pool_test.lookup('_items', {})
|
33
|
+
expect(item3.object_id).to_not eq(item2.object_id)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should remove all of the way down" do
|
37
|
+
@pool_test.instance_variable_set('@pool', {:name => {:ok => true}, :yep => true})
|
38
|
+
|
39
|
+
@pool_test.remove(:name, :ok)
|
40
|
+
|
41
|
+
expect(@pool_test.instance_variable_get('@pool')).to eq({:yep => true})
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should lookup all items at a path" do
|
45
|
+
item1 = @pool_test.lookup('_items', '_some', {:name => 'bob'})
|
46
|
+
item2 = @pool_test.lookup('_items', '_some', {:name => 'jim'})
|
47
|
+
|
48
|
+
expect(@pool_test.lookup_all('_items', '_some')).to eq([item1, item2])
|
49
|
+
end
|
50
|
+
end
|