spontaneous 0.2.0.beta1 → 0.2.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (335) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.locat +42 -0
  4. data/.travis/gemfiles/Gemfile.empty +7 -0
  5. data/.travis.yml +18 -0
  6. data/Gemfile +12 -8
  7. data/LICENSE +1 -1
  8. data/Rakefile +15 -157
  9. data/Readme.markdown +1 -1
  10. data/application/css/core.css.scss +22 -146
  11. data/application/css/definitions.css.scss +7 -3
  12. data/application/css/dialogue.css.scss +26 -1
  13. data/application/css/editing.css.scss +70 -28
  14. data/application/css/font.css.scss +1 -1
  15. data/application/css/popover.css.scss +2 -0
  16. data/application/css/top.css.scss +231 -0
  17. data/application/js/add_alias_dialogue.js +1 -1
  18. data/application/js/add_home_dialogue.js +1 -1
  19. data/application/js/ajax.js +61 -31
  20. data/application/js/box.js +4 -4
  21. data/application/js/conflicted_field_dialogue.js +1 -1
  22. data/application/js/content.js +5 -5
  23. data/application/js/dom.js +5 -0
  24. data/application/js/edit_panel.js +1 -0
  25. data/application/js/editing.js +1 -1
  26. data/application/js/extensions.js +8 -0
  27. data/application/js/field/boolean.js +31 -0
  28. data/application/js/field/file.js +32 -4
  29. data/application/js/field/image.js +24 -9
  30. data/application/js/field/markdown.js +87 -59
  31. data/application/js/field/select.js +1 -1
  32. data/application/js/field/webvideo.js +6 -1
  33. data/application/js/init.js +2 -2
  34. data/application/js/jquery-selection-position.js +130 -0
  35. data/application/js/location.js +4 -25
  36. data/application/js/meta_view/user_admin.js +2 -2
  37. data/application/js/metadata.js +2 -2
  38. data/application/js/page_browser.js +1 -1
  39. data/application/js/panel/root_menu.js +0 -1
  40. data/application/js/popover.js +27 -12
  41. data/application/js/popover_view.js +20 -4
  42. data/application/js/preview.js +31 -16
  43. data/application/js/progress.js +22 -21
  44. data/application/js/publish.js +18 -7
  45. data/application/js/sharded_upload.js +9 -6
  46. data/application/js/spontaneous.js +3 -1
  47. data/application/js/top_bar.js +264 -173
  48. data/application/js/upload.js +12 -5
  49. data/application/js/upload_manager.js +4 -3
  50. data/application/js/user.js +1 -2
  51. data/application/js/views/box_view.js +1 -1
  52. data/application/js/views/page_view.js +16 -5
  53. data/application/js/views/piece_view.js +5 -4
  54. data/application/static/font/fontawesome-webfont-1c66a4738b40ef0f6b1abca0ba9a796d.ttf +0 -0
  55. data/application/views/index.erb +6 -14
  56. data/application/views/login.erb +6 -25
  57. data/application/views/schema_modification_error.html.erb +3 -7
  58. data/db/migrations/20130114120000_create_revision_tables.rb +2 -2
  59. data/db/migrations/20130813111009_increase_path_length.rb +14 -0
  60. data/gem-public_cert.pem +20 -0
  61. data/lib/spontaneous/asset/app_compiler.rb +44 -0
  62. data/lib/spontaneous/asset/environment.rb +225 -0
  63. data/lib/spontaneous/asset.rb +2 -67
  64. data/lib/spontaneous/box.rb +0 -1
  65. data/lib/spontaneous/capistrano/deploy.rb +2 -2
  66. data/lib/spontaneous/capistrano/sync.rb +1 -1
  67. data/lib/spontaneous/cli/init.rb +36 -13
  68. data/lib/spontaneous/cli/server.rb +0 -1
  69. data/lib/spontaneous/cli/site.rb +2 -1
  70. data/lib/spontaneous/cli.rb +3 -1
  71. data/lib/spontaneous/collections/entry_set.rb +4 -12
  72. data/lib/spontaneous/collections/hash_with_fallback.rb +20 -0
  73. data/lib/spontaneous/collections/prototype_set.rb +6 -5
  74. data/lib/spontaneous/crypt.rb +2 -2
  75. data/lib/spontaneous/data_mapper/content_model/associations.rb +115 -63
  76. data/lib/spontaneous/data_mapper.rb +1 -1
  77. data/lib/spontaneous/errors.rb +6 -0
  78. data/lib/spontaneous/extensions/object_space.rb +6 -0
  79. data/lib/spontaneous/facet.rb +1 -0
  80. data/lib/spontaneous/field/base.rb +86 -13
  81. data/lib/spontaneous/field/boolean.rb +65 -0
  82. data/lib/spontaneous/field/file.rb +17 -6
  83. data/lib/spontaneous/field/html.rb +13 -0
  84. data/lib/spontaneous/field/image/size.rb +76 -0
  85. data/lib/spontaneous/field/image.rb +99 -414
  86. data/lib/spontaneous/field/tags.rb +36 -0
  87. data/lib/spontaneous/field/update.rb +1 -1
  88. data/lib/spontaneous/field/webvideo/fallback.rb +41 -0
  89. data/lib/spontaneous/field/webvideo/vimeo.rb +113 -0
  90. data/lib/spontaneous/field/webvideo/vine.rb +94 -0
  91. data/lib/spontaneous/field/webvideo/youtube.rb +133 -0
  92. data/lib/spontaneous/field/webvideo.rb +100 -250
  93. data/lib/spontaneous/field.rb +1 -1
  94. data/lib/spontaneous/generators/site/Gemfile.tt +5 -14
  95. data/lib/spontaneous/generators/site/assets/README.md +20 -0
  96. data/lib/spontaneous/generators/site/assets/css/site.scss +8 -0
  97. data/lib/spontaneous/generators/site/assets/js/site.js +6 -0
  98. data/lib/spontaneous/generators/site/config/deploy.rb.tt +9 -0
  99. data/lib/spontaneous/generators/site/config/user_levels.yml +14 -3
  100. data/lib/spontaneous/generators/site/public/README.md +12 -0
  101. data/lib/spontaneous/generators/site/templates/layouts/standard.html.cut.tt +2 -2
  102. data/lib/spontaneous/generators/site.rb +77 -35
  103. data/lib/spontaneous/layout.rb +6 -7
  104. data/lib/spontaneous/loader.rb +21 -13
  105. data/lib/spontaneous/media/file.rb +22 -9
  106. data/lib/spontaneous/media/image/attributes.rb +33 -0
  107. data/lib/spontaneous/media/image/format/gif.rb +4 -0
  108. data/lib/spontaneous/media/image/format/jpg.rb +17 -0
  109. data/lib/spontaneous/media/image/format/png.rb +4 -0
  110. data/lib/spontaneous/media/image/format/webp.rb +26 -0
  111. data/lib/spontaneous/media/image/format.rb +79 -0
  112. data/lib/spontaneous/media/image/optimizer.rb +69 -0
  113. data/lib/spontaneous/media/image/processor.rb +17 -0
  114. data/lib/spontaneous/media/image/renderable.rb +52 -0
  115. data/lib/spontaneous/media/image/skeptick.rb +70 -0
  116. data/lib/spontaneous/media/image.rb +50 -0
  117. data/lib/spontaneous/media/temp_file.rb +4 -0
  118. data/lib/spontaneous/media.rb +1 -0
  119. data/lib/spontaneous/model/core/aliases.rb +14 -8
  120. data/lib/spontaneous/model/core/boxes.rb +5 -2
  121. data/lib/spontaneous/model/core/entries.rb +4 -0
  122. data/lib/spontaneous/model/core/entry.rb +1 -0
  123. data/lib/spontaneous/model/core/fields.rb +5 -2
  124. data/lib/spontaneous/model/core/locks.rb +16 -0
  125. data/lib/spontaneous/model/core/media.rb +1 -15
  126. data/lib/spontaneous/model/core.rb +31 -1
  127. data/lib/spontaneous/model/page/controllers.rb +2 -2
  128. data/lib/spontaneous/model/page/formats.rb +1 -4
  129. data/lib/spontaneous/model/page/layouts.rb +6 -2
  130. data/lib/spontaneous/model/page/locks.rb +8 -2
  131. data/lib/spontaneous/model/page/page_tree.rb +2 -2
  132. data/lib/spontaneous/model/page/paths.rb +74 -9
  133. data/lib/spontaneous/model/page.rb +11 -3
  134. data/lib/spontaneous/model.rb +6 -6
  135. data/lib/spontaneous/output/context/render_cache.rb +23 -0
  136. data/lib/spontaneous/output/context.rb +56 -30
  137. data/lib/spontaneous/output/helpers/script_helper.rb +9 -53
  138. data/lib/spontaneous/output/helpers/stylesheet_helper.rb +8 -40
  139. data/lib/spontaneous/output/template/renderer.rb +17 -5
  140. data/lib/spontaneous/output.rb +0 -1
  141. data/lib/spontaneous/paths.rb +6 -2
  142. data/lib/spontaneous/permissions/access_key.rb +18 -0
  143. data/lib/spontaneous/permissions/user.rb +1 -1
  144. data/lib/spontaneous/permissions.rb +4 -1
  145. data/lib/spontaneous/plugins/application/state.rb +19 -12
  146. data/lib/spontaneous/prototypes/field_prototype.rb +14 -8
  147. data/lib/spontaneous/published_revision.rb +7 -0
  148. data/lib/spontaneous/publishing/immediate.rb +43 -34
  149. data/lib/spontaneous/publishing/revision.rb +9 -6
  150. data/lib/spontaneous/rack/asset_server.rb +20 -0
  151. data/lib/spontaneous/rack/back/alias.rb +46 -0
  152. data/lib/spontaneous/rack/back/application_assets.rb +28 -0
  153. data/lib/spontaneous/rack/back/base.rb +34 -0
  154. data/lib/spontaneous/rack/back/changes.rb +19 -0
  155. data/lib/spontaneous/rack/back/content.rb +54 -0
  156. data/lib/spontaneous/rack/back/events.rb +38 -0
  157. data/lib/spontaneous/rack/back/field.rb +37 -0
  158. data/lib/spontaneous/rack/back/file.rb +118 -0
  159. data/lib/spontaneous/rack/back/helpers.rb +71 -0
  160. data/lib/spontaneous/rack/back/index.rb +16 -0
  161. data/lib/spontaneous/rack/back/login.rb +47 -0
  162. data/lib/spontaneous/rack/back/map.rb +24 -0
  163. data/lib/spontaneous/rack/back/page.rb +46 -0
  164. data/lib/spontaneous/rack/back/preview.rb +43 -0
  165. data/lib/spontaneous/rack/back/schema.rb +30 -0
  166. data/lib/spontaneous/rack/back/site.rb +25 -0
  167. data/lib/spontaneous/rack/back/site_assets.rb +13 -0
  168. data/lib/spontaneous/rack/back/unsupported_browser.rb +7 -0
  169. data/lib/spontaneous/rack/{user_admin.rb → back/user_admin.rb} +2 -5
  170. data/lib/spontaneous/rack/back.rb +85 -764
  171. data/lib/spontaneous/rack/cacheable_file.rb +3 -3
  172. data/lib/spontaneous/rack/front.rb +16 -9
  173. data/lib/spontaneous/rack/middleware/authenticate.rb +65 -0
  174. data/lib/spontaneous/rack/middleware/csrf.rb +66 -0
  175. data/lib/spontaneous/rack/middleware/reloader.rb +52 -0
  176. data/lib/spontaneous/rack/middleware/scope.rb +60 -0
  177. data/lib/spontaneous/rack/middleware.rb +6 -0
  178. data/lib/spontaneous/rack/page_controller.rb +18 -5
  179. data/lib/spontaneous/rack/public.rb +17 -11
  180. data/lib/spontaneous/rack.rb +34 -24
  181. data/lib/spontaneous/revision.rb +29 -2
  182. data/lib/spontaneous/schema/uid.rb +4 -3
  183. data/lib/spontaneous/schema/uid_map.rb +5 -24
  184. data/lib/spontaneous/schema.rb +1 -0
  185. data/lib/spontaneous/search/database.rb +8 -0
  186. data/lib/spontaneous/search/field.rb +1 -1
  187. data/lib/spontaneous/search/index.rb +3 -5
  188. data/lib/spontaneous/server.rb +1 -1
  189. data/lib/spontaneous/simultaneous.rb +1 -1
  190. data/lib/spontaneous/site/features.rb +4 -5
  191. data/lib/spontaneous/site/helpers.rb +22 -5
  192. data/lib/spontaneous/site/instance.rb +2 -2
  193. data/lib/spontaneous/site/selectors.rb +22 -3
  194. data/lib/spontaneous/storage/cloud.rb +13 -9
  195. data/lib/spontaneous/storage/local.rb +11 -6
  196. data/lib/spontaneous/style.rb +40 -23
  197. data/lib/spontaneous/utils/database/mysql_dumper.rb +1 -1
  198. data/lib/spontaneous/utils/smush_it.rb +1 -1
  199. data/lib/spontaneous/version.rb +1 -1
  200. data/lib/spontaneous.rb +35 -33
  201. data/spontaneous.gemspec +53 -787
  202. data/test/experimental/test_crypt.rb +56 -56
  203. data/test/experimental/test_features.rb +16 -27
  204. data/test/fixtures/assets/public1/css/data.css.scss +3 -0
  205. data/test/fixtures/assets/public1/css/image1.css.scss +4 -0
  206. data/test/fixtures/assets/public1/css/import.css.scss +1 -0
  207. data/test/fixtures/assets/public1/css/urlhash.css.scss +3 -0
  208. data/test/fixtures/assets/public1/js/a.js +1 -1
  209. data/test/fixtures/assets/public1/js/all.js +4 -0
  210. data/test/fixtures/assets/public1/js/{m.coffee → m.js.coffee} +1 -0
  211. data/test/fixtures/assets/public1/x.js +1 -0
  212. data/test/fixtures/assets/public2/css/all.css +4 -0
  213. data/test/fixtures/assets/public2/css/missing.css.scss +3 -0
  214. data/test/fixtures/assets/public2/i/y.png +0 -0
  215. data/test/fixtures/assets/public2/js/b.js +1 -1
  216. data/test/fixtures/assets/public2/js/c.js +1 -1
  217. data/test/fixtures/images/size.extended.webp +0 -0
  218. data/test/fixtures/images/size.lossless.webp +0 -0
  219. data/test/fixtures/images/size.lossy.webp +0 -0
  220. data/test/fixtures/schema/before.yml +4 -4
  221. data/test/fixtures/schema/schema.yml +1 -1
  222. data/test/fixtures/templates/aliases/aaa.html.cut +0 -0
  223. data/test/fixtures/templates/extended/partial_with_renderer.html.cut +1 -0
  224. data/test/fixtures/templates/extended/with_includes_and_renderer.html.cut +2 -0
  225. data/test/functional/test_application.rb +108 -106
  226. data/test/functional/test_back.rb +924 -930
  227. data/test/functional/test_front.rb +285 -238
  228. data/test/functional/test_user_manager.rb +75 -100
  229. data/test/integration/test_installation.rb +1 -1
  230. data/test/support/matchers.rb +12 -0
  231. data/test/support/minitest.rb +121 -0
  232. data/test/support/rack.rb +45 -0
  233. data/test/support/test_start_finish.rb +103 -0
  234. data/test/test_helper.rb +21 -68
  235. data/test/test_integration_helper.rb +1 -3
  236. data/test/unit/test_alias.rb +432 -408
  237. data/test/unit/test_asset_bundler.rb +58 -58
  238. data/test/unit/test_assets.rb +485 -155
  239. data/test/unit/test_async.rb +16 -37
  240. data/test/unit/test_authentication.rb +425 -457
  241. data/test/unit/test_boxes.rb +191 -191
  242. data/test/unit/test_changesets.rb +244 -254
  243. data/test/unit/test_config.rb +128 -142
  244. data/test/unit/test_content.rb +313 -359
  245. data/test/unit/test_content_inheritance.rb +29 -30
  246. data/test/unit/test_datamapper.rb +1205 -1080
  247. data/test/unit/test_datamapper_content.rb +49 -51
  248. data/test/unit/test_extensions.rb +23 -23
  249. data/test/unit/test_fields.rb +1488 -1180
  250. data/test/unit/test_formats.rb +158 -158
  251. data/test/unit/test_generators.rb +98 -40
  252. data/test/unit/test_helpers.rb +73 -76
  253. data/test/unit/test_image_size.rb +53 -22
  254. data/test/unit/test_images.rb +164 -165
  255. data/test/unit/test_layouts.rb +133 -122
  256. data/test/unit/test_logger.rb +14 -17
  257. data/test/unit/test_media.rb +69 -84
  258. data/test/unit/test_modifications.rb +513 -525
  259. data/test/unit/test_page.rb +462 -361
  260. data/test/unit/test_permissions.rb +379 -364
  261. data/test/unit/test_piece.rb +67 -75
  262. data/test/unit/test_plugins.rb +82 -89
  263. data/test/unit/test_prototype_set.rb +215 -216
  264. data/test/unit/test_prototypes.rb +114 -124
  265. data/test/unit/test_publishing.rb +252 -289
  266. data/test/unit/test_render.rb +167 -115
  267. data/test/unit/test_revisions.rb +436 -444
  268. data/test/unit/test_schema.rb +339 -309
  269. data/test/unit/test_search.rb +577 -574
  270. data/test/unit/test_serialisation.rb +136 -147
  271. data/test/unit/test_site.rb +252 -227
  272. data/test/unit/test_skeptick.rb +130 -0
  273. data/test/unit/test_storage.rb +46 -40
  274. data/test/unit/test_structure.rb +57 -66
  275. data/test/unit/test_styles.rb +104 -104
  276. data/test/unit/test_templates.rb +72 -57
  277. data/test/unit/test_type_hierarchy.rb +15 -16
  278. data/test/unit/test_visibility.rb +239 -257
  279. metadata +455 -326
  280. data/application/js/vendor/JS.Class-2.1.5/CHANGELOG +0 -283
  281. data/application/js/vendor/JS.Class-2.1.5/MIT-LICENSE +0 -30
  282. data/application/js/vendor/JS.Class-2.1.5/README +0 -30
  283. data/application/js/vendor/JS.Class-2.1.5/min/command.js +0 -1
  284. data/application/js/vendor/JS.Class-2.1.5/min/comparable.js +0 -1
  285. data/application/js/vendor/JS.Class-2.1.5/min/constant_scope.js +0 -1
  286. data/application/js/vendor/JS.Class-2.1.5/min/decorator.js +0 -1
  287. data/application/js/vendor/JS.Class-2.1.5/min/enumerable.js +0 -1
  288. data/application/js/vendor/JS.Class-2.1.5/min/forwardable.js +0 -1
  289. data/application/js/vendor/JS.Class-2.1.5/min/hash.js +0 -1
  290. data/application/js/vendor/JS.Class-2.1.5/min/linked_list.js +0 -1
  291. data/application/js/vendor/JS.Class-2.1.5/min/loader.js +0 -1
  292. data/application/js/vendor/JS.Class-2.1.5/min/method_chain.js +0 -1
  293. data/application/js/vendor/JS.Class-2.1.5/min/observable.js +0 -1
  294. data/application/js/vendor/JS.Class-2.1.5/min/package.js +0 -1
  295. data/application/js/vendor/JS.Class-2.1.5/min/proxy.js +0 -1
  296. data/application/js/vendor/JS.Class-2.1.5/min/ruby.js +0 -1
  297. data/application/js/vendor/JS.Class-2.1.5/min/set.js +0 -1
  298. data/application/js/vendor/JS.Class-2.1.5/min/stack_trace.js +0 -1
  299. data/application/js/vendor/JS.Class-2.1.5/min/state.js +0 -1
  300. data/application/js/vendor/JS.Class-2.1.5/min/stdlib.js +0 -16
  301. data/application/js/vendor/jquery-1.6.2.min.js +0 -18
  302. data/application/js/vendor/jquery-ui-1.8.16.custom.min.js +0 -791
  303. data/application/js/vendor/jquery-ui-1.8.9.custom.min.js +0 -415
  304. data/application/static/font/fontawesome-webfont-5c5c21100a346972a82c34c5e96ffcfe.ttf +0 -0
  305. data/application/static/select-arrow-6e7dd3745b00e934b0d7a3250c46558b.png +0 -0
  306. data/bin/limit-upload +0 -5
  307. data/bin/unlimit-upload +0 -3
  308. data/lib/spontaneous/asset/file.rb +0 -25
  309. data/lib/spontaneous/asset/source.rb +0 -28
  310. data/lib/spontaneous/image_size.rb +0 -123
  311. data/lib/spontaneous/output/assets/compression.rb +0 -58
  312. data/lib/spontaneous/output/assets.rb +0 -32
  313. data/lib/spontaneous/rack/around_back.rb +0 -20
  314. data/lib/spontaneous/rack/around_front.rb +0 -27
  315. data/lib/spontaneous/rack/around_preview.rb +0 -22
  316. data/lib/spontaneous/rack/assets.rb +0 -126
  317. data/lib/spontaneous/rack/authentication.rb +0 -20
  318. data/lib/spontaneous/rack/cookie_authentication.rb +0 -38
  319. data/lib/spontaneous/rack/helpers.rb +0 -52
  320. data/lib/spontaneous/rack/http.rb +0 -18
  321. data/lib/spontaneous/rack/media.rb +0 -30
  322. data/lib/spontaneous/rack/query_authentication.rb +0 -35
  323. data/lib/spontaneous/rack/reloader.rb +0 -45
  324. data/lib/spontaneous/rack/user_helpers.rb +0 -28
  325. /data/{README → application/js/field/markdown/text_command.js} +0 -0
  326. /data/application/js/vendor/{JS.Class-2.1.5/min/core.js → js.class-2.1.5.min.js} +0 -0
  327. /data/test/fixtures/assets/public1/css/{a.scss → a.css.scss} +0 -0
  328. /data/{lib/spontaneous/generators/site/public/css/site.scss → test/fixtures/assets/public1/x.css} +0 -0
  329. /data/{lib/spontaneous/generators/site/public/js/.empty_directory → test/fixtures/assets/public1/x.png} +0 -0
  330. /data/test/fixtures/assets/public2/css/{b.scss → b.css.scss} +0 -0
  331. /data/test/fixtures/assets/public2/js/{n.coffee → n.js.coffee} +0 -0
  332. /data/test/fixtures/back/{public → assets}/css/sass_include.scss +0 -0
  333. /data/test/fixtures/back/{public → assets}/css/sass_template.scss +0 -0
  334. /data/test/fixtures/back/{public → assets}/js/coffeescript.coffee +0 -0
  335. /data/{lib/spontaneous/generators/site/public/js/site.js → test/fixtures/templates/aliases/aa_alias.html.cut} +0 -0
