From 14e3135d00c779cd38be710459219b9b12ba9013 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Fri, 2 Sep 2016 07:18:33 +0000 Subject: [PATCH 1/2] react --- exercises/react/react.rb | 67 +++++++++++++ exercises/react/react_test.rb | 176 ++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 exercises/react/react.rb create mode 100644 exercises/react/react_test.rb diff --git a/exercises/react/react.rb b/exercises/react/react.rb new file mode 100644 index 0000000000..0e84c62783 --- /dev/null +++ b/exercises/react/react.rb @@ -0,0 +1,67 @@ +class Reactor + def create_input(initial_value) + InputCell::new(initial_value) + end + + def create_compute(*inputs, &block) + ComputeCell::new(inputs, ->() { block.call(*inputs.map(&:value)) }) + end +end + +class Cell + attr_reader :value + + def initialize(initial_value) + @value = initial_value + @dependencies = [] + end + + protected + + attr_reader :dependencies +end + +class InputCell < Cell + def value=(new_value) + @value = new_value + @dependencies.each(&:update_dependencies) + @dependencies.each(&:fire_callbacks) + end +end + +class ComputeCell < Cell + def initialize(inputs, compute) + super(compute.call) + @last_value = @value + @compute = compute + inputs.each { |i| i.dependencies << self } + @callbacks = {} + @callbacks_issued = 0 + end + + def add_callback(&block) + @callbacks_issued += 1 + @callbacks[@callbacks_issued] = block + @callbacks_issued + end + + def remove_callback(id) + @callbacks.delete(id) + end + + # TODO: Would like for only InputCells and ComputeCells to call these two. + + def update_dependencies + new_value = @compute.call + return if new_value == @value + @value = new_value + @dependencies.each(&:update_dependencies) + end + + def fire_callbacks + return if @value == @last_value + @callbacks.each_value { |c| c.call(@value) } + @last_value = @value + @dependencies.each(&:fire_callbacks) + end +end diff --git a/exercises/react/react_test.rb b/exercises/react/react_test.rb new file mode 100644 index 0000000000..6ec5789feb --- /dev/null +++ b/exercises/react/react_test.rb @@ -0,0 +1,176 @@ +require 'minitest/autorun' +require_relative 'react' + +class ReactTest < Minitest::Test + def test_input_cells_have_a_value + reactor = Reactor.new + input = reactor.create_input(10) + assert_equal 10, input.value + end + + def test_input_cells_can_be_set + reactor = Reactor.new + input = reactor.create_input(4) + assert_equal 4, input.value + input.value = 20 + assert_equal 20, input.value + end + + def test_compute_cells_calculate_initial_value + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v + 1 } + assert_equal 2, output.value + end + + def test_compute_cell_takes_inputs_in_the_right_order + reactor = Reactor.new + one = reactor.create_input(1) + two = reactor.create_input(2) + output = reactor.create_compute(one, two) { |v1, v2| v1 + v2 * 10 } + assert_equal 21, output.value + end + + def test_compute_cells_update_value_when_dependencies_are_changed + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v + 1 } + assert_equal 2, output.value + input.value = 3 + assert_equal 4, output.value + end + + def test_compute_cell_can_depend_on_other_compute_cells + reactor = Reactor.new + input = reactor.create_input(1) + times_two = reactor.create_compute(input) { |v| v * 2 } + times_thirty = reactor.create_compute(input) { |v| v * 30 } + output = reactor.create_compute(times_two, times_thirty) { |v1, v2| v1 + v2 } + assert_equal 32, output.value + input.value = 3 + assert_equal 96, output.value + end + + def test_compute_cells_fire_callbacks + values = [] + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v + 1 } + output.add_callback { |v| values << v } + input.value = 3 + assert_equal [4], values + end + + def test_callbacks_only_fire_on_change + values = [] + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v < 3 ? 111 : 222 } + output.add_callback { |v| values << v } + input.value = 2 + assert_equal [], values + input.value = 4 + assert_equal [222], values + end + + def test_callbacks_can_fire_multiple_times + values = [] + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v + 1 } + output.add_callback { |v| values << v } + input.value = 2 + assert_equal [3], values + input.value = 3 + assert_equal [3, 4], values + end + + def test_callbacks_can_fire_from_multiple_cells + values1 = [] + values2 = [] + reactor = Reactor.new + input = reactor.create_input(1) + plus_one = reactor.create_compute(input) { |v| v + 1 } + minus_one = reactor.create_compute(input) { |v| v - 1 } + plus_one.add_callback { |v| values1 << v } + minus_one.add_callback { |v| values2 << v } + input.value = 10 + assert_equal [11], values1 + assert_equal [9], values2 + end + + def test_callbacks_can_be_added_and_removed + values1 = [] + values2 = [] + values3 = [] + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v + 1 } + callback = output.add_callback { |v| values1 << v } + output.add_callback { |v| values2 << v } + input.value = 31 + assert_equal [32], values1 + assert_equal [32], values2 + output.remove_callback(callback) + output.add_callback { |v| values3 << v } + input.value = 41 + assert_equal [32], values1, 'callback should not be called after removal' + assert_equal [32, 42], values2 + assert_equal [42], values3 + end + + def test_removing_a_callback_multiple_times_doesnt_interfere_with_others + values1 = [] + values2 = [] + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v + 1 } + callback = output.add_callback { |v| values1 << v } + output.add_callback { |v| values2 << v } + 10.times { output.remove_callback(callback) } + input.value = 2 + assert_equal [], values1 + assert_equal [3], values2 + end + + def test_removing_a_callback_multiple_times_doesnt_interfere_with_others + values1 = [] + values2 = [] + reactor = Reactor.new + input = reactor.create_input(1) + output = reactor.create_compute(input) { |v| v + 1 } + callback = output.add_callback { |v| values1 << v } + output.add_callback { |v| values2 << v } + 10.times { output.remove_callback(callback) } + input.value = 2 + assert_equal [], values1 + assert_equal [3], values2 + end + + def test_callbacks_should_only_be_called_once_even_if_multiple_dependencies_change + values = [] + reactor = Reactor.new + input = reactor.create_input(1) + plus_one = reactor.create_compute(input) { |v| v + 1 } + minus_one1 = reactor.create_compute(input) { |v| v - 1 } + minus_one2 = reactor.create_compute(minus_one1) { |v| v - 1 } + output = reactor.create_compute(plus_one, minus_one2) { |v1, v2| v1 * v2 } + output.add_callback { |v| values << v } + input.value = 4 + assert_equal [10], values + end + + def test_callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt_change + values = [] + reactor = Reactor.new + input = reactor.create_input(1) + plus_one = reactor.create_compute(input) { |v| v + 1 } + minus_one = reactor.create_compute(input) { |v| v - 1 } + always_two = reactor.create_compute(plus_one, minus_one) { |v1, v2| v1 - v2 } + always_two.add_callback { |v| values << v } + 10.times { |i| + input.value = i + assert_equal [], values + } + end +end From 4aa6d64a512749555b38017e1b580857f3744776 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sun, 11 Sep 2016 08:11:21 +0000 Subject: [PATCH 2/2] react: remove Reactor --- exercises/react/react.rb | 19 ++------ exercises/react/react_test.rb | 89 +++++++++++++++-------------------- 2 files changed, 42 insertions(+), 66 deletions(-) diff --git a/exercises/react/react.rb b/exercises/react/react.rb index 0e84c62783..ee29e66b70 100644 --- a/exercises/react/react.rb +++ b/exercises/react/react.rb @@ -1,13 +1,3 @@ -class Reactor - def create_input(initial_value) - InputCell::new(initial_value) - end - - def create_compute(*inputs, &block) - ComputeCell::new(inputs, ->() { block.call(*inputs.map(&:value)) }) - end -end - class Cell attr_reader :value @@ -30,10 +20,11 @@ def value=(new_value) end class ComputeCell < Cell - def initialize(inputs, compute) - super(compute.call) + def initialize(*inputs, &compute) + new_value = -> { compute.call(*inputs.map(&:value)) } + super(new_value.call) @last_value = @value - @compute = compute + @new_value = new_value inputs.each { |i| i.dependencies << self } @callbacks = {} @callbacks_issued = 0 @@ -52,7 +43,7 @@ def remove_callback(id) # TODO: Would like for only InputCells and ComputeCells to call these two. def update_dependencies - new_value = @compute.call + new_value = @new_value.call return if new_value == @value @value = new_value @dependencies.each(&:update_dependencies) diff --git a/exercises/react/react_test.rb b/exercises/react/react_test.rb index 6ec5789feb..67f538fe45 100644 --- a/exercises/react/react_test.rb +++ b/exercises/react/react_test.rb @@ -3,49 +3,43 @@ class ReactTest < Minitest::Test def test_input_cells_have_a_value - reactor = Reactor.new - input = reactor.create_input(10) + input = InputCell.new(10) assert_equal 10, input.value end def test_input_cells_can_be_set - reactor = Reactor.new - input = reactor.create_input(4) + input = InputCell.new(4) assert_equal 4, input.value input.value = 20 assert_equal 20, input.value end def test_compute_cells_calculate_initial_value - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v + 1 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v + 1 } assert_equal 2, output.value end def test_compute_cell_takes_inputs_in_the_right_order - reactor = Reactor.new - one = reactor.create_input(1) - two = reactor.create_input(2) - output = reactor.create_compute(one, two) { |v1, v2| v1 + v2 * 10 } + one = InputCell.new(1) + two = InputCell.new(2) + output = ComputeCell.new(one, two) { |v1, v2| v1 + v2 * 10 } assert_equal 21, output.value end def test_compute_cells_update_value_when_dependencies_are_changed - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v + 1 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v + 1 } assert_equal 2, output.value input.value = 3 assert_equal 4, output.value end def test_compute_cell_can_depend_on_other_compute_cells - reactor = Reactor.new - input = reactor.create_input(1) - times_two = reactor.create_compute(input) { |v| v * 2 } - times_thirty = reactor.create_compute(input) { |v| v * 30 } - output = reactor.create_compute(times_two, times_thirty) { |v1, v2| v1 + v2 } + input = InputCell.new(1) + times_two = ComputeCell.new(input) { |v| v * 2 } + times_thirty = ComputeCell.new(input) { |v| v * 30 } + output = ComputeCell.new(times_two, times_thirty) { |v1, v2| v1 + v2 } assert_equal 32, output.value input.value = 3 assert_equal 96, output.value @@ -53,9 +47,8 @@ def test_compute_cell_can_depend_on_other_compute_cells def test_compute_cells_fire_callbacks values = [] - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v + 1 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v + 1 } output.add_callback { |v| values << v } input.value = 3 assert_equal [4], values @@ -63,9 +56,8 @@ def test_compute_cells_fire_callbacks def test_callbacks_only_fire_on_change values = [] - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v < 3 ? 111 : 222 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v < 3 ? 111 : 222 } output.add_callback { |v| values << v } input.value = 2 assert_equal [], values @@ -75,9 +67,8 @@ def test_callbacks_only_fire_on_change def test_callbacks_can_fire_multiple_times values = [] - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v + 1 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v + 1 } output.add_callback { |v| values << v } input.value = 2 assert_equal [3], values @@ -88,10 +79,9 @@ def test_callbacks_can_fire_multiple_times def test_callbacks_can_fire_from_multiple_cells values1 = [] values2 = [] - reactor = Reactor.new - input = reactor.create_input(1) - plus_one = reactor.create_compute(input) { |v| v + 1 } - minus_one = reactor.create_compute(input) { |v| v - 1 } + input = InputCell.new(1) + plus_one = ComputeCell.new(input) { |v| v + 1 } + minus_one = ComputeCell.new(input) { |v| v - 1 } plus_one.add_callback { |v| values1 << v } minus_one.add_callback { |v| values2 << v } input.value = 10 @@ -103,9 +93,8 @@ def test_callbacks_can_be_added_and_removed values1 = [] values2 = [] values3 = [] - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v + 1 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v + 1 } callback = output.add_callback { |v| values1 << v } output.add_callback { |v| values2 << v } input.value = 31 @@ -122,9 +111,8 @@ def test_callbacks_can_be_added_and_removed def test_removing_a_callback_multiple_times_doesnt_interfere_with_others values1 = [] values2 = [] - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v + 1 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v + 1 } callback = output.add_callback { |v| values1 << v } output.add_callback { |v| values2 << v } 10.times { output.remove_callback(callback) } @@ -136,9 +124,8 @@ def test_removing_a_callback_multiple_times_doesnt_interfere_with_others def test_removing_a_callback_multiple_times_doesnt_interfere_with_others values1 = [] values2 = [] - reactor = Reactor.new - input = reactor.create_input(1) - output = reactor.create_compute(input) { |v| v + 1 } + input = InputCell.new(1) + output = ComputeCell.new(input) { |v| v + 1 } callback = output.add_callback { |v| values1 << v } output.add_callback { |v| values2 << v } 10.times { output.remove_callback(callback) } @@ -149,12 +136,11 @@ def test_removing_a_callback_multiple_times_doesnt_interfere_with_others def test_callbacks_should_only_be_called_once_even_if_multiple_dependencies_change values = [] - reactor = Reactor.new - input = reactor.create_input(1) - plus_one = reactor.create_compute(input) { |v| v + 1 } - minus_one1 = reactor.create_compute(input) { |v| v - 1 } - minus_one2 = reactor.create_compute(minus_one1) { |v| v - 1 } - output = reactor.create_compute(plus_one, minus_one2) { |v1, v2| v1 * v2 } + input = InputCell.new(1) + plus_one = ComputeCell.new(input) { |v| v + 1 } + minus_one1 = ComputeCell.new(input) { |v| v - 1 } + minus_one2 = ComputeCell.new(minus_one1) { |v| v - 1 } + output = ComputeCell.new(plus_one, minus_one2) { |v1, v2| v1 * v2 } output.add_callback { |v| values << v } input.value = 4 assert_equal [10], values @@ -162,11 +148,10 @@ def test_callbacks_should_only_be_called_once_even_if_multiple_dependencies_chan def test_callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt_change values = [] - reactor = Reactor.new - input = reactor.create_input(1) - plus_one = reactor.create_compute(input) { |v| v + 1 } - minus_one = reactor.create_compute(input) { |v| v - 1 } - always_two = reactor.create_compute(plus_one, minus_one) { |v1, v2| v1 - v2 } + input = InputCell.new(1) + plus_one = ComputeCell.new(input) { |v| v + 1 } + minus_one = ComputeCell.new(input) { |v| v - 1 } + always_two = ComputeCell.new(plus_one, minus_one) { |v1, v2| v1 - v2 } always_two.add_callback { |v| values << v } 10.times { |i| input.value = i