Skip to content

Commit 9d706a8

Browse files
committed
Add specs for current_value= setter with all edge cases
1 parent 0833507 commit 9d706a8

3 files changed

Lines changed: 199 additions & 33 deletions

File tree

lib/bindable/binding.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,42 @@ def evaluate(obj)
4949
end
5050
end
5151
end
52+
53+
# Set the current value by traversing the path and updating the final location
54+
def current_value=(new_value)
55+
old_root = @watch_obj.root_value
56+
57+
# Navigate to the parent and set the value at the final step
58+
parent = @watch_obj.instance_variable_get(:@values)
59+
path_steps = @path.dup
60+
61+
# If path is empty, can't set at root
62+
return new_value if path_steps.empty?
63+
64+
# Navigate to the parent of the final step
65+
while path_steps.length > 1
66+
step = path_steps.shift
67+
case step[:type]
68+
when :property
69+
parent = parent[step[:key]]
70+
when :index
71+
parent = parent[step[:index]]
72+
end
73+
end
74+
75+
# Apply the final step to set the value
76+
final_step = path_steps.first
77+
case final_step[:type]
78+
when :property
79+
# For hash/object properties, use []= directly
80+
parent[final_step[:key]] = new_value
81+
when :index
82+
# For array indices, use []=
83+
parent[final_step[:index]] = new_value
84+
end
85+
86+
@watch_obj.notify_change(old_root)
87+
new_value
88+
end
5289
end
5390
end

lib/bindable/value.rb

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,7 @@ def current_value
2020

2121
# Set the current value by traversing the path and updating the final location
2222
def current_value=(new_value)
23-
old_root = @watch_obj.root_value
24-
25-
# Navigate to the parent and set the value at the final step
26-
parent = @watch_obj.instance_variable_get(:@values)
27-
path_steps = @path.dup
28-
29-
# If path is empty, can't set at root
30-
return new_value if path_steps.empty?
31-
32-
# Navigate to the parent of the final step
33-
while path_steps.length > 1
34-
step = path_steps.shift
35-
case step[:type]
36-
when :property
37-
parent = parent[step[:key]]
38-
when :index
39-
parent = parent[step[:index]]
40-
end
41-
end
42-
43-
# Apply the final step to set the value
44-
final_step = path_steps.first
45-
case final_step[:type]
46-
when :property
47-
# For hash/object properties, use []= directly
48-
parent[final_step[:key]] = new_value
49-
when :index
50-
# For array indices, use []=
51-
parent[final_step[:index]] = new_value
52-
end
53-
54-
@watch_obj.notify_change(old_root)
55-
new_value
23+
bind.current_value = new_value
5624
end
5725

5826
# Delegate all methods to the underlying value, but handle mutations

spec/support/shared_examples.rb

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,167 @@
198198
end
199199
end
200200

