1local class = require 'middleclass'
2local Object = class.Object
3
4local function is_lua_5_2_compatible()
5  return type(rawlen) == 'function'
6end
7
8describe('Metamethods', function()
9
10  describe('Custom Metamethods', function()
11    local Vector= class('Vector')
12    function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end
13    function Vector.__tostring(a) return a.class.name .. '[' .. a.x .. ',' .. a.y .. ',' .. a.z .. ']' end
14    function Vector.__eq(a,b)     return a.x==b.x and a.y==b.y and a.z==b.z end
15    function Vector.__lt(a,b)     return a() < b() end
16    function Vector.__le(a,b)     return a() <= b() end
17    function Vector.__add(a,b)    return Vector:new(a.x+b.x, a.y+b.y ,a.z+b.z) end
18    function Vector.__sub(a,b)    return Vector:new(a.x-b.x, a.y-b.y, a.z-b.z) end
19    function Vector.__div(a,s)    return Vector:new(a.x/s, a.y/s, a.z/s) end
20    function Vector.__unm(a)      return Vector:new(-a.x, -a.y, -a.z) end
21    function Vector.__concat(a,b) return a.x*b.x+a.y*b.y+a.z*b.z end
22    function Vector.__call(a)     return math.sqrt(a.x*a.x+a.y*a.y+a.z*a.z) end
23    function Vector.__pow(a,b)
24      return Vector:new(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x)
25    end
26    function Vector.__mul(a,b)
27      if type(b)=="number" then return Vector:new(a.x*b, a.y*b, a.z*b) end
28      if type(a)=="number" then return Vector:new(a*b.x, a*b.y, a*b.z) end
29    end
30    function Vector.__len(a)    return 3 end
31    function Vector.__pairs(a)
32      local t = {x=a.x,y=a.y,z=a.z}
33      return coroutine.wrap(function()
34          for k,v in pairs(t) do
35            coroutine.yield(k,v)
36          end
37        end)
38    end
39    function Vector.__ipairs(a)
40      local t = {a.x,a.y,a.z}
41      return coroutine.wrap(function()
42          for k,v in ipairs(t) do
43            coroutine.yield(k,v)
44          end
45        end)
46    end
47
48    local a = Vector:new(1,2,3)
49    local b = Vector:new(2,4,6)
50
51    for metamethod,values in pairs({
52      __tostring = { tostring(a), "Vector[1,2,3]" },
53      __eq =       { a,    a},
54      __lt =       { a<b,  true },
55      __le =       { a<=b, true },
56      __add =      { a+b,  Vector(3,6,9) },
57      __sub =      { b-a,  Vector(1,2,3) },
58      __div =      { b/2,  Vector(1,2,3) },
59      __unm =      { -a,   Vector(-1,-2,-3) },
60      __concat =   { a..b, 28 },
61      __call =     { a(), math.sqrt(14) },
62      __pow =      { a^b,  Vector(0,0,0) },
63      __mul =      { 4*a,  Vector(4,8,12) }
64      --__index =    { b[1], 3 }
65    }) do
66      describe(metamethod, function()
67        it('works as expected', function()
68          assert.equal(values[1], values[2])
69        end)
70      end)
71    end
72
73    if is_lua_5_2_compatible() then
74
75      describe('__len', function()
76        it('works as expected', function()
77          assert.equal(#a, 3)
78        end)
79      end)
80
81      describe('__pairs', function()
82        it('works as expected',function()
83          local output = {}
84          for k,v in pairs(a) do
85            output[k] = v
86          end
87          assert.are.same(output,{x=1,y=2,z=3})
88        end)
89      end)
90
91      describe('__ipairs', function()
92        it('works as expected',function()
93          local output = {}
94          for _,i in ipairs(a) do
95            output[#output+1] = i
96          end
97          assert.are.same(output,{1,2,3})
98        end)
99      end)
100
101    end
102
103    describe('Inherited Metamethods', function()
104      local Vector2= class('Vector2', Vector)
105      function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
106
107      local c = Vector2:new(1,2,3)
108      local d = Vector2:new(2,4,6)
109      for metamethod,values in pairs({
110        __tostring = { tostring(c), "Vector2[1,2,3]" },
111        __eq =       { c, c },
112        __lt =       { c<d,  true },
113        __le =       { c<=d, true },
114        __add =      { c+d,  Vector(3,6,9) },
115        __sub =      { d-c,  Vector(1,2,3) },
116        __div =      { d/2,  Vector(1,2,3) },
117        __unm =      { -c,   Vector(-1,-2,-3) },
118        __concat =   { c..d, 28 },
119        __call =     { c(), math.sqrt(14) },
120        __pow =      { c^d,  Vector(0,0,0) },
121        __mul =      { 4*c,  Vector(4,8,12) },
122      }) do
123        describe(metamethod, function()
124          it('works as expected', function()
125            assert.equal(values[1], values[2])
126          end)
127        end)
128      end
129
130      if is_lua_5_2_compatible() then
131
132        describe('__len', function()
133          it('works as expected', function()
134            assert.equal(#c, 3)
135          end)
136        end)
137
138        describe('__pairs', function()
139          it('works as expected',function()
140            local output = {}
141            for k,v in pairs(c) do
142              output[k] = v
143            end
144            assert.are.same(output,{x=1,y=2,z=3})
145          end)
146        end)
147
148        describe('__ipairs', function()
149          it('works as expected', function()
150            local output = {}
151            for _,i in ipairs(c) do
152              output[#output+1] = i
153            end
154            assert.are.same(output,{1,2,3})
155          end)
156        end)
157
158      end
159
160    end)
161
162  end)
163
164  describe('Default Metamethods', function()
165
166    local Peter, peter
167
168    before_each(function()
169      Peter = class('Peter')
170      peter = Peter()
171    end)
172
173    describe('A Class', function()
174      it('has a call metamethod properly set', function()
175        assert.is_true(peter:isInstanceOf(Peter))
176      end)
177      it('has a tostring metamethod properly set', function()
178        assert.equal(tostring(Peter), 'class Peter')
179      end)
180    end)
181
182    describe('An instance', function()
183      it('has a tostring metamethod, returning a different result from Object.__tostring', function()
184        assert.not_equal(Peter.__tostring, Object.__tostring)
185        assert.equal(tostring(peter), 'instance of class Peter')
186      end)
187    end)
188  end)
189
190end)
191