smalruby-editor 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of smalruby-editor might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d71a9e4754ddc023b99130576187b13e97cca3ce
4
- data.tar.gz: 4ed1b62b217727d2a96bfa9c337049a8850e2684
3
+ metadata.gz: 27f4c696fc7ebd2507f3025d757ac3a956eb3e10
4
+ data.tar.gz: 70aec400f0cb75231ab0d19b5250eecab9e64ae5
5
5
  SHA512:
6
- metadata.gz: bb60b2b55e43a4403b29441647b3eae78b6ae563be42d2b84c5c6f4971eccd10875fe131bac0013042d641a0aa5165b564472cf1cc3db5039fadb54ac688e9f5
7
- data.tar.gz: 77272e287ef54a97a9c8fdf70e9da1c02a7a62eee9cf5e05df6413b0ae4285b6686616aa35020ebe64735e1ead930cb80e1d00840f193c4a24250bc49661f237
6
+ metadata.gz: 8d2a30f76ea5fbed2ffaf37b38c359641ac148d9a9d73e5bd3aff095eb50beff67a25204fad4d5e895604e5cc13345aadc85255fa7004384dbfc22d764380e93
7
+ data.tar.gz: 50ba103b5055f534b8b816ee4d2805f117ec88a3939265844fab51d2f3c15a5e5b1a73c48a039e5ce8731ff19130c9a94c6de7cb3cafa3e2de011efa7686d8d3
data/.rubocop.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  AllCops:
2
2
  Excludes:
3
3
  - db/seeds.rb