@@ -1,10 +1,11 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require File.expand_path('../../test_helper', __FILE__)
4
+ require 'fog'
4
5
 
5
- class FieldsTest < MiniTest::Spec
6
+ describe "Fields" do
6
7
 
7
- def setup
8
+ before do
8
9
  @site = setup_site
9
10
  @now = Time.now
10
11
  stub_time(@now)
@@ -12,1424 +13,1731 @@ class FieldsTest < MiniTest::Spec
12
13
  Site.background_mode = :immediate
13
14
  end
14
15
 
15
- def teardown
16
+ after do
16
17
  teardown_site
17
18
  end
18
19
 
19
- context "Fields" do
20
- context "New content instances xxx" do
21
- setup do
22
- @content_class = Class.new(Piece) do
23
- field :title, :default => "Magic"
24
- field :thumbnail, :image
25
- end
26
- @instance = @content_class.new
27
- end
28
-
29
- should "have fields with values defined by prototypes" do
30
- f = @instance.fields[:title]
31
- assert f.class < Spontaneous::Field::String
32
- f.value.should == "Magic"
20
+ describe "New content instances" do
21
+ before do
22
+ @content_class = Class.new(Piece) do
23
+ field :title, :default => "Magic"
24
+ field :thumbnail, :image
33
25
  end
26
+ @instance = @content_class.new
27
+ end
34
28
 
35
- should "have shortcut access methods to fields" do
36
- @instance.fields.thumbnail.should == @instance.fields[:thumbnail]
37
- end
38
- should "have a shortcut setter on the Content fields" do
39
- @instance.fields.title = "New Title"
40
- end
29
+ it "have fields with values defined by prototypes" do
30
+ f = @instance.fields[:title]
31
+ assert f.class < Spontaneous::Field::String
32
+ f.value.must_equal "Magic"
33
+ end
41
34
 
42
- should "have a shortcut getter on the Content instance itself" do
43
- @instance.title.should == @instance.fields[:title]
44
- end
35
+ it "have shortcut access methods to fields" do
36
+ @instance.fields.thumbnail.must_equal @instance.fields[:thumbnail]
37
+ end
38
+ it "have a shortcut setter on the Content fields" do
39
+ @instance.fields.title = "New Title"
40
+ end
45
41
 
46
- should "have a shortcut setter on the Content instance itself" do
47
- @instance.title = "Boing!"
48
- @instance.fields[:title].value.should == "Boing!"
49
- end
42
+ it "have a shortcut getter on the Content instance itself" do
43
+ @instance.title.must_equal @instance.fields[:title]
44
+ end
50
45
 
51
- # TODO: I want to allow this but don't like overwriting the ::fields
52
- # method like this.
53
- # should "allow the definition of multiple fields at once" do
54
- # content_class = Class.new(Piece) do
55
- # fields :title, :photo, :date
56
- # end
57
- # end
46
+ it "have a shortcut setter on the Content instance itself" do
47
+ @instance.title = "Boing!"
48
+ @instance.fields[:title].value.must_equal "Boing!"
58
49
  end
59
50
 
60
- context "Overwriting fields" do
61
- setup do
62
- @class1 = Class.new(Piece) do
63
- field :title, :string, :default => "One"
64
- field :date, :string
65
- end
66
- @class2 = Class.new(@class1) do
67
- field :title, :image, :default => "Two", :title => "Two"
68
- end
69
- @class3 = Class.new(@class2) do
70
- field :date, :image, :default => "Three", :title => "Three"
71
- end
72
- @instance = @class2.new
73
- end
74
-
75
- should "overwrite field definitions" do
76
- @class2.fields.first.name.should == :title
77
- @class2.fields.last.name.should == :date
78
- @class2.fields.length.should == 2
79
- @class2.fields.title.schema_id.should == @class1.fields.title.schema_id
80
- @class2.fields.title.title.should == "Two"
81
- @class2.fields.title.title.should == "Two"
82
- @class3.fields.date.title.should == "Three"
83
- @class3.fields.date.schema_id.should == @class1.fields.date.schema_id
84
- assert @instance.title.class < Spontaneous::Field::Image
85
- @instance.title.value.to_s.should == "Two"
86
- instance1 = @class1.new
87
- instance3 = @class3.new
88
- @instance.title.schema_id.should == instance1.title.schema_id
89
- instance1.title.schema_id.should == instance3.title.schema_id
90
- end
91
- end
92
- context "Field Prototypes" do
93
- setup do
94
- @content_class = Class.new(Piece) do
95
- field :title
96
- field :synopsis, :string
97
- end
98
- @content_class.field :complex, :image, :default => "My default", :comment => "Use this to"
99
- end
51
+ # TODO: I want to allow this but don't like overwriting the ::fields
52
+ # method like this.
53
+ # it "allow the definition of multiple fields at once" do
54
+ # content_class = Class.new(Piece) do
55
+ # fields :title, :photo, :date
56
+ # end
57
+ # end
58
+ end
100
59
 
101
- should "be creatable with just a field name" do
102
- @content_class.field_prototypes[:title].must_be_instance_of(Spontaneous::Prototypes::FieldPrototype)
103
- @content_class.field_prototypes[:title].name.should == :title
60
+ describe "Overwriting fields" do
61
+ before do
62
+ @class1 = Class.new(Piece) do
63
+ field :title, :string, :default => "One"
64
+ field :date, :string
104
65
  end
105
-
106
- should "work with just a name & options" do
107
- @content_class.field :minimal, :default => "Small"
108
- @content_class.field_prototypes[:minimal].name.should == :minimal
109
- @content_class.field_prototypes[:minimal].default.should == "Small"
66
+ @class2 = Class.new(@class1) do
67
+ field :title, :image, :default => "Two", :title => "Two"
110
68
  end
111
-
112
- should "default to basic string class" do
113
- assert @content_class.field_prototypes[:title].instance_class < Spontaneous::Field::String
69
+ @class3 = Class.new(@class2) do
70
+ field :date, :image, :default => "Three", :title => "Three"
114
71
  end
72
+ @instance = @class2.new
73
+ end
115
74
 
116
- should "map :string type to Field::String" do
117
- assert @content_class.field_prototypes[:synopsis].instance_class < Spontaneous::Field::String
75
+ it "overwrite field definitions" do
76
+ @class2.fields.first.name.must_equal :title
77
+ @class2.fields.last.name.must_equal :date
78
+ @class2.fields.length.must_equal 2
79
+ @class2.fields.title.schema_id.must_equal @class1.fields.title.schema_id
80
+ @class2.fields.title.title.must_equal "Two"
81
+ @class2.fields.title.title.must_equal "Two"
82
+ @class3.fields.date.title.must_equal "Three"
83
+ @class3.fields.date.schema_id.must_equal @class1.fields.date.schema_id
84
+ assert @instance.title.class < Spontaneous::Field::Image
85
+ @instance.title.value.to_s.must_equal "Two"
86
+ instance1 = @class1.new
87
+ instance3 = @class3.new
88
+ @instance.title.schema_id.must_equal instance1.title.schema_id
89
+ instance1.title.schema_id.must_equal instance3.title.schema_id
90
+ end
91
+ end
92
+ describe "Field Prototypes" do
93
+ before do
94
+ @content_class = Class.new(Piece) do
95
+ field :title
96
+ field :synopsis, :string
118
97
  end
98
+ @content_class.field :complex, :image, :default => "My default", :comment => "Use this to"
99
+ end
119
100
 
120
- should "be listable" do
121
- @content_class.field_names.should == [:title, :synopsis, :complex]
122
- end
101
+ it "be creatable with just a field name" do
102
+ @content_class.field_prototypes[:title].must_be_instance_of(Spontaneous::Prototypes::FieldPrototype)
103
+ @content_class.field_prototypes[:title].name.must_equal :title
104
+ end
123
105
 
124
- should "be testable for existance" do
125
- @content_class.field?(:title).should be_true
126
- @content_class.field?(:synopsis).should be_true
127
- @content_class.field?(:non_existant).should be_false
128
- i = @content_class.new
129
- i.field?(:title).should be_true
130
- i.field?(:non_existant).should be_false
131
- end
106
+ it "work with just a name & options" do
107
+ @content_class.field :minimal, :default => "Small"
108
+ @content_class.field_prototypes[:minimal].name.must_equal :minimal
109
+ @content_class.field_prototypes[:minimal].default.must_equal "Small"
110
+ end
132
111
 
112
+ it "default to basic string class" do
113
+ assert @content_class.field_prototypes[:title].instance_class < Spontaneous::Field::String
114
+ end
133
115
 
134
- context "default values" do
135
- setup do
136
- @prototype = @content_class.field_prototypes[:title]
137
- end
116
+ it "map :string type to Field::String" do
117
+ assert @content_class.field_prototypes[:synopsis].instance_class < Spontaneous::Field::String
118
+ end
138
119
 
120
+ it "be listable" do
121
+ @content_class.field_names.must_equal [:title, :synopsis, :complex]
122
+ end
139
123
 
140
- should "default to a value of ''" do
141
- @prototype.default.should == ""
142
- end
124
+ it "be testable for existance" do
125
+ assert @content_class.field?(:title)
126
+ assert @content_class.field?(:synopsis)
127
+ refute @content_class.field?(:non_existant)
128
+ i = @content_class.new
129
+ assert i.field?(:title)
130
+ refute i.field?(:non_existant)
131
+ end
143
132
 
144
- should "get recieve calculated default values if default is a proc" do
145
- n = 0
146
- @content_class.field :dynamic, :default => proc { (n += 1) }
147
- instance = @content_class.new
148
- instance.dynamic.value.should == "1"
149
- instance = @content_class.new
150
- instance.dynamic.value.should == "2"
151
- end
152
133
 
153
- should "be able to calculate default values based on properties of owner" do
154
- @content_class.field :dynamic, :default => proc { |owner| owner.title.value }
155
- instance = @content_class.new(:title => "Frog")
156
- instance.dynamic.value.should == "Frog"
157
- end
134
+ describe "default values" do
135
+ before do
136
+ @prototype = @content_class.field_prototypes[:title]
137
+ end
158
138
 
159
- should "match name to type if sensible" do
160
- content_class = Class.new(Piece) do
161
- field :image
162
- field :date
163
- field :chunky
164
- end
165
139
 
166
- assert content_class.field_prototypes[:image].field_class < Spontaneous::Field::Image
167
- assert content_class.field_prototypes[:date].field_class < Spontaneous::Field::Date
168
- assert content_class.field_prototypes[:chunky].field_class < Spontaneous::Field::String
169
- end
140
+ it "default to a value of ''" do
141
+ @prototype.default.must_equal ""
170
142
  end
171
143
 
172
- context "Field titles" do
173
- setup do
174
- @content_class = Class.new(Piece) do
175
- field :title
176
- field :having_fun_yet
177
- field :synopsis, :title => "Custom Title"
178
- field :description, :title => "Simple Description"
179
- end
180
- @title = @content_class.field_prototypes[:title]
181
- @having_fun = @content_class.field_prototypes[:having_fun_yet]
182
- @synopsis = @content_class.field_prototypes[:synopsis]
183
- @description = @content_class.field_prototypes[:description]
184
- end
185
-
186
- should "default to a sensible title" do
187
- @title.title.should == "Title"
188
- @having_fun.title.should == "Having Fun Yet"
189
- @synopsis.title.should == "Custom Title"
190
- @description.title.should == "Simple Description"
191
- end
144
+ it "get recieve calculated default values if default is a proc" do
145
+ n = 0
146
+ @content_class.field :dynamic, :default => proc { (n += 1) }
147
+ instance = @content_class.new
148
+ instance.dynamic.value.must_equal "1"
149
+ instance = @content_class.new
150
+ instance.dynamic.value.must_equal "2"
192
151
  end
193
- context "option parsing" do
194
- setup do
195
- @prototype = @content_class.field_prototypes[:complex]
196
- end
197
152
 
198
- should "parse field class" do
199
- assert @prototype.field_class < Spontaneous::Field::Image
200
- end
153
+ it "be able to calculate default values based on properties of owner" do
154
+ @content_class.field :dynamic, :default => proc { |owner| owner.title.value }
155
+ instance = @content_class.new(:title => "Frog")
156
+ instance.dynamic.value.must_equal "Frog"
157
+ end
201
158
 
202
- should "parse default value" do
203
- @prototype.default.should == "My default"
159
+ it "match name to type if sensible" do
160
+ content_class = Class.new(Piece) do
161
+ field :image
162
+ field :date
163
+ field :chunky
204
164
  end
205
165
 
