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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/Readme.md +14 -0
  3. data/VERSION +1 -1
  4. data/app/volt/controllers/notices_controller.rb +9 -0
  5. data/app/volt/tasks/live_query/data_store.rb +12 -0
  6. data/app/volt/tasks/live_query/live_query.rb +86 -0
  7. data/app/volt/tasks/live_query/live_query_pool.rb +36 -0
  8. data/app/volt/tasks/live_query/query_tracker.rb +95 -0
  9. data/app/volt/tasks/query_tasks.rb +57 -0
  10. data/app/volt/tasks/store_tasks.rb +4 -17
  11. data/lib/volt.rb +2 -0
  12. data/lib/volt/console.rb +1 -1
  13. data/lib/volt/controllers/model_controller.rb +4 -0
  14. data/lib/volt/extra_core/array.rb +9 -0
  15. data/lib/volt/extra_core/extra_core.rb +1 -0
  16. data/lib/volt/extra_core/hash.rb +11 -0
  17. data/lib/volt/extra_core/object.rb +4 -0
  18. data/lib/volt/models/array_model.rb +56 -0
  19. data/lib/volt/models/model.rb +6 -11
  20. data/lib/volt/models/model_helpers.rb +12 -0
  21. data/lib/volt/models/persistors/array_store.rb +120 -21
  22. data/lib/volt/models/persistors/model_identity_map.rb +12 -0
  23. data/lib/volt/models/persistors/model_store.rb +20 -60
  24. data/lib/volt/models/persistors/query/query_listener.rb +87 -0
  25. data/lib/volt/models/persistors/query/query_listener_pool.rb +9 -0
  26. data/lib/volt/models/persistors/store.rb +11 -13
  27. data/lib/volt/models/url.rb +1 -1
  28. data/lib/volt/page/bindings/attribute_binding.rb +2 -2
  29. data/lib/volt/page/bindings/base_binding.rb +13 -1
  30. data/lib/volt/page/bindings/component_binding.rb +1 -1
  31. data/lib/volt/page/bindings/content_binding.rb +2 -2
  32. data/lib/volt/page/bindings/each_binding.rb +25 -21
  33. data/lib/volt/page/bindings/event_binding.rb +4 -6
  34. data/lib/volt/page/bindings/if_binding.rb +4 -5
  35. data/lib/volt/page/bindings/template_binding.rb +4 -4
  36. data/lib/volt/page/channel.rb +0 -1
  37. data/lib/volt/page/document.rb +7 -0
  38. data/lib/volt/page/page.rb +4 -4
  39. data/lib/volt/page/reactive_template.rb +2 -2
  40. data/lib/volt/page/targets/dom_section.rb +5 -0
  41. data/lib/volt/page/tasks.rb +10 -40
  42. data/lib/volt/page/template_renderer.rb +4 -4
  43. data/lib/volt/reactive/events.rb +14 -0
  44. data/lib/volt/reactive/reactive_array.rb +17 -7
  45. data/lib/volt/reactive/reactive_value.rb +65 -1
  46. data/lib/volt/server.rb +1 -1
  47. data/lib/volt/server/if_binding_setup.rb +3 -1
  48. data/lib/volt/server/socket_connection_handler.rb +7 -5
  49. data/lib/volt/server/template_parser.rb +7 -7
  50. data/lib/volt/tasks/dispatcher.rb +3 -0
  51. data/lib/volt/utils/ejson.rb +9 -0
  52. data/lib/volt/utils/generic_counting_pool.rb +44 -0
  53. data/lib/volt/utils/generic_pool.rb +88 -0
  54. data/spec/models/reactive_array_spec.rb +43 -0
  55. data/spec/models/reactive_generator_spec.rb +58 -0
  56. data/spec/models/reactive_value_spec.rb +6 -0
  57. data/spec/page/bindings/content_binding_spec.rb +36 -0
  58. data/spec/spec_helper.rb +13 -12
  59. data/spec/tasks/live_query_spec.rb +20 -0
  60. data/spec/tasks/query_tasks.rb +10 -0
  61. data/spec/tasks/query_tracker_spec.rb +120 -0
  62. data/spec/templates/template_binding_spec.rb +16 -10
  63. data/spec/utils/generic_counting_pool_spec.rb +36 -0
  64. data/spec/utils/generic_pool_spec.rb +50 -0
  65. metadata +29 -5
  66. data/app/volt/tasks/channel_tasks.rb +0 -55
  67. 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
- RSpec.configure do |config|
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
- # Run specs in random order to surface order dependencies. If you find an
14
- # order dependency and want to debug it, you can fix the order by providing
15
- # the seed, which is printed after each run.
16
- # --seed 1234
17
- config.order = 'random'
18
-
19
- config.enable_any_instance_mocks = true
20
- end
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,10 @@
1
+ if RUBY_PLATFORM != 'opal'
2
+ describe "QueryTasks" do
3
+ before do
4
+ load File.join(File.dirname(__FILE__), "../../app/volt/tasks/query_tasks.rb")
5
+ end
6
+
7
+
8
+
9
+ end
10
+ 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
- $page = Page.new
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
- $page.templates = {
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
- $page.templates = {
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
- $page.templates = {
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
- $page.templates = {
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
- $page.templates = {
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
- $page.templates = {
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
- $page.templates = {
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
- $page.templates = {
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
- $page.templates = {
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