lebowski 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|