4
+ - spec/fixtures/**
4
5
 
5
6
  AsciiComments:
6
7
  Enabled: false
data/.travis.yml CHANGED
@@ -8,7 +8,6 @@ before_script:
8
8
  - mysql -e 'create database myapp_test;'
9
9
 
10
10
  script:
11
- - export SECRET_KEY_BASE='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
12
11
  - export COVERALLS='1'
13
12
  - RAILS_ENV=test bundle exec rake db:migrate --trace
14
13
  - bundle exec rake db:test:prepare
data/README.rdoc CHANGED
@@ -17,7 +17,7 @@ The Smalruby(smɔ́ːrúːbi) Project will provide a Ruby learning environment f
17
17
  requirements:
18
18
 
19
19
  * Windows or UNIX like OS (Mac OS X, Linux, etc...)
20
- * Ruby 2.0.0-p247 or higher.
20
+ * Ruby 2.0.0-p353 or higher.
21
21
  * MySQL
22
22
  * Git
23
23
 
@@ -0,0 +1,139 @@
1
+ # Place all the behaviors and hooks related to the matching controller here.
2
+ # All this logic will automatically be available in application.js.
3
+ # You can use CoffeeScript in this file: http://coffeescript.org/
4
+
5
+ changed = false
6
+
7
+ successMessage = (title, msg = '', selector = '#messages') ->
8
+ html = $('<div class="alert alert-success" style="display: none">')
9
+ .append("<h4><i class=\"icon-star\"></i>#{title}</h4>")
10
+ .append(msg)
11
+ $(selector).append(html)
12
+ html.fadeIn('slow')
13
+ .delay(3000)
14
+ .fadeOut('slow')
15
+
16
+ errorMessage = (msg, selector = '#messages') ->
17
+ html = $('<div class="alert alert-error" style="display: none">')
18
+ .append('<button type="button" class="close" data-dismiss="alert">×</button>')
19
+ .append('<h4><i class="icon-exclamation-sign"></i>エラー</h4>')
20
+ .append(msg)
21
+ $(selector).append(html)
22
+ html.fadeIn('slow')
23
+ .delay(10000)
24
+ .fadeOut('slow')
25
+
26
+ $ ->
27
+ saving = false
28
+
29
+ textEditor = ace.edit('text-editor')
30
+ textEditor.setTheme('ace/theme/github')
31
+ textEditor.setShowInvisibles(true)
32
+
33
+ textEditor.focus()
34
+ textEditor.gotoLine(0, 0)
35
+
36
+ textEditor.on 'change', (e) ->
37
+ changed = true
38
+
39
+ session = textEditor.getSession()
40
+ session.setMode('ace/mode/ruby')
41
+ session.setTabSize(2)
42
+ session.setUseSoftTabs(true)
43
+
44
+ $('#check-button').click (e) ->
45
+ e.preventDefault()
46
+ $.ajax
47
+ url: '/source_codes/check'
48
+ type: 'POST'
49
+ data:
50
+ source_code:
51
+ data: session.getDocument().getValue()
52
+ dataType: 'json'
53
+ success: (data, textStatus, jqXHR) ->
54
+ if data.length == 0
55
+ successMessage('チェックしました', 'ただし、プログラムを動かすとエラーが見つかるかもしれません。')
56
+ else
57
+ for errorInfo in data
58
+ do (errorInfo) ->
59
+ msg = "#{errorInfo.row}行"
60
+ if errorInfo.column > 0
61
+ msg += "、#{errorInfo.column}文字"
62
+ errorMessage(msg + ": #{errorInfo.message}")
63
+
64
+ $('#save-button').click (e) ->
65
+ e.preventDefault()
66
+ filename = $.trim($('#filename').val())
67
+ if filename.length <= 0
68
+ errorMessage('セーブする前にプログラムに名前をつけてね!')
69
+ $('#filename').focus()
70
+ else
71
+ afterSave = ->
72
+ saving = true
73
+ changed = false
74
+ successMessage('セーブしました')
75
+ $.ajax
76
+ url: '/source_codes/'
77
+ type: 'POST'
78
+ data:
79
+ source_code:
80
+ filename: filename
81
+ data: session.getDocument().getValue()
82
+ dataType: 'json'
83
+ success: (data, textStatus, jqXHR) ->
84
+ <% if Rails.env == 'standalone' %>
85
+ $.ajax
86
+ url: '/source_codes/write'
87
+ type: 'DELETE'
88
+ dataType: 'json'
89
+ success: (data, textStatus, jqXHR) ->
90
+ if data.source_code.error && confirm("前に#{filename}という名前でセーブしているけど本当にセーブしますか?\nセーブすると前に作成したプログラムは消えてしまうよ!")
91
+ $.ajax
92
+ url: '/source_codes/write'
93
+ type: 'DELETE'
94
+ data:
95
+ force: true
96
+ dataType: 'json'
97
+ success: (data, textStatus, jqXHR) ->
98
+ afterSave()
99
+ else
100
+ afterSave()
101
+ <% else %>
102
+ afterSave()
103
+ $('#download-link').click()
104
+ <% end %>
105
+
106
+ $('#filename').keypress (e) ->
107
+ e = window.event if !e
108
+ if e.keyCode == 13
109
+ $('#save-button').click()
110
+ false
111
+ else
112
+ true
113
+
114
+ $('#load-button').click (e) ->
115
+ e.preventDefault()
116
+ if changed
117
+ return unless confirm('まだセーブしていないのでロードするとプログラムが消えてしまうよ!\nそれでもロードしますか?')
118
+ $(@).parent().find('input[name="source_code[file]"]').click()
119
+
120
+ $('#file-form').fileupload
121
+ dataType: 'json'
122
+ done: (e, data) ->
123
+ info = data.result.source_code
124
+ if info.error
125
+ errorMessage(info.filename + 'は' + info.error)
126
+ else
127
+ $('#filename').val(info.filename)
128
+ session.getDocument().setValue(info.data)
129
+ textEditor.focus()
130
+ textEditor.moveCursorTo(0, 0)
131
+ changed = false
132
+ successMessage('ロードしました')
133
+
134
+ window.onbeforeunload = (event) ->
135
+ if !saving && session.getDocument().getValue().length > 0
136
+ return '作成中のプログラムが消えてしまうよ!'
137
+ else
138
+ saving = false
139
+ return
@@ -2,33 +2,41 @@
2
2
  require 'nkf'
3
3
 
4
4
  class SourceCodesController < ApplicationController
5
+ before_filter :check_whether_standalone, only: :write
6
+
5
7
  def check
6
8
  render json: SourceCode.new(source_code_params).check_syntax
7
9
  end
8
10
 
9
11
  def create
10
- source_code = SourceCode.create!(source_code_params)
12
+ sc = SourceCode.create!(source_code_params)
11
13
  session[:source_code] = {
12
- id: source_code.id,
13
- digest: source_code.digest,
14
+ id: sc.id,
15
+ digest: sc.digest,
14
16
  }
15
- render json: { source_code: { id: source_code.id } }
17
+ render json: { source_code: { id: sc.id } }
16
18
  end
17
19
 
18
20
  def download
19
- source_code = SourceCode.find(session[:source_code][:id])
20
- unless source_code.digest == session[:source_code][:digest]
21
- fail ActiveRecord::RecordNotFound
22
- end
23
-
24
21
  send_data(source_code.data,
25
- filename: encode_filename(source_code.filename),
22
+ filename: url_encode_filename(source_code.filename),
26
23
  disposition: 'attachment',
27
24
  type: 'text/plain; charset=utf-8')
28
25
 
29
- source_code.destroy
26
+ destroy_source_code_and_delete_session(source_code)
27
+ end
30
28
 
31
- session[:source_code] = nil
29
+ def write
30
+ res = { source_code: { filename: source_code.filename } }
31
+
32
+ write_source_code(source_code)
33
+
34
+ destroy_source_code_and_delete_session(source_code)
35
+
36
+ render json: res
37
+ rescue => e
38
+ res[:source_code][:error] = e.message
39
+ render json: res
32
40
  end
33
41
 
34
42
  def load
@@ -45,6 +53,12 @@ class SourceCodesController < ApplicationController
45
53
 
46
54
  private
47
55
 
56
+ def check_whether_standalone
57
+ unless Rails.env == 'standalone'
58
+ fail "#{self.class.name}##{action_name}はstandaloneモードのみの機能です"
59
+ end
60
+ end
61
+
48
62
  def source_code_params
49
63
  params.require(:source_code).permit(:data, :filename)
50
64
  end
@@ -57,11 +71,36 @@ class SourceCodesController < ApplicationController
57
71
  }
58
72
  end
59
73
 
60
- def encode_filename(filename)
74
+ def url_encode_filename(filename)
61
75
  if request.env['HTTP_USER_AGENT'] =~ /MSIE|Trident/
62
76
  return ERB::Util.url_encode(filename)
63
77
  else
64
78
  filename
65
79
  end
66
80
  end
81
+
82
+ def source_code
83
+ return @source_code if @source_code
84
+ sc = SourceCode.find(session[:source_code][:id])
85
+ unless sc.digest == session[:source_code][:digest]
86
+ fail ActiveRecord::RecordNotFound
87
+ end
88
+ @source_code = sc
89
+ end
90
+
91
+ def write_source_code(source_code)
92
+ path = Pathname("~/#{source_code.filename}").expand_path.to_s
93
+
94
+ fail 'すでに同じ名前のプログラムがあります' if File.exist?(path) && params[:force].blank?
95
+
96
+ File.open(path, 'w') do |f|
97
+ f.write(source_code.data)
98
+ end
99
+ end
100
+
101
+ def destroy_source_code_and_delete_session(source_code)
102
+ source_code.destroy
103
+
104
+ session[:source_code] = nil
105
+ end
67
106
  end
@@ -7,6 +7,7 @@ require 'digest/sha2'
7
7
  # ソースコードを表現するモデル
8
8
  class SourceCode < ActiveRecord::Base
9
9
  validates :filename, presence: true
10
+ validate :validate_filename
10
11
  validates :data, presence: true, allow_blank: true
11
12
 
12
13
  # シンタックスをチェックする
@@ -39,4 +40,10 @@ class SourceCode < ActiveRecord::Base
39
40
  RbConfig::CONFIG['RUBY_INSTALL_NAME'])
40
41
  Open3.capture3("#{ruby_cmd} -c #{path}")
41
42
  end
43
+
44
+ def validate_filename
45
+ if File.basename(filename) != filename
46
+ errors.add(:filename, 'includes directory separator(s)')
47
+ end
48
+ end
42
49
  end
@@ -1,14 +1,16 @@
1
1
  <div class="navbar navbar-inverse">
2
2
  <div class="navbar-inner">
3
3
  <ul class="nav">
4
- <li class="active"><a id="ruby-tab" href="#">Ruby</a></li>
4
+ <li class="active"><a id="ruby-tab" href="#" onclick="window.event.preventDefault(); return false;">Ruby</a></li>
5
5
  </ul>
6
6
 
7
7
  <%= form_tag(source_codes_load_path, id: "file-form", class: "navbar-form pull-right", method: "post", multipart: true) do %>
8
8
  <button id="check-button" type="button" class="btn btn-primary">チェック</button>
9
9
  <input id="filename" type="text" class="span4" placeholder="プログラムの名前を入れてね(例:01.rb)">
10
10
  <button id="save-button" type="button" class="btn btn-primary">セーブ</button>
11
+ <% unless Rails.env == 'standalone' %>
11
12
  <%= link_to '', source_codes_download_path, id: 'download-link', style: 'display: none', 'data-method' => 'delete' %>
13
+ <% end %>
12
14
  <button id="load-button" class="btn btn-primary">ロード</button>
13
15
  <input id="load-file" type="file" name="source_code[file]" style="display: none">
14
16
  <% end %>
@@ -19,4 +21,3 @@
19
21
  </div>
20
22
 
21
23
  <div id="text-editor"></div>
22
-
data/bin/smalruby-editor CHANGED
@@ -4,7 +4,6 @@
4
4
  require 'pathname'
5
5
 
6
6
  ENV['RAILS_ENV'] = 'standalone'
7
- ENV['SECRET_KEY_BASE'] ||= 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
8
7
  ENV['SMALRUBY_EDITOR_HOME'] ||= Pathname('~/.smalruby-editor').expand_path.to_s
9
8
 
10
9
  APP_PATH = File.expand_path('../../config/application', __FILE__)
@@ -37,3 +37,12 @@ production:
37
37
  username: root
38
38
  password:
39
39
  host: localhost
40
+
41
+ standalone:
42
+ adapter: mysql2
43
+ encoding: utf8
44
+ database: smalruby-editor_standalone
45
+ pool: 5
46
+ username: root
47
+ password:
48
+ host: localhost
@@ -23,3 +23,9 @@ production:
23
23
  database: db/production.sqlite3
24
24
  pool: 5
25
25
  timeout: 5000
26
+
27
+ standalone:
28
+ adapter: sqlite3
29
+ database: db/standalone.sqlite3
30
+ pool: 5
31
+ timeout: 5000
@@ -1,17 +1,15 @@
1
1
  require_relative 'production'
2
2
 
3
3
  SmalrubyEditor::Application.configure do
4
- unless ENV['SMALRUBY_EDITOR_HOME']
5
- fail 'you must set SMALRUBY_EDITOR_HOME environment variable.'
6
- end
7
-
8
- home_dir = SmalrubyEditor.create_home_directory
4
+ if ENV['SMALRUBY_EDITOR_HOME'].present?
5
+ home_dir = SmalrubyEditor.create_home_directory
9
6
 
10
- config.paths.add 'config/database',
11
- with: home_dir.join('config/database.yml')
12
- config.paths.add 'log', with: home_dir.join("log/#{Rails.env}.log")
13
- config.paths.add 'tmp', with: home_dir.join('tmp')
14
- config.paths.add 'tmp/cache', with: home_dir.join('tmp/cache')
7
+ config.paths.add('config/database',
8
+ with: home_dir.join('config/database.yml'))
9
+ config.paths.add('log', with: home_dir.join("log/#{Rails.env}.log"))
10
+ config.paths.add('tmp', with: home_dir.join('tmp'))
11
+ config.paths.add('tmp/cache', with: home_dir.join('tmp/cache'))
12
+ end
15
13
 
16
14
  config.serve_static_assets = true
17
15
  config.log_level = :warn
@@ -9,4 +9,9 @@
9
9
 
10
10
  # Make sure your secret_key_base is kept private
11
11
  # if you're sharing your code publicly.
12
- SmalrubyEditor::Application.config.secret_key_base = ENV['SECRET_KEY_BASE']
12
+ if Rails.env == 'production'
13
+ secret_key_base = ENV['SECRET_KEY_BASE']
14
+ else
15
+ secret_key_base = '123456789012345678901234567890'
16
+ end
17
+ SmalrubyEditor::Application.config.secret_key_base = secret_key_base
data/config/routes.rb CHANGED
@@ -6,6 +6,7 @@ SmalrubyEditor::Application.routes.draw do
6
6
  post 'source_codes/check'
7
7
  delete 'source_codes/download'
8
8
  post 'source_codes/load'
9
+ delete 'source_codes/write'
9
10
 
10
11
  # The priority is based upon order of creation: first created ->
11
12
  # highest priority.
@@ -1,3 +1,3 @@
1
1
  module SmalrubyEditor
2
- VERSION = '0.0.6'
2
+ VERSION = '0.0.7'
3
3
  end
data/lib/tasks/gem.rake CHANGED
@@ -1,5 +1,10 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
- task 'build' => ['assets:clobber', 'assets:precompile']
3
+ task 'assets:precompile:standalone' do
4
+ Rails.env = ENV['RAILS_ENV'] = 'standalone'
5
+ Rake::Task['assets:precompile'].invoke
6
+ end
7
+
8
+ task 'build' => ['assets:clobber', 'assets:precompile:standalone']
4
9
 
5
10
 
data/lib/tasks/rspec.rake CHANGED
@@ -1,16 +1,16 @@
1
- begin
2
- if defined? RSpec # otherwise fails on non-live environments
3
- task(:spec).clear
4
- desc "Run all specs/features in spec directory"
5
- RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
6
- t.pattern = './spec/{,**/}*{_spec.rb,.feature}'
7
- end
8
-
9
- namespace :spec do
10
- desc "Run the code examples in spec/acceptance"
11
- RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
12
- t.pattern = './spec/acceptance/{,**/}*.feature'
13
- end
14
- end
15
- end
16
- end
1
+ begin
2
+ if defined? RSpec # otherwise fails on non-live environments
3
+ task(:spec).clear
4
+ desc "Run all specs/features in spec directory"
5
+ RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') do |t|
6
+ t.pattern = './spec/{,**/}*{_spec.rb,.feature}'
7
+ end
8
+
9
+ namespace :spec do
10
+ desc "Run the code examples in spec/acceptance"
11
+ RSpec::Core::RakeTask.new(:acceptance => 'db:test:prepare') do |t|
12
+ t.pattern = './spec/acceptance/{,**/}*.feature'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  task :rubocop do
2
- sh 'bundle exec rubocop --rails app lib bin config'
2
+ sh 'bundle exec rubocop --rails app lib bin config spec'
3
3
  end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ # language: ja