201+
describe 'current_value= setter' do
202+
it 'sets value on simple property and triggers watchers' do
203+
watch.count = 5
204+
binding = watch.count.bind
205+
206+
values = []
207+
binding.watch { |old, new| values << [old, new] }
208+
209+
binding.current_value = 10
210+
expect(values).to eq([[5, 10]])
211+
expect(watch.count).to eq(10)
212+
end
213+
214+
it 'sets value on hash key and triggers watchers' do
215+
watch.data = { name: 'Alice', age: 30 }
216+
binding = watch.data[:age].bind
217+
218+
values = []
219+
binding.watch { |old, new| values << [old, new] }
220+
221+
binding.current_value = 31
222+
expect(values).to eq([[30, 31]])
223+
expect(watch.data[:age]).to eq(31)
224+
end
225+
226+
it 'sets value on array index and triggers watchers' do
227+
watch.list = [1, 2, 3]
228+
binding = watch.list[1].bind
229+
230+
values = []
231+
binding.watch { |old, new| values << [old, new] }
232+
233+
binding.current_value = 99
234+
expect(values).to eq([[2, 99]])
235+
expect(watch.list).to eq([1, 99, 3])
236+
end
237+
238+
it 'sets value on deeply nested hash path' do
239+
watch.data = { users: [{ name: 'Alice', age: 30 }] }
240+
binding = watch.data[:users][0][:age].bind
241+
242+
values = []
243+
binding.watch { |old, new| values << [old, new] }
244+
245+
binding.current_value = 35
246+
expect(values).to eq([[30, 35]])
247+
expect(watch.data[:users][0][:age]).to eq(35)
248+
end
249+
250+
it 'sets value on deeply nested array path' do
251+
watch.matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
252+
binding = watch.matrix[1][2].bind
253+
254+
values = []
255+
binding.watch { |old, new| values << [old, new] }
256+
257+
binding.current_value = 99
258+
expect(values).to eq([[6, 99]])
259+
expect(watch.matrix[1][2]).to eq(99)
260+
end
261+
262+
it 'sets value on mixed hash/array path' do
263+
watch.data = { items: [{ id: 1, count: 5 }, { id: 2, count: 10 }] }
264+
binding = watch.data[:items][1][:count].bind
265+
266+
values = []
267+
binding.watch { |old, new| values << [old, new] }
268+
269+
binding.current_value = 20
270+
expect(values).to eq([[10, 20]])
271+
expect(watch.data[:items][1][:count]).to eq(20)
272+
end
273+
274+
it 'triggers multiple watchers when setting value' do
275+
watch.value = 1
276+
binding = watch.value.bind
277+
278+
values1 = []
279+
values2 = []
280+
binding.watch { |old, new| values1 << [old, new] }
281+
binding.watch { |old, new| values2 << [old, new] }
282+
283+
binding.current_value = 5
284+
expect(values1).to eq([[1, 5]])
285+
expect(values2).to eq([[1, 5]])
286+
end
287+
288+
it 'does not trigger when setting to same value' do
289+
watch.value = 42
290+
binding = watch.value.bind
291+
292+
triggered = false
293+
binding.watch { |_old, _new| triggered = true }
294+
295+
binding.current_value = 42
296+
expect(triggered).to be false
297+
end
298+
299+
it 'handles setting nil value' do
300+
watch.value = 'something'
301+
binding = watch.value.bind
302+
303+
values = []
304+
binding.watch { |old, new| values << [old, new] }
305+
306+
binding.current_value = nil
307+
expect(values).to eq([['something', nil]])
308+
expect(watch.value).to be_nil
309+
end
310+
311+
it 'handles setting value from nil' do
312+
watch.value = nil
313+
binding = watch.value.bind
314+
315+
values = []
316+
binding.watch { |old, new| values << [old, new] }
317+
318+
binding.current_value = 'something'
319+
expect(values).to eq([[nil, 'something']])
320+
expect(watch.value).to eq('something')
321+
end
322+
323+
it 'sets complex objects as values' do
324+
watch.data = { config: { timeout: 30 } }
325+
binding = watch.data[:config].bind
326+
327+
values = []
328+
binding.watch { |old, new| values << [old, new] }
329+
330+
new_config = { timeout: 60, retries: 3 }
331+
binding.current_value = new_config
332+
expect(values).to eq([[{ timeout: 30 }, new_config]])
333+
expect(watch.data[:config]).to eq(new_config)
334+
end
335+
336+
it 'works with array element after mutations' do
337+
watch.items = [10, 20, 30]
338+
binding = watch.items[0].bind
339+
340+
values = []
341+
binding.watch { |old, new| values << [old, new] }
342+
343+
binding.current_value = 100
344+
expect(values).to eq([[10, 100]])
345+
expect(watch.items).to eq([100, 20, 30])
346+
347+
watch.items.push(40)
348+
binding.current_value = 200
349+
expect(values).to eq([[10, 100], [100, 200]])
350+
expect(watch.items).to eq([200, 20, 30, 40])
351+
end
352+
353+
it 'returns the new value when setting' do
354+
watch.value = 1
355+
binding = watch.value.bind
356+
357+
result = binding.current_value = 42
358+
expect(result).to eq(42)
359+
end
360+
end
361+
201362
describe 'integration with Ruby collections' do
202363
it 'works as array index' do
203364
watch.index = 2

0 commit comments

Comments
 (0)