netzke-testing 0.9.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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +106 -0
  6. data/Rakefile +1 -0
  7. data/app/assets/javascripts/netzke/.keep +0 -0
  8. data/app/assets/javascripts/netzke/testing/helpers/actions.js.coffee +74 -0
  9. data/app/assets/javascripts/netzke/testing/helpers/expectations.js.coffee +14 -0
  10. data/app/assets/javascripts/netzke/testing/helpers/form.js.coffee +7 -0
  11. data/app/assets/javascripts/netzke/testing/helpers/grid.js.coffee +107 -0
  12. data/app/assets/javascripts/netzke/testing/helpers/queries.js.coffee +88 -0
  13. data/app/assets/vendor/javascripts/expect/expect.js +1253 -0
  14. data/app/assets/vendor/javascripts/mocha/mocha.js +5340 -0
  15. data/app/assets/vendor/stylesheets/mocha/mocha.css +231 -0
  16. data/app/controllers/.keep +0 -0
  17. data/app/controllers/netzke/netzke/testing_controller.rb +28 -0
  18. data/app/helpers/.keep +0 -0
  19. data/app/helpers/netzke_testing_helper.rb +2 -0
  20. data/app/mailers/.keep +0 -0
  21. data/app/models/.keep +0 -0
  22. data/app/views/.keep +0 -0
  23. data/app/views/layouts/netzke/testing.html.erb +35 -0
  24. data/app/views/netzke/index.html.erb +2 -0
  25. data/config/routes.rb +8 -0
  26. data/lib/netzke/testing/engine.rb +6 -0
  27. data/lib/netzke/testing/helpers.rb +47 -0
  28. data/lib/netzke/testing/version.rb +5 -0
  29. data/lib/netzke/testing.rb +22 -0
  30. data/lib/netzke-testing.rb +1 -0
  31. data/netzke-testing.gemspec +23 -0
  32. data/test/controllers/netzke_testing_controller_test.rb +9 -0
  33. data/test/dummy/README.rdoc +28 -0
  34. data/test/dummy/Rakefile +6 -0
  35. data/test/dummy/app/assets/images/.keep +0 -0
  36. data/test/dummy/app/assets/javascripts/application.js +13 -0
  37. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  38. data/test/dummy/app/components/foo.rb +6 -0
  39. data/test/dummy/app/controllers/application_controller.rb +5 -0
  40. data/test/dummy/app/controllers/concerns/.keep +0 -0
  41. data/test/dummy/app/helpers/application_helper.rb +2 -0
  42. data/test/dummy/app/mailers/.keep +0 -0
  43. data/test/dummy/app/models/.keep +0 -0
  44. data/test/dummy/app/models/concerns/.keep +0 -0
  45. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  46. data/test/dummy/bin/bundle +3 -0
  47. data/test/dummy/bin/rails +4 -0
  48. data/test/dummy/bin/rake +4 -0
  49. data/test/dummy/config/application.rb +24 -0
  50. data/test/dummy/config/boot.rb +5 -0
  51. data/test/dummy/config/database.yml +25 -0
  52. data/test/dummy/config/environment.rb +5 -0
  53. data/test/dummy/config/environments/development.rb +23 -0
  54. data/test/dummy/config/environments/production.rb +80 -0
  55. data/test/dummy/config/environments/test.rb +36 -0
  56. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  58. data/test/dummy/config/initializers/inflections.rb +16 -0
  59. data/test/dummy/config/initializers/mime_types.rb +5 -0
  60. data/test/dummy/config/initializers/secret_token.rb +12 -0
  61. data/test/dummy/config/initializers/session_store.rb +3 -0
  62. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  63. data/test/dummy/config/locales/en.yml +23 -0
  64. data/test/dummy/config/routes.rb +58 -0
  65. data/test/dummy/config.ru +4 -0
  66. data/test/dummy/lib/assets/.keep +0 -0
  67. data/test/dummy/log/.keep +0 -0
  68. data/test/dummy/public/404.html +58 -0
  69. data/test/dummy/public/422.html +58 -0
  70. data/test/dummy/public/500.html +57 -0
  71. data/test/dummy/public/favicon.ico +0 -0
  72. data/test/dummy/spec/javascripts/foo.js.coffee +3 -0
  73. data/test/helpers/netzke_testing_helper_test.rb +4 -0
  74. data/test/integration/navigation_test.rb +10 -0
  75. data/test/netzke_testing_test.rb +7 -0
  76. data/test/test_helper.rb +15 -0
  77. metadata +193 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f6a8e145a80cbd76fe5872a58d199b5c0c3f6002
