def test_od_oi(self):
        """
        A past opperations is an object delete which gets applied to this
        object insert.
        """
        array_path = ['a', 'b', 3, 4]
        op1 = Op('od', array_path, offset='X')
        cs1 = Changeset('doc_id', 'author', [])
        cs1.add_op(op1)

        # op2 happens at a different path, so should not be affected
        op2 = Op('oi', ['a', 'b', 3, 5], offset='Y', val=['XYZ'])
        op2.ot(cs1)
        assert op2.t_path == ['a', 'b', 3, 5]
        assert op2.t_offset == 'Y'
        assert op2.t_val == ['XYZ']
        assert not op2.is_noop()
        op1.hazards = []

        # op3 happens at path, but differant offset, no change
        op3 = Op('oi', array_path, offset='W', val=['XYZ'])
        op3.ot(cs1)
        assert op3.t_path == array_path
        assert op3.t_offset == 'W'
        assert not op3.is_noop()
        op1.hazards = []

        # op4 happens within deletion
        op4_path = array_path + ['X']
        op4 = Op('oi', op4_path, offset='R', val=['XYZ'])
        op4.ot(cs1)
        assert op4.is_noop()
        op1.hazards = []

        # op5 inserts right where there was a deletion. Some previous conflict,
        # so noop
        op5 = Op('oi', array_path, offset='X', val='XYZ')
        op5.ot(cs1)
        assert op5.t_path == array_path
        assert op5.t_offset == 'X'
        assert op5.t_val == 'XYZ'
        assert op5.is_noop()
        op1.hazards = []

        # op6 is at a partial path along deletion, no change
        op6_path = ['a']
        op6 = Op('oi', op6_path, offset='c', val=['XYZ'])
        op6.ot(cs1)
        assert op6.t_path == ['a']
        assert op6.t_offset == 'c'
        assert op6.t_val == ['XYZ']
        assert not op6.is_noop()
        op1.hazards = []
    def test_oi_oi(self):
        """
        A past opperation is an Object insert which gets applied to this object
        insert. The only opportunity for conflict is if the later insert
        happens along a path that is no longer valid.
        """
        array_path = ['a', 'b', 3, 4]
        op1 = Op('oi', array_path, offset='X', val=['Y', 'Z'])
        cs1 = Changeset('doc_id', 'author', [])
        cs1.add_op(op1)

        # op2 happens at a different path, so should not be affected
        op2 = Op('oi', ['a', 'b', 3, 5], offset='c', val=["XYZ"])
        op2.ot(cs1)
        assert op2.t_path == ['a', 'b', 3, 5]
        op1.hazards = []

        # op3 happens along path, so it will just overwrite past data
        op3 = Op('oi', ['a'], offset='b', val="XYZ")
        op3.ot(cs1)
        assert op3.t_path == ['a']
        assert op3.t_offset == 'b'
        assert op3.t_val == 'XYZ'
        op1.hazards = []

        # op4 is at same path, different offset, so no conflict
        op4 = Op('oi', array_path, offset='W', val="WWW")
        op4.ot(cs1)
        assert op4.t_path == array_path
        assert op4.t_offset == 'W'
        assert op4.t_val == 'WWW'
        assert not op4.is_noop()
        op1.hazards = []

        # op5 is at same path and offset, so previous op takes precedence
        op5 = Op('oi', array_path, offset='X', val=["XXX"])
        op5.ot(cs1)
        assert op5.t_path == array_path
        assert op5.t_offset == 'X'
        assert op5.t_val == ['XXX']
        assert op5.is_noop()
        op1.hazards = []

        # op6 path is deep within a previous object insert
        op6_path = array_path + ['X', 9, 'c']
        op6 = Op('oi', op6_path, offset=8, val=["XYZ"])
        op6.ot(cs1)
        assert op6.is_noop()
        op1.hazards = []
    def test_oi_od(self):
        """
        A past opperation is an object insert which gets applied to these
        object deletes.
        """
        array_path = ['a', 'b', 3, 4]
        op1 = Op('oi', array_path, offset='X', val=['X', 'Y'])
        cs1 = Changeset('doc_id', 'author', [])
        cs1.add_op(op1)

        # op2 happens at a different path, so should not be affected
        op2 = Op('od', ['a', 'b', 3, 5], offset='R')
        op2.ot(cs1)
        assert op2.t_path == ['a', 'b', 3, 5]
        assert op2.t_offset == 'R'
        assert not op2.is_noop()
        op1.hazards = []

        # op3 deletes a key were unknown op had inserted one. Could not have
        # intended to delete what it did not know, so noop
        op3 = Op('od', array_path, offset='X')
        op3.ot(cs1)
        assert op3.t_path == array_path
        assert op3.t_offset == 'X'
        assert op3.is_noop()
        op1.hazards = []

        # same as above, but well within the inserted value
        op4_path = array_path + ['X']
        op4 = Op('od', op4_path, offset='Y')
        op4.ot(cs1)
        assert op4.t_path == array_path + ['X']
        assert op4.t_offset == 'Y'
        assert op4.is_noop()
        op1.hazards = []

        # op5 is at same path, differant offset. No change
        op5 = Op('od', array_path, offset='R')
        op5.ot(cs1)
        assert op5.t_path == array_path
        assert op5.t_offset == 'R'
        assert not op5.is_noop()
        op1.hazards = []

        # op6 is at shorter path. No change
        op6 = Op('od', ['a'], offset='c')
        op6.ot(cs1)
        assert op6.t_path == ['a']
        assert op6.t_offset == 'c'
        assert not op6.is_noop()
        op1.hazards = []

        # op7 deletes whatever was previously changed
        op7 = Op('od', ['a'], offset='b')
        op7.ot(cs1)
        assert op7.t_path == ['a']
        assert op7.t_offset == 'b'
        assert not op7.is_noop()
        op1.hazards = []
    def test_od_od(self):
        """
        A past opperation is an object delete which gets applied to these
        object deletes.
        """
        array_path = ['a', 'b', 3, 4]
        op1 = Op('od', array_path, offset='X')
        cs1 = Changeset('doc_id', 'author', [])
        cs1.add_op(op1)

        # op2 happens at a different path, so should not be affected
        op2 = Op('od', ['a', 'b', 3, 5], offset='Y')
        op2.ot(cs1)
        assert op2.t_path == ['a', 'b', 3, 5]
        assert op2.t_offset == 'Y'
        assert not op2.is_noop()
        op1.hazards = []

        # op3 happens at same path but different offset
        op3 = Op('od', array_path, offset='R')
        op3.ot(cs1)
        assert op3.t_path == array_path
        assert op3.t_offset == 'R'
        assert not op3.is_noop()
        op1.hazards = []

        # op4 tries to delete the same key as op1
        op4 = Op('od', array_path, offset='X')
        op4.ot(cs1)
        assert op4.is_noop()
        op1.hazards = []

        # op5 tries to delete something within what was already deleted
        op5_path = array_path + ['X']
        op5 = Op('od', op5_path, offset='R')
        op5.ot(cs1)
        assert op5.is_noop()
        op1.hazards = []

        # op6 is at a shorter, different path. No change
        op6_path = ['a']
        op6 = Op('od', op6_path, offset='c')
        op6.ot(cs1)
        assert op6.t_path == ['a']
        assert op6.t_offset == 'c'
        assert not op6.is_noop()
        op1.hazards = []

        # op9 is at shorter, same path. No change
        op9_path = ['a']
        op9 = Op('od', op9_path, offset='b')
        op9.ot(cs1)
        assert op9.t_path == op9_path
        assert op9.t_offset == 'b'
        assert not op9.is_noop()
        op1.hazards = []