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
@@ -2,12 +2,12 @@ require 'volt/page/bindings/base_binding'
|
|
2
2
|
|
3
3
|
class TemplateRenderer < BaseBinding
|
4
4
|
attr_reader :context
|
5
|
-
def initialize(target, context, binding_name, template_name)
|
5
|
+
def initialize(page, target, context, binding_name, template_name)
|
6
6
|
# puts "new template renderer: #{context.inspect} - #{binding_name.inspect}"
|
7
|
-
super(target, context, binding_name)
|
7
|
+
super(page, target, context, binding_name)
|
8
8
|
|
9
9
|
# puts "Template Name: #{template_name}"
|
10
|
-
@template =
|
10
|
+
@template = @page.templates[template_name]
|
11
11
|
@sub_bindings = []
|
12
12
|
|
13
13
|
if @template
|
@@ -22,7 +22,7 @@ class TemplateRenderer < BaseBinding
|
|
22
22
|
|
23
23
|
bindings.each_pair do |id,bindings_for_id|
|
24
24
|
bindings_for_id.each do |binding|
|
25
|
-
@sub_bindings << binding.call(target, context, id)
|
25
|
+
@sub_bindings << binding.call(page, target, context, id)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
data/lib/volt/reactive/events.rb
CHANGED
@@ -201,5 +201,19 @@ module Events
|
|
201
201
|
def trigger_by_scope!(event, *args, &block)
|
202
202
|
trigger!(event, block, *args)
|
203
203
|
end
|
204
|
+
|
205
|
+
# Takes an event and a list of method names, and triggers the event for each listener
|
206
|
+
# coming off of those methods.
|
207
|
+
def trigger_for_methods!(event, *method_names)
|
208
|
+
trigger_by_scope!(event, [], nil) do |scope|
|
209
|
+
if scope
|
210
|
+
method_name = scope.first
|
211
|
+
|
212
|
+
method_names.include?(method_name)
|
213
|
+
else
|
214
|
+
false
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
204
218
|
|
205
219
|
end
|
@@ -87,6 +87,14 @@ class ReactiveArray# < Array
|
|
87
87
|
self.delete_at(@array.index(val))
|
88
88
|
end
|
89
89
|
|
90
|
+
# Removes all items in the array model.
|
91
|
+
tag_method(:clear) do
|
92
|
+
destructive!
|
93
|
+
end
|
94
|
+
def clear
|
95
|
+
@array = []
|
96
|
+
trigger!('changed')
|
97
|
+
end
|
90
98
|
|
91
99
|
tag_method(:<<) do
|
92
100
|
pass_reactive!
|
@@ -126,14 +134,16 @@ class ReactiveArray# < Array
|
|
126
134
|
tag_method(:insert) do
|
127
135
|
destructive!
|
128
136
|
end
|
129
|
-
|
130
|
-
|
131
|
-
old_size = self.size
|
132
|
-
result = @array.insert(*args)
|
137
|
+
def insert(index, *objects)
|
138
|
+
result = @array.insert(index, *objects)
|
133
139
|
|
134
|
-
|
135
|
-
|
136
|
-
|
140
|
+
# All objects from index to the end have "changed"
|
141
|
+
index.upto(result.size-1) do |idx|
|
142
|
+
trigger_for_index!('changed', idx)
|
143
|
+
end
|
144
|
+
|
145
|
+
objects.size.times do |count|
|
146
|
+
trigger_on_direct_listeners!('added', index+count)
|
137
147
|
end
|
138
148
|
|
139
149
|
trigger_size_change!
|
@@ -42,7 +42,7 @@ class ReactiveValue < BasicObject
|
|
42
42
|
# Proxy methods to the ReactiveManager. We want to have as few
|
43
43
|
# as possible methods on reactive values, so all other methods
|
44
44
|
# are forwarded to the object the reactive value points to.
|
45
|
-
[:cur, :cur=, :on, :trigger!, :trigger_by_scope!].each do |method_name|
|
45
|
+
[:cur, :cur=, :deep_cur, :on, :trigger!, :trigger_by_scope!].each do |method_name|
|
46
46
|
define_method(method_name) do |*args, &block|
|
47
47
|
@reactive_manager.send(method_name, *args, &block)
|
48
48
|
end
|
@@ -173,6 +173,63 @@ class ReactiveValue < BasicObject
|
|
173
173
|
return [wrapped_object, self]
|
174
174
|
end
|
175
175
|
end
|
176
|
+
|
177
|
+
# Return a new reactive value that listens for changes on any
|
178
|
+
# ReactiveValues inside of its children (hash values, array items, etc..)
|
179
|
+
# This is useful if someone is passing in a set of options, but the main
|
180
|
+
# hash isn't a ReactiveValue, but you want to listen for changes inside
|
181
|
+
# of the hash.
|
182
|
+
#
|
183
|
+
# skip_if_no_reactives lets you get back a non-reactive value in the event
|
184
|
+
# that there are no child reactive values.
|
185
|
+
def self.from_hash(hash, skip_if_no_reactives=false)
|
186
|
+
::ReactiveGenerator.from_hash(hash)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class ReactiveGenerator
|
191
|
+
# Takes a hash and returns a ReactiveValue that depends on
|
192
|
+
# any ReactiveValue's inside of the hash (or children).
|
193
|
+
def self.from_hash(hash, skip_if_no_reactives=false)
|
194
|
+
reactives = find_reactives(hash)
|
195
|
+
|
196
|
+
if skip_if_no_reactives && reactives.size == 0
|
197
|
+
# There weren't any reactives, we can just use the hash
|
198
|
+
return hash
|
199
|
+
else
|
200
|
+
# Create a new reactive value that listens on all of its
|
201
|
+
# child reactive values.
|
202
|
+
value = ReactiveValue.new(hash)
|
203
|
+
|
204
|
+
reactives.each do |child|
|
205
|
+
value.reactive_manager.add_parent!(child)
|
206
|
+
end
|
207
|
+
|
208
|
+
return value
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Recursively loop through the data, returning a list of all
|
213
|
+
# reactive values in the hash, array, etc..
|
214
|
+
def self.find_reactives(object)
|
215
|
+
found = []
|
216
|
+
if object.reactive?
|
217
|
+
found << object
|
218
|
+
|
219
|
+
found += find_reactives(object.cur)
|
220
|
+
elsif object.is_a?(Array)
|
221
|
+
object.each do |item|
|
222
|
+
found += find_reactives(item)
|
223
|
+
end
|
224
|
+
elsif object.is_a?(Hash)
|
225
|
+
object.each_pair do |key, value|
|
226
|
+
found += find_reactives(key)
|
227
|
+
found += find_reactives(value)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
return found.flatten
|
232
|
+
end
|
176
233
|
end
|
177
234
|
|
178
235
|
class ReactiveManager
|
@@ -266,6 +323,13 @@ class ReactiveManager
|
|
266
323
|
end
|
267
324
|
end
|
268
325
|
|
326
|
+
# Returns a copy of the object with where all ReactiveValue's are replaced
|
327
|
+
# with their current value.
|
328
|
+
# NOTE: Classes need to implement their own deep_cur method for this to work,
|
329
|
+
# it works out of the box with arrays and hashes.
|
330
|
+
def deep_cur
|
331
|
+
self.cur.deep_cur
|
332
|
+
end
|
269
333
|
|
270
334
|
# Method calls can be tagged so the reactive value knows
|
271
335
|
# how to handle them. This lets you check the state of
|
data/lib/volt/server.rb
CHANGED
@@ -24,6 +24,8 @@ class IfBindingSetup < BindingSetup
|
|
24
24
|
"[#{content}, #{branch[1].inspect}]"
|
25
25
|
end.join(', ')
|
26
26
|
|
27
|
-
|
27
|
+
# variables are captured for branches, so we must prefix them so they don't conflict.
|
28
|
+
# page, target, context, id
|
29
|
+
"lambda { |__p, __t, __c, __id| IfBinding.new(__p, __t, __c, __id, [#{branches}]) }"
|
28
30
|
end
|
29
31
|
end
|
@@ -37,7 +37,6 @@ class SocketConnectionHandler < SockJS::Session
|
|
37
37
|
# Messages are json and wrapped in an array
|
38
38
|
message = JSON.parse(message).first
|
39
39
|
|
40
|
-
puts "GOT: #{message.inspect}"
|
41
40
|
@@dispatcher.dispatch(self, message)
|
42
41
|
end
|
43
42
|
|
@@ -48,12 +47,15 @@ class SocketConnectionHandler < SockJS::Session
|
|
48
47
|
end
|
49
48
|
|
50
49
|
def closed
|
51
|
-
puts "CHANNEL CLOSED"
|
50
|
+
puts "CHANNEL CLOSED: #{self.inspect}"
|
52
51
|
# Remove ourself from the available channels
|
53
52
|
@@channels.delete(self)
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
|
54
|
+
QueryTasks.new(self).close!
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"<#{self.class.to_s}:#{object_id}>"
|
57
59
|
end
|
58
60
|
|
59
61
|
end
|
@@ -72,7 +72,7 @@ class Template
|
|
72
72
|
def add_template(node, content, name='Template')
|
73
73
|
html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
|
74
74
|
|
75
|
-
@current_scope.add_binding(@binding_number, "lambda { |
|
75
|
+
@current_scope.add_binding(@binding_number, "lambda { |__p, __t, __c, __id| #{name}Binding.new(__p, __t, __c, __id, #{@template_parser.template_path.inspect}, Proc.new { [#{content}] }) }")
|
76
76
|
|
77
77
|
@binding_number += 1
|
78
78
|
return html
|
@@ -84,7 +84,7 @@ class Template
|
|
84
84
|
content, variable_name = content.strip.split(/ as /)
|
85
85
|
|
86
86
|
template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
|
87
|
-
@current_scope.add_binding(@binding_number, "lambda { |
|
87
|
+
@current_scope.add_binding(@binding_number, "lambda { |__p, __t, __c, __id| EachBinding.new(__p, __t, __c, __id, Proc.new { #{content} }, #{variable_name.inspect}, #{template_name.inspect}) }")
|
88
88
|
|
89
89
|
# Add the node, the binding number, then store the location where the
|
90
90
|
# bindings for this block starts.
|
@@ -151,7 +151,7 @@ class Template
|
|
151
151
|
def add_text_binding(content)
|
152
152
|
html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
|
153
153
|
|
154
|
-
@current_scope.add_binding(@binding_number, "lambda { |
|
154
|
+
@current_scope.add_binding(@binding_number, "lambda { |__p, __t, __c, __id| ContentBinding.new(__p, __t, __c, __id, Proc.new { #{content} }) }")
|
155
155
|
|
156
156
|
@binding_number += 1
|
157
157
|
return html
|
@@ -194,7 +194,7 @@ class Template
|
|
194
194
|
getter = "_tmp = #{content[1..-2]}.or('') ; _tmp.reactive_manager.setter! { |val| self.#{content[1..-2]} = val } ; _tmp"
|
195
195
|
end
|
196
196
|
|
197
|
-
@current_scope.add_binding(node['id'], "lambda { |
|
197
|
+
@current_scope.add_binding(node['id'], "lambda { |__p, __t, __c, __id| AttributeBinding.new(__p, __t, __c, __id, #{attribute.inspect}, Proc.new { #{getter} }) }")
|
198
198
|
end
|
199
199
|
|
200
200
|
def add_multiple_getters(node, attribute, content)
|
@@ -208,7 +208,7 @@ class Template
|
|
208
208
|
|
209
209
|
reactive_template_path = add_reactive_template(content)
|
210
210
|
|
211
|
-
@current_scope.add_binding(node['id'], "lambda { |
|
211
|
+
@current_scope.add_binding(node['id'], "lambda { |__p, __t, __c, __id| AttributeBinding.new(__p, __t, __c, __id, #{attribute.inspect}, Proc.new { ReactiveTemplate.new(__p, __c, #{reactive_template_path.inspect}) }) }")
|
212
212
|
end
|
213
213
|
|
214
214
|
# Returns a path to a template for the content. This can be passed
|
@@ -237,7 +237,7 @@ class Template
|
|
237
237
|
node['href'] ||= ''
|
238
238
|
end
|
239
239
|
|
240
|
-
@current_scope.add_binding(node['id'], "lambda { |
|
240
|
+
@current_scope.add_binding(node['id'], "lambda { |__p, __t, __c, __id| EventBinding.new(__p, __t, __c, __id, #{event.inspect}, Proc.new {|event| #{content} })}")
|
241
241
|
end
|
242
242
|
|
243
243
|
def pull_closed_block_scopes(scope=@current_scope)
|
@@ -337,7 +337,7 @@ class Template
|
|
337
337
|
# Has multiple bindings, we need to render a template here
|
338
338
|
attr_template_path = add_reactive_template(content)
|
339
339
|
|
340
|
-
value = "Proc.new { ReactiveTemplate.new(
|
340
|
+
value = "Proc.new { ReactiveTemplate.new(__p, __c, #{attr_template_path.inspect}) }"
|
341
341
|
end
|
342
342
|
|
343
343
|
attribute_hash[attribute_node.name] = value
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'volt/utils/generic_pool'
|
2
|
+
|
3
|
+
# A counting pool behaves like a normal GenericPool, except for
|
4
|
+
# each time lookup is called, remove should be called when complete.
|
5
|
+
# The item will be completely removed from the GenericCountingPool
|
6
|
+
# only when it has been removed an equal number of times it has been
|
7
|
+
# looked up.
|
8
|
+
class GenericCountingPool < GenericPool
|
9
|
+
# return a created item with a count
|
10
|
+
def generate_new(*args)
|
11
|
+
[0, create(*args)]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Finds an item and tracks that it was checked out. Use
|
15
|
+
# #remove when the item is no longer needed.
|
16
|
+
def find(*args, &block)
|
17
|
+
item = __lookup(*args, &block)
|
18
|
+
|
19
|
+
item[0] += 1
|
20
|
+
|
21
|
+
return item[1]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Lookups an item
|
25
|
+
def lookup(*args, &block)
|
26
|
+
item = super(*args, &block)
|
27
|
+
|
28
|
+
return item[1]
|
29
|
+
end
|
30
|
+
|
31
|
+
def transform_item(item)
|
32
|
+
[0, item]
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(*args)
|
36
|
+
item = __lookup(*args)
|
37
|
+
item[0] -= 1
|
38
|
+
|
39
|
+
if item[0] == 0
|
40
|
+
# Last one using this item has removed it.
|
41
|
+
super(*args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# GenericPool is a base class you can inherit from to cache items
|
2
|
+
# based on a lookup.
|
3
|
+
#
|
4
|
+
# GenericPool assumes either a block is passed to lookup, or a
|
5
|
+
# #create method, that takes the path arguments and reutrns a new instance.
|
6
|
+
#
|
7
|
+
# GenericPool can handle as deep of paths as needed. You can also lookup
|
8
|
+
# all of the items at a sub-path with #lookup_all
|
9
|
+
#
|
10
|
+
# TODO: make the lookup/create threadsafe
|
11
|
+
class GenericPool
|
12
|
+
attr_reader :pool
|
13
|
+
def initialize
|
14
|
+
@pool = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def lookup(*args, &block)
|
18
|
+
section = @pool
|
19
|
+
|
20
|
+
# TODO: This is to work around opal issue #500
|
21
|
+
if RUBY_PLATFORM == 'opal'
|
22
|
+
args.pop if args.last == nil
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
args.each_with_index do |arg, index|
|
27
|
+
last = (args.size-1) == index
|
28
|
+
|
29
|
+
if last
|
30
|
+
# return, creating if needed
|
31
|
+
return(section[arg] ||= create_new_item(*args, &block))
|
32
|
+
else
|
33
|
+
next_section = section[arg]
|
34
|
+
next_section ||= (section[arg] = {})
|
35
|
+
section = next_section
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Does the actual creating, if a block is not passed in, it calls
|
41
|
+
# #create on the class.
|
42
|
+
def create_new_item(*args)
|
43
|
+
if block_given?
|
44
|
+
new_item = yield(*args)
|
45
|
+
else
|
46
|
+
new_item = create(*args)
|
47
|
+
end
|
48
|
+
|
49
|
+
return transform_item(new_item)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Allow other pools to override how the created item gets stored.
|
53
|
+
def transform_item(item)
|
54
|
+
item
|
55
|
+
end
|
56
|
+
|
57
|
+
# Make sure we call the pool one from lookup_all and not
|
58
|
+
# an overridden one.
|
59
|
+
alias_method :__lookup, :lookup
|
60
|
+
|
61
|
+
def lookup_all(*args)
|
62
|
+
__lookup(*args).values
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove(*args)
|
66
|
+
stack = []
|
67
|
+
section = @pool
|
68
|
+
|
69
|
+
args.each_with_index do |arg, index|
|
70
|
+
stack << section
|
71
|
+
|
72
|
+
if args.size-1 == index
|
73
|
+
section.delete(arg)
|
74
|
+
else
|
75
|
+
section = section[arg]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
(stack.size-1).downto(1) do |index|
|
80
|
+
node = stack[index]
|
81
|
+
parent = stack[index-1]
|
82
|
+
|
83
|
+
if node.size == 0
|
84
|
+
parent.delete(args[index-1])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -17,6 +17,49 @@ describe ReactiveArray do
|
|
17
17
|
expect(@changed).to eq(true)
|
18
18
|
end
|
19
19
|
|
20
|
+
it "should trigger changed from an insert in all places after the index" do
|
21
|
+
model = ReactiveValue.new(Model.new)
|
22
|
+
model._my_ary = [1,2,3]
|
23
|
+
|
24
|
+
count1 = 0
|
25
|
+
count2 = 0
|
26
|
+
model._my_ary[1].on('changed') { count1 += 1 }
|
27
|
+
model._my_ary[3].on('changed') { count2 += 1 }
|
28
|
+
expect(count1).to eq(0)
|
29
|
+
expect(count2).to eq(0)
|
30
|
+
|
31
|
+
model._my_ary.insert(1, 10)
|
32
|
+
expect(count1).to eq(1)
|
33
|
+
expect(count2).to eq(1)
|
34
|
+
|
35
|
+
expect(model._my_ary.cur).to eq([1,10,2,3])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should pass the index the item was inserted at" do
|
39
|
+
model = ReactiveValue.new(Model.new)
|
40
|
+
model._my_ary = [1,2,3]
|
41
|
+
|
42
|
+
model._my_ary.on('added') do |_, index|
|
43
|
+
expect(index).to eq(2)
|
44
|
+
end
|
45
|
+
|
46
|
+
model._my_ary.insert(2, 20)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should pass the index the item was inserted at with multiple inserted objects" do
|
50
|
+
model = ReactiveValue.new(Model.new)
|
51
|
+
model._my_ary = [1,2,3]
|
52
|
+
|
53
|
+
received = []
|
54
|
+
model._my_ary.on('added') do |_, index|
|
55
|
+
received << index
|
56
|
+
end
|
57
|
+
|
58
|
+
model._my_ary.insert(2, 20, 30)
|
59
|
+
|
60
|
+
expect(received).to eq([2, 3])
|
61
|
+
end
|
62
|
+
|
20
63
|
it "should trigger changed on methods of an array model that involve just one cell" do
|
21
64
|
model = ReactiveValue.new(ReactiveArray.new)
|
22
65
|
|