3
+ @javascript
4
+ @standalone
5
+ 機能: Save - プログラムのセーブ(standaloneモード)
6
+ シナリオ: セーブボタンを押してローカルマシンにプログラムを保存できる
7
+ 前提 "エディタ" 画面を表示する
8
+ かつ テキストエディタに "puts 'Hello, World!'" を入力済みである
9
+ かつ プログラムの名前に "01.rb" を指定する
10
+
11
+ もし "セーブボタン" をクリックする
12
+ かつ JavaScriptによるリクエストが終わるまで待つ
13
+
14
+ ならば ホームディレクトリに "01.rb" が存在すること
15
+ かつ ホームディレクトリの "01.rb" の内容が "puts 'Hello, World!'" であること
16
+ かつ "メッセージ" に "セーブしました" を含むこと
17
+
18
+ シナリオ: ローカルマシンに同じ名前のプログラムがある状態でセーブボタンを押す
19
+ 前提 "エディタ" 画面を表示する
20
+ かつ ホームディレクトリに "puts 'Hello, World!'" という内容の "01.rb" が存在する
21
+ かつ テキストエディタに "n = 0" を入力済みである
22
+ かつ プログラムの名前に "01.rb" を指定する
23
+
24
+ もし "セーブボタン" をクリックする
25
+ かつ JavaScriptによるリクエストが終わるまで待つ
26
+
27
+ ならば 確認メッセージ "前に01.rbという名前でセーブしているけど本当にセーブしますか?\nセーブすると前に作成したプログラムは消えてしまうよ!" を表示すること
28
+ かつ ホームディレクトリの "01.rb" の内容が "puts 'Hello, World!'" であること
29
+
30
+ もし JavaScriptによるリクエストが終わるまで待つ
31
+
32
+ ならば "メッセージ" に "セーブしました" を含むこと
@@ -2,19 +2,24 @@
2
2
  # language: ja
