screw-unit 0.3.3 → 0.5.1

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 (116) hide show
  1. data/CHANGES +10 -0
  2. data/Rakefile +27 -25
  3. data/VERSION.yml +4 -0
  4. data/bin/screw_unit_server +14 -4
  5. data/core/EXAMPLE.html +1 -1
  6. data/core/example/spec/suite.html +1 -1
  7. data/core/lib/jquery-1.3.2.js +4376 -0
  8. data/core/lib/jquery.fn.js +2 -1
  9. data/core/lib/screw.behaviors.js +8 -7
  10. data/core/lib/screw.builder.js +34 -19
  11. data/core/lib/screw.css +1 -1
  12. data/core/lib/screw.events.js +5 -5
  13. data/core/lib/screw.matchers.js +59 -2
  14. data/core/spec/suite.html +2 -1
  15. data/core/spec/with_screw_context_spec.js +9 -0
  16. data/lib/screw_unit.rb +13 -16
  17. data/lib/screw_unit/representations.rb +2 -0
  18. data/lib/screw_unit/{resources/spec.rb → representations/spec.html.rb} +55 -39
  19. data/spec/functional/functional_spec.rb +1 -1
  20. data/spec/functional/functional_spec_helper.rb +21 -33
  21. data/spec/functional/functional_spec_server_starter.rb +68 -0
  22. data/spec/unit/js_test_core/specs/spec_file_spec.rb +52 -18
  23. data/spec/unit/unit_spec_helper.rb +24 -91
  24. data/vendor/js-test-core/CHANGES +8 -0
  25. data/vendor/js-test-core/Rakefile +1 -0
  26. data/vendor/js-test-core/lib/js_test_core.rb +22 -14
  27. data/vendor/js-test-core/lib/js_test_core/app.rb +12 -0
  28. data/vendor/js-test-core/lib/js_test_core/client.rb +21 -9
  29. data/vendor/js-test-core/lib/js_test_core/configuration.rb +35 -0
  30. data/vendor/js-test-core/lib/js_test_core/extensions.rb +1 -1
  31. data/vendor/js-test-core/lib/js_test_core/extensions/selenium/client/driver.rb +7 -0
  32. data/vendor/js-test-core/lib/js_test_core/models.rb +8 -0
  33. data/vendor/js-test-core/lib/js_test_core/models/selenium_session.rb +80 -0
  34. data/vendor/js-test-core/lib/js_test_core/representations.rb +11 -0
  35. data/vendor/js-test-core/lib/js_test_core/representations/dir.html.rb +24 -0
  36. data/vendor/js-test-core/lib/js_test_core/representations/not_found.html.rb +15 -0
  37. data/vendor/js-test-core/lib/js_test_core/representations/page.html.rb +41 -0
  38. data/vendor/js-test-core/lib/js_test_core/representations/spec.html.rb +24 -0
  39. data/vendor/js-test-core/lib/js_test_core/resources.rb +13 -7
  40. data/vendor/js-test-core/lib/js_test_core/resources/core_file.rb +19 -0
  41. data/vendor/js-test-core/lib/js_test_core/resources/file.rb +42 -20
  42. data/vendor/js-test-core/lib/js_test_core/resources/implementations_deprecation.rb +12 -0
  43. data/vendor/js-test-core/lib/js_test_core/resources/not_found.rb +35 -0
  44. data/vendor/js-test-core/lib/js_test_core/resources/resource.rb +16 -0
  45. data/vendor/js-test-core/lib/js_test_core/resources/selenium_session.rb +104 -0
  46. data/vendor/js-test-core/lib/js_test_core/resources/spec_file.rb +63 -0
  47. data/vendor/js-test-core/lib/js_test_core/resources/web_root.rb +5 -60
  48. data/vendor/js-test-core/{vendor/thin-rest/README → spec/example_core/subdir/SubDirFile.js} +0 -0
  49. data/vendor/js-test-core/spec/example_specs/custom_dir_and_suite/passing_spec.js +6 -0
  50. data/vendor/js-test-core/spec/example_specs/custom_suite.html +8 -0
  51. data/vendor/js-test-core/spec/example_specs/foo/failing_spec.js +1 -1
  52. data/vendor/js-test-core/spec/example_specs/foo/passing_spec.js +1 -1
  53. data/vendor/js-test-core/spec/spec_helpers/be_http.rb +32 -0
  54. data/vendor/js-test-core/spec/spec_helpers/example_group.rb +36 -0
  55. data/vendor/js-test-core/spec/spec_helpers/fake_selenium_driver.rb +27 -0
  56. data/vendor/js-test-core/spec/spec_helpers/show_test_exceptions.rb +22 -0
  57. data/vendor/js-test-core/spec/unit/js_test_core/client_spec.rb +35 -10
  58. data/vendor/js-test-core/spec/unit/js_test_core/configuration_spec.rb +44 -0
  59. data/vendor/js-test-core/spec/unit/js_test_core/models/selenium_session_spec.rb +85 -0
  60. data/vendor/js-test-core/spec/unit/js_test_core/resources/core_file_spec.rb +60 -0
  61. data/vendor/js-test-core/spec/unit/js_test_core/resources/file_spec.rb +54 -63
  62. data/vendor/js-test-core/spec/unit/js_test_core/resources/implementations_deprecation_spec.rb +18 -0
  63. data/vendor/js-test-core/spec/unit/js_test_core/resources/not_found_spec.rb +51 -0
  64. data/vendor/js-test-core/spec/unit/js_test_core/resources/selenium_session_spec.rb +362 -0
  65. data/vendor/js-test-core/spec/unit/js_test_core/resources/spec_file_spec.rb +120 -0
  66. data/vendor/js-test-core/spec/unit/js_test_core/resources/web_root_spec.rb +15 -19
  67. data/vendor/js-test-core/spec/unit/unit_spec_helper.rb +16 -149
  68. data/vendor/js-test-core/vendor/lucky-luciano/README.markdown +7 -0
  69. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano.rb +4 -0
  70. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano/resource.rb +135 -0
  71. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano/rspec.rb +4 -0
  72. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano/rspec/be_http.rb +32 -0
  73. data/vendor/js-test-core/vendor/lucky-luciano/spec/lucky_luciano/resource_spec.rb +231 -0
  74. data/vendor/js-test-core/vendor/lucky-luciano/spec/spec_helper.rb +48 -0
  75. data/vendor/js-test-core/vendor/{thin-rest → lucky-luciano}/spec/spec_suite.rb +1 -2
  76. metadata +134 -145
  77. data/core/lib/jquery-1.2.6.js +0 -3549
  78. data/lib/screw_unit/resources.rb +0 -2
  79. data/spec/unit/js_test_core/specs/spec_dir_spec.rb +0 -38
  80. data/vendor/js-test-core/lib/js_test_core/rack.rb +0 -2
  81. data/vendor/js-test-core/lib/js_test_core/rack/commonlogger.rb +0 -5
  82. data/vendor/js-test-core/lib/js_test_core/rails_server.rb +0 -22
  83. data/vendor/js-test-core/lib/js_test_core/resources/dir.rb +0 -67
  84. data/vendor/js-test-core/lib/js_test_core/resources/file_not_found.rb +0 -11
  85. data/vendor/js-test-core/lib/js_test_core/resources/runner.rb +0 -107
  86. data/vendor/js-test-core/lib/js_test_core/resources/session.rb +0 -44
  87. data/vendor/js-test-core/lib/js_test_core/resources/session_finish.rb +0 -17
  88. data/vendor/js-test-core/lib/js_test_core/resources/specs/spec_dir.rb +0 -46
  89. data/vendor/js-test-core/lib/js_test_core/resources/specs/spec_file.rb +0 -17
  90. data/vendor/js-test-core/lib/js_test_core/selenium.rb +0 -2
  91. data/vendor/js-test-core/lib/js_test_core/selenium/selenium_driver.rb +0 -5
  92. data/vendor/js-test-core/lib/js_test_core/server.rb +0 -50
  93. data/vendor/js-test-core/lib/js_test_core/thin.rb +0 -3
  94. data/vendor/js-test-core/lib/js_test_core/thin/backends/js_test_core_server.rb +0 -9
  95. data/vendor/js-test-core/lib/js_test_core/thin/js_test_core_connection.rb +0 -8
  96. data/vendor/js-test-core/spec/unit/js_test_core/rails_server_spec.rb +0 -45
  97. data/vendor/js-test-core/spec/unit/js_test_core/resources/dir_spec.rb +0 -52
  98. data/vendor/js-test-core/spec/unit/js_test_core/resources/file_not_found_spec.rb +0 -16
  99. data/vendor/js-test-core/spec/unit/js_test_core/resources/runners/runner_spec.rb +0 -303
  100. data/vendor/js-test-core/spec/unit/js_test_core/resources/session_finish_spec.rb +0 -79
  101. data/vendor/js-test-core/spec/unit/js_test_core/resources/session_spec.rb +0 -82
  102. data/vendor/js-test-core/spec/unit/js_test_core/resources/specs/spec_dir_spec.rb +0 -105
  103. data/vendor/js-test-core/spec/unit/js_test_core/resources/specs/spec_file_spec.rb +0 -42
  104. data/vendor/js-test-core/spec/unit/js_test_core/server_spec.rb +0 -117
  105. data/vendor/js-test-core/spec/unit/thin/js_test_core_connection_spec.rb +0 -6
  106. data/vendor/js-test-core/vendor/thin-rest/CHANGES +0 -2
  107. data/vendor/js-test-core/vendor/thin-rest/lib/thin_rest.rb +0 -9
  108. data/vendor/js-test-core/vendor/thin-rest/lib/thin_rest/connection.rb +0 -116
  109. data/vendor/js-test-core/vendor/thin-rest/lib/thin_rest/extensions.rb +0 -3
  110. data/vendor/js-test-core/vendor/thin-rest/lib/thin_rest/extensions/object.rb +0 -21
  111. data/vendor/js-test-core/vendor/thin-rest/lib/thin_rest/resource.rb +0 -108
  112. data/vendor/js-test-core/vendor/thin-rest/lib/thin_rest/resource_invalid.rb +0 -4
  113. data/vendor/js-test-core/vendor/thin-rest/lib/thin_rest/routing_error.rb +0 -5
  114. data/vendor/js-test-core/vendor/thin-rest/spec/thin_rest/connection_spec.rb +0 -207
  115. data/vendor/js-test-core/vendor/thin-rest/spec/thin_rest/resource_spec.rb +0 -127
  116. data/vendor/js-test-core/vendor/thin-rest/spec/thin_rest_spec_helper.rb +0 -124
