describe BubbleWrap::KVO do class KvoExample include BubbleWrap::KVO attr_accessor :age attr_accessor :label attr_accessor :items def initialize @items = [ "Apple", "Banana", "Chickpeas" ] @age = 1 @label = text_class.alloc.initWithFrame [[0,0],[320, 30]] set_text "Foo" end # Test helper def get_text @label.send(text_method_name) end def set_text(text) @label.send("#{text_method_name}=", text) end def observe_label(&block) observe(@label, text_method_name, &block) end def observe_label!(&block) observe!(@label, text_method_name, &block) end def observe_collection(&block) observe(self, :items, &block) end def unobserve_label unobserve(@label, text_method_name) end def unobserve_label! unobserve!(@label, text_method_name) end def text_method_name App.osx? ? :stringValue : :text end def text_class App.osx? ? NSTextField : UILabel end def update_collection self.items += [ "Rice" ] end # def unobserve_all #unobserve(@label, "text") #unobserve(self, "items") #end end describe "Registry" do before do @example = BW::KVO::Registry.new end after do @example = nil end # add it "should add an observer block" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path", &block) @example.registered?(target, "key_path").should == true end it "should not add an observer block if the key path is not present" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, nil, &block) @example.registered?(target, nil).should == false end it "should not add an observer block if the block is not present" do target = Object.new @example.add(target, "key_path") @example.registered?(target, "key_path").should == false end # remove it "should remove an observer block" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path", &block) @example.remove(target, "key_path") @example.registered?(target, "key_path").should == false end it "should not remove an observer block if the target is not present" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path", &block) @example.remove(nil, "key_path") @example.registered?(target, "key_path").should == true end it "should not remove an observer block if the key path is not present" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path", &block) @example.remove(target, nil) @example.registered?(target, "key_path").should == true end it "should remove only one observer block" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path1", &block) @example.add(target, "key_path2", &block) @example.remove(target, "key_path1") @example.registered?(target, "key_path1").should == false @example.registered?(target, "key_path2").should == true end # remove all it "should remove all observer blocks" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path1", &block) @example.add(target, "key_path2", &block) @example.remove_all @example.registered?(target, "key_path1").should == false @example.registered?(target, "key_path2").should == false end it "should remove target from targets if no observers remain" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path", &block) @example.remove(target, "key_path") @example.callbacks.length.should == 0 end it "should not remove target from targets if observers remain" do target = Object.new block = lambda { |old_value, new_value| } @example.add(target, "key_path1", &block) @example.add(target, "key_path2", &block) @example.remove(target, "key_path1") @example.callbacks.length.should > 0 end end describe "API" do before do @example = KvoExample.new end after do @example.unobserve_all @example = nil end # observe it "should observe a key path" do observed = false @example.observe_label do |old_value, new_value| observed = true old_value.should == "Foo" new_value.should == "Bar" end @example.set_text "Bar" observed.should == true end it "should immediately observe a key path" do @example.set_text "Foo" observed = false @example.observe_label! do |new_value| observed = true new_value.should == "Foo" end observed.should == true end it "should observe a key path with more than one block" do observed_one = false observed_two = false observed_three = false @example.observe_label do |old_value, new_value| observed_one = true end @example.observe_label do |old_value, new_value| observed_two = true end @example.observe_label do |old_value, new_value| observed_three = true end @example.set_text "Bar" observed_one.should == true observed_two.should == true observed_three.should == true end # unobserve it "should unobserve a key path" do observed = false @example.observe_label do |old_value, new_value| observed = true end @example.unobserve_label @example.set_text "Bar" observed.should == false end it "should unobserve immediate observer" do observed_times = 0 @example.observe_label do |old_value, new_value| observed_times += 1 end @example.unobserve_label! @example.set_text "Bar" observed_times.should == 1 end # without target it "should observe a key path without a target" do observed = false @example.observe :age do |old_value, new_value| observed = true old_value.should == 1 new_value.should == 2 end @example.age = 2 observed.should == true end it "should unobserve a key path without a target" do observed = false @example.observe :age do |old_value, new_value| observed = true end @example.unobserve :age @example.age = 2 observed.should == false end # with multiple keypaths it "should observe multiple key paths" do observed = [] @example.observe [:age, :items] do |old_value, new_value| observed << [old_value, new_value] end @example.age = 2 @example.age = 3 @example.update_collection observed.should.be == [ [1, 2], [2, 3], [ ["Apple", "Banana", "Chickpeas"], ["Apple", "Banana", "Chickpeas", "Rice"] ] ] end it "should observe multiple key paths with key_path argument" do observed_changes = [] @example.observe [:age, :items] do |old_value, new_value, key_path| observed_changes << { key_path => [old_value, new_value] } end @example.age = 2 @example.age = 3 @example.update_collection observed_changes.should.be == [ {"age" => [1, 2]}, {"age" => [2, 3]}, { "items" => [ ["Apple", "Banana", "Chickpeas"], ["Apple", "Banana", "Chickpeas", "Rice"] ] } ] end it "should immediately observe multiple key paths" do observed_changes = [] @example.observe! [:age, :items] do |new_value| observed_changes << new_value end @example.age = 2 @example.age = 3 @example.update_collection observed_changes.should.be == [1, ["Apple", "Banana", "Chickpeas"], 2, 3, ["Apple", "Banana", "Chickpeas", "Rice"]] end it "should immediately observe multiple key paths with key_path argument" do observed_changes = [] @example.observe! [:age, :items] do |new_value, key_path| observed_changes << { key_path => new_value } end @example.age = 2 @example.age = 3 @example.update_collection observed_changes.should.be == [ {"age" => 1}, {"items" => ["Apple", "Banana", "Chickpeas"]}, {"age" => 2}, {"age" => 3}, {"items" => ["Apple", "Banana", "Chickpeas", "Rice"]} ] end it "should unobserve multiple key paths" do observed = 0 @example.observe [:age, :items] do observed += 1 end @example.unobserve [:age] @example.age = 2 @example.update_collection observed.should.be == 1 end end end