joosy 0.1.0.alpha → 1.0.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/.codoopts +5 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +15 -2
  4. data/Gemfile.lock +102 -81
  5. data/Guardfile +16 -16
  6. data/LICENSE +22 -0
  7. data/MIT-LICENSE +2 -2
  8. data/README.md +118 -0
  9. data/app/assets/javascripts/joosy/core/application.js.coffee +53 -0
  10. data/app/assets/javascripts/joosy/core/form.js.coffee +338 -0
  11. data/app/assets/javascripts/joosy/core/helpers/form.js.coffee +72 -0
  12. data/app/assets/javascripts/joosy/core/helpers/view.js.coffee +42 -0
  13. data/app/assets/javascripts/joosy/core/helpers/widgets.js.coffee +14 -0
  14. data/app/assets/javascripts/joosy/core/joosy.js.coffee +184 -0
  15. data/app/assets/javascripts/joosy/core/layout.js.coffee +168 -0
  16. data/app/assets/javascripts/joosy/core/modules/container.js.coffee +124 -0
  17. data/app/assets/javascripts/joosy/core/modules/events.js.coffee +122 -0
  18. data/app/assets/javascripts/joosy/core/modules/filters.js.coffee +39 -0
  19. data/app/assets/javascripts/joosy/core/modules/log.js.coffee +36 -0
  20. data/app/assets/javascripts/joosy/core/modules/module.js.coffee +117 -0
  21. data/app/assets/javascripts/joosy/core/modules/renderer.js.coffee +200 -0
  22. data/app/assets/javascripts/joosy/core/modules/time_manager.js.coffee +46 -0
  23. data/app/assets/javascripts/joosy/core/modules/widgets_manager.js.coffee +87 -0
  24. data/app/assets/javascripts/joosy/core/page.js.coffee +387 -0
  25. data/app/assets/javascripts/joosy/core/preloader.js.coffee +13 -0
  26. data/app/assets/javascripts/joosy/core/resource/collection.js.coffee +175 -0
  27. data/app/assets/javascripts/joosy/core/resource/generic.js.coffee +303 -0
  28. data/app/assets/javascripts/joosy/core/resource/rest.js.coffee +244 -0
  29. data/app/assets/javascripts/joosy/core/resource/rest_collection.js.coffee +24 -0
  30. data/app/assets/javascripts/joosy/core/router.js.coffee +201 -0
  31. data/app/assets/javascripts/joosy/core/templaters/rails_jst.js.coffee +37 -0
  32. data/app/assets/javascripts/joosy/core/widget.js.coffee +85 -0
  33. data/app/assets/javascripts/joosy/preloaders/caching.js.coffee +169 -0
  34. data/app/assets/javascripts/joosy/preloaders/inline.js.coffee +56 -0
  35. data/{vendor → app}/assets/javascripts/joosy.js.coffee +0 -1
  36. data/app/helpers/joosy/sprockets_helper.rb +39 -12
  37. data/joosy.gemspec +4 -3
  38. data/lib/joosy/rails/engine.rb +12 -1
  39. data/lib/joosy/rails/version.rb +2 -2
  40. data/lib/joosy.rb +9 -0
  41. data/lib/rails/generators/joosy/application_generator.rb +15 -3
  42. data/lib/rails/generators/joosy/joosy_base.rb +2 -2
  43. data/lib/rails/generators/joosy/layout_generator.rb +8 -1
  44. data/lib/rails/generators/joosy/page_generator.rb +21 -6
  45. data/lib/rails/generators/joosy/preloader_generator.rb +14 -7
  46. data/lib/rails/generators/joosy/resource_generator.rb +29 -0
  47. data/lib/rails/generators/joosy/templates/app/helpers/application.js.coffee +4 -0
  48. data/lib/rails/generators/joosy/templates/app/layouts/application.js.coffee +1 -0
  49. data/lib/rails/generators/joosy/templates/app/layouts/template.js.coffee +1 -1
  50. data/lib/rails/generators/joosy/templates/app/pages/application.js.coffee +1 -1
  51. data/lib/rails/generators/joosy/templates/app/pages/template.js.coffee +3 -3
  52. data/lib/rails/generators/joosy/templates/app/pages/welcome/index.js.coffee +22 -0
  53. data/lib/rails/generators/joosy/templates/app/resources/template.js.coffee +2 -0
  54. data/lib/rails/generators/joosy/templates/app/routes.js.coffee +7 -1
  55. data/lib/rails/generators/joosy/templates/app/templates/layouts/application.jst.hamlc +2 -0
  56. data/lib/rails/generators/joosy/templates/app/templates/pages/welcome/index.jst.hamlc +7 -0
  57. data/lib/rails/generators/joosy/templates/app/widgets/template.js.coffee +2 -2
  58. data/lib/rails/generators/joosy/templates/app.js.coffee +4 -0
  59. data/lib/rails/generators/joosy/templates/app_preloader.js.coffee.erb +9 -12
  60. data/lib/rails/generators/joosy/templates/app_resources_predefiner.js.coffee.erb +11 -0
  61. data/lib/rails/generators/joosy/templates/preload.html.erb +5 -6
  62. data/lib/rails/generators/joosy/templates/preload.html.haml +3 -5
  63. data/lib/rails/generators/joosy/widget_generator.rb +8 -1
  64. data/lib/rails/resources_with_joosy.rb +11 -0
  65. data/spec/javascripts/helpers/spec_helper.js.coffee +25 -0
  66. data/spec/javascripts/joosy/core/application_spec.js.coffee +40 -0
  67. data/spec/javascripts/joosy/core/form_spec.js.coffee +200 -0
  68. data/spec/javascripts/joosy/core/helpers/forms_spec.js.coffee +103 -0
  69. data/spec/javascripts/joosy/core/helpers/view_spec.js.coffee +10 -0
  70. data/spec/javascripts/joosy/core/joosy_spec.js.coffee +97 -0
  71. data/spec/javascripts/joosy/core/layout_spec.js.coffee +50 -0
  72. data/spec/javascripts/joosy/core/modules/container_spec.js.coffee +32 -27
  73. data/spec/javascripts/joosy/core/modules/events_spec.js.coffee +55 -18
  74. data/spec/javascripts/joosy/core/modules/filters_spec.js.coffee +28 -27
  75. data/spec/javascripts/joosy/core/modules/log_spec.js.coffee +3 -3
  76. data/spec/javascripts/joosy/core/modules/module_spec.js.coffee +6 -15
  77. data/spec/javascripts/joosy/core/modules/renderer_spec.js.coffee +203 -0
  78. data/spec/javascripts/joosy/core/modules/time_manager_spec.js.coffee +12 -7
  79. data/spec/javascripts/joosy/core/modules/widget_manager_spec.js.coffee +31 -17
  80. data/spec/javascripts/joosy/core/page_spec.js.coffee +178 -0
  81. data/spec/javascripts/joosy/core/resource/collection_spec.js.coffee +84 -0
  82. data/spec/javascripts/joosy/core/resource/generic_spec.js.coffee +149 -0
  83. data/spec/javascripts/joosy/core/resource/rest_collection_spec.js.coffee +31 -0
  84. data/spec/javascripts/joosy/core/resource/rest_spec.js.coffee +171 -0
  85. data/spec/javascripts/joosy/core/router_spec.js.coffee +143 -0
  86. data/spec/javascripts/joosy/core/templaters/rails_jst_spec.js.coffee +25 -0
  87. data/spec/javascripts/joosy/core/widget_spec.js.coffee +40 -0
  88. data/spec/javascripts/joosy/preloaders/caching_spec.js.coffee +36 -0
  89. data/spec/javascripts/joosy/preloaders/inline_spec.js.coffee +16 -0
  90. data/spec/javascripts/support/assets/coolface.jpg +0 -0
  91. data/spec/javascripts/support/assets/okay.jpg +0 -0
  92. data/spec/javascripts/support/assets/test.js +1 -0
  93. data/spec/javascripts/support/sinon-ie-1.3.1.js +82 -0
  94. data/vendor/assets/javascripts/jquery.form.js +978 -963
  95. data/vendor/assets/javascripts/metamorph.js +409 -0
  96. data/vendor/assets/javascripts/sugar.js +1057 -366
  97. metadata +95 -50
  98. data/README.rdoc +0 -3
  99. data/lib/joosy/forms.rb +0 -47
  100. data/lib/joosy-rails.rb +0 -5
  101. data/lib/rails/generators/joosy/templates/preload.html.slim +0 -21
  102. data/tmp/javascripts/.gitignore +0 -1
  103. data/tmp/spec/javascripts/helpers/.gitignore +0 -1
  104. data/vendor/assets/javascripts/base64.js +0 -135
  105. data/vendor/assets/javascripts/inflection.js +0 -656
  106. data/vendor/assets/javascripts/joosy/core/application.js.coffee +0 -26
  107. data/vendor/assets/javascripts/joosy/core/form.js.coffee +0 -87
  108. data/vendor/assets/javascripts/joosy/core/joosy.js.coffee +0 -62
  109. data/vendor/assets/javascripts/joosy/core/layout.js.coffee +0 -38
  110. data/vendor/assets/javascripts/joosy/core/modules/container.js.coffee +0 -51
  111. data/vendor/assets/javascripts/joosy/core/modules/events.js.coffee +0 -17
  112. data/vendor/assets/javascripts/joosy/core/modules/filters.js.coffee +0 -39
  113. data/vendor/assets/javascripts/joosy/core/modules/log.js.coffee +0 -12
  114. data/vendor/assets/javascripts/joosy/core/modules/module.js.coffee +0 -28
  115. data/vendor/assets/javascripts/joosy/core/modules/time_manager.js.coffee +0 -23
  116. data/vendor/assets/javascripts/joosy/core/modules/widgets_manager.js.coffee +0 -45
  117. data/vendor/assets/javascripts/joosy/core/page.js.coffee +0 -136
  118. data/vendor/assets/javascripts/joosy/core/resource/rest.js.coffee +0 -69
  119. data/vendor/assets/javascripts/joosy/core/resource/rest_collection.js.coffee +0 -42
  120. data/vendor/assets/javascripts/joosy/core/router.js.coffee +0 -75
  121. data/vendor/assets/javascripts/joosy/core/widget.js.coffee +0 -35
  122. data/vendor/assets/javascripts/joosy/preloader/development.js.coffee +0 -27
  123. data/vendor/assets/javascripts/joosy/preloader/production.js.coffee +0 -84
