ballonizer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/examples/ballonizer_app/config.ru +59 -0
  3. data/examples/ballonizer_app/index.html +159 -0
  4. data/examples/ballonizer_js_module/index.html +196 -0
  5. data/lib/assets/javascripts/ballonizer.js +482 -0
  6. data/lib/assets/stylesheets/ballonizer.css +78 -0
  7. data/lib/ballonizer.rb +201 -36
  8. data/spec/ballonizer_spec.rb +153 -2
  9. data/spec/javascripts/ballonizer_spec.js +568 -0
  10. data/spec/javascripts/fixtures/ballonized-xkcd-with-anchor-in-image.html +163 -0
  11. data/spec/javascripts/fixtures/ballonized-xkcd-with-ballons.html +163 -0
  12. data/spec/javascripts/fixtures/ballonized-xkcd-without-ballons.html +163 -0
  13. data/spec/javascripts/fixtures/xkcd.css +191 -0
  14. data/spec/javascripts/helpers/jasmine-jquery.js +660 -0
  15. data/spec/javascripts/helpers/jquery.simulate-ext.js +32 -0
  16. data/spec/javascripts/helpers/jquery.simulate.drag-n-drop.js +583 -0
  17. data/spec/javascripts/helpers/jquery.simulate.js +328 -0
  18. data/spec/javascripts/support/jasmine.yml +99 -0
  19. data/vendor/assets/javascripts/jquery-2.0.1.js +8837 -0
  20. data/vendor/assets/javascripts/jquery-ui-1.10.3.custom.min.js +6 -0
  21. data/vendor/assets/javascripts/jquery.json-2.4.min.js +24 -0
  22. data/vendor/assets/stylesheets/ui-lightness/images/animated-overlay.gif +0 -0
  23. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  24. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  25. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_flat_10_000000_40x100.png +0 -0
  26. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  27. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  28. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  29. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  30. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  31. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  32. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_222222_256x240.png +0 -0
  33. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_228ef1_256x240.png +0 -0
  34. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_ef8c08_256x240.png +0 -0
  35. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_ffd27a_256x240.png +0 -0
  36. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_ffffff_256x240.png +0 -0
  37. data/vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.3.custom.min.css +5 -0
  38. metadata +51 -3