3
3
  @javascript
4
4
  機能: Check - プログラムのエラーチェック
5
- シナリオ: チェックボタンを押してプログラムの正しいかチェックできる
5
+ シナリオ: チェックボタンを押してプログラムにシンタックスエラーがないいことをチェックできる
6
6
  前提 "エディタ" 画面を表示する
7
7
  かつ テキストエディタに "puts 'Hello, World!'" を入力済みである
8
8
 
9
9
  もし "チェックボタン" をクリックする
10
10
  かつ JavaScriptによるリクエストが終わるまで待つ
11
11
 
12
- ならば "メッセージ" に "エラー" を含まないこと
12
+ ならば "メッセージ" に "チェックしました" を含むこと
13
+ かつ "メッセージ" に "ただし、プログラムを動かすとエラーが見つかるかもしれません。" を含むこと
13
14
 
14
- もし テキストエディタに "puts Hello, World!'" を入力済みである
15
- かつ "チェックボタン" をクリックする
15
+ シナリオ: チェックボタンを押してプログラムにシンタックスエラーがあることをチェックできる
16
+ 前提 "エディタ" 画面を表示する
17
+ かつ テキストエディタに "puts Hello, World!'" を入力済みである
18
+
19
+ もし "チェックボタン" をクリックする
16
20
  かつ JavaScriptによるリクエストが終わるまで待つ
17
21
 
18
22
  ならば "メッセージ" に "エラー" を含むこと
19
23
  かつ "メッセージ" に "1行、19文字: syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '('" を含むこと
20
24
  かつ "メッセージ" に "1行: unterminated string meets end of file" を含むこと
25
+ かつ "メッセージ" に "チェックしました" を含まないこと
@@ -10,6 +10,9 @@
10
10
 
11
11
  ならば テキストエディタに "files/01.rb" を読み込むこと
12
12
  かつ "プログラム名の入力欄" は "01.rb" であること
13
+ かつ テキストエディタの 0 行目の 0 文字目にカーソルがあること
14
+ かつ テキストエディタにフォーカスがあること
15
+ かつ "メッセージ" に "ロードしました" を含むこと
13
16
 
14
17
  シナリオ: プログラムを入力済み状態でプログラムを読み込む
15
18
  前提 "エディタ" 画面を表示する
@@ -20,6 +23,9 @@
20
23
 
21
24
  ならば テキストエディタに "files/01.rb" を読み込むこと
22
25
  かつ "プログラム名の入力欄" は "01.rb" であること
26
+ かつ テキストエディタの 0 行目の 0 文字目にカーソルがあること
27
+ かつ テキストエディタにフォーカスがあること
28
+ かつ "メッセージ" に "ロードしました" を含むこと
23
29
 
24
30
  シナリオ: 間違って画像を読み込もうとする
25
31
  前提 "エディタ" 画面を表示する
@@ -30,6 +36,7 @@
30
36
  ならば テキストエディタのプログラムは "" であること
31
37
  かつ "プログラム名の入力欄" は "" であること
32
38
  かつ "メッセージ" に "favicon.icoはRubyのプログラムではありません" を含むこと
39
+ かつ "メッセージ" に "ロードしました" を含まないこと
33
40
 