@@ -0,0 +1,12 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class ImplementationsDeprecation < Resource
4
+ map "/implementations"
5
+
6
+ get "*" do
7
+ new_path = File.path("javascripts", *params["splat"])
8
+ [301, {'Location' => new_path}, "This page has been moved to #{new_path}"]
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class NotFound < Resource
4
+ map "*"
5
+
6
+ get "/" do
7
+ call
8
+ end
9
+
10
+ put "/" do
11
+ call
12
+ end
13
+
14
+ post "/" do
15
+ call
16
+ end
17
+
18
+ delete "/" do
19
+ call
20
+ end
21
+
22
+ def call
23
+ body = Representations::NotFound.new(:message => "File #{request.path_info} not found").to_s
24
+ [
25
+ 404,
26
+ {
27
+ "Content-Type" => "text/html",
28
+ "Content-Length" => body.size.to_s
29
+ },
30
+ body
31
+ ]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class Resource < LuckyLuciano::Resource
4
+ protected
5
+
6
+ def spec_root_path; server.spec_root_path; end
7
+ def public_path; server.public_path; end
8
+ def core_path; server.core_path; end
9
+ def root_url; server.root_url; end
10
+
11
+ def server
12
+ JsTestCore::Configuration
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,104 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class SeleniumSession < Resource
4
+ map "/selenium_sessions"
5
+
6
+ post "/" do
7
+ do_post params["selenium_browser_start_command"]
8
+ end
9
+
10
+ post "/firefox" do
11
+ do_post "*firefox"
12
+ end
13
+
14
+ post '/iexplore' do |env, name|
15
+ do_post "*iexplore"
16
+ end
17
+
18
+ get "/:session_id" do
19
+ do_get
20
+ end
21
+
22
+ post "/finish" do
23
+ do_finish
24
+ end
25
+
26
+ post "/:session_id/finish" do
27
+ do_finish
28
+ end
29
+
30
+ include FileUtils
31
+ RUNNING = 'running'
32
+ SUCCESSFUL_COMPLETION = 'success'
33
+ FAILURE_COMPLETION = 'failure'
34
+
35
+ protected
36
+
37
+ def do_get
38
+ selenium_session = Models::SeleniumSession.find(session_id)
39
+ if selenium_session
40
+ body = if selenium_session.running?
41
+ "status=#{RUNNING}"
42
+ else
43
+ if selenium_session.successful?
44
+ "status=#{SUCCESSFUL_COMPLETION}"
45
+ else
46
+ "status=#{FAILURE_COMPLETION}&reason=#{selenium_session.run_result}"
47
+ end
48
+ end
49
+ [
50
+ 200,
51
+ {'Content-Length' => body.length},
52
+ body
53
+ ]
54
+ else
55
+ body = Representations::NotFound.new(:message => "Could not find session #{session_id}").to_s
56
+ [
57
+ 404,
58
+ {
59
+ "Content-Type" => "text/html",
60
+ "Content-Length" => body.size.to_s
61
+ },
62
+ body
63
+ ]
64
+ end
65
+ end
66
+
67
+ def do_finish
68
+ if selenium_session = Models::SeleniumSession.find(session_id)
69
+ selenium_session.finish(request['text'])
70
+ else
71
+ STDOUT.puts request['text']
72
+ end
73
+ [200, {}, request['text']]
74
+ end
75
+
76
+ def session_id
77
+ params["session_id"] || request.cookies["session_id"]
78
+ end
79
+
80
+ def do_post(selenium_browser_start_command)
81
+ selenium_session = Models::SeleniumSession.new({
82
+ :spec_url => request['spec_url'].to_s == "" ? full_spec_suite_url : request['spec_url'],
83
+ :selenium_browser_start_command => selenium_browser_start_command,
84
+ :selenium_host => request['selenium_host'].to_s == "" ? 'localhost' : request['selenium_host'].to_s,
85
+ :selenium_port => request['selenium_port'].to_s == "" ? 4444 : Integer(request['selenium_port'])
86
+ })
87
+ selenium_session.start
88
+
89
+ body = "session_id=#{selenium_session.session_id}"
90
+ [
91
+ 200,
92
+ {
93
+ "Content-Length" => body.length
94
+ },
95
+ body
96
+ ]
97
+ end
98
+
99
+ def full_spec_suite_url
100
+ "#{Configuration.root_url}/specs"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,63 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class SpecFile < ::JsTestCore::Resources::File
4
+ class << self
5
+ def spec_representation_class
6
+ @spec_representation_class ||= JsTestCore::Representations::Spec
7
+ end
8
+
9
+ attr_writer :spec_representation_class
10
+ end
11
+
12
+ map "/specs"
13
+
14
+ get "/?" do
15
+ do_get
16
+ end
17
+
18
+ get "*" do
19
+ do_get
20
+ end
21
+
22
+ protected
23
+
24
+ def do_get
25
+ if ::File.exists?(absolute_path)
26
+ if ::File.directory?(absolute_path)
27
+ spec_files = ::Dir["#{absolute_path}/**/*_spec.js"].map do |file|
28
+ ["#{relative_path}#{file.gsub(absolute_path, "")}"]
29
+ end
30
+ get_generated_spec(absolute_path, spec_files)
31
+ else
32
+ super
33
+ end
34
+ elsif ::File.exists?("#{absolute_path}.js")
35
+ get_generated_spec("#{absolute_path}.js", ["#{relative_path}.js"])
36
+ else
37
+ pass
38
+ end
39
+ end
40
+
41
+ def get_generated_spec(real_path, spec_files)
42
+ html = render_spec(spec_files)
43
+ [
44
+ 200,
45
+ {
46
+ 'Content-Type' => "text/html",
47
+ 'Last-Modified' => ::File.mtime(real_path).rfc822,
48
+ 'Content-Length' => html.length
49
+ },
50
+ html
51
+ ]
52
+ end
53
+
54
+ def render_spec(spec_files)
55
+ self.class.spec_representation_class.new(:spec_files => spec_files).to_s
56
+ end
57
+
58
+ def absolute_path
59
+ @absolute_path ||= ::File.expand_path("#{spec_root_path}#{relative_path.gsub(%r{^/specs}, "")}")
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,65 +1,10 @@
1
1
  module JsTestCore
