ruber 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGES +42 -1
  2. data/lib/ruber/application/application.rb +25 -5
  3. data/lib/ruber/application/plugin.yaml +2 -10
  4. data/lib/ruber/component_manager.rb +2 -2
  5. data/lib/ruber/document_project.rb +5 -3
  6. data/lib/ruber/editor/document.rb +5 -4
  7. data/lib/ruber/editor/ktexteditor_wrapper.rb +1 -1
  8. data/lib/ruber/exception_widgets.rb +1 -1
  9. data/lib/ruber/main_window/main_window.rb +4 -3
  10. data/lib/ruber/main_window/main_window_actions.rb +2 -2
  11. data/lib/ruber/main_window/main_window_internal.rb +1 -1
  12. data/lib/ruber/output_widget.rb +17 -5
  13. data/lib/ruber/project.rb +34 -3
  14. data/lib/ruber/project_dir_scanner.rb +171 -0
  15. data/lib/ruber/settings_container.rb +7 -7
  16. data/lib/ruber/settings_dialog.rb +24 -24
  17. data/lib/ruber/version.rb +1 -1
  18. data/lib/ruber/world/environment.rb +1 -0
  19. data/lib/ruber/world/plugin.yaml +7 -2
  20. data/lib/ruber/{application → world}/project_files_widget.rb +0 -0
  21. data/lib/ruber/{application → world}/ui/project_files_rule_chooser_widget.rb +2 -2
  22. data/lib/ruber/{application → world}/ui/project_files_rule_chooser_widget.ui +0 -0
  23. data/lib/ruber/{application → world}/ui/project_files_widget.rb +2 -2
  24. data/lib/ruber/{application → world}/ui/project_files_widget.ui +0 -0
  25. data/plugins/auto_end/auto_end.rb +21 -18
  26. data/plugins/autosave/autosave.rb +1 -1
  27. data/plugins/find_in_files/find_in_files.rb +1 -1
  28. data/plugins/irb/irb.png +0 -0
  29. data/plugins/irb/irb.rb +142 -0
  30. data/plugins/irb/irb.svg +240 -0
  31. data/plugins/irb/irb_controller.rb +541 -0
  32. data/plugins/irb/irbrc.rb +21 -0
  33. data/plugins/irb/plugin.yaml +19 -0
  34. data/plugins/irb/ui/irb_config_widget.rb +151 -0
  35. data/plugins/irb/ui/irb_config_widget.ui +123 -0
  36. data/plugins/irb/ui/irb_tool_widget.rb +97 -0
  37. data/plugins/irb/ui/irb_tool_widget.ui +86 -0
  38. data/plugins/project_browser/project_browser.rb +1 -1
  39. data/plugins/rspec/plugin.yaml +6 -3
  40. data/plugins/rspec/rspec.rb +172 -473
  41. data/plugins/rspec/tool_widget.rb +462 -0
  42. data/plugins/rspec/ui/rspec_project_widget.rb +58 -38
  43. data/plugins/rspec/ui/rspec_project_widget.ui +68 -64
  44. data/plugins/ruberri/class_formatter.rb +126 -0
  45. data/plugins/ruberri/method_formatter.rb +90 -0
  46. data/plugins/ruberri/plugin.yaml +13 -0
  47. data/plugins/ruberri/ruberri.rb +226 -0
  48. data/plugins/ruberri/search.rb +111 -0
  49. data/plugins/ruberri/ui/tool_widget.rb +73 -0
  50. data/plugins/ruberri/ui/tool_widget.ui +49 -0
  51. data/plugins/ruby_runner/ruby_runner.rb +2 -2
  52. data/plugins/ruby_syntax_checker/plugin.yaml +11 -0
  53. data/plugins/ruby_syntax_checker/ruby_syntax_checker.rb +147 -0
  54. data/plugins/syntax_checker/plugin.yaml +10 -6
  55. data/plugins/syntax_checker/syntax_checker.rb +216 -520
  56. data/plugins/syntax_checker/ui/config_widget.rb +61 -0
  57. data/plugins/syntax_checker/ui/config_widget.ui +44 -0
  58. data/plugins/yaml_syntax_checker/plugin.yaml +11 -0
  59. data/plugins/yaml_syntax_checker/yaml_syntax_checker.rb +62 -0
  60. data/ruber.desktop +0 -0
  61. data/spec/auto_end_spec.rb +224 -186
  62. data/spec/document_project_spec.rb +9 -1
  63. data/spec/document_spec.rb +9 -0
  64. data/spec/environment_spec.rb +12 -0
  65. data/spec/output_widget_spec.rb +69 -2
  66. data/spec/project_dir_scanner_spec.rb +195 -0
  67. data/spec/project_spec.rb +43 -73
  68. data/spec/ruby_syntax_checker_spec.rb +361 -0
  69. data/spec/syntax_checker_spec.rb +1132 -0
  70. data/spec/yaml_syntax_checker_spec.rb +130 -0
  71. metadata +232 -225
  72. data/lib/ruber/application/project_files_list.rb +0 -320
  73. data/spec/project_files_list_spec.rb +0 -411