@@ -0,0 +1,568 @@
1
+ // The order of the requires bellow is important
2
+ //= require jquery-2.0.1.js
3
+ //= require jquery-ui-1.10.3.custom.min.js
4
+ //= require jquery.json-2.4.min.js
5
+ //= require jquery.simulate.js
6
+ //= require jquery.simulate-ext.js
7
+ //= require jquery.simulate.drag-n-drop.js
8
+
9
+ describe("Ballonizer", function () {
10
+ /* jshint strict: false */
11
+ // We disable the strict mode here because the libs included
12
+ // with "//= require" break the tests if with strict mode
13
+
14
+ // Variables pre-set before each test
15
+ var actionFormURL,
16
+ imageToBallonizeCSSSelector,
17
+ initialBallonText,
18
+ instance;
19
+
20
+ beforeEach(function () {
21
+ actionFormURL = "/action/path/to/submit";
22
+ imageToBallonizeCSSSelector = ".ballonizer_image_container";
23
+ initialBallonText = "double click to edit";
24
+ instance = null;
25
+
26
+ this.addMatchers({
27
+ toHaveBallonizerForm: function (actionFormURL) {
28
+ var lastBodyChild = this.actual.children().last();
29
+
30
+ expect(lastBodyChild).toBe("form.ballonizer_page_form" +
31
+ "[method='post'][action='" + actionFormURL + "']");
32
+ expect(lastBodyChild).toContain("input[type='submit']" +
33
+ "[name='ballonizer_submit']");
34
+ expect(lastBodyChild).toContain("input[type='hidden']" +
35
+ "[name='ballonizer_data']");
36
+
37
+ return true;
38
+ },
39
+ toBeAnEmptyObject: function () {
40
+ var obj = this.actual;
41
+ for (var p in obj) {
42
+ if (obj.hasOwnProperty(p)) {
43
+ this.message = "expected object to be empty but" +
44
+ "found the key '" + p + "'";
45
+
46
+ return false;
47
+ }
48
+ }
49
+ return true;
50
+ }
51
+ });
52
+ });
53
+
54
+ afterEach(function () {
55
+ // Remove left-overs created out of the #jasmine-fixtures container
56
+ $(".ballonizer_page_form").remove();
57
+ });
58
+
59
+ it("it's defined", function () {
60
+ expect(Ballonizer).toBeDefined();
61
+ });
62
+
63
+ describe("when exist images to ballonize in the document", function () {
64
+ describe("but none in the context", function () {
65
+ it("doesn't create a form", function () {
66
+ loadFixtures("ballonized-xkcd-without-ballons.html");
67
+ Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#bottom"));
68
+ expect($("form.ballonizer_page_form")).not.toExist();
69
+ });
70
+ it(".getForm return null", function () {
71
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#bottom"));
72
+ expect(instance.getForm()).toEqual(null);
73
+ });
74
+ });
75
+ describe("and they are in the context", function () {
76
+ it("create a hidden form as the last child of the context", function () {
77
+ loadFixtures("ballonized-xkcd-without-ballons.html");
78
+ Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
79
+ expect($("#jasmine-fixtures")).toHaveBallonizerForm(actionFormURL);
80
+ expect($("form.ballonizer_page_form")).toBeHidden();
81
+ });
82
+ it("changes in .getForm result affect the node in the DOM", function () {
83
+ loadFixtures("ballonized-xkcd-without-ballons.html");
84
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
85
+ instance.getForm().attr("method", "get");
86
+ expect($("form.ballonizer_page_form")).toHaveAttr("method", "get");
87
+ });
88
+ });
89
+ describe("when the ballonized image is double clicked", function () {
90
+ it("a new ballon will be created", function () {
91
+ loadFixtures("ballonized-xkcd-without-ballons.html");
92
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
93
+
94
+ var ballonizedImg = $(".ballonizer_image_container img");
95
+ ballonizedImg.trigger("dblclick");
96
+ expect($(".ballonizer_ballon")).toExist();
97
+ });
98
+ it("the default action of clicking the image (if any) will not trigger", function () {
99
+ loadFixtures("ballonized-xkcd-with-anchor-in-image.html");
100
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
101
+ var spyAnchor = spyOnEvent("#comic a", "click");
102
+ var ballonizedImg = $(".ballonizer_image_container img");
103
+ // In a real browser the dblclick event will be generated with one or
104
+ // two clicks events before it (http://api.jquery.com/dblclick/)
105
+ ballonizedImg.trigger("click");
106
+ expect(spyAnchor).toHaveBeenPrevented();
107
+ });
108
+ });
109
+ });
110
+
111
+ describe(".getBallonizedImageContainers", function () {
112
+ describe("when there's no image to ballonize", function () {
113
+ it("return an object without any keys", function () {
114
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
115
+ var ballonizedImageContainers = instance.getBallonizedImageContainers();
116
+ expect(ballonizedImageContainers).toBeAnEmptyObject();
117
+ });
118
+ });
119
+ describe("when there's a image to ballonize", function () {
120
+ it("return an object with the img src as key and the object as value", function () {
121
+ loadFixtures("ballonized-xkcd-without-ballons.html");
122
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
123
+ var ballonizedImageContainers = instance.getBallonizedImageContainers();
124
+ expect(ballonizedImageContainers.hasOwnProperty("http://imgs.xkcd.com/comics/cells.png")).toBeTruthy();
125
+ });
126
+ });
127
+ });
128
+ describe("BallonizedImageContainer", function () {
129
+ it("adds a hidden form for the ballon edition in the container", function () {
130
+ loadFixtures("ballonized-xkcd-without-ballons.html");
131
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
132
+ expect($(".ballonizer_image_container")).toContain("form.ballonizer_image_form");
133
+ // What the value of method is not important, but the method attribute
134
+ // is required (http://www.w3.org/TR/html401/interact/forms.html#h-17.3)
135
+ expect($("form.ballonizer_image_form")).toHaveAttr("method");
136
+ expect($("form.ballonizer_image_form")).toBeHidden();
137
+ });
138
+ describe("when there's a image with ballons", function () {
139
+ it("getBallons return they", function () {
140
+ loadFixtures("ballonized-xkcd-with-ballons.html");
141
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
142
+ var ballonizedImageContainers = instance.getBallonizedImageContainers();
143
+ var ballons = ballonizedImageContainers["http://imgs.xkcd.com/comics/cells.png"].getBallons();
144
+
145
+ expect(ballons.length).toEqual(2);
146
+ expect(ballons[0].getText()).toEqual("When you see a claim that a common drug or vitamin \"kills cancer cells in a petri dish\", keep in mind:");
147
+ expect(ballons[1].getText()).toEqual("So does a handgun.");
148
+ expect(ballons[0].getPositionAndSize()).toEqual(
149
+ { left: 0, top: 0, width: 218, height: 82 }
150
+ );
151
+ expect(ballons[1].getPositionAndSize()).toEqual(
152
+ { left: 21, top: 319, width: 170, height: 19 }
153
+ );
154
+ });
155
+ });
156
+ });
157
+ describe("InterfaceBallon", function () {
158
+ var containerWidth, containerHeight;
159
+ var getBallons = function () {
160
+ loadFixtures("ballonized-xkcd-with-ballons.html");
161
+ instance = Ballonizer(actionFormURL, imageToBallonizeCSSSelector, $("#jasmine-fixtures"));
162
+ var imageContainer = instance.getBallonizedImageContainers()["http://imgs.xkcd.com/comics/cells.png"],
163
+ ballons = imageContainer.getBallons(),
164
+ containerNode = imageContainer.getContainerNode();
165
+
166
+ containerWidth = containerNode.width();
167
+ containerHeight = containerNode.height();
168
+
169
+ return ballons;
170
+ };
171
+ var realWorldEvent = function (eventName, obj) {
172
+ switch (eventName) {
173
+ case "dblclick":
174
+ obj.mousedown().mouseup().click().mousedown().mouseup().click().dblclick();
175
+ break;
176
+ case "click":
177
+ obj.mousedown().mouseup().click();
178
+ break;
179
+ }
180
+
181
+ return obj;
182
+ };
183
+ var ballons = null;
184
+
185
+ it("creates a hidden edit ballon for each ballon", function () {
186
+ ballons = getBallons();
187
+
188
+ var imgContainer = ballons[0].getBallonizedImageContainer().getContainerNode();
189
+ var imgForm = $("form.ballonizer_image_form", imgContainer);
190
+
191
+ expect(imgForm.children().length).toEqual(2);
192
+ expect(imgForm.children()).toBeHidden();
193
+ expect(imgForm).toContain("textarea.ballonizer_edition_ballon");
194
+
195
+ var textfieldTexts = imgForm.children().toArray().map(function (e, ix) {
196
+ /* jshint unused: false */
197
+ return $(e).val();
198
+ }).sort();
199
+ var ballonTexts = jQuery.map(ballons, function (e, ix) {
200
+ /* jshint unused: false */
201
+ return e.getText();
202
+ }).sort();
203
+
204
+ expect(textfieldTexts).toEqual(ballonTexts);
205
+ });
206
+ it("initial state is 'initial'", function () {
207
+ ballons = getBallons();
208
+ expect(ballons[0].getState()).toEqual("initial");
209
+ expect(ballons[1].getState()).toEqual("initial");
210
+ });
211
+ describe("when in initial state", function () {
212
+ it("the ballon can be dragged (and this update the getBounds return)", function () {
213
+ ballons = getBallons();
214
+ // We are using the second ballon because the first
215
+ // can't be dragged in the x axis, it already occupy
216
+ // all the x axis
217
+ var oldBounds = ballons[1].getPositionAndSize();
218
+ ballons[1].getNode().simulate("drag-n-drop", { dx: -20, dy: -20 });
219
+ expect(ballons[1].getPositionAndSize()).toEqual({
220
+ left: (oldBounds.left - 20),
221
+ top: (oldBounds.top - 20),
222
+ width: oldBounds.width,
223
+ height: oldBounds.height
224
+ });
225
+ });
226
+ it("the ballon can be resized (and this update the getBounds return)", function () {
227
+ ballons = getBallons();
228
+ var oldBounds = ballons[0].getPositionAndSize();
229
+ var resizeHandle = $(".ui-icon-gripsmall-diagonal-se", ballons[0].getNode());
230
+
231
+ // The handle is hidden when not hovering over the ballon
232
+ // (which includes the handle)
233
+ resizeHandle.mouseover().simulate("drag-n-drop", {
234
+ dx: -20,
235
+ dy: -20
236
+ }).mouseout();
237
+
238
+ expect(ballons[0].getPositionAndSize()).toEqual({
239
+ left: oldBounds.left,
240
+ top: oldBounds.top,
241
+ width: oldBounds.width - 20,
242
+ height: oldBounds.height - 20
243
+ });
244
+ });
245
+ });
246
+ describe("when dblclicked 2x(mousedown + mouseup + click) + dblclick", function () {
247
+ it("alternate to edit mode initial -> edit)", function () {
248
+ ballons = getBallons();
249
+ realWorldEvent("dblclick", ballons[0].getNode());
250
+ expect(ballons[0].getState()).toEqual("edit");
251
+ });
252
+ it("hide the normal ballon and put the textarea (focused) in the place", function () {
253
+ ballons = getBallons();
254
+
255
+ // Test from the initial state
256
+ realWorldEvent("dblclick", ballons[0].getNode());
257
+
258
+ expect(ballons[0].getNormalNode()).toBeHidden();
259
+ expect(ballons[0].getEditionNode()).toBeVisible();
260
+ expect(ballons[0].getEditionNode()).toBeFocused();
261
+
262
+ expect(ballons[0].getEditionNode().attr("style")).toEqual(
263
+ ballons[0].getNormalNode().attr("style")
264
+ );
265
+ });
266
+ });
267
+ describe("when the focus is lost in edit mode", function () {
268
+ it("the ballon return to the initial mode with the new text", function () {
269
+ ballons = getBallons();
270
+ // Edit the text of the first ballon
271
+ realWorldEvent("dblclick", ballons[0].getNode());
272
+ var firstNewText = "This is the first ballon text";
273
+ ballons[0].getNode().val(firstNewText);
274
+ // simulate focus loss
275
+ ballons[0].getNode().blur();
276
+ expect(ballons[0].getNormalNode()).toBeVisible();
277
+ expect(ballons[0].getText()).toEqual(firstNewText);
278
+ expect(ballons[0].getNormalNode().text()).toEqual(firstNewText);
279
+ expect(ballons[0].getEditionNode()).toBeHidden();
280
+ });
281
+ describe("and the ballon is empty (or only with spaces)", function () {
282
+ it("the ballon is removed", function () {
283
+ ballons = getBallons();
284
+ var imgContainer = ballons[0].getBallonizedImageContainer();
285
+
286
+ realWorldEvent("dblclick", ballons[0].getNode());
287
+ ballons[0].getNode().val("");
288
+ ballons[0].getNode().blur();
289
+
290
+ ballons = imgContainer.getBallons();
291
+
292
+ expect(ballons.length).toEqual(1);
293
+ expect($(".ballonizer_ballon")).toHaveLength(1);
294
+ expect($("form.ballonizer_image_form > *")).toHaveLength(1);
295
+ });
296
+ });
297
+ });
298
+ describe("when the ballons change", function () {
299
+ var htmlUnescape = function (value) {
300
+ return (value)
301
+ .replace(/"/g, '"')
302
+ .replace(/'/g, "'")
303
+ .replace(/&lt;/g, '<')
304
+ .replace(/&gt;/g, '>')
305
+ .replace(/&amp;/g, '&');
306
+ };
307
+
308
+ describe("position", function () {
309
+ var changePosition = function (ballon, dx, dy) {
310
+ ballon.getNode().simulate("drag-n-drop", { dx: dx, dy: dy });
311
+
312
+ return ballon;
313
+ };
314
+ // We are using the second ballon in the tests because
315
+ // the first can't be dragged in the x axis, it already
316
+ // occupy all the x axis
317
+ it("the button to submit change appears", function () {
318
+ ballons = getBallons();
319
+ changePosition(ballons[1], -20, -20);
320
+ expect($("form.ballonizer_page_form > input[name='ballonizer_submit']")).toBeVisible();
321
+ });
322
+ it("the serialize methods shows the new values", function () {
323
+ ballons = getBallons();
324
+ var ballon = ballons[1],
325
+ image = ballon.getBallonizedImageContainer(),
326
+ ballonizer = image.getBallonizerInstance(),
327
+ bounds = ballon.getPositionAndSize(),
328
+ expectedLeft = (bounds.left - 20) / containerWidth,
329
+ expectedTop = (bounds.top - 20) / containerHeight;
330
+
331
+ changePosition(ballons[1], -20, -20);
332
+
333
+ expect(ballon.serialize().left).toEqual(expectedLeft);
334
+ expect(ballon.serialize().top).toEqual(expectedTop);
335
+ expect(image.serialize()[1].left).toEqual(expectedLeft);
336
+ expect(image.serialize()[1].top).toEqual(expectedTop);
337
+ expect(ballonizer.serialize()["http://imgs.xkcd.com/comics/cells.png"][1].left).toEqual(expectedLeft);
338
+ expect(ballonizer.serialize()["http://imgs.xkcd.com/comics/cells.png"][1].top).toEqual(expectedTop);
339
+ });
340
+ it("the page form data update", function () {
341
+ ballons = getBallons();
342
+ var formData = $("form.ballonizer_page_form > input[name='ballonizer_data']");
343
+ expect(formData).toHaveValue("");
344
+ changePosition(ballons[1], -20, -20);
345
+ expect(jQuery.parseJSON(htmlUnescape(formData.val()))).toEqual({
346
+ "http://imgs.xkcd.com/comics/cells.png": [
347
+ { left: 0,
348
+ top: 0,
349
+ width: 1,
350
+ height: 0.24188790560471976,
351
+ text: 'When you see a claim that a common drug or vitamin "kills cancer cells in a petri dish", keep in mind:'
352
+ },
353
+ { left: 0.0045871559633027525,
354
+ top: 0.8820058997050148,
355
+ width: 0.7798165137614679,
356
+ height: 0.05604719764011799,
357
+ text: 'So does a handgun.'
358
+ }
359
+ ]
360
+ });
361
+ });
362
+ });
363
+ describe("size", function () {
364
+ var changeSize = function (ballon, dx, dy) {
365
+ var resizeHandle = $(".ui-icon-gripsmall-diagonal-se", ballon.getNode());
366
+
367
+ // The handle is hidden when not hovering over the ballon (which includes the handle)
368
+ resizeHandle.mouseover().simulate("drag-n-drop", { dx: dx, dy: dy }).mouseout();
369
+
370
+ return ballon;
371
+ };
372
+ it("the button to submit change appears", function () {
373
+ ballons = getBallons();
374
+ changeSize(ballons[0], -20, -20);
375
+ expect($("form.ballonizer_page_form > input[name='ballonizer_submit']")).toBeVisible();
376
+ });
377
+ it("the serialize methods shows the new values", function () {
378
+ ballons = getBallons();
379
+ var ballon = ballons[0],
380
+ image = ballon.getBallonizedImageContainer(),
381
+ ballonizer = image.getBallonizerInstance(),
382
+ bounds = ballon.getPositionAndSize(),
383
+ expectedWidth = (bounds.width - 20) / containerWidth,
384
+ expectedHeight = (bounds.height - 20) / containerHeight;
385
+
386
+ changeSize(ballons[0], -20, -20);
387
+
388
+ expect(ballon.serialize().width).toEqual(expectedWidth);
389
+ expect(ballon.serialize().height).toEqual(expectedHeight);
390
+ expect(image.serialize()[0].width).toEqual(expectedWidth);
391
+ expect(image.serialize()[0].height).toEqual(expectedHeight);
392
+ expect(ballonizer.serialize()["http://imgs.xkcd.com/comics/cells.png"][0].width).toEqual(expectedWidth);
393
+ expect(ballonizer.serialize()["http://imgs.xkcd.com/comics/cells.png"][0].height).toEqual(expectedHeight);
394
+ });
395
+ it("the page form data update", function () {
396
+ ballons = getBallons();
397
+ var formData = $("form.ballonizer_page_form > input[name='ballonizer_data']");
398
+ expect(formData).toHaveValue("");
399
+ changeSize(ballons[0], -20, -20);
400
+ expect(jQuery.parseJSON(htmlUnescape(formData.val()))).toEqual({
401
+ "http://imgs.xkcd.com/comics/cells.png": [
402
+ { left: 0,
403
+ top: 0,
404
+ width: 0.908256880733945,
405
+ height: 0.18289085545722714,
406
+ text: 'When you see a claim that a common drug or vitamin "kills cancer cells in a petri dish", keep in mind:'
407
+ },
408
+ { left: 0.0963302752293578,
409
+ top: 0.9410029498525073,
410
+ width: 0.7798165137614679,
411
+ height: 0.05604719764011799,
412
+ text: 'So does a handgun.'
413
+ }
414
+ ]
415
+ });
416
+ });
417
+ });
418
+ describe("text", function () {
419
+ var changeText = function (ballon, text) {
420
+ realWorldEvent("dblclick", ballon.getNode());
421
+ ballon.getNode().val(text);
422
+ ballon.getNode().blur();
423
+
424
+ return ballon;
425
+ };
426
+ it("the button to submit change appears", function () {
427
+ ballons = getBallons();
428
+ changeText(ballons[0], "Ballon new text");
429
+ expect($("form.ballonizer_page_form > input[name='ballonizer_submit']")).toBeVisible();
430
+ });
431
+ it("the serialize methods shows the new values", function () {
432
+ ballons = getBallons();
433
+ var ballon = ballons[0],
434
+ text = "Ballon new text",
435
+ image = ballon.getBallonizedImageContainer(),
436
+ ballonizer = image.getBallonizerInstance();
437
+
438
+ changeText(ballon, text);
439
+
440
+ expect(ballon.serialize().text).toEqual(text);
441
+ expect(image.serialize()[0].text).toEqual(text);
442
+ expect(ballonizer.serialize()["http://imgs.xkcd.com/comics/cells.png"][0].text).toEqual(text);
443
+ });
444
+ it("the page form data update", function () {
445
+ ballons = getBallons();
446
+ var formData = $("form.ballonizer_page_form > input[name='ballonizer_data']");
447
+ expect(formData).toHaveValue("");
448
+ changeText(ballons[0], "Ballon new text");
449
+ expect(jQuery.parseJSON(htmlUnescape(formData.val()))).toEqual({
450
+ "http://imgs.xkcd.com/comics/cells.png": [
451
+ { left: 0,
452
+ top: 0,
453
+ width: 1,
454
+ height: 0.24188790560471976,
455
+ text: 'Ballon new text'
456
+ },
457
+ { left: 0.0963302752293578,
458
+ top: 0.9410029498525073,
459
+ width: 0.7798165137614679,
460
+ height: 0.05604719764011799,
461
+ text: 'So does a handgun.'
462
+ }
463
+ ]
464
+ });
465
+ });
466
+ });
467
+ describe("a ballon being added", function () {
468
+ var addBallon = function () {
469
+ $(".ballonizer_image_container img").trigger(
470
+ jQuery.Event("dblclick", { pageX: 490, pageY: 444 })
471
+ );
472
+ };
473
+
474
+ it("the button to submit change appears", function () {
475
+ getBallons();
476
+ addBallon();
477
+ expect($("form.ballonizer_page_form > input[name='ballonizer_submit']")).toBeVisible();
478
+ });
479
+ it("the serialize methods shows the new values", function () {
480
+ ballons = getBallons();
481
+ addBallon();
482
+ var image = ballons[0].getBallonizedImageContainer(),
483
+ imageData = image.serialize(),
484
+ ballonizer = image.getBallonizerInstance(),
485
+ ballonizerData = ballonizer.serialize();
486
+
487
+ expect(imageData.length).toEqual(3);
488
+ expect(ballonizerData["http://imgs.xkcd.com/comics/cells.png"].length).toEqual(3);
489
+ });
490
+ it("the page form data update", function () {
491
+ getBallons();
492
+ var formData = $("form.ballonizer_page_form > input[name='ballonizer_data']");
493
+ expect(formData).toHaveValue("");
494
+ addBallon();
495
+ expect(jQuery.parseJSON(htmlUnescape(formData.val()))).toEqual({
496
+ "http://imgs.xkcd.com/comics/cells.png": [
497
+ { left: 0,
498
+ top: 0,
499
+ width: 1,
500
+ height: 0.24188790560471976,
501
+ text: 'When you see a claim that a common drug or vitamin "kills cancer cells in a petri dish", keep in mind:'
502
+ },
503
+ { left: 0.0963302752293578,
504
+ top: 0.9410029498525073,
505
+ width: 0.7798165137614679,
506
+ height: 0.05604719764011799,
507
+ text: 'So does a handgun.'
508
+ },
509
+ { left: 0.40825688073394495,
510
+ top: 0.05604719764011799,
511
+ width: 0.591743119266055,
512
+ height: 0.12094395280235988,
513
+ text: 'double click to edit ballon text'
514
+ }
515
+ ]
516
+ });
517
+ });
518
+ });
519
+ describe("a ballon being removed", function () {
520
+ var removeBallon = function (ballon) {
521
+ realWorldEvent("dblclick", ballon.getNode());
522
+ ballon.getNode().val("");
523
+ ballon.getNode().blur();
524
+
525
+ return ballon;
526
+ };
527
+ it("the button to submit change appears", function () {
528
+ ballons = getBallons();
529
+ removeBallon(ballons[0]);
530
+ expect($("form.ballonizer_page_form > input[name='ballonizer_submit']")).toBeVisible();
531
+
532
+ return ballons;
533
+ });
534
+ it("the serialize methods shows the new values", function () {
535
+ ballons = getBallons();
536
+
537
+ var image = ballons[0].getBallonizedImageContainer(),
538
+ ballonizer = image.getBallonizerInstance();
539
+
540
+ removeBallon(ballons[0]);
541
+
542
+ var imageData = image.serialize(),
543
+ ballonizerData = ballonizer.serialize();
544
+
545
+ expect(imageData.length).toEqual(1);
546
+ expect(ballonizerData["http://imgs.xkcd.com/comics/cells.png"].length).toEqual(1);
547
+ });
548
+ it("the page form data update", function () {
549
+ ballons = getBallons();
550
+ var formData = $("form.ballonizer_page_form > input[name='ballonizer_data']");
551
+ expect(formData).toHaveValue("");
552
+ removeBallon(ballons[0]);
553
+ expect(jQuery.parseJSON(htmlUnescape(formData.val()))).toEqual({
554
+ "http://imgs.xkcd.com/comics/cells.png": [
555
+ { left: 0.0963302752293578,
556
+ top: 0.9410029498525073,
557
+ width: 0.7798165137614679,
558
+ height: 0.05604719764011799,
559
+ text: 'So does a handgun.'
560
+ }
561
+ ]
562
+ });
563
+ });
564
+ });
565
+ });
566
+ });
567
+ });
568
+