34
41
  シナリオ: プログラムを修正後にロードボタンを押す
35
42
  前提 "エディタ" 画面を表示する
@@ -37,7 +44,7 @@
37
44
 
38
45
  もし "ロードボタン" をクリックする
39
46
 
40
- ならば 確認メッセージ "まだセーブしていないのでロードするとプログラムが消えてしまうよ!それでもロードしますか?" を表示すること
47
+ ならば 確認メッセージ "まだセーブしていないのでロードするとプログラムが消えてしまうよ!\nそれでもロードしますか?" を表示すること
41
48
 
42
49
  シナリオ: プログラムを修正後にセーブしてからロードボタンを押す
43
50
  前提 "エディタ" 画面を表示する
@@ -71,4 +78,4 @@
71
78
 
72
79
  もし "ロードボタン" をクリックする
73
80
 
74
- ならば 確認メッセージ "まだセーブしていないのでロードするとプログラムが消えてしまうよ!それでもロードしますか?" を表示すること
81
+ ならば 確認メッセージ "まだセーブしていないのでロードするとプログラムが消えてしまうよ!\nそれでもロードしますか?" を表示すること
@@ -11,6 +11,7 @@
11
11
  かつ JavaScriptによるリクエストが終わるまで待つ
12
12
 
13
13
  ならば "01.rb" をダウンロードすること
14
+ かつ "メッセージ" に "セーブしました" を含むこと
14
15
 
15
16
  シナリオ: 日本語のファイル名でプログラムを保存できる
16
17
  前提 "エディタ" 画面を表示する
@@ -21,6 +22,7 @@
21
22
  かつ JavaScriptによるリクエストが終わるまで待つ
22
23
 
23
24
  ならば "日本語.rb" をダウンロードすること
25
+ かつ "メッセージ" に "セーブしました" を含むこと
24
26
 
25
27
  シナリオ: ファイル名を入力せずにセーブボタンを押す
26
28
  前提 "エディタ" 画面を表示する
@@ -29,3 +31,4 @@
29
31
 
30
32
  ならば ダウンロードしないこと
31
33
  かつ "プログラム名の入力欄" にフォーカスがあること
34
+ かつ "メッセージ" に "セーブする前にプログラムに名前をつけてね!" を含むこと
@@ -248,6 +248,179 @@ describe SourceCodesController do
248
248
  end
249
249
  end
250
250
 
251
+ describe 'プログラムをホームディレクトリに保存してサーバ上から削除する' \
252
+ ' (DELETE write)' do
253
+ let(:source_code) {
254
+ SourceCode.create!(filename: '01.rb', data: 'puts "Hello, World!"')
255
+ }
256
+ let(:params) { {} }
257
+ let(:_session) {
258
+ {
259
+ source_code: {
260
+ id: source_code.id,
261
+ digest: source_code.digest,
262
+ }
263
+ }
264
+ }
265
+
266
+ context 'RAILS_ENVがstandaloneの場合', set_standalone_rails_env: true do
267
+ let(:path) { Pathname("~/#{source_code.filename}").expand_path.to_s }
268
+
269
+ context 'セッションが正しい場合' do
270
+ shared_examples 'success writing' do
271
+ let(:target_file) {
272
+ f = double('File')
273
+ allow(f).to receive(:write)
274
+ f
275
+ }
276
+
277
+ before do
278
+ allow(File).to receive(:open).and_yield(target_file)
279
+ xhr :delete, :write, params, _session
280
+ end
281
+
282
+ specify { expect(response).to be_success }
283
+
284
+ specify 'ホームディレクトリにファイルを保存する' do
285
+ expect(File).to have_received(:open).with(path, 'w').once
286
+ expect(target_file)
287
+ .to have_received(:write).with(source_code.data).once
288
+ end
289
+
290
+ specify 'プログラムをサーバ上から削除する' do
291
+ expect(SourceCode).to have(0).records
292
+ end
293
+
294
+ specify 'セッションから[:source_code]を削除する' do
295
+ expect(session[:source_code]).to be_nil
296
+ end
297
+ end
298
+
299
+ context '同じ名前のファイルが存在しない場合' do
300
+ before do
301
+ allow(File).to receive(:exist?).with(path).and_return(false)
302
+ end
303
+
304
+ context 'ファイル名が半角英数字の場合' do
305
+ include_examples 'success writing'
306
+ end
307
+
308
+ context 'ファイル名が日本語の場合' do
309
+ let(:source_code) {
310
+ SourceCode.create!(filename: '日本語でも表現できる.rb',
311
+ data: 'puts "Hello, World!"')
312
+ }
313
+
314
+ include_examples 'success writing'
315
+ end
316
+ end
317
+
318
+ context '同じ名前のファイルが存在する場合' do
319
+ before do
320
+ allow(File).to receive(:exist?).with(path).and_return(true)
321
+ end
322
+
323
+ context '上書き保存モードの場合' do
324
+ let(:params) { { force: true } }
325
+
326
+ include_examples 'success writing'
327
+ end
328
+
329
+ context '上書き保存モードではない場合' do
330
+ before do
331
+ xhr :delete, :write, params, _session
332
+ end
333
+
334
+ specify 'プログラムをサーバ上から削除しない' do
335
+ expect(SourceCode).to have(1).records
336
+ end
337
+
338
+ specify 'セッションから[:source_code]を削除しない' do
339
+ expect(session[:source_code]).to eq(_session[:source_code])
340
+ end
341
+ end
342
+ end
343
+ end
344
+
345
+ context 'セッションが不正な場合' do
346
+ shared_examples 'raise exception' do
347
+ it {
348
+ expect {
349
+ xhr :delete, :write, params, _session
350
+ }.to raise_exception
351
+ }
352
+ end
353
+
354
+ context 'セッションに[:source_code]がない場合' do
355
+ let(:_session) { {} }
356
+
357
+ include_examples 'raise exception'
358
+ end
359
+
360
+ context 'セッションに[:source_code][:id]がない場合' do
361
+ let(:_session) {
362
+ {
363
+ source_code: {
364
+ digest: source_code.digest,
365
+ }
366
+ }
367
+ }
368
+
369
+ include_examples 'raise exception'
370
+ end
371
+
372
+ context 'セッションに[:source_code][:digest]がない場合' do
373
+ let(:_session) {
374
+ {
375
+ source_code: {
376
+ id: source_code.id,
377
+ }
378
+ }
379
+ }
380
+
381
+ include_examples 'raise exception'
382
+ end
383
+
384
+ context 'セッションの[:source_code][:digest]が不正な場合' do
385
+ let(:_session) {
386
+ {
387
+ source_code: {
388
+ id: source_code.id,
389
+ digest: 'invalid_digest',
390
+ }
391
+ }
392
+ }
393
+
394
+ include_examples 'raise exception'
395
+ end
396
+ end
397
+ end
398
+
399
+ context 'RAILS_ENVがstandalone以外の場合' do
400
+ subject do
401
+ xhr :delete, :write, params, _session
402
+ end
403
+
404
+ it { expect { subject }.to raise_exception }
405
+
406
+ it 'プログラムをサーバ上から削除しない' do
407
+ begin
408
+ subject
409
+ rescue
410
+ expect(SourceCode).to have(1).records
411
+ end
412
+ end
413
+
414
+ it 'セッションから[:source_code]を削除しない' do
415
+ begin
416
+ subject
417
+ rescue
418
+ expect(session[:source_code]).to eq(_session[:source_code])
419
+ end
420
+ end
421
+ end
422
+ end
423
+
251
424
  describe 'プログラムを読み込む (POST load)' do
