midas 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.bundle/config +2 -0
  2. data/.document +5 -0
  3. data/.gitignore +32 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +20 -0
  6. data/README.textile +21 -0
  7. data/Rakefile +84 -0
  8. data/VERSION +1 -0
  9. data/features/step_definitions/editor_steps.rb +16 -0
  10. data/features/step_definitions/web_steps.rb +205 -0
  11. data/features/support/env.rb +39 -0
  12. data/features/support/paths.rb +29 -0
  13. data/features/view_editor.feature +8 -0
  14. data/generators/midas/midas_generator.rb +1 -0
  15. data/lib/midas.rb +1 -0
  16. data/midas.gemspec +131 -0
  17. data/public/images/midas/toolbars/actions/_background.png +0 -0
  18. data/public/images/midas/toolbars/actions/_background_radio.png +0 -0
  19. data/public/images/midas/toolbars/actions/_separator.png +0 -0
  20. data/public/images/midas/toolbars/actions/extra/prefspane.png +0 -0
  21. data/public/images/midas/toolbars/actions/extra/todospane.png +0 -0
  22. data/public/images/midas/toolbars/actions/historypanel.png +0 -0
  23. data/public/images/midas/toolbars/actions/insertcharacter.png +0 -0
  24. data/public/images/midas/toolbars/actions/insertlink.png +0 -0
  25. data/public/images/midas/toolbars/actions/insertmedia.png +0 -0
  26. data/public/images/midas/toolbars/actions/insertobject.png +0 -0
  27. data/public/images/midas/toolbars/actions/inserttable.png +0 -0
  28. data/public/images/midas/toolbars/actions/inspectorpanel.png +0 -0
  29. data/public/images/midas/toolbars/actions/notespanel.png +0 -0
  30. data/public/images/midas/toolbars/actions/preview.png +0 -0
  31. data/public/images/midas/toolbars/actions/redo.png +0 -0
  32. data/public/images/midas/toolbars/actions/save.png +0 -0
  33. data/public/images/midas/toolbars/actions/undo.png +0 -0
  34. data/public/images/midas/toolbars/htmleditor/_background.png +0 -0
  35. data/public/images/midas/toolbars/htmleditor/_line_separator.png +0 -0
  36. data/public/images/midas/toolbars/htmleditor/_separator.png +0 -0
  37. data/public/images/midas/toolbars/htmleditor/buttons.png +0 -0
  38. data/public/javascripts/midas/config.js +181 -0
  39. data/public/javascripts/midas/dialog.js +9 -0
  40. data/public/javascripts/midas/midas.js +307 -0
  41. data/public/javascripts/midas/native_extensions.js +43 -0
  42. data/public/javascripts/midas/palette.js +108 -0
  43. data/public/javascripts/midas/region.js +194 -0
  44. data/public/javascripts/midas/statusbar.js +84 -0
  45. data/public/javascripts/midas/toolbar.js +255 -0
  46. data/public/javascripts/prototype.js +6001 -0
  47. data/public/midas/backcolor.html +97 -0
  48. data/public/midas/examples/bundled.html +60 -0
  49. data/public/midas/examples/iframe.html +73 -0
  50. data/public/midas/examples/index.html +73 -0
  51. data/public/midas/examples/javascript_archive.js +111 -0
  52. data/public/midas/forecolor.html +97 -0
  53. data/public/stylesheets/midas/dialog.css +2 -0
  54. data/public/stylesheets/midas/midas.css +9 -0
  55. data/public/stylesheets/midas/palette.css +50 -0
  56. data/public/stylesheets/midas/region.css +16 -0
  57. data/public/stylesheets/midas/statusbar.css +17 -0
  58. data/public/stylesheets/midas/toolbar.css +262 -0
  59. data/rails/init.rb +1 -0
  60. data/spec/javascripts/dialog_spec.js +7 -0
  61. data/spec/javascripts/fixtures/midas_fixture.html +27 -0
  62. data/spec/javascripts/fixtures/midas_styles.css +14 -0
  63. data/spec/javascripts/fixtures/native_extensions_fixture.html +5 -0
  64. data/spec/javascripts/helpers/browser_detection.js +15 -0
  65. data/spec/javascripts/helpers/event_simulation.js +505 -0
  66. data/spec/javascripts/helpers/spec_helper.js +125 -0
  67. data/spec/javascripts/midas_spec.js +284 -0
  68. data/spec/javascripts/native_extensions_spec.js +39 -0
  69. data/spec/javascripts/palette_spec.js +59 -0
  70. data/spec/javascripts/region_spec.js +441 -0
  71. data/spec/javascripts/statusbar_spec.js +61 -0
  72. data/spec/javascripts/support/jasmine.yml +82 -0
  73. data/spec/javascripts/support/jasmine_config.rb +39 -0
  74. data/spec/javascripts/support/jasmine_runner.rb +19 -0
  75. data/spec/javascripts/toolbar_spec.js +149 -0
  76. data/spec/ruby/helpers/spec_helper.rb +9 -0
  77. data/spec/ruby/midas_spec.rb +9 -0
  78. data/spec/spec.opts +1 -0
  79. data/tasks/midas_tasks.rake +105 -0
  80. metadata +166 -0
