ballonizer 0.1.0 → 0.2.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 (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
+