OSDN Git Service

speech balloon template changed
authoryasushiito <yasushiito@git.sourceforge.jp>
Sat, 2 Jun 2012 00:49:10 +0000 (09:49 +0900)
committeryasushiito <yasushiito@git.sourceforge.jp>
Sat, 2 Jun 2012 00:49:10 +0000 (09:49 +0900)
18 files changed:
app/controllers/speech_balloon_templates_controller.rb
app/models/original_picture.rb
app/models/speech_balloon_template.rb
app/models/system_picture.rb
app/views/speech_balloon_templates/browse.html.erb
app/views/speech_balloon_templates/list.html.erb
app/views/speech_balloon_templates/show.html.erb
app/views/system/browse.html.erb
config/application.rb
db/migrate/20120102071851_create_system_pictures.rb
db/migrate/20120424100555_create_speech_balloon_templates.rb
db/schema.rb
spec/factories.rb
spec/json/invalid_speech_balloon_templates.json [new file with mode: 0644]
spec/json/speech_balloon_template.json [new file with mode: 0644]
spec/json/speech_balloon_templates.json [new file with mode: 0644]
spec/models/speech_balloon_template_spec.rb
spec/models/system_picture_spec.rb

index d17ff32..cbb2d5b 100644 (file)
@@ -33,7 +33,7 @@ class SpeechBalloonTemplatesController < ApplicationController
   # GET /speech_balloon_templates
   # GET /speech_balloon_templates.json
   def index
-    @speech_balloon_templates = SpeechBalloon.all
+    @speech_balloon_templates = SpeechBalloonTemplate.all
 
     respond_to do |format|
       format.html # index.html.erb
@@ -44,25 +44,21 @@ class SpeechBalloonTemplatesController < ApplicationController
   # GET /speech_balloon_templates/1
   # GET /speech_balloon_templates/1.json
   def show
-    @speech_balloon_template = SpeechBalloon.find(params[:id], include: {:balloon_templates => :speech_templates})
+    @speech_balloon_template = SpeechBalloonTemplate.find(params[:id])
 
     respond_to do |format|
       format.html # show.html.erb
       format.json {
-        render :json => @speech_balloon_template.to_json(include: {
-          :balloon_templates => {:include => :speech_templates}
-        })
+        render :json => @speech_balloon_template.to_json()
       }
       format.jsonp {
-        render :json => "callback(" + @speech_balloon_template.to_json(include: {
-          :balloon_templates => {:include => :speech_templates}
-        }) + ")"
+        render :json => "callback(" + @speech_balloon_template.to_json() + ")"
       }
     end
   end
 
   def list
-    @speech_balloon_templates = SpeechBalloon.all
+    @speech_balloon_templates = SpeechBalloonTemplate.all
 
     respond_to do |format|
       format.html { render layout: 'system' }
@@ -71,7 +67,7 @@ class SpeechBalloonTemplatesController < ApplicationController
   end
 
   def browse
-    @speech_balloon_template = SpeechBalloon.find(params[:id])
+    @speech_balloon_template = SpeechBalloonTemplate.find(params[:id])
 
     respond_to do |format|
       format.html { render layout: 'system' }
@@ -82,7 +78,7 @@ class SpeechBalloonTemplatesController < ApplicationController
   # POST /speech_balloon_templates
   # POST /speech_balloon_templates.json
   def create
-    @speech_balloon_template = SpeechBalloon.new(params[:speech_balloon_template])
+    @speech_balloon_template = SpeechBalloonTemplate.new(params[:speech_balloon_template])
     if em = validate_param(params[:speech_balloon_template])
       respond_to do |format|
         format.json { render json: em, status: :unprocessable_entity }
@@ -103,11 +99,11 @@ class SpeechBalloonTemplatesController < ApplicationController
   # DELETE /speech_balloon_templates/1
   # DELETE /speech_balloon_templates/1.json
   def destroy
-    @speech_balloon_template = SpeechBalloon.find(params[:id])
+    @speech_balloon_template = SpeechBalloonTemplate.find(params[:id])
     @speech_balloon_template.destroy
 
     respond_to do |format|
-      format.html { redirect_to speech_balloon_templates_url }
+      format.html { redirect_to :action => :list }
       format.json { head :ok }
     end
   end
index 91a6791..722e110 100644 (file)
@@ -12,10 +12,6 @@ class OriginalPicture < ActiveRecord::Base
   
   before_destroy :destroy_with_file
   
-  def validate
-    errors.add(:filesize, 'size over(1MB)') if self.filesize > 1000000
-  end
-  
   def supply_default art
     self.license_id = art.default_license_id if self.license_id.blank?
   end
index de6d053..b5f12e0 100644 (file)
@@ -1,12 +1,89 @@
 class SpeechBalloonTemplate < ActiveRecord::Base
-  has_many :balloon_templates, :dependent => :destroy
-  has_many :speech_templates, :dependent => :destroy
-  accepts_nested_attributes_for :balloon_templates
-  accepts_nested_attributes_for :speech_templates
+  has_many :speech_balloons
+  
+  validates :name, :presence => true, :uniqueness => true, :length => {:maximum => 50}
+  validates :classname, :presence => true, :length => {:maximum => 50}
+  validates :caption, :presence => true, :length => {:maximum => 30}
+  validates :t, :presence => true, :numericality => {:greater_than_or_equal_to => 0}
+  validates :settings, :presence => true
+  
   before_create :supply_t
   
   def supply_t
-    self.t = SpeechBalloon.maximum(:t).to_i + 1
+    m = SpeechBalloonTemplate.maximum(:t)
+    t = m.blank? ? -1 : m.to_i
+    self.t = t + 1
+  end
+  
+  def pic_rehash hash
+    res = true
+    hash["templates"].each do |n, v|
+      d = (v["balloon"] || {})["system_picture"]
+      next nil if d.blank?
+      sp = SystemPicture.store Base64.decode64(d.to_s)
+      unless sp
+        res = false
+        break
+      end
+      v["balloon"]["system_picture_id"] = sp.id
+      v["balloon"].delete "system_picture"
+    end
+    res
+  end
+  
+  def store hash
+    res = nil
+    SpeechBalloonTemplate.transaction do
+      res = pic_rehash hash
+      if res
+        data = {:classname => hash["classname"], :caption => hash["caption"], :settings => hash.to_s}
+        self.attributes = data
+        res = self.save
+      end
+      raise ActiveRecord::Rollback unless res
+    end
+    res
+  end
+  
+  def self.store name, hash
+    st = SpeechBalloonTemplate.find_by_name name
+    st = SpeechBalloonTemplate.new(:name => name) unless st
+    st.store hash
+    st
+  end
+  
+  def self.parse(data)
+    res = data
+    begin
+      res = JSON.parse(data) if data.is_a?(String)
+    rescue 
+      return false
+    end
+    res
+  end
+  
+  def self.import(data)
+    d = SpeechBalloonTemplate.parse(data)
+    return false unless d
+    res = []
+    SpeechBalloonTemplate.transaction do
+      d.each do |name, hash|
+        st = SpeechBalloonTemplate.store name, hash
+        res.push(st) unless st.valid?
+      end
+      raise ActiveRecord::Rollback unless res.empty?
+    end
+    res
+  end
+  
+  def self.import_file(filename)
+    t = nil
+    begin
+      t = File.open(filename, 'r').read
+    rescue
+      return false
+    end
+    SpeechBalloonTemplate.import t
   end
   
 end