2
2
  module Resources
3
- class WebRoot < ThinRest::Resource
4
- route "" do |env, name|
5
- self
6
- end
7
- route "core" do |env, name|
8
- Resources::Dir.new(env.merge(
9
- :absolute_path => JsTestCore::Server.core_path,
10
- :relative_path => "/core"
11
- ))
12
- end
13
- route "implementations" do |env, name|
14
- Resources::Dir.new(env.merge(
15
- :absolute_path => JsTestCore::Server.implementation_root_path,
16
- :relative_path => "/implementations"
17
- ))
18
- end
19
- route "sessions", "JsTestCore::Resources::Session::Collection"
20
- route "session" do |env, name|
21
- Session.new(env.merge(:id => rack_request.cookies["session_id"]))
22
- end
23
- route "runners", "JsTestCore::Resources::Runner::Collection"
24
- route "specs" do |env, name|
25
- if self.class.dispatch_strategy == :specs
26
- Resources::Specs::SpecDir.new(env.merge(
27
- :absolute_path => JsTestCore::Server.spec_root_path,
28
- :relative_path => "/specs"
29
- ))
30
- else
31
- Resources::FileNotFound.new(env.merge(:name => name))
32
- end
33
- end
34
- route ANY do |env, name|
35
- potential_file_in_public_path = "#{public_path}/#{name}"
36
- if ::File.directory?(potential_file_in_public_path)
37
- Resources::Dir.new(env.merge(
38
- :absolute_path => potential_file_in_public_path,
39
- :relative_path => "/#{name}"
40
- ))
41
- elsif ::File.exists?(potential_file_in_public_path)
42
- Resources::File.new(env.merge(
43
- :absolute_path => potential_file_in_public_path,
44
- :relative_path => "/#{name}"
45
- ))
46
- else
47
- Resources::FileNotFound.new(env.merge(:name => name))
48
- end
49
- end
50
-
51
- class << self
52
- attr_accessor :dispatch_strategy
53
- def dispatch_specs
54
- self.dispatch_strategy = :specs
55
- end
56
- end
57
-
58
- property :public_path
59
-
60
- def get
61
- connection.send_head(200, :Location => "/#{self.class.dispatch_strategy}")
62
- connection.send_body("<html><head></head><body>Welcome to the Js Test Server. Click the following link to run you <a href=/specs>spec suite</a>.</body></html>")
3
+ class WebRoot < Resource
4
+ map "/"
5
+
6
+ get("") do
7
+ [200, {}, "<html><head></head><body>Welcome to the Js Test Server. Click the following link to run you <a href=/specs>spec suite</a>.</body></html>"]
63
8
  end
