spontaneous 0.2.0.beta9 → 0.2.0.beta10

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/LICENSE +18 -17
  4. data/Rakefile +1 -1
  5. data/application/css/core.css.scss +1 -1
  6. data/application/css/dialogue.css.scss +8 -20
  7. data/application/js/preview.js +28 -7
  8. data/application/js/publish.js +15 -4
  9. data/application/js/top_bar.js +0 -16
  10. data/application/js/views/piece_view.js +1 -1
  11. data/lib/spontaneous/asset/environment.rb +16 -1
  12. data/lib/spontaneous/box.rb +68 -0
  13. data/lib/spontaneous/capistrano/deploy.rb +7 -4
  14. data/lib/spontaneous/capistrano/sync.rb +2 -2
  15. data/lib/spontaneous/cli/init.rb +70 -19
  16. data/lib/spontaneous/cli/init/db.rb +34 -55
  17. data/lib/spontaneous/cli/init/mysql.rb +5 -5
  18. data/lib/spontaneous/cli/init/postgresql.rb +8 -9
  19. data/lib/spontaneous/cli/init/sqlite.rb +1 -2
  20. data/lib/spontaneous/cli/migrate.rb +0 -1
  21. data/lib/spontaneous/cli/site.rb +4 -0
  22. data/lib/spontaneous/collections/entry_set.rb +11 -0
  23. data/lib/spontaneous/data_mapper/content_model.rb +2 -0
  24. data/lib/spontaneous/data_mapper/content_model/serialization.rb +2 -2
  25. data/lib/spontaneous/extensions/array.rb +12 -2
  26. data/lib/spontaneous/field/base.rb +10 -0
  27. data/lib/spontaneous/field/file.rb +32 -2
  28. data/lib/spontaneous/field/image.rb +24 -2
  29. data/lib/spontaneous/field/select.rb +8 -0
  30. data/lib/spontaneous/field/webvideo.rb +8 -0
  31. data/lib/spontaneous/generators/site/config/initializers/fields.rb +55 -0
  32. data/lib/spontaneous/json.rb +3 -2
  33. data/lib/spontaneous/logger.rb +2 -2
  34. data/lib/spontaneous/media/file.rb +3 -3
  35. data/lib/spontaneous/media/image/attributes.rb +72 -6
  36. data/lib/spontaneous/media/image/renderable.rb +53 -20
  37. data/lib/spontaneous/media/store.rb +3 -3
  38. data/lib/spontaneous/media/store/backend.rb +16 -0
  39. data/lib/spontaneous/media/store/cloud.rb +52 -12
  40. data/lib/spontaneous/media/store/local.rb +6 -3
  41. data/lib/spontaneous/model.rb +3 -0
  42. data/lib/spontaneous/model/core/entries.rb +34 -13
  43. data/lib/spontaneous/model/core/entry.rb +3 -1
  44. data/lib/spontaneous/model/page/controllers.rb +1 -2
  45. data/lib/spontaneous/model/page/paths.rb +18 -7
  46. data/lib/spontaneous/output/context.rb +0 -8
  47. data/lib/spontaneous/output/template/renderer.rb +2 -0
  48. data/lib/spontaneous/plugins/application/state.rb +0 -4
  49. data/lib/spontaneous/prototypes/field_prototype.rb +4 -0
  50. data/lib/spontaneous/publishing/immediate.rb +0 -5
  51. data/lib/spontaneous/publishing/progress.rb +2 -2
  52. data/lib/spontaneous/publishing/rerender.rb +1 -4
  53. data/lib/spontaneous/publishing/simultaneous.rb +19 -17
  54. data/lib/spontaneous/publishing/steps.rb +12 -3
  55. data/lib/spontaneous/rack.rb +2 -0
  56. data/lib/spontaneous/rack/asset_server.rb +5 -2
  57. data/lib/spontaneous/rack/back.rb +9 -1
  58. data/lib/spontaneous/rack/back/base.rb +1 -0
  59. data/lib/spontaneous/rack/back/changes.rb +5 -0
  60. data/lib/spontaneous/rack/back/preview.rb +4 -4
  61. data/lib/spontaneous/rack/back/private.rb +11 -0
  62. data/lib/spontaneous/rack/middleware/scope.rb +16 -4
  63. data/lib/spontaneous/rack/page_controller.rb +2 -2
  64. data/lib/spontaneous/rack/public.rb +52 -4
  65. data/lib/spontaneous/sequel.rb +10 -13
  66. data/lib/spontaneous/site.rb +28 -8
  67. data/lib/spontaneous/site/publishing.rb +1 -1
  68. data/lib/spontaneous/site/storage.rb +7 -4
  69. data/lib/spontaneous/tasks/environment.rake +3 -0
  70. data/lib/spontaneous/utils/database/postgres_dumper.rb +23 -2
  71. data/lib/spontaneous/version.rb +1 -1
  72. data/spontaneous.gemspec +7 -12
  73. data/test/fixtures/assets/public1/css/data.css.scss +1 -1
  74. data/test/functional/test_application.rb +15 -0
  75. data/test/functional/test_cli.rb +109 -3
  76. data/test/functional/test_front.rb +108 -10
  77. data/test/test_helper.rb +3 -3
  78. data/test/unit/fields/test_boolean_fields.rb +80 -0
  79. data/test/unit/fields/test_date_fields.rb +47 -0
  80. data/test/unit/fields/test_file_field.rb +210 -0
  81. data/test/unit/{test_images.rb → fields/test_image_fields.rb} +133 -15
  82. data/test/unit/fields/test_location_fields.rb +41 -0
  83. data/test/unit/fields/test_option_fields.rb +61 -0
  84. data/test/unit/fields/test_tag_list_fields.rb +45 -0
  85. data/test/unit/fields/test_text_fields.rb +124 -0
  86. data/test/unit/fields/test_web_video_fields.rb +198 -0
  87. data/test/unit/test_assets.rb +22 -22
  88. data/test/unit/test_boxes.rb +34 -13
  89. data/test/unit/test_changesets.rb +1 -0
  90. data/test/unit/test_extensions.rb +17 -0
  91. data/test/unit/test_fields.rb +20 -643
  92. data/test/unit/test_media.rb +9 -9
  93. data/test/unit/test_page.rb +47 -0
  94. data/test/unit/test_publishing_pipeline.rb +2 -2
  95. data/test/unit/test_serialisation.rb +37 -0
  96. data/test/unit/test_storage.rb +42 -3
  97. metadata +37 -17