index 9d01831..db857b7 100644 (file)
@@ -2,11 +2,13 @@ class SystemPicture < ActiveRecord::Base
   has_many :balloons
   has_many :balloon_templates
   
-  before_destroy :destroy_with_file
+  validates :ext, :presence => true, :length => {:maximum => 4}, :inclusion => {:in => ['png', 'jpeg', 'gif']}
+  validates :width, :presence => true, :numericality => true, :natural_number => true
+  validates :height, :presence => true, :numericality => true, :natural_number => true
+  validates :filesize, :presence => true, :numericality => {:greater_than => 0, :less_than_or_equal_to => 2000000}, :natural_number => true
+  validates :md5, :presence => true, :length => {:maximum => 32}
   
-  def validate
-    errors.add(:filesize, 'size over(1MB)') if self.filesize > 1000000
-  end
+  before_destroy :destroy_with_file
   
   def destroy_with_file
     PictureIO.system_picture_io.delete self.filename
@@ -28,10 +30,38 @@ class SystemPicture < ActiveRecord::Base
     '/system_pictures/' + filename
   end
   
-  def store(rimg)
-    bindata = rimg.to_blob
-    PictureIO.system_picture_io.put bindata, self.filename
-    true
+  def data_to_mgk picture_data
+    begin
+      mgk = Magick::Image.from_blob(picture_data).shift
+    rescue 
+      self.errors.add :base, 'magick failed'
+      return false
+    end
+    mgk
+  end
+  
+  def store(picture_data)
+    mgk = data_to_mgk picture_data
+    return false unless mgk
+    res = false
+    self.attributes = {:ext => mgk.format.downcase, :width => mgk.columns, :height => mgk.rows, 
+      :filesize => mgk.filesize
+    }
+    self.md5 = Digest::MD5.hexdigest(picture_data) unless self.md5
+    SystemPicture.transaction do
+      if res = self.save
+        res = PictureIO.system_picture_io.put(picture_data, self.filename)
+      end
+    end
+    res
+  end
+  
+  def self.store(picture_data)
+    md5 = Digest::MD5.hexdigest(picture_data)
+    sp = SystemPicture.find_by_md5(md5)
+    sp = SystemPicture.new() unless sp
+    res = sp.store picture_data
+    res ? sp : nil
   end
   
   def restore(subdir = nil)
index 154e592..d447525 100644 (file)
@@ -2,30 +2,27 @@
 
 <p>
   <b>Name:</b>
-  <%= h @speech_balloon.name %>
+  <%= h @speech_balloon_template.name %>
 </p>
 
 <p>
-  <b>Tail count:</b>
-  <%= @speech_balloon.tail_count %>
+  <b>classname:</b>
+  <%= h @speech_balloon_template.classname %>
 </p>
 
 <p>
-  <b>size count:</b>
-  <%= @speech_balloon.size_count %>
+  <b>caption:</b>
+  <%= h @speech_balloon_template.caption %>
 </p>
 
 <p>
   <b>t:</b>
-  <%= @speech_balloon.t %>
+  <%= @speech_balloon_template.t %>
 </p>
-<% @speech_balloon.balloon_templates.each do |balloon_template| -%>\r
-  <div>\r
-    <p>\r
-      Tail:<%= balloon_template.tail -%>\r
-      Size:<%= balloon_template.size -%>\r
-    </p>\r
-    <img src="<%= balloon_template.system_picture.url -%>" />\r
-  </div>\r
-<% end -%>\r
+
+<p>
+  <b>settings:</b>
+  <%= h @speech_balloon_template.settings %>
+</p>
+
 <%= link_to 'Back', :action => :list %>
index d9e7471..175673d 100644 (file)
@@ -1,27 +1,27 @@
-<h1>Listing speech_balloons</h1>
+<h1>Listing speech_balloon_templates</h1>
 <table>
   <tr>
     <th>id</th>
     <th>Name</th>
-    <th>Tail count</th>
-    <th>size count</th>
+    <th>classname</th>
+    <th>caption</th>
     <th>t</th>
     <th>created_at</th>
     <th>updated_at</th>
     <th></th>
   </tr>
 
-<% @speech_balloons.each do |speech_balloon| %>
+<% @speech_balloon_templates.each do |speech_balloon_template| %>
   <tr>
-    <td><%= link_to speech_balloon.id, :action => :browse, :id => speech_balloon.id %></td>
-    <td><%= h speech_balloon.name %></td>
-    <td><%= speech_balloon.tail_count %></td>
-    <td><%= speech_balloon.size_count %></td>
-    <td><%= speech_balloon.t %></td>
-    <td><%= speech_balloon.created_at %></td>
-    <td><%= speech_balloon.updated_at %></td>
+    <td><%= link_to speech_balloon_template.id, :action => :browse, :id => speech_balloon_template.id %></td>
+    <td><%= h speech_balloon_template.name %></td>
+    <td><%= h speech_balloon_template.classname %></td>
+    <td><%= h speech_balloon_template.caption %></td>
+    <td><%= speech_balloon_template.t %></td>
+    <td><%= speech_balloon_template.created_at %></td>
+    <td><%= speech_balloon_template.updated_at %></td>
     <td>
-      <%= link_to 'Destroy', speech_balloon, confirm: 'Are you sure?', method: :delete %>
+      <%= link_to 'Destroy', speech_balloon_template, confirm: 'Are you sure?', method: :delete %>
     </td>
   </tr>
 <% end %>
index 6ef4812..180db12 100644 (file)
@@ -2,30 +2,27 @@
 
 <p>
   <b>Name:</b>
-  <%= h @speech_balloon.name %>
+  <%= h @speech_balloon_template.name %>
 </p>
 
 <p>
-  <b>Tail count:</b>
-  <%= @speech_balloon.tail_count %>
+  <b>classname:</b>
+  <%= h @speech_balloon_template.classname %>
 </p>
 
 <p>
-  <b>size count:</b>
-  <%= @speech_balloon.size_count %>
+  <b>caption:</b>
+  <%= h @speech_balloon_template.caption %>
 </p>
 
 <p>
   <b>t:</b>
-  <%= @speech_balloon.t %>
+  <%= @speech_balloon_template.t %>
 </p>
-<% @speech_balloon.balloon_templates.each do |balloon_template| -%>\r
-  <div>\r
-    <p>\r
-      Tail:<%= balloon_template.tail -%>\r
-      Size:<%= balloon_template.size -%>\r
-    </p>\r
-    <img src="<%= balloon_template.system_picture.url -%>" />\r
-  </div>\r
-<% end -%>\r
-<%= link_to 'Back', speech_balloons_path %>
+
+<p>
+  <b>settings:</b>
+  <%= h @speech_balloon_template.settings %>
+</p>
+
+<%= link_to 'Back', speech_balloon_templates_path %>
index e6d7f83..836bacd 100644 (file)
   </tr>
   <tr>
     <td>