@@ -0,0 +1,11 @@
1
+ # Whitelist here modules and sub-modules which resources should be predefined
2
+ # e.g. ['front', 'front/products', nil] if you have
3
+ # Front::SomeController and Front::Products::AnotherController
4
+ # with the associated namespaces in your routes
5
+ # and want to predefine resources with no namespace
6
+
7
+ # Joosy.defineResources <%= "<%= Joosy::SprocketsHelper.joosy_resources(['" + file_name + "', nil]).to_json %" + ">" %>
8
+
9
+ # or predefine all resources
10
+
11
+ Joosy.defineResources <%= "<%= Joosy::SprocketsHelper.joosy_resources.to_json %" + ">" %>
@@ -1,22 +1,21 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Loading...</title>
4
+ <title><%= file_path %></title>
5
5
 
6
6
  <script type="text/javascript">
7
- window.preload = {
8
- libraries: <%= "<%= extract_sources_and_sizes_from_include_tag('#{file_path}') %"+">" %>,
9
- development: <%= "<%= Rails.env.development? %"+">" %>
7
+ window.joosy = {
8
+ libraries: <%= "<%= extract_sources_and_sizes_from_include_tag('#{file_path}') %"+">" %>,
9
+ environment: '<%= "<%= Rails.env.to_s %"+">" %>'
10
10
  };
11
11
  </script>
12
12
 
13
13
  <%= "<%= stylesheet_link_tag 'application' %"+">" %>
14
- <%= "<%= javascript_include_tag 'application' %"+">" %>
15
- <%= "<%= javascript_include_tag '#{file_path}_preloader' %"+">" %>
16
14
  <%= "<%= csrf_meta_tags %"+">" %>
17
15
  </head>
18
16
 
19
17
  <body>
18
+ <%= "<%= javascript_include_tag '#{file_path}_preloader' %"+">" %>
20
19
  <div id="application">
21
20
  <div id="preloader">
22
21
  Loading application...
@@ -1,20 +1,18 @@
1
1
  !!! 5
2
2
  %html
3
3
  %head
4
- %title> Loading...
4
+ %title> <%= file_path %>
5
5
 
6
6
  javascript:
7
7
  window.preload = {
8
- libraries: #{extract_sources_and_sizes_from_include_tag('<%= file_path %>')},
9
- development: #{Rails.env.development?}
8
+ libraries: #{extract_sources_and_sizes_from_include_tag('<%= file_path %>')}
10
9
  };
11
10
 
12
11
  = stylesheet_link_tag 'application'
13
- = javascript_include_tag 'application'
14
- = javascript_include_tag '<%= file_path %>_preloader'
15
12
  = csrf_meta_tags
16
13
 
17
14
  %body
15
+ = javascript_include_tag '<%= file_path %>_preloader'
18
16
  #application
19
17
  #preloader
20
18
  Loading application...
@@ -18,7 +18,14 @@ module Joosy
18
18
  protected
19
19
 
20
20
  def app_path
21
- File.join class_path
21
+ unless class_path.size == 1
22
+ puts <<HELP
23
+ Usage: rails generate joosy:widget joosy_app_name/widget_name
24
+ Tip: do not add Widget suffix to widget_name
25
+ HELP
26
+ exit 1
27
+ end
28
+ class_path[0]
22
29
  end
23
30
  end
24
31
  end
@@ -0,0 +1,11 @@
1
+ require 'action_dispatch/routing/mapper'
2
+
3
+ module ActionDispatch::Routing::Mapper::Resources
4
+ def resources_with_joosy(*resources, &block)
5
+ resources_without_joosy(*resources, &block).tap do
6
+ namespace = Joosy::Rails::Engine.resources[@scope[:module]] ||= {}
7
+ namespace[resources[0].to_s.singularize] = "#{@scope[:shallow_path]}/#{resources[0]}"
8
+ end
9
+ end
10
+ alias_method_chain :resources, :joosy
11
+ end
@@ -1,6 +1,11 @@
1
1
  beforeEach ->
2
+ Joosy.Resource.Generic.resetIdentity()
3
+
4
+ window.JST = {}
2
5
  $('body').append('<div id="ground">')
6
+
3
7
  @ground = $('body #ground')
8
+
4
9
  @seedGround = ->
5
10
  @ground.html('
6
11
  <div id="application" class="application">
@@ -20,5 +25,25 @@ beforeEach ->
20
25
  </div>
21
26
  ')
22
27
 
28
+ @addMatchers
29
+ toBeSequenced: () ->
30
+ if !Object.isArray(@actual) || @actual.length == 0
31
+ console.log 'toBeSequenced: not array or empty array given'
32
+ return false
33
+ i = 0
34
+ for spy in @actual
35
+ unless spy.callCount == 1
36
+ console.log "toBeSequenced: spy ##{i} was called #{spy.callCount} times"
37
+ return false
38
+ i++
39
+ if @actual.length > 1
40
+ for spy in @actual.from(1)
41
+ i = @actual.indexOf spy
42
+ previous = @actual[i - 1]
43
+ unless spy.calledAfter previous
44
+ console.log "toBeSequenced: spy ##{i} wasn't called after spy ##{i - 1}"
45
+ return false
46
+ return true
47
+
23
48
  afterEach ->
24
49
  @ground.remove() unless @polluteGround
@@ -0,0 +1,40 @@
1
+ describe "Joosy.Application", ->
2
+
3
+ beforeEach ->
4
+ sinon.stub(Joosy.Router, "__setupRoutes")
5
+ @seedGround()
6
+
7
+ afterEach ->
8
+ Joosy.Router.__setupRoutes.restore()
9
+
10
+ it "should initialize", ->
11
+ Joosy.Application.initialize 'app', '#application'
12
+ expect(Joosy.Application.page).toBeUndefined()
13
+ expect(Joosy.Application.selector).toEqual '#application'
14
+ expect(Joosy.Application.sandboxSelector).toMatch /#[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}/
15
+ expect($(Joosy.Application.sandboxSelector).length).toEqual 1
16
+ expect(Joosy.Router.__setupRoutes.callCount).toEqual 1
17
+ expect(Joosy.Application.name).toEqual 'app'
18
+
19
+ it "should set container", ->
20
+ expect(Joosy.Application.content()).toEqual $('#application')
21
+
22
+ it "should properly initialize and save pages", ->
23
+ spy = sinon.spy()
24
+ class Page1
25
+ constructor: spy
26
+ class Page2
27
+ constructor: spy
28
+
29
+ Joosy.Application.setCurrentPage(Page1, {foo: 'bar'})
30
+
31
+ expect(Joosy.Application.page instanceof Page1).toBeTruthy()
32
+ expect(spy.callCount).toEqual 1
33
+ expect(spy.args[0]).toEqual [{foo: 'bar'}, undefined]
34
+
35
+ Joosy.Application.setCurrentPage(Page2, {bar: 'baz'})
36
+
37
+ expect(Joosy.Application.page instanceof Page2).toBeTruthy()
38
+ expect(spy.callCount).toEqual 2
39
+ expect(spy.args[1][0]).toEqual {bar: 'baz'}
40
+ expect(spy.args[1][1] instanceof Page1).toBeTruthy()
@@ -0,0 +1,200 @@
1
+ describe "Joosy.Form", ->
2
+
3
+ beforeEach ->
4
+ @server = sinon.fakeServer.create()
5
+ @seedGround()
6
+ @nudeForm = "<form id='nude'><input name='test[foo]'/><input name='test[bar]'/><input name='test[bool]' type='checkbox' value='1'/></form>"
7
+ @putForm = "<form id='put' method='put'><input name='test[camel_baz]'/></form>"
8
+ @moreForm = "<form id='more' method='put'><input name='test[ololo]'/></form>"
9
+
10
+ @ground.find('#sidebar').after(@nudeForm).after(@putForm).after(@moreForm)
11
+
12
+ @nudeForm = $('#nude')
13
+ @putForm = $('#put')
14
+ @moreForm = $('#more')
15
+
16
+ class Test extends Joosy.Resource.REST
17
+ @entity 'test'
18
+ @Test = Test
19
+
20
+ @resource = Test.build
21
+ id: 1,
22
+ foo: 'foo',
23
+ bar: 'bar'
24
+ camelBaz: 'baz'
25
+ bool: true
26
+
27
+ afterEach ->
28
+ @server.restore()
29
+
30
+ describe "Initialization", ->
31
+
32
+ beforeEach ->
33
+ @spy = sinon.spy $.fn, 'ajaxForm'
34
+
35
+ afterEach ->
36
+ @spy.restore()
37
+
38
+ it "should properly act with options", ->
39
+ formWithProperties = new Joosy.Form @nudeForm, invalidationClass: 'fluffy'
40
+ expect(formWithProperties.container).toEqual @nudeForm
41
+ expect(formWithProperties.invalidationClass).toEqual 'fluffy'
42
+ expect(formWithProperties.fields.length).toEqual 3
43
+
44
+ expect(@spy.callCount).toEqual 1
45
+
46
+ it "should properly act with callback", ->
47
+ formWithCallback = new Joosy.Form @putForm, callback=sinon.spy()
48
+ expect(formWithCallback.container).toEqual @putForm
49
+ expect(formWithCallback.invalidationClass).toEqual 'field_with_errors'
50
+ expect(formWithCallback.success).toBe callback
51
+ expect(formWithCallback.fields.length).toEqual 1
52
+
53
+ expect(@spy.callCount).toEqual 1
54
+
55
+ it "should hijack form method if it differs from POST/GET", ->
56
+ form = new Joosy.Form @putForm, callback=sinon.spy()
57
+ marker = @putForm.find "input[type=hidden]"
58
+ expect(@putForm.attr('method')?.toLowerCase()).toEqual 'post'
59
+ expect(marker.attr 'name').toEqual '_method'
60
+ expect(marker.attr 'value').toEqual 'put'
61
+
62
+ describe "Filling", ->
63
+
64
+ beforeEach ->
65
+ @nudeForm = new Joosy.Form @nudeForm
66
+ @putForm = new Joosy.Form @putForm
67
+ @moreForm = new Joosy.Form @moreForm
68
+
69
+ it "should fill form, set proper action and method and store resource", ->
70
+ @nudeForm.fill @resource
71
+ expect(@nudeForm.fields[0].value).toEqual 'foo'
72
+ expect(@nudeForm.fields[1].value).toEqual 'bar'
73
+ expect(@nudeForm.fields[2].checked).toEqual true
74
+ expect(@nudeForm.fields[2].value).toEqual '1'
75
+ expect(@nudeForm.container.attr('method').toLowerCase()).toEqual 'post'
76
+ expect(@nudeForm.container.attr 'action').toEqual '/tests/1'
77
+ expect(@nudeForm.__resource).toEqual @resource
78
+
79
+ it "should fill form with camelized properties", ->
80
+ @putForm.fill @resource
81
+ expect(@putForm.fields[0].value).toEqual 'baz'
82
+ expect(@putForm.container.attr('method').toLowerCase()).toEqual 'post'
83
+ expect(@putForm.container.attr 'action').toEqual '/tests/1'
84
+
85
+ it "should fill form with decorator", ->
86
+ @moreForm.fill @resource,
87
+ decorator: (e) ->
88
+ e.ololo = e.camelBaz
89
+ e
90
+ expect(@moreForm.fields[0].value).toEqual 'baz'
91
+
92
+ it "should fill form with extended action", ->
93
+ @nudeForm.fill @resource,
94
+ action: @resource.memberPath(from: 'calculate')
95
+ expect(@nudeForm.fields[0].value).toEqual 'foo'
96
+ expect(@nudeForm.fields[1].value).toEqual 'bar'
97
+ expect(@nudeForm.container.attr 'action').toEqual '/tests/1/calculate'
98
+
99
+ resource = @Test.build 'someId'
100
+
101
+ @nudeForm.fill resource,
102
+ action: resource.memberPath(from: 'calculate')
103
+ expect(@nudeForm.container.attr 'action').toEqual '/tests/someId/calculate'
104
+
105
+ describe "Callbacks", ->
106
+
107
+ beforeEach ->
108
+ @nudeForm = new Joosy.Form @nudeForm, @spy=sinon.spy()
109
+ @nudeForm.fill @resource
110
+ @nudeForm.container.submit()
111
+ @target = @server.requests.last()
112
+
113
+ it "should trigger 'success'", ->
114
+ expect(@target.method).toEqual 'POST'
115
+ expect(@target.url).toEqual '/tests/1'
116
+ @target.respond 200, 'Content-Type': 'application/json', '{"form": "works"}'
117
+ expect(@spy.callCount).toEqual 1
118
+ expect(@spy.args[0][0]).toEqual {form: 'works'}
119
+
120
+ it "should fill class for invalidated fields by default", ->
121
+ @target.respond 422, 'Content-Type': 'application/json', '{"foo": "error!"}'
122
+ expect($(@nudeForm.fields[0]).attr 'class').toEqual 'field_with_errors'
123
+
124
+ it "should trigger 'error' and complete default action if it returned true", ->
125
+ @nudeForm.error = sinon.spy ->
126
+ true
127
+ @target.respond 422, 'Content-Type': 'application/json', '{"foo": "error!"}'
128
+ expect($(@nudeForm.fields[0]).attr 'class').toEqual 'field_with_errors'
129
+ expect(@nudeForm.error.callCount).toEqual 1
130
+ expect(@nudeForm.error.args[0][0]).toEqual
131
+ "foo": "error!"
132
+
133
+ it "should trigger 'error' and skip default action if it returned false", ->
134
+ @nudeForm.error = sinon.spy ->
135
+ false
136
+ @target.respond 422, 'Content-Type': 'application/json', '{"foo": "error!"}'
137
+ expect($(@nudeForm.fields[0]).attr 'class').toNotEqual 'field_with_errors'
138
+ expect(@nudeForm.error.callCount).toEqual 1
139
+
140
+ it "should clear fields before another submit", ->
141
+ @target.respond 422, 'Content-Type': 'application/json', '{"foo": "error!"}'
142
+ expect($(@nudeForm.fields[0]).attr 'class').toEqual 'field_with_errors'
143
+ @nudeForm.container.submit()
144
+ expect($(@nudeForm.fields[0]).attr 'class').toNotEqual 'field_with_errors'
145
+
146
+ it "should trigger 'before' and do default action if it returns true", ->
147
+ @target.respond 422, 'Content-Type': 'application/json', '{"foo": "error!"}'
148
+ expect($(@nudeForm.fields[0]).attr 'class').toEqual 'field_with_errors'
149
+ @nudeForm.before = sinon.spy ->
150
+ true
151
+ @nudeForm.container.submit()
152
+ expect($(@nudeForm.fields[0]).attr 'class').toNotEqual 'field_with_errors'
153
+ expect(@nudeForm.before.callCount).toEqual 1
154
+
155
+ it "should trigger 'before' and skip default action if it returns false", ->
156
+ @target.respond 422, 'Content-Type': 'application/json', '{"foo": "error!"}'
157
+ expect($(@nudeForm.fields[0]).attr 'class').toEqual 'field_with_errors'
158
+ @nudeForm.before = sinon.spy ->
159
+ false
160
+ @nudeForm.container.submit()
161
+ expect($(@nudeForm.fields[0]).attr 'class').toEqual 'field_with_errors'
162
+ expect(@nudeForm.before.callCount).toEqual 1
163
+
164
+ describe "Error response handling", ->
165
+
166
+ beforeEach ->
167
+ @nudeForm = new Joosy.Form @nudeForm, @spy=sinon.spy()
168
+
169
+ it "should prepare simple response", ->
170
+ errors = {zombie: ['suck'], puppies: ['rock']}
171
+ result = @nudeForm.__stringifyErrors(errors)
172
+
173
+ expect(result).toEqual zombie: ['suck'], puppies: ['rock']
174
+
175
+ it "should prepare inline response", ->
176
+ errors = {"zombie.in1.subin1": ['suck'], "zombie.in2": ['rock']}
177
+ result = @nudeForm.__stringifyErrors(errors)
178
+
179
+ expect(result).toEqual {"zombie[in1][subin1]": ['suck'], "zombie[in2]": ['rock']}
180
+
181
+ it "should prepare inline response with resource attached", ->
182
+ @nudeForm.fill @resource
183
+ errors = {"zombie.in1.subin1": ['suck'], "zombie.in2": ['rock']}
184
+ result = @nudeForm.__stringifyErrors(errors)
185
+
186
+ expect(result).toEqual {"test[zombie][in1][subin1]": ['suck'], "test[zombie][in2]": ['rock']}
187
+
188
+ it "should prepare simple response with resource attached", ->
189
+ @nudeForm.fill @resource
190
+ errors = {zombie: ['suck'], puppies: ['rock']}
191
+ result = @nudeForm.__stringifyErrors(errors)
192
+
193
+ expect(result).toEqual { "test[zombie]": ['suck'], "test[puppies]": ['rock'] }
194
+
195
+ it "should prepare complexe response", ->
196
+ @nudeForm.fill @resource
197
+ errors = {fluffies: {zombie: {mumbas: ['ololo']}}}
198
+ result = @nudeForm.__stringifyErrors(errors)
199
+
200
+ expect(result).toEqual { "fluffies[zombie][mumbas]": ['ololo'] }
@@ -0,0 +1,103 @@
1
+ describe "Joosy.Helpers.Form", ->
2
+ beforeEach ->
3
+ @addMatchers toBeTag: (tagName, content, attrs) ->
4
+ @message = =>
5
+ "Expected #{@actual} to be a tag #{tagName} with attributes #{JSON.stringify attrs} and content #{content}"
6
+
7
+ tag = $ @actual
8
+ flag = true
9
+
10
+ flag = flag && tag.length == 1
11
+ flag = flag && tag[0].nodeName == tagName.toUpperCase()
12
+ flag = flag && tag.html() == content
13
+
14
+ for name, val of attrs
15
+ flag = flag && tag.attr(name) == val
16
+
17
+ flag = flag && tag[0].attributes.length == Object.keys(attrs).length
18
+
19
+ flag
20
+
21
+ class Test extends Joosy.Resource.Generic
22
+ @entity 'test'
23
+
24
+ h = Joosy.Helpers.Application
25
+ resource = Test.build 1
26
+
27
+ describe "simple", ->
28
+ ['text', 'file', 'hidden', 'password'].each (type) =>
29
+ it "renders #{type}Field", ->
30
+ expect(h["#{type}Field"] 'a', 'b', {a: 'b'}).toBeTag 'input', '', id: 'a_b', name: 'a[b]', a: 'b', type: type
31
+
32
+ it "renders nested methods", ->
33
+ expect(h.textField 'a', '[b]', {a: 'b'}).toBeTag 'input', '', id: 'a_b', name: 'a[b]', a: 'b', type: 'text'
34
+ expect(h.textField 'a', '[b][c]', {a: 'b'}).toBeTag 'input', '', id: 'a_b_c', name: 'a[b][c]', a: 'b', type: 'text'
35
+
36
+ it "renders label", ->
37
+ expect(h.label 'a', 'b', {a: 'b'}, 'test').toBeTag 'label', 'test', for: 'a_b', a: 'b'
38
+
39
+ it "renders checkBox", ->
40
+ tags = h.checkBox('a', 'b', {a: 'b'}).match(/<[^>]+>/g)
41
+
42
+ expect(tags[0]).toBeTag 'input', '', value: '0', id: 'a_b', name: 'a[b]', type: 'hidden'
43
+ expect(tags[1]).toBeTag 'input', '', value: '1', id: 'a_b', name: 'a[b]', type: 'checkbox', a: 'b'
44
+
45
+ it "renders radioButton", ->
46
+ expect(h.radioButton 'a', 'b', 'test', {a: 'b'}).toBeTag 'input', '', value: 'test', id: 'a_b', name: 'a[b]', type: 'radio', a: 'b'
47
+
48
+ it "renders textArea", ->
49
+ expect(h.textArea 'a', 'b', {a: 'b', value: 'foo'}).toBeTag 'textarea', 'foo', id: 'a_b', name: 'a[b]', a: 'b'
50
+
51
+ it "renders formFor", ->
52
+ callback = sinon.spy()
53
+ expect(h.formFor resource, callback).toMatch /<form id=".*"><\/form>/
54
+ expect(callback.callCount).toEqual 1
55
+ expect(callback.args[0][0].label?).toBeTruthy()
56
+
57
+ describe "resource", ->
58
+ callback = sinon.spy()
59
+ h.formFor(resource, callback)
60
+ form = callback.args[0][0]
61
+
62
+ ['text', 'file', 'hidden', 'password'].each (type) =>
63
+ it "renders #{type}Field", ->
64
+ expect(form["#{type}Field"] 'b', {a: 'b'}).toBeTag 'input', '', id: 'test_b', name: 'test[b]', a: 'b', type: type
65
+
66
+ it "renders label", ->
67
+ expect(form.label 'b', {a: 'b'}, 'test').toBeTag 'label', 'test', for: 'test_b', a: 'b'
68
+
69
+ it "renders checkBox", ->
70
+ tags = form.checkBox('b', {a: 'b'}).match(/<[^>]+>/g)
71
+
72
+ expect(tags[0]).toBeTag 'input', '', value: '0', id: 'test_b', name: 'test[b]', type: 'hidden'
73
+ expect(tags[1]).toBeTag 'input', '', value: '1', id: 'test_b', name: 'test[b]', type: 'checkbox', a: 'b'
74
+
75
+ it "renders radioButton", ->
76
+ expect(form.radioButton 'b', 'test', {a: 'b'}).toBeTag 'input', '', value: 'test', id: 'test_b', name: 'test[b]', type: 'radio', a: 'b'
77
+
78
+ it "renders textArea", ->
79
+ expect(form.textArea 'b', {a: 'b', value: 'foo'}).toBeTag 'textarea', 'foo', id: 'test_b', name: 'test[b]', a: 'b'
80
+
81
+ describe "resource with extendIds", ->
82
+ callback = sinon.spy()
83
+ h.formFor(resource, extendIds: true, callback)
84
+ form = callback.args[0][0]
85
+
86
+ ['text', 'file', 'hidden', 'password'].each (type) =>
87
+ it "renders #{type}Field", ->
88
+ expect(form["#{type}Field"] 'b', {a: 'b'}).toBeTag 'input', '', id: 'test_1_b', name: 'test[b]', a: 'b', type: type
89
+
90
+ it "renders label", ->
91
+ expect(form.label 'b', {a: 'b'}, 'test').toBeTag 'label', 'test', for: 'test_1_b', a: 'b'
92
+
93
+ it "renders checkBox", ->
94
+ tags = form.checkBox('b', {a: 'b'}).match(/<[^>]+>/g)
95
+
96
+ expect(tags[0]).toBeTag 'input', '', value: '0', id: 'test_1_b', name: 'test[b]', type: 'hidden'
97
+ expect(tags[1]).toBeTag 'input', '', value: '1', id: 'test_1_b', name: 'test[b]', type: 'checkbox', a: 'b'
98
+
99
+ it "renders radioButton", ->
100
+ expect(form.radioButton 'b', 'test', {a: 'b'}).toBeTag 'input', '', value: 'test', id: 'test_1_b', name: 'test[b]', type: 'radio', a: 'b'
101
+
102
+ it "renders textArea", ->
103
+ expect(form.textArea 'b', {a: 'b', value: 'foo'}).toBeTag 'textarea', 'foo', id: 'test_1_b', name: 'test[b]', a: 'b'
@@ -0,0 +1,10 @@
1
+ describe "Joosy.Helpers.View", ->
2
+
3
+ h = Joosy.Helpers.Application
4
+
5
+ it "renders tag with string inner", ->
6
+ expect(h.tag 'div', {id: 'ololo'}, 'test').toEqual '<div id="ololo">test</div>'
7
+
8
+ it "renders tag with lambda inner", ->
9
+ data = h.tag 'div', {id: 'ololo'}, -> h.tag 'div', {id: 'ololo'}, 'test'
10
+ expect(data).toEqual '<div id="ololo"><div id="ololo">test</div></div>'
@@ -0,0 +1,97 @@
1
+ describe "Joosy", ->
2
+
3
+ it "should properly initialize", ->
4
+ expect(Joosy.debug).toBeFalsy()
5
+ expect(Joosy.Modules).toBeDefined()
6
+ expect(Joosy.Resource).toBeDefined()
7
+
8
+ it "should declare namespaces", ->
9
+ Joosy.namespace 'Namespaces.Test1'
10
+ Joosy.namespace 'Namespaces.Test2', ->
11
+ @bingo = 'bongo'
12
+ expect(window.Namespaces.Test1).toBeDefined()
13
+ expect(window.Namespaces.Test2.bingo).toEqual('bongo')
14
+
15
+ it "should imprint namespace paths in Joosy.Module descendants", ->
16
+ Joosy.namespace 'Irish', ->
17
+ class @Pub extends Joosy.Module
18
+
19
+ Joosy.namespace 'British', ->
20
+ class @Pub extends Joosy.Module
21
+
22
+ Joosy.namespace 'Keltic', ->
23
+ class @Pub extends Irish.Pub
24
+
25
+ expect(Irish.Pub.__namespace__).toEqual ["Irish"]
26
+ expect(British.Pub.__namespace__).toEqual ["British"]
27
+ expect(Keltic.Pub.__namespace__).toEqual ["Keltic"]
28
+
29
+ Joosy.namespace 'Deeply.Nested', ->
30
+ class @Klass extends Joosy.Module
31
+
32
+ expect(Deeply.Nested.Klass.__namespace__).toEqual ["Deeply", "Nested"]
33
+
34
+ class @Flat extends Joosy.Module
35
+
36
+ expect(@Flat.__namespace__).toEqual []
37
+
38
+ it "should set up helpers", ->
39
+ Joosy.helpers 'Hoge', ->
40
+ @fuga = ->
41
+ "piyo"
42
+
43
+ expect(window.Joosy.Helpers).toBeDefined()
44
+ expect(window.Joosy.Helpers.Hoge).toBeDefined()
45
+ expect(window.Joosy.Helpers.Hoge.fuga()).toBe "piyo"
46
+
47
+ it "should generate proper UUIDs", ->
48
+ uuids = []
49
+ 2.times ->
50
+ uuids.push Joosy.uuid()
51
+ expect(uuids.unique().length).toEqual(2)
52
+ expect(uuids[0]).toMatch /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}/
53
+
54
+ it "should build proper URLs", ->
55
+ expect(Joosy.buildUrl 'http://www.org').toEqual('http://www.org')
56
+ expect(Joosy.buildUrl 'http://www.org#hash').toEqual('http://www.org#hash')
57
+ expect(Joosy.buildUrl 'http://www.org', {foo: 'bar'}).toEqual('http://www.org?foo=bar')
58
+ expect(Joosy.buildUrl 'http://www.org?bar=baz', {foo: 'bar'}).toEqual('http://www.org?bar=baz&foo=bar')
59
+
60
+ it "should preload images", ->
61
+ path = "/spec/javascripts/support/assets/"
62
+ images = [path+"okay.jpg", path+"okay.jpg"]
63
+
64
+ callback = sinon.spy()
65
+
66
+ runs -> Joosy.preloadImages path+"coolface.jpg", callback
67
+ waits(150)
68
+ runs -> expect(callback.callCount).toEqual(1)
69
+
70
+ runs -> Joosy.preloadImages images, callback
71
+ waits(150)
72
+ runs -> expect(callback.callCount).toEqual(2)
73
+
74
+ it "should define resource", ->
75
+ container = {}
76
+ Joosy.defineResource 'foo', '', container
77
+ expect(Object.isFunction container.Foo).toBeTruthy()
78
+ expect(Object.isFunction container.FoosCollection).toBeTruthy()
79
+ Joosy.defineResource 'boo'
80
+ expect(Object.isFunction Boo).toBeTruthy()
81
+ expect(Object.isFunction BoosCollection).toBeTruthy()
82
+
83
+ it "should define resource", ->
84
+ window.Defined = 'this'
85
+ window.DefinedsCollection = 'that'
86
+ Joosy.defineResources
87
+ '':
88
+ test: '/test'
89
+ defined: 'no'
90
+ 'Scope.Test':
91
+ another: ''
92
+ expect(Object.isFunction Test).toBeTruthy()
93
+ expect(Object.isFunction TestsCollection).toBeTruthy()
94
+ expect(Object.isFunction Scope.Test.Another).toBeTruthy()
95
+ expect(Object.isFunction Scope.Test.AnothersCollection).toBeTruthy()
96
+ expect(Defined).toEqual 'this'
97
+ expect(DefinedsCollection).toEqual 'that'
@@ -0,0 +1,50 @@
1
+ describe "Joosy.Layout", ->
2
+
3
+ beforeEach ->
4
+ class @TestLayout extends Joosy.Layout
5
+ @box = new @TestLayout()
6
+
7
+ it "should have appropriate accessors", ->
8
+ callback_names = ['beforePaint', 'paint', 'erase']
9
+ callback_names.each (func) =>
10
+ @TestLayout[func] 'callback'
11
+ expect(@TestLayout::['__' + func]).toEqual 'callback'
12
+
13
+ it "should have default view", ->
14
+ @box = new @TestLayout()
15
+ expect(@box.__renderer instanceof Function).toBeTruthy()
16
+
17
+ it "should use Router", ->
18
+ target = sinon.stub Joosy.Router, 'navigate'
19
+ @box.navigate 'there'
20
+ expect(target.callCount).toEqual 1
21
+ expect(target.alwaysCalledWithExactly 'there').toBeTruthy()
22
+ Joosy.Router.navigate.restore()
23
+
24
+ it "should load itself", ->
25
+ spies = []
26
+ spies.push sinon.spy(@box, 'refreshElements')
27
+ spies.push sinon.spy(@box, '__delegateEvents')
28
+ spies.push sinon.spy(@box, '__setupWidgets')
29
+ spies.push sinon.spy(@box, '__runAfterLoads')
30
+ @box.__load(@ground)
31
+ expect(spies).toBeSequenced()
32
+
33
+ it "should unload itself", ->
34
+ spies = []
35
+ spies.push sinon.spy(@box, '__clearTime')
36
+ spies.push sinon.spy(@box, '__unloadWidgets')
37
+ spies.push sinon.spy(@box, '__runAfterUnloads')
38
+ @box.__unload()
39
+ expect(spies).toBeSequenced()
40
+
41
+ it "should generate uuid", ->
42
+ sinon.spy Joosy, 'uuid'
43
+ @box.yield()
44
+ expect(Joosy.uuid.callCount).toEqual 1
45
+ expect(@box.uuid).toBeDefined()
46
+ Joosy.uuid.restore()
47
+
48
+ it "should uuid as selector", ->
49
+ @box.yield()
50
+ expect(@box.content().selector).toEqual '#' + @box.uuid