@@ -147,15 +147,15 @@ describe "Assets" do
147
147
  let(:x_js_digest) { asset_digest('public1/x.js') }
148
148
  # these are compiled so fairly complex to calculate their digests
149
149
  # not impossible, but annoying
150
- let(:n_js_digest) { '74f175e03a4bdc8c807aba4ae0314938' }
151
- let(:m_js_digest) { 'dd35b142dc75b6ec15b2138e9e91c0c3' }
152
- let(:all_js_digest) { 'd406fc3c21d90828a2f0a718c89e8d99' }
150
+ let(:n_js_digest) { 'a5c86b004242d16e0dbf818068bbb248' }
151
+ let(:m_js_digest) { 'c0f2e1c2a7d1cc9666ccb48cc9fd610e' }
152
+ let(:all_js_digest) { '9759f54463d069b39d9c04c3e1d63745' }
153
153
 
154
- let(:a_css_digest) { '7b04d295476986c24d8c77245943e5b9' }
155
- let(:b_css_digest) { '266643993e14da14f2473d45f003bd2c' }
156
- let(:c_css_digest) { 'fc8ba0d0aae64081dc00b8444a198fb8' }
157
- let(:x_css_digest) { '2560aec2891794825eba770bf84823fb' }
158
- let(:all_css_digest) { 'cf61c624b91b9ea126804291ac55bd5d' }
154
+ let(:a_css_digest) { 'b4ec507466566b839328b924893e80fb' }
155
+ let(:b_css_digest) { '97b252913f48c160b1eed120f7544203' }
156
+ let(:c_css_digest) { '3fcc1da2378a42e97cbd11cb85b8115a' }
157
+ let(:x_css_digest) { '88b3052bb5d7723b6a6a8b628fbee34e' }
158
+ let(:all_css_digest) { '9bac5a609f816594005b07a3a8aa2368' }
159
159
 
160
160
  it "includes all js dependencies" do
161
161
  result = context.scripts('js/all', 'js/m', 'js/c', 'x')
@@ -204,15 +204,15 @@ describe "Assets" do
204
204
  let(:app) { Spontaneous::Rack::Back.application(site) }
205
205
  let(:context) { preview_context }
206
206
 
207
- let(:c_js_digest) { 'f669550dd7e10e9646ad781f44756950' }
208
- let(:x_js_digest) { '6b4c9176b2838a4949a18284543fc19c' }
207
+ let(:c_js_digest) { '3ab39368d6e128169ac2401bc49fcdb2' }
208
+ let(:x_js_digest) { 'e755ffcead8090406c04b2813b9bdce9' }
209
209
  let(:n_js_digest) { '74f175e03a4bdc8c807aba4ae0314938' }
210
- let(:m_js_digest) { 'dd35b142dc75b6ec15b2138e9e91c0c3' }
211
- let(:all_js_digest) { 'cd1f681752f5038421be0bc5ea0e855d' }
210
+ let(:m_js_digest) { 'c0f2e1c2a7d1cc9666ccb48cc9fd610e' }
211
+ let(:all_js_digest) { 'd782ef6abb69b1eb3e4c503478a660db' }
212
212
 
213
- let(:c_css_digest) { 'fc8ba0d0aae64081dc00b8444a198fb8' }
214
- let(:x_css_digest) { '2560aec2891794825eba770bf84823fb' }
215
- let(:all_css_digest) { 'bb2c289a27b3d5d4467dde6d60722fd3' }
213
+ let(:c_css_digest) { '3fcc1da2378a42e97cbd11cb85b8115a' }
214
+ let(:x_css_digest) { '88b3052bb5d7723b6a6a8b628fbee34e' }
215
+ let(:all_css_digest) { '4b837b285d3a1998c48e1811e62292d8' }
216
216
 
217
217
  describe "javascript" do
218
218
  it "include scripts as separate files with finger prints" do
@@ -382,14 +382,14 @@ describe "Assets" do
382
382
  end
383
383
 
384
384
  describe "javascript" do
385
- let(:all_sha) { "ed62549e8edc1f61a1e27136602f01d9" }
386
- let(:x_sha) { "66e92be1e412458f6ff02f4c5dd9beb1" }
385
+ let(:all_sha) { "29ced6e75f651ea6963bd2f2ffdd745e" }
386
+ let(:x_sha) { "2b8678f6d71dc1fc0e44ad9f6a5811b3" }
387
387
  it "bundles & fingerprints local scripts" do
388
388
  result = context.scripts('js/all', 'js/m.js', 'js/c.js', 'x')
389
389
  result.must_equal [
390
390
  %(<script type="text/javascript" src="/assets/js/all-#{all_sha}.js"></script>),
391
- '<script type="text/javascript" src="/assets/js/m-a5be7324bc314d5cf470a59c3732ef10.js"></script>',
392
- '<script type="text/javascript" src="/assets/js/c-c24bcbb4f9647b078cc919746aa7fc3a.js"></script>',
391
+ '<script type="text/javascript" src="/assets/js/m-66885c19e856373c6b9dab3a41885dbf.js"></script>',
392
+ '<script type="text/javascript" src="/assets/js/c-0201f986795c0d9b4fb850236496359f.js"></script>',
393
393
  %(<script type="text/javascript" src="/assets/x-#{x_sha}.js"></script>)
394
394
  ].join("\n")
395
395
  end
@@ -456,14 +456,14 @@ describe "Assets" do
456
456
  end
457
457
 
458
458
  describe "css" do
459
- let(:all_sha) { "2e17f25ddeba996223a6cd1e28e7a319" }
460
- let(:x_sha) { "2560aec2891794825eba770bf84823fb" }
459
+ let(:all_sha) { "a72e3c2850e95f5c89930cae40a66c29" }
460
+ let(:x_sha) { "88b3052bb5d7723b6a6a8b628fbee34e" }
461
461
 
462
462
  it "bundles & fingerprints local stylesheets" do
