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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/LICENSE +18 -17
- data/Rakefile +1 -1
- data/application/css/core.css.scss +1 -1
- data/application/css/dialogue.css.scss +8 -20
- data/application/js/preview.js +28 -7
- data/application/js/publish.js +15 -4
- data/application/js/top_bar.js +0 -16
- data/application/js/views/piece_view.js +1 -1
- data/lib/spontaneous/asset/environment.rb +16 -1
- data/lib/spontaneous/box.rb +68 -0
- data/lib/spontaneous/capistrano/deploy.rb +7 -4
- data/lib/spontaneous/capistrano/sync.rb +2 -2
- data/lib/spontaneous/cli/init.rb +70 -19
- data/lib/spontaneous/cli/init/db.rb +34 -55
- data/lib/spontaneous/cli/init/mysql.rb +5 -5
- data/lib/spontaneous/cli/init/postgresql.rb +8 -9
- data/lib/spontaneous/cli/init/sqlite.rb +1 -2
- data/lib/spontaneous/cli/migrate.rb +0 -1
- data/lib/spontaneous/cli/site.rb +4 -0
- data/lib/spontaneous/collections/entry_set.rb +11 -0
- data/lib/spontaneous/data_mapper/content_model.rb +2 -0
- data/lib/spontaneous/data_mapper/content_model/serialization.rb +2 -2
- data/lib/spontaneous/extensions/array.rb +12 -2
- data/lib/spontaneous/field/base.rb +10 -0
- data/lib/spontaneous/field/file.rb +32 -2
- data/lib/spontaneous/field/image.rb +24 -2
- data/lib/spontaneous/field/select.rb +8 -0
- data/lib/spontaneous/field/webvideo.rb +8 -0
- data/lib/spontaneous/generators/site/config/initializers/fields.rb +55 -0
- data/lib/spontaneous/json.rb +3 -2
- data/lib/spontaneous/logger.rb +2 -2
- data/lib/spontaneous/media/file.rb +3 -3
- data/lib/spontaneous/media/image/attributes.rb +72 -6
- data/lib/spontaneous/media/image/renderable.rb +53 -20
- data/lib/spontaneous/media/store.rb +3 -3
- data/lib/spontaneous/media/store/backend.rb +16 -0
- data/lib/spontaneous/media/store/cloud.rb +52 -12
- data/lib/spontaneous/media/store/local.rb +6 -3
- data/lib/spontaneous/model.rb +3 -0
- data/lib/spontaneous/model/core/entries.rb +34 -13
- data/lib/spontaneous/model/core/entry.rb +3 -1
- data/lib/spontaneous/model/page/controllers.rb +1 -2
- data/lib/spontaneous/model/page/paths.rb +18 -7
- data/lib/spontaneous/output/context.rb +0 -8
- data/lib/spontaneous/output/template/renderer.rb +2 -0
- data/lib/spontaneous/plugins/application/state.rb +0 -4
- data/lib/spontaneous/prototypes/field_prototype.rb +4 -0
- data/lib/spontaneous/publishing/immediate.rb +0 -5
- data/lib/spontaneous/publishing/progress.rb +2 -2
- data/lib/spontaneous/publishing/rerender.rb +1 -4
- data/lib/spontaneous/publishing/simultaneous.rb +19 -17
- data/lib/spontaneous/publishing/steps.rb +12 -3
- data/lib/spontaneous/rack.rb +2 -0
- data/lib/spontaneous/rack/asset_server.rb +5 -2
- data/lib/spontaneous/rack/back.rb +9 -1
- data/lib/spontaneous/rack/back/base.rb +1 -0
- data/lib/spontaneous/rack/back/changes.rb +5 -0
- data/lib/spontaneous/rack/back/preview.rb +4 -4
- data/lib/spontaneous/rack/back/private.rb +11 -0
- data/lib/spontaneous/rack/middleware/scope.rb +16 -4
- data/lib/spontaneous/rack/page_controller.rb +2 -2
- data/lib/spontaneous/rack/public.rb +52 -4
- data/lib/spontaneous/sequel.rb +10 -13
- data/lib/spontaneous/site.rb +28 -8
- data/lib/spontaneous/site/publishing.rb +1 -1
- data/lib/spontaneous/site/storage.rb +7 -4
- data/lib/spontaneous/tasks/environment.rake +3 -0
- data/lib/spontaneous/utils/database/postgres_dumper.rb +23 -2
- data/lib/spontaneous/version.rb +1 -1
- data/spontaneous.gemspec +7 -12
- data/test/fixtures/assets/public1/css/data.css.scss +1 -1
- data/test/functional/test_application.rb +15 -0
- data/test/functional/test_cli.rb +109 -3
- data/test/functional/test_front.rb +108 -10
- data/test/test_helper.rb +3 -3
- data/test/unit/fields/test_boolean_fields.rb +80 -0
- data/test/unit/fields/test_date_fields.rb +47 -0
- data/test/unit/fields/test_file_field.rb +210 -0
- data/test/unit/{test_images.rb → fields/test_image_fields.rb} +133 -15
- data/test/unit/fields/test_location_fields.rb +41 -0
- data/test/unit/fields/test_option_fields.rb +61 -0
- data/test/unit/fields/test_tag_list_fields.rb +45 -0
- data/test/unit/fields/test_text_fields.rb +124 -0
- data/test/unit/fields/test_web_video_fields.rb +198 -0
- data/test/unit/test_assets.rb +22 -22
- data/test/unit/test_boxes.rb +34 -13
- data/test/unit/test_changesets.rb +1 -0
- data/test/unit/test_extensions.rb +17 -0
- data/test/unit/test_fields.rb +20 -643
- data/test/unit/test_media.rb +9 -9
- data/test/unit/test_page.rb +47 -0
- data/test/unit/test_publishing_pipeline.rb +2 -2
- data/test/unit/test_serialisation.rb +37 -0
- data/test/unit/test_storage.rb +42 -3
- metadata +37 -17
data/test/unit/test_assets.rb
CHANGED
@@ -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) { '
|
151
|
-
let(:m_js_digest) { '
|
152
|
-
let(:all_js_digest) { '
|
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) { '
|
155
|
-
let(:b_css_digest) { '
|
156
|
-
let(:c_css_digest) { '
|
157
|
-
let(:x_css_digest) { '
|
158
|
-
let(:all_css_digest) { '
|
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) { '
|
208
|
-
let(:x_js_digest) { '
|
207
|
+
let(:c_js_digest) { '3ab39368d6e128169ac2401bc49fcdb2' }
|
208
|
+
let(:x_js_digest) { 'e755ffcead8090406c04b2813b9bdce9' }
|
209
209
|
let(:n_js_digest) { '74f175e03a4bdc8c807aba4ae0314938' }
|
210
|
-
let(:m_js_digest) { '
|
211
|
-
let(:all_js_digest) { '
|
210
|
+
let(:m_js_digest) { 'c0f2e1c2a7d1cc9666ccb48cc9fd610e' }
|
211
|
+
let(:all_js_digest) { 'd782ef6abb69b1eb3e4c503478a660db' }
|
212
212
|
|
213
|
-
let(:c_css_digest) { '
|
214
|
-
let(:x_css_digest) { '
|
215
|
-
let(:all_css_digest) { '
|
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) { "
|
386
|
-
let(:x_sha) { "
|
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-
|
392
|
-
'<script type="text/javascript" src="/assets/js/c-
|
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) { "
|
460
|
-
let(:x_sha) { "
|
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-
|
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
|
data/test/unit/test_boxes.rb
CHANGED
@@ -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 <
|
398
|
+
class ::Allowed1 < Piece
|
378
399
|
style :frank
|
379
400
|
style :freddy
|
380
401
|
end
|
381
|
-
class ::Allowed2 <
|
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 <
|
408
|
+
class ::Allowed3 < Piece
|
388
409
|
style :arthur
|
389
410
|
style :lancelot
|
390
411
|
end
|
391
|
-
class ::Allowed4 <
|
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 <
|
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(:
|
570
|
-
instance.boxes[:b].stubs(:
|
571
|
-
instance.boxes[:c].stubs(:
|
572
|
-
instance.boxes[:d].stubs(:
|
573
|
-
end
|
574
|
-
@b.boxes[:e].stubs(:
|
575
|
-
@c.boxes[:e].stubs(:
|
576
|
-
@c.boxes[:f].stubs(:
|
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
|
@@ -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
|
|
data/test/unit/test_fields.rb
CHANGED
@@ -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 & 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&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
|