252
425
  before do
253
426
  post :load, source_code: { file: load_file }
@@ -18,7 +18,8 @@ describe SmalrubyEditor do
18
18
  end
19
19
 
20
20
  describe 'ホームディレクトリ' do
21
- %w(config db log tmp tmp/cache tmp/pids tmp/sessions tmp/sockets).each do |dir|
21
+ %w(config db log tmp
22
+ tmp/cache tmp/pids tmp/sessions tmp/sockets).each do |dir|
22
23
  it "#{dir}ディレクトリを作成する" do
23
24
  expect(@home_dir.join(dir)).to be_directory
24
25
  end
@@ -54,7 +55,8 @@ describe SmalrubyEditor do
54
55
  end
55
56
  end
56
57
 
57
- describe '引数を指定しない場合は環境変数SMALRUBY_EDITOR_HOMEに従う', create_home_directory: true do
58
+ describe '引数を指定しない場合は環境変数SMALRUBY_EDITOR_HOMEに従う',
59
+ create_home_directory: true do
58
60
  before(:all) do
59
61
  ENV['SMALRUBY_EDITOR_HOME'] = @home_dir.to_s
60
62
  SmalrubyEditor.create_home_directory
@@ -2,6 +2,17 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe SourceCode, 'Rubyのソースコードを表現するモデル' do
5
+ describe 'validation' do
6
+ describe 'filename' do
7
+ specify 'ディレクトリセパレータを含むことはできない' do
8
+ sc = SourceCode.new(filename: '/etc/passwd')
9
+ expect(sc).not_to be_valid
10
+ expect(sc.errors[:filename])
11
+ .to include('includes directory separator(s)')
12
+ end
13
+ end
14
+ end
15
+
5
16
  describe '#check_syntax', 'シンタックスをチェックする' do