@@ -0,0 +1,361 @@
1
+ require 'spec/framework'
2
+ require 'spec/common'
3
+
4
+ require 'tempfile'
5
+
6
+ require 'plugins/ruby_syntax_checker/ruby_syntax_checker'
7
+
8
+ describe Ruber::RubySyntaxChecker::Plugin do
9
+
10
+ RSpec::Matchers.define :be_same_error_as do |other_err|
11
+ match do |err|
12
+ err.line == other_err.line and err.column == other_err.column and
13
+ err.message == other_err.message and err.formatted_message ==
14
+ other_err.formatted_message
15
+ end
16
+ end
17
+
18
+ NEEDED_PLUGINS = [:syntax_checker, :autosave, :ruby_runner, :ruby_development]
19
+
20
+ before :all do
21
+ NEEDED_PLUGINS.each do |pl|
22
+ dir = File.join 'plugins', pl.to_s
23
+ Ruber[:components].load_plugin dir
24
+ end
25
+ end
26
+
27
+ before do
28
+ Ruber[:components].load_plugin 'plugins/ruby_syntax_checker'
29
+ end
30
+
31
+ after do
32
+ Ruber[:components].unload_plugin :ruby_syntax_checker if Ruber[:ruby_syntax_checker]
33
+ end
34
+
35
+ after :all do
36
+ NEEDED_PLUGINS.reverse_each{|pl| Ruber[:components].unload_plugin pl}
37
+ end
38
+
39
+ describe 'when created' do
40
+
41
+ before do
42
+ @file = Tempfile.open ['', '.rb']
43
+ end
44
+
45
+ after do
46
+ @file.close
47
+ end
48
+
49
+ it 'registers a syntax checker of class Ruber::RubySyntaxChecker::Checker for files ending in .rb with the syntax checker' do
50
+ checker = Ruber::RubySyntaxChecker::Checker.new flexmock
51
+ flexmock(Ruber::RubySyntaxChecker::Checker).should_receive(:new).once.with(Ruber::Document).and_return checker
52
+ doc = Ruber[:world].document @file.path
53
+ end
54
+
55
+ end
56
+
57
+ describe 'when unloaded' do
58
+
59
+ before do
60
+ @file = Tempfile.open ['', '.rb']
61
+ end
62
+
63
+ after do
64
+ @file.close
65
+ end
66
+
67
+ it 'removes the Ruber::RubySyntaxChecker::Checker syntax checker' do
68
+ Ruber[:components].unload_plugin :ruby_syntax_checker
69
+ flexmock(Ruber::RubySyntaxChecker::Checker).should_receive(:new).never
70
+ doc = Ruber[:world].document @file.path
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ describe Ruber::RubySyntaxChecker::Checker do
78
+ NEEDED_PLUGINS = [:syntax_checker, :autosave, :ruby_runner, :ruby_development]
79
+
80
+ before :all do
81
+ NEEDED_PLUGINS.each do |pl|
82
+ dir = File.join 'plugins', pl.to_s
83
+ Ruber[:components].load_plugin dir
84
+ end
85
+ end
86
+
87
+ after :all do
88
+ NEEDED_PLUGINS.reverse_each{|pl| Ruber[:components].unload_plugin pl}
89
+ end
90
+
91
+ describe '#check_syntax' do
92
+
93
+ before do
94
+ @file = Tempfile.open ['', '.rb']
95
+ @doc = Ruber[:world].document @file.path
96
+ @checker = Ruber::RubySyntaxChecker::Checker.new @doc
97
+ end
98
+
99
+ after do
100
+ @doc.close false
101
+ end
102
+
103
+ context 'when the first argument is valid ruby' do
104
+
105
+ it 'returns nil' do
106
+ str = <<-EOS
107
+ class X
108
+ def y
109
+ if z
110
+ 1 + 2
111
+ end
112
+ end
113
+ end
114
+ EOS
115
+ @doc.text = str
116
+ @checker.check_syntax(str, true).should be_nil
117
+ end
118
+
119
+ end
120
+
121
+ context 'when the first argument is not valid ruby' do
122
+
123
+ it 'returns an array of Ruber::RubySyntaxChecker::SyntaxError with the line of the error if the column is unknown' do
124
+ str = <<-EOS
125
+ class X
126
+ def y
127
+ 1 +
128
+ end
129
+ end
130
+ EOS
131
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(3, nil, 'unexpected keyword_end', 'unexpected keyword_end')
132
+ @checker.check_syntax(str, false)[0].should be_same_error_as(exp)
133
+ end
134
+
135
+ it 'determines the column number from lines containing all spaces and a single ^' do
136
+ str = <<-EOS
137
+ def x
138
+ Y = 1
139
+ end
140
+ EOS
141
+ @doc.text = str
142
+ @checker.check_syntax(str, false)[0].column.should == 5
143
+ end
144
+
145
+ it 'attempts to compute the correct position if the error line before the one with the ^ starts with ...' do
146
+ pending 'find an error which causes the above situation'
147
+ end
148
+
149
+ context 'when ruby reports a syntax error' do
150
+
151
+ error_types = [
152
+ [:missing_end, "ruby reports a missing end keyword", 'class X;def y;end'],
153
+ [:extra_end, "ruby reports an unexpected end keyword at EOF", 'class X;def y;end;end;end'],
154
+ [:misplaced_end, "ruby reports an unexpected end keyword not at EOF", 'def x;1+end'],
155
+ [:missing_close_paren, "ruby reports an expected )", 'def x;1*(2+1;end'],
156
+ [:extra_close_paren, "ruby reports an unexpected )", 'x;1*2+1)'],
157
+ [:missing_close_bracket, "ruby reports an expected ]", 'x=['],
158
+ [:extra_close_bracket, "ruby reports an unexpected ]", 'x=]'],
159
+ [:missing_close_brace, "ruby reports an expected }", 'x={'],
160
+ [:extra_close_brace, "ruby reports an unexpected }", 'x=}'],
161
+ [:extra_else, "ruby reports an unexpected else", "x;else;end"],
162
+ [:missing_quote, "ruby reports an unterminated string", "def x;'"],
163
+ [:missing_regexp_close_paren, "ruby reports unmatched open parenthesis in regexp", '/xy ( a/'],
164
+ [:extra_regexp_close_paren, "ruby reports unmatched close parenthesis in regexp", '/xy ) a/'],
165
+ [:missing_regexp_close_bracket, "ruby reports premature end of char-class", '/ xy [/'],
166
+ [:unknown_regexp_option, "ruby reports an unknown regexp option", '/a/t'],
167
+ [:dynamic_constant_assignment , "ruby reports a dynamic constant assignment", 'def x;X=2;end'],
168
+ [:extra_when, "ruby reports an unexpected when keyword", 'when 2 then 3'],
169
+ [:extra_rescue, "ruby reports an unexpected rescue keyword", 'rescue'],
170
+ [:extra_ensure, "ruby reports an unexpected ensure keyword", 'ensure'],
171
+ [:missing_block_comment_end, "ruby reports an embedded document meets end of file", '=begin'],
172
+ [:missing_heredoc_end, "ruby reports it can't find the end of a heredoc", "str=<<EOS"]
173
+ ]
174
+
175
+ error_types.each do |type, cond, code|
176
+ it "sets the error_type attribute of the error object to #{type} if #{cond}" do
177
+ errors = @checker.check_syntax(code, false)
178
+ errors[0].error_type.should == type
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ it 'works correctly when ruby reports unmatched open parenthesis in a regexp' do
185
+ str = '/xy ( a/'
186
+ @doc.text = str
187
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(0, nil, 'end pattern with unmatched parenthesis: /xy ( a/', 'end pattern with unmatched parenthesis: /xy ( a/')
188
+ @checker.check_syntax(str, false)[0].should be_same_error_as(exp)
189
+ end
190
+
191
+ it 'works correctly when ruby reports the premature end of a character class' do
192
+ str = '/xy [ a/'
193
+ @doc.text = str
194
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(0, nil, 'premature end of char-class: /xy [ a/', 'premature end of char-class: /xy [ a/')
195
+ @checker.check_syntax(str, false)[0].should be_same_error_as(exp)
196
+ end
197
+
198
+ it 'works correctly when ruby reports an invalid regexp option' do
199
+ str = '/xy/t'
200
+ @doc.text = str
201
+ exp = [Ruber::RubySyntaxChecker::SyntaxError.new(0, nil, 'unknown regexp option - t', 'unknown regexp option - t')]
202
+ @checker.check_syntax(str, false).should be_same_error_as(exp)
203
+ end
204
+
205
+ it 'works correctly when ruby reports a class definition in a method body' do
206
+ str = <<-EOS
207
+ def x
208
+ class Y
209
+ end
210
+ end
211
+ EOS
212
+ @doc.text = str
213
+ msg = 'class definition in method body'
214
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(1, nil, msg, msg)
215
+ @checker.check_syntax(str, false)[0].should == exp
216
+ end
217
+
218
+ it 'works correctly when ruby reports a module definition in a method body' do
219
+ str = <<-EOS
220
+ def x
221
+ module Y
222
+ end
223
+ end
224
+ EOS
225
+ @doc.text = str
226
+ msg = 'module definition in method body '
227
+ exp = [Ruber::RubySyntaxChecker::SyntaxError.new(1, nil, msg, msg)]
228
+ @checker.check_syntax(str, false).should == exp
229
+ end
230
+
231
+ it 'works correctly when ruby reports an unterminated string' do
232
+ @doc.text = 'def x;"'
233
+ msg = "unterminated string meets end of file "
234
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(0, nil, msg, msg)
235
+ @checker.check_syntax(@doc.text, false)[0].should be_same_error_as(exp)
236
+ end
237
+
238
+ it 'works correctly when ruby reports an unterminated block comment' do
239
+ @doc.text = '=begin x'
240
+ msg = "embedded document meets end of file "
241
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(0, nil, msg, msg)
242
+ @checker.check_syntax(@doc.text, false)[0].should be_same_error_as(exp)
243
+ end
244
+
245
+ it 'works correctly when ruby reports an unterminated heredoc' do
246
+ @doc.text = "str=<<EOS"
247
+ msg = 'can\'t find string "EOS" anywhere before EOF '
248
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(0, nil, msg, msg)
249
+ @checker.check_syntax(@doc.text, false)[0].should be_same_error_as(exp)
250
+ end
251
+
252
+ it 'works correctly when ruby reports a dynamic constant assignment' do
253
+ str = <<-EOS
254
+ def x
255
+ Y = 1
256
+ end
257
+ EOS
258
+ @doc.text = str
259
+ msg = "dynamic constant assignment \n Y = 1"
260
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(1, 5, msg, msg)
261
+ @checker.check_syntax(str, false)[0].should be_same_error_as(exp)
262
+ end
263
+
264
+ it 'works correctly with unknown syntax errors' do
265
+ error_msg = '-e:10:xyz'
266
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
267
+ exp = Ruber::RubySyntaxChecker::SyntaxError.new(nil, nil, error_msg, error_msg)
268
+ @checker.check_syntax('', false)[0].should be_same_error_as(exp)
269
+ end
270
+
271
+ end
272
+
273
+ it 'uses the ruby interpreter stored in the ruby/ruby project option' do
274
+ # To check whether the correct interpreter is being used, a syntax which is
275
+ # correct in ruby 1.8 and incorrect in 1.9 is used
276
+ @doc.own_project[:ruby, :ruby] = '/usr/bin/ruby18'
277
+ str = <<-EOS
278
+ if x: y
279
+ else z
280
+ end
281
+ EOS
282
+ @doc.text = str
283
+ @checker.check_syntax(str, false).should be_nil
284
+ end
285
+
286
+ it 'raises Ruber::SyntaxChecker::SyntaxNotChecked if the given interpreter doesn\'t exist' do
287
+ @doc.own_project[:ruby, :ruby] = '/usr/bin/nonexisting-ruby'
288
+ lambda{@checker.check_syntax('', false)}.should raise_error(Ruber::SyntaxChecker::SyntaxNotChecked)
289
+ end
290
+
291
+ context 'if the second argument is true' do
292
+
293
+ it 'replaces instances of "expected $end" with "expected end of file" in the formatter error message' do
294
+ error_msg = '-e:12: syntax error, expected $end'
295
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
296
+ res = @checker.check_syntax('', true)
297
+ res[0].formatted_message.should == 'expected end of file'
298
+ end
299
+
300
+ it 'replaces instances of "expecting $end" with "expecting end of file" in the formatter error message' do
301
+ error_msg = '-e:12: syntax error, expecting $end'
302
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
303
+ res = @checker.check_syntax('', true)
304
+ res[0].formatted_message.should == 'expecting end of file'
305
+ end
306
+
307
+ it 'replaces instances of "unexpected $end" with "unexpected end of file" in the formatter error message' do
308
+ error_msg = '-e:12: syntax error, unexpected $end'
309
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
310
+ res = @checker.check_syntax('', true)
311
+ res[0].formatted_message.should == 'unexpected end of file'
312
+ end
313
+
314
+ it 'replaces instances of "expected kEND" with "expected `end` keyword" in the formatter error message' do
315
+ error_msg = '-e:12: syntax error, expected kEND'
316
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
317
+ res = @checker.check_syntax('', true)
318
+ res[0].formatted_message.should == 'expected `end` keyword'
319
+ end
320
+
321
+ it 'replaces instances of "expecting kEND" with "expecting `end` keyword" in the formatter error message' do
322
+ error_msg = '-e:12: syntax error, expecting kEND'
323
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
324
+ res = @checker.check_syntax('', true)
325
+ res[0].formatted_message.should == 'expecting `end` keyword'
326
+ end
327
+
328
+ it 'replaces instances of "unexpected kEND" with "unexpected `end` keyword" in the formatter error message' do
329
+ error_msg = '-e:12: syntax error, unexpected kEND'
330
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
331
+ res = @checker.check_syntax('', true)
332
+ res[0].formatted_message.should == 'unexpected `end` keyword'
333
+ end
334
+
335
+ it 'replaces instances of "expected keyword_end" with "expected `end` keyword" in the formatter error message' do
336
+ error_msg = '-e:12: syntax error, expected keyword_end'
337
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
338
+ res = @checker.check_syntax('', true)
339
+ res[0].formatted_message.should == 'expected `end` keyword'
340
+ end
341
+
342
+ it 'replaces instances of "expecting keyword_end" with "expecting `end` keyword" in the formatter error message' do
343
+ error_msg = '-e:12: syntax error, expecting keyword_end'
344
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
345
+ res = @checker.check_syntax('', true)
346
+ res[0].formatted_message.should == 'expecting `end` keyword'
347
+ end
348
+
349
+ it 'replaces instances of "unexpected keyword_end" with "unexpected `end` keyword" in the formatter error message' do
350
+ error_msg = '-e:12: syntax error, unexpected keyword_end'
351
+ flexmock(Open3).should_receive(:popen3).once.and_return error_msg
352
+ res = @checker.check_syntax('', true)
353
+ res[0].formatted_message.should == 'unexpected `end` keyword'
354
+ end
355
+
356
+ end
357
+
358
+ end
359
+
360
+
361
+ end
@@ -0,0 +1,1132 @@
1
+ require 'spec/framework'
2
+ require 'spec/common'
3
+
4
+ require 'tempfile'
5
+
6
+ require 'plugins/syntax_checker/syntax_checker'
7
+
8
+ describe Ruber::SyntaxChecker::Plugin do
9
+
10
+ before do
11
+ Ruber[:world].close_all :documents, :discard
12
+ Ruber[:components].load_plugin 'plugins/syntax_checker/'
13
+ @plug = Ruber[:syntax_checker]
14
+ end
15
+
16
+ after do
17
+ Ruber[:world].close_all :documents, :discard
18
+ Ruber[:components].unload_plugin :syntax_checker
19
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
20
+ led.delete_later
21
+ Qt::Application.send_posted_events led, Qt::Event::DeferredDelete
22
+ end
23
+
24
+ it 'inherits Ruber::Plugin' do
25
+ Ruber::SyntaxChecker::Plugin.ancestors.should include(Ruber::Plugin)
26
+ end
27
+
28
+ context 'when created' do
29
+
30
+ it 'has no registered syntax checkers' do
31
+ @plug.instance_variable_get(:@syntax_checkers).should == {}
32
+ end
33
+
34
+ it 'inserts a KDE::Led in the status bar' do
35
+ Ruber[:main_window].status_bar.find_children(KDE::Led).should_not be_empty
36
+ end
37
+
38
+ it 'sets the current status to :unknown' do
39
+ @plug.current_status.should == :unknown
40
+ end
41
+
42
+ end
43
+
44
+ context 'when unloaded' do
45
+
46
+ it 'removes the led from the status bar' do
47
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
48
+ led_id = led.object_id
49
+ flexmock(Ruber[:main_window].status_bar).should_receive(:remove_widget).with(FlexMock.on{|arg| arg.object_id == led_id}).once
50
+ #The plugin will be unloaded in the after block
51
+ end
52
+
53
+ end
54
+
55
+ describe 'format_error' do
56
+
57
+ context 'if the argument\'s line, column and formatted_message methods all return non nil' do
58
+
59
+ it 'returns a string of the form "Line lineno+1, column columnno+1: formatted_message"' do
60
+ err = OpenStruct.new :line => 5, :column => 3, :formatted_message => 'Xyz'
61
+ @plug.format_error(err).should == 'Line 6, column 4: Xyz'
62
+ end
63
+
64
+ end
65
+
66
+ context 'if the argument\'s line and formatted_message methods all return non nil but the column method returns nil' do
67
+
68
+ it 'returns a string of the form "Line lineno+1: formatted_message"' do
69
+ err = OpenStruct.new :line => 5, :formatted_message => 'Xyz'
70
+ @plug.format_error(err).should == 'Line 6: Xyz'
71
+ end
72
+
73
+ end
74
+
75
+ context 'if the line method of the argument returns nil and the formatted_message method does not' do
76
+
77
+ it 'returns a string containing only the formatted message' do
78
+ err = OpenStruct.new :formatted_message => 'Xyz'
79
+ @plug.format_error(err).should == 'Xyz'
80
+ err = OpenStruct.new :column => 4, :formatted_message => 'Xyz'
81
+ @plug.format_error(err).should == 'Xyz'
82
+ end
83
+
84
+ end
85
+
86
+ context 'if the formatted_message method of the argument returns nil' do
87
+
88
+ it 'uses the value returned by the message method of the argument if not nil' do
89
+ err = OpenStruct.new :line => 5, :column => 3, :message => 'Xyz'
90
+ @plug.format_error(err).should == 'Line 6, column 4: Xyz'
91
+ err = OpenStruct.new :line => 5, :message => 'Xyz'
92
+ @plug.format_error(err).should == 'Line 6: Xyz'
93
+ err = OpenStruct.new :message => 'Xyz'
94
+ @plug.format_error(err).should == 'Xyz'
95
+ err = OpenStruct.new :column => 5, :message => 'Xyz'
96
+ @plug.format_error(err).should == 'Xyz'
97
+ end
98
+
99
+ it 'uses the string "UNKNOWN ERROR" if the message method also returns nil' do
100
+ err = OpenStruct.new :line => 5, :column => 3
101
+ @plug.format_error(err).should == 'Line 6, column 4: UNKNOWN ERROR'
102
+ err = OpenStruct.new :line => 5
103
+ @plug.format_error(err).should == 'Line 6: UNKNOWN ERROR'
104
+ err = OpenStruct.new
105
+ @plug.format_error(err).should == 'UNKNOWN ERROR'
106
+ err = OpenStruct.new :column => 5
107
+ @plug.format_error(err).should == 'UNKNOWN ERROR'
108
+ end
109
+
110
+ end
111
+
112
+ it 'translates all messages using KDE.i18n' do
113
+ flexmock(KDE).should_receive(:i18n).once.with('Line %d').and_return('Riga %d')
114
+ flexmock(KDE).should_receive(:i18n).once.with(', column %d: ').and_return(', colonna %d: ')
115
+ flexmock(KDE).should_receive(:i18n).once.with('Xyz').and_return('Abc')
116
+ flexmock(KDE).should_receive(:i18n).once.with('UNKNOWN ERROR').and_return('ERRORE SCONOSCIUTO')
117
+ err = OpenStruct.new :line => 5, :column => 3, :formatted_message => 'Xyz'
118
+ @plug.format_error(err).should == 'Riga 6, colonna 4: Abc'
119
+ @plug.format_error(OpenStruct.new).should == 'ERRORE SCONOSCIUTO'
120
+ end
121
+
122
+ end
123
+
124
+ describe '#set_current_status' do
125
+
126
+ context 'if the first argument is :correct' do
127
+
128
+ it 'sets the current_errors attribute to nil' do
129
+ @plug.instance_variable_set :@current_errors, []
130
+ @plug.set_current_status :correct
131
+ @plug.current_errors.should be_nil
132
+ end
133
+
134
+ it 'sets the led to the color contained in the COLORS hash under the :correct key' do
135
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
136
+ led.color = Qt::Color.new Qt.black
137
+ @plug.set_current_status :correct
138
+ led.color.should == Ruber::SyntaxChecker::Plugin::COLORS[:correct]
139
+ end
140
+
141
+ it 'sets the tooltip of the led to "No syntax errors"' do
142
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
143
+ @plug.set_current_status :correct
144
+ led.tool_tip.should == "No syntax errors"
145
+ end
146
+
147
+ it 'sets the current_status attribute to :correct' do
148
+ @plug.set_current_status :correct
149
+ @plug.current_status.should == :correct
150
+ end
151
+
152
+ end
153
+
154
+ context 'if the first argument is :unknown' do
155
+
156
+ it 'sets the current_errors attribute to nil' do
157
+ @plug.instance_variable_set :@current_errors, []
158
+ @plug.set_current_status :unknown
159
+ @plug.current_errors.should be_nil
160
+ end
161
+
162
+ it 'sets the led to the color contained in the COLORS hash under the :unknown key' do
163
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
164
+ led.color = Qt::Color.new Qt.black
165
+ @plug.set_current_status :unknown
166
+ led.color.should == Ruber::SyntaxChecker::Plugin::COLORS[:unknown]
167
+ end
168
+
169
+ it 'sets the tooltip of the led to "Unknown document type"' do
170
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
171
+ @plug.set_current_status :unknown
172
+ led.tool_tip.should == "Unknown document type"
173
+ end
174
+
175
+ it 'sets the current_status attribute to :unknown' do
176
+ @plug.set_current_status :unknown
177
+ @plug.current_status.should == :unknown
178
+ end
179
+
180
+ end
181
+
182
+ context 'if the first argument is :incorrect' do
183
+
184
+ before do
185
+ @errors = [
186
+ OpenStruct.new( :line => 15, :column => 2, :message => 'xyz', :formatted_message=> 'XYZ'),
187
+ OpenStruct.new( :line => 15, :column => 0, :message => 'abc', :formatted_message=> 'ABC'),
188
+ ]
189
+ end
190
+
191
+ context 'and the second argument is an array with at least one element' do
192
+
193
+ it 'stores the contents of the second argument in the current_errors attribute' do
194
+ @plug.set_current_status :incorrect, @errors
195
+ @plug.current_errors.should == @errors
196
+ @plug.current_errors.should_not equal(@errors)
197
+ end
198
+
199
+ it 'sets the tooltip of the led to a list of the formatted_message for the errors given as second argument, preceded by lines and columns (increased by 1)' do
200
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
201
+ @plug.set_current_status :incorrect, @errors
202
+ led.tool_tip.should == "Line 16, column 3: XYZ\nLine 16, column 1: ABC"
203
+ end
204
+
205
+ end
206
+
207
+ context 'and the second argument is an empty array' do
208
+
209
+ it 'stores an empty array in the current_errors attribute' do
210
+ @plug.set_current_status :incorrect, []
211
+ @plug.current_errors.should == []
212
+ end
213
+
214
+ it 'sets the tooltip of the led to "There are syntax errors"' do
215
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[-1]
216
+ @plug.set_current_status :incorrect, []
217
+ led.tool_tip.should == "There are syntax errors"
218
+ end
219
+
220
+ end
221
+
222
+ it 'sets the led to the color contained in the COLORS hash under the :incorrect key' do
223
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
224
+ led.color = Qt::Color.new Qt.black
225
+ @plug.set_current_status :incorrect, @errors
226
+ led.color.should == Ruber::SyntaxChecker::Plugin::COLORS[:incorrect]
227
+ end
228
+
229
+ it 'sets the current_status attribute to :incorrect' do
230
+ @plug.set_current_status :incorrect
231
+ @plug.current_status.should == :incorrect
232
+ end
233
+
234
+ end
235
+
236
+ end
237
+
238
+ context 'when the user right-clicks on the led' do
239
+
240
+ before do
241
+ @led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
242
+ end
243
+
244
+ context 'if the current_status attribute is :correct' do
245
+
246
+ it 'displays a menu with the entry "No syntax errors"' do
247
+ @plug.set_current_status :correct
248
+ actions_prc = Proc.new do |list|
249
+ list.count == 1 && list[0].text == 'No syntax errors'
250
+ end
251
+ flexmock(Qt::Menu).should_receive(:exec).once.with(FlexMock.on(&actions_prc), Qt::Point)
252
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
253
+ Qt::Application.send_event(@led, ev)
254
+ end
255
+
256
+ end
257
+
258
+ context 'if the current_status attribute is :unknown' do
259
+
260
+ it 'doesn\'t display any menu' do
261
+ @plug.set_current_status :unknown
262
+ flexmock(Qt::Menu).should_receive(:exec).never
263
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
264
+ Qt::Application.send_event(@led, ev)
265
+ end
266
+
267
+ end
268
+
269
+ context 'if the current_status attribute is :incorrect' do
270
+
271
+ context 'and the current_errors attribute is empty' do
272
+
273
+ it 'displays a menu with the entry "There are syntax errors"' do
274
+ @plug.set_current_status :incorrect
275
+ actions_prc = Proc.new do |list|
276
+ list.count == 1 && list[0].text == 'There are syntax errors'
277
+ end
278
+ flexmock(Qt::Menu).should_receive(:exec).once.with(FlexMock.on(&actions_prc), Qt::Point)
279
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
280
+ Qt::Application.send_event(@led, ev)
281
+ end
282
+
283
+ end
284
+
285
+ context 'and the current_errors attribute is not empty' do
286
+
287
+ before do
288
+ @errors = [
289
+ OpenStruct.new(:line => 15, :column => 2, :message => 'xyz', :formatted_message => 'XYZ'),
290
+ OpenStruct.new(:line => 15, :mesage => 'abc', :formatted_message => 'ABC'),
291
+ ]
292
+ @plug.set_current_status :incorrect, @errors
293
+ end
294
+
295
+ it 'displays a menu with an entry for each error (increasing lines and columns by 1)' do
296
+ exp_texts = ["Line 16, column 3: XYZ", "Line 16: ABC"]
297
+ actions_prc = Proc.new{|list| list.map{|a| a.text} == exp_texts}
298
+ flexmock(Qt::Menu).should_receive(:exec).once.with(FlexMock.on(&actions_prc), Qt::Point)
299
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
300
+ Qt::Application.send_event(@led, ev)
301
+ end
302
+
303
+ end
304
+
305
+ end
306
+
307
+ end
308
+
309
+ context 'when the user clicks on an entry in the context menu' do
310
+
311
+ before do
312
+ @led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
313
+ @docs = Array.new(3){Ruber[:world].new_document}
314
+ @views = @docs.map{|d| Ruber[:world].active_environment.editor_for! d}
315
+ Ruber[:world].active_environment.activate_editor @views[0]
316
+ end
317
+
318
+ context 'and the current_errors attribute contains errors' do
319
+
320
+ before do
321
+ @errors = [
322
+ OpenStruct.new(:line => 15, :column => 2, :message => 'xyz', :formatted_message=> 'XYZ'),
323
+ OpenStruct.new(:line => 15, :message => 'abc', :formatted_message => 'ABC'),
324
+ OpenStruct.new(:message =>'fgh'),
325
+ ]
326
+ @plug.set_current_status :incorrect, @errors
327
+ end
328
+
329
+ it 'moves the cursor of the current view to the line and column of the error corresponding to the chosen entry' do
330
+ actions = @errors.map{|e| KDE::Action.new(e.format, nil)}
331
+ actions.each do |a|
332
+ flexmock(KDE::Action).should_receive(:new).once.and_return(a)
333
+ end
334
+ flexmock(Qt::Menu).should_receive(:exec).once.and_return(actions[0])
335
+ flexmock(@views[0]).should_receive(:go_to).with(15, 2).once
336
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
337
+ Qt::Application.send_event(@led, ev)
338
+ end
339
+
340
+ it 'moves the cursor to column 0 of the appropriate line if the error doesn\'t specify a column number' do
341
+ actions = @errors.map{|e| KDE::Action.new(e.format, nil)}
342
+ actions.each do |a|
343
+ flexmock(KDE::Action).should_receive(:new).once.and_return(a)
344
+ end
345
+ flexmock(Qt::Menu).should_receive(:exec).once.and_return(actions[1])
346
+ flexmock(@views[0]).should_receive(:go_to).with(15, 0).once
347
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
348
+ Qt::Application.send_event(@led, ev)
349
+ end
350
+
351
+ it 'doesn\'t move the cursor if the error doesn\'t specify a line number' do
352
+ actions = @errors.map{|e| KDE::Action.new(e.format, nil)}
353
+ actions.each do |a|
354
+ flexmock(KDE::Action).should_receive(:new).once.and_return(a)
355
+ end
356
+ flexmock(Qt::Menu).should_receive(:exec).once.and_return(actions[2])
357
+ flexmock(@views[0]).should_receive(:go_to).never
358
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
359
+ Qt::Application.send_event(@led, ev)
360
+ end
361
+
362
+ end
363
+
364
+ context 'and the current_errors attribute is nil or empty' do
365
+
366
+ it 'does nothing' do
367
+ action = KDE::Action.new 'Text', nil
368
+ flexmock(KDE::Action).should_receive(:new).and_return(action)
369
+ flexmock(Qt::Menu).should_receive(:exec).and_return(action)
370
+ flexmock(@views[0]).should_receive(:go_to).never
371
+ @plug.set_current_status :correct
372
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
373
+ Qt::Application.send_event(@led, ev)
374
+ @plug.set_current_status :unknown
375
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
376
+ Qt::Application.send_event(@led, ev)
377
+ @plug.set_current_status :incorrect
378
+ ev = Qt::ContextMenuEvent.new Qt::ContextMenuEvent::Mouse, Qt::Point.new(0,0)
379
+ Qt::Application.send_event(@led, ev)
380
+ end
381
+
382
+ end
383
+
384
+ end
385
+
386
+ context 'when the user clicks on the led' do
387
+
388
+ before do
389
+ @led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
390
+ @docs = Array.new(3){Ruber[:world].new_document}
391
+ @views = @docs.map{|d| Ruber[:world].active_environment.editor_for! d}
392
+ Ruber[:world].active_environment.activate_editor @views[0]
393
+ end
394
+
395
+ context 'and the current_errors attribute contains errors' do
396
+
397
+ before do
398
+ @errors = [
399
+ OpenStruct.new(:line => 15, :column => 2, :message => 'xyz', :formatted_message=> 'XYZ'),
400
+ OpenStruct.new(:line => 15, :message => 'abc', :formatted_message => 'ABC'),
401
+ OpenStruct.new(:message =>'fgh'),
402
+ ]
403
+ @plug.set_current_status :incorrect, @errors
404
+ end
405
+
406
+ it 'moves the cursor to the line and column associated with the first error' do
407
+ flexmock(@views[0]).should_receive(:go_to).with(15, 2).once
408
+ ev = Qt::MouseEvent.new Qt::Event::MouseButtonRelease, Qt::Point.new(0,0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier
409
+ Qt::Application.send_event(@led, ev)
410
+ end
411
+
412
+ it 'moves the cursor to column 0 of the appropriate line if the error doesn\'t specify a column number' do
413
+ @errors[0].column = nil
414
+ flexmock(@views[0]).should_receive(:go_to).with(15, 0).once
415
+ ev = Qt::MouseEvent.new Qt::Event::MouseButtonRelease, Qt::Point.new(0,0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier
416
+ Qt::Application.send_event(@led, ev)
417
+ end
418
+
419
+ it 'does nothing if the first error doesn\'t specify a line number' do
420
+ @errors[0].line = nil
421
+ flexmock(@views[0]).should_receive(:go_to).never
422
+ ev = Qt::MouseEvent.new Qt::Event::MouseButtonRelease, Qt::Point.new(0,0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier
423
+ Qt::Application.send_event(@led, ev)
424
+ end
425
+
426
+ end
427
+
428
+ context 'and the current_errors attribute is nil or empty' do
429
+
430
+ it 'does nothing' do
431
+ flexmock(@views[0]).should_receive(:go_to).never
432
+ @plug.set_current_status :correct
433
+ ev = Qt::MouseEvent.new Qt::Event::MouseButtonRelease, Qt::Point.new(0,0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier
434
+ Qt::Application.send_event(@led, ev)
435
+ @plug.set_current_status :unknown
436
+ ev = Qt::MouseEvent.new Qt::Event::MouseButtonRelease, Qt::Point.new(0,0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier
437
+ Qt::Application.send_event(@led, ev)
438
+ @plug.set_current_status :incorrect
439
+ ev = Qt::MouseEvent.new Qt::Event::MouseButtonRelease, Qt::Point.new(0,0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier
440
+ Qt::Application.send_event(@led, ev)
441
+ end
442
+
443
+ end
444
+
445
+ end
446
+
447
+ describe '#register_syntax_checker' do
448
+
449
+ before do
450
+ @cls = Class.new
451
+ @mimetypes = %w[application/x-ruby text/x-python]
452
+ @patterns = %w[*.rb *.py]
453
+ end
454
+
455
+ it 'stores the given class with the associated rules' do
456
+ @plug.register_syntax_checker @cls, @mimetypes, @patterns
457
+ @plug.instance_variable_get(:@syntax_checkers)[@cls].should == [@mimetypes, @patterns]
458
+ end
459
+
460
+ it 'emits the syntax_checker_added signal' do
461
+ mk = flexmock{|m| m.should_receive(:syntax_checker_added).once}
462
+ @plug.connect(SIGNAL(:syntax_checker_added)){mk.syntax_checker_added}
463
+ @plug.register_syntax_checker @cls, @mimetypes, @patterns
464
+ end
465
+
466
+ it 'raises ArgumentError if the given syntax checker has already been registered' do
467
+ cls = Class.new
468
+ mimetypes = %w[application/x-ruby]
469
+ patterns = %w[*.rb]
470
+ @plug.register_syntax_checker cls, mimetypes, patterns
471
+ lambda{@plug.register_syntax_checker cls, ['text/plain']}.should raise_error(ArgumentError, "#{cls} has already been registered as syntax checker")
472
+ end
473
+
474
+ end
475
+
476
+ describe '#syntax_checker_for' do
477
+
478
+ context 'if a syntax checker for which the document\'s file_type_match? method returns true exists' do
479
+
480
+ it 'returns the syntax checker class' do
481
+ doc = Ruber[:world].new_document
482
+ checkers = Array.new(2){Class.new}
483
+ @plug.register_syntax_checker checkers[0], ['text/x-python']
484
+ @plug.register_syntax_checker checkers[1], ['application/x-ruby']
485
+ flexmock(doc).should_receive(:file_type_match?).with(['text/x-python'], []).and_return false
486
+ flexmock(doc).should_receive(:file_type_match?).with(['application/x-ruby'], []).and_return true
487
+ checker = @plug.syntax_checker_for doc
488
+ checker.should == checkers[1]
489
+ end
490
+
491
+ it 'returns the first syntax checker with matches if there are more than one of them' do
492
+ doc = Ruber[:world].new_document
493
+ checkers = Array.new(2){Class.new}
494
+ @plug.register_syntax_checker checkers[0], ['text/x-python']
495
+ @plug.register_syntax_checker checkers[1], ['application/x-ruby']
496
+ flexmock(doc).should_receive(:file_type_match?).with(['text/x-python'], []).and_return true
497
+ flexmock(doc).should_receive(:file_type_match?).with(['application/x-ruby'], []).and_return true
498
+ checker = @plug.syntax_checker_for doc
499
+ checker.should == checkers[0]
500
+ end
501
+
502
+ end
503
+
504
+ context 'if the document\'s file_type_match? method returns false for all syntax checkers' do
505
+
506
+ it 'returns nil' do
507
+ doc = Ruber[:world].new_document
508
+ checkers = Array.new(2){Class.new}
509
+ @plug.register_syntax_checker checkers[0], ['text/x-python']
510
+ @plug.register_syntax_checker checkers[1], ['application/x-ruby']
511
+ flexmock(doc).should_receive(:file_type_match?).with(['text/x-python'], []).and_return false
512
+ flexmock(doc).should_receive(:file_type_match?).with(['application/x-ruby'], []).and_return false
513
+ checker = @plug.syntax_checker_for doc
514
+ checker.should be_nil
515
+ end
516
+
517
+ end
518
+
519
+ end
520
+
521
+ describe '#remove_syntax_checker' do
522
+
523
+ before do
524
+ @cls = Class.new
525
+ @plug.register_syntax_checker @cls, ['application/x-ruby']
526
+ end
527
+
528
+ it 'removes the given syntax checker from the list' do
529
+ @plug.remove_syntax_checker @cls
530
+ @plug.instance_variable_get(:@syntax_checkers).should_not include(@cls)
531
+ end
532
+
533
+ it 'emits the syntax_checker_removed signal' do
534
+ mk = flexmock{|m| m.should_receive(:syntax_checker_removed).once}
535
+ @plug.connect(SIGNAL(:syntax_checker_removed)){mk.syntax_checker_removed}
536
+ @plug.remove_syntax_checker @cls
537
+ end
538
+
539
+ it 'does nothing if the given syntax checker has not been registered' do
540
+ cls = Class.new
541
+ mk = flexmock{|m| m.should_receive(:syntax_checker_removed).never}
542
+ @plug.connect(SIGNAL(:syntax_checker_removed)){mk.syntax_checker_removed}
543
+ lambda{@plug.remove_syntax_checker cls}.should_not raise_error
544
+ end
545
+
546
+ end
547
+
548
+ describe '#load_settings ' do
549
+
550
+ it 'emits the :settings_changed signal' do
551
+ mk = flexmock{|m| m.should_receive(:settings_changed).once}
552
+ @plug.connect(SIGNAL(:settings_changed)){mk.settings_changed}
553
+ @plug.load_settings
554
+ end
555
+
556
+ end
557
+
558
+ context 'when the syntax checker extension complete a syntax check' do
559
+
560
+ before do
561
+ @doc = Ruber[:world].document __FILE__
562
+ @ext = @doc.extension(:syntax_checker)
563
+ @errors = [OpenStruct.new(:line => 2, :column => 5, :message => 'X', :formatted_message=> 'x')]
564
+ end
565
+
566
+ after do
567
+ @doc.close false
568
+ end
569
+
570
+ it 'updates the current state accordingly if the extension is associated with the active document' do
571
+ flexmock(@doc).should_receive(:active?).and_return true
572
+ flexmock(@ext).should_receive(:status).and_return(:incorrect)
573
+ flexmock(@ext).should_receive(:errors).and_return @errors
574
+ flexmock(@plug).should_receive(:set_current_status).once.with(:incorrect, @errors)
575
+ @ext.instance_eval{emit syntax_checked(@doc)}
576
+ end
577
+
578
+ it 'does nothing if the extension is not associated with the active document' do
579
+ flexmock(@doc).should_receive(:active?).and_return false
580
+ flexmock(@ext).should_receive(:status).and_return(:incorrect)
581
+ flexmock(@ext).should_receive(:errors).and_return @errors
582
+ flexmock(@plug).should_receive(:set_current_status).never
583
+ @ext.instance_eval{emit syntax_checked(@doc)}
584
+ end
585
+
586
+ end
587
+
588
+ end
589
+
590
+ describe Ruber::SyntaxChecker::Extension do
591
+
592
+ before do
593
+ Ruber[:world].close_all :documents, :discard
594
+ Ruber[:components].load_plugin 'plugins/syntax_checker/'
595
+ @plug = Ruber[:syntax_checker]
596
+ @file = Tempfile.new ['', '.rb']
597
+ @doc = Ruber[:world].document @file.path
598
+ @ext = @doc.own_project.extension(:syntax_checker)
599
+ end
600
+
601
+ after do
602
+ Ruber[:components].unload_plugin :syntax_checker
603
+ led = Ruber[:main_window].status_bar.find_children(KDE::Led)[0]
604
+ led.delete_later
605
+ Qt::Application.send_posted_events led, Qt::Event::DeferredDelete
606
+ @file.close
607
+ end
608
+
609
+ it 'inherits Qt::Object' do
610
+ Ruber::SyntaxChecker::Extension.ancestors.should include(Qt::Object)
611
+ end
612
+
613
+ it 'includes the Ruber::Extension module' do
614
+ Ruber::SyntaxChecker::Extension.ancestors.should include(Ruber::Extension)
615
+ end
616
+
617
+ context 'when created' do
618
+
619
+ it 'creates a new syntax checker as appropriate for the document' do
620
+ cls = Class.new{def initialize doc;end; def check_syntax *args;end}
621
+ flexmock(Ruber[:syntax_checker]).should_receive(:syntax_checker_for).with(@doc).once.and_return cls
622
+ ext = Ruber::SyntaxChecker::Extension.new @doc.own_project
623
+ ext.instance_variable_get(:@checker).should be_a(cls)
624
+ end
625
+
626
+ it 'doesn\'t attempt to create a syntax checker if there is none suitable for the document associated with the extension' do
627
+ doc = Ruber[:world].new_document
628
+ cls = Class.new{def check_syntax *args;end}
629
+ flexmock(Ruber[:syntax_checker]).should_receive(:syntax_checker_for).with(doc).once.and_return nil
630
+ flexmock(doc.own_project).should_receive(:[]).with(:syntax_checker, :auto_check).and_return true
631
+ ext = Ruber::SyntaxChecker::Extension.new doc.own_project
632
+ ext.instance_variable_get(:@checker).should be_nil
633
+ end
634
+
635
+ it 'reads the syntax_checker/time_interval option from the config' do
636
+ flexmock(Ruber[:config]).should_receive(:[]).with(:syntax_checker, :time_interval).once.and_return 10
637
+ ext = Ruber::SyntaxChecker::Extension.new @doc.own_project
638
+ end
639
+
640
+ it 'has the errors attribute set to nil' do
641
+ @ext.errors.should be_nil
642
+ end
643
+
644
+ it 'has the status attribute set to unknown' do
645
+ @ext.status.should == :unknown
646
+ end
647
+
648
+ context 'if the document is active' do
649
+
650
+ before do
651
+ flexmock(@doc).should_receive(:active?).and_return true
652
+ end
653
+
654
+ it 'creates a single shot timer' do
655
+ ext = Ruber::SyntaxChecker::Extension.new @doc.own_project
656
+ timer = ext.instance_variable_get :@timer
657
+ timer.should be_a Qt::Timer
658
+ timer.singleShot.should be_true
659
+ end
660
+
661
+ end
662
+
663
+ end
664
+
665
+ describe 'when the document\'s URL changes' do
666
+
667
+ before do
668
+ @cls = Class.new{def check_syntax *args;end}
669
+ flexmock(Ruber[:syntax_checker]).should_receive(:syntax_checker_for).by_default.and_return @cls
670
+ end
671
+
672
+ it 'creates a new syntax checker if needed' do
673
+ @doc.instance_eval{emit document_url_changed(self)}
674
+ @ext.instance_variable_get(:@checker).should be_a(@cls)
675
+ end
676
+
677
+ it 'removes the old syntax checker if no valid syntax checkers are found' do
678
+ @ext.instance_variable_set :@checker, @cls.new
679
+ flexmock(Ruber[:syntax_checker]).should_receive(:syntax_checker_for).once.and_return nil
680
+ @doc.instance_eval{emit document_url_changed(self)}
681
+ @ext.instance_variable_get(:@checker).should be_nil
682
+ end
683
+
684
+ end
685
+
686
+ context 'when the plugin emits the syntax_checker_added signal' do
687
+
688
+ context 'and the extension has no syntax checker' do
689
+
690
+ it 'creates a syntax checker if possible' do
691
+ cls = Class.new{def check_syntax *args;end}
692
+ flexmock(Ruber[:syntax_checker]).should_receive(:syntax_checker_for).with(@doc).once.and_return cls
693
+ @plug.instance_eval{emit syntax_checker_added}
694
+ @ext.instance_variable_get(:@checker).should be_a(cls)
695
+ end
696
+
697
+ end
698
+
699
+ context 'and the extension alredy has a syntax checker' do
700
+
701
+ it 'does nothing' do
702
+ @ext.instance_variable_set :@checker, Object.new
703
+ flexmock(Ruber[:syntax_checker]).should_receive(:syntax_checker_for).with(@doc).never
704
+ @plug.instance_eval{emit syntax_checker_added}
705
+ end
706
+
707
+ end
708
+
709
+ end
710
+
711
+ context 'when the plugin emits the syntax_checker_removed signal' do
712
+
713
+ it 'attempts to create a new syntax checker if possible' do
714
+ cls = Class.new{def check_syntax *args;end}
715
+ flexmock(Ruber[:syntax_checker]).should_receive(:syntax_checker_for).with(@doc).once.and_return cls
716
+ @plug.instance_eval{emit syntax_checker_removed}
717
+ @ext.instance_variable_get(:@checker).should be_a(cls)
718
+ end
719
+
720
+ end
721
+
722
+ context 'when the syntax checker is changed' do
723
+
724
+ context 'and the syntax_checker/auto_check document option is true' do
725
+
726
+ before do
727
+ @doc.own_project[:syntax_checker, :auto_check] = true
728
+ end
729
+
730
+ it 'performs a syntax check' do
731
+ cls = Class.new
732
+ flexmock(@plug).should_receive(:syntax_checker_for).and_return cls
733
+ mk = flexmock{|m| m.should_receive(:check_syntax).once}
734
+ flexmock(cls).should_receive(:new).once.and_return mk
735
+ @plug.instance_eval{emit syntax_checker_added}
736
+ end
737
+
738
+ end
739
+
740
+ context 'and the syntax_checker/auto_check document option is false' do
741
+
742
+ before do
743
+ @doc.own_project[:syntax_checker, :auto_check] = false
744
+ end
745
+
746
+ it 'performs a syntax check' do
747
+ cls = Class.new
748
+ flexmock(@plug).should_receive(:syntax_checker_for).and_return cls
749
+ mk = flexmock{|m| m.should_receive(:check_syntax).never}
750
+ flexmock(cls).should_receive(:new).once.and_return mk
751
+ @plug.instance_eval{emit syntax_checker_added}
752
+ end
753
+
754
+ end
755
+
756
+ end
757
+
758
+ describe '#check_syntax' do
759
+
760
+ context 'if there\'s a syntax checker' do
761
+
762
+ before do
763
+ @checker = flexmock{|mk| mk.should_receive(:check_syntax).by_default}
764
+ @ext.instance_variable_set :@checker, @checker
765
+ end
766
+
767
+ it 'calls the check_syntax method of the checker passing it the text of the document and the value of the :format key' do
768
+ @doc.text = 'xyz'
769
+ @checker.should_receive(:check_syntax).once.with 'xyz', true
770
+ @ext.check_syntax :format => true
771
+ end
772
+
773
+ context 'and the #check_syntax method of the syntax checker returns nil' do
774
+
775
+ it 'returns a hash with the :result entry set to :correct and the :errors entry set to nil' do
776
+ @checker.should_receive(:check_syntax).and_return nil
777
+ @ext.check_syntax.should == {:result => :correct, :errors => nil}
778
+ end
779
+
780
+ end
781
+
782
+ context 'and the #check_syntax method of the syntax checker returns an array' do
783
+
784
+ it 'returns a hash with the :result entry set to :incorrect and the :errors entry set to the array' do
785
+ errors = [OpenStruct.new(:line => 0, :column => 2)]
786
+ @checker.should_receive(:check_syntax).and_return errors
787
+ @ext.check_syntax.should == {:result => :incorrect, :errors => errors}
788
+ end
789
+
790
+ end
791
+
792
+ context 'and the #check_syntax method of the syntax checker raises Ruber::SyntaxChecker::SyntaxNotChecked' do
793
+
794
+ it 'returns a hash with the :result entry set to :unknown and the :errors entry set to nil' do
795
+ flexmock(@checker).should_receive(:check_syntax).and_raise(Ruber::SyntaxChecker::SyntaxNotChecked)
796
+ @ext.check_syntax.should == {:result => :unknown, :errors => nil}
797
+ end
798
+
799
+ end
800
+
801
+ end
802
+
803
+ context 'and there isn\'t a syntax checker' do
804
+
805
+ it 'doesn\'t attempt to call the check_syntax method of the syntax checker' do
806
+ lambda{@ext.check_syntax}.should_not raise_error
807
+ end
808
+
809
+ it 'returns a hash with the :result entry set to :unknown and the :errors entry set to nil' do
810
+ @ext.check_syntax.should == {:result => :unknown, :errors => nil}
811
+ end
812
+
813
+ end
814
+
815
+ context 'if the :format and :update options are true' do
816
+
817
+ before do
818
+ @checker = flexmock{|mk| mk.should_receive(:check_syntax).by_default}
819
+ @ext.instance_variable_set :@checker, @checker
820
+ end
821
+
822
+ it 'sets the :status entry to the same value of the :result entry in the returned hash' do
823
+ @checker.should_receive(:check_syntax).once.and_return nil
824
+ @checker.should_receive(:check_syntax).once.and_return []
825
+ @ext.check_syntax :format => true, :update => true
826
+ @ext.status.should == :correct
827
+ @ext.check_syntax :format => true, :update => true
828
+ @ext.status.should == :incorrect
829
+ @ext.instance_variable_set :@checker, nil
830
+ @ext.check_syntax :format => true, :update => true
831
+ @ext.status.should == :unknown
832
+ end
833
+
834
+ it 'sets the errors attribute to the value returned by the #check_syntax method of the checker' do
835
+ errors = [
836
+ OpenStruct.new(:line => 0, :column => 2),
837
+ OpenStruct.new(:line => 1, :column => 0),
838
+ ]
839
+ flexmock(@checker).should_receive(:check_syntax).once.and_return errors
840
+ flexmock(@checker).should_receive(:check_syntax).once.and_return nil
841
+ @ext.check_syntax
842
+ @ext.errors.should == errors
843
+ @ext.check_syntax
844
+ @ext.errors.should be_nil
845
+ end
846
+
847
+ it 'sets the errors attribute to nil if there\'s no syntax checker' do
848
+ @ext.instance_variable_set :@checker, nil
849
+ @ext.check_syntax :format => true, :update => true
850
+ @ext.errors.should be_nil
851
+ end
852
+
853
+ it 'emits the syntax_checked signal passing the document as argument' do
854
+ mk = flexmock{|m| m.should_receive(:syntax_checked).once.with(@doc)}
855
+ @ext.connect(SIGNAL('syntax_checked(QObject*)')){|doc| mk.syntax_checked doc}
856
+ @ext.check_syntax
857
+ end
858
+
859
+ context 'and the #check_syntax method of the checker returns non-nil' do
860
+
861
+ it 'adds a mark of a custom type for each found error to the document' do
862
+ pending "Use marks only when KTextEditor::MarkInterface actually works"
863
+ @doc.text = "abc\ncde\nfgh"
864
+ errors = [
865
+ Ruber::SyntaxChecker::SyntaxError.new(0,2),
866
+ Ruber::SyntaxChecker::SyntaxError.new(1,0)
867
+ ]
868
+ flexmock(@checker).should_receive(:check_syntax).and_return errors
869
+ @ext.check_syntax
870
+ iface = @doc.interface('mark_interface')
871
+ (iface.mark(0) & @ext.class::SyntaxErrorMark).should_not == 0
872
+ (iface.mark(1) & @ext.class::SyntaxErrorMark).should_not == 0
873
+ (iface.mark(2) & @ext.class::SyntaxErrorMark).should == 0
874
+ end
875
+
876
+ it 'doesn\'t add marks for errors without a line number' do
877
+ pending "Use marks only when KTextEditor::MarkInterface#marks actually works"
878
+ @doc.text = "abc\ncde\nfgh"
879
+ checker = Object.new
880
+ @ext.instance_variable_set :@checker, checker
881
+ errors = [
882
+ Ruber::SyntaxChecker::SyntaxError.new(0,2),
883
+ Ruber::SyntaxChecker::SyntaxError.new(nil, 2)
884
+ ]
885
+ flexmock(checker).should_receive(:check_syntax).and_return errors
886
+ @ext.check_syntax
887
+ iface = @doc.interface('mark_interface')
888
+ (iface.mark(0) & @ext.class::SyntaxErrorMark).should_not == 0
889
+ (iface.mark(1) & @ext.class::SyntaxErrorMark).should == 0
890
+ (iface.mark(2) & @ext.class::SyntaxErrorMark).should == 0
891
+ end
892
+
893
+ end
894
+
895
+ context 'and the #check_syntax method of the checker returns nil' do
896
+
897
+ it 'clears all the marks put by the extension' do
898
+ pending "Use marks only when KTextEditor::MarkInterface actually works"
899
+ @doc.text = "abc\ncde\nfgh"
900
+ iface = @doc.interface('mark_interface')
901
+ iface.add_mark 0, @ext.class::SyntaxErrorMark
902
+ iface.add_mark 2, @ext.class::SyntaxErrorMark
903
+ flexmock(@checker).should_receive(:check_syntax).and_return nil
904
+ iface.marks.should be_empty
905
+ end
906
+
907
+ end
908
+
909
+ end
910
+
911
+ shared_examples_for 'the syntax checker extension when it should not update after a syntax check' do
912
+
913
+ before do
914
+ @checker = Object.new
915
+ flexmock(@checker).should_receive(:check_syntax).by_default
916
+ @ext.instance_variable_set :@checker, @checker
917
+ end
918
+
919
+ it 'doesn\'t change the marks' do
920
+ pending "Use marks only when KTextEditor::MarkInterface actually works"
921
+ end
922
+
923
+ it 'doesn\'t emit the syntax_checked signal' do
924
+ mk = flexmock{|m| m.should_receive(:syntax_checked).never}
925
+ @ext.connect(SIGNAL('syntax_checked(QObject*)')){|doc| mk.syntax_checked doc}
926
+ @ext.check_syntax @options
927
+ end
928
+
929
+ it 'doesn\'t change the errors attribute' do
930
+ errors = [
931
+ OpenStruct.new(:line => 0, :column => 2),
932
+ OpenStruct.new(:column => 2)
933
+ ]
934
+ @ext.instance_variable_set :@errors, errors
935
+ flexmock(@checker).should_receive(:check_syntax).and_return nil
936
+ @ext.check_syntax @options
937
+ @ext.errors.should == errors
938
+ end
939
+
940
+ end
941
+
942
+ context 'when the :format option is false' do
943
+
944
+ before do
945
+ @options = {:format => false, :update => true}
946
+ end
947
+
948
+ it_behaves_like 'the syntax checker extension when it should not update after a syntax check'
949
+
950
+ end
951
+
952
+ context 'when the :update option is false' do
953
+
954
+ before do
955
+ @options = {:format => true, :update => false}
956
+ end
957
+
958
+ it_behaves_like 'the syntax checker extension when it should not update after a syntax check'
959
+
960
+ end
961
+
962
+ end
963
+
964
+ shared_examples_for 'the syntax checker extension doing an auto syntax check' do
965
+
966
+ context 'if the syntax_checker/check document option is true' do
967
+
968
+ before do
969
+ @doc.own_project[:syntax_checker, :auto_check] = true
970
+ end
971
+
972
+ it 'checks the document syntax passing no arguments to #check_syntax' do
973
+ flexmock(@ext).should_receive(:check_syntax).once.with_no_args
974
+ @proc.call
975
+ end
976
+
977
+ end
978
+
979
+ context 'if the syntax_checker/auto_check document option is false' do
980
+
981
+ before do
982
+ @doc.own_project[:syntax_checker, :auto_check] = false
983
+ end
984
+
985
+ it 'doesn\'t check the document syntax' do
986
+ flexmock(@ext).should_receive(:check_syntax).never
987
+ @proc.call
988
+ end
989
+
990
+ end
991
+
992
+ end
993
+
994
+ context 'when the document is saved' do
995
+
996
+ before do
997
+ @proc = Proc.new{@doc.instance_eval{emit document_saved_or_uploaded(self, false)}}
998
+ end
999
+
1000
+ it_behaves_like 'the syntax checker extension doing an auto syntax check'
1001
+
1002
+ end
1003
+
1004
+ context 'when the document becomes active' do
1005
+
1006
+ before do
1007
+ @proc = Proc.new{@doc.activate}
1008
+ end
1009
+
1010
+ it_behaves_like 'the syntax checker extension doing an auto syntax check'
1011
+
1012
+ it 'creates a single shot timer' do
1013
+ @doc.activate
1014
+ timer = @ext.instance_variable_get :@timer
1015
+ timer.should be_a(Qt::Timer)
1016
+ timer.singleShot.should be_true
1017
+ end
1018
+
1019
+ end
1020
+
1021
+ context 'when the document becomes inactive' do
1022
+
1023
+ before do
1024
+ @doc.activate
1025
+ end
1026
+
1027
+ it 'stops the timer' do
1028
+ flexmock(@ext.instance_variable_get(:@timer)).should_receive(:stop).once
1029
+ @doc.deactivate
1030
+ end
1031
+
1032
+ it 'calls the #delete_later method of the timer' do
1033
+ flexmock(@ext.instance_variable_get(:@timer)).should_receive(:delete_later).once
1034
+ @doc.deactivate
1035
+ end
1036
+
1037
+ it 'removes the timer' do
1038
+ @doc.deactivate
1039
+ @ext.instance_variable_get(:@timer).should be_nil
1040
+ end
1041
+
1042
+ end
1043
+
1044
+ context 'when the plugin emits the settings_changed signal' do
1045
+
1046
+ it 'reads the syntax_checker/time_interval option from the config' do
1047
+ flexmock(Ruber[:config]).should_receive(:[]).with(:syntax_checker, :time_interval).once.and_return 10
1048
+ @plug.instance_eval{emit settings_changed}
1049
+ end
1050
+
1051
+ end
1052
+
1053
+ context 'when the text in the document changes' do
1054
+
1055
+ context 'and the document is active' do
1056
+
1057
+ before do
1058
+ @doc.deactivate
1059
+ @doc.activate
1060
+ end
1061
+
1062
+ context 'and the time interval is greater than 0' do
1063
+
1064
+ it 'starts the timer using the given time interval multiplied by 1000' do
1065
+ @ext.instance_variable_set :@time_interval, 10
1066
+ timer = @ext.instance_variable_get :@timer
1067
+ flexmock(timer).should_receive(:start).with(10_000).once
1068
+ @doc.text = 'y'
1069
+ end
1070
+
1071
+ end
1072
+
1073
+ context 'and the time interval is 0' do
1074
+
1075
+ it 'doesn\'t start the timer' do
1076
+ @ext.instance_variable_set :@time_interval, 0
1077
+ timer = @ext.instance_variable_get :@timer
1078
+ flexmock(timer).should_receive(:start).never
1079
+ @doc.text = 'x'
1080
+ end
1081
+
1082
+
1083
+ end
1084
+
1085
+ end
1086
+
1087
+ context 'and the document is not active' do
1088
+
1089
+ before do
1090
+ @doc.deactivate
1091
+ end
1092
+
1093
+ it 'doesn\'t attempt to start the timer' do
1094
+ @ext.instance_variable_set :@time_interval, 10
1095
+ timer = @ext.instance_variable_get :@timer
1096
+ flexmock(timer).should_receive(:start).never
1097
+ @doc.text = 'x'
1098
+ end
1099
+
1100
+ end
1101
+
1102
+ end
1103
+
1104
+ context 'when the timer times out' do
1105
+
1106
+ it 'calls the auto_check method' do
1107
+ @doc.activate
1108
+ flexmock(@ext).should_receive(:auto_check).once
1109
+ @ext.instance_variable_get(:@timer).instance_eval{emit timeout}
1110
+ end
1111
+
1112
+ end
1113
+
1114
+ describe '#remove_from_project' do
1115
+
1116
+ it 'stops the timer' do
1117
+ @doc.activate
1118
+ flexmock(@ext.instance_variable_get(:@timer)).should_receive(:stop).once
1119
+ @ext.remove_from_project
1120
+ #needed becouse remove_from_project is called in the after block when
1121
+ #unloading the plugin. Without this, the method is called twice
1122
+ flexmock(@ext).should_receive(:remove_from_project)
1123
+ end
1124
+
1125
+ it 'doesn\'t stop the timer if it doesn\'t exist' do
1126
+ @ext.instance_variable_set(:@timer, nil)
1127
+ lambda{@ext.remove_from_project}.should_not raise_error
1128
+ end
1129
+
1130
+ end
1131
+
1132
+ end