4
+ data.tar.gz: e7bef8a910494ec36fc1d4979f01f990bb1baac7
5
+ SHA512:
6
+ metadata.gz: ecb548ed750a7cdaa03b3a8eb517524c220de8f4a0bc8f845a5a34ad8425773afdb3c8c595633d5d5d240752d2d827b8f3c825ebe06dfe972ed906b622acc3a2
7
+ data.tar.gz: 8db04d54ff7ebbd9c731297c783dc02ff7a2faea022512bdef617b49b01ef339ea22dc99cd23528c40641e2693db5b4c422a53bfdd105eefafcb1fc6e086d422
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in netzke-testing.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Max Gorin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Netzke Testing
2
+
3
+ This gem helps with development and testing of Netzke components. In parcticular, it helps you with:
4
+
5
+ * isolated component development
6
+ * client-side testing of components with Mocha and Expect.js
7
+
8
+ Usage:
9
+
10
+ gem 'netzke_testing'
11
+
12
+ ## Isolated component development
13
+
14
+ The gem implements a Rails engine, which (in development and test environments only) adds a route to load your
15
+ application's Netzke components individually, which can be useful for isolated development. Example (say, we have a
16
+ UserGrid component defined):
17
+
18
+ http://localhost:3000/netzke/components/UserGrid
19
+
20
+ This will load a view with UserGrid occupying the available window width, with default height of 400px. You can change
21
+ the height by providing the `height` parameter in the URL:
22
+
23
+ http://localhost:3000/netzke/components/UserGrid?height=600
24
+
25
+ ## Testing components with Mocha and Expect.js
26
+
27
+ Place the Mocha specs (written in Coffeescript) for your components inside `spec/javascripts` folder. An example spec
28
+ may look like this (in `spec/javascripts/user_grid.js.coffee`):
29
+
30
+ describe 'UserGrid', ->
31
+ it 'shows proper title', ->
32
+ grid = Ext.ComponentQuery.query('panel[id="user_grid"]')[0]
33
+ expect(grid.getHeader().title).to.eql 'Test component'
34
+
35
+ This spec can be run by appending the `spec` parameter to the url:
36
+
37
+ http://localhost:3000/netzke/components/UserGrid?spec=user_grid
38
+
39
+ Specs can be structured into directories. For example, let's say we have a namescope for admin components:
40
+
41
+ class Admin::UserGrid < Netzke::Basepack::Grid
42
+ end
43
+
44
+ It makes sense to put the corresponding specs in `spec/javascripts/admin/user_grid.js.coffee`. In this case, the URL
45
+ to run the Mocha specs will be:
46
+
47
+ http://localhost:3000/netzke/components/UserGrid?spec=admin/user_grid
48
+
49
+ ## Mocha spec helpers
50
+
51
+ The gem provides a number of helpers that may help you writing less code and make your specs look something like this:
52
+
53
+ describe 'UserGrid', ->
54
+ it 'allows instant removing of all users with a single button click', (done) ->
55
+ click button 'Remove all'
56
+ wait ->
57
+ expectToSee header 'Empty'
58
+ done()
59
+
60
+ Keep in mind the following:
61
+
62
+ * the current set of helpers is in flux, and may be drastically changed sooner than you may expect
63
+ * the helpers directly pollute the `window` namespace; if you decide you're better off without provided helpers,
64
+ specify 'no-helpers=true' as an extra URL parameter
65
+
66
+ See the [source code](TODO) for currently implemented helpers. Also, refer to other Netzke gems source code (like
67
+ netzke-core and netzke-basepack) to see examples using the helpers.
68
+
69
+ ## Testing with selenium webdriver
70
+
71
+ Generate the `netzke_mocha_spec.rb` file that will automatically run the specs that follow a certain naming convention:
72
+
73
+ rails g netzke_testing
74
+
75
+ This spec will pick up all the `*_spec.js.coffee` files from `spec/javascripts` folder and generate an `it` clause for
76
+ each of them. Let's say we want to create the spec for UserGrid. For this we name the spec file
77
+ `spec/javascripts/user_grid_spec.js.coffee`. And the other way around: when `netzke_mocha_spec.rb` finds a file called
78
+ `spec/javascripts/order_grid_spec.js.coffee`, it'll assume existance of `OrderGrid` component that should be tested.
79
+
80
+ ## Mixing client- and server-side testing code
81
+
82
+ Often we want to run some Ruby code before running the Mocha spec (e.g. to seed some test data using factories), or
83
+ after (e.g. to assert changes in the database). In this case you can create a RSpec spec that uses the `run_mocha_spec`
84
+ helper provided by the `netzke_testing` gem. Here's an example (in `spec/user_grid_spec.rb`):
85
+
86
+ require 'spec_helper'
87
+ feature GridWithDestructiveButton do
88
+ it 'allows instant removing of all records with a single button click', js: true do
89
+ 10.times { FactoryGirl.create :user }
90
+ User.count.should == 10
91
+ run_mocha_spec 'grid_with_destructive_button'
92
+ User.count.should == 0
93
+ end
94
+ end
95
+
96
+ The `run_mocha_spec` here will run a Mocha spec from `spec/grid_with_destructive_button.js.coffee`.
97
+
98
+ You can explicitely specify a component to run the spec on (in order to override the convention):
99
+
100
+ run_mocha_spec 'grid_with_destructive_button', component: 'UserGrid'
101
+
102
+ ---
103
+ Copyright (c) 2008-2013 [Max Gorin](https://twitter.com/uptomax), released under the MIT license (see LICENSE).
104
+
105
+ **Note** that Ext JS is licensed [differently](http://www.sencha.com/products/extjs/license/), and you may need to
106
+ purchase a commercial license in order to use it in your projects!
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
File without changes
@@ -0,0 +1,74 @@
1
+ Ext.Ajax.on 'beforerequest', ->
2
+ Netzke.ajaxCount = window.ajaxCount || 0
3
+ Netzke.ajaxCount += 1
4
+
5
+ Ext.Ajax.on 'requestcomplete', ->
6
+ Netzke.ajaxCount -= 1
7
+
8
+ Ext.apply window,
9
+ # Examples:
10
+ #
11
+ # wait ->
12
+ # afterAllAjaxActivityIsStopped()
13
+ #
14
+ # wait 2000, ->
15
+ # afterTwoSeconds()
16
+ wait: () ->
17
+ if typeof arguments[0] == 'function'
18
+ callback = arguments[0]
19
+ i = 0
20
+ id = setInterval ->
21
+ i += 1
22
+ if i >= 100
23
+ clearInterval(id)
24
+ callback.call()
25
+
26
+ # this way we ensure another 20ms cycle before we issue a callback
27
+ i = 100 if Netzke.ajaxCount == 0
28
+ , 100
29
+ else
30
+ delay = arguments[0]
31
+ callback = arguments[1]
32
+ setInterval ->
33
+ callback.call()
34
+ , delay
35
+
36
+ click: (cmp) ->
37
+ if Ext.isString(cmp)
38
+ throw "Could not locate " + cmp
39
+ else if (cmp.isXType) # is Ext component
40
+ if (cmp.isXType('tool'))
41
+ # a hack needed for tools
42
+ el = cmp.toolEl
43
+ else
44
+ el = cmp.getEl()
45
+
46
+ el.dom.click()
47
+ else if Ext.isElement(cmp)
48
+ cmp.click()
49
+
50
+ # Closes the first found window
51
+ closeWindow: ->
52
+ Ext.ComponentQuery.query("window[hidden=false]")[0].close()
53
+
54
+ select: (value, params, callback) ->
55
+ params ?= params
56
+ combo = params.in
57
+ if combo.isExpanded
58
+ combo.setValue combo.findRecordByDisplay value
59
+ combo.collapse()
60
+ else
61
+ combo.onTriggerClick()
62
+ if callback
63
+ wait ->
64
+ rec = combo.findRecordByDisplay value
65
+ combo.select rec
66
+ combo.fireEvent('select', combo, rec )
67
+ combo.collapse()
68
+ callback.call()
69
+ else
70
+ rec = combo.findRecordByDisplay value
71
+ combo.select rec
72
+ combo.fireEvent('select', combo, rec )
73
+ combo.collapse()
74
+
@@ -0,0 +1,14 @@
1
+ Ext.apply window,
2
+ expectToSee: (el) ->
3
+ expect(Ext.isObject(el) || Ext.isElement(el)).to.be.ok()
4
+
5
+ expectToNotSee: (el) ->
6
+ expect(Ext.isString(el)).to.be.ok()
7
+
8
+ expectDisabled: (cmp) ->
9
+ throw cmp + " not found" if Ext.isString(cmp)
10
+ expect(cmp.isDisabled()).to.be(true)
11
+
12
+ expectInvisibleBodyOf: (cmp) ->
13
+ throw cmp + " not found" if Ext.isString(cmp)
14
+ expect(cmp.body.isVisible()).to.be false
@@ -0,0 +1,7 @@
1
+ Ext.apply window,
2
+ fill: (field, params) ->
3
+ field.setValue(params.with)
4
+
5
+ expandCombo: (combo) ->
6
+ combo = Ext.ComponentQuery.query("combo{isVisible(true)}[name="+combo+"]")[0]
7
+ combo.onTriggerClick()
@@ -0,0 +1,107 @@
1
+ Ext.apply window,
2
+ grid: (title) ->
3
+ if title
4
+ Ext.ComponentQuery.query('grid[title="'+title+'"]')[0]
5
+ else
6
+ Ext.ComponentQuery.query('grid{isVisible(true)}')[0]
7
+
8
+ expandRowCombo: (field, params) ->
9
+ g = g || this.grid()
10
+ editor = g.getPlugin('celleditor')
11
+ column = g.headerCt.items.findIndex('name', field) - 1
12
+ window.editor = editor
13
+ editor.startEditByPosition({row: g.getSelectionModel().getCurrentPosition().row, column: column})
14
+ editor.activeEditor.field.onTriggerClick()
15
+
16
+ # Example:
17
+ # addRecords {title: 'Foo'}, {title: 'Bar'}, to: grid('Books'), submit: true
18
+ addRecords: ->
19
+ params = arguments[arguments.length - 1]
20
+ for record in arguments
21
+ if (record != params)
22
+ record = params.to.getStore().add(record)[0]
23
+ record.isNew = true
24
+ click button 'Apply' if params.submit
25
+
26
+ addRecord: (recordData, params) ->
27
+ params = params || []
28
+ grid = params.to || this.grid()
29
+ record = grid.getStore().add(recordData)
30
+ grid.getSelectionModel().select(grid.getStore().last())
31
+
32
+ updateRecord: (recordData, params) ->
33
+ params = params || []
34
+ grid = params.to || this.grid()
35
+ record = grid.getSelectionModel().getSelection()[0]
36
+ for key,value of recordData
37
+ record.set(key, value)
38
+
39
+ selectAssociation: (attr, value, callback) ->
40
+ expandRowCombo attr
41
+ wait ->
42
+ select value, in: combobox(attr)
43
+ # wait ->
44
+ callback.call()
45
+
46
+ valuesInColumn: (name, params) ->
47
+ params ?= {}
48
+ grid = params.in || this.grid()
49
+ out = []
50
+ grid.getStore().each (r) ->
51
+ assocValue = r.get('meta').associationValues[name]
52
+ out.push(if assocValue then assocValue else r.get(name))
53
+ out
54
+
55
+ selectAllRows: (params) ->
56
+ params ?= {}
57
+ grid = params.in || this.grid()
58
+ grid.getSelectionModel().selectAll()
59
+
60
+ # rowDisplayValues in: grid('Books'), of: grid('Books').getStore().last()
61
+ # Without parameters, assumes the first found grid and the selected row
62
+ rowDisplayValues: (params) ->
63
+ params ?= {}
64
+ grid = params.in || this.grid()
65
+ record = params.of || grid.getSelectionModel().getSelection()[0]
66
+
67
+ visibleColumns = []
68
+ Ext.each grid.columns, (c) ->
69
+ visibleColumns.push(c) if c.isVisible()
70
+
71
+ i = -1
72
+ return Ext.Array.map(Ext.DomQuery.select('tr[data-recordid="'+record.internalId+'"] td div'), (cell) ->
73
+ i++
74
+ if visibleColumns[i].attrType == 'boolean'
75
+ record.get(visibleColumns[i].name)
76
+ else
77
+ cell.innerHTML
78
+ )
79
+
80
+ # selectLastRow()
81
+ # selectLastRow in: grid('Book')
82
+ selectLastRow: (params) ->
83
+ params ?= {}
84
+ grid = params.in || this.grid()
85
+ grid.getSelectionModel().select(grid.getStore().last())
86
+
87
+ # selectFirstRow()
88
+ # selectFirstRow in: grid('Book')
89
+ selectFirstRow: (params) ->
90
+ params ?= {}
91
+ grid = params.in || this.grid()
92
+ grid.getSelectionModel().select(grid.getStore().first())
93
+
94
+ # Example:
95
+ # editLastRow {title: 'Foo', exemplars: 10}
96
+ editLastRow: ->
97
+ data = arguments[0]
98
+ grid = Ext.ComponentQuery.query("grid")[0]
99
+ store = grid.getStore()
100
+ record = store.last()
101
+ for key of data
102
+ record.set(key, data[key])
103
+
104
+ completeEditing: (g) ->
105
+ g = g || this.grid()
106
+ e = g.getPlugin('celleditor')
107
+ e.completeEdit()
@@ -0,0 +1,88 @@
1
+ # Query helpers will return a string denoting what was searched for, when a component/element itself could not be found. This can be used by other helpers to display a more informative error.
2
+ # KNOWN ISSUE: if the passed parameter contains symbols like "():,.", it results in an invalid query.
3
+ Ext.apply window,
4
+ header: (title) ->
5
+ Ext.ComponentQuery.query('header{isVisible(true)}[title="'+title+'"]')[0] || 'header ' + title
6
+
7
+ tab: (title) ->
8
+ Ext.ComponentQuery.query('tab[text="'+title+'"]')[0] || 'tab ' + title
9
+
10
+ panelWithContent: (text) ->
11
+ Ext.DomQuery.select("div.x-panel-body:contains(" + text + ")")[0] || 'panel with content ' + text
12
+
13
+ button: (text) ->
14
+ Ext.ComponentQuery.query("button{isVisible(true)}[text='"+text+"']")[0] || "button " + text
15
+
16
+ tool: (type) ->
17
+ Ext.ComponentQuery.query("tool{isVisible(true)}[type='"+type+"']")[0] || 'tool ' + type
18
+
19
+ component: (id) ->
20
+ Ext.ComponentQuery.query("panel{isVisible(true)}[id='"+id+"']")[0] || 'component ' + id
21
+
22
+ somewhere: (text) ->
23
+ Ext.DomQuery.select("*:contains(" + text + ")")[0] || 'anywhere ' + text
24
+
25
+ # used as work-around for the invalid query problem
26
+ currentPanelTitle: ->
27
+ panel = Ext.ComponentQuery.query('panel[hidden=false]')[0]
28
+ throw "Panel not found" if !panel
29
+ panel.getHeader().title
30
+
31
+ combobox: (name) ->
32
+ Ext.ComponentQuery.query("combo{isVisible(true)}[name='"+name+"']")[0] ||
33
+ 'combobox ' + name
34
+
35
+ icon: (tooltip) ->
36
+ Ext.DomQuery.select('img[data-qtip="'+tooltip+'"]')[0] || 'icon ' + tooltip
37
+
38
+ textfield: (name) ->
39
+ Ext.ComponentQuery.query("textfield{isVisible(true)}[name='"+name+"']")[0] ||
40
+ 'textfield ' + name
41
+
42
+ numberfield: (name) ->
43
+ Ext.ComponentQuery.query("numberfield{isVisible(true)}[name='"+name+"']")[0] ||
44
+ 'numberfield ' + name
45
+
46
+ datefield: (name) ->
47
+ Ext.ComponentQuery.query("datefield{isVisible(true)}[name='"+name+"']")[0] ||
48
+ 'datefield ' + name
49
+
50
+ xdatetime: (name) ->
51
+ Ext.ComponentQuery.query("xdatetime{isVisible(true)}[name='"+name+"']")[0] ||
52
+ 'xdatetime ' + name
53
+
54
+ textFieldWith: (text) ->
55
+ _componentLike "textfield", "value", text
56
+
57
+ comboboxWith: (text) ->
58
+ _componentLike "combo", "rawValue", text
59
+
60
+ textAreaWith: (text) ->
61
+ _componentLike "textareafield", "value", text
62
+
63
+ numberFieldWith: (value) ->
64
+ _componentLike "numberfield", "value", value
65
+
66
+ activeWindow: ->
67
+ Ext.WindowMgr.getActive()
68
+
69
+ dateTimeFieldWith: (value) ->
70
+ res = 'xdatetime with value ' + value
71
+ Ext.each Ext.ComponentQuery.query('xdatetime'), (item) ->
72
+ if item.getValue().toString() == (new Date(value)).toString()
73
+ res = item
74
+ return
75
+ res
76
+
77
+ dateFieldWith: (value) ->
78
+ res = 'datefield with value ' + value
79
+ Ext.each Ext.ComponentQuery.query('datefield'), (item) ->
80
+ if item.getValue().toString() == (new Date(value)).toString()
81
+ res = item
82
+ return
83
+ res
84
+
85
+ _componentLike:(type,attr,value)->
86
+ Ext.ComponentQuery.query(type+'['+attr+'='+value+']')[0] || type + " with " + attr + " '" + value + "'"
87
+ # alias
88
+ window.anywhere = window.somewhere