+      <%= link_to 'speech_balloons', :controller => 'speech_balloons', :action => :list %>
+    </td>
+  </tr>
+  <tr>
+    <td>
       <%= link_to 'balloons', :controller => 'balloons', :action => :list %>
     </td>
   </tr>
 <table>
   <tr>
     <td>
-      <%= link_to 'speech_balloons', :controller => 'speech_balloons', :action => :list %>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <%= link_to 'balloon_templates', :controller => 'balloon_templates', :action => :list %>
-    </td>
-  </tr>
-  <tr>
-    <td>
-      <%= link_to 'speech_templates', :controller => 'speech_templates', :action => :list %>
+      <%= link_to 'speech_balloon_templates', :controller => 'speech_balloon_templates', :action => :list %>
     </td>
   </tr>
 </table>
index 1f18627..2b7bcb8 100644 (file)
@@ -7,6 +7,7 @@ require "action_mailer/railtie"
 require "active_resource/railtie"
 require "sprockets/railtie"
 # require "rails/test_unit/railtie"
+require 'digest/md5'
 
 if defined?(Bundler)
   # If you precompile assets before deploying to production, use this line
@@ -52,11 +53,11 @@ config.assets.initialize_on_precompile = false
     # Version of your assets, change this if you want to expire all your assets
     config.assets.version = '1.0'
     
