mercury-rails 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.
Files changed (153) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +152 -0
  3. data/VERSION +1 -0
  4. data/app/assets/images/mercury/button.png +0 -0
  5. data/app/assets/images/mercury/clippy.png +0 -0
  6. data/app/assets/images/mercury/default-snippet.png +0 -0
  7. data/app/assets/images/mercury/loading-dark.gif +0 -0
  8. data/app/assets/images/mercury/loading-light.gif +0 -0
  9. data/app/assets/images/mercury/search-icon.png +0 -0
  10. data/app/assets/images/mercury/toolbar/editable/buttons.png +0 -0
  11. data/app/assets/images/mercury/toolbar/markupable/buttons.png +0 -0
  12. data/app/assets/images/mercury/toolbar/primary/_expander.png +0 -0
  13. data/app/assets/images/mercury/toolbar/primary/_pressed.png +0 -0
  14. data/app/assets/images/mercury/toolbar/primary/historypanel.png +0 -0
  15. data/app/assets/images/mercury/toolbar/primary/insertcharacter.png +0 -0
  16. data/app/assets/images/mercury/toolbar/primary/insertlink.png +0 -0
  17. data/app/assets/images/mercury/toolbar/primary/insertmedia.png +0 -0
  18. data/app/assets/images/mercury/toolbar/primary/inserttable.png +0 -0
  19. data/app/assets/images/mercury/toolbar/primary/inspectorpanel.png +0 -0
  20. data/app/assets/images/mercury/toolbar/primary/notespanel.png +0 -0
  21. data/app/assets/images/mercury/toolbar/primary/objectspanel.png +0 -0
  22. data/app/assets/images/mercury/toolbar/primary/preview.png +0 -0
  23. data/app/assets/images/mercury/toolbar/primary/redo.png +0 -0
  24. data/app/assets/images/mercury/toolbar/primary/save.png +0 -0
  25. data/app/assets/images/mercury/toolbar/primary/todospanel.png +0 -0
  26. data/app/assets/images/mercury/toolbar/primary/undo.png +0 -0
  27. data/app/assets/images/mercury/toolbar/snippetable/buttons.png +0 -0
  28. data/app/assets/javascripts/mercury.js +30 -0
  29. data/app/assets/javascripts/mercury/dialog.js.coffee +75 -0
  30. data/app/assets/javascripts/mercury/dialogs/backcolor.js.coffee +6 -0
  31. data/app/assets/javascripts/mercury/dialogs/forecolor.js.coffee +6 -0
  32. data/app/assets/javascripts/mercury/dialogs/formatblock.js.coffee +4 -0
  33. data/app/assets/javascripts/mercury/dialogs/objectspanel.js.coffee +10 -0
  34. data/app/assets/javascripts/mercury/dialogs/style.js.coffee +4 -0
  35. data/app/assets/javascripts/mercury/history_buffer.js.coffee +30 -0
  36. data/app/assets/javascripts/mercury/mercury.js.coffee +293 -0
  37. data/app/assets/javascripts/mercury/modal.js.coffee +177 -0
  38. data/app/assets/javascripts/mercury/modals/htmleditor.js.coffee +10 -0
  39. data/app/assets/javascripts/mercury/modals/insertcharacter.js.coffee +4 -0
  40. data/app/assets/javascripts/mercury/modals/insertlink.js.coffee +92 -0
  41. data/app/assets/javascripts/mercury/modals/insertmedia.js.coffee +72 -0
  42. data/app/assets/javascripts/mercury/modals/insertsnippet.js.coffee +11 -0
  43. data/app/assets/javascripts/mercury/modals/inserttable.js.coffee +56 -0
  44. data/app/assets/javascripts/mercury/native_extensions.js.coffee +47 -0
  45. data/app/assets/javascripts/mercury/page_editor.js.coffee +139 -0
  46. data/app/assets/javascripts/mercury/palette.js.coffee +29 -0
  47. data/app/assets/javascripts/mercury/panel.js.coffee +97 -0
  48. data/app/assets/javascripts/mercury/region.js.coffee +103 -0
  49. data/app/assets/javascripts/mercury/regions/editable.js.coffee +546 -0
  50. data/app/assets/javascripts/mercury/regions/markupable.js.coffee +380 -0
  51. data/app/assets/javascripts/mercury/regions/snippetable.js.coffee +127 -0
  52. data/app/assets/javascripts/mercury/select.js.coffee +40 -0
  53. data/app/assets/javascripts/mercury/snippet.js.coffee +92 -0
  54. data/app/assets/javascripts/mercury/snippet_toolbar.js.coffee +69 -0
  55. data/app/assets/javascripts/mercury/statusbar.js.coffee +25 -0
  56. data/app/assets/javascripts/mercury/table_editor.js.coffee +266 -0
  57. data/app/assets/javascripts/mercury/toolbar.button.js.coffee +152 -0
  58. data/app/assets/javascripts/mercury/toolbar.button_group.js.coffee +42 -0
  59. data/app/assets/javascripts/mercury/toolbar.expander.js.coffee +56 -0
  60. data/app/assets/javascripts/mercury/toolbar.js.coffee +72 -0
  61. data/app/assets/javascripts/mercury/tooltip.js.coffee +67 -0
  62. data/app/assets/javascripts/mercury/uploader.js.coffee +213 -0
  63. data/app/assets/javascripts/mercury/websocket.js.coffee +34 -0
  64. data/app/assets/stylesheets/mercury.css +31 -0
  65. data/app/assets/stylesheets/mercury/dialog.scss +178 -0
  66. data/app/assets/stylesheets/mercury/mercury.scss +119 -0
  67. data/app/assets/stylesheets/mercury/modal.scss +192 -0
  68. data/app/assets/stylesheets/mercury/statusbar.scss +23 -0
  69. data/app/assets/stylesheets/mercury/toolbar.scss +417 -0
  70. data/app/assets/stylesheets/mercury/tooltip.scss +26 -0
  71. data/app/assets/stylesheets/mercury/uploader.scss +109 -0
  72. data/app/controllers/images_controller.rb +19 -0
  73. data/app/controllers/mercury_controller.rb +20 -0
  74. data/app/models/image.rb +14 -0
  75. data/app/views/layouts/mercury.html.haml +12 -0
  76. data/app/views/mercury/modals/character.html.haml +252 -0
  77. data/app/views/mercury/modals/htmleditor.html.haml +8 -0
  78. data/app/views/mercury/modals/link.html.haml +31 -0
  79. data/app/views/mercury/modals/media.html.haml +33 -0
  80. data/app/views/mercury/modals/sanitizer.html.haml +4 -0
  81. data/app/views/mercury/modals/table.html.haml +49 -0
  82. data/app/views/mercury/palettes/backcolor.html.haml +79 -0
  83. data/app/views/mercury/palettes/forecolor.html.haml +79 -0
  84. data/app/views/mercury/panels/history.html.haml +0 -0
  85. data/app/views/mercury/panels/notes.html.haml +0 -0
  86. data/app/views/mercury/panels/snippets.html.haml +10 -0
  87. data/app/views/mercury/selects/formatblock.html.haml +10 -0
  88. data/app/views/mercury/selects/style.html.haml +4 -0
  89. data/app/views/mercury/snippets/example.html.haml +2 -0
  90. data/app/views/mercury/snippets/example_options.html.haml +16 -0
  91. data/config/engine.rb +6 -0
  92. data/config/routes.rb +15 -0
  93. data/db/migrate/20110526035601_create_images.rb +11 -0
  94. data/features/editing/basic.feature +11 -0
  95. data/features/step_definitions/debug_steps.rb +14 -0
  96. data/features/step_definitions/web_steps.rb +211 -0
  97. data/features/support/env.rb +46 -0
  98. data/features/support/paths.rb +35 -0
  99. data/features/support/selectors.rb +42 -0
  100. data/lib/mercury-rails.rb +4 -0
  101. data/log/.gitkeep +0 -0
  102. data/mercury-rails.gemspec +230 -0
  103. data/spec/javascripts/mercury/dialog_spec.js.coffee +258 -0
  104. data/spec/javascripts/mercury/history_buffer_spec.js.coffee +79 -0
  105. data/spec/javascripts/mercury/mercury_spec.js.coffee +52 -0
  106. data/spec/javascripts/mercury/native_extensions_spec.js.coffee +66 -0
  107. data/spec/javascripts/mercury/page_editor_spec.js.coffee +435 -0
  108. data/spec/javascripts/mercury/palette_spec.js.coffee +51 -0
  109. data/spec/javascripts/mercury/panel_spec.js.coffee +147 -0
  110. data/spec/javascripts/mercury/region_spec.js.coffee +261 -0
  111. data/spec/javascripts/mercury/regions/_editable_.js.coffee +0 -0
  112. data/spec/javascripts/mercury/regions/_markupable_.js.coffee +0 -0
  113. data/spec/javascripts/mercury/regions/snippetable_spec.js.coffee +368 -0
  114. data/spec/javascripts/mercury/select_spec.js.coffee +51 -0
  115. data/spec/javascripts/mercury/snippet_spec.js.coffee +246 -0
  116. data/spec/javascripts/mercury/snippet_toolbar_spec.js.coffee +186 -0
  117. data/spec/javascripts/mercury/statusbar_spec.js.coffee +78 -0
  118. data/spec/javascripts/mercury/table_editor_spec.js.coffee +192 -0
  119. data/spec/javascripts/mercury/toolbar.button_group_spec.js.coffee +92 -0
  120. data/spec/javascripts/mercury/toolbar.button_spec.js.coffee +341 -0
  121. data/spec/javascripts/mercury/toolbar.expander_spec.js.coffee +120 -0
  122. data/spec/javascripts/mercury/toolbar_spec.js.coffee +152 -0
  123. data/spec/javascripts/mercury/tooltip_spec.js.coffee +188 -0
  124. data/spec/javascripts/mercury/uploader_spec.js.coffee +512 -0
  125. data/spec/javascripts/responses/blank.html +1 -0
  126. data/spec/javascripts/spec_helper.js +513 -0
  127. data/spec/javascripts/templates/mercury/dialog.html +2 -0
  128. data/spec/javascripts/templates/mercury/page_editor.html +24 -0
  129. data/spec/javascripts/templates/mercury/palette.html +16 -0
  130. data/spec/javascripts/templates/mercury/panel.html +16 -0
  131. data/spec/javascripts/templates/mercury/region.html +2 -0
  132. data/spec/javascripts/templates/mercury/regions/snippetable.html +4 -0
  133. data/spec/javascripts/templates/mercury/select.html +16 -0
  134. data/spec/javascripts/templates/mercury/snippet.html +1 -0
  135. data/spec/javascripts/templates/mercury/snippet_toolbar.html +16 -0
  136. data/spec/javascripts/templates/mercury/statusbar.html +7 -0
  137. data/spec/javascripts/templates/mercury/table_editor.html +65 -0
  138. data/spec/javascripts/templates/mercury/toolbar.button.html +64 -0
  139. data/spec/javascripts/templates/mercury/toolbar.button_group.html +9 -0
  140. data/spec/javascripts/templates/mercury/toolbar.expander.html +18 -0
  141. data/spec/javascripts/templates/mercury/toolbar.html +10 -0
  142. data/spec/javascripts/templates/mercury/tooltip.html +12 -0
  143. data/spec/javascripts/templates/mercury/uploader.html +11 -0
  144. data/vendor/assets/javascripts/jquery-1.6.js +8865 -0
  145. data/vendor/assets/javascripts/jquery-ui-1.8.13.custom.min.js +249 -0
  146. data/vendor/assets/javascripts/jquery-ui-1.8.13.sortable.custom.js +1078 -0
  147. data/vendor/assets/javascripts/jquery.easing.js +173 -0
  148. data/vendor/assets/javascripts/jquery.json2.js +178 -0
  149. data/vendor/assets/javascripts/jquery.serialize_object.js +16 -0
  150. data/vendor/assets/javascripts/jquery.ujs.js +289 -0
  151. data/vendor/assets/javascripts/liquidmetal.js +88 -0
  152. data/vendor/assets/javascripts/showdown.js +1362 -0
  153. metadata +364 -0