@@ -0,0 +1,441 @@
1
+ describe('Midas.Region', function() {
2
+
3
+ beforeEach(function() {
4
+ jasmine.loadFixture('midas_fixture');
5
+ jasmine.loadCSS('midas_styles');
6
+ });
7
+
8
+ afterEach(function () {
9
+ try {
10
+ this.region.destroy();
11
+ this.region = null;
12
+ } catch(e) {}
13
+ jasmine.unloadCSS('midas_styles');
14
+ });
15
+
16
+ it('should make an element editable', function() {
17
+ this.region = new Midas.Region('region1');
18
+
19
+ expect($('region1').contentEditable).toEqual('true');
20
+ expect($('region1').hasClassName('midas-region')).toEqual(true);
21
+ });
22
+
23
+ it('should make an empty region contain a  , and reset that on focus', function() {
24
+
25
+ // this seems like a weird test, but it's for firefox, and is relatively unnoticable.
26
+ // firefox doesn't like to give focus to a complely blank contentEditable region, so
27
+ // we put a   inside empty ones, and then reset it on focus/click, so you don't
28
+ // really see the  
29
+ if (jasmine.browser.Gecko) {
30
+ this.region = new Midas.Region('region3');
31
+
32
+ expect($('region3').innerHTML).toEqual(' ');
33
+
34
+ jasmine.simulate.click(this.region.element);
35
+
36
+ expect($('region3').innerHTML).toEqual(' ');
37
+ }
38
+ });
39
+
40
+ it('should accept options in the constructor', function() {
41
+ this.region = new Midas.Region('region1', {sandwich: 'icecream'});
42
+
43
+ expect(this.region.options['sandwich']).toEqual('icecream');
44
+ });
45
+
46
+ it('should allow retrieval and update of the regions contents', function() {
47
+ this.region = new Midas.Region('region1');
48
+
49
+ expect(this.region.getContents()).toEqual('region1');
50
+
51
+ this.region.setContents('bacon');
52
+ expect(this.region.getContents()).toEqual('bacon');
53
+ });
54
+
55
+ it('should serialize', function() {
56
+ this.region = new Midas.Region('region1');
57
+ var serialized = this.region.serialize();
58
+
59
+ expect(serialized['name']).toEqual('region1');
60
+ expect(serialized['content']).toEqual('region1');
61
+ });
62
+
63
+ it('should destroy', function() {
64
+ this.region = new Midas.Region('region1');
65
+ this.region.destroy();
66
+
67
+ expect($('region1').contentEditable).toEqual('false');
68
+ expect($('region1').hasClassName('midas-region')).toEqual(false);
69
+ });
70
+
71
+ describe('behave according to options', function() {
72
+
73
+ it('should support the inline option: true', function() {
74
+ this.region = new Midas.Region('region1', {inline: true});
75
+
76
+ var height = this.region.element.getHeight();
77
+ this.region.setContents(Array(100).join('<br/>'));
78
+
79
+ expect(this.region.element.getHeight()).not.toEqual(height);
80
+ });
81
+
82
+ it('should support the inline option: false', function() {
83
+ this.region = new Midas.Region('region1', {inline: false});
84
+
85
+ var height = this.region.element.getHeight();
86
+ this.region.setContents(Array(100).join('<br/>'));
87
+
88
+ expect(this.region.element.getHeight()).toEqual(height);
89
+ });
90
+
91
+ });
92
+
93
+ describe('keys that have special behaviors', function() {
94
+
95
+ it('should indent li elements when pressing tab', function() {
96
+ this.region = new Midas.Region('region4');
97
+
98
+ expect($('div5').down('ul').down('ul')).toBeUndefined();
99
+
100
+ jasmine.simulate.selection($('div5').down('span'));
101
+ this.region.updateSelections();
102
+ jasmine.simulate.tab(this.region.element);
103
+
104
+ expect($('div5').down('ul').down('ul')).toBeDefined();
105
+
106
+ jasmine.simulate.selection($('div6').down('span'));
107
+ this.region.updateSelections();
108
+ jasmine.simulate.tab(this.region.element);
109
+
110
+ expect($('div6').innerHTML).toEqual("<span>this isn't in a li</span>");
111
+ });
112
+
113
+ });
114
+
115
+ describe('actions and behaviors that are handled', function() {
116
+
117
+ beforeEach(function() {
118
+ this.region = new Midas.Region('region1');
119
+ });
120
+
121
+ it('should fall back to the standard execCommand', function() {
122
+ var spy = spyOn(document, 'execCommand').andCallThrough();
123
+ this.region.handleAction('delete');
124
+
125
+ expect(spy).wasCalledWith('delete', false, null);
126
+ });
127
+
128
+ it('should throw an exception when the action is unknown', function() {
129
+
130
+ // this test doesn't work in webkit because execCommand doesn't
131
+ // return false ever, so it's impossible to tell if the command
132
+ // was handled or not.
133
+
134
+ try {
135
+ this.region.handleAction('pizza');
136
+ } catch(e) {
137
+ expect(e.toString()).toEqual('Unknown action "pizza"')
138
+ }
139
+ });
140
+
141
+ describe('when a behavior is configured', function() {
142
+
143
+ beforeEach(function() {
144
+ this.oldBehaviors = Midas.Config.behaviors;
145
+ Midas.Config.behaviors = {
146
+ bagel: {havati: 'lettuce'},
147
+ bold: {execCommand: 'italic'},
148
+ underline: {execCommand: ['insertHTML', '<div>peanut<div>']},
149
+ horizontalrule: {insertElement: function() {
150
+ return new Element('hr');
151
+ }},
152
+ pagebreak: {insertHTML: function() {
153
+ return '<div>walnut</div>';
154
+ }}
155
+ };
156
+ });
157
+
158
+ afterEach(function() {
159
+ Midas.Config.behaviors = this.oldBehaviors;
160
+ });
161
+
162
+ it('should handle execCommand actions', function() {
163
+ var spy = spyOn(this.region, 'execCommand').andCallThrough();
164
+ this.region.handleAction('bold');
165
+
166
+ expect(spy).wasCalledWith('italic', undefined);
167
+ });
168
+
169
+ it('should handle execCommand actions with array', function() {
170
+ var spy = spyOn(this.region, 'execCommand').andCallThrough();
171
+ this.region.handleAction('underline');
172
+
173
+ expect(spy).wasCalledWith('insertHTML', '<div>peanut<div>');
174
+ });
175
+
176
+ it('should handle insertHTML actions', function() {
177
+ var spy = spyOn(this.region.handle, 'insertHTML').andCallThrough();
178
+ this.region.handleAction('pagebreak');
179
+
180
+ expect(spy).wasCalledWith(Midas.Config.behaviors['pagebreak']['insertHTML']);
181
+ });
182
+
183
+ it('should throw an exception when the behavior is unknown', function() {
184
+ try {
185
+ this.region.handleAction('bagel');
186
+ } catch(e) {
187
+ expect(e.toString()).toEqual('Unknown behavior method "havati"');
188
+ }
189
+ });
190
+
191
+ });
192
+
193
+ describe('expecting special cases', function() {
194
+
195
+ it('should handle indent', function() {
196
+ this.region = new Midas.Region('region4');
197
+ jasmine.simulate.selection($('region4').down('#div1'));
198
+ this.region.updateSelections();
199
+ this.region.handleAction('indent');
200
+
201
+ if (jasmine.browser.WebKit) {
202
+ expect($('region4').select('blockquote').length).toEqual(1);
203
+ } else if(jasmine.browser.Gecko) {
204
+ expect($('region4').down('#div1').up().tagName).toEqual('BLOCKQUOTE');
205
+ }
206
+ });
207
+
208
+ it('should handle removeformatting', function() {
209
+ this.region = new Midas.Region('region4');
210
+ jasmine.simulate.selection($('region4').down('#div3'));
211
+ this.region.updateSelections();
212
+ this.region.handleAction('removeformatting');
213
+
214
+ if (jasmine.browser.WebKit) {
215
+ expect($('region4').down('#div3').innerHTML).toEqual('there is no html here<br>');
216
+ } else if(jasmine.browser.Gecko) {
217
+ expect($('region4').down('#div2').innerHTML).toEqual('there is no html here');
218
+ }
219
+ });
220
+
221
+ });
222
+
223
+ describe('expecting built in browser actions', function() {
224
+
225
+ beforeEach(function() {
226
+ this.region = new Midas.Region('region4');
227
+ this.div = $('region4').down('#action');
228
+ jasmine.simulate.selection(this.div);
229
+ this.region.updateSelections();
230
+ });
231
+
232
+ var actions = $w('bold italic underline strikethrough subscript superscript justifyleft justifycenter justifyright justifyfull insertorderedlist insertunorderedlist');
233
+ actions.each(function(action) {
234
+ it('should handle ' + action, function() {
235
+ var resultDiv = $('region4').down('#action');
236
+
237
+ this.region.handleAction(action);
238
+
239
+ // based on the nature of how the browsers decide to implement each "commands"
240
+ // functionality, we have to test all the supported browsers slightly differently.
241
+ // this highlights the fact that we should be normalizing this behavior in our own
242
+ // code (if performance allows).
243
+
244
+ switch (action) {
245
+ case 'bold':
246
+ if (jasmine.browser.WebKit) {
247
+ expect(resultDiv.select('b').length).toEqual(1);
248
+ } else if (jasmine.browser.Gecko) {
249
+ expect(resultDiv.select('b').length).toEqual(1);
250
+ }
251
+ break;
252
+ case 'italic':
253
+ if (jasmine.browser.WebKit) {
254
+ expect(resultDiv.select('i').length).toEqual(1);
255
+ } else if (jasmine.browser.Gecko) {
256
+ expect(resultDiv.select('i').length).toEqual(1);
257
+ }
258
+ break;
259
+ case 'underline':
260
+ if (jasmine.browser.AppleWebKit) {
261
+ expect(resultDiv.down('span').getStyle('text-decoration')).toEqual('underline');
262
+ } else if (jasmine.browser.ChromeWebKit) {
263
+ expect(resultDiv.select('u').length).toEqual(1);
264
+ } else if (jasmine.browser.Gecko) {
265
+ expect(resultDiv.select('u').length).toEqual(1);
266
+ }
267
+ break;
268
+ case 'strikethrough':
269
+ if (jasmine.browser.AppleWebKit) {
270
+ expect(resultDiv.down('span').getStyle('text-decoration')).toEqual('line-through');
271
+ } else if (jasmine.browser.ChromeWebKit) {
272
+ expect(resultDiv.select('s').length).toEqual(1);
273
+ } else if (jasmine.browser.Gecko) {
274
+ expect(resultDiv.select('strike').length).toEqual(1);
275
+ }
276
+ break;
277
+ case 'subscript':
278
+ if (jasmine.browser.WebKit) {
279
+ expect(resultDiv.innerHTML).toEqual('<sub>action in region4</sub>');
280
+ } else if(jasmine.browser.Gecko) {
281
+ expect(resultDiv.innerHTML).toEqual('<sub>action in region4</sub>');
282
+ }
283
+ break;
284
+ case 'superscript':
285
+ if (jasmine.browser.WebKit) {
286
+ expect(resultDiv.innerHTML).toEqual('<sup>action in region4</sup>');
287
+ } else if(jasmine.browser.Gecko) {
288
+ expect(resultDiv.innerHTML).toEqual('<sup>action in region4</sup>');
289
+ }
290
+ break;
291
+ case 'justifyleft':
292
+ if (jasmine.browser.WebKit) {
293
+ expect(resultDiv.getStyle('text-align')).toEqual('left');
294
+ } else if(jasmine.browser.Gecko) {
295
+ expect(resultDiv.getAttribute('align')).toEqual('left');
296
+ }
297
+ break;
298
+ case 'justifycenter':
299
+ if (jasmine.browser.WebKit) {
300
+ expect(resultDiv.getStyle('text-align')).toEqual('center');
301
+ } else if(jasmine.browser.Gecko) {
302
+ expect(resultDiv.getAttribute('align')).toEqual('center');
303
+ }
304
+ break;
305
+ case 'justifyright':
306
+ if (jasmine.browser.WebKit) {
307
+ expect(resultDiv.getStyle('text-align')).toEqual('right');
308
+ } else if(jasmine.browser.Gecko) {
309
+ expect(resultDiv.getAttribute('align')).toEqual('right');
310
+ }
311
+ break;
312
+ case 'justifyfull':
313
+ if (jasmine.browser.WebKit) {
314
+ expect(resultDiv.getStyle('text-align')).toEqual('justify');
315
+ } else if(jasmine.browser.Gecko) {
316
+ expect(resultDiv.getAttribute('align')).toEqual('justify');
317
+ }
318
+ break;
319
+ case 'insertorderedlist':
320
+ if (jasmine.browser.WebKit) {
321
+ expect(resultDiv.innerHTML).toEqual('<ol><li>action in region4<br></li></ol>');
322
+ } else if(jasmine.browser.Gecko) {
323
+ expect(resultDiv.innerHTML).toEqual('<ol><li>action in region4</li></ol>');
324
+ }
325
+ break;
326
+ case 'insertunorderedlist':
327
+ if (jasmine.browser.WebKit) {
328
+ expect(resultDiv.innerHTML).toEqual('<ul><li>action in region4<br></li></ul>');
329
+ } else if(jasmine.browser.Gecko) {
330
+ expect(resultDiv.innerHTML).toEqual('<ul><li>action in region4</li></ul>');
331
+ }
332
+ break;
333
+ }
334
+ });
335
+
336
+ }.bind(this));
337
+
338
+ it('should handle undo and redo', function() {
339
+ this.div = $('region4').down('#action');
340
+ jasmine.simulate.selection(this.div.childNodes[0]);
341
+ this.region.updateSelections();
342
+ jasmine.simulate.keypress(this.div, {charCode: 'a'.charCodeAt(0)});
343
+
344
+ if (jasmine.browser.WebKit) {
345
+ // can't get this working in webkit, however, it does in fact work
346
+ } else if(jasmine.browser.Gecko) {
347
+ expect(this.div.innerHTML).toEqual('a');
348
+
349
+ this.region.handleAction('undo');
350
+
351
+ expect(this.div.innerHTML).toEqual('action in region4');
352
+
353
+ this.region.handleAction('redo');
354
+
355
+ expect(this.div.innerHTML).toEqual('a');
356
+ }
357
+ });
358
+
359
+ it('should handle outdent', function() {
360
+ this.region.handleAction('indent');
361
+ this.region.handleAction('indent');
362
+
363
+ if (jasmine.browser.WebKit) {
364
+ expect(this.region.element.select('blockquote').length).toEqual(2);
365
+ } else {
366
+ expect(this.div.up().tagName).toEqual('BLOCKQUOTE');
367
+ expect(this.div.up().up().tagName).toEqual('BLOCKQUOTE');
368
+ }
369
+
370
+ this.region.handleAction('outdent');
371
+
372
+ if (jasmine.browser.WebKit) {
373
+ expect(this.region.element.select('blockquote').length).toEqual(1);
374
+ } else {
375
+ expect(this.div.up().tagName).toEqual('BLOCKQUOTE');
376
+ expect(this.div.up().up().tagName).not.toEqual('BLOCKQUOTE');
377
+ }
378
+ });
379
+
380
+ });
381
+
382
+ });
383
+
384
+ describe('events that fire', function() {
385
+
386
+ beforeEach(function() {
387
+ this.spy = spyOn(Event, 'fire').andCallFake(function() {
388
+ jasmine.log('>> Mock Event.fire called with ' + arguments.length + ' arguments...');
389
+ });
390
+ });
391
+
392
+ it('should fire an event when it gets focus', function() {
393
+ this.region = new Midas.Region('region1');
394
+
395
+ // this doesn't work in ci, but it seems like it should
396
+ //jasmine.simulate.focus(this.region.element);
397
+
398
+ jasmine.simulate.click(this.region.element);
399
+ expect(this.spy.callCount).toEqual(1);
400
+ });
401
+
402
+ it('should fire an event when it gets clicked', function() {
403
+ this.region = new Midas.Region('region1');
404
+
405
+ jasmine.simulate.click(this.region.element);
406
+ expect(this.spy.callCount).toEqual(1);
407
+ });
408
+
409
+ it('should fire an event when a key is pressed', function() {
410
+ this.region = new Midas.Region('region1');
411
+
412
+ jasmine.simulate.keypress(this.region.element);
413
+ expect(this.spy.callCount).toEqual(1);
414
+ });
415
+
416
+ it('should update selections on keyup', function() {
417
+ this.region = new Midas.Region('region1');
418
+ var spy = spyOn(this.region, 'updateSelections').andCallThrough();
419
+
420
+ jasmine.simulate.keydown(this.region.element, {keyCode: 65});
421
+ jasmine.simulate.keyup(this.region.element, {keyCode: 65});
422
+
423
+ expect(spy.callCount).toEqual(1);
424
+ });
425
+
426
+ it('should track selections on mousedown, and on mouseup update them', function() {
427
+ this.region = new Midas.Region('region1');
428
+ var spy = spyOn(this.region, 'updateSelections').andCallThrough();
429
+
430
+ window.getSelection().selectAllChildren(this.region.element);
431
+
432
+ jasmine.simulate.mousedown(this.region.element);
433
+ expect(this.region.selecting).toEqual(true);
434
+
435
+ jasmine.simulate.mouseup(this.region.element);
436
+ expect(spy.callCount).toEqual(1);
437
+ });
438
+
439
+ });
440
+
441
+ });
@@ -0,0 +1,61 @@
1
+ describe('Midas.Statusbar', function() {
2
+
3
+ beforeEach(function() {
4
+ jasmine.loadFixture('midas_fixture');
5
+ });
6
+
7
+ afterEach(function () {
8
+ try {
9
+ this.statusbar.destroy();
10
+ this.statusbar = null;
11
+ } catch(e) {}
12
+ });
13
+
14
+ it('should accept options in the constructor', function() {
15
+ this.statusbar = new Midas.Statusbar({lettuce: 'banana'});
16
+
17
+ expect(this.statusbar.options['lettuce']).toEqual('banana');
18
+ });
19
+
20
+ it('should make a statusbar', function() {
21
+ this.statusbar = new Midas.Statusbar();
22
+
23
+ expect(this.statusbar.element).not.toBeFalsy();
24
+ expect($$('.midas-statusbar').length).toEqual(1);
25
+ });
26
+
27
+ it('should be able to put the statusbar inside an existing element', function() {
28
+ this.statusbar = new Midas.Statusbar({appendTo: 'statusbar'});
29
+
30
+ expect($('statusbar').innerHTML).not.toEqual('statusbar');
31
+ });
32
+
33
+ it('should destroy', function() {
34
+ this.statusbar = new Midas.Statusbar();
35
+ this.statusbar.destroy();
36
+
37
+ expect(this.statusbar.element).not.toBeFalsy();
38
+ expect($$('.midas-statusbar').length).toEqual(0);
39
+ });
40
+
41
+ describe('panels that are default', function() {
42
+
43
+ it('should display a path', function() {
44
+ runs(function() {
45
+ this.statusbar = new Midas.Statusbar({appendTo: 'statusbar'});
46
+
47
+ var span = $('div5').down('span');
48
+ jasmine.simulate.selection(span);
49
+ this.statusbar.update({element: $('region4')}, {});
50
+ });
51
+
52
+ waits(10);
53
+
54
+ runs(function() {
55
+ expect(this.statusbar.element.innerHTML).toMatch(/<a>div<\/a> .* <a>ul<\/a> .* <a>li<\/a>/);
56
+ });
57
+ });
58
+
59
+ });
60
+
61
+ });