206
- should "parse ui comment" do
207
- @prototype.comment.should == "Use this to"
208
- end
166
+ assert content_class.field_prototypes[:image].field_class < Spontaneous::Field::Image
167
+ assert content_class.field_prototypes[:date].field_class < Spontaneous::Field::Date
168
+ assert content_class.field_prototypes[:chunky].field_class < Spontaneous::Field::String
209
169
  end
170
+ end
210
171
 
211
- context "sub-classes" do
212
- setup do
213
- @subclass = Class.new(@content_class) do
214
- field :child_field
215
- end
216
- @subsubclass = Class.new(@subclass) do
217
- field :distant_relation
218
- end
219
- end
220
-
221
- should "inherit super class's field prototypes" do
222
- @subclass.field_names.should == [:title, :synopsis, :complex, :child_field]
223
- @subsubclass.field_names.should == [:title, :synopsis, :complex, :child_field, :distant_relation]
172
+ describe "Field titles" do
173
+ before do
174
+ @content_class = Class.new(Piece) do
175
+ field :title
176
+ field :having_fun_yet
177
+ field :synopsis, :title => "Custom Title"
178
+ field :description, :title => "Simple Description"
224
179
  end
180
+ @title = @content_class.field_prototypes[:title]
181
+ @having_fun = @content_class.field_prototypes[:having_fun_yet]
182
+ @synopsis = @content_class.field_prototypes[:synopsis]
183
+ @description = @content_class.field_prototypes[:description]
184
+ end
225
185
 
226
- should "deal intelligently with manual setting of field order" do
227
- @reordered_class = Class.new(@subsubclass) do
228
- field_order :child_field, :complex
229
- end
230
- @reordered_class.field_names.should == [:child_field, :complex, :title, :synopsis, :distant_relation]
231
- end
186
+ it "default to a sensible title" do
187
+ @title.title.must_equal "Title"
188
+ @having_fun.title.must_equal "Having Fun Yet"
189
+ @synopsis.title.must_equal "Custom Title"
190
+ @description.title.must_equal "Simple Description"
232
191
  end
233
192
  end
234
-
235
- context "Values" do
236
- setup do
237
- @field_class = Class.new(S::Field::Base) do
238
- def outputs
239
- [:html, :plain, :fancy]
240
- end
241
- def generate_html(value)
242
- "<#{value}>"
243
- end
244
- def generate_plain(value)
245
- "*#{value}*"
246
- end
247
-
248
- def generate(output, value)
249
- case output
250
- when :fancy
251
- "#{value}!"
252
- else
253
- value
254
- end
255
- end
256
- end
257
- @field = @field_class.new
193
+ describe "option parsing" do
194
+ before do
195
+ @prototype = @content_class.field_prototypes[:complex]
258
196
  end
259
197
 
260
- should "be used as the comparator" do
261
- f1 = @field_class.new
262
- f1.value = "a"
263
- f2 = @field_class.new
264
- f2.value = "b"
265
- (f1 <=> f2).should == -1
266
- (f2 <=> f1).should == 1
267
- (f1 <=> f1).should == 0
268
- [f2, f1].sort.map(&:value).should == ["<a>", "<b>"]
198
+ it "parse field class" do
199
+ assert @prototype.field_class < Spontaneous::Field::Image
269
200
  end
270
201
 
271
- should "be transformed by the update method" do
272
- @field.value = "Hello"
273
- @field.value.should == "<Hello>"
274
- @field.value(:html).should == "<Hello>"
275
- @field.value(:plain).should == "*Hello*"
276
- @field.value(:fancy).should == "Hello!"
277
- @field.unprocessed_value.should == "Hello"
202
+ it "parse default value" do
203
+ @prototype.default.must_equal "My default"
278
204
  end
279
205
 
280
- should "appear in the to_s method" do
281
- @field.value = "String"
282
- @field.to_s.should == "<String>"
283
- @field.to_s(:html).should == "<String>"
284
- @field.to_s(:plain).should == "*String*"
206
+ it "parse ui comment" do
207
+ @prototype.comment.must_equal "Use this to"
285
208
  end
209
+ end
286
210
 
287
- should "escape ampersands by default" do
288
- field_class = Class.new(S::Field::String) do
211
+ describe "sub-classes" do
212
+ before do
213
+ @subclass = Class.new(@content_class) do
214
+ field :child_field
215
+ end
216
+ @subsubclass = Class.new(@subclass) do
217
+ field :distant_relation
289
218
  end
290
- field = field_class.new
291
- field.value = "Hello & Welcome"
292
- field.value(:html).should == "Hello &amp; Welcome"
293
- field.value(:plain).should == "Hello & Welcome"
294
219
  end
295
220
 
296
- should "educate quotes" do
297
- field_class = Class.new(S::Field::String)
298
- field = field_class.new
299
- field.value = %("John's first... example")
300
- field.value(:html).should == "“John’s first… example”"
301
- field.value(:plain).should == "“John’s first… example”"
221
+ it "inherit super class's field prototypes" do
222
+ @subclass.field_names.must_equal [:title, :synopsis, :complex, :child_field]
223
+ @subsubclass.field_names.must_equal [:title, :synopsis, :complex, :child_field, :distant_relation]
302
224
  end
303
225
 
304
- should "not process values coming from db" do
305
- class ContentClass1 < Piece
306
- end
307
- $transform = lambda { |value| "<#{value}>" }
308
- ContentClass1.field :title do
309
- def generate_html(value)
310
- $transform[value]
311
- end
226
+ it "deal intelligently with manual setting of field order" do
227
+ @reordered_class = Class.new(@subsubclass) do
228
+ field_order :child_field, :complex
312
229
  end
313
- instance = ContentClass1.new
314
- instance.fields.title = "Monkey"
315
- instance.save
316
-
317
- $transform = lambda { |value| "*#{value}*" }
318
- instance = ContentClass1[instance.id]
319
- instance.fields.title.value.should == "<Monkey>"
320
- FieldsTest.send(:remove_const, :ContentClass1)
230
+ @reordered_class.field_names.must_equal [:child_field, :complex, :title, :synopsis, :distant_relation]
321
231
  end
322
232
  end
323
233
 
324
- context "field instances" do
325
- setup do
326
- ::CC = Class.new(Piece) do
327
- field :title, :default => "Magic" do
328
- def generate_html(value)
329
- "*#{value}*"
330
- end
331
- end
234
+ describe "fallback values" do
235
+ before do
236
+ @content_class = Class.new(Piece) do
237
+ field :title
238
+ field :desc1, :fallback => :title
239
+ field :desc2, :fallback => :desc1
240
+ field :desc3, :fallback => :desc9
332
241
  end
333
- @instance = CC.new
242
+ Object.send :const_set, :FieldWithFallbacks, @content_class
243
+ @instance = @content_class.new(:title => "TITLE")
334
244
  end
335
245
 
336
- teardown do
337
- Object.send(:remove_const, :CC)
246
+ after do
247
+ Object.send :remove_const, :FieldWithFallbacks rescue nil
338
248
  end
339
249
 
340
- should "have a link back to their owner" do
341
- @instance.fields.title.owner.should == @instance
250
+ it "uses the fallback value for empty fields" do
251
+ @instance.desc1.value.must_equal "TITLE"
342
252
  end
343
253
 
344
- should "be created with the right default value" do
345
- f = @instance.fields.title
346
- f.value.should == "*Magic*"
254
+ it "cascades the fallback" do
255
+ @instance.desc2.value.must_equal "TITLE"
347
256
  end
348
257
 
349
- should "eval blocks from prototype defn" do
350
- f = @instance.fields.title
351
- f.value = "Boo"
352
- f.value.should == "*Boo*"
353
- f.unprocessed_value.should == "Boo"
258
+ it "uses the field value if present" do
259
+ @instance.desc2 = "DESC2"
260
+ @instance.desc2.value.must_equal "DESC2"
354
261
  end
355
262
 
356
- should "have a reference to their prototype" do
357
- f = @instance.fields.title
358
- f.prototype.should == CC.field_prototypes[:title]
263
+ it "deserializes values properly" do
264
+ @instance.desc2 = "DESC2"
265
+ @instance.save
266
+ @instance.reload
267
+ @instance.desc2.processed_values[:html].must_equal "DESC2"
359
268
  end
360
269
 
361
- should "return the item which isnt empty when using the / method" do
362
- a = CC.new(:title => "")
363
- b = CC.new(:title => "b")
364
- (a.title / b.title).should == b.title
365
- a.title = "a"
366
- (a.title / b.title).should == a.title
270
+ it "ignores invalid field names" do
271
+ @instance.desc3.value.must_equal ""
367
272
  end
273
+ end
274
+ end
368
275
 
369
- should "return the item which isnt empty when using the | method" do
370
- a = CC.new(:title => "")
371
- b = CC.new(:title => "b")
372
- (a.title | b.title).should == b.title
373
- a.title = "a"
374
- (a.title | b.title).should == a.title
276
+ describe "Values" do
277
+ before do
278
+ @field_class = Class.new(S::Field::Base) do
279
+ def outputs
280
+ [:html, :plain, :fancy]
281
+ end
282
+ def generate_html(value)
283
+ "<#{value}>"
284
+ end
285
+ def generate_plain(value)
286
+ "*#{value}*"
287
+ end
288
+
289
+ def generate(output, value)
290
+ case output
291
+ when :fancy
292
+ "#{value}!"
293
+ else
294
+ value
295
+ end
296
+ end
375
297
  end
298
+ @field = @field_class.new
299
+ end
300
+
301
+ it "be used as the comparator" do
302
+ f1 = @field_class.new
303
+ f1.value = "a"
304
+ f2 = @field_class.new
305
+ f2.value = "b"
306
+ (f1 <=> f2).must_equal -1
307
+ (f2 <=> f1).must_equal 1
308
+ (f1 <=> f1).must_equal 0
309
+ [f2, f1].sort.map(&:value).must_equal ["<a>", "<b>"]
310
+ end
311
+
312
+ it "be transformed by the update method" do
313
+ @field.value = "Hello"
314
+ @field.value.must_equal "<Hello>"
315
+ @field.value(:html).must_equal "<Hello>"
316
+ @field.value(:plain).must_equal "*Hello*"
317
+ @field.value(:fancy).must_equal "Hello!"
318
+ @field.unprocessed_value.must_equal "Hello"
319
+ end
320
+
321
+ it "appear in the to_s method" do
322
+ @field.value = "String"
323
+ @field.to_s.must_equal "<String>"
324
+ @field.to_s(:html).must_equal "<String>"
325
+ @field.to_s(:plain).must_equal "*String*"
326
+ end
376
327
 
377
- should "return the item which isnt empty when using the or method" do
378
- a = CC.new(:title => "")
379
- b = CC.new(:title => "b")
380
- (a.title.or(b.title)).should == b.title
381
- a.title = "a"
382
- (a.title.or(b.title)).should == a.title
328
+ it "escape ampersands by default" do
329
+ field_class = Class.new(S::Field::String) do
383
330
  end
331
+ field = field_class.new
332
+ field.value = "Hello & Welcome"
333
+ field.value(:html).must_equal "Hello &amp; Welcome"
334
+ field.value(:plain).must_equal "Hello & Welcome"
335
+ end
384
336
 
337
+ it "educate quotes" do
338
+ field_class = Class.new(S::Field::String)
339
+ field = field_class.new
340
+ field.value = %("John's first... example")
341
+ field.value(:html).must_equal "“John’s first… example”"
342
+ field.value(:plain).must_equal "“John’s first… example”"
385
343
  end
386
344
 
387
- context "Field value persistence" do
388
- setup do
389
- class ::PersistedField < Piece
390
- field :title, :default => "Magic"
391
- end
345
+ it "not process values coming from db" do
346
+ class ContentClass1 < Piece
392
347
  end
393
- teardown do
394
- Object.send(:remove_const, :PersistedField)
348
+ $transform = lambda { |value| "<#{value}>" }
349
+ ContentClass1.field :title do
350
+ def generate_html(value)
351
+ $transform[value]
352
+ end
395
353
  end
354
+ instance = ContentClass1.new
355
+ instance.fields.title = "Monkey"
356
+ instance.save
396
357
 
397
- should "work" do
398
- instance = ::PersistedField.new
399
- instance.fields.title.value = "Changed"
400
- instance.save
401
- id = instance.id
402
- instance = ::PersistedField[id]
403
- instance.fields.title.value.should == "Changed"
404
- end
358
+ $transform = lambda { |value| "*#{value}*" }
359
+ instance = ContentClass1[instance.id]
360
+ instance.fields.title.value.must_equal "<Monkey>"
361
+ Object.send(:remove_const, :ContentClass1)
405
362
  end
363
+ end
406
364
 
407
- context "Value version" do
408
- setup do
409
- class ::PersistedField < Piece
410
- field :title, :default => "Magic"
365
+ describe "field instances" do
366
+ before do
367
+ ::CC = Class.new(Piece) do
368
+ field :title, :default => "Magic" do
369
+ def generate_html(value)
370
+ "*#{value}*"
371
+ end
411
372
  end
412
373
  end
413
- teardown do
414
- Object.send(:remove_const, :PersistedField)
415
- end
374
+ @instance = CC.new
375
+ end
416
376
 
417
- should "be increased after a change" do
418
- instance = ::PersistedField.new
419
- instance.fields.title.version.should == 0
420
- instance.fields.title.value = "Changed"
421
- instance.save
422
- instance = ::PersistedField[instance.id]
423
- instance.fields.title.value.should == "Changed"
424
- instance.fields.title.version.should == 1
425
- end
377
+ after do
378
+ Object.send(:remove_const, :CC)
379
+ end
426
380
 
427
- should "not be increased if the value remains constant" do
428
- instance = ::PersistedField.new
429
- instance.fields.title.version.should == 0
430
- instance.fields.title.value = "Changed"
431
- instance.save
432
- instance = ::PersistedField[instance.id]
433
- instance.fields.title.value = "Changed"
434
- instance.save
435
- instance = ::PersistedField[instance.id]
436
- instance.fields.title.value.should == "Changed"
437
- instance.fields.title.version.should == 1
438
- instance.fields.title.value = "Changed!"
439
- instance.save
440
- instance = ::PersistedField[instance.id]
441
- instance.fields.title.version.should == 2
442
- end
381
+ it "have a link back to their owner" do
382
+ @instance.fields.title.owner.must_equal @instance
443
383
  end
444
384
 
445
- context "Available output formats" do
446
- should "include HTML & PDF and default to default value" do
447
- f = S::Field::Base.new
448
- f.value = "Value"
449
- f.to_html.should == "Value"
450
- f.to_pdf.should == "Value"
451
- end
385
+ it "be created with the right default value" do
386
+ f = @instance.fields.title
387
+ f.value.must_equal "*Magic*"
452
388
  end
453
389
 
454
- context "Markdown fields" do
455
- setup do
456
- class ::MarkdownContent < Piece
457
- field :text1, :markdown
458
- field :text2, :text
459
- end
460
- @instance = MarkdownContent.new
461
- end
462
- teardown do
463
- Object.send(:remove_const, :MarkdownContent)
464
- end
390
+ it "eval blocks from prototype defn" do
391
+ f = @instance.fields.title
392
+ f.value = "Boo"
393
+ f.value.must_equal "*Boo*"
394
+ f.unprocessed_value.must_equal "Boo"
395
+ end
465
396
 
466
- should "be available as the :markdown type" do
467
- assert MarkdownContent.field_prototypes[:text1].field_class < Spontaneous::Field::Markdown
468
- end
469
- should "be available as the :text type" do
470
- assert MarkdownContent.field_prototypes[:text2].field_class < Spontaneous::Field::Markdown
471
- end
397
+ it "have a reference to their prototype" do
398
+ f = @instance.fields.title
399
+ f.prototype.must_equal CC.field_prototypes[:title]
400
+ end
472
401
 
473
- should "process input into HTML" do
474
- @instance.text1 = "*Hello* **World**"
475
- @instance.text1.value.should == "<p><em>Hello</em> <strong>World</strong></p>\n"
476
- end
402
+ it "return the item which isnt empty when using the / method" do
403
+ a = CC.new(:title => "")
404
+ b = CC.new(:title => "b")
405
+ (a.title / b.title).must_equal b.title
406
+ a.title = "a"
407
+ (a.title / b.title).must_equal a.title
408
+ end
477
409
 
478
- should "use more sensible linebreaks" do
479
- @instance.text1 = "With\nLinebreak"
480
- @instance.text1.value.should == "<p>With<br />\nLinebreak</p>\n"
481
- @instance.text2 = "With \nLinebreak"
482
- @instance.text2.value.should == "<p>With<br />\nLinebreak</p>\n"
483
- end
410
+ it "return the item which isnt empty when using the | method" do
411
+ a = CC.new(:title => "")
412
+ b = CC.new(:title => "b")
413
+ (a.title | b.title).must_equal b.title
414
+ a.title = "a"
415
+ (a.title | b.title).must_equal a.title
484
416
  end
485
417
 
486
- context "Editor classes" do
487
- should "be defined in base types" do
488
- base_class = Spontaneous::Field::Image
489
- base_class.editor_class.should == "Spontaneous.Field.Image"
490
- base_class = Spontaneous::Field::Date
491
- base_class.editor_class.should == "Spontaneous.Field.Date"
492
- base_class = Spontaneous::Field::Markdown
493
- base_class.editor_class.should == "Spontaneous.Field.Markdown"
494
- base_class = Spontaneous::Field::String
495
- base_class.editor_class.should == "Spontaneous.Field.String"
418
+ it "return the item which isnt empty when using the or method" do
419
+ a = CC.new(:title => "")
420
+ b = CC.new(:title => "b")
421
+ (a.title.or(b.title)).must_equal b.title
422
+ a.title = "a"
423
+ (a.title.or(b.title)).must_equal a.title
424
+ end
425
+
426
+ end
427
+
428
+ describe "Field value persistence" do
429
+ before do
430
+ class ::PersistedField < Piece
431
+ field :title, :default => "Magic"
496
432
  end
