midas 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 (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
+ });