64
9
  end
65
10
  end
@@ -0,0 +1,6 @@
1
+ require("/javascripts/foo");
2
+ describe("A passing spec", {
3
+ 'passes': function() {
4
+ value_of(Foo.value).should_be(true);
5
+ }
6
+ })
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+ <title>Custom Suite</title>
4
+ </head>
5
+ <body>
6
+
7
+ </body>
8
+ </html>
@@ -1,4 +1,4 @@
1
- require("/implementations/foo");
1
+ require("/javascripts/foo");
2
2
  describe("A failing spec in foo", {
3
3
  'fails': function() {
4
4
  value_of(Foo.value).should_be(false);
@@ -1,4 +1,4 @@
1
- require("/implementations/foo");
1
+ require("/javascripts/foo");
2
2
  describe("A passing spec", {
3
3
  'passes': function() {
4
4
  value_of(Foo.value).should_be(true);
@@ -0,0 +1,32 @@
1
+ module BeHttp
2
+ include Spec::Matchers
3
+ def be_http(status, headers, body)
4
+ SimpleMatcher.new(nil) do |given, matcher|
5
+ description = (<<-DESC).gsub(/^ +/, "")
6
+ be an http of
7
+ expected status: #{status.inspect}
8
+ actual status : #{given.status.inspect}
9
+
10
+ expected headers containing: #{headers.inspect}
11
+ actual headers : #{given.headers.inspect}
12
+
13
+ expected body containing: #{body.inspect}
14
+ actual body : #{given.body.inspect}
15
+ DESC
16
+ matcher.failure_message = description
17
+ matcher.negative_failure_message = "not #{description}"
18
+
19
+ passed = true
20
+ unless given.status == status
21
+ passed = false
22
+ end
23
+ unless headers.all?{|k, v| given.headers[k] == headers[k]}
24
+ passed = false
25
+ end
26
+ unless body.is_a?(Regexp) ? given.body =~ body : given.body.include?(body)
27
+ passed = false
28
+ end
29
+ passed
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ class Spec::ExampleGroup
2
+ class << self
3
+ def macro(name, &block)
4
+ eigen do
5
+ define_method(name, &block)
6
+ end
7
+ end
8
+
9
+ def eigen(&block)
10
+ eigen_class = (class << self; self; end)
11
+ eigen_class.class_eval(&block)
12
+ eigen_class
13
+ end
14
+ end
15
+
16
+ include Rack::Test::Methods
17
+ include BeHttp
18
+ attr_reader :core_path, :spec_root_path, :public_path, :server, :connection
19
+ before(:all) do
20
+ dir = File.dirname(__FILE__)
21
+ @core_path = File.expand_path("#{LIBRARY_ROOT_DIR}/spec/example_core")
22
+ @spec_root_path = File.expand_path("#{LIBRARY_ROOT_DIR}/spec/example_specs")
23
+ @public_path = File.expand_path("#{LIBRARY_ROOT_DIR}/spec/example_public")
24
+ stub(Thread).start.yields
25
+ end
26
+
27
+ before(:each) do
28
+ JsTestCore::Configuration.instance.spec_root_path = spec_root_path
29
+ JsTestCore::Configuration.instance.public_path = public_path
30
+ JsTestCore::Configuration.instance.core_path = core_path
31
+ end
32
+
33
+ def app
34
+ Sinatra::Application
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ class FakeSeleniumDriver
2
+ SESSION_ID = "DEADBEEF"
3
+ attr_reader :session_id
4
+
5
+ def initialize
6
+ @session_id = nil
7
+ end
8
+
9
+ def start
10
+ @session_id = SESSION_ID
11
+ end
12
+
13
+ def stop
14
+ @session_id = nil
15
+ end
16
+
17
+ def open(url)
18
+ end
19
+
20
+ def create_cookie(key_value, options="")
21
+
22
+ end
23
+
24
+ def session_started?
25
+ !!@session_id
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ class ShowTestExceptions
2
+ attr_reader :app
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ app.call(env)
10
+ rescue StandardError, LoadError, SyntaxError => e
11
+ body = [
12
+ e.message,
13
+ e.backtrace.join("\n\t")
14
+ ].join("\n")
15
+ [
16
+ 500,
17
+ {"Content-Type" => "text",
18
+ "Content-Length" => body.size.to_s},
19
+ body
20
+ ]
21
+ end
22
+ end