lebowski 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +3 -0
- data/License.txt +20 -0
- data/Manifest.txt +84 -0
- data/README.md +146 -0
- data/Rakefile +42 -0
- data/bin/lebowski +26 -0
- data/bin/lebowski-spec +29 -0
- data/bin/lebowski-start-server +24 -0
- data/lib/lebowski.rb +15 -0
- data/lib/lebowski/core.rb +35 -0
- data/lib/lebowski/foundation.rb +52 -0
- data/lib/lebowski/foundation/application.rb +315 -0
- data/lib/lebowski/foundation/core.rb +61 -0
- data/lib/lebowski/foundation/core_query.rb +231 -0
- data/lib/lebowski/foundation/dom_element.rb +114 -0
- data/lib/lebowski/foundation/errors/argument_invalid_type.rb +31 -0
- data/lib/lebowski/foundation/errors/unexpected_type.rb +30 -0
- data/lib/lebowski/foundation/mixins/collection_item_view_support.rb +87 -0
- data/lib/lebowski/foundation/mixins/delegate_support.rb +22 -0
- data/lib/lebowski/foundation/mixins/inline_text_field_support.rb +29 -0
- data/lib/lebowski/foundation/mixins/key_check.rb +35 -0
- data/lib/lebowski/foundation/mixins/list_item_view_support.rb +36 -0
- data/lib/lebowski/foundation/mixins/positioned_element.rb +20 -0
- data/lib/lebowski/foundation/mixins/stall_support.rb +79 -0
- data/lib/lebowski/foundation/mixins/user_actions.rb +302 -0
- data/lib/lebowski/foundation/mixins/wait_actions.rb +44 -0
- data/lib/lebowski/foundation/object_array.rb +305 -0
- data/lib/lebowski/foundation/panes/alert.rb +117 -0
- data/lib/lebowski/foundation/panes/main.rb +20 -0
- data/lib/lebowski/foundation/panes/menu.rb +100 -0
- data/lib/lebowski/foundation/panes/modal.rb +21 -0
- data/lib/lebowski/foundation/panes/palette.rb +21 -0
- data/lib/lebowski/foundation/panes/pane.rb +24 -0
- data/lib/lebowski/foundation/panes/panel.rb +25 -0
- data/lib/lebowski/foundation/panes/picker.rb +43 -0
- data/lib/lebowski/foundation/panes/sheet.rb +21 -0
- data/lib/lebowski/foundation/proxy_factory.rb +87 -0
- data/lib/lebowski/foundation/proxy_object.rb +670 -0
- data/lib/lebowski/foundation/sc_object.rb +38 -0
- data/lib/lebowski/foundation/views/button.rb +20 -0
- data/lib/lebowski/foundation/views/checkbox.rb +63 -0
- data/lib/lebowski/foundation/views/collection.rb +304 -0
- data/lib/lebowski/foundation/views/container.rb +32 -0
- data/lib/lebowski/foundation/views/disclosure.rb +59 -0
- data/lib/lebowski/foundation/views/grid.rb +21 -0
- data/lib/lebowski/foundation/views/label.rb +30 -0
- data/lib/lebowski/foundation/views/list.rb +67 -0
- data/lib/lebowski/foundation/views/list_item.rb +280 -0
- data/lib/lebowski/foundation/views/menu_item.rb +27 -0
- data/lib/lebowski/foundation/views/radio.rb +32 -0
- data/lib/lebowski/foundation/views/segmented.rb +97 -0
- data/lib/lebowski/foundation/views/select_field.rb +139 -0
- data/lib/lebowski/foundation/views/support/simple_item_array.rb +249 -0
- data/lib/lebowski/foundation/views/text_field.rb +108 -0
- data/lib/lebowski/foundation/views/view.rb +108 -0
- data/lib/lebowski/runtime.rb +7 -0
- data/lib/lebowski/runtime/errors/remote_control_command_execution_error.rb +9 -0
- data/lib/lebowski/runtime/errors/remote_control_command_timeout_error.rb +9 -0
- data/lib/lebowski/runtime/errors/remote_control_error.rb +9 -0
- data/lib/lebowski/runtime/errors/selenium_server_error.rb +9 -0
- data/lib/lebowski/runtime/object_encoder.rb +123 -0
- data/lib/lebowski/runtime/sprout_core_driver.rb +14 -0
- data/lib/lebowski/runtime/sprout_core_extensions.rb +600 -0
- data/lib/lebowski/scui.rb +18 -0
- data/lib/lebowski/scui/mixins/node_item_view_support.rb +136 -0
- data/lib/lebowski/scui/mixins/terminal_view_support.rb +25 -0
- data/lib/lebowski/scui/views/combo_box.rb +119 -0
- data/lib/lebowski/scui/views/date_picker.rb +148 -0
- data/lib/lebowski/scui/views/linkit.rb +36 -0
- data/lib/lebowski/spec.rb +17 -0
- data/lib/lebowski/spec/core.rb +21 -0
- data/lib/lebowski/spec/matchers/be.rb +63 -0
- data/lib/lebowski/spec/matchers/has.rb +40 -0
- data/lib/lebowski/spec/matchers/match_supporters/has_object_function.rb +67 -0
- data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_no_prefix.rb +50 -0
- data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_prefix_has.rb +50 -0
- data/lib/lebowski/spec/matchers/match_supporters/match_supporter.rb +29 -0
- data/lib/lebowski/spec/matchers/method_missing.rb +24 -0
- data/lib/lebowski/spec/operators/operator.rb +20 -0
- data/lib/lebowski/spec/operators/that.rb +116 -0
- data/lib/lebowski/spec/util.rb +26 -0
- data/lib/lebowski/version.rb +17 -0
- data/resources/selenium-server.jar +0 -0
- data/resources/user-extensions.js +1421 -0
- metadata +198 -0
@@ -0,0 +1,670 @@
|
|
1
|
+
# ==========================================================================
|
2
|
+
# Project: Lebowski Framework - The SproutCore Test Automation Framework
|
3
|
+
# License: Licensed under MIT license (see License.txt)
|
4
|
+
# ==========================================================================
|
5
|
+
|
6
|
+
module Lebowski
|
7
|
+
module Foundation
|
8
|
+
|
9
|
+
#
|
10
|
+
# ProxyObject is the root object for all objects that are to proxy an object within a
|
11
|
+
# web brower. This provides all of the core functionality allowing you to communicate with
|
12
|
+
# a remote object and access other remote objects through the use of relative property paths.
|
13
|
+
#
|
14
|
+
# In the case where you have created a custom SproutCore object and want to have a proxy for it
|
15
|
+
# then your proxy must inherit from the SCObject class that inherits this class.
|
16
|
+
#
|
17
|
+
class ProxyObject
|
18
|
+
|
19
|
+
include Lebowski::Foundation
|
20
|
+
include Lebowski::Foundation::Mixins::WaitActions
|
21
|
+
|
22
|
+
attr_reader :parent, # The parent object of this object. Must derive from Lebowski::Foundation::ProxyObject
|
23
|
+
:rel_path, # The relative path to the remote object using SC property path notation
|
24
|
+
:driver # The SproutCore driver that is used to actually communicate with the remote object
|
25
|
+
|
26
|
+
attr_accessor :name # A name for this object. Useful for printing out statements and debugging
|
27
|
+
|
28
|
+
#
|
29
|
+
# Creates a new proxy object instance.
|
30
|
+
#
|
31
|
+
# Try to refrain from overriding this method. Instead, if you wish to perform some operations
|
32
|
+
# during the time an object is being initialized, override the init_ext method
|
33
|
+
#
|
34
|
+
# @param parent {Object} parent object of this object. Must inherit from Lebowski::Foundation::ProxyObject
|
35
|
+
# @param rel_path {String} a relative path to the remote object (e.g. 'foo', 'foo.bar')
|
36
|
+
# @param driver {Object} used to remotely communicate with the remote object.
|
37
|
+
#
|
38
|
+
# @see #init_ext
|
39
|
+
#
|
40
|
+
def initialize(parent=nil, rel_path=nil, driver=nil)
|
41
|
+
|
42
|
+
if not init_expected_parent_type.nil?
|
43
|
+
if not parent.kind_of? init_expected_parent_type
|
44
|
+
raise ArgumentInvalidTypeError.new "parent", parent, init_expected_parent_type
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@parent = parent
|
49
|
+
@rel_path = rel_path
|
50
|
+
@driver = driver
|
51
|
+
@guid = nil
|
52
|
+
@defined_paths = {}
|
53
|
+
@defined_proxies = {}
|
54
|
+
@name = ""
|
55
|
+
|
56
|
+
init_ext()
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Override this method for any initialization procedures during the time the object is being
|
61
|
+
# inialized
|
62
|
+
#
|
63
|
+
# @see #initialize
|
64
|
+
#
|
65
|
+
def init_ext()
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Use when you wish to represent a proxied object as something else other then the proxy
|
71
|
+
# returned by this object when accessed with a relative path using []. This is useful
|
72
|
+
# in cases where you have a view that is simply composed of other views but itself is not
|
73
|
+
# custom view inherited from SC.View. As an example, it is common in SproutCore to
|
74
|
+
# create a complex view that lives only within an SC.Page, like so:
|
75
|
+
#
|
76
|
+
# MyApp.mainPage = SC.Page.create({
|
77
|
+
#
|
78
|
+
# composedView: SC.View.design({
|
79
|
+
# layout: { top: 0, bottom: 0, left: 0, right: 0 },
|
80
|
+
# childViews: 'buttonOne buttonTwo statusLabel'.w(),
|
81
|
+
#
|
82
|
+
# buttonOne: SC.ButtonView.design({
|
83
|
+
# ...
|
84
|
+
# }),
|
85
|
+
#
|
86
|
+
# buttonTwo: SC.ButtonView.design({
|
87
|
+
# ...
|
88
|
+
# }),
|
89
|
+
#
|
90
|
+
# statusLabel: SC.LabelView.design({
|
91
|
+
# ...
|
92
|
+
# })
|
93
|
+
# })
|
94
|
+
#
|
95
|
+
# })
|
96
|
+
#
|
97
|
+
# Since the root view (composedView) is just a basic SC.View, accessing it using
|
98
|
+
# the proxy's [] convention would just give you back a basic View proxy. This then
|
99
|
+
# means you have to access the child views explicitly every time you want to interact
|
100
|
+
# with the composed view. This can be brittle since the view's structure can change.
|
101
|
+
# Instead you can make a proxy to abstract away the interal structure and make it
|
102
|
+
# easier to work with the view. Therefore, we could make a proxy as follows:
|
103
|
+
#
|
104
|
+
# ComposedView < Lebowski::Foundation::Views::View
|
105
|
+
#
|
106
|
+
# def click_button_one()
|
107
|
+
# self['buttonOne'].click
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# def click_button_two()
|
111
|
+
# self['buttonTwo'].click
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# def status()
|
115
|
+
# return self['statusLabel.value']
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# With the proxy above, you can then do the following:
|
121
|
+
#
|
122
|
+
# view = App['mainPage.composedView', View]
|
123
|
+
# view = view.represent_as(ComposedView)
|
124
|
+
# view.click_button_one
|
125
|
+
# status = view.status
|
126
|
+
#
|
127
|
+
def represent_as(type)
|
128
|
+
if not (type.kind_of?(Class) and type.ancestors.member?(ProxyObject))
|
129
|
+
raise ArgumentInvalidTypeError.new "type", type, 'class < ProxyObject'
|
130
|
+
end
|
131
|
+
|
132
|
+
obj = type.new @parent, @rel_path, @driver
|
133
|
+
return obj
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Returns the absolute path of this object based on the parent object heirarchy. As
|
138
|
+
# an example, this object's parent has an absolute path of 'foo' and this object has
|
139
|
+
# relative path of 'bar', then the absolute path will be 'foo.bar'
|
140
|
+
#
|
141
|
+
def abs_path()
|
142
|
+
if not @abs_path.nil?
|
143
|
+
return @abs_path
|
144
|
+
end
|
145
|
+
|
146
|
+
if @parent.nil? or @parent.abs_path.nil?
|
147
|
+
return @rel_path
|
148
|
+
end
|
149
|
+
|
150
|
+
@abs_path = "#{@parent.abs_path}.#{rel_path}"
|
151
|
+
|
152
|
+
return @abs_path
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Returns the absolute path given a relative path. Say, for example, that a
|
157
|
+
# proxy object has an absolute path of 'mainPage.mainPane.someView'. When
|
158
|
+
# given a relative path of 'foo.bar', the returned value would be:
|
159
|
+
#
|
160
|
+
# 'mainPage.mainPane.someView.foo.bar'
|
161
|
+
#
|
162
|
+
def abs_path_with(rel_path)
|
163
|
+
path = abs_path
|
164
|
+
return rel_path if path.nil?
|
165
|
+
return "#{path}.#{rel_path}"
|
166
|
+
end
|
167
|
+
|
168
|
+
def define(key, rel_path, expected_type=nil)
|
169
|
+
if (not key.kind_of?(String)) or key.empty? or (not key.match(/[\. ]/).nil?)
|
170
|
+
raise ArgumentError.raise "key must be a valid string"
|
171
|
+
end
|
172
|
+
|
173
|
+
if (not rel_path.kind_of?(String)) or rel_path.empty?
|
174
|
+
raise ArgumentError.raise "rel_path must be a valid string"
|
175
|
+
end
|
176
|
+
|
177
|
+
if @defined_paths.has_key? key
|
178
|
+
raise ArgumentError.raise "key '#{key}' already defined as path '#{@defined_paths[key]}'"
|
179
|
+
end
|
180
|
+
|
181
|
+
first_path_part = rel_path_first_part(rel_path)
|
182
|
+
sub_path = rel_path_sub_path(rel_path, first_path_part)
|
183
|
+
|
184
|
+
type = ""
|
185
|
+
err_abs_path = abs_path_with(rel_path)
|
186
|
+
|
187
|
+
if @defined_paths.has_key? first_path_part
|
188
|
+
obj = @defined_paths[first_path_part]
|
189
|
+
type = obj.sc_type_of(sub_path)
|
190
|
+
err_abs_path = obj.abs_path_with(sub_path)
|
191
|
+
else
|
192
|
+
type = sc_type_of(rel_path)
|
193
|
+
end
|
194
|
+
|
195
|
+
if not (type == SC_T_OBJECT or type == SC_T_HASH)
|
196
|
+
err_msg = "Error trying to define key '#{key}'. "
|
197
|
+
err_msg << "Relative path '#{rel_path}' does not point to an object. "
|
198
|
+
err_msg << "Path is refencing: #{type}. Absolute path = #{err_abs_path}"
|
199
|
+
raise ArgumentError.new err_msg
|
200
|
+
end
|
201
|
+
|
202
|
+
obj = self[rel_path, expected_type]
|
203
|
+
|
204
|
+
@defined_paths[key] = obj
|
205
|
+
|
206
|
+
return obj
|
207
|
+
end
|
208
|
+
|
209
|
+
def defined()
|
210
|
+
return @defined_paths.clone if (not @defined_paths.nil?)
|
211
|
+
return {}
|
212
|
+
end
|
213
|
+
|
214
|
+
def proxy(klass, rel_path)
|
215
|
+
obj = self[rel_path]
|
216
|
+
if not obj.kind_of?(ProxyObject)
|
217
|
+
raise ArgumentError.new "rel_path does not point to an object that can be proxied: #{obj} (#{obj.class})"
|
218
|
+
end
|
219
|
+
|
220
|
+
if not (klass.kind_of?(Class) and klass.ancestors.member?(ProxyObject))
|
221
|
+
raise ArgumentInvalidTypeError.new "klass", klass, 'class < ProxyObject'
|
222
|
+
end
|
223
|
+
|
224
|
+
@defined_proxies[rel_path] = obj.represent_as(klass)
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# Given a relative path, unravel it to access an object. Unraveling means to take
|
229
|
+
# any defined paths in the given relative path and convert the entire path back
|
230
|
+
# into a full relative path without definitions.
|
231
|
+
#
|
232
|
+
def unravel_relative_path(rel_path)
|
233
|
+
if not @defined_proxies[rel_path].nil?
|
234
|
+
return @defined_proxies[rel_path]
|
235
|
+
end
|
236
|
+
|
237
|
+
first_path_part = rel_path_first_part(rel_path)
|
238
|
+
sub_path = rel_path_sub_path(rel_path, first_path_part)
|
239
|
+
|
240
|
+
return rel_path if (not @defined_paths.has_key?(first_path_part))
|
241
|
+
|
242
|
+
obj = @defined_paths[first_path_part]
|
243
|
+
return obj if sub_path.empty?
|
244
|
+
|
245
|
+
result = obj.unravel_relative_path(sub_path)
|
246
|
+
|
247
|
+
return "#{obj.rel_path}.#{result}" if result.kind_of?(String)
|
248
|
+
return result if result.kind_of?(Lebowski::Foundation::SCObject)
|
249
|
+
|
250
|
+
raise StandardError.new "Unexpected result unreeling rel path: #{result}"
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# Gets the remote SproutCore GUID for this object
|
255
|
+
#
|
256
|
+
def sc_guid()
|
257
|
+
# We only need to fetch the remote GUID once since it never changes for a given instance
|
258
|
+
@guid = @driver.get_sc_guid(abs_path) if @guid.nil?
|
259
|
+
return @guid
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Gets the remote SC class name for this object
|
264
|
+
#
|
265
|
+
def sc_class()
|
266
|
+
# We only need to fetch the remote SC class name once since it never changes for a given instance
|
267
|
+
@class_name = @driver.get_sc_object_class_name(abs_path) if @class_name.nil?
|
268
|
+
return @class_name
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# Gets all the remote SproutCore classes that the proxied object derives from. This will return
|
273
|
+
# an array of strings representing the names of the classes. As an example, if the
|
274
|
+
# proxy was communicating with an object that was of type SC.ButtonView then the
|
275
|
+
# result would be the following:
|
276
|
+
#
|
277
|
+
# ['SC.ButtonView', 'SC.View', 'SC.Object']
|
278
|
+
#
|
279
|
+
# The last item in the array is always 'SC.Object' since that is the root object for all
|
280
|
+
# SproutCore objects.
|
281
|
+
#
|
282
|
+
def sc_all_classes()
|
283
|
+
@all_class_names = @driver.get_sc_object_class_names(abs_path) if @all_class_names.nil?
|
284
|
+
return @all_class_names
|
285
|
+
end
|
286
|
+
|
287
|
+
#
|
288
|
+
# Checks if the remote proxied object is a kind of given SC class
|
289
|
+
#
|
290
|
+
def sc_kind_of?(type)
|
291
|
+
if not (type.kind_of?(Class) or type.kind_of?(String))
|
292
|
+
raise ArgumentInvalidTypeError.new "type", type, 'class < SCObject', String
|
293
|
+
end
|
294
|
+
|
295
|
+
if type.kind_of?(Class) and type.ancestors.member?(SCObject)
|
296
|
+
type = type.represented_sc_class
|
297
|
+
end
|
298
|
+
|
299
|
+
type = type.downcase
|
300
|
+
result = sc_all_classes.detect do |val|
|
301
|
+
val.downcase == type
|
302
|
+
end
|
303
|
+
return (not result.nil?)
|
304
|
+
end
|
305
|
+
|
306
|
+
def sc_type_of(rel_path)
|
307
|
+
return @driver.get_sc_type_of(abs_path_with(rel_path))
|
308
|
+
end
|
309
|
+
|
310
|
+
def sc_path_defined?(rel_path)
|
311
|
+
return (not sc_type_of(rel_path) == SC_T_UNDEFINED)
|
312
|
+
end
|
313
|
+
|
314
|
+
def none?(rel_path)
|
315
|
+
type = sc_type_of(rel_path)
|
316
|
+
return (type == SC_T_UNDEFINED or type == SC_T_NULL)
|
317
|
+
end
|
318
|
+
|
319
|
+
def object?(rel_path)
|
320
|
+
type = sc_type_of(rel_path)
|
321
|
+
return (type == SC_T_OBJECT or type == SC_T_HASH)
|
322
|
+
end
|
323
|
+
|
324
|
+
#
|
325
|
+
# The primary method used to access a proxied object's properties. Accessing
|
326
|
+
# a property is done using a relative property path. The path is a chain of
|
327
|
+
# properties connected using dots '.'. The type is automatically determined, but
|
328
|
+
# in cases where a particular type is expected, you can optionally supply what the
|
329
|
+
# expected type should be.
|
330
|
+
#
|
331
|
+
# As an example, to access an object's property called 'foo', you can do the
|
332
|
+
# following:
|
333
|
+
#
|
334
|
+
# value = object['foo']
|
335
|
+
#
|
336
|
+
# If you expect the value's type to be, say, an number, you can do the following:
|
337
|
+
#
|
338
|
+
# value = object['foo', :number]
|
339
|
+
#
|
340
|
+
# In the case where you expect the value to be a type of object you can do one
|
341
|
+
# of the following:
|
342
|
+
#
|
343
|
+
# value = object['foo', 'SC.SomeObject']
|
344
|
+
#
|
345
|
+
# value = object['foo', SomeObject]
|
346
|
+
#
|
347
|
+
# In the first case, you are supply the object type as a string, in the second case
|
348
|
+
# you are supplying the expected type with a proxy class. For the second option to
|
349
|
+
# work you must first supply the proxy to the proxy factory.
|
350
|
+
#
|
351
|
+
# To access a property through a chain of objects, you supply a relative path, like
|
352
|
+
# so:
|
353
|
+
#
|
354
|
+
# value = object['path.to.some.property']
|
355
|
+
#
|
356
|
+
# Remember that the path is relative to the object you passed the path to. The approach
|
357
|
+
# is used to work with how you would normally access properties using the SproutCore
|
358
|
+
# framework.
|
359
|
+
#
|
360
|
+
# The fundamental types detected within the web browser are the following:
|
361
|
+
#
|
362
|
+
# Null - null in JavaScript
|
363
|
+
# Error - SproutCore error object
|
364
|
+
# String - string in JavaScript
|
365
|
+
# Number - number in JavaScript
|
366
|
+
# Boolean - boolean in JavaScript
|
367
|
+
# Hash - a JavaScript hash object
|
368
|
+
# Object - a SproutCore object
|
369
|
+
# Array - a JavaScript array
|
370
|
+
# Class - A SproutCore class
|
371
|
+
#
|
372
|
+
# Based on the value's type within the browser, this method will translate the value as
|
373
|
+
# follows:
|
374
|
+
#
|
375
|
+
# Null -> nil
|
376
|
+
# Error -> :error
|
377
|
+
# String -> standard string
|
378
|
+
# Number -> standard number
|
379
|
+
# Boolean -> standard boolean
|
380
|
+
# Hash -> a generic proxy object
|
381
|
+
# Object -> closest matching proxy object
|
382
|
+
# Array -> standard array for basic types; an object array (ObjectArray) for objects
|
383
|
+
# Class -> a generic proxy object
|
384
|
+
#
|
385
|
+
# If the given relative path tries to reference a property that is not defined then :undefined
|
386
|
+
# is returned.
|
387
|
+
#
|
388
|
+
# The two special cases are when the basic type of the relative path is a SproutCore object or
|
389
|
+
# an array. In the case of a SproutCore object, the closest matching object type will be returned
|
390
|
+
# based on what proxies have been provided to the proxy factory. For instance, let's say you have
|
391
|
+
# custom view that derives from SC.View. If no proxy has been made for the custom view then
|
392
|
+
# the next closest proxy will be returned, which would be a View proxy that is already part
|
393
|
+
# of the lebowski framework. If your require a proxy to interact with the custom view then you
|
394
|
+
# need to add that proxy to the proxy framework.
|
395
|
+
#
|
396
|
+
# When the type is an array, the proxy object will check the content of the array to determine
|
397
|
+
# their type. If all the content in the array are of the same type then it will return a
|
398
|
+
# corresponding array made up content with that type. So, for example, if an object has
|
399
|
+
# a property that is an array of strings then a basic array of string will be returned. In the
|
400
|
+
# case where the array contains either hash objects or SproutCore objects then an ObjectArray
|
401
|
+
# will be returned.
|
402
|
+
#
|
403
|
+
def [](rel_path, expected_type=nil)
|
404
|
+
|
405
|
+
result = unravel_relative_path(rel_path)
|
406
|
+
|
407
|
+
if (not result.kind_of?(String))
|
408
|
+
if (not expected_type.nil?)
|
409
|
+
got_expected_type = (expected_type == :object or result.sc_kind_of?(expected_type))
|
410
|
+
if (not got_expected_type)
|
411
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, "object", result)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
return result
|
415
|
+
end
|
416
|
+
|
417
|
+
rel_path = result
|
418
|
+
type = sc_type_of(rel_path)
|
419
|
+
|
420
|
+
case type
|
421
|
+
when SC_T_NULL
|
422
|
+
return handle_type_null(rel_path, expected_type)
|
423
|
+
|
424
|
+
when SC_T_UNDEFINED
|
425
|
+
return handle_type_undefined(rel_path, expected_type)
|
426
|
+
|
427
|
+
when SC_T_ERROR
|
428
|
+
return handle_type_error(rel_path, expected_type)
|
429
|
+
|
430
|
+
when SC_T_STRING
|
431
|
+
return handle_type_string(rel_path, expected_type)
|
432
|
+
|
433
|
+
when SC_T_NUMBER
|
434
|
+
return handle_type_number(rel_path, expected_type)
|
435
|
+
|
436
|
+
when SC_T_BOOL
|
437
|
+
return handle_type_bool(rel_path, expected_type)
|
438
|
+
|
439
|
+
when SC_T_ARRAY
|
440
|
+
return handle_type_array(rel_path, expected_type)
|
441
|
+
|
442
|
+
when SC_T_HASH
|
443
|
+
return handle_type_hash(rel_path, expected_type)
|
444
|
+
|
445
|
+
when SC_T_OBJECT
|
446
|
+
return handle_type_object(rel_path, expected_type)
|
447
|
+
|
448
|
+
when SC_T_CLASS
|
449
|
+
return handle_type_class(rel_path, expected_type)
|
450
|
+
|
451
|
+
else
|
452
|
+
raise StandardError.new "Unrecognized returned type '#{type}' for path #{abs_path_with(rel_path)}"
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
#
|
457
|
+
# Override the == operator so that a proxy object can be compared to another
|
458
|
+
# proxy object via their SproutCore GUIDs
|
459
|
+
#
|
460
|
+
def ==(obj)
|
461
|
+
return (self.sc_guid == obj.sc_guid) if obj.kind_of?(ProxyObject)
|
462
|
+
return super(obj)
|
463
|
+
end
|
464
|
+
|
465
|
+
alias_method :eql?, :==
|
466
|
+
|
467
|
+
#
|
468
|
+
# Override method_missing so that we can access a proxied object's properties using
|
469
|
+
# a more conventional Ruby approach. So instead of accessing an object's property
|
470
|
+
# using the [] convention, we can instead do the following:
|
471
|
+
#
|
472
|
+
# value = proxied_object.foo # compared to proxied_object['foo']
|
473
|
+
#
|
474
|
+
# This will also translate the name of property into camel case that is normally
|
475
|
+
# used in JavaScript. So, if an object in JavaScript has a property with the
|
476
|
+
# name 'fooBar', you can access that property using the standard Ruby convention
|
477
|
+
# like so:
|
478
|
+
#
|
479
|
+
# value = proxied_object.foo_bar
|
480
|
+
#
|
481
|
+
# It will be converted back into 'fooBar'. If the property does not exist
|
482
|
+
# on the proxied object then an exception will be thrown. If you want to access
|
483
|
+
# property without an exception being thrown then use the [] convention using
|
484
|
+
# a relative property path string.
|
485
|
+
#
|
486
|
+
def method_missing(sym, *args, &block)
|
487
|
+
if (not sym.to_s =~ /\?$/) and (args.length == 0)
|
488
|
+
camel_case = to_camel_case(sym.to_s)
|
489
|
+
return self[camel_case] if sc_path_defined?(camel_case)
|
490
|
+
end
|
491
|
+
super
|
492
|
+
end
|
493
|
+
|
494
|
+
private
|
495
|
+
|
496
|
+
def init_expected_parent_type()
|
497
|
+
return nil
|
498
|
+
end
|
499
|
+
|
500
|
+
def rel_path_first_part(rel_path)
|
501
|
+
first_path_part = rel_path.match(/^((\w|-)*)\./)
|
502
|
+
first_path_part = rel_path if first_path_part.nil?
|
503
|
+
first_path_part = first_path_part[1] if first_path_part.kind_of?(MatchData)
|
504
|
+
return first_path_part
|
505
|
+
end
|
506
|
+
|
507
|
+
def rel_path_sub_path(rel_path, first_path_part)
|
508
|
+
sub_path = (first_path_part != rel_path) ? rel_path.sub(/^(\w|-)*\./, "") : ""
|
509
|
+
return sub_path
|
510
|
+
end
|
511
|
+
|
512
|
+
def handle_type_null(rel_path, expected_type)
|
513
|
+
if (not expected_type.nil?) and not (expected_type == :null)
|
514
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_NULL, :null)
|
515
|
+
end
|
516
|
+
|
517
|
+
return nil
|
518
|
+
end
|
519
|
+
|
520
|
+
def handle_type_undefined(rel_path, expected_type)
|
521
|
+
if (not expected_type.nil?) and not (expected_type == :undefined)
|
522
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_UNDEFINED, :undefined)
|
523
|
+
end
|
524
|
+
|
525
|
+
return :undefined
|
526
|
+
end
|
527
|
+
|
528
|
+
def handle_type_error(rel_path, expected_type)
|
529
|
+
if (not expected_type.nil?) and not (expected_type == :error)
|
530
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_ERROR, :error)
|
531
|
+
end
|
532
|
+
|
533
|
+
return :error
|
534
|
+
end
|
535
|
+
|
536
|
+
def handle_type_bool(rel_path, expected_type)
|
537
|
+
value = @driver.get_sc_path_boolean_value(abs_path_with(rel_path))
|
538
|
+
|
539
|
+
if (not expected_type.nil?) and not (expected_type == :boolean or expected_type == :bool)
|
540
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_BOOL, value)
|
541
|
+
end
|
542
|
+
|
543
|
+
return value
|
544
|
+
end
|
545
|
+
|
546
|
+
def handle_type_string(rel_path, expected_type)
|
547
|
+
value = @driver.get_sc_path_string_value(abs_path_with(rel_path))
|
548
|
+
|
549
|
+
if (not expected_type.nil?) and not (expected_type == :string)
|
550
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_STRING, value)
|
551
|
+
end
|
552
|
+
|
553
|
+
return value
|
554
|
+
end
|
555
|
+
|
556
|
+
def handle_type_number(rel_path, expected_type)
|
557
|
+
value = @driver.get_sc_path_number_value(abs_path_with(rel_path))
|
558
|
+
|
559
|
+
if (not expected_type.nil?) and not (expected_type == :number)
|
560
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_NUMBER, value)
|
561
|
+
end
|
562
|
+
|
563
|
+
return value
|
564
|
+
end
|
565
|
+
|
566
|
+
def handle_type_class(rel_path, expected_type)
|
567
|
+
# TODO: Need to handle this case better
|
568
|
+
value = ProxyObject.new self, rel_path, @driver
|
569
|
+
|
570
|
+
if (not expected_type.nil?) and not (expected_type == :class)
|
571
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_CLASS, value)
|
572
|
+
end
|
573
|
+
|
574
|
+
return value
|
575
|
+
end
|
576
|
+
|
577
|
+
def handle_type_hash(rel_path, expected_type)
|
578
|
+
value = ProxyObject.new self, rel_path, @driver
|
579
|
+
|
580
|
+
if (not expected_type.nil?) and not (expected_type == :object or expected_type == :hash)
|
581
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_HASH, value)
|
582
|
+
end
|
583
|
+
|
584
|
+
return value
|
585
|
+
end
|
586
|
+
|
587
|
+
def handle_type_object(rel_path, expected_type)
|
588
|
+
|
589
|
+
value = nil
|
590
|
+
|
591
|
+
class_names = @driver.get_sc_object_class_names(abs_path_with(rel_path))
|
592
|
+
matching_class = class_names.detect { |name| ProxyFactory.has_key?(name) }
|
593
|
+
if matching_class.nil?
|
594
|
+
value = ProxyFactory.create_proxy(Lebowski::Foundation::SCObject, self, rel_path)
|
595
|
+
else
|
596
|
+
value = ProxyFactory.create_proxy(matching_class, self, rel_path)
|
597
|
+
end
|
598
|
+
|
599
|
+
if not expected_type.nil?
|
600
|
+
got_expected_type = (expected_type == :object or value.sc_kind_of?(expected_type))
|
601
|
+
if not got_expected_type
|
602
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_OBJECT, value)
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
return value
|
607
|
+
|
608
|
+
end
|
609
|
+
|
610
|
+
def handle_type_array(rel_path, expected_type)
|
611
|
+
content_type = @driver.get_sc_type_of_array_content(abs_path_with(rel_path))
|
612
|
+
|
613
|
+
got_expected_type = false
|
614
|
+
|
615
|
+
# TODO: This needs a better solution
|
616
|
+
case content_type
|
617
|
+
when SC_T_NUMBER
|
618
|
+
got_expected_type = (expected_type == :array or expected_type == :number_array)
|
619
|
+
value = @driver.get_sc_path_number_array_value(abs_path_with(rel_path))
|
620
|
+
when SC_T_STRING
|
621
|
+
got_expected_type = (expected_type == :array or expected_type == :string_array)
|
622
|
+
value = @driver.get_sc_path_string_array_value(abs_path_with(rel_path))
|
623
|
+
when SC_T_BOOL
|
624
|
+
got_expected_type = (expected_type == :array or expected_type == :boolean_array or expected_type == :bool_array)
|
625
|
+
value = @driver.get_sc_path_boolean_array_value(abs_path_with(rel_path))
|
626
|
+
when SC_T_HASH
|
627
|
+
got_expected_type = (expected_type == :array or expected_type == :object_array or expected_type == :hash_array)
|
628
|
+
value = ObjectArray.new self, rel_path
|
629
|
+
when SC_T_OBJECT
|
630
|
+
got_expected_type = (expected_type == :array or expected_type == :object_array)
|
631
|
+
value = ObjectArray.new self, rel_path
|
632
|
+
when "empty"
|
633
|
+
got_expected_type = (expected_type == :array)
|
634
|
+
value = []
|
635
|
+
else
|
636
|
+
got_expected_type = (expected_type == :array)
|
637
|
+
# TODO: Replace with correct logic. Temporary for now
|
638
|
+
value = []
|
639
|
+
end
|
640
|
+
|
641
|
+
if (not expected_type.nil?) and (not got_expected_type)
|
642
|
+
raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_ARRAY, value)
|
643
|
+
end
|
644
|
+
|
645
|
+
return value
|
646
|
+
end
|
647
|
+
|
648
|
+
#
|
649
|
+
# Will return a string in camel case format for any value that follows the Ruby
|
650
|
+
# variable and method naming convention (e.g. my_variable_name). As an example:
|
651
|
+
#
|
652
|
+
# Util.to_camel_case(:some_long_name) # => "someLongName"
|
653
|
+
# Util.to_camel_case("function_foo_bar") # => "functionFooBar"
|
654
|
+
#
|
655
|
+
def to_camel_case(value)
|
656
|
+
camel_case_str = ""
|
657
|
+
word_counter = 1
|
658
|
+
words = value.to_s.split('_')
|
659
|
+
return words[0] if words.length == 1
|
660
|
+
words.each do |word|
|
661
|
+
camel_case_str << ((word_counter == 1) ? word : word.sub(/./) { |s| s.upcase })
|
662
|
+
word_counter = word_counter.next
|
663
|
+
end
|
664
|
+
return camel_case_str
|
665
|
+
end
|
666
|
+
|
667
|
+
end
|
668
|
+
|
669
|
+
end
|
670
|
+
end
|