6
17
  let(:source_code) {
7
18
  SourceCode.new(data: data)
data/spec/spec_helper.rb CHANGED
@@ -24,9 +24,6 @@ Spork.prefork do
24
24
  # in spec/support/ and its subdirectories.
25
25
  Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
26
26
 
27
- load 'spec/steps/global_variable.rb', true
28
- Dir.glob('spec/steps/**/*_steps.rb') { |f| load f, true }
29
-
30
27
  # Checks for pending migrations before tests are run.
31
28
  # If you are not using ActiveRecord, you can remove this line.
32
29
  ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
@@ -48,7 +45,7 @@ Spork.prefork do
48
45
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
49
46
  # examples within a transaction, remove the following line or assign false
50
47
  # instead of true.
51
- config.use_transactional_fixtures = true
48
+ config.use_transactional_fixtures = false
52
49
 
53
50
  # If true, the base class of anonymous controllers will be inferred
54
51
  # automatically. This will be the default behavior in future versions of
@@ -106,9 +103,40 @@ Spork.prefork do
106
103
 
107
104
  config.after(javascript: true) do
108
105
  page.execute_script('window.onbeforeunload = null')
109
- if selenium?
110
- FileUtils.rm_rf(downloads_dir)
111
- end
106
+ FileUtils.rm_rf(downloads_dir) if selenium?
107
+ end
108
+
109
+ config.before(:all, javascript: true, standalone: true) do
110
+ expire_assets_cache
111
+ end
112
+
113
+ config.after(:all, javascript: true, standalone: true) do
114
+ expire_assets_cache
115
+ end
116
+
117
+ config.before(javascript: true, standalone: true) do
118
+ page.driver.restart
119
+
120
+ @_rails_env = Rails.env
121
+ Rails.env = ENV['RAILS_ENV'] = 'standalone'
122
+ @_home = ENV['HOME']
123
+ @_tmpdir = ENV['HOME'] = Dir.mktmpdir('smalruby-')
124
+ end
125
+
126
+ config.after(javascript: true, standalone: true) do
127
+ FileUtils.remove_entry_secure(@_tmpdir)
128
+ ENV['HOME'] = @_home
129
+ Rails.env = ENV['RAILS_ENV'] = @_rails_env
130
+
131
+ page.driver.restart
132
+ end
133
+
134
+ config.before :suite do
135
+ DatabaseRewinder.clean_all
136
+ end
137
+
138
+ config.after :each do
139
+ DatabaseRewinder.clean
112
140
  end
113
141
  end
114
142
  end
@@ -4,8 +4,7 @@ text_editor = 'テキストエディタ'
4
4
 
5
5
  step 'テキストエディタにフォーカスがあること' do
6
6
  expect(page.evaluate_script(<<-JS)).to be_true
7
- $('#{NAME_INFO[text_editor][:selector]} textarea.ace_text-input').get(0) ==
8
- document.activeElement
7
+ ace.edit('#{NAME_INFO[text_editor][:id]}').isFocused()
9
8
  JS
10
9
  end
11
10
 
@@ -37,12 +37,10 @@ step 'ダウンロードが完了するまで待つ' do
37
37
  if poltergeist?
38
38
  until page.response_headers['Content-Disposition'] ||
39
39
  (start_time + 5.seconds) < Time.now
40
- sleep 1
40
+ sleep(1)
41
41
  end
42
42
  elsif selenium?
43
- until downloads_dir.exist? || (start_time + 5.seconds) < Time.now
44
- sleep 1
45
- end
43
+ sleep(1) until downloads_dir.exist? || (start_time + 5.seconds) < Time.now
46
44
  end
47
45
  end
48
46
 
@@ -66,6 +64,12 @@ step 'ダウンロードしないこと' do
66
64
  end
67
65
  end
68
66
 
67
+ step ':name にフォーカスを移す' do |name|
68
+ page.execute_script(<<-JS)
69
+ $('#{NAME_INFO[name][:selector]}').focus()
70
+ JS
71
+ end
72
+
69
73
  step ':name にフォーカスがあること' do |name|
70
74
  # HACK: 現在のPhantomJSでは$(':focus')は動作しない
71
75
  # https://github.com/netzpirat/guard-jasmine/issues/48
@@ -75,6 +79,8 @@ step ':name にフォーカスがあること' do |name|
75
79
  end
76
80
 
77
81
  step ':filename をロードする' do |filename|
82
+ step '"ロードボタン" にフォーカスを移す'
83
+
78
84
  # HACK: input#load-file[type="file"]は非表示要素であるため、.show()し
79
85
  # ないと見つけられずattach_fileが失敗する
80
86
  page.execute_script("$('#load-file').show()")
@@ -123,6 +129,7 @@ step '警告ダイアログの :name ボタンをクリックする' do |name|
123
129
  end
124
130
 
125
131
  step '確認メッセージ :message を表示すること' do |message|
132
+ message.gsub!('\n', "\n")
126
133
  if poltergeist?
127
134
  actual = page.evaluate_script('window.confirmMsg')
128
135
  page.execute_script('window.confirmMsg = null')
@@ -158,3 +165,19 @@ step '実際にはファイルをロードしないようにしておく' do
158
165
  JS
159
166
  end
160
167
  end
168
+
169
+ step 'ホームディレクトリに :program という内容の :filename が存在する' do |program, filename|
170
+ File.open(Pathname("~/#{filename}").expand_path, 'w') do |f|
171
+ f.write(program)
172
+ end
173
+ end
174
+
175
+ step 'ホームディレクトリに :filename が存在すること' do |filename|
176
+ path = Pathname("~/#{filename}").expand_path
177
+ expect(path).to be_exist
178
+ end
179
+
180
+ step 'ホームディレクトリの :filename の内容が :program であること' do |filename, program|
181
+ path = Pathname("~/#{filename}").expand_path
182
+ expect(path.read).to eq(program)
183
+ end
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # assetsのキャッシュを削除する
4
+ #
5
+ # ただし、このメソッドを呼び出した後で page.driver.restart を実行しなけ
6
+ # ればブラウザ側のキャッシュが有効になったままとなる。
7
+ def expire_assets_cache
8
+ env = Rails.application.assets
9
+ if Rails.application.config.cache_classes
10
+ env.instance_variable_set('@assets', {})
11
+ env.instance_variable_set('@digests', {})
12
+ env = env.instance_variable_get('@environment')
13
+ end
14
+ env.send(:expire_index!)
15
+ Dir.glob(Rails.root.join('tmp/cache/assets/test/sprockets/*')) { |f|
16
+ FileUtils.remove_entry_secure(f)
17
+ }
18
+ end
@@ -0,0 +1,15 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'tmpdir'
4
+
5
+ # Rails.envをstandaloneにする
6
+ shared_context 'set standalone rails env', set_standalone_rails_env: true do
7
+ before do
8
+ @_rails_env = Rails.env
9
+ Rails.env = ENV['RAILS_ENV'] = 'standalone'
10
+ end
11
+
12
+ after do
13
+ Rails.env = ENV['RAILS_ENV'] = @_rails_env
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ load 'spec/steps/global_variable.rb', true
2
+ Dir.glob('spec/steps{,/**}/*_steps.rb') { |f| load f, true }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smalruby-editor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kouji Takao
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-24 00:00:00.000000000 Z
11
+ date: 2014-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -246,7 +246,7 @@ files:
246
246
  - app/assets/images/.keep
247
247
  - app/assets/images/favicon.ico
248
248
  - app/assets/javascripts/application.js
249
- - app/assets/javascripts/editor.js.coffee
249
+ - app/assets/javascripts/editor.js.coffee.erb
250
250
  - app/assets/stylesheets/application.css
251
251
  - app/assets/stylesheets/editor.css.scss
252
252
  - app/assets/stylesheets/flatstrap-custom.css.scss
@@ -310,6 +310,7 @@ files:
310
310
  - public/robots.txt
311
311
  - smalruby-editor.gemspec
312
312
  - spec/acceptance/readme.md
313
+ - spec/acceptance/standalone/save.feature
313
314
  - spec/acceptance/text_editor/base.feature
314
315
  - spec/acceptance/text_editor/check.feature
315
316
  - spec/acceptance/text_editor/load.feature
@@ -325,16 +326,19 @@ files:
325
326
  - spec/steps/ace_steps.rb
326
327
  - spec/steps/global_variable.rb
327
328
  - spec/steps/text_editor_steps.rb
329
+ - spec/support/assets.rb
328
330
  - spec/support/capybara.rb
331
+ - spec/support/env.rb
332
+ - spec/turnip_helper.rb
329
333
  - vendor/assets/javascripts/.keep
330
334
  - vendor/assets/stylesheets/.keep
331
- - public/assets/application-abb819194306ad9fdda88e33fda0b06c.js
332
- - public/assets/application-abb819194306ad9fdda88e33fda0b06c.js.gz
335
+ - public/assets/application-759ce4d1ac1e0e3991e6d350c8f98fc5.js
336
+ - public/assets/application-759ce4d1ac1e0e3991e6d350c8f98fc5.js.gz
333
337
  - public/assets/application-f51ea0e777d97f12a8ce84308f31eb57.css
334
338
  - public/assets/application-f51ea0e777d97f12a8ce84308f31eb57.css.gz
335
339
  - public/assets/favicon-c7ae857bb9d06de8742ae2d337157e83.ico
336
340
  - public/assets/loading-e8e6dd7833131c92a6c3b9c8ccc6a6ac.gif
337
- - public/assets/manifest-0d0a1d57ad92e968912e72263d610a6e.json
341
+ - public/assets/manifest-3e334cebc159781a2b475049ad366f6a.json
338
342
  - public/assets/progressbar-82023a146fba2a0f6d925629ed2b09c5.gif
339
343
  homepage: https://github.com/smalruby/smalruby-editor
340
344
  licenses:
@@ -395,6 +399,7 @@ specification_version: 4
395
399
  summary: A visual programming editor.
396
400
  test_files:
397
401
  - spec/acceptance/readme.md
402
+ - spec/acceptance/standalone/save.feature
398
403
  - spec/acceptance/text_editor/base.feature
399
404
  - spec/acceptance/text_editor/check.feature
400
405
  - spec/acceptance/text_editor/load.feature
@@ -410,4 +415,7 @@ test_files:
410
415
  - spec/steps/ace_steps.rb
411
416
  - spec/steps/global_variable.rb
412
417
  - spec/steps/text_editor_steps.rb
418
+ - spec/support/assets.rb
413
419
  - spec/support/capybara.rb
420
+ - spec/support/env.rb
421
+ - spec/turnip_helper.rb
@@ -1,105 +0,0 @@
1
- # Place all the behaviors and hooks related to the matching controller here.
2
- # All this logic will automatically be available in application.js.
3
- # You can use CoffeeScript in this file: http://coffeescript.org/
4
-
5
- changed = false
6
-
7
- $ ->
8
- saving = false
9
-
10
- textEditor = ace.edit('text-editor')
11
- textEditor.setTheme('ace/theme/github')
12
- textEditor.setShowInvisibles(true)
13
-
14
- textEditor.focus()
15
- textEditor.gotoLine(0, 0)
16
-
17
- textEditor.on('change', (e) ->
18
- changed = true
19
- )
20
-
21
- session = textEditor.getSession()
22
- session.setMode('ace/mode/ruby')
23
- session.setTabSize(2)
24
- session.setUseSoftTabs(true)
25
-
26
- # FIXME: エディタの切り替え機能を実装するときに以下の処理を削除する。
27
- # なお、navbarの中のリンクの見た目をよくするためにa要素を使っているのだが、
28
- # デフォルトではRubyをクリックするとリンク先に遷移してしまう。そこでリンク
29
- # をクリックしてもリンク先に遷移しないようにしている。
30
- $('ul.nav li a').click (e) ->
31
- e.preventDefault()
32
- false
33
-
34
- $('#check-button').click (e) ->
35
- e.preventDefault()
36
- data =
37
- source_code:
38
- data: session.getDocument().getValue()
39
- success = (data, textStatus, jqXHR) ->
40
- for errorInfo in data
41
- do (errorInfo) ->
42
- msg = $('<div class="alert alert-error" style="display: none">')
43
- .append('<button type="button" class="close" data-dismiss="alert">×</button>')
44
- .append('<h4><i class="icon-exclamation-sign"></i>エラー</h4>')
45
- .append("#{errorInfo.row}行")
46
- if errorInfo.column > 0
47
- msg.append("、#{errorInfo.column}文字")
48
- msg.append(": #{errorInfo.message}")
49
- $('#messages').append(msg)
50
- msg.fadeIn('slow')
51
- $.post('/source_codes/check', data, success, 'json')
52
-
53
- $('#save-button').click (e) ->
54
- e.preventDefault()
55
- filename = $.trim($('#filename').val())
56
- if filename.length <= 0
57
- $('#filename').focus()
58
- else
59
- data =
60
- source_code:
61
- filename: filename
62
- data: session.getDocument().getValue()
63
- success = (data, textStatus, jqXHR) ->
64
- saving = true
65
- changed = false
66
- $('#download-link').click()
67
- $.post('/source_codes/', data, success, 'json')
68
-
69
- $('#filename').keypress (e) ->
70
- e = window.event if !e
71
- if e.keyCode == 13
72
- $('#save-button').click()
73
- false
74
- else
75
- true
76
-
77
- $('#load-button').click (e) ->
78
- e.preventDefault()
79
- if changed
80
- return unless confirm('まだセーブしていないのでロードするとプログラムが消えてしまうよ!それでもロードしますか?')
81
- $(@).parent().find('input[name="source_code[file]"]').click()
82
-
83
- $('#file-form').fileupload(
84
- dataType: 'json'
85
- done: (e, data) ->
86
- info = data.result.source_code
87
- if info.error
88
- msg = $('<div class="alert alert-error" style="display: none">')
89
- .append('<button type="button" class="close" data-dismiss="alert">×</button>')
90
- .append('<h4><i class="icon-exclamation-sign"></i>エラー</h4>')
91
- .append(info.filename + 'は' + info.error)
92
- $('#messages').append(msg)
93
- msg.fadeIn('slow').delay(5000).fadeOut('slow')
94
- else
95
- $('#filename').val(info.filename)
96
- session.getDocument().setValue(info.data)
97
- changed = false
98
- )
99
-
100
- window.onbeforeunload = (event) ->
101
- if !saving && session.getDocument().getValue().length > 0
102
- return '作成中のプログラムが消えてしまいます。'
103
- else
104
- saving = false
105
- return