433
+ end
434
+ after do
435
+ Object.send(:remove_const, :PersistedField)
436
+ end
497
437
 
498
- should "be inherited in subclasses" do
499
- base_class = Spontaneous::Field::Image
500
- @field_class = Class.new(base_class)
501
- @field_class.stubs(:name).returns("CustomField")
502
- @field_class.editor_class.should == base_class.editor_class
503
- @field_class2 = Class.new(@field_class)
504
- @field_class2.stubs(:name).returns("CustomField2")
505
- @field_class2.editor_class.should == base_class.editor_class
438
+ it "work" do
439
+ instance = ::PersistedField.new
440
+ instance.fields.title.value = "Changed"
441
+ instance.save
442
+ id = instance.id
443
+ instance = ::PersistedField[id]
444
+ instance.fields.title.value.must_equal "Changed"
445
+ end
446
+ end
447
+
448
+ describe "Value version" do
449
+ before do
450
+ class ::PersistedField < Piece
451
+ field :title, :default => "Magic"
506
452
  end
507
- should "correctly defined by field prototypes" do
508
- base_class = Spontaneous::Field::Image
509
- class ::CustomField < Spontaneous::Field::Image
510
- self.register(:custom)
511
- end
453
+ end
454
+ after do
455
+ Object.send(:remove_const, :PersistedField)
456
+ end
512
457
 
513
- class ::CustomContent < ::Piece
514
- field :custom
515
- end
516
- assert CustomContent.fields.custom.instance_class < CustomField
458
+ it "be increased after a change" do
459
+ instance = ::PersistedField.new
460
+ instance.fields.title.version.must_equal 0
461
+ instance.fields.title.value = "Changed"
462
+ instance.save
463
+ instance = ::PersistedField[instance.id]
464
+ instance.fields.title.value.must_equal "Changed"
465
+ instance.fields.title.version.must_equal 1
466
+ end
517
467
 
518
- CustomContent.fields.custom.instance_class.editor_class.should == Spontaneous::Field::Image.editor_class
468
+ it "not be increased if the value remains constant" do
469
+ instance = ::PersistedField.new
470
+ instance.fields.title.version.must_equal 0
471
+ instance.fields.title.value = "Changed"
472
+ instance.save
473
+ instance = ::PersistedField[instance.id]
474
+ instance.fields.title.value = "Changed"
475
+ instance.save
476
+ instance = ::PersistedField[instance.id]
477
+ instance.fields.title.value.must_equal "Changed"
478
+ instance.fields.title.version.must_equal 1
479
+ instance.fields.title.value = "Changed!"
480
+ instance.save
481
+ instance = ::PersistedField[instance.id]
482
+ instance.fields.title.version.must_equal 2
483
+ end
484
+ end
485
+
486
+ describe "Available output formats" do
487
+ it "include HTML & PDF and default to default value" do
488
+ f = S::Field::Base.new
489
+ f.value = "Value"
490
+ f.to_html.must_equal "Value"
491
+ f.to_pdf.must_equal "Value"
492
+ end
493
+ end
519
494
 
520
- Object.send(:remove_const, :CustomContent)
521
- Object.send(:remove_const, :CustomField)
495
+ describe "String Fields" do
496
+ before do
497
+ @content_class = Class.new(::Piece) do
498
+ field :title, :string
522
499
  end
500
+ @instance = @content_class.new
501
+ @field = @instance.title
523
502
  end
524
503
 
525
- context "Field versions" do
526
- setup do
527
- @user = Spontaneous::Permissions::User.create(:email => "user@example.com", :login => "user", :name => "user", :password => "rootpass")
528
- @user.reload
504
+ it "should escape ampersands for the html format" do
505
+ @field.value = "This & That"
506
+ @field.value(:html).must_equal "This &amp; That"
507
+ end
508
+ end
529
509
 
530
- class ::Piece
531
- field :title
532
- end
533
- # @content_class.stubs(:name).returns("ContentClass")
534
- @instance = ::Piece.create
510
+ describe "Markdown fields" do
511
+ before do
512
+ class ::MarkdownContent < Piece
513
+ field :text1, :markdown
514
+ field :text2, :text
535
515
  end
516
+ @instance = MarkdownContent.new
517
+ end
518
+ after do
519
+ Object.send(:remove_const, :MarkdownContent)
520
+ end
536
521
 
537
- teardown do
538
- # Object.send(:remove_const, :Piece) rescue nil
539
- Spontaneous::Permissions::User.delete
540
- ::Content.delete
541
- S::Field::FieldVersion.delete
542
- end
522
+ it "be available as the :markdown type" do
523
+ assert MarkdownContent.field_prototypes[:text1].field_class < Spontaneous::Field::Markdown
524
+ end
525
+ it "be available as the :text type" do
526
+ assert MarkdownContent.field_prototypes[:text2].field_class < Spontaneous::Field::Markdown
527
+ end
543
528
 
544
- should "start out as empty" do
545
- assert @instance.title.versions.empty?, "Field version list should be empty"
546
- end
529
+ it "process input into HTML" do
530
+ @instance.text1 = "*Hello* **World**"
531
+ @instance.text1.value.must_equal "<p><em>Hello</em> <strong>World</strong></p>\n"
532
+ end
547
533
 
548
- should "be created every time a field is modified" do
549
- @instance.title.value = "one"
550
- @instance.save.reload
551
- v = @instance.title.versions
552
- v.count.should == 1
553
- end
534
+ it "use more sensible linebreaks" do
535
+ @instance.text1 = "With\nLinebreak"
536
+ @instance.text1.value.must_equal "<p>With<br />\nLinebreak</p>\n"
537
+ @instance.text2 = "With \nLinebreak"
538
+ @instance.text2.value.must_equal "<p>With<br />\nLinebreak</p>\n"
539
+ end
540
+ end
554
541
 
555
- should "have a creation date" do
556
- now = Time.now + 1000
557
- stub_time(now)
558
- @instance.title.value = "one"
559
- @instance.save.reload
560
- @instance.reload
561
- vv = @instance.title.versions
562
- v = vv.first
563
- v.created_at.to_i.should == now.to_i
564
- end
542
+ describe "Editor classes" do
543
+ it "be defined in base types" do
544
+ base_class = Spontaneous::Field::Image
545
+ base_class.editor_class.must_equal "Spontaneous.Field.Image"
546
+ base_class = Spontaneous::Field::Date
547
+ base_class.editor_class.must_equal "Spontaneous.Field.Date"
548
+ base_class = Spontaneous::Field::Markdown
549
+ base_class.editor_class.must_equal "Spontaneous.Field.Markdown"
550
+ base_class = Spontaneous::Field::String
551
+ base_class.editor_class.must_equal "Spontaneous.Field.String"
552
+ end
565
553
 
566
- should "save the previous value" do
567
- stub_time(@now)
568
- @instance.title.value = "one"
569
- @instance.save.reload
570
- vv = @instance.title.versions
571
- v = vv.first
572
- v.value.should == ""
573
- stub_time(@now+10)
574
- @instance.title.value = "two"
575
- @instance.save.reload
576
- vv = @instance.title.versions
577
- v = vv.first
578
- v.value.should == "one"
579
- stub_time(@now+20)
580
- @instance.title.value = "three"
581
- @instance.save.reload
582
- vv = @instance.title.versions
583
- v = vv.first
584
- v.value.should == "two"
554
+ it "be inherited in subclasses" do
555
+ base_class = Spontaneous::Field::Image
556
+ @field_class = Class.new(base_class)
557
+ @field_class.stubs(:name).returns("CustomField")
558
+ @field_class.editor_class.must_equal base_class.editor_class
559
+ @field_class2 = Class.new(@field_class)
560
+ @field_class2.stubs(:name).returns("CustomField2")
561
+ @field_class2.editor_class.must_equal base_class.editor_class
562
+ end
563
+ it "correctly defined by field prototypes" do
564
+ base_class = Spontaneous::Field::Image
565
+ class ::CustomField < Spontaneous::Field::Image
566
+ self.register(:custom)
585
567
  end
586
568
 
587
- should "keep a track of the version number" do
588
- stub_time(@now)
589
- @instance.title.value = "one"
590
- @instance.save.reload
591
- vv = @instance.title.versions
592
- v = vv.first
593
- v.version.should == 1
594
- stub_time(@now+10)
595
- @instance.title.value = "two"
596
- @instance.save.reload
597
- vv = @instance.title.versions
598
- vv.count.should == 2
599
- v = vv.first
600
- v.version.should == 2
569
+ class ::CustomContent < ::Piece
570
+ field :custom
601
571
  end
572
+ assert CustomContent.fields.custom.instance_class < CustomField
602
573
 
603
- should "remember the responsible editor" do
604
- @instance.current_editor = @user
605
- @instance.title.value = "one"
606
- @instance.save.reload
607
- vv = @instance.title.versions
608
- v = vv.first
609
- v.user.should == @user
610
- end
574
+ CustomContent.fields.custom.instance_class.editor_class.must_equal Spontaneous::Field::Image.editor_class
611
575
 
612
- should "have quick access to the last version" do
613
- stub_time(@now)
614
- @instance.title.value = "one"
615
- @instance.save.reload
616
- vv = @instance.title.versions
617
- v = vv.first
618
- v.value.should == ""
619
- stub_time(@now+10)
620
- @instance.title.value = "two"
621
- @instance.save.reload
622
- vv = @instance.title.versions
623
- v = vv.first
624
- v.value.should == "one"
625
- @instance.title.previous_version.value.should == "one"
626
- end
576
+ Object.send(:remove_const, :CustomContent)
577
+ Object.send(:remove_const, :CustomField)
627
578
  end
579
+ end
628
580
 
629
- context "String fields" do
630
- should "be aliased to the :title type" do
631
- @content_class = Class.new(::Piece) do
632
- field :title, default: "Right"
633
- field :something, :title
634
- end
635
- instance = @content_class.new
636
- assert instance.fields.title.class.ancestors.include?(Spontaneous::Field::String), ":title type should inherit from StringField"
637
- instance.title.value.should == "Right"
581
+ describe "Field versions" do
582
+ before do
583
+ @user = Spontaneous::Permissions::User.create(:email => "user@example.com", :login => "user", :name => "user", :password => "rootpass")
584
+ @user.reload
585
+
586
+ class ::Piece
587
+ field :title
638
588
  end
589
+ # @content_class.stubs(:name).returns("ContentClass")
590
+ @instance = ::Piece.create
639
591
  end
640
592
 
641
- context "WebVideo fields" do
642
- setup do
643
- @content_class = Class.new(::Piece) do
644
- field :video, :webvideo
645
- end
646
- @content_class.stubs(:name).returns("ContentClass")
647
- @instance = @content_class.new
648
- end
593
+ after do
594
+ # Object.send(:remove_const, :Piece) rescue nil
595
+ Spontaneous::Permissions::User.delete
596
+ ::Content.delete
597
+ S::Field::FieldVersion.delete
598
+ end
649
599
 
650
- should "have their own editor type" do
651
- @content_class.fields.video.export(nil)[:type].should == "Spontaneous.Field.WebVideo"
652
- @instance.video = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
653
- fields = @instance.export(nil)[:fields]
654
- fields[0][:processed_value].should == @instance.video.src
655
- end
600
+ it "start out as empty" do
601
+ assert @instance.title.versions.empty?, "Field version list should be empty"
602
+ end
603
+
604
+ it "be created every time a field is modified" do
605
+ @instance.title.value = "one"
606
+ @instance.save.reload
607
+ v = @instance.title.versions
608
+ v.count.must_equal 1
609
+ end
610
+
611
+ it "have a creation date" do
612
+ now = Time.now + 1000
613
+ stub_time(now)
614
+ @instance.title.value = "one"
615
+ @instance.save.reload
616
+ @instance.reload
617
+ vv = @instance.title.versions
618
+ v = vv.first
619
+ v.created_at.to_i.must_equal now.to_i
620
+ end
621
+
622
+ it "save the previous value" do
623
+ stub_time(@now)
624
+ @instance.title.value = "one"
625
+ @instance.save.reload
626
+ vv = @instance.title.versions
627
+ v = vv.first
628
+ v.value.must_equal ""
629
+ stub_time(@now+10)
630
+ @instance.title.value = "two"
631
+ @instance.save.reload
632
+ vv = @instance.title.versions
633
+ v = vv.first
634
+ v.value.must_equal "one"
635
+ stub_time(@now+20)
636
+ @instance.title.value = "three"
637
+ @instance.save.reload
638
+ vv = @instance.title.versions
639
+ v = vv.first
640
+ v.value.must_equal "two"
641
+ end
642
+
643
+ it "keep a track of the version number" do
644
+ stub_time(@now)
645
+ @instance.title.value = "one"
646
+ @instance.save.reload
647
+ vv = @instance.title.versions
648
+ v = vv.first
649
+ v.version.must_equal 1
650
+ stub_time(@now+10)
651
+ @instance.title.value = "two"
652
+ @instance.save.reload
653
+ vv = @instance.title.versions
654
+ vv.count.must_equal 2
655
+ v = vv.first
656
+ v.version.must_equal 2
657
+ end
658
+
659
+ it "remember the responsible editor" do
660
+ @instance.current_editor = @user
661
+ @instance.title.value = "one"
662
+ @instance.save.reload
663
+ vv = @instance.title.versions
664
+ v = vv.first
665
+ v.user.must_equal @user
666
+ end
667
+
668
+ it "have quick access to the last version" do
669
+ stub_time(@now)
670
+ @instance.title.value = "one"
671
+ @instance.save.reload
672
+ vv = @instance.title.versions
673
+ v = vv.first
674
+ v.value.must_equal ""
675
+ stub_time(@now+10)
676
+ @instance.title.value = "two"
677
+ @instance.save.reload
678
+ vv = @instance.title.versions
679
+ v = vv.first
680
+ v.value.must_equal "one"
681
+ @instance.title.previous_version.value.must_equal "one"
682
+ end
683
+ end
656
684
 
657
- should "recognise youtube URLs" do
658
- @instance.video = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
659
- @instance.video.value.should == "http://www.youtube.com/watch?v=_0jroAM_pO4&amp;feature=feedrec_grec_index"
660
- @instance.video.id.should == "_0jroAM_pO4"
661
- @instance.video.video_type.should == "youtube"
685
+ describe "String fields" do
686
+ it "be aliased to the :title type" do
687
+ @content_class = Class.new(::Piece) do
688
+ field :title, default: "Right"
689
+ field :something, :title
662
690
  end
691
+ instance = @content_class.new
692
+ assert instance.fields.title.class.ancestors.include?(Spontaneous::Field::String), ":title type should inherit from StringField"
693
+ instance.title.value.must_equal "Right"
694
+ end
695
+ end
663
696
 
664
- should "recognise Vimeo URLs" do
665
- @instance.video = "http://vimeo.com/31836285"
666
- @instance.video.value.should == "http://vimeo.com/31836285"
667
- @instance.video.id.should == "31836285"
668
- @instance.video.video_type.should == "vimeo"
697
+ describe "WebVideo fields" do
698
+ before do
699
+ @content_class = Class.new(::Piece) do
700
+ field :video, :webvideo
669
701
  end
702
+ @content_class.stubs(:name).returns("ContentClass")
703
+ @instance = @content_class.new
704
+ @field = @instance.video
705
+ end
670
706
 
671
- context "with player settings" do
672
- setup do
673
- @content_class.field :video2, :webvideo, :player => {
674
- :width => 680, :height => 384,
675
- :fullscreen => true, :autoplay => true, :loop => true,
676
- :showinfo => false,
677
- :youtube => { :theme => 'light', :hd => true, :controls => false },
678
- :vimeo => { :color => "ccc", :api => true }
679
- }
680
- @instance = @content_class.new
681
- @field = @instance.video2
682
- end
707
+ it "have their own editor type" do
708
+ @content_class.fields.video.export(nil)[:type].must_equal "Spontaneous.Field.WebVideo"
709
+ @instance.video = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
710
+ fields = @instance.export(nil)[:fields]
711
+ fields[0][:processed_value].must_equal @instance.video.src
712
+ end
683
713
 
