smalruby-editor 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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