-    config.autoload_paths += %W(#{config.root}/lib/validators)\r
+    config.autoload_paths += %W(#{config.root}/lib/validators)
   end
 end
 module ActiveRecord
   class Forbidden < ActiveRecordError
-  end\r
+  end
 end
-\r
+
index f62055d..ae89408 100644 (file)
@@ -5,6 +5,7 @@ class CreateSystemPictures < ActiveRecord::Migration
       t.integer :width, :null => false
       t.integer :height, :null => false
       t.integer :filesize, :null => false
+      t.string :md5, :null => false, :limit => 32
 
       t.timestamps
     end
index 66e05d6..15dc922 100644 (file)
@@ -1,8 +1,15 @@
 class CreateSpeechBalloonTemplates < ActiveRecord::Migration
   def change
     create_table :speech_balloon_templates do |t|
+      t.string :name, :null => false, :limit => 50
+      t.string :classname, :null => false, :limit => 50
+      t.string :caption, :null => false, :limit => 30
+      t.integer :t, :null => false, :default => 0
+      t.string :settings, :null => false
 
       t.timestamps
     end
+    add_index :speech_balloon_templates, [:name], :unique => true, :name => 'speech_balloon_templates_name'
+    add_index :speech_balloon_templates, [:t], :unique => true, :name => 'speech_balloon_templates_t'
   end
 end
index 7d8a12f..2d863a7 100644 (file)
@@ -216,10 +216,18 @@ ActiveRecord::Schema.define(:version => 20120424100555) do
   end\r
 \r
   create_table "speech_balloon_templates", :force => true do |t|\r
+    t.string   "name",       :limit => 50,                :null => false\r
+    t.string   "classname",  :limit => 50,                :null => false\r
+    t.string   "caption",    :limit => 30,                :null => false\r
+    t.integer  "t",                        :default => 0, :null => false\r
+    t.string   "settings",                                :null => false\r
     t.datetime "created_at"\r
     t.datetime "updated_at"\r
   end\r
 \r
+  add_index "speech_balloon_templates", ["name"], :name => "speech_balloon_templates_name", :unique => true\r
+  add_index "speech_balloon_templates", ["t"], :name => "speech_balloon_templates_t", :unique => true\r
+\r
   create_table "speech_balloons", :force => true do |t|\r
     t.string   "name",                      :null => false\r
     t.integer  "tail_count", :default => 1, :null => false\r
@@ -261,10 +269,11 @@ ActiveRecord::Schema.define(:version => 20120424100555) do
   add_index "speeches", ["balloon_id"], :name => "index_speeches_on_balloon_id"\r
 \r
   create_table "system_pictures", :force => true do |t|\r
-    t.string   "ext",        :null => false\r
-    t.integer  "width",      :null => false\r
-    t.integer  "height",     :null => false\r
-    t.integer  "filesize",   :null => false\r
+    t.string   "ext",                      :null => false\r
+    t.integer  "width",                    :null => false\r
+    t.integer  "height",                   :null => false\r
+    t.integer  "filesize",                 :null => false\r
+    t.string   "md5",        :limit => 32, :null => false\r
     t.datetime "created_at"\r
     t.datetime "updated_at"\r
   end\r
index 6cb4f0a..0ce9e0e 100644 (file)
@@ -140,9 +140,17 @@ Factory.define :system_picture, :class => SystemPicture do |sp|
   sp.ext 'png'
   sp.width 222
   sp.height 111
-  sp.fileseze 100000
+  sp.filesize 100000
+  sp.md5 '098f6bcd4621d373cade4e832627b4f6'
 end
 
+Factory.define :speech_balloon_template, :class => SpeechBalloonTemplate do |sbt|
+  sbt.name 'Plain@pettanr.com'
+  sbt.classname 'Plain'
+  sbt.caption 'normal fuki'
+  sbt.t 0
+  sbt.settings '{}'
+end
 Factory.define :balloon, :class => Balloon do |balloon|
   balloon.panel_id 1
   balloon.balloon_template_id 1 
diff --git a/spec/json/invalid_speech_balloon_templates.json b/spec/json/invalid_speech_balloon_templates.json
new file mode 100644 (file)
index 0000000..9ec2d2c
--- /dev/null
@@ -0,0 +1,59 @@
+{\r
+  "square@pettan.com": {\r
+    "classname": "Square",\r
+    "caption": "\92·\95û\8c`",\r
+    "default_width": 150,\r
+    "default_height": 150,\r
+    "templates": {\r
+      "all": {\r
+        "balloon": {\r
+          "system_picture": "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"\r
+        },\r
+        "speech": {\r
+          "x_rate": 1,\r
+          "y_rate": 1,\r
+          "width_rate": 98,\r
+          "height_rate": 98\r
+        }\r
+      }\r
+    }\r
+  },\r
+  "square@pettan.com": {\r
+    "classname": "Square",\r
+    "caption": "\92·\95û\8c`",\r
+    "default_width": 150,\r
+    "default_height": 150,\r
+    "templates": {\r
+      "all": {\r
+        "balloon": {\r
+          "system_picture": "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"\r
+        },\r
+        "speech": {\r
+          "x_rate": 1,\r
+          "y_rate": 1,\r
+          "width_rate": 98,\r
+          "height_rate": 98\r
+        }\r
+      }\r
+    }\r
+  },\r
+  "square@pettan.com": {\r
+    "classname": "Square",\r
+    "caption": "\92·\95û\8c`",\r
+    "default_width": 150,\r
+    "default_height": 150,\r
+    "templates": {\r
+      "all": {\r
+        "balloon": {\r
+          "system_picture": "R0lGODlhLAEsAXAAACH5BAEAAPwALAAAAAAsASwBhwAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAAAj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOq3MeypcuXMGPKnEmzps2bOHPq3Mmzp8+fQIP2LCi0qNGjSJMqXcq0KU2iTqNKnUq1qtWoUK9q3cq1q1emWb+KHUu27NewZtOqXcv2J9q2cOPKZft2rt27eKvWzcu3r1+ge/8KHkz4ZeDCiBPjPay4sWO1jB9Lnsw1MuXLmJ1azsy5s9DNnkOLxgl6tOnTLkujXi1aNevXmV3Dni1ZNu3biW3j3i1YN+/feX0DHy5XOPHja40jX05WOfPnXZ1Dn25VOvXrWAli3w7XOvfvRr2DGR/vVjv589HNo19fXT3799kHwp+v2T39++GZ7ePfX14+///9CQTggDyJR+B5Bh44XoIKfsdgg9s9COF1Ek44XYUWPodhhsttyOFxHn44XIgi/kZiibudiOJtKq44W4suvgZjjKvNSONpNt44Wo46hsZjj539CGRs+g3JnpBGUoZkkrUVySR5Sz7ZWJRS5uZkldxRiSVhWm7Z25VeUtdlmH2NSWZwYJ7JnJlq2sVmm8WlCSdxlm/O2VaddiYnZ5684cmnWX7+2dyegtIWaKFnEYqojIouWmOjjuIIaaQ7Tkqpj5ZeGmSmmhLpX6e4HQrqVKKOGp+AphrKaaqPlcrqUq6+mlSssh5Fa61F3YprULruGiAAvkr6abCYDkvspsYe6ymqyiLLbLPLAgstZ71Oa1O11j61arZubsttnMl+qxi24hrmbbl0nYsuZGfqrgtou+4OGm68f5Hrrr3r4ouuvuXyK66/3wLMrcDZEmytwdMiDK3CzTKsrMPHQkysxMFS7KvFu2KMq8a1ciyrx6+CzKrIqZJsqsmjogyqyp2yrKnLl8JMqcyR0uyozYvijKjOhfIsZKjPfwLNp9B5Em2n0XMiDafSbTKtptNnQk2m1GFS7aXVW2KNpdZVci2l10+CzaTYSZJtpNlDog2k2j2yraPbN8JNo9wx0u2i3SvijaLeJfItot8fAs6h4BkSbqHhEyIOoeINMq59oOMHQk6g5ANSDqDl/2HOn+b7cY6f5/eBTp/o85EOn+nvoX4kvPQmOm/rfKm+nuzo0Y4g67BXhnvuWtkO5e68t/d68HP5viDwxJOKfPKnSss8msM/n2700rNLffXvXo+9vM9u393y3tsKfvi5jk8+r+af/6v6d6bPfoHuv694k/Hg0e9g/PKThn/+1+7Pv7ba+5/yAijA5hVwLPbLkv8OaC4CMhBWC3wgSxIYoQhKkILYwSCFLPhADYqJgwz04IVAeEARQseEGiJhAVG4JhUKkIUdcuH/YIgcGoJIhvyzIZ1wmD8djoiH8vMhcIRoIiC+j4h9MiL7S5CYIiWqj4mhcuL5oMgiKZKPiqpyoASRgsUXWTF8XYRNGBmlxS3mp4xm/MwXvTdG1rTxUWhMI2BUQsc62vGOeMyjHvfIxz768SMBAQA7"\r
+        },\r
+        "speech": {\r
+          "x_rate": 1,\r
+          "y_rate": 1,\r
+          "width_rate": 98,\r
+          "height_rate": 98\r
+        }\r
+      }\r
+    }\r
+  }\r
+}\r
diff --git a/spec/json/speech_balloon_template.json b/spec/json/speech_balloon_template.json
new file mode 100644 (file)
index 0000000..abe92ec
--- /dev/null
@@ -0,0 +1,21 @@
+{\r
+  "square@pettan.com": {\r
+    "classname": "Square",\r
+    "caption": "長方形",\r
+    "default_width": 150,\r
+    "default_height": 150,\r
+    "templates": {\r
+      "all": {\r
+        "balloon": {\r
+          "system_picture": "R0lGODlhLAEsAXAAACH5BAEAAPwALAAAAAAsASwBhwAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAAAj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOq3MeypcuXMGPKnEmzps2bOHPq3Mmzp8+fQIP2LCi0qNGjSJMqXcq0KU2iTqNKnUq1qtWoUK9q3cq1q1emWb+KHUu27NewZtOqXcv2J9q2cOPKZft2rt27eKvWzcu3r1+ge/8KHkz4ZeDCiBPjPay4sWO1jB9Lnsw1MuXLmJ1azsy5s9DNnkOLxgl6tOnTLkujXi1aNevXmV3Dni1ZNu3biW3j3i1YN+/feX0DHy5XOPHja40jX05WOfPnXZ1Dn25VOvXrWAli3w7XOvfvRr2DGR/vVjv589HNo19fXT3799kHwp+v2T39++GZ7ePfX14+///9CQTggDyJR+B5Bh44XoIKfsdgg9s9COF1Ek44XYUWPodhhsttyOFxHn44XIgi/kZiibudiOJtKq44W4suvgZjjKvNSONpNt44Wo46hsZjj539CGRs+g3JnpBGUoZkkrUVySR5Sz7ZWJRS5uZkldxRiSVhWm7Z25VeUtdlmH2NSWZwYJ7JnJlq2sVmm8WlCSdxlm/O2VaddiYnZ5684cmnWX7+2dyegtIWaKFnEYqojIouWmOjjuIIaaQ7Tkqpj5ZeGmSmmhLpX6e4HQrqVKKOGp+AphrKaaqPlcrqUq6+mlSssh5Fa61F3YprULruGiAAvkr6abCYDkvspsYe6ymqyiLLbLPLAgstZ71Oa1O11j61arZubsttnMl+qxi24hrmbbl0nYsuZGfqrgtou+4OGm68f5Hrrr3r4ouuvuXyK66/3wLMrcDZEmytwdMiDK3CzTKsrMPHQkysxMFS7KvFu2KMq8a1ciyrx6+CzKrIqZJsqsmjogyqyp2yrKnLl8JMqcyR0uyozYvijKjOhfIsZKjPfwLNp9B5Em2n0XMiDafSbTKtptNnQk2m1GFS7aXVW2KNpdZVci2l10+CzaTYSZJtpNlDog2k2j2yraPbN8JNo9wx0u2i3SvijaLeJfItot8fAs6h4BkSbqHhEyIOoeINMq59oOMHQk6g5ANSDqDl/2HOn+b7cY6f5/eBTp/o85EOn+nvoX4kvPQmOm/rfKm+nuzo0Y4g67BXhnvuWtkO5e68t/d68HP5viDwxJOKfPKnSss8msM/n2700rNLffXvXo+9vM9u393y3tsKfvi5jk8+r+af/6v6d6bPfoHuv694k/Hg0e9g/PKThn/+1+7Pv7ba+5/yAijA5hVwLPbLkv8OaC4CMhBWC3wgSxIYoQhKkILYwSCFLPhADYqJgwz04IVAeEARQseEGiJhAVG4JhUKkIUdcuH/YIgcGoJIhvyzIZ1wmD8djoiH8vMhcIRoIiC+j4h9MiL7S5CYIiWqj4mhcuL5oMgiKZKPiqpyoASRgsUXWTF8XYRNGBmlxS3mp4xm/MwXvTdG1rTxUWhMI2BUQsc62vGOeMyjHvfIxz768SMBAQA7"\r
+        },\r
+        "speech": {\r
+          "x_rate": 1,\r
+          "y_rate": 1,\r
+          "width_rate": 98,\r
+          "height_rate": 98\r
+        }\r
+      }\r
+    }\r
+  }\r
+}\r
diff --git a/spec/json/speech_balloon_templates.json b/spec/json/speech_balloon_templates.json
new file mode 100644 (file)
index 0000000..dadd1e9
--- /dev/null
@@ -0,0 +1,70 @@
+{\r
+  "square@pettan.com": {\r
+    "classname": "Square",\r
+    "caption": "\92·\95û\8c`",\r
+    "default_width": 150,\r
+    "default_height": 150,\r
+    "templates": {\r
+      "all": {\r
+        "balloon": {\r
+          "system_picture": "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"\r
+        },\r
+        "speech": {\r
+          "x_rate": 1,\r
+          "y_rate": 1,\r
+          "width_rate": 98,\r
+          "height_rate": 98\r
+        }\r
+      }\r
+    }\r
+  },\r
+  "square with size@pettan.com": {\r
+    "classname": "SquareWithSize",\r
+    "caption": "\92·\95û\8c`\89ü",\r
+    "default_width": 150,\r
+    "default_height": 100,\r
+    "size_count": 3,\r
+    "width_offset": 0,\r
+    "height_offset": 0,\r
+    "width_step": 100,\r
+    "height_step": 100,\r
+    "templates": {\r
+      "small": {\r
+        "balloon": {\r
+          "size": 0,\r
+          "system_picture": "R0lGODlhZABkAPcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAABkAGQAAAj/AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGAfu28ixo8ePIEOKHEmypMmOBU+qXMmypcqULmPKnFkSJs2bOGXazMmzJ8mdPoMK3Qd0qFGcRY8qjZl0qdOXBJ9Kbdl0qtWPVa9qJRp1q1ePWb9KDSvWKdmySs+iNap2rdC2bn3CjctzLl2kXe+OzavXLN++af8CZit48NvChuUiTlx3MWO8Gh8PtSv5JOXKNR1jZqp5M9XOnldeDg1yNGmwoE9njqx6punWXFnDdvm6dW3Vt0/nJr07dG/PvzcHxzy8cnHJxx8nZ7w8cXPDzwdHBzy9b3W91+9mp7s9bne339eGX0c7vmx5see/pve6fmt7re+vxrc6f2r9vbJns7z/lL/f/PpBBWCAJvm3lIGBDUjgT6ktyBGCR0FImIIOhiThZA1WeOFhFFaIVYYObhiUiIp16CFKGaWo4oostuhiQQEBADs="\r
+        },\r
+        "speech": {\r
+          "x_rate": 3,\r
+          "y_rate": 3,\r
+          "width_rate": 94,\r
+          "height_rate": 94\r
+        }\r
+      },\r
+      "middle": {\r
+        "balloon": {\r
+          "size": 1,\r
+          "system_picture": "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"\r
+        },\r
+        "speech": {\r
+          "x_rate": 2,\r
+          "y_rate": 2,\r
+          "width_rate": 96,\r
+          "height_rate": 96\r
+        }\r
+      },\r
+      "large": {\r
+        "balloon": {\r
+          "size": 2,\r
+          "system_picture": "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"\r
+        },\r
+        "speech": {\r
+          "x_rate": 1,\r
+          "y_rate": 1,\r
+          "width_rate": 98,\r
+          "height_rate": 98\r
+        }\r
+      }\r
+    }\r
+  }\r
+}\r
index 1445421..3540bec 100644 (file)
@@ -1,5 +1,405 @@
+# -*- encoding: utf-8 -*-
 require 'spec_helper'
-
+#フキダシテンプレート
 describe SpeechBalloonTemplate do
-  pending "add some examples to (or delete) #{__FILE__}"
+  before do
+    #テストデータを用意してね
+    @f = Rails.root + 'spec/json/speech_balloon_template.json'
+    @t = File.open(@f, 'r').read
+    @j = JSON.parse @t
+    @fs = Rails.root + 'spec/json/speech_balloon_templates.json'
+    @ts = File.open(@fs, 'r').read
+    @js = JSON.parse @ts
+    @fes = Rails.root + 'spec/json/invalid_speech_balloon_templates.json'
+    @tes = File.open(@fes, 'r').read
+    @jes = JSON.parse @tes
+    
+    @admin = Factory :admin
+    @license = Factory :license
+  end
+  
+  describe '検証に於いて' do
+    before do
+    end
+    
+    it 'オーソドックスなデータなら通る' do
+      @st = Factory.build :speech_balloon_template
+      @st.should be_valid
+    end
+    
+    context 'nameを検証するとき' do
+      before do
+        @st = Factory.build :speech_balloon_template
+      end
+      it 'テストデータの確認' do
+        @st.name = 'abcdefghi0abcdefghi0abcdefghi0abcdefghi0abcdefghi0'
+        @st.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @st.name = ''
+        @st.should_not be_valid
+      end
+      it '51文字以上なら失敗する' do
+        @st.name = 'a'*51
+        @st.should_not be_valid
+      end
+    end
+    context 'classnameを検証するとき' do
+      before do
+        @st = Factory.build :speech_balloon_template
+      end
+      it 'テストデータの確認' do
+        @st.classname = 'abcdefghi0abcdefghi0abcdefghi0abcdefghi0abcdefghi0'
+        @st.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @st.classname = ''
+        @st.should_not be_valid
+      end
+      it '51文字以上なら失敗する' do
+        @st.classname = 'a'*51
+        @st.should_not be_valid
+      end
+    end
+    context 'captionを検証するとき' do
+      before do
+        @st = Factory.build :speech_balloon_template
+      end
+      it 'テストデータの確認' do
+        @st.caption = 'abcdefghi0abcdefghi0abcdefghi0'
+        @st.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @st.caption = ''
+        @st.should_not be_valid
+      end
+      it '31文字以上なら失敗する' do
+        @st.caption = 'a'*31
+        @st.should_not be_valid
+      end
+    end
+    context 'tを検証するとき' do
+      before do
+        @st = Factory.build :speech_balloon_template
+      end
+      it 'テストデータの確認' do
+        @st.t = 0
+        @st.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @st.t = nil
+        @st.should_not be_valid
+      end
+      it '数値でなければ失敗する' do
+        @st.t = 'a'
+        @st.should_not be_valid
+      end
+      it '負なら失敗する' do
+        @st.t = -1
+        @st.should_not be_valid
+      end
+    end
+    context 'settingsを検証するとき' do
+      before do
+        @st = Factory.build :speech_balloon_template
+      end
+      it 'テストデータの確認' do
+        @st.settings = '{}'
+        @st.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @st.settings = nil
+        @st.should_not be_valid
+      end
+    end
+  end
+  
+  describe 'データ補充に於いて' do
+    before do
+    end
+    
+  end
+  
+  describe 'Json解析に於いて' do
+    before do
+    end
+    context 'テキストを渡されたとき' do
+      it 'Json解析している' do
+        JSON.should_receive(:parse).with(@t).exactly(1)
+        r = SpeechBalloonTemplate.parse @t
+      end
+      it 'Hashで返す' do
+        r = SpeechBalloonTemplate.parse @t
+        r.is_a?(Hash).should be_true
+      end
+    end
+    context 'パース失敗したとき' do
+      it 'Falseを返す' do
+        JSON.should_receive(:parse).with(any_args).and_raise('StandardError')
+        r = SpeechBalloonTemplate.parse @t
+        r.should be_false
+      end
+    end
+  end
+  
+  describe '対象フキダシテンプレートの取得に於いて' do
+    before do
+      @st = Factory :speech_balloon_template
+    end
+    context 'nameが一致するフキダシテンプレートがないとき' do
+      it '新規フキダシテンプレートを準備して返す' do
+        r = SpeechBalloonTemplate.update_speech_balloon_template 'newtemplate', {:classname => 'New'}
+        r.should be_a_new SpeechBalloonTemplate
+        r[:classname].should eq 'New'
+      end
+    end
+    context 'nameが一致するフキダシテンプレートがあるとき' do
+      it '該当フキダシテンプレートを値設定して返す' do
+        r = SpeechBalloonTemplate.update_speech_balloon_template @st.name, {:classname => 'Update'}
+        r.is_a?(SpeechBalloonTemplate).should be_true
+        r.should_not be_a_new SpeechBalloonTemplate
+        r[:classname].should eq 'Update'
+      end
+    end
+  end
+  
+  describe 'フキダシテンプレート保存に於いて' do
+    before do
+      @st = Factory.build :speech_balloon_template
+    end
+    context 'つつがなく終わるとき' do
+      it '保存を問い合わせている' do
+        SpeechBalloonTemplate.any_instance.should_receive(:save).exactly(1)
+        @st.store
+      end
+      it 'フキダシテンプレートが作成されている' do
+        lambda {
+          @st.store
+        }.should change SpeechBalloonTemplate, :count
+      end
+    end
+  end
+  
+  describe 'フキダシテンプレート更新に於いて' do
+    before do
+      @st = Factory :speech_balloon_template
+      @attr = {:name => @st.name, :classname => 'Update', :t => 0, :caption => 'yp', :settings => '{}'}
+      @newattr = {:name => @st.name, :classname => 'New', :t => 1, :caption => 'nw', :settings => '{}'}
+    end
+    context 'つつがなく終わるとき' do
+      it '対象フキダシテンプレートを問い合わせている' do
+        SpeechBalloonTemplate.stub(:update_speech_balloon_template).with(any_args).and_return(SpeechBalloonTemplate.new(@attr))
+        SpeechBalloonTemplate.should_receive(:update_speech_balloon_template).exactly(1)
+        SpeechBalloonTemplate.store @st.name, @attr
+      end
+      context '新規のとき' do
+        it 'フキダシテンプレートを保存しようとしている' do
+          SpeechBalloonTemplate.any_instance.should_receive(:save).exactly(1)
+          SpeechBalloonTemplate.store 'newtemplate', @newattr
+        end
+        it 'フキダシテンプレートが作成されている' do
+          lambda {
+            SpeechBalloonTemplate.store 'newtemplate', @newattr
+          }.should change SpeechBalloonTemplate, :count
+        end
+      end
+      context '更新のとき' do
+        it 'フキダシテンプレートを保存しようとしている' do
+          SpeechBalloonTemplate.any_instance.should_receive(:save).exactly(1)
+          SpeechBalloonTemplate.store @st.name, @attr
+        end
+        it 'フキダシテンプレートの数に変化がない' do
+          lambda {
+            SpeechBalloonTemplate.store @st.name, @attr
+          }.should_not change SpeechBalloonTemplate, :count
+        end
+      end
+      it '属性が一致している' do
+        r = SpeechBalloonTemplate.store 'newtemplate', @newattr
+        r.classname.should eq 'New'
+      end
+      it '保存されたSpeechBalloonTemplateオブジェクトを返す' do
+        r = SpeechBalloonTemplate.store 'newtemplate', @newattr
+p r.errors.full_messages
+        r.should_not be_a_new SpeechBalloonTemplate
+      end
+    end
+    context 'フキダシテンプレートの作成に失敗するとき' do
+      before do
+        SpeechBalloonTemplate.any_instance.stub(:save).with(any_args).and_return(false)
+      end
+      context '新規のとき' do
+        it 'フキダシテンプレートに変化がない' do
+          lambda {
+            r = SpeechBalloonTemplate.store 'newtemplate', @newattr
+          }.should_not change License, :count
+        end
+      end
+      context '更新のとき' do
+        it 'フキダシテンプレート属性に変化がない' do
+          lambda {
+            r = SpeechBalloonTemplate.store @st.name, @attr
+          }.should_not change License.find(@st.id), :classname
+        end
+      end
+    end
+  end
+  
+  describe 'テキスト取り込みに於いて' do
+    #成功でTrue、パース失敗でFalse、失敗は保存エラーのモデルを配列で返す
+    before do
+    end
+    context 'つつがなく終わるとき' do
+      it 'Json解析を依頼する' do
+        SpeechBalloonTemplate.should_receive(:parse).with(any_args).exactly(1)
+        SpeechBalloonTemplate.stub(:parse).with(any_args).and_return(@j)
+        SpeechBalloonTemplate.import(@t)
+      end
+      it 'フキダシテンプレート更新を一回依頼する' do
+        SpeechBalloonTemplate.stub(:store).with(any_args).and_return(SpeechBalloonTemplate.new)
+        SpeechBalloonTemplate.any_instance.stub(:valid?).with(any_args).and_return(true)
+        SpeechBalloonTemplate.should_receive(:store).with(any_args).exactly(1)
+        SpeechBalloonTemplate.import(@t)
+      end
+      it 'フキダシテンプレートが追加される' do
+        lambda {
+          SpeechBalloonTemplate.import(@t)
+        }.should change SpeechBalloonTemplate, :count
+      end
+      it '[]を返す' do
+        SpeechBalloonTemplate.import(@t).should eq []
+      end
+    end
+    context '複数データがつつがなく終わるとき' do
+      it 'フキダシテンプレート更新を二回依頼する' do
+        SpeechBalloonTemplate.stub(:store).with(any_args).and_return(SpeechBalloonTemplate.new)
+        SpeechBalloonTemplate.any_instance.stub(:valid?).with(any_args).and_return(true)
+        SpeechBalloonTemplate.should_receive(:store).with(any_args).exactly(2)
+        SpeechBalloonTemplate.import(@ts)
+      end
+      it 'フキダシテンプレートが二個追加される' do
+        lambda {
+          SpeechBalloonTemplate.import(@ts)
+        }.should change(SpeechBalloonTemplate, :count).by 2
+      end
+      it '[]を返す' do
+        SpeechBalloonTemplate.import(@ts).should eq []
+      end
+    end
+    #例外ケース
+    context 'Json解析に失敗したとき' do
+      before do
+        SpeechBalloonTemplate.stub(:parse).with(any_args).and_return(false)
+      end
+      it 'フキダシテンプレートの数に変化がない' do
+        lambda {
+          SpeechBalloonTemplate.import(@t)
+        }.should_not change SpeechBalloonTemplate, :count
+      end
+      it 'Falseを返す' do
+        SpeechBalloonTemplate.import(@t).should be_false
+      end
+    end
+    context 'フキダシテンプレート作成に失敗したとき' do
+      before do
+        SpeechBalloonTemplate.any_instance.stub(:save).with(any_args).and_return(false)
+        SpeechBalloonTemplate.any_instance.stub(:valid?).with(any_args).and_return(false)
+      end
+      it 'フキダシテンプレートの数に変化がない' do
+        lambda {
+          SpeechBalloonTemplate.import(@t)
+        }.should_not change SpeechBalloonTemplate, :count
+      end
+      it '配列を返す' do
+        r = SpeechBalloonTemplate.import(@t)
+        r.is_a?(Array).should be_true
+      end
+      it '配列の中身は一件' do
+        r = SpeechBalloonTemplate.import(@t)
+        r.should have(1).items
+      end
+      it 'フキダシテンプレートオブジェクトが入っている' do
+        r = SpeechBalloonTemplate.import(@t)
+        r.first.is_a?(SpeechBalloonTemplate).should be_true
+      end
+    end
+    context '複数のフキダシテンプレート作成に失敗したとき' do
+      #三件中、二件の失敗、一件を成功させ、成功データは戻り値に含まないことを確認する
+      it 'フキダシテンプレートの数に変化がない' do
+        lambda {
+          SpeechBalloonTemplate.import(@tes)
+        }.should_not change SpeechBalloonTemplate, :count
+      end
+      it '途中で保存に失敗しても全件更新依頼する' do
+        SpeechBalloonTemplate.stub(:store).with(any_args).and_return(SpeechBalloonTemplate.new)
+        SpeechBalloonTemplate.should_receive(:store).with(any_args).exactly(3)
+        SpeechBalloonTemplate.import(@tes)
+      end
+      it '配列を返す' do
+        r = SpeechBalloonTemplate.import(@tes)
+        r.is_a?(Array).should be_true
+      end
+      it '配列の中身は2件' do
+        r = SpeechBalloonTemplate.import(@tes)
+        r.should have(2).items
+      end
+      it '配列の中身は失敗したフキダシテンプレートオブジェクトが入っている' do
+        r = SpeechBalloonTemplate.import(@tes)
+        r[0].is_a?(SpeechBalloonTemplate).should be_true
+        r[0]["name"].should eq 'fail1'
+        r[1].is_a?(SpeechBalloonTemplate).should be_true
+        r[1]["name"].should eq 'fail2'
+      end
+    end
+  end
+  
+  describe 'インポートエラーの表示に於いて' do
+  end
+  
+  describe 'ファイル取り込みに於いて' do
+    before do
+      SpeechBalloonTemplate.stub(:import).with(any_args).and_return(true)
+    end
+    context 'つつがなく終わるとき' do
+      before do
+        SpeechBalloonTemplate.stub(:import).with(any_args).and_return(true)
+      end
+      it 'ファイルを開いてテキストを読む' do
+        File.should_receive(:open).with(@f, 'r').exactly(1)
+        SpeechBalloonTemplate.import_file(@f)
+      end
+      it 'テキスト取り込みを依頼する' do
+        SpeechBalloonTemplate.should_receive(:import).with(any_args).exactly(1)
+        SpeechBalloonTemplate.import_file(@f)
+      end
+      #テキスト取り込み成功でTrueが返る
+      it 'Trueを返す' do
+        SpeechBalloonTemplate.import_file(@f).should be_true
+      end
+    end
+    context 'ファイルが開けないとき' do
+      before do
+        File.stub(:open).with(any_args).and_raise('StandardError')
+      end
+      it 'ファイルエラーのメッセージを出力する' do
+        pending
+      end
+      it 'Falseを返す' do
+        SpeechBalloonTemplate.import_file(@f).should be_false
+      end
+    end
+    #失敗したときは、失敗したフキダシテンプレートが配列で返る
+    context 'テキスト取り込みが失敗したとき' do
+      before do
+        SpeechBalloonTemplate.stub(:import).with(any_args).and_return(false)
+      end
+      it '各フキダシテンプレートのエラーメッセージを出力する' do
+        pending
+      end
+      it 'Falseを返す' do
+        SpeechBalloonTemplate.import_file(@f).should be_false
+      end
+    end
+  end
+  
 end
index cc05684..626ec47 100644 (file)
@@ -1,5 +1,284 @@
+# -*- encoding: utf-8 -*-
 require 'spec_helper'
-
+#システム画像
 describe SystemPicture do
-  pending "add some examples to (or delete) #{__FILE__}"
+  before do
+    @admin = Factory :admin
+    @license = Factory :license
+  end
+  
+  describe '検証に於いて' do
+    before do
+    end
+    
+    it 'オーソドックスなデータなら通る' do
+      @sp = Factory.build :system_picture
+      @sp.should be_valid
+    end
+    
+    context 'extを検証するとき' do
+      before do
+        @sp = Factory.build :system_picture
+      end
+      it 'テストデータの確認' do
+        @sp.ext = 'jpeg'
+        @sp.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @sp.ext = ''
+        @sp.should_not be_valid
+      end
+      it '5文字以上なら失敗する' do
+        @sp.ext = 'a'*5
+        @sp.should_not be_valid
+      end
+      it 'png,gif,jpeg以外なら失敗する' do
+        @sp.ext = 'bmp'
+        @sp.should_not be_valid
+      end
+    end
+    context 'widthを検証するとき' do
+      before do
+        @sp = Factory.build :system_picture
+      end
+      it 'テストデータの確認' do
+        @sp.width = 1
+        @sp.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @sp.width = nil
+        @sp.should_not be_valid
+      end
+      it '数値でなければ失敗する' do
+        @sp.width = 'a'
+        @sp.should_not be_valid
+      end
+      it '0なら失敗する' do
+        @sp.width = '0'
+        @sp.should_not be_valid
+      end
+      it '負でも失敗する' do
+        @sp.width = -1
+        @sp.should_not be_valid
+      end
+    end
+    context 'heightを検証するとき' do
+      before do
+        @sp = Factory.build :system_picture
+      end
+      it 'テストデータの確認' do
+        @sp.height = 1
+        @sp.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @sp.height = nil
+        @sp.should_not be_valid
+      end
+      it '数値でなければ失敗する' do
+        @sp.height = 'a'
+        @sp.should_not be_valid
+      end
+      it '0なら失敗する' do
+        @sp.height = '0'
+        @sp.should_not be_valid
+      end
+      it '負でも失敗する' do
+        @sp.height = -1
+        @sp.should_not be_valid
+      end
+    end
+    context 'filesizeを検証するとき' do
+      before do
+        @sp = Factory.build :system_picture
+      end
+      it 'テストデータの確認' do
+        @sp.filesize = 1
+        @sp.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @sp.filesize = nil
+        @sp.should_not be_valid
+      end
+      it '数値でなければ失敗する' do
+        @sp.filesize = 'a'
+        @sp.should_not be_valid
+      end
+      it '負なら失敗する' do
+        @sp.filesize = '-1'
+        @sp.should_not be_valid
+      end
+      it '2MB以上なら失敗する' do
+        @sp.filesize = 2000000+1
+        @sp.should_not be_valid
+      end
+    end
+    context 'md5を検証するとき' do
+      before do
+        @sp = Factory.build :system_picture
+      end
+      it 'テストデータの確認' do
+        @sp.md5 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+        @sp.should be_valid
+      end
+      it 'nullなら失敗する' do
+        @sp.md5 = ''
+        @sp.should_not be_valid
+      end
+      it '32文字以上なら失敗する' do
+        @sp.md5 = 'a'*33
+        @sp.should_not be_valid
+      end
+    end
+  end
+  
+  describe 'データ補充に於いて' do
+    before do
+    end
+    
+  end
+  
+  describe '作成・更新に於いて' do
+    before do
+      @sp = Factory.build :system_picture
+      #RMagickのスタブをおいておく
+      class Mgk ; class Image ; end ; end
+      @filesize = 76543
+      Mgk::Image.stub(:from_blob).with(any_args).and_return([Mgk.new])
+      Mgk.any_instance.stub(:format).with(any_args).and_return('png')
+      Mgk.any_instance.stub(:rows).with(any_args).and_return(200)
+      Mgk.any_instance.stub(:columns).with(any_args).and_return(100)
+      Mgk.any_instance.stub(:filesize).with(any_args).and_return(@filesize)
+      Mgk.any_instance.stub(:to_blob).with(any_args).and_return('data')
+    end
+    context '事前チェック' do
+      before do
+        #すべての処理が正常パターンで通過すれば、一番深い分岐まで通る。
+        #それで外部のメソッド呼び出しだけに着目してテストする。
+        SystemPicture.any_instance.stub(:data_to_mgk).with(any_args).and_return(Mgk.new)
+        SystemPicture.any_instance.stub(:save).with(any_args).and_return(true)
+        PictureIO::LocalPicture.any_instance.stub(:put).with(any_args).and_return(true)
+      end
+      it 'RMagick変換を依頼している' do
+        SystemPicture.any_instance.should_receive(:data_to_mgk).exactly(1)
+        @sp.store 'bindata'
+      end
+      it '自身に属性をセットしている' do
+        SystemPicture.any_instance.should_receive(:attributes=).exactly(1)
+        @sp.store 'bindata'
+      end
+      it '自身が保存されている' do
+        SystemPicture.any_instance.should_receive(:save).exactly(1)
+        @sp.store 'bindata'
+      end
+      it 'PictureIoに画像データの保存を依頼している' do
+        PictureIO::LocalPicture.any_instance.should_receive(:put).with(any_args).exactly(1)
+        @sp.store 'bindata'
+      end
+    end
+    context 'つつがなく終わるとき' do
+      before do
+        #すべての処理を正常パターンで通過させ、保存機能をチェックする。
+        SystemPicture.any_instance.stub(:data_to_mgk).with(any_args).and_return(Mgk.new)
+#        SystemPicture.any_instance.stub(:save).with(any_args).and_return(true)
+        PictureIO::LocalPicture.any_instance.stub(:put).with(any_args).and_return(true)
+      end
+      it '自身に属性をセットしている' do
+        lambda {
+          @sp.store 'bindata'
+        }.should change @sp, :filesize
+      end
+      it 'システム画像モデルが作成されている' do
+        lambda {
+          @sp.store 'bindata'
+        }.should change SystemPicture, :count
+      end
+      it 'システム画像が保存されている' do
+        @sp.store 'bindata'
+        SystemPicture.find(@sp).should_not be_nil
+      end
+      it 'Trueを返す' do
+        @sp.store('bindata').should eq true
+      end
+    end
+    #以下から例外ケース。処理先頭から失敗させていく
+    context 'RMagick変換が失敗したとき' do
+      before do
+        SystemPicture.any_instance.stub(:data_to_mgk).with(any_args).and_return(false)
+      end
+      it 'falseを返す' do
+        @sp.store('bindata').should be_false
+      end
+      it '自身の保存は呼ばれていない' do
+        SystemPicture.any_instance.should_not_receive(:save)
+      end
+    end
+    context '自身の保存に失敗したとき' do
+      before do
+        SystemPicture.any_instance.stub(:data_to_mgk).with(any_args).and_return(Mgk.new)
+        SystemPicture.any_instance.stub(:save).with(any_args).and_return(false)
+      end
+      it 'falseを返す' do
+        @sp.store('bindata').should be_false
+      end
+      it '更新されていない' do
+        @sp.store('bindata')
+        @sp.should be_a_new SystemPicture
+      end
+      it '原画の保存は呼ばれていない' do
+        PictureIO::LocalPicture.any_instance.should_not_receive(:put)
+      end
+    end
+  end
+  
+  describe '置換に於いて' do
+    before do
+      @sp = Factory :system_picture
+    end
+    context '新規のとき' do
+      before do
+        SystemPicture.stub(:find_by_md5).with(any_args).and_return(nil)
+        SystemPicture.any_instance.stub(:store).with(any_args).and_return(true)
+      end
+      it '新規オブジェクト生成している' do
+        r = SystemPicture.store 'bindata'
+        r.should be_a_new SystemPicture
+      end
+      it '作成依頼している' do
+        SystemPicture.any_instance.should_receive(:store).with('bindata').exactly(1)
+        SystemPicture.store 'bindata'
+      end
+      it '保存された行を返す' do
+        r = SystemPicture.store 'bindata'
+        r.should_not eq @sp
+      end
+    end
+    context '既存のとき' do
+      before do
+        SystemPicture.stub(:find_by_md5).with(any_args).and_return(@sp)
+        SystemPicture.any_instance.stub(:store).with(any_args).and_return(true)
+      end
+      it '新規オブジェクト生成していない' do
+        SystemPicture.should_receive(:new).with(any_args).exactly(0)
+        SystemPicture.store 'bindata'
+      end
+      it '作成依頼している' do
+        SystemPicture.any_instance.should_receive(:store).with('bindata').exactly(1)
+        SystemPicture.store 'bindata'
+      end
+      it '保存された行を返す' do
+        r = SystemPicture.store 'bindata'
+        r.should eq @sp
+      end
+    end
+    context '保存失敗のとき' do
+      before do
+        SystemPicture.stub(:find_by_md5).with(any_args).and_return(@sp)
+        SystemPicture.any_instance.stub(:store).with(any_args).and_return(false)
+      end
+      it 'nilを返す' do
+        r = SystemPicture.store 'bindata'
+        r.should eq nil
+      end
+    end
+  end
 end