684
- should "use the configuration in the youtube player HTML" do
685
- @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
686
- html = @field.render(:html)
687
- html.should =~ /^<iframe/
688
- html.should =~ %r{src="http://www\.youtube\.com/embed/_0jroAM_pO4}
689
- html.should =~ /width="680"/
690
- html.should =~ /height="384"/
691
- html.should =~ /theme=light/
692
- html.should =~ /hd=1/
693
- html.should =~ /fs=1/
694
- html.should =~ /controls=0/
695
- html.should =~ /autoplay=1/
696
- html.should =~ /showinfo=0/
697
- html.should =~ /showsearch=0/
698
- @field.render(:html, :youtube => {:showsearch => 1}).should =~ /showsearch=1/
699
- @field.render(:html, :youtube => {:theme => 'dark'}).should =~ /theme=dark/
700
- @field.render(:html, :width => 100).should =~ /width="100"/
701
- @field.render(:html, :loop => true).should =~ /loop=1/
702
- end
714
+ it "recognise youtube URLs" do
715
+ @instance.video = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
716
+ @instance.video.value.must_equal "http://www.youtube.com/watch?v=_0jroAM_pO4&amp;feature=feedrec_grec_index"
717
+ @instance.video.video_id.must_equal "_0jroAM_pO4"
718
+ @instance.video.provider_id.must_equal "youtube"
719
+ end
703
720
 
704
- should "use the configuration in the Vimeo player HTML" do
705
- @field.value = "http://vimeo.com/31836285"
706
- html = @field.render(:html)
707
- html.should =~ /^<iframe/
708
- html.should =~ %r{src="http://player\.vimeo\.com/video/31836285}
709
- html.should =~ /width="680"/
710
- html.should =~ /height="384"/
711
- html.should =~ /color=ccc/
712
- html.should =~ /webkitAllowFullScreen="yes"/
713
- html.should =~ /allowFullScreen="yes"/
714
- html.should =~ /autoplay=1/
715
- html.should =~ /title=0/
716
- html.should =~ /byline=0/
717
- html.should =~ /portrait=0/
718
- html.should =~ /api=1/
719
- @field.render(:html, :vimeo => {:color => 'f0abcd'}).should =~ /color=f0abcd/
720
- @field.render(:html, :loop => true).should =~ /loop=1/
721
- @field.render(:html, :title => true).should =~ /title=1/
722
- @field.render(:html, :title => true).should =~ /byline=0/
723
- end
721
+ it "recognise Vimeo URLs" do
722
+ @instance.video = "http://vimeo.com/31836285"
723
+ @instance.video.value.must_equal "http://vimeo.com/31836285"
724
+ @instance.video.video_id.must_equal "31836285"
725
+ @instance.video.provider_id.must_equal "vimeo"
726
+ end
724
727
 
725
- should "provide a version of the YouTube player params in JSON/JS format" do
726
- @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
727
- json = Spontaneous::JSON.parse(@field.render(:json))
728
- json[:"tagname"].should == "iframe"
729
- json[:"tag"].should == "<iframe/>"
730
- attr = json[:"attr"]
731
- attr.must_be_instance_of(Hash)
732
- attr[:"src"].should =~ %r{^http://www\.youtube\.com/embed/_0jroAM_pO4}
733
- attr[:"src"].should =~ /theme=light/
734
- attr[:"src"].should =~ /hd=1/
735
- attr[:"src"].should =~ /fs=1/
736
- attr[:"src"].should =~ /controls=0/
737
- attr[:"src"].should =~ /autoplay=1/
738
- attr[:"src"].should =~ /showinfo=0/
739
- attr[:"src"].should =~ /showsearch=0/
740
- attr[:"width"].should == 680
741
- attr[:"height"].should == 384
742
- attr[:"frameborder"].should == "0"
743
- attr[:"type"].should == "text/html"
744
- end
728
+ it "recognise Vine URLs" do
729
+ @instance.video = "https://vine.co/v/brI7pTPb3qU"
730
+ @instance.video.value.must_equal "https://vine.co/v/brI7pTPb3qU"
731
+ @instance.video.video_id.must_equal "brI7pTPb3qU"
732
+ @instance.video.provider_id.must_equal "vine"
733
+ end
745
734
 
746
- should "provide a version of the Vimeo player params in JSON/JS format" do
747
- @field.value = "http://vimeo.com/31836285"
748
- json = Spontaneous::JSON.parse(@field.render(:json))
749
- json[:"tagname"].should == "iframe"
750
- json[:"tag"].should == "<iframe/>"
751
- attr = json[:"attr"]
752
- attr.must_be_instance_of(Hash)
753
- attr[:"src"].should =~ /color=ccc/
754
- attr[:"src"].should =~ /autoplay=1/
755
- attr[:"src"].should =~ /title=0/
756
- attr[:"src"].should =~ /byline=0/
757
- attr[:"src"].should =~ /portrait=0/
758
- attr[:"src"].should =~ /api=1/
759
- attr[:"webkitAllowFullScreen"].should == "yes"
760
- attr[:"allowFullScreen"].should == "yes"
761
- attr[:"width"].should == 680
762
- attr[:"height"].should == 384
763
- attr[:"frameborder"].should == "0"
764
- attr[:"type"].should == "text/html"
765
- end
735
+ it "silently handles unknown providers" do
736
+ @instance.video = "https://idontdovideo.com/video?id=brI7pTPb3qU"
737
+ @instance.video.value.must_equal "https://idontdovideo.com/video?id=brI7pTPb3qU"
738
+ @instance.video.video_id.must_equal "https://idontdovideo.com/video?id=brI7pTPb3qU"
739
+ @instance.video.provider_id.must_equal nil
740
+ end
766
741
 
767
742
 
768
- should "use the YouTube api to extract video metadata" do
769
- 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
743
+ it "use the YouTube api to extract video metadata" do
744
+ 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
770
745
 
771
- response_xml_file = File.expand_path("../../fixtures/fields/youtube_api_response.xml", __FILE__)
772
- connection = mock()
773
- @field.expects(:open).with("http://gdata.youtube.com/feeds/api/videos/_0jroAM_pO4?v=2").returns(connection)
774
- doc = Nokogiri::XML(File.open(response_xml_file))
775
- Nokogiri.expects(:XML).with(connection).returns(doc)
776
- @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4"
777
- @field.values.should == youtube_info.merge(:id => "_0jroAM_pO4", :type => "youtube", :html => "http://www.youtube.com/watch?v=_0jroAM_pO4")
778
- end
746
+ response_xml_file = File.expand_path("../../fixtures/fields/youtube_api_response.xml", __FILE__)
747
+ connection = mock()
748
+ Spontaneous::Field::WebVideo::YouTube.any_instance.expects(:open).with("http://gdata.youtube.com/feeds/api/videos/_0jroAM_pO4?v=2").returns(connection)
749
+ doc = Nokogiri::XML(File.open(response_xml_file))
750
+ Nokogiri.expects(:XML).with(connection).returns(doc)
751
+ @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4"
752
+ @field.values.must_equal youtube_info.merge(:video_id => "_0jroAM_pO4", :provider => "youtube", :html => "http://www.youtube.com/watch?v=_0jroAM_pO4")
753
+ end
779
754
 
780
- should "use the Vimeo api to extract video metadata" do
781
- 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"=>640, "height"=>360, "tags"=>"neon indian, analog, korg, moog, theremin, micropiano, microkorg, kaossilator, kaossilator pro", "embed_privacy"=>"anywhere"}.symbolize_keys
782
- connection = mock()
783
- connection.expects(:read).returns(Spontaneous.encode_json([vimeo_info]))
784
- @field.expects(:open).with("http://vimeo.com/api/v2/video/29987529.json").returns(connection)
785
- @field.value = "http://vimeo.com/29987529"
786
- @field.values.should == vimeo_info.merge(:id => "29987529", :type => "vimeo", :html => "http://vimeo.com/29987529")
787
- end
788
- end
755
+ it "use the Vimeo api to extract video metadata" do
756
+ 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
789
757
 
758
+ connection = mock()
759
+ connection.expects(:read).returns(Spontaneous.encode_json([vimeo_info]))
760
+ Spontaneous::Field::WebVideo::Vimeo.any_instance.expects(:open).with("http://vimeo.com/api/v2/video/29987529.json").returns(connection)
761
+ @field.value = "http://vimeo.com/29987529"
762
+ @field.values.must_equal vimeo_info.merge(:video_id => "29987529", :provider => "vimeo", :html => "http://vimeo.com/29987529")
790
763
  end
791
764
 
792
- context "Location fields" do
793
- setup do
794
- @content_class = Class.new(::Piece) do
795
- field :location
796
- end
797
- @content_class.stubs(:name).returns("ContentClass")
765
+ describe "with player settings" do
766
+ before do
767
+ @content_class.field :video2, :webvideo, :player => {
768
+ :width => 680, :height => 384,
769
+ :fullscreen => true, :autoplay => true, :loop => true,
770
+ :showinfo => false,
771
+ :youtube => { :theme => 'light', :hd => true, :controls => false },
772
+ :vimeo => { :color => "ccc", :api => true }
773
+ }
798
774
  @instance = @content_class.new
799
- @field = @instance.location
775
+ @field = @instance.video2
776
+ end
777
+
778
+ it "use the configuration in the youtube player HTML" do
779
+ @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
780
+ html = @field.render(:html)
781
+ html.must_match /^<iframe/
782
+ html.must_match %r{src="http://www\.youtube\.com/embed/_0jroAM_pO4}
783
+ html.must_match /width="680"/
784
+ html.must_match /height="384"/
785
+ html.must_match /theme=light/
786
+ html.must_match /hd=1/
787
+ html.must_match /fs=1/
788
+ html.must_match /controls=0/
789
+ html.must_match /autoplay=1/
790
+ html.must_match /showinfo=0/
791
+ html.must_match /showsearch=0/
792
+ @field.render(:html, :youtube => {:showsearch => 1}).must_match /showsearch=1/
793
+ @field.render(:html, :youtube => {:theme => 'dark'}).must_match /theme=dark/
794
+ @field.render(:html, :width => 100).must_match /width="100"/
795
+ @field.render(:html, :loop => true).must_match /loop=1/
796
+ end
797
+
798
+ it "use the configuration in the Vimeo player HTML" do
799
+ @field.value = "http://vimeo.com/31836285"
800
+ html = @field.render(:html)
801
+ html.must_match /^<iframe/
802
+ html.must_match %r{src="http://player\.vimeo\.com/video/31836285}
803
+ html.must_match /width="680"/
804
+ html.must_match /height="384"/
805
+ html.must_match /color=ccc/
806
+ html.must_match /webkitAllowFullScreen="yes"/
807
+ html.must_match /allowFullScreen="yes"/
808
+ html.must_match /autoplay=1/
809
+ html.must_match /title=0/
810
+ html.must_match /byline=0/
811
+ html.must_match /portrait=0/
812
+ html.must_match /api=1/
813
+ @field.render(:html, :vimeo => {:color => 'f0abcd'}).must_match /color=f0abcd/
814
+ @field.render(:html, :loop => true).must_match /loop=1/
815
+ @field.render(:html, :title => true).must_match /title=1/
816
+ @field.render(:html, :title => true).must_match /byline=0/
817
+ end
818
+
819
+ it "provide a version of the YouTube player params in JSON/JS format" do
820
+ @field.value = "http://www.youtube.com/watch?v=_0jroAM_pO4&feature=feedrec_grec_index"
821
+ json = Spontaneous::JSON.parse(@field.render(:json))
822
+ json[:"tagname"].must_equal "iframe"
823
+ json[:"tag"].must_equal "<iframe/>"
824
+ attr = json[:"attr"]
825
+ attr.must_be_instance_of(Hash)
826
+ attr[:"src"].must_match %r{^http://www\.youtube\.com/embed/_0jroAM_pO4}
827
+ attr[:"src"].must_match /theme=light/
828
+ attr[:"src"].must_match /hd=1/
829
+ attr[:"src"].must_match /fs=1/
830
+ attr[:"src"].must_match /controls=0/
831
+ attr[:"src"].must_match /autoplay=1/
832
+ attr[:"src"].must_match /showinfo=0/
833
+ attr[:"src"].must_match /showsearch=0/
834
+ attr[:"width"].must_equal 680
835
+ attr[:"height"].must_equal 384
836
+ attr[:"frameborder"].must_equal "0"
837
+ attr[:"type"].must_equal "text/html"
838
+ end
839
+
840
+ it "provide a version of the Vimeo player params in JSON/JS format" do
841
+ @field.value = "http://vimeo.com/31836285"
842
+ json = Spontaneous::JSON.parse(@field.render(:json))
843
+ json[:"tagname"].must_equal "iframe"
844
+ json[:"tag"].must_equal "<iframe/>"
845
+ attr = json[:"attr"]
846
+ attr.must_be_instance_of(Hash)
847
+ attr[:"src"].must_match /color=ccc/
848
+ attr[:"src"].must_match /autoplay=1/
849
+ attr[:"src"].must_match /title=0/
850
+ attr[:"src"].must_match /byline=0/
851
+ attr[:"src"].must_match /portrait=0/
852
+ attr[:"src"].must_match /api=1/
853
+ attr[:"webkitAllowFullScreen"].must_equal "yes"
854
+ attr[:"allowFullScreen"].must_equal "yes"
855
+ attr[:"width"].must_equal 680
856
+ attr[:"height"].must_equal 384
857
+ attr[:"frameborder"].must_equal "0"
858
+ attr[:"type"].must_equal "text/html"
859
+ end
860
+
861
+
862
+ it "can properly embed a Vine video" do
863
+ @field.value = "https://vine.co/v/brI7pTPb3qU"
864
+ embed = @field.render(:html)
865
+ embed.must_match %r(iframe)
866
+ embed.must_match %r(src=["']https://vine\.co/v/brI7pTPb3qU/card["'])
867
+ # Vine videos are square
868
+ embed.must_match %r(width=["']680["'])
869
+ embed.must_match %r(height=["']680["'])
870
+ end
871
+
872
+ it "falls back to a simple iframe for unknown providers xxx" do
873
+ @field.value = "https://unknownprovider.net/xx/brI7pTPb3qU"
874
+ embed = @field.render(:html)
875
+ embed.must_match %r(iframe)
876
+ embed.must_match %r(src=["']https://unknownprovider.net/xx/brI7pTPb3qU["'])
877
+ embed.must_match %r(width=["']680["'])
878
+ embed.must_match %r(height=["']384["'])
800
879
  end
880
+ end
801
881
 
802
- should "use a standard string editor" do
803
- @content_class.fields.location.export(nil)[:type].should == "Spontaneous.Field.String"
804
- end
882
+ end
805
883
 
806
- should "successfullt geolocate an address" do
807
- @field.value = "Cambridge, England"
808
- @field.value(:lat).should == 52.2053370
809
- @field.value(:lng).should == 0.1218170
810
- @field.value(:country).should == "United Kingdom"
811
- @field.value(:formatted_address).should == "Cambridge, UK"
884
+ describe "HTML fields" do
885
+ before do
886
+ @content_class = Class.new(::Piece) do
887
+ field :raw, :html
888
+ end
889
+ @content_class.stubs(:name).returns("ContentClass")
890
+ @instance = @content_class.new
891
+ @field = @instance.raw
892
+ end
812
893
 
813
- @field.latitude.should == 52.2053370
814
- @field.longitude.should == 0.1218170
815
- @field.lat.should == 52.2053370
816
- @field.lng.should == 0.1218170
894
+ it "does no escaping of input" do
895
+ @field.value = "<script>\n</script>"
896
+ @field.value(:html).must_equal "<script>\n</script>"
897
+ end
898
+ end
817
899
 
818
- @field.country.should == "United Kingdom"
819
- @field.formatted_address.should == "Cambridge, UK"
900
+ describe "Location fields" do
901
+ before do
902
+ @content_class = Class.new(::Piece) do
903
+ field :location
820
904
  end
905
+ @content_class.stubs(:name).returns("ContentClass")
906
+ @instance = @content_class.new
907
+ @field = @instance.location
821
908
  end
822
909
 
823
- context "Option fields" do
824
- setup do
825
- @content_class = Class.new(::Piece) do
826
- field :options, :select, :options => [
827
- ["a", "Value A"],
828
- ["b", "Value B"],
829
- ["c", "Value C"]
830
- ]
831
- end
832
- @content_class.stubs(:name).returns("ContentClass")
833
- @instance = @content_class.new
834
- @field = @instance.options
835
- end
910
+ it "use a standard string editor" do
911
+ @content_class.fields.location.export(nil)[:type].must_equal "Spontaneous.Field.String"
912
+ end
836
913
 
837
- should "use a specific editor class" do
838
- @content_class.fields.options.export(nil)[:type].should == "Spontaneous.Field.Select"
839
- end
914
+ it "successfullt geolocate an address" do
915
+ @field.value = "Cambridge, England"
916
+ @field.value(:lat).must_equal 52.2053370
917
+ @field.value(:lng).must_equal 0.1218170
918
+ @field.value(:country).must_equal "United Kingdom"
919
+ @field.value(:formatted_address).must_equal "Cambridge, UK"
840
920
 
841
- should "select the options class for fields named options" do
842
- @content_class.field :type, :select, :options => [["a", "A"]]
843
- assert @content_class.fields.options.instance_class.ancestors.include?(Spontaneous::Field::Select)
844
- end
921
+ @field.latitude.must_equal 52.2053370
922
+ @field.longitude.must_equal 0.1218170
923
+ @field.lat.must_equal 52.2053370
924
+ @field.lng.must_equal 0.1218170
845
925
 
846
- should "accept a list of strings as options" do
847
- @content_class.field :type, :select, :options => ["a", "b"]
848
- @instance = @content_class.new
849
- @instance.type.option_list.should == [["a", "a"], ["b", "b"]]
850
- end
926
+ @field.country.must_equal "United Kingdom"
927
+ @field.formatted_address.must_equal "Cambridge, UK"
928
+ end
929
+ end
851
930
 
852
- should "accept a json string as a value and convert it properly" do
853
- @field.value = %(["a", "Value A"])
854
- @field.value.should == "a"
855
- @field.value(:label).should == "Value A"
856
- @field.label.should == "Value A"
857
- @field.unprocessed_value.should == %(["a", "Value A"])
931
+ describe "Option fields" do
932
+ before do
933
+ @content_class = Class.new(::Piece) do
934
+ field :options, :select, :options => [
935
+ ["a", "Value A"],
936
+ ["b", "Value B"],
937
+ ["c", "Value C"]
938
+ ]
858
939
  end
940
+ @content_class.stubs(:name).returns("ContentClass")
941
+ @instance = @content_class.new
942
+ @field = @instance.options
859
943
  end
860
944
 
861
- context "File fields" do
862
- setup do
863
- @content_class = Class.new(::Piece)
864
- @prototype = @content_class.field :file
865
- @content_class.stubs(:name).returns("ContentClass")
866
- @instance = @content_class.create
867
- @field = @instance.file
868
- end
945
+ it "use a specific editor class" do
946
+ @content_class.fields.options.export(nil)[:type].must_equal "Spontaneous.Field.Select"
947
+ end
869
948
 
870
- should "have a distinct editor class" do
871
- @prototype.instance_class.editor_class.should == "Spontaneous.Field.File"
872
- end
949
+ it "select the options class for fields named options" do
950
+ @content_class.field :type, :select, :options => [["a", "A"]]
951
+ assert @content_class.fields.options.instance_class.ancestors.include?(Spontaneous::Field::Select)
952
+ end
873
953
 
874
- should "adopt any field called 'file'" do
875
- assert @field.is_a?(Spontaneous::Field::File), "Field should be an instance of FileField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
876
- end
954
+ it "accept a list of strings as options" do
955
+ @content_class.field :type, :select, :options => ["a", "b"]
956
+ @instance = @content_class.new
957
+ @instance.type.option_list.must_equal [["a", "a"], ["b", "b"]]
958
+ end
877
959
 
878
- should "copy files to the media folder" do
879
- path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
880
- assert File.exists?(path), "Test file #{path} does not exist"
881
- File.open(path, 'rb') do |file|
882
- @field.value = {
883
- :tempfile => file,
884
- :type => "application/pdf",
885
- :filename => "vimlogo.pdf"
886
- }
887
- end
888
- url = @field.value
889
- path = File.join File.dirname(Spontaneous.media_dir), url
890
- assert File.exist?(path), "Media file should have been copied into place"
891
- end
960
+ it "accept a json string as a value and convert it properly" do
961
+ @field.value = %(["a", "Value A"])
962
+ @field.value.must_equal "a"
963
+ @field.value(:label).must_equal "Value A"
964
+ @field.label.must_equal "Value A"
965
+ @field.unprocessed_value.must_equal %(["a", "Value A"])
966
+ end
967
+ end
892
968
 
893
- should "generate the requisite file metadata" do
894
- path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
895
- assert File.exists?(path), "Test file #{path} does not exist"
896
- File.open(path, 'rb') do |file|
897
- @field.value = {
898
- :tempfile => file,
899
- :type => "application/pdf",
900
- :filename => "vimlogo.pdf"
901
- }
902
- end
903
- @field.value(:html).should =~ %r{/media/.+/vimlogo.pdf$}
904
- @field.value.should =~ %r{/media/.+/vimlogo.pdf$}
905
- @field.path.should == @field.value
906
- @field.value(:filesize).should == 2254
907
- @field.filesize.should == 2254
908
- @field.value(:filename).should == "vimlogo.pdf"
909
- @field.filename.should == "vimlogo.pdf"
969
+ describe "File fields" do
970
+ before do
971
+ @content_class = Class.new(::Piece)
972
+ @prototype = @content_class.field :file
973
+ @content_class.stubs(:name).returns("ContentClass")
974
+ @instance = @content_class.create
975
+ @field = @instance.file
976
+ end
977
+
978
+ it "have a distinct editor class" do
979
+ @prototype.instance_class.editor_class.must_equal "Spontaneous.Field.File"
980
+ end
981
+
982
+ it "adopt any field called 'file'" do
983
+ assert @field.is_a?(Spontaneous::Field::File), "Field should be an instance of FileField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
984
+ end
985
+
986
+ it "copy files to the media folder" do
987
+ path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
988
+ assert File.exists?(path), "Test file #{path} does not exist"
989
+ File.open(path, 'rb') do |file|
990
+ @field.value = {
991
+ :tempfile => file,
992
+ :type => "application/pdf",
993
+ :filename => "vimlogo.pdf"
994
+ }
910
995
  end
996
+ url = @field.value
997
+ path = File.join File.dirname(Spontaneous.media_dir), url
998
+ assert File.exist?(path), "Media file should have been copied into place"
999
+ end
911
1000
 
912
- should "just accept the given value if passed a path to a non-existant file" do
913
- @field.value = "/images/nosuchfile.rtf"
914
- @field.value.should == "/images/nosuchfile.rtf"
915
- @field.filename.should == "nosuchfile.rtf"
916
- @field.filesize.should == 0
1001
+ it "generate the requisite file metadata" do
1002
+ path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
1003
+ assert File.exists?(path), "Test file #{path} does not exist"
1004
+ File.open(path, 'rb') do |file|
1005
+ @field.value = {
1006
+ :tempfile => file,
1007
+ :type => "application/pdf",
1008
+ :filename => "vimlogo.pdf"
1009
+ }
917
1010
  end
1011
+ @field.value(:html).must_match %r{/media/.+/vimlogo.pdf$}
1012
+ @field.value.must_match %r{/media/.+/vimlogo.pdf$}
1013
+ @field.path.must_equal @field.value
1014
+ @field.value(:filesize).must_equal 2254
1015
+ @field.filesize.must_equal 2254
1016
+ @field.value(:filename).must_equal "vimlogo.pdf"
1017
+ @field.filename.must_equal "vimlogo.pdf"
1018
+ end
1019
+
1020
+ it "just accept the given value if passed a path to a non-existant file" do
1021
+ @field.value = "/images/nosuchfile.rtf"
1022
+ @field.value.must_equal "/images/nosuchfile.rtf"
1023
+ @field.filename.must_equal "nosuchfile.rtf"
1024
+ @field.filesize.must_equal 0
1025
+ end
918
1026
 
919
- should "copy the given file if passed a path to an existing file" do
1027
+ it "copy the given file if passed a path to an existing file" do
1028
+ path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
1029
+ @field.value = path
1030
+ @field.value.must_match %r{/media/.+/vimlogo.pdf$}
1031
+ @field.filename.must_equal "vimlogo.pdf"
1032
+ @field.filesize.must_equal 2254
1033
+ end
1034
+
1035
+ describe "with cloud storage" do
1036
+ before do
1037
+ ::Fog.mock!
1038
+ @aws_credentials = {
1039
+ :provider=>"AWS",
1040
+ :aws_secret_access_key=>"SECRET_ACCESS_KEY",
1041
+ :aws_access_key_id=>"ACCESS_KEY_ID"
1042
+ }
1043
+ @storage = S::Storage::Cloud.new(@aws_credentials, "media.example.com")
1044
+ @site.expects(:storage).returns(@storage)
1045
+ end
1046
+
1047
+ it "sets the content-disposition header if defined as an 'attachment'" do
1048
+ prototype = @content_class.field :attachment, :file, attachment: true
1049
+ field = @instance.attachment
920
1050
  path = File.expand_path("../../fixtures/images/vimlogo.pdf", __FILE__)
921
- @field.value = path
922
- @field.value.should =~ %r{/media/.+/vimlogo.pdf$}
923
- @field.filename.should == "vimlogo.pdf"
924
- @field.filesize.should == 2254
1051
+ @storage.expects(:copy).with(path, is_a(Array), { content_type: "application/pdf", content_disposition: 'attachment; filename=vimlogo.pdf'})
1052
+ field.value = path
925
1053
  end
926
1054
  end
927
- context "Date fields" do
928
- setup do
929
- @content_class = Class.new(::Piece)
930
- @prototype = @content_class.field :date
931
- @content_class.stubs(:name).returns("ContentClass")
932
- @instance = @content_class.create
933
- @field = @instance.date
934
- end
1055
+ end
1056
+ describe "Date fields" do
1057
+ before do
1058
+ @content_class = Class.new(::Piece)
1059
+ @prototype = @content_class.field :date
1060
+ @content_class.stubs(:name).returns("ContentClass")
1061
+ @instance = @content_class.create
1062
+ @field = @instance.date
1063
+ end
935
1064
 
936
- should "have a distinct editor class" do
937
- @prototype.instance_class.editor_class.should == "Spontaneous.Field.Date"
938
- end
1065
+ it "have a distinct editor class" do
1066
+ @prototype.instance_class.editor_class.must_equal "Spontaneous.Field.Date"
1067
+ end
939
1068
 
940
- should "adopt any field called 'date'" do
941
- assert @field.is_a?(Spontaneous::Field::Date), "Field should be an instance of DateField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
942
- end
1069
+ it "adopt any field called 'date'" do
1070
+ assert @field.is_a?(Spontaneous::Field::Date), "Field should be an instance of DateField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
1071
+ end
943
1072
 
944
- should "default to an empty string" do
945
- @field.value(:html).should == ""
946
- @field.value(:plain).should == ""
947
- end
1073
+ it "default to an empty string" do
1074
+ @field.value(:html).must_equal ""
1075
+ @field.value(:plain).must_equal ""
1076
+ end
948
1077
 
949
- should "correctly parse strings" do
950
- @field.value = "Friday, 8 June, 2012"
951
- @field.value(:html).should == %(<time datetime="2012-06-08">Friday, 8 June, 2012</time>)
952
- @field.value(:plain).should == %(Friday, 8 June, 2012)
953
- @field.date.should == Date.parse("Friday, 8 June, 2012")
954
- end
1078
+ it "correctly parse strings" do
1079
+ @field.value = "Friday, 8 June, 2012"
1080
+ @field.value(:html).must_equal %(<time datetime="2012-06-08">Friday, 8 June, 2012</time>)
1081
+ @field.value(:plain).must_equal %(Friday, 8 June, 2012)
1082
+ @field.date.must_equal Date.parse("Friday, 8 June, 2012")
1083
+ end
955
1084
 
956
- should "allow for setting a custom default format" do
957
- prototype = @content_class.field :datef, :date, :format => "%d %b %Y, %a"
958
- instance = @content_class.new
959
- field = instance.datef
960
- field.value = "Friday, 8 June, 2012"
961
- field.value(:html).should == %(<time datetime="2012-06-08">08 Jun 2012, Fri</time>)
962
- field.value(:plain).should == %(08 Jun 2012, Fri)
963
- end
964
- end
965
-
966
- context "Asynchronous processing" do
967
- setup do
968
- S::Site.background_mode = :simultaneous
969
- @image = File.expand_path("../../fixtures/images/rose.jpg", __FILE__)
970
- @model = (::Piece)
971
- @model.field :title
972
- @model.field :image
973
- @model.field :description, :markdown
974
- @model.box :items do
975
- field :title
976
- field :image
977
- end
978
- @instance = @model.create
979
- end
1085
+ it "allow for setting a custom default format" do
1086
+ prototype = @content_class.field :datef, :date, :format => "%d %b %Y, %a"
1087
+ instance = @content_class.new
1088
+ field = instance.datef
1089
+ field.value = "Friday, 8 June, 2012"
1090
+ field.value(:html).must_equal %(<time datetime="2012-06-08">08 Jun 2012, Fri</time>)
1091
+ field.value(:plain).must_equal %(08 Jun 2012, Fri)
1092
+ end
1093
+ end
980
1094
 
981
- # should "be disabled if the background mode is set to immediate" do
982
- # S::Site.background_mode = :immediate
983
- # S::Field::Update.asynchronous_update_class.should == S::Field::Update::Immediate
984
- # end
1095
+ describe "Tag list fields" do
1096
+ before do
1097
+ @content_class = Class.new(::Piece)
1098
+ @prototype = @content_class.field :tags
1099
+ @content_class.stubs(:name).returns("ContentClass")
1100
+ @instance = @content_class.create
1101
+ @field = @instance.tags
1102
+ end
985
1103
 
986
- # should "be enabled if the background mode is set to simultaneous" do
987
- # S::Site.background_mode = :simultaneous
988
- # S::Field::Update.asynchronous_update_class.should == S::Field::Update::Simultaneous
989
- # end
1104
+ it "has a distinct editor class" # eventually...
990
1105
 
991
- should "be able to resolve fields id" do
992
- S::Field.find(@instance.image.id, @instance.items.title.id).should == [
993
- @instance.image, @instance.items.title
994
- ]
995
- end
1106
+ it "adopts any field called 'tags'" do
1107
+ assert @field.is_a?(Spontaneous::Field::Tags), "Field should be an instance of TagsField but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
1108
+ end
996
1109
 
997
- should "not raise errors for invalid fields" do
998
- S::Field.find("0", "#{@instance.id}/xxx/#{@instance.items.title.schema_id}", "#{@instance.items.id}/nnn", @instance.items.title.id).should == [ @instance.items.title ]
999
- end
1110
+ it "defaults to an empty list" do
1111
+ @field.value(:html).must_equal ""
1112
+ @field.value(:tags).must_equal []
1113
+ end
1000
1114
 
1001
- should "return a single field if given a single id" do
1002
- S::Field.find(@instance.image.id).should == @instance.image
1003
- end
1115
+ it "correctly parses strings" do
1116
+ @field.value = 'this that "the other" more'
1117
+ @field.value(:html).must_equal 'this that "the other" more'
1118
+ @field.value(:tags).must_equal ["this", "that", "the other", "more"]
1119
+ end
1004
1120
 
1005
- should "be disabled for Date fields" do
1006
- f = S::Field::Date.new
1007
- f.asynchronous?.should be_false
1008
- end
1121
+ it "includes Enumerable" do
1122
+ @field.value = 'this that "the other" more'
1123
+ @field.map(&:upcase).must_equal ["THIS", "THAT", "THE OTHER", "MORE"]
1124
+ end
1125
+
1126
+ it "allows for tags with commas" do
1127
+ @field.value = %(this that "the, other" more)
1128
+ @field.map(&:upcase).must_equal ["THIS", "THAT", "THE, OTHER", "MORE"]
1129
+ end
1130
+ end
1131
+
1132
+ describe "Boolean fields" do
1133
+
1134
+ before do
1135
+ @content_class = Class.new(::Piece)
1136
+ @prototype = @content_class.field :switch
1137
+ @content_class.stubs(:name).returns("ContentClass")
1138
+ @instance = @content_class.create
1139
+ @field = @instance.switch
1140
+ end
1141
+
1142
+ it "has a distinct editor class" do
1143
+ @prototype.instance_class.editor_class.must_equal "Spontaneous.Field.Boolean"
1144
+ end
1145
+
1146
+ it "adopts any field called 'switch'" do
1147
+ assert @field.is_a?(Spontaneous::Field::Boolean), "Field should be an instance of Boolean but instead has the following ancestors #{ @prototype.instance_class.ancestors }"
1148
+ end
1149
+
1150
+ it "defaults to true" do
1151
+ @field.value.must_equal true
1152
+ @field.value(:html).must_equal "Yes"
1153
+ @field.value(:string).must_equal "Yes"
1154
+ end
1155
+
1156
+ it "changes string value to 'No'" do
1157
+ @field.value = false
1158
+ @field.value(:string).must_equal "No"
1159
+ end
1160
+
1161
+ it "flags itself as 'empty' if false" do # I think...
1162
+ @field.empty?.must_equal false
1163
+ @field.value = false
1164
+ @field.empty?.must_equal true
1165
+ end
1009
1166
 
1010
- should "be disabled for Location fields" do
1011
- f = S::Field::Location.new
1012
- f.asynchronous?.should be_false
1167
+ it "uses the given state labels" do
1168
+ prototype = @content_class.field :boolean, true: "Enabled", false: "Disabled"
1169
+ field = prototype.to_field(@instance)
1170
+ field.value.must_equal true
1171
+ field.value(:string).must_equal "Enabled"
1172
+ field.value = false
1173
+ field.value(:string).must_equal "Disabled"
1174
+ field.value(:html).must_equal "Disabled"
1175
+ end
1176
+
1177
+ it "uses the given default" do
1178
+ prototype = @content_class.field :boolean, default: false, true: "On", false: "Off"
1179
+ field = prototype.to_field(@instance)
1180
+ field.value.must_equal false
1181
+ field.value(:string).must_equal "Off"
1182
+ end
1183
+
1184
+ it "returns the string value from #to_s" do
1185
+ prototype = @content_class.field :boolean, default: false, true: "On", false: "Off"
1186
+ field = prototype.to_field(@instance)
1187
+ field.to_s.must_equal "Off"
1188
+ end
1189
+
1190
+ it "has shortcut accessors" do
1191
+ state = @field.value(:boolean)
1192
+ @field.on?.must_equal state
1193
+ @field.checked?.must_equal state
1194
+ @field.enabled?.must_equal state
1195
+ end
1196
+
1197
+ it "exports the labels to the interface" do
1198
+ prototype = @content_class.field :boolean, default: false, true: "Yes Please", false: "No Thanks"
1199
+ exported = prototype.instance_class.export(nil)
1200
+ exported.must_equal({:labels=>{:true=>"Yes Please", :false=>"No Thanks"}})
1201
+ end
1202
+ end
1203
+
1204
+ describe "Asynchronous processing" do
1205
+ before do
1206
+ S::Site.background_mode = :simultaneous
1207
+ @image = File.expand_path("../../fixtures/images/size.gif", __FILE__)
1208
+ @model = (::Piece)
1209
+ @model.field :title
1210
+ @model.field :image
1211
+ @model.field :description, :markdown
1212
+ @model.box :items do
1213
+ field :title
1214
+ field :image
1215
+ end
1216
+ @instance = @model.create
1217
+ end
1218
+
1219
+ # it "be disabled if the background mode is set to immediate" do
1220
+ # S::Site.background_mode = :immediate
1221
+ # S::Field::Update.asynchronous_update_class.must_equal S::Field::Update::Immediate
1222
+ # end
1223
+
1224
+ # it "be enabled if the background mode is set to simultaneous" do
1225
+ # S::Site.background_mode = :simultaneous
1226
+ # S::Field::Update.asynchronous_update_class.must_equal S::Field::Update::Simultaneous
1227
+ # end
1228
+
1229
+ it "be able to resolve fields id" do
1230
+ S::Field.find(@instance.image.id, @instance.items.title.id).must_equal [
1231
+ @instance.image, @instance.items.title
1232
+ ]
1233
+ end
1234
+
1235
+ it "not raise errors for invalid fields" do
1236
+ S::Field.find("0", "#{@instance.id}/xxx/#{@instance.items.title.schema_id}", "#{@instance.items.id}/nnn", @instance.items.title.id).must_equal [ @instance.items.title ]
1237
+ end
1238
+
1239
+ it "return a single field if given a single id" do
1240
+ S::Field.find(@instance.image.id).must_equal @instance.image
1241
+ end
1242
+
1243
+ it "be disabled for Date fields" do
1244
+ f = S::Field::Date.new
1245
+ refute f.asynchronous?
1246
+ end
1247
+
1248
+ it "be disabled for Location fields" do
1249
+ f = S::Field::Location.new
1250
+ refute f.asynchronous?
1251
+ end
1252
+
1253
+ it "be disabled for LongString fields" do
1254
+ f = S::Field::LongString.new
1255
+ refute f.asynchronous?
1256
+ end
1257
+
1258
+ it "be disabled for Markdown fields" do
1259
+ f = S::Field::Markdown.new
1260
+ refute f.asynchronous?
1261
+ end
1262
+
1263
+ it "be disabled for Select fields" do
1264
+ f = S::Field::Select.new
1265
+ refute f.asynchronous?
1266
+ end
1267
+
1268
+ it "be disabled for String fields" do
1269
+ f = S::Field::String.new
1270
+ refute f.asynchronous?
1271
+ end
1272
+
1273
+ it "be disabled for WebVideo fields" do
1274
+ f = S::Field::WebVideo.new
1275
+ refute f.asynchronous?
1276
+ end
1277
+
1278
+ it "be enabled for File fields" do
1279
+ f = S::Field::File.new
1280
+ assert f.asynchronous?
1281
+ end
1282
+
1283
+ it "be enabled for Image fields" do
1284
+ f = S::Field::Image.new
1285
+ assert f.asynchronous?
1286
+ end
1287
+
1288
+ it "immediately update a group of fields passed in parameter format" do
1289
+ field = @instance.image
1290
+ File.open(@image, "r") do |file|
1291
+ fields = {
1292
+ @instance.title.schema_id.to_s => "Updated title",
1293
+ @instance.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1294
+ @instance.description.schema_id.to_s => "Updated description"
1295
+ }
1296
+ Spontaneous::Field.update(@instance, fields, nil, false)
1297
+ @instance.reload
1298
+ @instance.title.value.must_equal "Updated title"
1299
+ @instance.description.value.must_equal "<p>Updated description</p>\n"
1300
+ field.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1013
1301
  end
1302
+ end
1303
+
1304
+ it "asynchronously update a group of fields passed in parameter format" do
1305
+ field = @instance.image
1306
+ Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1307
+ "fields" => [field.id]
1308
+ })
1014
1309
 
1015
- should "be disabled for LongString fields" do
1016
- f = S::Field::LongString.new
1017
- f.asynchronous?.should be_false
1310
+ File.open(@image, "r") do |file|
1311
+ fields = {
1312
+ @instance.title.schema_id.to_s => "Updated title",
1313
+ @instance.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1314
+ @instance.description.schema_id.to_s => "Updated description"
1315
+ }
1316
+ @instance.expects(:save).at_least_once
1317
+
1318
+ Spontaneous::Field.update(@instance, fields, nil, true)
1319
+
1320
+ @instance.title.value.must_equal "Updated title"
1321
+ @instance.description.value.must_equal "<p>Updated description</p>\n"
1322
+ field.value.must_equal ""
1323
+ field.pending_value.must_equal({
1324
+ :timestamp => S::Field.timestamp(@now),
1325
+ :version => 1,
1326
+ :value => {
1327
+ :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
1328
+ :type=>"image/gif", :format => "gif",
1329
+ :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1330
+ :filename=>"something.gif",
1331
+ :src => "/media/tmp/#{field.media_id}/something.gif"
1332
+ }
1333
+ })
1334
+ field.process_pending_value
1335
+ field.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1336
+ field.pending_value.must_be_nil
1018
1337
  end
1338
+ end
1019
1339
 
1020
- should "be disabled for Markdown fields" do
1021
- f = S::Field::Markdown.new
1022
- f.asynchronous?.should be_false
1340
+ it "asynchronously update a single field value" do
1341
+ field = @instance.image
1342
+ Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1343
+ "fields" => [field.id]
1344
+ })
1345
+ File.open(@image, "r") do |file|
1346
+ field.pending_version.must_equal 0
1347
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1348
+ field.value.must_equal ""
1349
+ field.pending_value.must_equal({
1350
+ :timestamp => S::Field.timestamp(@now),
1351
+ :version => 1,
1352
+ :value => {
1353
+ :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
1354
+ :type=>"image/gif", :format => "gif",
1355
+ :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1356
+ :filename=>"something.gif",
1357
+ :src => "/media/tmp/#{field.media_id}/something.gif"
1358
+ }
1359
+ })
1360
+ field.pending_version.must_equal 1
1361
+ field.process_pending_value
1362
+ field.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1023
1363
  end
1364
+ end
1024
1365
 
1025
- should "be disabled for Select fields" do
1026
- f = S::Field::Select.new
1027
- f.asynchronous?.should be_false
1366
+ it "synchronously update box fields" do
1367
+ box = @instance.items
1368
+ File.open(@image, "r") do |file|
1369
+ fields = {
1370
+ box.title.schema_id.to_s => "Updated title",
1371
+ box.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1372
+ }
1373
+ Spontaneous::Field.update(box, fields, nil, false)
1374
+ box.title.value.must_equal "Updated title"
1375
+ box.image.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/#{box.schema_id}/0001/something.gif"
1376
+ box.image.pending_version.must_equal 1
1028
1377
  end
1378
+ end
1029
1379
 
1030
- should "be disabled for String fields" do
1031
- f = S::Field::String.new
1032
- f.asynchronous?.should be_false
1380
+ it "asynchronously update box fields" do
1381
+ box = @instance.items
1382
+ field = box.image
1383
+ Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1384
+ "fields" => [field.id]
1385
+ })
1386
+ File.open(@image, "r") do |file|
1387
+ fields = {
1388
+ box.title.schema_id.to_s => "Updated title",
1389
+ box.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1390
+ }
1391
+ Spontaneous::Field.update(box, fields, nil, true)
1392
+ box.title.value.must_equal "Updated title"
1393
+ field.value.must_equal ""
1394
+ field.pending_value.must_equal({
1395
+ :timestamp => S::Field.timestamp(@now),
1396
+ :version => 1,
1397
+ :value => {
1398
+ :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
1399
+ :type=>"image/gif", :format => "gif",
1400
+ :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1401
+ :filename=>"something.gif",
1402
+ :src => "/media/tmp/#{field.media_id}/something.gif"
1403
+ }
1404
+ })
1033
1405
  end
1406
+ end
1034
1407
 
1035
- should "be disabled for WebVideo fields" do
1036
- f = S::Field::WebVideo.new
1037
- f.asynchronous?.should be_false
1408
+ it "deletes used temp files after processing" do
1409
+ field = @instance.image
1410
+ tempfile = "#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif"
1411
+ Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1412
+ "fields" => [field.id]
1413
+ })
1414
+ File.open(@image, "r") do |file|
1415
+ field.pending_version.must_equal 0
1416
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1417
+ field.value.must_equal ""
1418
+ field.pending_value.must_equal({
1419
+ :timestamp => S::Field.timestamp(@now),
1420
+ :version => 1,
1421
+ :value => {
1422
+ :width=>50, :height=>67, :dimensions => [50,67], :filesize=>3951,
1423
+ :type=>"image/gif", :format => "gif",
1424
+ :tempfile=>"#{@site.root}/cache/media/tmp/#{field.media_id}/something.gif",
1425
+ :filename=>"something.gif",
1426
+ :src => "/media/tmp/#{field.media_id}/something.gif"
1427
+ }
1428
+ })
1429
+ field.pending_version.must_equal 1
1430
+ assert ::File.exist?(tempfile)
1431
+ field.process_pending_value
1432
+ field.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1038
1433
  end
1434
+ refute ::File.exist?(tempfile)
1435
+ end
1039
1436
 
1040
- should "be enabled for File fields" do
1041
- f = S::Field::File.new
1042
- f.asynchronous?.should be_true
1437
+ it "immediately update asynchronous fields if background mode is :immediate" do
1438
+ S::Site.background_mode = :immediate
1439
+ File.open(@image, "r") do |file|
1440
+ fields = {
1441
+ @instance.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"}
1442
+ }
1443
+ Spontaneous::Simultaneous.expects(:fire).never
1444
+ Spontaneous::Field.update(@instance, fields, nil, true)
1445
+ @instance.image.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1043
1446
  end
1447
+ end
1448
+
1449
+ it "not update a field if user does not have necessary permissions" do
1450
+ user = mock()
1451
+ @instance.title.expects(:writable?).with(user).at_least_once.returns(false)
1452
+ fields = {
1453
+ @instance.title.schema_id.to_s => "Updated title"
1454
+ }
1455
+ Spontaneous::Field.update(@instance, fields, user, true)
1456
+ @instance.title.value.must_equal ""
1457
+ end
1458
+
1459
+ it "call Fields::Update::Immediate from the cli" do
1460
+ immediate = mock()
1461
+ immediate.expects(:pages).returns([])
1462
+ immediate.expects(:run)
1463
+ Spontaneous::Field::Update::Immediate.expects(:new).with([@instance.image, @instance.items.title]).returns(immediate)
1464
+ # Thor generates a warning about creating a task with no 'desc'
1465
+ silence_logger {
1466
+ Spontaneous::Cli::Fields.any_instance.stubs(:prepare!)
1467
+ }
1468
+ Spontaneous::Cli::Fields.start(["update", "--fields", @instance.image.id, @instance.items.title.id])
1469
+ end
1044
1470
 
1045
- should "be enabled for Image fields" do
1046
- f = S::Field::Image.new
1047
- f.asynchronous?.should be_true
1471
+ it "call Fields::Update::Immediate from the cli with a single field" do
1472
+ silence_logger {
1473
+ Spontaneous::Cli::Fields.any_instance.stubs(:prepare!)
1474
+ }
1475
+ Spontaneous::Cli::Fields.start(["update", "--fields", @instance.image.id])
1476
+ end
1477
+
1478
+ it "revert to immediate updating if connection to simultaneous fails" do
1479
+ File.open(@image, "r") do |file|
1480
+ Spontaneous::Field.set(@instance.image, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1481
+ @instance.image.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1482
+ @instance.image.pending_value.must_be_nil
1048
1483
  end
1484
+ end
1049
1485
 
1050
- should "immediately update a group of fields passed in parameter format" do
1051
- File.open(@image, "r") do |file|
1052
- fields = {
1053
- @instance.title.schema_id.to_s => "Updated title",
1054
- @instance.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1055
- @instance.description.schema_id.to_s => "Updated description"
1056
- }
1057
- Spontaneous::Field.update(@instance, fields, nil, false)
1058
- @instance.reload
1059
- @instance.title.value.should == "Updated title"
1060
- @instance.description.value.should == "<p>Updated description</p>\n"
1061
- @instance.image.value.should == "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1486
+ describe "page locks" do
1487
+ before do
1488
+ @now = Time.now
1489
+ stub_time(@now)
1490
+ LockedPage = Class.new(::Page)
1491
+ LockedPage.field :image
1492
+ LockedPage.box :instances do
1493
+ field :image
1494
+ field :title
1495
+ end
1496
+ LockedPiece = @model
1497
+ @page = LockedPage.create
1498
+ @instance = LockedPiece.create
1499
+ @page.instances << @instance
1500
+ @page.save.reload
1501
+ @instance.save.reload
1502
+ # The PageLock associations cache the Content model
1503
+ # but since this changes every time later tests
1504
+ # use an old version of Content with an old schema
1505
+ S::PageLock.all_association_reflections.each do |r|
1506
+ # Clear the cached class
1507
+ r[:cache] = {}
1062
1508
  end
1063
1509
  end
1064
1510
 
1065
- should "asynchronously update a group of fields passed in parameter format" do
1511
+ after do
1512
+ Spontaneous::PageLock.delete
1513
+ Object.send :remove_const, :LockedPage rescue nil
1514
+ Object.send :remove_const, :LockedPiece rescue nil
1515
+ end
1516
+
1517
+ it "be created when scheduling a page field for async updating" do
1066
1518
  Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1067
- "fields" => [@instance.image.id]
1519
+ "fields" => [@page.image.id]
1068
1520
  })
1069
-
1070
1521
  File.open(@image, "r") do |file|
1071
- fields = {
1072
- @instance.title.schema_id.to_s => "Updated title",
1073
- @instance.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1074
- @instance.description.schema_id.to_s => "Updated description"
1075
- }
1076
- @instance.expects(:save).at_least_once
1077
-
1078
- Spontaneous::Field.update(@instance, fields, nil, true)
1079
-
1080
- @instance.title.value.should == "Updated title"
1081
- @instance.description.value.should == "<p>Updated description</p>\n"
1082
- @instance.image.value.should == ""
1083
- @instance.image.pending_value.should == {
1084
- :timestamp => S::Field.timestamp(@now),
1085
- :version => 1,
1086
- :value => {
1087
- :width=>400, :height=>533, :filesize=>54746,
1088
- :type=>"image/gif",
1089
- :tempfile=>"#{@site.root}/cache/media/tmp/#{S::Media.pad_id(@instance.id)}/something.gif",
1090
- :filename=>"something.gif",
1091
- :src => "/media/tmp/#{S::Media.pad_id(@instance.id)}/something.gif"
1092
- }
1093
- }
1094
- @instance.image.process_pending_value
1095
- @instance.image.value.should == "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1096
- @instance.image.pending_value.should be_nil
1522
+ Spontaneous::Field.set(@page.image, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1523
+ @page.image.value.must_equal ""
1524
+ @page.update_locks.length.must_equal 1
1525
+ lock = @page.update_locks.first
1526
+ lock.field.must_equal @page.image
1527
+ lock.content.must_equal @page
1528
+ lock.page.must_equal @page
1529
+ lock.description.must_match /something\.gif/
1530
+ lock.created_at.must_equal @now
1531
+ lock.location.must_equal "Field ‘image’"
1532
+ assert @page.locked_for_update?
1097
1533
  end
1098
1534
  end
1099
1535
 
1100
- should "asynchronously update a single field value" do
1536
+ it "not create locks for fields processed immediately" do
1537
+ field = @instance.title
1538
+ Spontaneous::Field.set(field, "Updated Title", nil, true)
1539
+ field.value.must_equal "Updated Title"
1540
+ @page.update_locks.length.must_equal 0
1541
+ refute @page.locked_for_update?
1542
+ end
1543
+
1544
+ it "be created when scheduling a box field for async updating" do
1545
+ field = @page.instances.image
1101
1546
  Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1102
- "fields" => [@instance.image.id]
1547
+ "fields" => [field.id]
1103
1548
  })
1104
1549
  File.open(@image, "r") do |file|
1105
- @instance.image.pending_version.should == 0
1106
- Spontaneous::Field.set(@instance.image, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1107
- @instance.image.value.should == ""
1108
- @instance.image.pending_value.should == {
1109
- :timestamp => S::Field.timestamp(@now),
1110
- :version => 1,
1111
- :value => {
1112
- :width=>400, :height=>533, :filesize=>54746,
1113
- :type=>"image/gif",
1114
- :tempfile=>"#{@site.root}/cache/media/tmp/#{S::Media.pad_id(@instance.id)}/something.gif",
1115
- :filename=>"something.gif",
1116
- :src => "/media/tmp/#{S::Media.pad_id(@instance.id)}/something.gif"
1117
- }
1118
- }
1119
- @instance.image.pending_version.should == 1
1120
- @instance.image.process_pending_value
1121
- @instance.image.value.should == "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1550
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1551
+ field.value.must_equal ""
1552
+ @page.update_locks.length.must_equal 1
1553
+ lock = @page.update_locks.first
1554
+ lock.field.must_equal field.reload
1555
+ lock.content.must_equal @page.reload
1556
+ lock.page.must_equal @page
1557
+ lock.description.must_match /something\.gif/
1558
+ lock.created_at.must_equal @now
1559
+ lock.location.must_equal "Field ‘image’ of box ‘instances’"
1560
+ assert @page.locked_for_update?
1122
1561
  end
1123
1562
  end
1124
1563
 
1125
- should "synchronously update box fields" do
1126
- box = @instance.items
1564
+ it "be created when scheduling a piece field for async updating" do
1565
+ field = @instance.image
1566
+ Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1567
+ "fields" => [field.id]
1568
+ })
1127
1569
  File.open(@image, "r") do |file|
1128
- fields = {
1129
- box.title.schema_id.to_s => "Updated title",
1130
- box.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1131
- }
1132
- Spontaneous::Field.update(box, fields, nil, false)
1133
- box.title.value.should == "Updated title"
1134
- box.image.value.should == "/media/#{S::Media.pad_id(@instance.id)}/#{box.schema_id}/0001/something.gif"
1135
- box.image.pending_version.should == 1
1570
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1571
+ field.value.must_equal ""
1572
+ @page.update_locks.length.must_equal 1
1573
+ lock = @page.update_locks.first
1574
+ lock.field.must_equal field
1575
+ lock.content.must_equal @instance
1576
+ lock.page.must_equal @page
1577
+ lock.description.must_match /something\.gif/
1578
+ lock.created_at.must_equal @now
1579
+ lock.location.must_equal "Field ‘image’ of entry 1 in box ‘instances’"
1580
+ assert @page.locked_for_update?
1136
1581
  end
1137
1582
  end
1138
1583
 
1139
- should "asynchronously update box fields" do
1140
- box = @instance.items
1584
+ it "be removed when the field has been processed" do
1141
1585
  Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1142
- "fields" => [box.image.id]
1586
+ "fields" => [@page.image.id]
1143
1587
  })
1144
1588
  File.open(@image, "r") do |file|
1145
- fields = {
1146
- box.title.schema_id.to_s => "Updated title",
1147
- box.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"},
1148
- }
1149
- Spontaneous::Field.update(box, fields, nil, true)
1150
- box.title.value.should == "Updated title"
1151
- box.image.value.should == ""
1152
- box.image.pending_value.should == {
1153
- :timestamp => S::Field.timestamp(@now),
1154
- :version => 1,
1155
- :value => {
1156
- :width=>400, :height=>533, :filesize=>54746,
1157
- :type=>"image/gif",
1158
- :tempfile=>"#{@site.root}/cache/media/tmp/#{S::Media.pad_id(@instance.id)}/#{box.schema_id}/something.gif",
1159
- :filename=>"something.gif",
1160
- :src => "/media/tmp/#{S::Media.pad_id(@instance.id)}/#{box.schema_id}/something.gif"
1161
- }
1162
- }
1589
+ Spontaneous::Field.set(@page.image, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1590
+ @page.image.value.must_equal ""
1591
+ @page.update_locks.length.must_equal 1
1592
+ assert @page.locked_for_update?
1593
+ # The lock manipulation is done by the updater
1594
+ # so calling update_pending_value on the field
1595
+ # won't clear any locks
1596
+ Spontaneous::Field::Update::Immediate.process([@page.image])
1597
+ @page.image.value.must_equal "/media/#{@page.id.to_s.rjust(5, "0")}/0001/something.gif"
1598
+ refute @page.reload.locked_for_update?
1163
1599
  end
1164
1600
  end
1165
1601
 
1166
- should "immediately update asynchronous fields if background mode is :immediate" do
1167
- S::Site.background_mode = :immediate
1602
+ it "send a completion event that includes a list of unlocked pages" do
1603
+ field = @instance.image
1604
+ Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1605
+ "fields" => [field.id]
1606
+ })
1607
+ Simultaneous.expects(:send_event).with('page_lock_status', "[#{@page.id}]")
1608
+
1168
1609
  File.open(@image, "r") do |file|
1169
- fields = {
1170
- @instance.image.schema_id.to_s => {:tempfile => file, :filename => "something.gif", :type => "image/gif"}
1610
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1611
+ assert @page.locked_for_update?
1612
+ silence_logger {
1613
+ Spontaneous::Cli::Fields.any_instance.stubs(:prepare!)
1171
1614
  }
1172
- Spontaneous::Simultaneous.expects(:fire).never
1173
- Spontaneous::Field.update(@instance, fields, nil, true)
1174
- @instance.image.value.should == "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1615
+ Spontaneous::Cli::Fields.start(["update", "--fields", field.id])
1175
1616
  end
1176
1617
  end
1177
1618
 
1178
- should "not update a field if user does not have necessary permissions" do
1179
- user = mock()
1180
- @instance.title.expects(:writable?).with(user).at_least_once.returns(false)
1181
- fields = {
1182
- @instance.title.schema_id.to_s => "Updated title"
1183
- }
1184
- Spontaneous::Field.update(@instance, fields, user, true)
1185
- @instance.title.value.should == ""
1186
- end
1187
-
1188
- should "call Fields::Update::Immediate from the cli" do
1189
- immediate = mock()
1190
- immediate.expects(:pages).returns([])
1191
- immediate.expects(:run)
1192
- Spontaneous::Field::Update::Immediate.expects(:new).with([@instance.image, @instance.items.title]).returns(immediate)
1193
- # Thor generates a warning about creating a task with no 'desc'
1194
- silence_logger {
1195
- Spontaneous::Cli::Fields.any_instance.stubs(:prepare!)
1196
- }
1197
- Spontaneous::Cli::Fields.start(["update", "--fields", @instance.image.id, @instance.items.title.id])
1198
- end
1199
-
1200
- should "call Fields::Update::Immediate from the cli with a single field" do
1201
- silence_logger {
1202
- Spontaneous::Cli::Fields.any_instance.stubs(:prepare!)
1203
- }
1204
- Spontaneous::Cli::Fields.start(["update", "--fields", @instance.image.id])
1205
- end
1619
+ it "ignore an update that has been superceded" do
1620
+ # user uploads an image and then changes their mind and uploads another
1621
+ # before the first one has been processed.
1622
+ # Pending value might have changed between the start of the update and the end
1623
+ # especially in the case of video processing or file upload
1624
+ # Before we update the value of a field or
1625
+ # clear pending values we need to be sure that they aren't still needed
1626
+ #
1206
1627
 
1207
- should "revert to immediate updating if connection to simultaneous fails" do
1628
+ field = @instance.image
1629
+ Spontaneous::Simultaneous.expects(:fire).at_least_once.with(:update_fields, {
1630
+ "fields" => [field.id]
1631
+ })
1208
1632
  File.open(@image, "r") do |file|
1209
- Spontaneous::Field.set(@instance.image, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1210
- @instance.image.value.should == "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1211
- @instance.image.pending_value.should be_nil
1212
- end
1213
- end
1214
-
1215
- context "page locks" do
1216
- setup do
1217
- @now = Time.now
1218
- stub_time(@now)
1219
- LockedPage = Class.new(::Page)
1220
- LockedPage.field :image
1221
- LockedPage.box :instances do
1222
- field :image
1223
- field :title
1224
- end
1225
- LockedPiece = @model
1226
- @page = LockedPage.create
1227
- @instance = LockedPiece.create
1228
- @page.instances << @instance
1229
- @page.save.reload
1230
- @instance.save.reload
1231
- # The PageLock associations cache the Content model
1232
- # but since this changes every time later tests
1233
- # use an old version of Content with an old schema
1234
- S::PageLock.all_association_reflections.each do |r|
1235
- # Clear the cached class
1236
- r[:cache] = {}
1237
- end
1633
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1238
1634
  end
1239
-
1240
- teardown do
1241
- Spontaneous::PageLock.delete
1242
- self.class.send :remove_const, :LockedPage rescue nil
1243
- self.class.send :remove_const, :LockedPiece rescue nil
1635
+ update = Spontaneous::Field::Update::Immediate.new(field)
1636
+ old, field = field, field.reload
1637
+ later = @now + 1
1638
+ t = S::Field.timestamp(later)
1639
+ S::Field.stubs(:timestamp).returns(t)
1640
+ File.open(@image, "r") do |file|
1641
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "else.gif", :type => "image/jpeg"}, nil, true)
1244
1642
  end
1245
-
1246
- should "be created when scheduling a page field for async updating" do
1247
- Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1248
- "fields" => [@page.image.id]
1249
- })
1250
- File.open(@image, "r") do |file|
1251
- Spontaneous::Field.set(@page.image, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1252
- @page.image.value.should == ""
1253
- @page.update_locks.length.should == 1
1254
- lock = @page.update_locks.first
1255
- lock.field.should == @page.image
1256
- lock.content.should == @page
1257
- lock.page.should == @page
1258
- lock.description.should =~ /something\.gif/
1259
- lock.created_at.should == @now
1260
- lock.location.should == "Field ‘image’"
1261
- @page.locked_for_update?.should be_true
1262
- end
1643
+ update.run
1644
+
1645
+ pending = field.pending_value
1646
+ pending[:value][:filename].must_equal "else.gif"
1647
+ end
1648
+
1649
+ it "merge async updates with synchronous ones affected during processing" do
1650
+ # Scenario:
1651
+ # - User uploads file to content item which gets scheduled for async processing
1652
+ # - User modifies synchronous field of same content item that gets immediately updated
1653
+ # - Async process completes and...
1654
+ # SHOULD
1655
+ # Keep the updated values from the immediate change
1656
+ # Merge in the results of the async change
1657
+ field = @instance.image
1658
+ Spontaneous::Simultaneous.expects(:fire).at_least_once.with(:update_fields, {
1659
+ "fields" => [field.id]
1660
+ })
1661
+ File.open(@image, "r") do |file|
1662
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1263
1663
  end
1264
-
1265
- should "not create locks for fields processed immediately" do
1266
- field = @instance.title
1267
- Spontaneous::Field.set(field, "Updated Title", nil, true)
1268
- field.value.should == "Updated Title"
1269
- @page.update_locks.length.should == 0
1270
- @page.locked_for_update?.should be_false
1664
+ # Create update but don't run it
1665
+ update = Spontaneous::Field::Update::Immediate.new(field)
1666
+ # Someone updates a field before the async update is run...
1667
+ content = ::Content.get(@instance.id)
1668
+ content.title = "Updated Title"
1669
+ content.save
1670
+
1671
+ # Now run the update with a field that's out of sync with the version in the db
1672
+ update.run
1673
+
1674
+ content = ::Content.get(@instance.id)
1675
+ content.title.value.must_equal "Updated Title"
1676
+ content.image.value.must_equal "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1677
+ end
1678
+
1679
+ it "merge async updates to box fields with synchronous ones affected during processing" do
1680
+ # The scenario for boxes is more complex because their fields are stored by their owner
1681
+ # not directly by themselves
1682
+ field = @page.instances.image
1683
+ Spontaneous::Simultaneous.expects(:fire).at_least_once.with(:update_fields, {
1684
+ "fields" => [field.id]
1685
+ })
1686
+ File.open(@image, "r") do |file|
1687
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1271
1688
  end
1689
+ # Create update but don't run it
1690
+ update = Spontaneous::Field::Update::Immediate.new(field)
1691
+ # Someone updates a field before the async update is run...
1692
+ content = ::Content.get(@page.id)
1693
+ content.instances.title = "Updated Title"
1694
+ content.save
1272
1695
 
1273
- should "be created when scheduling a box field for async updating" do
1274
- field = @page.instances.image
1275
- Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1276
- "fields" => [field.id]
1277
- })
1278
- File.open(@image, "r") do |file|
1279
- Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1280
- field.value.should == ""
1281
- @page.update_locks.length.should == 1
1282
- lock = @page.update_locks.first
1283
- lock.field.should == field.reload
1284
- lock.content.should == @page.reload
1285
- lock.page.should == @page
1286
- lock.description.should =~ /something\.gif/
1287
- lock.created_at.should == @now
1288
- lock.location.should == "Field ‘image’ of box ‘instances’"
1289
- @page.locked_for_update?.should be_true
1290
- end
1291
- end
1696
+ # Now run the update with a field that's out of sync with the version in the db
1697
+ update.run
1292
1698
 
1293
- should "be created when scheduling a piece field for async updating" do
1294
- field = @instance.image
1295
- Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1296
- "fields" => [field.id]
1297
- })
1298
- File.open(@image, "r") do |file|
1299
- Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1300
- field.value.should == ""
1301
- @page.update_locks.length.should == 1
1302
- lock = @page.update_locks.first
1303
- lock.field.should == field
1304
- lock.content.should == @instance
1305
- lock.page.should == @page
1306
- lock.description.should =~ /something\.gif/
1307
- lock.created_at.should == @now
1308
- lock.location.should == "Field ‘image’ of entry 1 in box ‘instances’"
1309
- @page.locked_for_update?.should be_true
1310
- end
1311
- end
1312
1699
 