463
463
  result = context.stylesheets('css/all', 'css/a.css', 'x')
464
464
  result.must_equal [
465
465
  %(<link rel="stylesheet" href="/assets/css/all-#{all_sha}.css" />),
466
- '<link rel="stylesheet" href="/assets/css/a-0164c6d5b696ec2f2c5e70cade040da8.css" />',
466
+ '<link rel="stylesheet" href="/assets/css/a-46541ee6e70fb12c030698e0addc2c79.css" />',
467
467
  %(<link rel="stylesheet" href="/assets/x-#{x_sha}.css" />)
468
468
  ].join("\n")
469
469
  end
@@ -370,25 +370,46 @@ describe "Boxes" do
370
370
  instance.box3.contents.map { |e| e.label }.must_equal ["0", "1", "c", "2", "3"]
371
371
  end
372
372
 
373
+ it 'should be available as a list of ids' do
374
+ BlankContent.box :box3
375
+ instance = BlankContent.new
376
+ contents = 3.times.map { instance.words << StyledContent.new }
377
+ 3.times.map { instance.images << StyledContent.new }
378
+ instance.save
379
+ contents.each(&:save)
380
+ ids = contents.map(&:id)
381
+ instance.words.ids.must_equal ids
382
+ end
383
+
384
+ it 'should be clearable' do
385
+ instance = BlankContent.create
386
+ box = instance.images
387
+ count = 4
388
+ count.times { |n| box << StyledContent.new(:label => n)}
389
+ instance.save
390
+ instance.images.clear!
391
+ instance.reload.images.length.must_equal 0
392
+ end
393
+
373
394
  end
374
395
 
375
396
  describe "Allowed types" do
376
397
  before do
377
- class ::Allowed1 < Content
398
+ class ::Allowed1 < Piece
378
399
  style :frank
379
400
  style :freddy
380
401
  end
381
- class ::Allowed2 < Content
402
+ class ::Allowed2 < Piece
382
403
  style :john
383
404
  style :paul
384
405
  style :ringo
385
406
  style :george
386
407
  end
387
- class ::Allowed3 < Content
408
+ class ::Allowed3 < Piece
388
409
  style :arthur
389
410
  style :lancelot
390
411
  end
391
- class ::Allowed4 < Content; end
412
+ class ::Allowed4 < Piece; end
392
413
 
393
414
  class ::Allowed11 < ::Allowed1; end
394
415
  class ::Allowed111 < ::Allowed1; end
@@ -402,7 +423,7 @@ describe "Boxes" do
402
423
  class ::ChildClass < ::Parent
403
424
  end
404
425
 
405
- class ::Allowable < Content
426
+ class ::Allowable < Piece
406
427
  box :parents, :type => :Parent
407
428
  end
408
429
 
@@ -566,14 +587,14 @@ describe "Boxes" do
566
587
  @b = ::B.new
567
588
  @c = ::C.new
568
589
  [@a, @b, @c].each do |instance|
569
- instance.boxes[:a].stubs(:render).with(anything).returns("[a]")
570
- instance.boxes[:b].stubs(:render).with(anything).returns("[b]")
571
- instance.boxes[:c].stubs(:render).with(anything).returns("[c]")
572
- instance.boxes[:d].stubs(:render).with(anything).returns("[d]")
573
- end
574
- @b.boxes[:e].stubs(:render).with(anything).returns("[e]")
575
- @c.boxes[:e].stubs(:render).with(anything).returns("[e]")
576
- @c.boxes[:f].stubs(:render).with(anything).returns("[f]")
590
+ instance.boxes[:a].stubs(:render_inline).with(anything).returns("[a]")
591
+ instance.boxes[:b].stubs(:render_inline).with(anything).returns("[b]")
592
+ instance.boxes[:c].stubs(:render_inline).with(anything).returns("[c]")
593
+ instance.boxes[:d].stubs(:render_inline).with(anything).returns("[d]")
594
+ end
595
+ @b.boxes[:e].stubs(:render_inline).with(anything).returns("[e]")
596
+ @c.boxes[:e].stubs(:render_inline).with(anything).returns("[e]")
597
+ @c.boxes[:f].stubs(:render_inline).with(anything).returns("[f]")
577
598
  end
578
599
 
579
600
  after do
@@ -78,6 +78,7 @@ describe "Change" do
78
78
  it "not list new pieces as available for publish" do
79
79
  root = Page.create(:title => "root")
80
80
  Content.publish(@revision)
81
+ root.reload
81
82
  root.things << Piece.new
82
83
  root.save.reload
83
84
  result = outstanding_changes[:changes]
@@ -42,5 +42,22 @@ describe "Extensions" do
42
42
  result.must_equal [["js"], ["coffee", "coffee"], ["js"], ["coffee"]]
43
43
  end
44
44
  end
45
+
46
+ describe "Array" do
47
+ it 'responds to #render by calling #render_inline on the contents' do
48
+ array = 3.times.map { mock }
49
+ array.each do |m|
50
+ m.expects(:render_inline).returns("x")
51
+ end
52
+ array.render.must_equal 'xxx'
53
+ end
54
+ it 'responds to #render_using by calling #render_inline_using on the contents' do
55
+ array = 3.times.map { mock }
56
+ array.each do |m|
57
+ m.expects(:render_inline_using).returns("x")
58
+ end
59
+ array.render_using(mock).must_equal 'xxx'
60
+ end
61
+ end
45
62
  end
46
63
 
@@ -510,87 +510,6 @@ describe "Fields" do
510
510
  end
511
511
  end
512
512
 
