~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/app/javascript/lazr/inlineedit/tests/inline_edit.js

[r=deryck][bug=803954] Bring lazr-js source into lp tree and package
        yui as a dependency

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (c) 2009, Canonical Ltd. All rights reserved. */
 
2
 
 
3
YUI().use('lazr.editor', 'lazr.testing.runner', 'node',
 
4
          'event', 'event-simulate', 'console', 'plugin', function(Y) {
 
5
var SAMPLE_HTML = "                                                                           \
 
6
 <h1>Single-line editing</h1>                                                                 \
 
7
  <div id='editable_single_text'>                                                             \
 
8
    <span id='single_text' class='yui3-editable_text-text'>Some editable inline text.</span>  \
 
9
    <button id='single_edit' class='yui3-editable_text-trigger'>Edit this</button>            \
 
10
  </div>                                                                                      \
 
11
  <hr />                                                                                      \
 
12
  <h1>Multi-line editing</h1>                                                                 \
 
13
  <div id='editable_multi_text'>                                                              \
 
14
    <button id='multi_edit' class='yui3-editable_text-trigger'>Edit this</button>             \
 
15
    <span id='multi_text' class='yui3-editable_text-text'>                                    \
 
16
      <p>Some editable multi-line text.</p></span>                                            \
 
17
  </div>                                                                                      \
 
18
";
 
19
 
 
20
var Assert = Y.Assert;  // For easy access to isTrue(), etc.
 
21
 
 
22
/* Helper to stamp a Node with an ID attribute.  Needed for YUI 2.X
 
23
 * testing, which is heavily ID-based.
 
24
 *
 
25
 * Returns the node's 'id' attribute.
 
26
 */
 
27
function id_for(node) {
 
28
    if (!node.getAttribute('id')) {
 
29
        var id = Y.stamp(node);
 
30
        node.setAttribute('id', id);
 
31
    }
 
32
    return node.getAttribute('id');
 
33
}
 
34
 
 
35
/*
 
36
 * A wrapper for the Y.Event.simulate() function.  The wrapper accepts
 
37
 * CSS selectors and Node instances instead of raw nodes.
 
38
 */
 
39
function simulate(selector, evtype) {
 
40
    var rawnode = Y.Node.getDOMNode(Y.one(selector));
 
41
    Y.Event.simulate(rawnode, evtype);
 
42
}
 
43
 
 
44
/* Helper function that creates a new editor instance. */
 
45
function make_editor(cfg) {
 
46
    return new Y.InlineEditor(cfg);
 
47
}
 
48
 
 
49
/* Helper function to clean up a dynamically added widget instance. */
 
50
function cleanup_widget(widget) {
 
51
    // Nuke the boundingBox, but only if we've touched the DOM.
 
52
    if (widget.get('rendered')) {
 
53
        var bb = widget.get('boundingBox');
 
54
        if (bb && Y.Node.getDOMNode(bb)) {
 
55
            var parentNode = bb.get('parentNode');
 
56
            if (parentNode && Y.Node.getDOMNode(parentNode)) {
 
57
                parentNode.removeChild(bb);
 
58
            }
 
59
        }
 
60
    }
 
61
    // Kill the widget itself.
 
62
    widget.destroy();
 
63
}
 
64
 
 
65
function setup_sample_html() {
 
66
    if (! Y.one("#scaffolding")) {
 
67
        Y.one(document.body).appendChild(
 
68
            Y.Node.create("<div id='scaffolding'></div>"));
 
69
    }
 
70
 
 
71
    Y.one("#scaffolding").set("innerHTML", SAMPLE_HTML);
 
72
}
 
73
 
 
74
function make_editable_text(cfg) {
 
75
    // For the editor
 
76
    // TODO: fix this ugly hack
 
77
    var defaults = {
 
78
        contentBox: '#editable_single_text',
 
79
        boundingBox: '#inline-edit-container'
 
80
    };
 
81
    return new Y.EditableText(Y.merge(defaults, cfg));
 
82
}
 
83
 
 
84
// Helper: convert size specification like "120px" to a number (in casu, 120).
 
85
var strip_px = /px$/;
 
86
function parse_size(size) {
 
87
    return parseInt(size.replace(strip_px, ''), 10);
 
88
}
 
89
 
 
90
var suite = new Y.Test.Suite("Inline Editor Tests");
 
91
 
 
92
suite.add(new Y.Test.Case({
 
93
 
 
94
    name: 'inline_editor_basics',
 
95
 
 
96
    setUp: function() {
 
97
        this.editor = make_editor();
 
98
    },
 
99
 
 
100
    tearDown: function() {
 
101
        cleanup_widget(this.editor);
 
102
    },
 
103
 
 
104
    test_input_value_set_during_sync: function() {
 
105
        /* The input element's value should be set during the syncUI()
 
106
         * call.
 
107
         */
 
108
        var ed = this.editor,
 
109
            desired_value = 'x';
 
110
 
 
111
        Assert.areNotEqual(
 
112
            desired_value,
 
113
            ed.get('value'),
 
114
            "Sanity check: the editor's value shouldn't equal our " +
 
115
            "desired value.");
 
116
        Assert.isFalse(
 
117
            ed.get('rendered'),
 
118
            "Sanity check: the widget shouldn't be rendered yet.");
 
119
 
 
120
        ed.set('value', desired_value);
 
121
        ed.render();
 
122
        Assert.areEqual(
 
123
            desired_value,
 
124
            ed.get('input_field').get('value'),
 
125
            "The editor's input field's value should have been set.");
 
126
    },
 
127
 
 
128
    test_getInput_method: function() {
 
129
        this.editor.render();
 
130
        Assert.areEqual(
 
131
            this.editor.get('input_field').get('value'),
 
132
            this.editor.getInput(),
 
133
            "The getInput() method should return the same value as " +
 
134
            "the editor's input field's current value.");
 
135
    },
 
136
 
 
137
    test_validate_values: function() {
 
138
        Assert.isFalse(this.editor.get('accept_empty'),
 
139
            "The editor shouldn't accept empty values by default.");
 
140
 
 
141
        var prev = this.editor.get('value');
 
142
        this.editor.set('value', null);
 
143
        Assert.areEqual(
 
144
            prev,
 
145
            this.editor.get('value'),
 
146
            "The editor's value should not have changed.");
 
147
 
 
148
        this.editor.set('value', '');
 
149
        Assert.areEqual(
 
150
            prev,
 
151
            this.editor.get('value'),
 
152
            "The editor should not accept the empty string as a " +
 
153
            "value if 'accept_empty' is false.");
 
154
 
 
155
        /* The control can be asked to accept empty values. */
 
156
        this.editor.set('accept_empty', true);
 
157
        this.editor.set('value', '');
 
158
        Assert.areEqual(
 
159
            '',
 
160
            this.editor.get('value'),
 
161
            "The editor should have accepted the empty string as a " +
 
162
            "valid value if 'accept_empty' is true.");
 
163
    },
 
164
 
 
165
    test_validate_empty_editor_input: function() {
 
166
        var ed = this.editor;
 
167
 
 
168
        // A helper to catch the 'save' event.
 
169
        var got_save = false;
 
170
        var after_save = function(ev) { got_save = true; };
 
171
        ed.after('ieditor:save', after_save);
 
172
 
 
173
        ed.render();
 
174
 
 
175
        Assert.isFalse(ed.hasErrors(),
 
176
            "Sanity check: the editor shouldn't be displaying any " +
 
177
            "errors.");
 
178
        Assert.isFalse(ed.get('accept_empty'),
 
179
            "Sanity check: the editor shouldn't accept empty inputs.");
 
180
 
 
181
        ed.get('input_field').set('value', '');
 
182
        ed.save();
 
183
 
 
184
        Assert.isTrue(ed.hasErrors(),
 
185
            "The editor should be displaying an error after the " +
 
186
            "trying to save an empty input.");
 
187
        Assert.isFalse(got_save,
 
188
            "The editor should not have fired a 'save' event.");
 
189
    },
 
190
 
 
191
    test_set_and_clear_error_message: function() {
 
192
        this.editor.render();
 
193
 
 
194
        var ed       = this.editor,
 
195
            edisplay = ed.get('error_message'),
 
196
            c_hidden   = 'yui3-ieditor-errors-hidden';
 
197
 
 
198
        Assert.isNotNull(
 
199
            edisplay,
 
200
            "The editor should have a valid error display node.");
 
201
 
 
202
        Assert.isTrue(
 
203
            edisplay.hasClass(c_hidden),
 
204
            "The error display should start out hidden.");
 
205
        Assert.isFalse(
 
206
            ed.get("in_error"),
 
207
            "The editor's 'in_error' attribute should not be set.");
 
208
 
 
209
        var msg = "An error has occured.";
 
210
        ed.showError(msg);
 
211
 
 
212
        Assert.areEqual(
 
213
            msg,
 
214
            edisplay.get('text'),
 
215
            "The error display's text should be set.");
 
216
        Assert.isFalse(
 
217
            edisplay.hasClass(c_hidden),
 
218
            "The error display should be visible when an error is set.");
 
219
        Assert.isTrue(
 
220
            ed.hasErrors(),
 
221
            "The editor .hasErrors() method should return true if " +
 
222
            "there are errors being displayed.");
 
223
        Assert.isTrue(
 
224
            ed.get("in_error"),
 
225
            "The editor's 'in_error' attribute should be set.");
 
226
 
 
227
        ed.clearErrors();
 
228
        Assert.isTrue(
 
229
            edisplay.hasClass(c_hidden),
 
230
            "The error display should be hidden when the error " +
 
231
            "is cleared.");
 
232
        Assert.isFalse(
 
233
            ed.hasErrors(),
 
234
            "The editor .hasErrors() method should return false " +
 
235
            "if there are no errors being displayed.");
 
236
    },
 
237
 
 
238
    test_save_input_to_editor: function() {
 
239
        var expected_value = 'abc',
 
240
            ed = this.editor;
 
241
 
 
242
        Assert.areNotEqual(
 
243
            expected_value,
 
244
            ed.get('value'),
 
245
            "Sanity check");
 
246
 
 
247
        ed.render();
 
248
        ed.get('input_field').set('value', expected_value);
 
249
        ed.save();
 
250
 
 
251
        Assert.areEqual(
 
252
            expected_value,
 
253
            ed.get('value'),
 
254
            "The value of the editor's input field should have been " +
 
255
            "saved to the editor's 'value' attribute.");
 
256
    },
 
257
 
 
258
    test_focus_method_focuses_editor_input: function() {
 
259
        this.editor.render();
 
260
 
 
261
        var input = this.editor.get('input_field'),
 
262
            test = this,
 
263
            focused = false;
 
264
 
 
265
        Y.on('focus', function() {
 
266
            focused = true;
 
267
        }, input);
 
268
 
 
269
        this.editor.focus();
 
270
 
 
271
        Assert.isTrue(focused,
 
272
            "The editor's input field should have received focus " +
 
273
            "after calling the editor's focus method.");
 
274
    },
 
275
 
 
276
    test_input_receives_focus_after_editor_errors: function() {
 
277
        this.editor.render();
 
278
 
 
279
        var ed = this.editor,
 
280
            input = this.editor.get('input_field'),
 
281
            got_focus = false;
 
282
 
 
283
        Assert.isFalse(
 
284
            ed.get('in_error'),
 
285
            "Sanity check: the editor should be clear of errors.");
 
286
        Assert.isFalse(
 
287
            ed.get('accept_empty'),
 
288
            "Sanity check: the editor should not accept empty " +
 
289
            "values.");
 
290
 
 
291
        // Force an error by setting the editor's input to the
 
292
        // empty string.
 
293
        input.set('value', '');
 
294
 
 
295
        var test = this;
 
296
        // Add our focus event listener.
 
297
        Y.on('focus', function() {
 
298
            got_focus = true;
 
299
        }, input);
 
300
 
 
301
        ed.save();
 
302
        Assert.isTrue(
 
303
            ed.get('in_error'),
 
304
            "Sanity check: the editor should be in an error state " +
 
305
            "after saving an empty value.");
 
306
 
 
307
        Assert.isTrue(
 
308
            got_focus,
 
309
            "The editor's input field should have the current " +
 
310
            "focus.");
 
311
    },
 
312
 
 
313
    test_widget_has_a_disabled_tabindex_when_focused: function() {
 
314
        // The tabindex attribute appears when the widget is focused.
 
315
        this.editor.render();
 
316
        this.editor.focus();
 
317
 
 
318
        // Be aware that in IE, get('tabIndex') and getAttribute('tabIndex')
 
319
        // return different values when set to -1. This is due to YUI's
 
320
        // getAttribute() calling dom_node.getAttribute('tabIndex', 2), which
 
321
        // is an IE extension.
 
322
        // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
 
323
        Assert.areEqual(
 
324
            -1,
 
325
            this.editor.get('boundingBox').get('tabIndex'),
 
326
            "The widget should have a tabindex of -1 (disabled).");
 
327
    },
 
328
 
 
329
    test_enter_key_saves_input: function() {
 
330
        this.editor.render();
 
331
 
 
332
        var ed = this.editor,
 
333
            input_element = Y.Node.getDOMNode(
 
334
                this.editor.get('input_field'));
 
335
 
 
336
        input_element.value = 'abc';
 
337
 
 
338
        // A helper to flag the 'save' event.
 
339
        var saved = false;
 
340
        function saveCheck(e) {
 
341
            saved = true;
 
342
        }
 
343
 
 
344
        ed.after('ieditor:save', saveCheck, this);
 
345
 
 
346
        // Simulate an 'Enter' key event in the editor's input field.
 
347
        Y.Event.simulate(input_element, "keydown", { keyCode: 13 });
 
348
 
 
349
        Assert.isFalse(ed.hasErrors());
 
350
        Assert.isTrue(saved,
 
351
            "Pressing the 'Enter' key inside the editor's input field " +
 
352
            "should save the input.");
 
353
    },
 
354
 
 
355
    test_enter_key_ignored_in_multiline: function() {
 
356
        this.editor.set('multiline', true);
 
357
        this.editor.render();
 
358
 
 
359
        var ed = this.editor;
 
360
        var input_element = Y.Node.getDOMNode(this.editor.get('input_field'));
 
361
 
 
362
        input_element.value = 'abc';
 
363
 
 
364
        // A helper to flag the 'save' event.
 
365
        var saved = false;
 
366
        function saveCheck(e) {
 
367
            saved = true;
 
368
        }
 
369
 
 
370
        ed.after('ieditor:save', saveCheck, this);
 
371
 
 
372
        // Simulate an 'Enter' key event in the editor's input field.
 
373
        Y.Event.simulate(input_element, "keydown", { keyCode: 13 });
 
374
 
 
375
        // Restore to previous state.
 
376
        this.editor.set('multiline', false);
 
377
 
 
378
        Assert.isFalse(ed.hasErrors());
 
379
        Assert.isFalse(saved,
 
380
            "Pressing the 'Enter' key in multiline mode " +
 
381
            "should not trigger a save.");
 
382
    },
 
383
 
 
384
    test_input_should_be_trimmed_of_whitespace: function() {
 
385
        this.editor.render();
 
386
 
 
387
        var input = this.editor.get('input_field');
 
388
 
 
389
        // Set a whitespace value as the input.
 
390
        input.set('value', '  ');
 
391
 
 
392
        this.editor.save();
 
393
 
 
394
        Assert.isTrue(
 
395
            this.editor.hasErrors(),
 
396
            "The editor should be displaying an error after trying to " +
 
397
            "save a whitespace value.");
 
398
    }
 
399
}));
 
400
 
 
401
suite.add(new Y.Test.Case({
 
402
    name: 'Initial value',
 
403
 
 
404
    setUp: function() {
 
405
        this.editor = make_editor({initial_value_override: 'Initial value'});
 
406
    },
 
407
 
 
408
    tearDown: function() {
 
409
        cleanup_widget(this.editor);
 
410
    },
 
411
 
 
412
    test_initial_value_override: function() {
 
413
        this.editor.render();
 
414
        Assert.areEqual(
 
415
            'Initial value',
 
416
            this.editor.get('input_field').get('value'),
 
417
            "The editor's input field should have the initial value.");
 
418
    }
 
419
}));
 
420
 
 
421
suite.add(new Y.Test.Case({
 
422
    name: 'Editable text initial values',
 
423
 
 
424
    setUp: function() {
 
425
        setup_sample_html();
 
426
        this.etext = make_editable_text(
 
427
            {initial_value_override: 'Initial value'});
 
428
    },
 
429
 
 
430
    tearDown: function() {
 
431
        // Reset the <span>.
 
432
        cleanup_widget(this.etext);
 
433
    },
 
434
 
 
435
    test_save_initial_value_override: function() {
 
436
        this.etext.render();
 
437
 
 
438
        Assert.areEqual(
 
439
            'Initial value',
 
440
            this.etext.editor.get('input_field').get('value'),
 
441
            "The input_field should have been set to the initial value.");
 
442
 
 
443
        this.etext.editor.save();
 
444
        Assert.areEqual(
 
445
            'Initial value',
 
446
            this.etext.editor.get('value'),
 
447
            "The editor's initial value did not get saved.");
 
448
        Assert.areEqual(
 
449
            null,
 
450
            this.etext.editor.get('initial_value_override'),
 
451
            "The editor's initial_value_override should be null.");
 
452
    },
 
453
 
 
454
    test_cancel_does_not_modify_value: function() {
 
455
        this.etext.render();
 
456
 
 
457
        Assert.areEqual(
 
458
            'Some editable inline text.',
 
459
            this.etext.editor.get('value'),
 
460
            "The editor's value is not what it should be.");
 
461
        Assert.areEqual(
 
462
            'Initial value',
 
463
            this.etext.editor.get('initial_value_override'),
 
464
            "The editor's initial_value_override is not what it should be.");
 
465
 
 
466
        this.etext.editor.cancel();
 
467
        Assert.areEqual(
 
468
            'Some editable inline text.',
 
469
            this.etext.editor.get('value'),
 
470
            "The editor's value did not get reset.");
 
471
        Assert.areEqual(
 
472
            'Initial value',
 
473
            this.etext.editor.get('initial_value_override'),
 
474
            "The editor's initial_value_override did not get preserved.");
 
475
    }
 
476
}));
 
477
 
 
478
suite.add(new Y.Test.Case({
 
479
 
 
480
    name: "Inline editor input sizing for a positive size value",
 
481
 
 
482
    setUp: function() {
 
483
        this.expected_size = 32;
 
484
        this.editor = make_editor({size: this.expected_size});
 
485
    },
 
486
 
 
487
    tearDown: function() {
 
488
        cleanup_widget(this.editor);
 
489
    },
 
490
 
 
491
    test_editor_size_attribute_matches_user_value: function() {
 
492
        Assert.areEqual(
 
493
            this.editor.get('size'),
 
494
            this.expected_size,
 
495
            "The editor's 'size' attribute should match the user's " +
 
496
            "specified size.");
 
497
    },
 
498
 
 
499
    test_input_field_size_matches_the_editor_size: function() {
 
500
        this.editor.render();
 
501
        var input = this.editor.get('input_field');
 
502
        Assert.areEqual(
 
503
            this.expected_size + 'ex',
 
504
            input.getStyle('width'),
 
505
            "The editor's input field size should have been set from the " +
 
506
            "'size' attribute.");
 
507
    }
 
508
 
 
509
}));
 
510
 
 
511
suite.add(new Y.Test.Case({
 
512
 
 
513
    name: "Inline editor input sizing for a null size value",
 
514
 
 
515
    setUp: function() {
 
516
        this.editor = make_editor();
 
517
        this.editor.render();
 
518
    },
 
519
 
 
520
    tearDown: function() {
 
521
        cleanup_widget(this.editor);
 
522
    },
 
523
 
 
524
    test_editor_size_attribute_is_null: function() {
 
525
        Assert.areEqual(
 
526
            null,
 
527
            this.editor.get('size'),
 
528
            "The editor's 'size' attribute should default to 'null'.");
 
529
    },
 
530
 
 
531
    test_editor_input_has_browser_default_size: function() {
 
532
        var input = this.editor.get('input_field');
 
533
        Assert.isFalse(
 
534
            input.hasAttribute('size'),
 
535
            "The editor's input field should have the browser default " +
 
536
            "size if the editor's size is 'null'.");
 
537
    }
 
538
}));
 
539
 
 
540
/*
 
541
 * XXX mars 20090206
 
542
 *
 
543
 * The following test is just for the attribute validators.  Most of this is
 
544
 * made necessary because YUI doesn't publish attribute validation errors.
 
545
 *
 
546
 * See ticket http://yuilibrary.com/projects/yui3/ticket/2525946
 
547
 */
 
548
suite.add(new Y.Test.Case({
 
549
 
 
550
    name: "Inline editor size attribute validation",
 
551
 
 
552
    setUp: function() {
 
553
        this.initial_size = null;
 
554
        this.editor = make_editor({size: this.initial_size});
 
555
    },
 
556
 
 
557
    test_editor_accepts_null_as_size: function() {
 
558
        this.editor.set('size', null);
 
559
        Assert.areEqual(
 
560
            null,
 
561
            this.editor.get('size'),
 
562
            "The editor should accept a null value for the size attribute.");
 
563
    },
 
564
 
 
565
    test_editor_accepts_positive_numbers_as_size: function() {
 
566
        this.editor.set('size', 123);
 
567
        Assert.areEqual(
 
568
            123,
 
569
            this.editor.get('size'),
 
570
            "The editor should accept a positive number as a valid size.");
 
571
    },
 
572
 
 
573
    test_editor_rejects_negative_numbers_for_size: function() {
 
574
        this.editor.set('size', -2);
 
575
        Assert.areEqual(
 
576
            this.initial_size,
 
577
            this.editor.get('size'),
 
578
            "The editor should not accept negative numbers for its size.");
 
579
    },
 
580
 
 
581
    test_editor_rejects_characters_for_size: function() {
 
582
        this.editor.set('size', 'a');
 
583
        Assert.areEqual(
 
584
            this.initial_size,
 
585
            this.editor.get('size'),
 
586
            "The editor should not accept strings for its size.");
 
587
    }
 
588
}));
 
589
 
 
590
 
 
591
suite.add(new Y.Test.Case({
 
592
 
 
593
    name: 'editor_save_state_change',
 
594
 
 
595
    setUp: function() {
 
596
        this.editor = make_editor();
 
597
    },
 
598
 
 
599
    tearDown: function() {
 
600
        cleanup_widget(this.editor);
 
601
    },
 
602
 
 
603
    test_ui_initial_state_is_not_waiting: function() {
 
604
        this.editor.render();
 
605
        Assert.isFalse(
 
606
            this.editor.get('boundingBox').hasClass('yui3-ieditor-waiting'),
 
607
            "The editor UI should not start out in the 'waiting' state.");
 
608
    },
 
609
 
 
610
    test_set_ui_waiting_state: function() {
 
611
        var ed = this.editor;
 
612
        ed.render();
 
613
 
 
614
        ed._uiSetWaiting();
 
615
 
 
616
        Assert.isTrue(
 
617
            ed.get('input_field').get('disabled'),
 
618
            "The editor's input should be disabled while in the " +
 
619
            "'waiting' state.");
 
620
        Assert.isTrue(
 
621
            ed.get('boundingBox').hasClass('yui3-ieditor-waiting'),
 
622
            "The editor's UI should reflect the 'waiting' state " +
 
623
            "with an appropriate class.");
 
624
    },
 
625
 
 
626
    test_clear_ui_waiting_state: function() {
 
627
        var ed = this.editor;
 
628
        ed.render();
 
629
 
 
630
        ed._uiSetWaiting();
 
631
        ed._uiClearWaiting();
 
632
 
 
633
        Assert.isFalse(
 
634
            ed.get('input_field').get('disabled'),
 
635
            "The editor's input should be re-enabled when clearing " +
 
636
            "the 'waiting' state.");
 
637
        Assert.isFalse(
 
638
            ed.get('boundingBox').hasClass('yui3-ieditor-waiting'),
 
639
            "The editor's UI should have the 'waiting' state " +
 
640
            "class removed.");
 
641
    }
 
642
}));
 
643
 
 
644
 
 
645
suite.add(new Y.Test.Case({
 
646
 
 
647
    name: 'editable_text',
 
648
 
 
649
    setUp: function() {
 
650
        setup_sample_html();
 
651
        this.etext = make_editable_text();
 
652
    },
 
653
 
 
654
    tearDown: function() {
 
655
        cleanup_widget(this.etext);
 
656
    },
 
657
 
 
658
    test_initial_values_from_DOM: function() {
 
659
        Assert.areEqual(
 
660
            Y.one("#single_text"),
 
661
            this.etext.get('text'),
 
662
            "The editor's text node should have been set from the " +
 
663
            "DOM.");
 
664
 
 
665
        Assert.areEqual(
 
666
            Y.one('#single_edit'),
 
667
            this.etext.get('trigger'),
 
668
            "The editor's trigger node should have been set from " +
 
669
            "the DOM.");
 
670
 
 
671
        Assert.areEqual(
 
672
            'Some editable inline text.',
 
673
            this.etext.editor.get('value'),
 
674
            "The editor's initial value should be set from it's " +
 
675
            "text node.");
 
676
 
 
677
        Assert.areEqual(
 
678
            this.etext.editor.get('value'),
 
679
            this.etext.get('value'),
 
680
            "The editable text's value should be the same as the " +
 
681
            "editor's.");
 
682
    },
 
683
 
 
684
    test_show: function() {
 
685
        /* The show() method should display the editor, and hide the
 
686
         * existing contents.
 
687
         */
 
688
        this.etext.render();
 
689
        this.etext.show_editor();
 
690
        Assert.isTrue(this.etext.editor.get('visible'),
 
691
            "The editor's 'visible' attribute should be true.");
 
692
    },
 
693
 
 
694
    test_hide: function() {
 
695
        /* The hide() method should hide the editor, and display the
 
696
         * original contents.
 
697
         */
 
698
        this.etext.render();
 
699
        this.etext.show_editor();
 
700
        this.etext.hide_editor();
 
701
        Assert.isFalse(this.etext.editor.get('visible'),
 
702
            "The editor's 'visible' attribute should be False.");
 
703
    },
 
704
 
 
705
    test_trigger_edit: function() {
 
706
        /* Clicking on the editable text's "Edit" button should
 
707
         * make the editor visible.
 
708
         */
 
709
        Assert.isFalse(this.etext.editor.get('visible'),
 
710
            "Sanity check, the editor should be hidden.");
 
711
 
 
712
        this.etext.render();
 
713
        simulate('#single_edit', 'click');
 
714
 
 
715
        Assert.isTrue(this.etext.editor.get('visible'),
 
716
            "The editor should be visible.");
 
717
    },
 
718
 
 
719
    test_text_is_updated_to_saved_value: function() {
 
720
        this.etext.render();
 
721
 
 
722
        // Grab the normalized text.
 
723
        var expected_value = 'abc';
 
724
 
 
725
        Assert.areNotEqual(
 
726
            expected_value,
 
727
            this.etext.get('value'),
 
728
            "Sanity check");
 
729
 
 
730
        simulate('#single_edit', 'click');
 
731
        this.etext.editor
 
732
            .get('input_field')
 
733
            .set('value', expected_value);
 
734
 
 
735
        this.etext.editor.save();
 
736
 
 
737
        Assert.areEqual(
 
738
            expected_value,
 
739
            this.etext.editor.get('value'),
 
740
            "Sanity check: the editor's value should have been " +
 
741
            "saved.");
 
742
 
 
743
        Assert.areEqual(
 
744
            expected_value,
 
745
            this.etext.get('value'),
 
746
            "The editable text's current value should be updated " +
 
747
            "after saving some new text in the editor.");
 
748
    },
 
749
 
 
750
    test_text_is_escaped: function() {
 
751
        this.etext.render();
 
752
 
 
753
        var input_value = '<i>l33t inject0r d00d</i> 0wnz y00';
 
754
        var shown_value = '&lt;i&gt;l33t inject0r d00d&lt;/i&gt; 0wnz y00';
 
755
 
 
756
        simulate('#single_edit', 'click');
 
757
        this.etext.editor.setInput(input_value);
 
758
        this.etext.editor.save();
 
759
 
 
760
        Assert.areEqual(
 
761
            shown_value,
 
762
            this.etext.get('text').get('innerHTML'),
 
763
            "Input text should be escaped before being inserted in HTML.");
 
764
        Assert.areEqual(
 
765
            input_value,
 
766
            this.etext.editor.getInput(),
 
767
            "Input text should be retained verbatim.");
 
768
    },
 
769
 
 
770
    test_accept_empty_attribute_passthrough: function() {
 
771
        var et = this.etext;
 
772
 
 
773
        Assert.areEqual(
 
774
            et.get('accept_empty'),
 
775
            et.editor.get('accept_empty'),
 
776
            "The editor and inline editor's 'accept_empty " +
 
777
            "should start out the same.");
 
778
 
 
779
        et.set('accept_empty', true);
 
780
        Assert.isTrue(
 
781
            et.editor.get('accept_empty'),
 
782
            "The inline editor's 'accept_empty' attribute should " +
 
783
            "also be set to 'true'.");
 
784
        Assert.isTrue(
 
785
            et.get('accept_empty'),
 
786
            "The editor's 'accept_empty' attribute should be true.");
 
787
 
 
788
        et.set('accept_empty', false);
 
789
        Assert.isFalse(
 
790
            et.get('accept_empty'),
 
791
            "The editor's 'accept_empty' attribute should be false.");
 
792
        Assert.isFalse(
 
793
            et.editor.get('accept_empty'),
 
794
            "The inline editor's 'accept_empty' attribute should " +
 
795
            "also be set to 'false'.");
 
796
    },
 
797
 
 
798
    test_widget_has_a_disabled_tabindex_when_focused: function() {
 
799
        // The tabindex attribute appears when the widget is focused.
 
800
        this.etext.render();
 
801
        this.etext.focus();
 
802
 
 
803
        // Be aware that in IE, get('tabIndex') and getAttribute('tabIndex')
 
804
        // return different values when set to -1. This is due to YUI's
 
805
        // getAttribute() calling dom_node.getAttribute('tabIndex', 2), which
 
806
        // is an IE extension.
 
807
        // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
 
808
 
 
809
        // On IE and KHTML, EditableText._onRender() will prevent the
 
810
        // default widget rendering that would set the tabIndex on the
 
811
        // boundingBox, so this test will fail for those browsers.
 
812
        Assert.areEqual(
 
813
            -1,
 
814
            this.etext.get('boundingBox').get('tabIndex'),
 
815
            "The widget should have a tabindex of -1 (disabled).");
 
816
    },
 
817
 
 
818
    test_trigger_is_disabled_if_the_widget_is_not_rendered: function() {
 
819
        var trigger = this.etext.get('trigger');
 
820
        Assert.isInstanceOf(
 
821
            Y.Node, trigger,
 
822
            "Sanity check: the editor's trigger should be a valid node.");
 
823
        Assert.isFalse(
 
824
            this.etext.get('rendered'),
 
825
            "Sanity check: the editor should not be rendered.");
 
826
 
 
827
        simulate(trigger, 'click');
 
828
        // Peek inside the box a bit, and check that the nested editor
 
829
        // instance is still invisible.  Assume that if it is, then
 
830
        // the show_editor() method was never called.
 
831
        Assert.isFalse(
 
832
            this.etext.editor.get('visible'),
 
833
            "Triggering an unrendered editor should not display the widget.");
 
834
    }
 
835
}));
 
836
 
 
837
suite.add(new Y.Test.Case({
 
838
 
 
839
    name: "EditableText single-line/multi-line modes",
 
840
 
 
841
    setUp: function() {
 
842
        setup_sample_html();
 
843
        this.single = make_editable_text({
 
844
            contentBox: '#editable_single_text',
 
845
            multiline: false
 
846
        });
 
847
        this.single.render();
 
848
        this.single.show_editor();
 
849
        this.multi = make_editable_text({
 
850
            contentBox: '#editable_multi_text',
 
851
            multiline: true
 
852
        });
 
853
        this.multi.render();
 
854
        this.multi.show_editor();
 
855
    },
 
856
 
 
857
    tearDown: function() {
 
858
        cleanup_widget(this.single);
 
859
        cleanup_widget(this.multi);
 
860
    },
 
861
 
 
862
    test_multi_line_has_larger_minimum: function() {
 
863
        var single = this.single.editor;
 
864
        var multi = this.multi.editor;
 
865
 
 
866
        single.setInput('');
 
867
        multi.setInput('');
 
868
 
 
869
        var single_height = single.get('input_field').getStyle('height');
 
870
        var multi_height = multi.get('input_field').getStyle('height');
 
871
 
 
872
        single_height = parse_size(single_height);
 
873
        multi_height = parse_size(multi_height);
 
874
 
 
875
        Assert.areNotEqual(
 
876
            multi_height,
 
877
            single_height,
 
878
            "Multi-line and single-line editors should have different sizes.");
 
879
        Assert.isTrue(
 
880
            multi_height > single_height,
 
881
            "Multi-line editor should start out larger.");
 
882
    },
 
883
 
 
884
    test_single_line_top_button_box: function() {
 
885
        var box = this.single.editor.get("top_buttons");
 
886
        Assert.areEqual(
 
887
            null,
 
888
            box,
 
889
            "Single-line editor should not have a top button box.");
 
890
    },
 
891
 
 
892
    test_multi_line_top_button_box: function() {
 
893
        var box = this.multi.editor.get("top_buttons");
 
894
        Assert.areNotEqual(
 
895
            null,
 
896
            box,
 
897
            "Multi-line editor should have a top button box.");
 
898
    }
 
899
}));
 
900
 
 
901
suite.add(new Y.Test.Case({
 
902
 
 
903
    name: "Editor input sizing",
 
904
 
 
905
    setUp: function() {
 
906
        this.expected_size = 23;
 
907
        this.editor = make_editor({
 
908
            contentBox: '#editable_single_text',
 
909
            size: this.expected_size
 
910
        });
 
911
        this.editor.render();
 
912
 
 
913
        this.long_line = 'mm mmmmm mmmmmmm mm mmm mm mmmmmmm mmmmmmmm mm mmm m';
 
914
        this.long_word = 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm';
 
915
        this.short_line = 'hi mom';
 
916
    },
 
917
 
 
918
    tearDown: function() {
 
919
        cleanup_widget(this.editor);
 
920
    },
 
921
 
 
922
    test_size_attribute_passthrough: function() {
 
923
        // create a new editor with the input size set to 23 characters.
 
924
        Assert.areEqual(
 
925
            this.expected_size,
 
926
            this.editor.get('size'),
 
927
            "The inline editor widget should have received a size " +
 
928
            "from the EditableText widget.");
 
929
    },
 
930
 
 
931
    test_long_text_wraps: function() {
 
932
        var editor = this.editor,
 
933
            input = editor.get('input_field'),
 
934
            original_height = input.getStyle('height');
 
935
 
 
936
        editor.setInput(this.long_line);
 
937
        var new_height = input.getStyle('height');
 
938
 
 
939
        Assert.areNotEqual(
 
940
            original_height,
 
941
            new_height,
 
942
            "Inserting a long text should grow the input area.");
 
943
        Assert.areEqual(
 
944
            'hidden',
 
945
            input.getStyle('overflow'),
 
946
            "Scrollbars should be hidden when no unbreakable lines present.");
 
947
        Assert.isTrue(
 
948
            parse_size(new_height) > parse_size(original_height),
 
949
            "A grown input area should be larger than before.");
 
950
    },
 
951
 
 
952
    test_long_words_scroll: function() {
 
953
        var editor = this.editor,
 
954
            input = editor.get('input_field'),
 
955
            original_height = input.getStyle('height');
 
956
 
 
957
        editor.setInput(this.long_word);
 
958
        var new_height = input.getStyle('height');
 
959
 
 
960
        Assert.areNotEqual(
 
961
            original_height,
 
962
            new_height,
 
963
            "Long words should add legroom for a horizontal scrollbar.");
 
964
        Assert.isTrue(
 
965
            parse_size(new_height) > parse_size(original_height),
 
966
            "A grown input area should be larger than before.");
 
967
    },
 
968
 
 
969
    test_resize_on_growth: function() {
 
970
        var editor = this.editor,
 
971
            input = editor.get('input_field');
 
972
 
 
973
        var test = this;
 
974
        var resized = false;
 
975
        editor.on('ieditor:resized', function() {
 
976
            resized = true;
 
977
        });
 
978
        input.set('value', this.long_line);
 
979
        editor.updateSize();
 
980
        Assert.isTrue(resized, "Editor resize event was not fired.");
 
981
    },
 
982
 
 
983
    test_resize_on_shrinkage: function() {
 
984
        var editor = this.editor,
 
985
            input = editor.get('input_field');
 
986
 
 
987
        editor.setInput(this.long_line);
 
988
 
 
989
        var test = this;
 
990
        var resized = false;
 
991
        editor.on('ieditor:resized', function() {
 
992
            resized = true;
 
993
        });
 
994
        input.set('value', this.short_line);
 
995
 
 
996
        editor.updateSize();
 
997
        Assert.isTrue(resized, "Editor resize event was not fired.");
 
998
    },
 
999
 
 
1000
    test_long_text_unwraps: function() {
 
1001
        var editor = this.editor,
 
1002
            input = editor.get('input_field');
 
1003
 
 
1004
        editor.setInput(this.short_line);
 
1005
        var original_height = input.getStyle('height');
 
1006
 
 
1007
        editor.setInput(this.long_line);
 
1008
        editor.setInput(this.short_line);
 
1009
        var new_height = input.getStyle('height');
 
1010
 
 
1011
        Assert.areEqual(
 
1012
            original_height,
 
1013
            new_height,
 
1014
            "Removing lines of text should shrink the input area.");
 
1015
    },
 
1016
 
 
1017
    test_long_words_unscroll: function() {
 
1018
        var editor = this.editor,
 
1019
            input = editor.get('input_field');
 
1020
 
 
1021
        editor.setInput(this.short_line);
 
1022
        var original_height = input.getStyle('height');
 
1023
 
 
1024
        editor.setInput(this.long_word);
 
1025
        editor.setInput(this.short_line);
 
1026
        var new_height = input.getStyle('height');
 
1027
 
 
1028
        Assert.areEqual(
 
1029
            original_height,
 
1030
            new_height,
 
1031
            "Removing long words should remove legroom for scrollbar.");
 
1032
        Assert.areEqual(
 
1033
            'hidden',
 
1034
            input.getStyle('overflow'),
 
1035
            "Scrollbars should be hidden when long lines are removed.");
 
1036
    }
 
1037
}));
 
1038
 
 
1039
suite.add(new Y.Test.Case({
 
1040
    name: "Window resizing",
 
1041
 
 
1042
    setUp: function() {
 
1043
        this.small_size = 5;
 
1044
        this.large_size = 40;
 
1045
        this.short_line = 'i';
 
1046
        // A line that fits in small_size but not in large_size.
 
1047
        this.long_line = 'x xx x xx x xx x xx';
 
1048
        // A word that fits in small_size but not in large_size.
 
1049
        this.long_word = 'xxxxxxxxxxxxxxxxxxx';
 
1050
        this.editor = make_editor();
 
1051
        this.editor.render();
 
1052
    },
 
1053
 
 
1054
    tearDown: function() {
 
1055
        cleanup_widget(this.editor);
 
1056
    },
 
1057
 
 
1058
    // Pretend that a window resize has changed the editor's width to
 
1059
    // the given number of columns.  Also lets you set new contents for
 
1060
    // the input box.  Returns resulting input box height in pixels.
 
1061
    _pretendResize: function(new_size, new_text) {
 
1062
        var text = (new_text ? new_text : this.editor.getInput());
 
1063
        var content_box = this.editor.get('contentBox');
 
1064
 
 
1065
        var old_input = this.editor.get('input_field');
 
1066
        content_box.removeChild(old_input);
 
1067
        this.editor.set('input_field', null);
 
1068
 
 
1069
        var old_alter_ego = this.editor.alter_ego;
 
1070
        content_box.removeChild(old_alter_ego);
 
1071
        this.editor.alter_ego = null;
 
1072
 
 
1073
        this.editor.set('size', new_size);
 
1074
        this.editor._initInput();
 
1075
 
 
1076
        this.editor.setInput(text);
 
1077
        this.editor._windowResize();
 
1078
        return this._getInputBoxHeight();
 
1079
    },
 
1080
 
 
1081
    _getInputBoxHeight: function() {
 
1082
        var input = this.editor.get('input_field');
 
1083
        return parse_size(input.getStyle('height'));
 
1084
    },
 
1085
 
 
1086
    // Helper: assert lower < higher, and print helpful message.
 
1087
    _assertLower: function(lower, higher, failure_text) {
 
1088
        if (!(lower < higher)) {
 
1089
            // Log values in separate statements to avoid infinite recursion
 
1090
            // during ill-typed attempts at string concatenation.
 
1091
            Y.log("Expected the first of these to be lower than the second:");
 
1092
            Y.log(lower);
 
1093
            Y.log(higher);
 
1094
        }
 
1095
        Assert.isTrue(lower < higher, failure_text);
 
1096
    },
 
1097
 
 
1098
    test_resize_might_not_change_layout: function() {
 
1099
        var roomy = this._pretendResize(this.large_size, this.short_line);
 
1100
        var tight = this._pretendResize(this.small_size);
 
1101
        Assert.areEqual(
 
1102
            roomy,
 
1103
            tight,
 
1104
            "If there's enough room, resizing should not affect height.");
 
1105
    },
 
1106
 
 
1107
    test_undersize_adds_lines: function() {
 
1108
        var roomy = this._pretendResize(this.large_size, this.long_line);
 
1109
        var tight = this._pretendResize(this.small_size);
 
1110
        this._assertLower(
 
1111
            roomy,
 
1112
            tight,
 
1113
            "Undersizing a long line should break it.");
 
1114
    },
 
1115
 
 
1116
    test_oversize_removes_lines: function() {
 
1117
        var tight = this._pretendResize(this.small_size, this.long_line);
 
1118
        var roomy = this._pretendResize(this.large_size);
 
1119
        this._assertLower(
 
1120
            roomy,
 
1121
            tight,
 
1122
            "Oversizing a long line should unbreak it.");
 
1123
    },
 
1124
 
 
1125
    test_undersize_adds_scrollbar: function() {
 
1126
        // Actually, a scrollbar and/or more lines.  The spec leaves it
 
1127
        // all up to the browser, but either way we'll see a higher
 
1128
        // input box.
 
1129
        var roomy = this._pretendResize(this.large_size, this.long_word);
 
1130
        var tight = this._pretendResize(this.small_size);
 
1131
        this._assertLower(
 
1132
            roomy,
 
1133
            tight,
 
1134
            "Undersizing a long word should require a taller input box.");
 
1135
    },
 
1136
 
 
1137
    test_oversize_removes_scrollbar: function() {
 
1138
        var tight = this._pretendResize(this.small_size, this.long_word);
 
1139
        var roomy = this._pretendResize(this.large_size);
 
1140
        this._assertLower(
 
1141
            roomy,
 
1142
            tight,
 
1143
            "Oversizing a long word should reduce input box height.");
 
1144
    },
 
1145
 
 
1146
    test_resize_works_while_hidden: function() {
 
1147
        var roomy = this._pretendResize(this.large_size, this.long_line);
 
1148
        this.editor.hide();
 
1149
        var tight = this._pretendResize(this.small_size);
 
1150
        this.editor.show();
 
1151
        this.editor.updateSize();
 
1152
        Assert.areNotEqual(
 
1153
            roomy,
 
1154
            tight,
 
1155
            "Editors should notice window resizes even while hidden.");
 
1156
    }
 
1157
}));
 
1158
 
 
1159
 
 
1160
suite.add(new Y.Test.Case({
 
1161
    name: "EditableText text value",
 
1162
 
 
1163
    setUp: function() {
 
1164
        setup_sample_html();
 
1165
        this.multi = make_editable_text({
 
1166
 
 
1167
            contentBox: '#editable_multi_text',
 
1168
            multiline: true
 
1169
        });
 
1170
        this.multi.render();
 
1171
    },
 
1172
 
 
1173
    tearDown: function() {
 
1174
        cleanup_widget(this.multi);
 
1175
    },
 
1176
 
 
1177
    test_text_value_no_trailing_newlines: function() {
 
1178
        var text = this.multi.get('value');
 
1179
        Assert.areEqual(
 
1180
           "Some editable multi-line text.",
 
1181
           text,
 
1182
           "The editor kills trailing whitespace.");
 
1183
    }
 
1184
}));
 
1185
 
 
1186
function FailedSavePlugin() {
 
1187
  FailedSavePlugin.superclass.constructor.apply(this, arguments);
 
1188
}
 
1189
 
 
1190
FailedSavePlugin.NAME = 'failedsave';
 
1191
FailedSavePlugin.NS = 'test';
 
1192
 
 
1193
Y.extend(FailedSavePlugin, Y.Plugin.Base, {
 
1194
    initializer: function(config) {
 
1195
      this.doBefore("_saveData", this._altSave);
 
1196
    },
 
1197
 
 
1198
    _altSave: function() {
 
1199
      var host  = this.get('host');
 
1200
      // Set the UI 'waiting' status.
 
1201
      host._uiSetWaiting();
 
1202
      host.showError("Some error occurred.");
 
1203
      // Make sure we clear the 'waiting' status.
 
1204
      host._uiClearWaiting();
 
1205
      return new Y.Do.Halt();
 
1206
    }
 
1207
  });
 
1208
 
 
1209
suite.add(new Y.Test.Case({
 
1210
    name: "Edit buttons enabled on error",
 
1211
 
 
1212
    setUp: function() {
 
1213
        setup_sample_html();
 
1214
        this.multi = make_editable_text({
 
1215
 
 
1216
            contentBox: '#editable_multi_text',
 
1217
            multiline: true
 
1218
        });
 
1219
        this.multi.render();
 
1220
        this.multi.show_editor();
 
1221
    },
 
1222
 
 
1223
    tearDown: function() {
 
1224
        cleanup_widget(this.multi);
 
1225
    },
 
1226
 
 
1227
    test_error_on_save_enabled_buttons: function() {
 
1228
        var editor = this.multi.editor;
 
1229
        editor.plug({fn:FailedSavePlugin});
 
1230
        // Now saving should invoke an error.
 
1231
        editor.save();
 
1232
        Assert.isTrue(editor.get('in_error'), "Editor should be in error");
 
1233
        // Both the submit and cancel buttons should be visible.
 
1234
        Assert.areEqual(
 
1235
            'inline',
 
1236
            editor.get('submit_button').getStyle('display'),
 
1237
            "Submit should be set to display:inline");
 
1238
        Assert.areEqual(
 
1239
            'inline',
 
1240
            editor.get('cancel_button').getStyle('display'),
 
1241
            "Cancel should be set to display:inline");
 
1242
    }
 
1243
}));
 
1244
 
 
1245
 
 
1246
 
 
1247
Y.lazr.testing.Runner.add(suite);
 
1248
Y.lazr.testing.Runner.run();
 
1249
 
 
1250
});