1313
- should "be removed when the field has been processed" do
1314
- Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1315
- "fields" => [@page.image.id]
1316
- })
1317
- File.open(@image, "r") do |file|
1318
- Spontaneous::Field.set(@page.image, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1319
- @page.image.value.should == ""
1320
- @page.update_locks.length.should == 1
1321
- @page.locked_for_update?.should be_true
1322
- # The lock manipulation is done by the updater
1323
- # so calling update_pending_value on the field
1324
- # won't clear any locks
1325
- Spontaneous::Field::Update::Immediate.process([@page.image])
1326
- @page.image.value.should == "/media/#{@page.id.to_s.rjust(5, "0")}/0001/something.gif"
1327
- @page.reload.locked_for_update?.should be_false
1328
- end
1329
- end
1700
+ content = ::Content.get(@page.id)
1701
+ content.instances.title.value.must_equal "Updated Title"
1702
+ content.instances.image.value.must_equal "/media/#{S::Media.pad_id(@page.id)}/#{@page.instances.schema_id}/0001/something.gif"
1703
+ end
1330
1704
 
1331
- should "send a completion event that includes a list of unlocked pages" do
1332
- field = @instance.image
1333
- Spontaneous::Simultaneous.expects(:fire).with(:update_fields, {
1334
- "fields" => [field.id]
1335
- })
1336
- Simultaneous.expects(:send_event).with('page_lock_status', "[#{@page.id}]")
1337
-
1338
- File.open(@image, "r") do |file|
1339
- Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1340
- @page.locked_for_update?.should be_true
1341
- silence_logger {
1342
- Spontaneous::Cli::Fields.any_instance.stubs(:prepare!)
1343
- }
1344
- Spontaneous::Cli::Fields.start(["update", "--fields", field.id])
1345
- end
1346
- end
1705
+ it "removes temporary files after processing" do
1706
+ end
1347
1707
 
1348
- should "ignore an update that has been superceded" do
1349
- # user uploads an image and then changes their mind and uploads another
1350
- # before the first one has been processed.
1351
- # Pending value might have changed between the start of the update and the end
1352
- # especially in the case of video processing or file upload
1353
- # Before we update the value of a field or
1354
- # clear pending values we need to be sure that they aren't still needed
1355
- #
1356
-
1357
- field = @instance.image
1358
- Spontaneous::Simultaneous.expects(:fire).at_least_once.with(:update_fields, {
1359
- "fields" => [field.id]
1360
- })
1361
- File.open(@image, "r") do |file|
1362
- Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1363
- end
1364
- update = Spontaneous::Field::Update::Immediate.new(field)
1365
- old, field = field, field.reload
1366
- later = @now + 1
1367
- t = S::Field.timestamp(later)
1368
- S::Field.stubs(:timestamp).returns(t)
1369
- File.open(@image, "r") do |file|
1370
- Spontaneous::Field.set(field, {:tempfile => file, :filename => "else.gif", :type => "image/jpeg"}, nil, true)
1371
- end
1372
- update.run
1708
+ it "be deleted when their page is deleted" do
1709
+ @page.image.stubs(:page_lock_description).returns("Lock description")
1710
+ lock = Spontaneous::PageLock.lock_field(@page.image)
1711
+ @page.destroy
1712
+ found = Spontaneous::PageLock[lock.id]
1713
+ found.must_be_nil
1714
+ end
1373
1715
 
1374
- pending = field.pending_value
1375
- pending[:value][:filename].should == "else.gif"
1376
- end
1716
+ it "be deleted when their owning content is deleted" do
1717
+ LockedPiece.field :title
1718
+ @instance.title.stubs(:page_lock_description).returns("Lock description")
1719
+ lock = Spontaneous::PageLock.lock_field(@instance.title)
1720
+ @instance.destroy
1721
+ found = Spontaneous::PageLock.filter(:content_id => @instance.id).first
1722
+ found.must_be_nil
1723
+ end
1377
1724
 
1378
- should "merge async updates with synchronous ones effected during processing" do
1379
- # Scenario:
1380
- # - User uploads file to content item which gets scheduled for async processing
1381
- # - User modifies synchronous field of same content item that gets immediately updated
1382
- # - Async process completes and...
1383
- # SHOULD
1384
- # Keep the updated values from the immediate change
1385
- # Merge in the results of the async change
1386
- field = @instance.image
1387
- Spontaneous::Simultaneous.expects(:fire).at_least_once.with(:update_fields, {
1388
- "fields" => [field.id]
1389
- })
1390
- File.open(@image, "r") do |file|
1391
- Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1392
- end
1393
- # Create update but don't run it
1394
- update = Spontaneous::Field::Update::Immediate.new(field)
1395
- # Someone updates a field before the async update is run...
1396
- content = ::Content.get(@instance.id)
1397
- content.title = "Updated Title"
1398
- content.save
1399
-
1400
- # Now run the update with a field that's out of sync with the version in the db
1401
- update.run
1402
-
1403
- content = ::Content.get(@instance.id)
1404
- content.title.value.should == "Updated Title"
1405
- content.image.value.should == "/media/#{S::Media.pad_id(@instance.id)}/0001/something.gif"
1725
+ it "deals gracefully with updating content that has been deleted" do
1726
+ field = @page.image
1727
+ Spontaneous::Simultaneous.expects(:fire).at_least_once.with(:update_fields, {
1728
+ "fields" => [field.id]
1729
+ })
1730
+ File.open(@image, "r") do |file|
1731
+ Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1406
1732
  end
1733
+ # Create update but don't run it
1734
+ update = Spontaneous::Field::Update::Immediate.new(field)
1407
1735
 
1408
- should "merge async updates to box fields with synchronous ones effected during processing" do
1409
- # The scenario for boxes is more complex because their fields are stored by their owner
1410
- # not directly by themselves
1411
- field = @page.instances.image
1412
- Spontaneous::Simultaneous.expects(:fire).at_least_once.with(:update_fields, {
1413
- "fields" => [field.id]
1414
- })
1415
- File.open(@image, "r") do |file|
1416
- Spontaneous::Field.set(field, {:tempfile => file, :filename => "something.gif", :type => "image/gif"}, nil, true)
1417
- end
1418
- # Create update but don't run it
1419
- update = Spontaneous::Field::Update::Immediate.new(field)
1420
- # Someone updates a field before the async update is run...
1421
- content = ::Content.get(@page.id)
1422
- content.instances.title = "Updated Title"
1423
- content.save
1424
-
1425
- # Now run the update with a field that's out of sync with the version in the db
1426
- update.run
1736
+ @page.destroy
1427
1737
 
1738
+ update.run
1428
1739
 
1429
- content = ::Content.get(@page.id)
1430
- content.instances.title.value.should == "Updated Title"
1431
- content.instances.image.value.should == "/media/#{S::Media.pad_id(@page.id)}/#{@page.instances.schema_id}/0001/something.gif"
1432
- end
1740
+ Content[@page.id].must_be_nil
1433
1741
  end
1434
1742
  end
1435
1743
  end