513
- describe "String Fields" do
514
- before do
515
- @content_class = Class.new(::Piece) do
516
- field :title, :string
517
- end
518
- @instance = @content_class.new
519
- @field = @instance.title
520
- end
521
-
522
- it "should escape ampersands for the html format" do
523
- @field.value = "This & That"
524
- @field.value(:html).must_equal "This &amp; That"
525
- end
526
- end
527
-
528
- describe "Markdown fields" do
529
- before do
530
- class ::MarkdownContent < Piece
531
- field :text1, :markdown
532
- field :text2, :richtext
533
- field :text3, :markup
534
- end
535
- @instance = MarkdownContent.new
536
- end
537
- after do
538
- Object.send(:remove_const, :MarkdownContent)
539
- end
540
-
541
- it "be available as the :markdown type" do
542
- assert MarkdownContent.field_prototypes[:text1].field_class < Spontaneous::Field::Markdown
543
- end
544
- it "be available as the :richtext type" do
545
- assert MarkdownContent.field_prototypes[:text2].field_class < Spontaneous::Field::Markdown
546
- end
547
- it "be available as the :markup type" do
548
- assert MarkdownContent.field_prototypes[:text3].field_class < Spontaneous::Field::Markdown
549
- end
550
-
551
- it "process input into HTML" do
552
- @instance.text1 = "*Hello* **World**"
553
- @instance.text1.value.must_equal "<p><em>Hello</em> <strong>World</strong></p>\n"
554
- end
555
-
556
- it "use more sensible linebreaks" do
557
- @instance.text1 = "With\nLinebreak"
558
- @instance.text1.value.must_equal "<p>With<br />\nLinebreak</p>\n"
559
- @instance.text2 = "With \nLinebreak"
560
- @instance.text2.value.must_equal "<p>With<br />\nLinebreak</p>\n"
561
- end
562
- end
563
-
564
- describe "LongString fields" do
565
- before do
566
- class ::LongStringContent < Piece
567
- field :long1, :longstring
568
- field :long2, :long_string
569
- field :long3, :text
570
- end
571
- @instance = LongStringContent.new
572
- end
573
- after do
574
- Object.send(:remove_const, :LongStringContent)
575
- end
576
-
577
- it "is available as the :longstring type" do
578
- assert LongStringContent.field_prototypes[:long1].field_class < Spontaneous::Field::LongString
579
- end
580
-
581
- it "is available as the :long_string type" do
582
- assert LongStringContent.field_prototypes[:long2].field_class < Spontaneous::Field::LongString
583
- end
584
-
585
- it "is available as the :text type" do
586
- assert LongStringContent.field_prototypes[:long3].field_class < Spontaneous::Field::LongString
587
- end
588
-
589
- it "translates newlines to <br/> tags" do
590
- @instance.long1 = "this\nlong\nstring"
591
- @instance.long1.value.must_equal "this<br />\nlong<br />\nstring"
592
- end
593
- end
594
513
 
595
514
  describe "Editor classes" do
596
515
  it "be defined in base types" do
@@ -741,568 +660,6 @@ describe "Fields" do
741
660
  end
742
661
  end
743
662
 