@@ -0,0 +1,152 @@
1
+ require '/assets/mercury/mercury.js'
2
+
3
+ describe "Mercury.Toolbar", ->
4
+
5
+ template 'mercury/toolbar.html'
6
+
7
+ beforeEach ->
8
+ ajaxSpy = spyOn($, 'ajax').andCallFake (url, options) =>
9
+ options.success('data') if options.success
10
+
11
+ afterEach ->
12
+ @toolbar = null
13
+ delete(@toolbar)
14
+
15
+ describe "constructor", ->
16
+
17
+ beforeEach ->
18
+ @buildSpy = spyOn(Mercury.Toolbar.prototype, 'build').andCallFake(=>)
19
+ @bindEventsSpy = spyOn(Mercury.Toolbar.prototype, 'bindEvents').andCallFake(=>)
20
+ @toolbar = new Mercury.Toolbar({appendTo: '#test', foo: true})
21
+
22
+ it "accepts options as an argument", ->
23
+ expect(@toolbar.options).toEqual({appendTo: '#test', foo: true})
24
+
25
+ it "calls build", ->
26
+ expect(@buildSpy.callCount).toEqual(1)
27
+
28
+ it "calls bindEvents", ->
29
+ expect(@bindEventsSpy.callCount).toEqual(1)
30
+
31
+
32
+ describe "#height", ->
33
+
34
+ beforeEach ->
35
+ spyOn(Mercury.Toolbar.prototype, 'buildButton').andCallFake(=> $('<div>'))
36
+ spyOn(Mercury.Toolbar.prototype, 'bindEvents').andCallFake(=>)
37
+ @toolbar = new Mercury.Toolbar({appendTo: '#test'})
38
+
39
+ it "knows it's own height", ->
40
+ expect(@toolbar.height()).toEqual(200) # styled with css in the template
41
+
42
+
43
+ describe "#build", ->
44
+
45
+ beforeEach ->
46
+ @buildButtonSpy = spyOn(Mercury.Toolbar.prototype, 'buildButton').andCallFake(=> $('<div>'))
47
+ @toolbar = new Mercury.Toolbar({appendTo: '#toolbar_container'})
48
+
49
+ it "builds an element", ->
50
+ expect($('.mercury-toolbar-container').length).toEqual(1)
51
+
52
+ it "can append to any element", ->
53
+ expect($('#toolbar_container .mercury-toolbar-container').length).toEqual(1)
54
+
55
+ it "builds out toolbar elements from the configuration", ->
56
+ expect($('.mercury-primary-toolbar').length).toEqual(1)
57
+ expect($('.mercury-editable-toolbar').length).toEqual(1)
58
+ expect($('.mercury-editable-toolbar').data('regions')).toEqual('editable,markupable')
59
+
60
+ it "builds buttons etc.", ->
61
+ expect(@buildButtonSpy.callCount).toBeGreaterThan(10)
62
+
63
+ it "sets it's width back to 100%", ->
64
+ expect(@toolbar.element.get(0).style.width).toEqual('100%')
65
+
66
+ it "disables all but the primary toolbar", ->
67
+ expect($('.mercury-editable-toolbar').hasClass('disabled')).toEqual(true)
68
+
69
+ it "adds an expander when white-space: nowrap (meaning the toolbar shouldn't wrap)", ->
70
+ expect($('.mercury-toolbar-button-container').length).toBeGreaterThan(1)
71
+ expect($('.mercury-toolbar-expander').length).toEqual(1)
72
+
73
+
74
+ describe "#buildButton", ->
75
+
76
+ beforeEach ->
77
+ @toolbar = new Mercury.Toolbar({appendTo: '#test'})
78
+
79
+ it "throws an exception when invalid options are passed", ->
80
+ expect(=> @toolbar.buildButton('foo', false)).toThrow('Unknown button structure -- please provide an array, object, or string for foo.')
81
+
82
+ it "returns false if the name is _custom, or _regions", ->
83
+ expect(@toolbar.buildButton('_custom', 'foo')).toEqual(false)
84
+ expect(@toolbar.buildButton('_regions', ['regiontype', 'another_regiontype'])).toEqual(false)
85
+
86
+ it "builds buttons", ->
87
+ html = $('<div>').html(@toolbar.buildButton('foobutton', ['title', 'summary', {}])).html()
88
+ expect(html).toContain('title="summary"')
89
+ expect(html).toContain('class="mercury-button mercury-foobutton-button"')
90
+ expect(html).toContain('<em>title</em>')
91
+
92
+ it "builds button groups", ->
93
+ html = $('<div>').html(@toolbar.buildButton('foogroup', {foobutton: ['title', 'summary', {}]})).html()
94
+ expect(html).toContain('class="mercury-button-group mercury-foogroup-group"')
95
+ expect(html).toContain('title="summary"')
96
+ expect(html).toContain('class="mercury-button mercury-foobutton-button"')
97
+ expect(html).toContain('<em>title</em>')
98
+
99
+ it "builds separators", ->
100
+ html = $('<div>').html(@toolbar.buildButton('foosep1', ' ')).html()
101
+ expect(html).toEqual('<hr class="mercury-separator">')
102
+
103
+ html = $('<div>').html(@toolbar.buildButton('foosep1', '-')).html()
104
+ expect(html).toEqual('<hr class="mercury-line-separator">')
105
+
106
+ it "builds buttons from configuration", ->
107
+ expect($('.mercury-primary-toolbar .mercury-save-button').length).toEqual(1)
108
+ expect($('.mercury-primary-toolbar .mercury-preview-button').length).toEqual(1)
109
+
110
+ it "builds button groups from configuration", ->
111
+ expect($('.mercury-editable-toolbar .mercury-decoration-group').length).toEqual(1)
112
+ expect($('.mercury-editable-toolbar .mercury-script-group').length).toEqual(1)
113
+
114
+ it "builds separators from configuration", ->
115
+ expect($('.mercury-separator').length).toBeGreaterThan(1);
116
+ expect($('.mercury-line-separator').length).toBeGreaterThan(1);
117
+
118
+
119
+ describe "observed events", ->
120
+
121
+ beforeEach ->
122
+ @toolbar = new Mercury.Toolbar({appendTo: '#test'})
123
+
124
+ describe "custom event: region:focused", ->
125
+
126
+ it "enables toolbars based on the region type", ->
127
+ $('.mercury-editable-toolbar').addClass('disabled')
128
+ Mercury.trigger('region:focused', {region: {type: 'editable'}})
129
+ expect($('.mercury.editable-toolbar').hasClass('disabled')).toEqual(false)
130
+
131
+ $('.mercury-editable-toolbar').addClass('disabled')
132
+ Mercury.trigger('region:focused', {region: {type: 'markupable'}})
133
+ expect($('.mercury.editable-toolbar').hasClass('disabled')).toEqual(false)
134
+
135
+
136
+ describe "custom event: region:blurred", ->
137
+
138
+ it "disables toolbars for the region type", ->
139
+ $('.mercury-editable-toolbar').removeClass('disabled')
140
+ Mercury.trigger('region:blurred', {region: {type: 'editable'}})
141
+ expect($('.mercury-editable-toolbar').hasClass('disabled')).toEqual(true)
142
+
143
+
144
+ describe "click", ->
145
+
146
+ it "triggers hide:dialogs", ->
147
+ spy = spyOn(Mercury, 'trigger')
148
+
149
+ jasmine.simulate.click(@toolbar.element.get(0))
150
+
151
+ expect(spy.callCount).toEqual(1)
152
+ expect(spy.argsForCall[0]).toEqual(['hide:dialogs'])
@@ -0,0 +1,188 @@
1
+ require '/assets/mercury/mercury.js'
2
+
3
+ describe "Mercury.tooltip", ->
4
+
5
+ template 'mercury/tooltip.html'
6
+
7
+ beforeEach ->
8
+ @forElement = $('#for_element')
9
+ $.fx.off = true
10
+
11
+ afterEach ->
12
+ Mercury.tooltip.visible = false
13
+ Mercury.tooltip.initialized = false
14
+
15
+ describe "singleton method", ->
16
+
17
+ beforeEach ->
18
+ @showSpy = spyOn(Mercury.tooltip, 'show').andCallFake(=>)
19
+
20
+ it "calls show", ->
21
+ Mercury.tooltip()
22
+ expect(@showSpy.callCount).toEqual(1)
23
+
24
+ it "returns the function object", ->
25
+ ret = Mercury.tooltip()
26
+ expect(ret).toEqual(Mercury.tooltip)
27
+
28
+
29
+ describe "#show", ->
30
+
31
+ beforeEach ->
32
+ @initializeSpy = spyOn(Mercury.tooltip, 'initialize').andCallFake(=>)
33
+ @updateSpy = spyOn(Mercury.tooltip, 'update').andCallFake(=>)
34
+ @appearSpy = spyOn(Mercury.tooltip, 'appear').andCallFake(=>)
35
+
36
+ it "gets the document from the element passed in", ->
37
+ Mercury.tooltip.show(@forElement, 'content')
38
+ expect(Mercury.tooltip.document.get(0)).toEqual($(document).get(0))
39
+
40
+ it "calls initialize", ->
41
+ Mercury.tooltip.show(@forElement, 'content')
42
+ expect(@initializeSpy.callCount).toEqual(1)
43
+
44
+ describe "if visible", ->
45
+
46
+ beforeEach ->
47
+ Mercury.tooltip.visible = true
48
+
49
+ it "calls update", ->
50
+ Mercury.tooltip.show(@forElement, 'content')
51
+ expect(@updateSpy.callCount).toEqual(1)
52
+
53
+ describe "if not visible", ->
54
+
55
+ beforeEach ->
56
+ Mercury.tooltip.visible = false
57
+
58
+ it "calls appear", ->
59
+ Mercury.tooltip.show(@forElement, 'content')
60
+ expect(@appearSpy.callCount).toEqual(1)
61
+
62
+
63
+ describe "#initialize", ->
64
+
65
+ it "calls build", ->
66
+ spy = spyOn(Mercury.tooltip, 'build').andCallFake(=>)
67
+ spyOn(Mercury.tooltip, 'bindEvents').andCallFake(=>)
68
+ Mercury.tooltip.initialize()
69
+ expect(spy.callCount).toEqual(1)
70
+
71
+ it "calls bindEvents", ->
72
+ spy = spyOn(Mercury.tooltip, 'bindEvents').andCallFake(=>)
73
+ Mercury.tooltip.initialize()
74
+ expect(spy.callCount).toEqual(1)
75
+
76
+ it "sets initialized to true", ->
77
+ Mercury.tooltip.initialize()
78
+ expect(Mercury.tooltip.initialized).toEqual(true)
79
+
80
+ it "does nothing if already initialized", ->
81
+ spy = spyOn(Mercury.tooltip, 'bindEvents').andCallFake(=>)
82
+ Mercury.tooltip.initialized = true
83
+ Mercury.tooltip.initialize()
84
+ expect(spy.callCount).toEqual(0)
85
+
86
+
87
+ describe "#build", ->
88
+
89
+ it "builds an element", ->
90
+ Mercury.tooltip.build()
91
+ html = $('<div>').html(Mercury.tooltip.element).html()
92
+ expect(html).toContain('class="mercury-tooltip"')
93
+
94
+ it "can append to any element", ->
95
+ Mercury.tooltip.options = {appendTo: '#tooltip_container'}
96
+ Mercury.tooltip.build()
97
+ expect($('#tooltip_container').html()).toContain('class="mercury-tooltip"')
98
+
99
+
100
+ describe "observed events", ->
101
+
102
+ describe "custom event: resize", ->
103
+
104
+ it "call position if visible", ->
105
+ Mercury.tooltip.visible = true
106
+ spy = spyOn(Mercury.tooltip, 'position').andCallFake(=>)
107
+ Mercury.trigger('resize')
108
+ expect(spy.callCount).toEqual(1)
109
+
110
+ Mercury.tooltip.visible = false
111
+ Mercury.trigger('resize')
112
+ expect(spy.callCount).toEqual(1)
113
+
114
+ describe "document scrolling", ->
115
+
116
+ it "calls position if visible", ->
117
+ # untestable
118
+
119
+ describe "element mousedown", ->
120
+
121
+ it "stops the event", ->
122
+ # untestable
123
+
124
+
125
+ describe "#appear", ->
126
+
127
+ beforeEach ->
128
+ Mercury.tooltip.build()
129
+ @updateSpy = spyOn(Mercury.tooltip, 'update').andCallFake(=>)
130
+
131
+ it "calls update", ->
132
+ Mercury.tooltip.appear()
133
+ expect(@updateSpy.callCount).toEqual(1)
134
+
135
+ it "shows the element", ->
136
+ Mercury.tooltip.appear()
137
+ expect(Mercury.tooltip.element.css('display')).toEqual('block')
138
+ expect(Mercury.tooltip.element.css('opacity')).toEqual('1')
139
+
140
+ it "sets visible to true", ->
141
+ Mercury.tooltip.visible = false
142
+ Mercury.tooltip.appear()
143
+ expect(Mercury.tooltip.visible).toEqual(true)
144
+
145
+
146
+ describe "#update", ->
147
+
148
+ beforeEach ->
149
+ Mercury.tooltip.build()
150
+ @positionSpy = spyOn(Mercury.tooltip, 'position').andCallFake(=>)
151
+
152
+ it "sets the html", ->
153
+ Mercury.tooltip.content = 'foo'
154
+ Mercury.tooltip.update()
155
+ expect(Mercury.tooltip.element.html()).toEqual('foo')
156
+
157
+ it "calls position", ->
158
+ Mercury.tooltip.update()
159
+ expect(@positionSpy.callCount).toEqual(1)
160
+
161
+
162
+ describe "#position", ->
163
+
164
+ beforeEach ->
165
+ Mercury.tooltip.build()
166
+ Mercury.displayRect = {top: 0, left: 0, width: 200, height: 200}
167
+
168
+ it "positions based on the element we're showing for", ->
169
+ Mercury.tooltip.forElement = @forElement
170
+ Mercury.tooltip.position()
171
+ expect(Mercury.tooltip.element.offset()).toEqual({top: 20 + @forElement.outerHeight(), left: 42})
172
+
173
+
174
+ describe "#hide", ->
175
+
176
+ beforeEach ->
177
+ Mercury.tooltip.build()
178
+ Mercury.tooltip.initialized = true
179
+
180
+ it "hides the element", ->
181
+ Mercury.tooltip.element.css({display: 'block'})
182
+ Mercury.tooltip.hide()
183
+ expect(Mercury.tooltip.element.css('display')).toEqual('none')
184
+
185
+ it "sets visible to false", ->
186
+ Mercury.tooltip.visible = true
187
+ Mercury.tooltip.hide()
188
+ expect(Mercury.tooltip.visible).toEqual(false)
@@ -0,0 +1,512 @@
1
+ require '/assets/mercury/mercury.js'
2
+
3
+ describe "Mercury.uploader", ->
4
+
5
+ template 'mercury/uploader.html'
6
+
7
+ beforeEach ->
8
+ $.fx.off = true
9
+ @mockFile = {
10
+ size: 1024
11
+ fileName: 'image.png'
12
+ type: 'image/png'
13
+ }
14
+
15
+ afterEach ->
16
+ Mercury.uploader.initialized = false
17
+
18
+ describe "singleton method", ->
19
+
20
+ beforeEach ->
21
+ @showSpy = spyOn(Mercury.uploader, 'show').andCallFake(=>)
22
+
23
+ it "calls show", ->
24
+ Mercury.uploader(@mockFile)
25
+ expect(@showSpy.callCount).toEqual(1)
26
+
27
+ it "returns the function object", ->
28
+ ret = Mercury.uploader(@mockFile)
29
+ expect(ret).toEqual(Mercury.uploader)
30
+
31
+
32
+ describe "#show", ->
33
+
34
+ beforeEach ->
35
+ @initializeSpy = spyOn(Mercury.uploader, 'initialize').andCallFake(=>)
36
+ @appearSpy = spyOn(Mercury.uploader, 'appear').andCallFake(=>)
37
+
38
+ it "expects a file object", ->
39
+ Mercury.uploader.show(@mockFile)
40
+ expect(Mercury.uploader.file.name).toEqual(@mockFile.fileName)
41
+
42
+ it "accepts options", ->
43
+ Mercury.uploader.show(@mockFile, {foo: 'bar'})
44
+ expect(Mercury.uploader.options).toEqual({foo: 'bar'})
45
+
46
+ it "creates a file instance from the file", ->
47
+ Mercury.uploader.show(@mockFile)
48
+ expect(Mercury.uploader.file.name).toEqual(@mockFile.fileName)
49
+ expect(Mercury.uploader.file.fullSize).toEqual(1024)
50
+
51
+ it "alerts and stops if the file has errors (too large or unsupported mimetype)", ->
52
+ @mockFile.size = 123123123123
53
+ spy = spyOn(window, 'alert').andCallFake(=>)
54
+ Mercury.uploader.show(@mockFile)
55
+ expect(spy.callCount).toEqual(1)
56
+ expect(spy.argsForCall[0]).toEqual(['Error: Too large'])
57
+
58
+ it "doesn't do anything unless xhr uploading is supported in the browser", ->
59
+ spyOn(Mercury.uploader, 'supported').andCallFake(=> false)
60
+ Mercury.uploader.show(@mockFile)
61
+ expect(@initializeSpy.callCount).toEqual(0)
62
+
63
+ it "triggers an event to focus the window", ->
64
+ spy = spyOn(Mercury, 'trigger').andCallFake(=>)
65
+ Mercury.uploader.show(@mockFile)
66
+ expect(spy.callCount).toEqual(1)
67
+ expect(spy.argsForCall[0]).toEqual(['focus:window'])
68
+
69
+ it "calls initialize", ->
70
+ Mercury.uploader.show(@mockFile)
71
+ expect(@initializeSpy.callCount).toEqual(1)
72
+
73
+ it "calls appear", ->
74
+ Mercury.uploader.show(@mockFile)
75
+ expect(@appearSpy.callCount).toEqual(1)
76
+
77
+
78
+ describe "#initialize", ->
79
+
80
+ beforeEach ->
81
+ @buildSpy = spyOn(Mercury.uploader, 'build').andCallFake(=>)
82
+ @bindEventsSpy = spyOn(Mercury.uploader, 'bindEvents').andCallFake(=>)
83
+
84
+ it "calls build", ->
85
+ Mercury.uploader.initialize()
86
+ expect(@buildSpy.callCount).toEqual(1)
87
+
88
+ it "calls bindEvents", ->
89
+ Mercury.uploader.initialize()
90
+ expect(@bindEventsSpy.callCount).toEqual(1)
91
+
92
+ it "only initializes once", ->
93
+ Mercury.uploader.initialize()
94
+ expect(@buildSpy.callCount).toEqual(1)
95
+ Mercury.uploader.initialize()
96
+ expect(@buildSpy.callCount).toEqual(1)
97
+
98
+
99
+ describe "#supported", ->
100
+
101
+ it "prototypes sendAsBinary onto XMLHttpRequest if it's not already there", ->
102
+ XMLHttpRequest.prototype.sendAsBinary = null
103
+ Mercury.uploader.supported()
104
+ expect(XMLHttpRequest.prototype.sendAsBinary).toBeDefined()
105
+
106
+ it "returns true if everything needed is supported", ->
107
+ ret = Mercury.uploader.supported()
108
+ expect(ret).toEqual(true)
109
+
110
+ it "returns false if everything isn't supported", ->
111
+ window.Uint8Array = null
112
+ ret = Mercury.uploader.supported()
113
+ expect(ret).toEqual(true)
114
+
115
+
116
+ describe "#build", ->
117
+
118
+ beforeEach ->
119
+ Mercury.uploader.options = {appendTo: '#test'}
120
+
121
+ it "builds an element structure", ->
122
+ Mercury.uploader.build()
123
+ html = $('<div>').html(Mercury.uploader.element).html()
124
+ expect(html).toContain('class="mercury-uploader"')
125
+ expect(html).toContain('class="mercury-uploader-preview"')
126
+ expect(html).toContain('<b><img></b>')
127
+ expect(html).toContain('class="mercury-uploader-details"')
128
+ expect(html).toContain('<span>Processing...</span>')
129
+ expect(html).toContain('class="mercury-uploader-indicator"')
130
+ expect(html).toContain('<div><b>0%</b></div>')
131
+
132
+ it "builds an overlay", ->
133
+ Mercury.uploader.build()
134
+ html = $('<div>').html(Mercury.uploader.overlay).html()
135
+ expect(html).toContain('class="mercury-uploader-overlay"')
136
+
137
+ it "appends to any element", ->
138
+ Mercury.uploader.options = {appendTo: '#uploader_container'}
139
+ Mercury.uploader.build()
140
+ expect($('#uploader_container .mercury-uploader').length).toEqual(1)
141
+
142
+
143
+ describe "observed events", ->
144
+
145
+ describe "custom event: resize", ->
146
+
147
+ it "calls position", ->
148
+ spy = spyOn(Mercury.uploader, 'position').andCallFake(=>)
149
+ Mercury.uploader.bindEvents()
150
+ Mercury.trigger('resize')
151
+ expect(spy.callCount).toEqual(1)
152
+
153
+
154
+ describe "#appear", ->
155
+
156
+ beforeEach ->
157
+ Mercury.uploader.options = {appendTo: '#test'}
158
+ Mercury.uploader.build()
159
+ @fillDisplaySpy = spyOn(Mercury.uploader, 'fillDisplay').andCallFake(=>)
160
+ @positionSpy = spyOn(Mercury.uploader, 'position').andCallFake(=>)
161
+ @loadImageSpy = spyOn(Mercury.uploader, 'loadImage').andCallFake(=>)
162
+
163
+ it "calls fillDisplay", ->
164
+ Mercury.uploader.appear()
165
+ expect(@fillDisplaySpy.callCount).toEqual(1)
166
+
167
+ it "calls position", ->
168
+ Mercury.uploader.appear()
169
+ expect(@positionSpy.callCount).toEqual(1)
170
+
171
+ it "displays the overlay, and the element", ->
172
+ Mercury.uploader.appear()
173
+ expect($('#test .mercury-uploader').css('display')).toEqual('block')
174
+ expect($('#test .mercury-uploader-overlay').css('display')).toEqual('block')
175
+
176
+ it "sets visible to true", ->
177
+ Mercury.uploader.appear()
178
+ expect(Mercury.uploader.visible).toEqual(true)
179
+
180
+ it "calls loadImage", ->
181
+ Mercury.uploader.appear()
182
+ expect(@loadImageSpy.callCount).toEqual(1)
183
+
184
+
185
+ describe "#position", ->
186
+
187
+ beforeEach ->
188
+ Mercury.uploader.options = {appendTo: '#test'}
189
+ Mercury.uploader.build()
190
+ @fillDisplaySpy = spyOn(Mercury.uploader, 'fillDisplay').andCallFake(=>)
191
+ @positionSpy = spyOn(Mercury.uploader, 'position').andCallFake(=>)
192
+
193
+ it "centers the element in the viewport", ->
194
+ # todo: this isn't really being tested
195
+ Mercury.uploader.element.css({display: 'block'})
196
+ Mercury.uploader.position()
197
+ @expect($('#test .mercury-uploader').offset()).toEqual({top: 0, left: 0})
198
+
199
+
200
+ describe "#fillDisplay", ->
201
+
202
+ beforeEach ->
203
+ Mercury.uploader.options = {appendTo: '#test'}
204
+ Mercury.uploader.file = {name: 'image.png', size: 1024, type: 'image/png'}
205
+ Mercury.uploader.build()
206
+
207
+ it "puts the file details into the element", ->
208
+ Mercury.uploader.fillDisplay()
209
+ expect($('#test .mercury-uploader-details').html()).toEqual('Name: image.png<br>Size: undefined<br>Type: image/png')
210
+
211
+
212
+ describe "#loadImage", ->
213
+
214
+ beforeEach ->
215
+ Mercury.uploader.options = {appendTo: '#test'}
216
+ Mercury.uploader.file = new Mercury.uploader.File(@mockFile)
217
+ Mercury.uploader.build()
218
+ spyOn(FileReader.prototype, 'readAsBinaryString').andCallFake(=>)
219
+ @readAsDataURLSpy = spyOn(Mercury.uploader.File.prototype, 'readAsDataURL').andCallFake((callback) => callback('data-url'))
220
+
221
+ it "calls file.readAsDataURL", ->
222
+ Mercury.uploader.loadImage()
223
+ expect(@readAsDataURLSpy.callCount).toEqual(1)
224
+
225
+ it "sets the preview image src to the file contents", ->
226
+ Mercury.uploader.loadImage()
227
+ expect($('#test .mercury-uploader-preview img').attr('src')).toEqual('data-url')
228
+
229
+ it "calls upload", ->
230
+ spy = spyOn(Mercury.uploader, 'upload').andCallFake(=>)
231
+ Mercury.uploader.loadImage()
232
+ expect(spy.callCount).toEqual(1)
233
+
234
+
235
+ describe "#upload", ->
236
+
237
+ it "", ->
238
+
239
+
240
+ describe "#updateStatus", ->
241
+
242
+ beforeEach ->
243
+ Mercury.uploader.options = {appendTo: '#test'}
244
+ Mercury.uploader.build()
245
+
246
+ it "updated the message in the progress display", ->
247
+ Mercury.uploader.updateStatus('status message')
248
+ expect($('#test .mercury-uploader-progress span').html()).toEqual('status message')
249
+
250
+ it "updates the progress indicator width", ->
251
+ Mercury.uploader.updateStatus('message', 512)
252
+ expect($('#test .mercury-uploader-indicator div').css('width')).toEqual('50%')
253
+
254
+ it "updates the progress indicator value", ->
255
+ Mercury.uploader.updateStatus('message', 512)
256
+ expect($('#test .mercury-uploader-indicator b').html()).toEqual('50%')
257
+
258
+
259
+ describe "#hide", ->
260
+
261
+ beforeEach ->
262
+ @setTimeoutSpy = spyOn(window, 'setTimeout')
263
+ Mercury.uploader.options = {appendTo: '#test'}
264
+ Mercury.uploader.build()
265
+
266
+ it "accepts a delay", ->
267
+ @setTimeoutSpy.andCallFake(=>)
268
+ Mercury.uploader.hide(1)
269
+ expect(@setTimeoutSpy.callCount).toEqual(1)
270
+ expect(@setTimeoutSpy.argsForCall[0][1]).toEqual(1000)
271
+
272
+ it "hides the overlay and element", ->
273
+ @setTimeoutSpy.andCallFake((callback) => callback())
274
+ Mercury.uploader.hide()
275
+ expect($('#test .mercury-uploader').css('opacity')).toEqual('0')
276
+ expect($('#test .mercury-uploader-overlay').css('opacity')).toEqual('0')
277
+
278
+ it "calls reset", ->
279
+ @setTimeoutSpy.andCallFake((callback) => callback())
280
+ spy = spyOn(Mercury.uploader, 'reset').andCallFake(=>)
281
+ Mercury.uploader.hide()
282
+ expect(spy.callCount).toEqual(1)
283
+
284
+ it "sets visible to false", ->
285
+ @setTimeoutSpy.andCallFake((callback) => callback())
286
+ Mercury.uploader.hide()
287
+ expect(Mercury.uploader.visible).toEqual(false)
288
+
289
+ it "focuses the frame", ->
290
+ @setTimeoutSpy.andCallFake((callback) => callback())
291
+ spy = spyOn(Mercury, 'trigger').andCallFake(=>)
292
+ Mercury.uploader.hide()
293
+ expect(spy.callCount).toEqual(1)
294
+ expect(spy.argsForCall[0]).toEqual(['focus:frame'])
295
+
296
+
297
+ describe "#reset", ->
298
+
299
+ beforeEach ->
300
+ Mercury.uploader.options = {appendTo: '#test'}
301
+ Mercury.uploader.build()
302
+
303
+ it "removes the preview image", ->
304
+ $('#test .mercury-uploader-indicator div').html('foo')
305
+ Mercury.uploader.reset()
306
+ expect($('#test .mercury-uploader-preview b').html()).toEqual('')
307
+
308
+ it "resets the progress back to 0", ->
309
+ $('#test .mercury-uploader-indicator div').css({width: '50%'})
310
+ $('#test .mercury-uploader-indicator b').html('50%')
311
+ Mercury.uploader.reset()
312
+ expect($('#test .mercury-uploader-indicator div').css('width')).toEqual('0px')
313
+ expect($('#test .mercury-uploader-indicator b').html()).toEqual('0%')
314
+
315
+ it "sets the status back to 'Processing...' for next time", ->
316
+ spy = spyOn(Mercury.uploader, 'updateStatus').andCallFake(=>)
317
+ Mercury.uploader.reset()
318
+ expect(spy.callCount).toEqual(1)
319
+
320
+
321
+ describe "uploaderEvents", ->
322
+
323
+ beforeEach ->
324
+ Mercury.uploader.file = @mockFile
325
+ @updateStatusSpy = spyOn(Mercury.uploader, 'updateStatus').andCallFake(=>)
326
+ @hideSpy = spyOn(Mercury.uploader, 'hide').andCallFake(=>)
327
+ @events = Mercury.uploader.uploaderEvents
328
+
329
+ describe ".onloadstart", ->
330
+
331
+ it "updates the status to 'Uploading...'", ->
332
+ @events.onloadstart.call(Mercury.uploader)
333
+ expect(@updateStatusSpy.callCount).toEqual(1)
334
+ expect(@updateStatusSpy.argsForCall[0]).toEqual(['Uploading...'])
335
+
336
+ describe ".onprogress", ->
337
+
338
+ it "updates the status to 'Uploading...' and passes the amount sent so far", ->
339
+ @events.onprogress.call(Mercury.uploader, {loaded: 512})
340
+ expect(@updateStatusSpy.callCount).toEqual(1)
341
+ expect(@updateStatusSpy.argsForCall[0]).toEqual(['Uploading...', 512])
342
+
343
+ describe ".onabort", ->
344
+
345
+ it "updates the status to 'Aborted'", ->
346
+ @events.onabort.call(Mercury.uploader)
347
+ expect(@updateStatusSpy.callCount).toEqual(1)
348
+ expect(@updateStatusSpy.argsForCall[0]).toEqual(['Aborted'])
349
+
350
+ it "calls hide", ->
351
+ @events.onabort.call(Mercury.uploader)
352
+ expect(@hideSpy.callCount).toEqual(1)
353
+
354
+ describe ".onload", ->
355
+
356
+ it "updates the status to 'Successfully uploaded' and passes the total file size", ->
357
+ @events.onload.call(Mercury.uploader)
358
+ expect(@updateStatusSpy.callCount).toEqual(1)
359
+ expect(@updateStatusSpy.argsForCall[0]).toEqual(['Successfully uploaded', 1024])
360
+
361
+ it "calls hide", ->
362
+ @events.onload.call(Mercury.uploader)
363
+ expect(@hideSpy.callCount).toEqual(1)
364
+
365
+ describe ".onerror", ->
366
+
367
+ it "updates the status to 'Error: Unable to upload the file'", ->
368
+ @events.onerror.call(Mercury.uploader)
369
+ expect(@updateStatusSpy.callCount).toEqual(1)
370
+ expect(@updateStatusSpy.argsForCall[0]).toEqual(['Error: Unable to upload the file'])
371
+
372
+ it "calls hide", ->
373
+ @events.onerror.call(Mercury.uploader)
374
+ expect(@hideSpy.callCount).toEqual(1)
375
+
376
+
377
+
378
+ describe "Mercury.uploader.File", ->
379
+
380
+ beforeEach ->
381
+ @mockFile = {
382
+ size: 1024
383
+ fileName: 'image.png'
384
+ type: 'image/png'
385
+ }
386
+
387
+ afterEach ->
388
+ @file = null
389
+ delete(@file)
390
+
391
+ describe "constructor", ->
392
+
393
+ it "expects a file", ->
394
+ @file = new Mercury.uploader.File(@mockFile)
395
+ expect(@file.file).toEqual(@mockFile)
396
+
397
+ it "reads attributes of the file and sets variables", ->
398
+ @file = new Mercury.uploader.File(@mockFile)
399
+ expect(@file.size).toEqual(1024)
400
+ expect(@file.fullSize).toEqual(1024)
401
+ expect(@file.readableSize).toEqual('1.00 kb')
402
+ expect(@file.name).toEqual('image.png')
403
+ expect(@file.type).toEqual('image/png')
404
+
405
+ it "adds errors if there's any", ->
406
+ Mercury.config.uploading.maxFileSize = 100
407
+ Mercury.config.uploading.allowedMimeTypes = ['image/foo']
408
+ @file = new Mercury.uploader.File(@mockFile)
409
+ expect(@file.errors).toEqual('Too large / Unsupported format')
410
+
411
+
412
+ describe "#readAsDataURL", ->
413
+
414
+ it "it calls readAsDataURL on a FileReader object", ->
415
+ spy = spyOn(window.FileReader.prototype, 'readAsDataURL').andCallFake(=>)
416
+ @file = new Mercury.uploader.File(@mockFile)
417
+ @file.readAsDataURL()
418
+ expect(spy.callCount).toEqual(1)
419
+
420
+ it "calls a callback if one was provided", ->
421
+ spyOn(FileReader.prototype, 'readAsDataURL').andCallFake(=>)
422
+ FileReader.prototype.result = 'result'
423
+ callCount = 0
424
+ callback = (r) => callCount += 1
425
+
426
+ @file = new Mercury.uploader.File(@mockFile)
427
+ onload = @file.readAsDataURL(callback)
428
+ onload()
429
+ expect(callCount).toEqual(1)
430
+
431
+
432
+ describe "#readAsBinaryString", ->
433
+
434
+ it "it calls readAsBinaryString on a FileReader object", ->
435
+ spy = spyOn(window.FileReader.prototype, 'readAsBinaryString').andCallFake(=>)
436
+ @file = new Mercury.uploader.File(@mockFile)
437
+ @file.readAsBinaryString()
438
+ expect(spy.callCount).toEqual(1)
439
+
440
+ it "calls a callback if one was provided", ->
441
+ spyOn(FileReader.prototype, 'readAsBinaryString').andCallFake(=>)
442
+ FileReader.prototype.result = 'result'
443
+ callCount = 0
444
+ callback = (r) => callCount += 1
445
+
446
+ @file = new Mercury.uploader.File(@mockFile)
447
+ onload = @file.readAsBinaryString(callback)
448
+ onload()
449
+ expect(callCount).toEqual(1)
450
+
451
+
452
+ describe "#updateSize", ->
453
+
454
+ it "updates the size based on a delta", ->
455
+ @file = new Mercury.uploader.File(@mockFile)
456
+ @file.updateSize(20)
457
+ expect(@file.fullSize).toEqual(1044)
458
+
459
+
460
+
461
+ describe "Mercury.uploader.MultiPartPost", ->
462
+
463
+ beforeEach ->
464
+ @mockFile = {
465
+ size: 1024
466
+ name: 'image.png'
467
+ type: 'image/png'
468
+ }
469
+
470
+ afterEach ->
471
+ @multiPartPost = null
472
+ delete(@multiPartPost)
473
+
474
+ describe "constructor", ->
475
+
476
+ it "expects an inputName, file, and file contents", ->
477
+ @multiPartPost = new Mercury.uploader.MultiPartPost('foo[bar]', @mockFile, 'file contents')
478
+ expect(@multiPartPost.inputName).toEqual('foo[bar]')
479
+ expect(@multiPartPost.file).toEqual(@mockFile)
480
+ expect(@multiPartPost.contents).toEqual('file contents')
481
+
482
+ it "accepts a formInputs object", ->
483
+ @multiPartPost = new Mercury.uploader.MultiPartPost('foo[bar]', @mockFile, 'file contents', {foo: 'bar'})
484
+ expect(@multiPartPost.formInputs).toEqual({foo: 'bar'})
485
+
486
+ it "defines a boundary string", ->
487
+ @multiPartPost = new Mercury.uploader.MultiPartPost('foo[bar]', @mockFile, 'file contents')
488
+ expect(@multiPartPost.boundary).toEqual('Boundaryx20072377098235644401115438165x')
489
+
490
+ it "calls buildBody", ->
491
+ spy = spyOn(Mercury.uploader.MultiPartPost.prototype, 'buildBody').andCallFake(=>)
492
+ @multiPartPost = new Mercury.uploader.MultiPartPost('foo[bar]', @mockFile, 'file contents')
493
+ expect(spy.callCount).toEqual(1)
494
+
495
+ it "sets a delta based on the body size and file size", ->
496
+ @multiPartPost = new Mercury.uploader.MultiPartPost('foo[bar]', @mockFile, 'file contents')
497
+ expect(@multiPartPost.delta).toEqual(-790)
498
+
499
+
500
+ describe "#buildBody", ->
501
+
502
+ it "creates a multipart post body with the file information", ->
503
+ @multiPartPost = new Mercury.uploader.MultiPartPost('foo[bar]', @mockFile, 'file contents')
504
+ expect(@multiPartPost.body).toContain('--Boundaryx20072377098235644401115438165x')
505
+ expect(@multiPartPost.body).toContain('Content-Disposition: form-data; name="foo[bar]"; filename="image.png"')
506
+ expect(@multiPartPost.body).toContain('Content-Type: image/png')
507
+ expect(@multiPartPost.body).toContain('Content-Transfer-Encoding: binary')
508
+ expect(@multiPartPost.body).toContain('file contents')
509
+
510
+ it "includes form inputs if passed in", ->
511
+ @multiPartPost = new Mercury.uploader.MultiPartPost('foo[bar]', @mockFile, 'file contents', {foo: 'bar'})
512
+ expect(@multiPartPost.body).toContain('Content-Disposition: form-data; name="foo"\r\n\r\nbar')