volt 0.5.18 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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