744
- describe "String fields" do
745
- it "be aliased to the :title type" do
746
- @content_class = Class.new(::Piece) do
747
- field :title, default: "Right"
748
- field :something, :title
749
- end
750
- instance = @content_class.new
751
- assert instance.fields.title.class.ancestors.include?(Spontaneous::Field::String), ":title type should inherit from StringField"
752
- instance.title.value.must_equal "Right"
753
- end
754
- end
755
-
756
- describe "WebVideo fields" do
757
- before do
758
- @content_class = Class.new(::Piece) do
759
- field :video, :webvideo
760
- end
761
- @content_class.stubs(:name).returns("ContentClass")
762
- @instance = @content_class.new
763
- @field = @instance.video
764
- end
765
-
766
- it "have their own editor type" do
767
- @content_class.fields.video.export(nil)[:type].must_equal "Spontaneous.Field.WebVideo"
768
- @instance.video = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
769
- fields = @instance.export(nil)[:fields]
770
- fields[0][:processed_value].must_equal @instance.video.src
771
- end
772
-
773
- it "recognise youtube URLs" do
774
- @instance.video = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
775
- @instance.video.value.must_equal "http://www.youtube.com/watch?v=_0jroAM_pO4&amp;feature=feedrec_grec_index"
776
- @instance.video.video_id.must_equal "_0jroAM_pO4"
777
- @instance.video.provider_id.must_equal "youtube"
778
- end
779
-
780
- it "recognise Vimeo URLs" do
781
- @instance.video = "http://vimeo.com/31836285"
782
- @instance.video.value.must_equal "http://vimeo.com/31836285"
783
- @instance.video.video_id.must_equal "31836285"
784
- @instance.video.provider_id.must_equal "vimeo"
785
- end
786
-
787
- it "recognise Vine URLs" do
788
- @instance.video = "https://vine.co/v/brI7pTPb3qU"
789
- @instance.video.value.must_equal "https://vine.co/v/brI7pTPb3qU"
790
- @instance.video.video_id.must_equal "brI7pTPb3qU"
791
- @instance.video.provider_id.must_equal "vine"
792
- end
793
-
794
- it "silently handles unknown providers" do
795
- @instance.video = "https://idontdovideo.com/video?id=brI7pTPb3qU"
796
- @instance.video.value.must_equal "https://idontdovideo.com/video?id=brI7pTPb3qU"
797
- @instance.video.video_id.must_equal "https://idontdovideo.com/video?id=brI7pTPb3qU"
798
- @instance.video.provider_id.must_equal nil
799
- end
800
-
801
-
802
- it "use the YouTube api to extract video metadata" do
803
- youtube_info = {"thumbnail_large" => "http://i.ytimg.com/vi/_0jroAM_pO4/hqdefault.jpg", "thumbnail_small"=>"http://i.ytimg.com/vi/_0jroAM_pO4/default.jpg", "title" => "Hilarious QI Moment - Cricket", "description" => "Rob Brydon makes a rather embarassing choice of words whilst discussing the relationship between a cricket's chirping and the temperature. Taken from QI XL Series H episode 11 - Highs and Lows", "user_name" => "morthasa", "upload_date" => "2011-01-14 19:49:44", "tags" => "Hilarious, QI, Moment, Cricket, fun, 11, stephen, fry, alan, davies, Rob, Brydon, SeriesH, Fred, MacAulay, Sandi, Toksvig", "duration" => 78, "stats_number_of_likes" => 297, "stats_number_of_plays" => 53295, "stats_number_of_comments" => 46}#.symbolize_keys
804
-
805
- response_xml_file = File.expand_path("../../fixtures/fields/youtube_api_response.xml", __FILE__)
806
- connection = mock()
807
- Spontaneous::Field::WebVideo::YouTube.any_instance.expects(:open).with("http://gdata.youtube.com/feeds/api/videos/_0jroAM_pO4?v=2").returns(connection)
808
- doc = Nokogiri::XML(File.open(response_xml_file))
809
- Nokogiri.expects(:XML).with(connection).returns(doc)
810
- @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4"
811
- @field.values.must_equal youtube_info.merge(:video_id => "_0jroAM_pO4", :provider => "youtube", :html => "http://www.youtube.com/watch?v=_0jroAM_pO4")
812
- end
813
-
814
- it "use the Vimeo api to extract video metadata" do
815
- vimeo_info = {"id"=>29987529, "title"=>"Neon Indian Plays The UO Music Shop", "description"=>"Neon Indian plays electronic instruments from the UO Music Shop, Fall 2011. Read more at blog.urbanoutfitters.com.", "url"=>"http://vimeo.com/29987529", "upload_date"=>"2011-10-03 18:32:47", "mobile_url"=>"http://vimeo.com/m/29987529", "thumbnail_small"=>"http://b.vimeocdn.com/ts/203/565/203565974_100.jpg", "thumbnail_medium"=>"http://b.vimeocdn.com/ts/203/565/203565974_200.jpg", "thumbnail_large"=>"http://b.vimeocdn.com/ts/203/565/203565974_640.jpg", "user_name"=>"Urban Outfitters", "user_url"=>"http://vimeo.com/urbanoutfitters", "user_portrait_small"=>"http://b.vimeocdn.com/ps/251/111/2511118_30.jpg", "user_portrait_medium"=>"http://b.vimeocdn.com/ps/251/111/2511118_75.jpg", "user_portrait_large"=>"http://b.vimeocdn.com/ps/251/111/2511118_100.jpg", "user_portrait_huge"=>"http://b.vimeocdn.com/ps/251/111/2511118_300.jpg", "stats_number_of_likes"=>85, "stats_number_of_plays"=>26633, "stats_number_of_comments"=>0, "duration"=>100, "width"=>1280, "height"=>360, "tags"=>"neon indian, analog, korg, moog, theremin, micropiano, microkorg, kaossilator, kaossilator pro", "embed_privacy"=>"anywhere"}.symbolize_keys
816
-
817
- connection = mock()
818
- connection.expects(:read).returns(Spontaneous.encode_json([vimeo_info]))
819
- Spontaneous::Field::WebVideo::Vimeo.any_instance.expects(:open).with("http://vimeo.com/api/v2/video/29987529.json").returns(connection)
820
- @field.value = "http://vimeo.com/29987529"
821
- @field.values.must_equal vimeo_info.merge(:video_id => "29987529", :provider => "vimeo", :html => "http://vimeo.com/29987529")
822
- end
823
-
824
- describe "with player settings" do
825
- before do
826
- @content_class.field :video2, :webvideo, :player => {
827
- :width => 680, :height => 384,
828
- :fullscreen => true, :autoplay => true, :loop => true,
829
- :showinfo => false,
830
- :youtube => { :theme => 'light', :hd => true, :controls => false },
831
- :vimeo => { :color => "ccc", :api => true }
832
- }
833
- @instance = @content_class.new
834
- @field = @instance.video2
835
- end
836
-
837
- it "use the configuration in the youtube player HTML" do
838
- @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
839
- html = @field.render(:html)
840
- html.must_match /^<iframe/
841
- html.must_match %r{src="//www\.youtube\.com/embed/_0jroAM_pO4}
842
- html.must_match /width="680"/
843
- html.must_match /height="384"/
844
- html.must_match /theme=light/
845
- html.must_match /hd=1/
846
- html.must_match /fs=1/
847
- html.must_match /controls=0/
848
- html.must_match /autoplay=1/
849
- html.must_match /showinfo=0/
850
- html.must_match /showsearch=0/
851
- @field.render(:html, :youtube => {:showsearch => 1}).must_match /showsearch=1/
852
- @field.render(:html, :youtube => {:theme => 'dark'}).must_match /theme=dark/
853
- @field.render(:html, :width => 100).must_match /width="100"/
854
- @field.render(:html, :loop => true).must_match /loop=1/
855
- end
856
-
857
- it "use the configuration in the Vimeo player HTML" do
858
- @field.value = "http://vimeo.com/31836285"
859
- html = @field.render(:html)
860
- html.must_match /^<iframe/
861
- html.must_match %r{src="//player\.vimeo\.com/video/31836285}
862
- html.must_match /width="680"/
863
- html.must_match /height="384"/
864
- html.must_match /color=ccc/
865
- html.must_match /webkitAllowFullScreen="yes"/
866
- html.must_match /allowFullScreen="yes"/
867
- html.must_match /autoplay=1/
868
- html.must_match /title=0/
869
- html.must_match /byline=0/
870
- html.must_match /portrait=0/
871
- html.must_match /api=1/
872
- @field.render(:html, :vimeo => {:color => 'f0abcd'}).must_match /color=f0abcd/
873
- @field.render(:html, :loop => true).must_match /loop=1/
874
- @field.render(:html, :title => true).must_match /title=1/
875
- @field.render(:html, :title => true).must_match /byline=0/
876
- end
877
-
878
- it "provide a version of the YouTube player params in JSON/JS format" do
879
- @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
880
- json = Spontaneous::JSON.parse(@field.render(:json))
881
- json[:"tagname"].must_equal "iframe"
882
- json[:"tag"].must_equal "<iframe/>"
883
- attr = json[:"attr"]
884
- attr.must_be_instance_of(Hash)
885
- attr[:"src"].must_match %r{^//www\.youtube\.com/embed/_0jroAM_pO4}
886
- attr[:"src"].must_match /theme=light/
887
- attr[:"src"].must_match /hd=1/
888
- attr[:"src"].must_match /fs=1/
889
- attr[:"src"].must_match /controls=0/
890
- attr[:"src"].must_match /autoplay=1/
891
- attr[:"src"].must_match /showinfo=0/
892
- attr[:"src"].must_match /showsearch=0/
893
- attr[:"width"].must_equal 680
894
- attr[:"height"].must_equal 384
895
- attr[:"frameborder"].must_equal "0"
896
- attr[:"type"].must_equal "text/html"
897
- end
898
-
899
- it "provide a version of the Vimeo player params in JSON/JS format" do
900
- @field.value = "http://vimeo.com/31836285"
901
- json = Spontaneous::JSON.parse(@field.render(:json))
902
- json[:"tagname"].must_equal "iframe"
903
- json[:"tag"].must_equal "<iframe/>"
904
- attr = json[:"attr"]
905
- attr.must_be_instance_of(Hash)
906
- attr[:"src"].must_match /color=ccc/
907
- attr[:"src"].must_match /autoplay=1/
908
- attr[:"src"].must_match /title=0/
909
- attr[:"src"].must_match /byline=0/
910
- attr[:"src"].must_match /portrait=0/
911
- attr[:"src"].must_match /api=1/
912
- attr[:"webkitAllowFullScreen"].must_equal "yes"
913
- attr[:"allowFullScreen"].must_equal "yes"
914
- attr[:"width"].must_equal 680
915
- attr[:"height"].must_equal 384
916
- attr[:"frameborder"].must_equal "0"
917
- attr[:"type"].must_equal "text/html"
918
- end
919
-
920
-
921
- it "can properly embed a Vine video" do
922
- @field.value = "https://vine.co/v/brI7pTPb3qU"
923
- embed = @field.render(:html)
924
- embed.must_match %r(iframe)
925
- embed.must_match %r(src=["']//vine\.co/v/brI7pTPb3qU/card["'])
926
- # Vine videos are square
927
- embed.must_match %r(width=["']680["'])
928
- embed.must_match %r(height=["']680["'])
929
- end
930
-
931
- it "falls back to a simple iframe for unknown providers xxx" do
932
- @field.value = "https://unknownprovider.net/xx/brI7pTPb3qU"
933
- embed = @field.render(:html)
934
- embed.must_match %r(iframe)
935
- embed.must_match %r(src=["']https://unknownprovider.net/xx/brI7pTPb3qU["'])
936
- embed.must_match %r(width=["']680["'])
937
- embed.must_match %r(height=["']384["'])
938
- end
939
- end
940
-
941
- end
942
-
943
- describe "HTML fields" do
944
- before do
945
- @content_class = Class.new(::Piece) do
946
- field :raw, :html
947
- end
948
- @content_class.stubs(:name).returns("ContentClass")
949
- @instance = @content_class.new
950
- @field = @instance.raw
951
- end
952
-
953
- it "does no escaping of input" do
954
- @field.value = "<script>\n</script>"
955
- @field.value(:html).must_equal "<script>\n</script>"
956
- end
957
- end
958
-
959
- describe "Location fields" do
960
- before do
961
- @content_class = Class.new(::Piece) do
962
- field :location
963
- end
964
- @content_class.stubs(:name).returns("ContentClass")
965
- @instance = @content_class.new
966
- @field = @instance.location
967
- end
968
-
969
- it "use a standard string editor" do
970
- @content_class.fields.location.export(nil)[:type].must_equal "Spontaneous.Field.String"
971
- end
972
-
973
- it "successfully geolocate an address" do
974
- # TODO: use mocking to avoid an external API request to googles geolocation service
975
- @field.value = "Cambridge, England"
976
- @field.value(:lat).must_equal 52.2053370
977
- @field.value(:lng).must_equal 0.1218170
978
- @field.value(:country).must_equal "United Kingdom"
979
- @field.value(:formatted_address).must_equal "Cambridge, Cambridge, UK"
980
-
981
- @field.latitude.must_equal 52.2053370
982
- @field.longitude.must_equal 0.1218170
983
- @field.lat.must_equal 52.2053370
984
- @field.lng.must_equal 0.1218170
985
-
986
- @field.country.must_equal "United Kingdom"
987
- @field.formatted_address.must_equal "Cambridge, Cambridge, UK"
988
- end
989
- end
990
-
991
- describe "Option fields" do
992
- before do
993
- @content_class = Class.new(::Piece) do
994
- field :options, :select, :options => [
995
- ["a", "Value A"],
996
- ["b", "Value B"],
997
- ["c", "Value C"]
998
- ]
999
- end
1000
- @content_class.stubs(:name).returns("ContentClass")
1001
- @instance = @content_class.new
1002
- @field = @instance.options
1003
- end
1004
-
1005
- it "use a specific editor class" do
1006
- @content_class.fields.options.export(nil)[:type].must_equal "Spontaneous.Field.Select"
1007
- end
1008
-
1009
- it "select the options class for fields named options" do
1010
- @content_class.field :type, :select, :options => [["a", "A"]]
1011
- assert @content_class.fields.options.instance_class.ancestors.include?(Spontaneous::Field::Select)
1012
- end
1013
-
1014
- it "accept a list of strings as options" do
1015
- @content_class.field :type, :select, :options => ["a", "b"]
1016
- @instance = @content_class.new
1017
- @instance.type.option_list.must_equal [["a", "a"], ["b", "b"]]
1018
- end
1019
-
1020
- it "accept a json string as a value and convert it properly" do
1021
- @field.value = %(["a", "Value A"])
1022
- @field.value.must_equal "a"
1023
- @field.value(:label).must_equal "Value A"
1024
- @field.label.must_equal "Value A"
1025
- @field.unprocessed_value.must_equal %(["a", "Value A"])
1026
- end
1027
- end
1028
-
1029
- describe "File fields" do
1030
- let(:path) { File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__) }
1031
-
1032
- before do
1033
- assert File.exists?(path), "Test file #{path} does not exist"
1034
- @content_class = Class.new(::Piece)
1035
- @prototype = @content_class.field :file
1036
- @content_class.stubs(:name).returns("ContentClass")
1037
- @instance = @content_class.create
1038
- @field = @instance.file
1039
- end
1040
-
1041
- it "have a distinct editor class" do
1042
- @prototype.instance_class.editor_class.must_equal "Spontaneous.Field.File"
1043
- end
1044
-
1045
- it "adopt any field called 'file'" do
1046
- assert @field.is_a?(Spontaneous::Field::File), "Field should be an instance of FileField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
1047
- end
1048
-
1049
- it "copy files to the media folder" do
1050
- File.open(path, 'rb') do |file|
1051
- @field.value = {
1052
- :tempfile => file,
1053
- :type => "application/pdf",
1054
- :filename => "vimlogo.pdf"
1055
- }
1056
- end
1057
- url = @field.value
1058
- path = File.join File.dirname(Spontaneous.media_dir), url
1059
- assert File.exist?(path), "Media file should have been copied into place"
1060
- end
1061
-
1062
- it "generate the requisite file metadata" do
1063
- File.open(path, 'rb') do |file|
1064
- @field.value = {
1065
- :tempfile => file,
1066
- :type => "application/pdf",
1067
- :filename => "vimlogo.pdf"
1068
- }
1069
- end
1070
- @field.value(:html).must_match %r{/media/.+/vimlogo.pdf$}
1071
- @field.value.must_match %r{/media/.+/vimlogo.pdf$}
1072
- @field.path.must_equal @field.value
1073
- @field.value(:filesize).must_equal 2254
1074
- @field.filesize.must_equal 2254
1075
- @field.value(:filename).must_equal "vimlogo.pdf"
1076
- @field.filename.must_equal "vimlogo.pdf"
1077
- end
1078
-
1079
- it "just accept the given value if passed a path to a non-existant file" do
1080
- @field.value = "/images/nosuchfile.rtf"
1081
- @field.value.must_equal "/images/nosuchfile.rtf"
1082
- @field.filename.must_equal "nosuchfile.rtf"
1083
- @field.filesize.must_equal 0
1084
- end
1085
-
1086
- it "copy the given file if passed a path to an existing file" do
1087
- @field.value = path
1088
- @field.value.must_match %r{/media/.+/vimlogo.pdf$}
1089
- @field.filename.must_equal "vimlogo.pdf"
1090
- @field.filesize.must_equal 2254
1091
- end
1092
-
1093
- it "sets the unprocessed value to a JSON encoded array of MD5 hash & filename" do
1094
- @field.value = path
1095
- @instance.save
1096
- @field.unprocessed_value.must_equal ["vimlogo.pdf", "1de7e866d69c2f56b4a3f59ed1c98b74"].to_json
1097
- end
1098
-
1099
- it "sets the field hash to the MD5 hash of the file" do
1100
- @field.value = path
1101
- @field.file_hash.must_equal "1de7e866d69c2f56b4a3f59ed1c98b74"
1102
- end
1103
-
1104
- it "sets the original filename of the file" do
1105
- @field.value = path
1106
- @field.original_filename.must_equal "vimlogo.pdf"
1107
- end
1108
-
1109
- it "doesn't set the hash of a file that can't be found" do
1110
- @field.value = "/images/nosuchfile.rtf"
1111
- @field.file_hash.must_equal ""
1112
- end
1113
-
1114
- it "sets the original filename of a file that can't be found" do
1115
- @field.value = "/images/nosuchfile.rtf"
1116
- @field.original_filename.must_equal "/images/nosuchfile.rtf"
1117
- end
1118
-
1119
- describe "clearing" do
1120
- def assert_file_field_empty
1121
- @field.value.must_equal ''
1122
- @field.filename.must_equal ''
1123
- @field.filesize.must_equal 0
1124
- end
1125
-
1126
- before do
1127
- path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
1128
- @field.value = path
1129
- end
1130
-
1131
- it "clears the value if set to the empty string" do
1132
- @field.value = ''
1133
- assert_file_field_empty
1134
- end
1135
- end
1136
-
1137
- describe "with cloud storage" do
1138
- before do
1139
- ::Fog.mock!
1140
- @aws_credentials = {
1141
- :provider=>"AWS",
1142
- :aws_secret_access_key=>"SECRET_ACCESS_KEY",
1143
- :aws_access_key_id=>"ACCESS_KEY_ID"
1144
- }
1145
- @storage = S::Media::Store::Cloud.new(@aws_credentials, "media.example.com")
1146
- @site.expects(:storage).returns(@storage)
1147
- end
1148
-
1149
- it "sets the content-disposition header if defined as an 'attachment'" do
1150
- prototype = @content_class.field :attachment, :file, attachment: true
1151
- field = @instance.attachment
1152
- path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
1153
- @storage.expects(:copy).with(path, is_a(Array), { content_type: "application/pdf", content_disposition: 'attachment; filename=vimlogo.pdf'})
1154
- field.value = path
1155
- end
1156
- end
1157
- end
1158
- describe "Date fields" do
1159
- before do
1160
- @content_class = Class.new(::Piece)
1161
- @prototype = @content_class.field :date
1162
- @content_class.stubs(:name).returns("ContentClass")
1163
- @instance = @content_class.create
1164
- @field = @instance.date
1165
- end
1166
-
1167
- it "have a distinct editor class" do
1168
- @prototype.instance_class.editor_class.must_equal "Spontaneous.Field.Date"
1169
- end
1170
-
1171
- it "adopt any field called 'date'" do
1172
- assert @field.is_a?(Spontaneous::Field::Date), "Field should be an instance of DateField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
1173
- end
1174
-
1175
- it "default to an empty string" do
1176
- @field.value(:html).must_equal ""
1177
- @field.value(:plain).must_equal ""
1178
- end
1179
-
1180
- it "correctly parse strings" do
1181
- @field.value = "Friday, 8 June, 2012"
1182
- @field.value(:html).must_equal %(<time datetime="2012-06-08">Friday, 8 June, 2012</time>)
1183
- @field.value(:plain).must_equal %(Friday, 8 June, 2012)
1184
- @field.date.must_equal Date.parse("Friday, 8 June, 2012")
1185
- end
1186
-
1187
- it "allow for setting a custom default format" do
1188
- prototype = @content_class.field :datef, :date, :format => "%d %b %Y, %a"
1189
- instance = @content_class.new
1190
- field = instance.datef
1191
- field.value = "Friday, 8 June, 2012"
1192
- field.value(:html).must_equal %(<time datetime="2012-06-08">08 Jun 2012, Fri</time>)
1193
- field.value(:plain).must_equal %(08 Jun 2012, Fri)
1194
- end
1195
- end
1196
-
1197
- describe "Tag list fields" do
1198
- before do
1199
- @content_class = Class.new(::Piece)
1200
- @prototype = @content_class.field :tags
1201
- @content_class.stubs(:name).returns("ContentClass")
1202
- @instance = @content_class.create
1203
- @field = @instance.tags
1204
- end
1205
-
1206
- it "has a distinct editor class" # eventually...
1207
-
1208
- it "adopts any field called 'tags'" do
1209
- assert @field.is_a?(Spontaneous::Field::Tags), "Field should be an instance of TagsField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
1210
- end
1211
-
1212
- it "defaults to an empty list" do
1213
- @field.value(:html).must_equal ""
1214
- @field.value(:tags).must_equal []
1215
- end
1216
-
1217
- it "correctly parses strings" do
1218
- @field.value = 'this that "the other" more'
1219
- @field.value(:html).must_equal 'this that "the other" more'
1220
- @field.value(:tags).must_equal ["this", "that", "the other", "more"]
1221
- end
1222
-
1223
- it "includes Enumerable" do
1224
- @field.value = 'this that "the other" more'
1225
- @field.map(&:upcase).must_equal ["THIS", "THAT", "THE OTHER", "MORE"]
1226
- end
1227
-
1228
- it "allows for tags with commas" do
1229
- @field.value = %(this that "the, other" more)
1230
- @field.map(&:upcase).must_equal ["THIS", "THAT", "THE, OTHER", "MORE"]
1231
- end
1232
- end
1233
-
1234
- describe "Boolean fields" do
1235
-
1236
- before do
1237
- @content_class = Class.new(::Piece)
1238
- @prototype = @content_class.field :switch
1239
- @content_class.stubs(:name).returns("ContentClass")
1240
- @instance = @content_class.create
1241
- @field = @instance.switch
1242
- end
1243
-
1244
- it "has a distinct editor class" do
1245
- @prototype.instance_class.editor_class.must_equal "Spontaneous.Field.Boolean"
1246
- end
1247
-
1248
- it "adopts any field called 'switch'" do
1249
- assert @field.is_a?(Spontaneous::Field::Boolean), "Field should be an instance of Boolean but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
1250
- end
1251
-
1252
- it "defaults to true" do
1253
- @field.value.must_equal true
1254
- @field.value(:html).must_equal "Yes"
1255
- @field.value(:string).must_equal "Yes"
1256
- end
1257
-
1258
- it "changes string value to 'No'" do
1259
- @field.value = false
1260
- @field.value(:string).must_equal "No"
1261
- end
1262
-
1263
- it "flags itself as 'empty' if false" do # I think...
1264
- @field.empty?.must_equal false
1265
- @field.value = false
1266
- @field.empty?.must_equal true
1267
- end
1268
-
1269
- it "uses the given state labels" do
1270
- prototype = @content_class.field :boolean, true: "Enabled", false: "Disabled"
1271
- field = prototype.to_field(@instance)
1272
- field.value.must_equal true
1273
- field.value(:string).must_equal "Enabled"
1274
- field.value = false
1275
- field.value(:string).must_equal "Disabled"
1276
- field.value(:html).must_equal "Disabled"
1277
- end
1278
-
1279
- it "uses the given default" do
1280
- prototype = @content_class.field :boolean, default: false, true: "On", false: "Off"
1281
- field = prototype.to_field(@instance)
1282
- field.value.must_equal false
1283
- field.value(:string).must_equal "Off"
1284
- end
1285
-
1286
- it "returns the string value from #to_s" do
1287
- prototype = @content_class.field :boolean, default: false, true: "On", false: "Off"
1288
- field = prototype.to_field(@instance)
1289
- field.to_s.must_equal "Off"
1290
- end
1291
-
1292
- it "has shortcut accessors" do
1293
- state = @field.value(:boolean)
1294
- @field.on?.must_equal state
1295
- @field.checked?.must_equal state
1296
- @field.enabled?.must_equal state
1297
- end
1298
-
1299
- it "exports the labels to the interface" do
1300
- prototype = @content_class.field :boolean, default: false, true: "Yes Please", false: "No Thanks"
1301
- exported = prototype.instance_class.export(nil)
1302
- exported.must_equal({:labels=>{:true=>"Yes Please", :false=>"No Thanks"}})
1303
- end
1304
- end
1305
-
1306
663
  describe "Asynchronous processing" do
1307
664
  before do
1308
665
  @site.background_mode = :simultaneous
@@ -1427,6 +784,7 @@ describe "Fields" do
1427
784
  :version => 1,
1428
785
  :value => {
1429
786
  :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
787
+ :storage_name=>"default",
1430
788
  :type=>"image/gif", :format => "gif",
1431
789
  :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1432
790
  :filename=>"something.gif",
@@ -1453,6 +811,7 @@ describe "Fields" do
1453
811
  :version => 1,
1454
812
  :value => {
1455
813
  :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
814
+ :storage_name=>"default",
1456
815
  :type=>"image/gif", :format => "gif",
1457
816
  :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1458
817
  :filename=>"something.gif",
@@ -1498,6 +857,7 @@ describe "Fields" do
1498
857
  :version => 1,
1499
858
  :value => {
1500
859
  :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
860
+ :storage_name=>"default",
1501
861
  :type=>"image/gif", :format => "gif",
1502
862
  :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1503
863
  :filename=>"something.gif",
@@ -1522,6 +882,7 @@ describe "Fields" do
1522
882
  :version => 1,
1523
883
  :value => {
1524
884
  :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
885
+ :storage_name=>"default",
1525
886
  :type=>"image/gif", :format => "gif",
1526
887
  :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1527
888
  :filename=>"something.gif",
@@ -1607,6 +968,22 @@ describe "Fields" do
1607
968
  end
1608
969
  end
1609
970
 
971
+ it "includes a local temp url for fields with pending values" do
972
+ field = @instance.image
973
+ Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
974
+ "fields" => [field.id]
975
+ })
976
+ File.open(@image, "r") do |file|
977
+ field.pending_version.must_equal 0
978
+ Spontaneous::Field.set(@site, field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
979
+ export = field.export
980
+ values = export[:processed_value]
981
+ assert values.key?(:__pending__)
982
+ pending = values[:__pending__][:value]
983
+ pending[:src].must_match %r{^/media/}
984
+ end
985
+ end
986
+
1610
987
  describe "page locks" do
1611
988
  before do
1612
989
  @now = Time.now