ruber 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. data/COPYING +339 -0
  2. data/INSTALL +137 -0
  3. data/LICENSE +8 -0
  4. data/bin/ruber +65 -0
  5. data/data/share/apps/ruber/core_components.yaml +31 -0
  6. data/data/share/apps/ruber/ruberui.rc +109 -0
  7. data/data/share/icons/ruber.png +0 -0
  8. data/data/share/pixmaps/ruby.png +0 -0
  9. data/icons/ruber-16.png +0 -0
  10. data/icons/ruber-32.png +0 -0
  11. data/icons/ruber-48.png +0 -0
  12. data/icons/ruber-8.png +0 -0
  13. data/lib/ruber/application/application.rb +288 -0
  14. data/lib/ruber/application/plugin.yaml +11 -0
  15. data/lib/ruber/component_manager.rb +899 -0
  16. data/lib/ruber/config/config.rb +82 -0
  17. data/lib/ruber/config/plugin.yaml +3 -0
  18. data/lib/ruber/document_project.rb +209 -0
  19. data/lib/ruber/documents/document_list.rb +416 -0
  20. data/lib/ruber/documents/plugin.yaml +4 -0
  21. data/lib/ruber/editor/document.rb +506 -0
  22. data/lib/ruber/editor/editor_view.rb +167 -0
  23. data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
  24. data/lib/ruber/exception_widgets.rb +245 -0
  25. data/lib/ruber/external_program_plugin.rb +397 -0
  26. data/lib/ruber/filtered_output_widget.rb +342 -0
  27. data/lib/ruber/gui_states_handler.rb +231 -0
  28. data/lib/ruber/kde_config_option_backend.rb +167 -0
  29. data/lib/ruber/kde_sugar.rb +249 -0
  30. data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
  31. data/lib/ruber/main_window/main_window.rb +524 -0
  32. data/lib/ruber/main_window/main_window_actions.rb +537 -0
  33. data/lib/ruber/main_window/main_window_internal.rb +239 -0
  34. data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
  35. data/lib/ruber/main_window/output_color_widget.rb +35 -0
  36. data/lib/ruber/main_window/plugin.yaml +58 -0
  37. data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
  38. data/lib/ruber/main_window/status_bar.rb +156 -0
  39. data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
  40. data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
  41. data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
  42. data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
  43. data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
  44. data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
  45. data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
  46. data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
  47. data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
  48. data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
  49. data/lib/ruber/main_window/workspace.rb +442 -0
  50. data/lib/ruber/output_widget.rb +1093 -0
  51. data/lib/ruber/plugin.rb +264 -0
  52. data/lib/ruber/plugin_like.rb +589 -0
  53. data/lib/ruber/plugin_specification.rb +106 -0
  54. data/lib/ruber/plugin_specification_reader.rb +451 -0
  55. data/lib/ruber/project.rb +493 -0
  56. data/lib/ruber/project_backend.rb +105 -0
  57. data/lib/ruber/projects/plugin.yaml +11 -0
  58. data/lib/ruber/projects/project_files_list.rb +314 -0
  59. data/lib/ruber/projects/project_files_widget.rb +301 -0
  60. data/lib/ruber/projects/project_list.rb +314 -0
  61. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
  62. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
  63. data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
  64. data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
  65. data/lib/ruber/qt_sugar.rb +673 -0
  66. data/lib/ruber/settings_container.rb +515 -0
  67. data/lib/ruber/settings_dialog.rb +244 -0
  68. data/lib/ruber/settings_dialog_manager.rb +503 -0
  69. data/lib/ruber/utils.rb +414 -0
  70. data/lib/ruber/yaml_option_backend.rb +159 -0
  71. data/outsider_files +15 -0
  72. data/plugins/autosave/autosave.rb +404 -0
  73. data/plugins/autosave/plugin.yaml +16 -0
  74. data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
  75. data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
  76. data/plugins/command/command.png +0 -0
  77. data/plugins/command/command.rb +74 -0
  78. data/plugins/command/plugin.yaml +11 -0
  79. data/plugins/find_in_files/find_in_files.rb +337 -0
  80. data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
  81. data/plugins/find_in_files/find_in_files_ui.rc +11 -0
  82. data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
  83. data/plugins/find_in_files/plugin.yaml +23 -0
  84. data/plugins/find_in_files/ui/config_widget.rb +58 -0
  85. data/plugins/find_in_files/ui/config_widget.ui +41 -0
  86. data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
  87. data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
  88. data/plugins/project_browser/plugin.yaml +10 -0
  89. data/plugins/project_browser/project_browser.rb +245 -0
  90. data/plugins/rake/plugin.yaml +39 -0
  91. data/plugins/rake/rake.png +0 -0
  92. data/plugins/rake/rake.rb +567 -0
  93. data/plugins/rake/rake_extension.rb +153 -0
  94. data/plugins/rake/rake_widgets.rb +615 -0
  95. data/plugins/rake/rakeui.rc +27 -0
  96. data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
  97. data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
  98. data/plugins/rake/ui/choose_task_widget.rb +77 -0
  99. data/plugins/rake/ui/choose_task_widget.ui +72 -0
  100. data/plugins/rake/ui/config_widget.rb +127 -0
  101. data/plugins/rake/ui/config_widget.ui +123 -0
  102. data/plugins/rake/ui/project_widget.rb +217 -0
  103. data/plugins/rake/ui/project_widget.ui +246 -0
  104. data/plugins/rspec/plugin.yaml +30 -0
  105. data/plugins/rspec/rspec.png +0 -0
  106. data/plugins/rspec/rspec.rb +945 -0
  107. data/plugins/rspec/rspec.svg +90 -0
  108. data/plugins/rspec/rspecui.rc +20 -0
  109. data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
  110. data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
  111. data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
  112. data/plugins/ruby_development/plugin.yaml +27 -0
  113. data/plugins/ruby_development/ruby_development.png +0 -0
  114. data/plugins/ruby_development/ruby_development.rb +453 -0
  115. data/plugins/ruby_development/ruby_developmentui.rc +19 -0
  116. data/plugins/ruby_development/ui/project_widget.rb +112 -0
  117. data/plugins/ruby_development/ui/project_widget.ui +108 -0
  118. data/plugins/ruby_runner/config_widget.rb +116 -0
  119. data/plugins/ruby_runner/plugin.yaml +26 -0
  120. data/plugins/ruby_runner/project_widget.rb +62 -0
  121. data/plugins/ruby_runner/ruby.png +0 -0
  122. data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
  123. data/plugins/ruby_runner/ruby_runner.rb +411 -0
  124. data/plugins/ruby_runner/ui/config_widget.rb +92 -0
  125. data/plugins/ruby_runner/ui/config_widget.ui +91 -0
  126. data/plugins/ruby_runner/ui/project_widget.rb +60 -0
  127. data/plugins/ruby_runner/ui/project_widget.ui +48 -0
  128. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
  129. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
  130. data/plugins/state/plugin.yaml +28 -0
  131. data/plugins/state/state.rb +520 -0
  132. data/plugins/state/ui/config_widget.rb +92 -0
  133. data/plugins/state/ui/config_widget.ui +89 -0
  134. data/plugins/syntax_checker/plugin.yaml +18 -0
  135. data/plugins/syntax_checker/syntax_checker.rb +662 -0
  136. data/ruber.desktop +10 -0
  137. data/spec/annotation_model_spec.rb +174 -0
  138. data/spec/common.rb +119 -0
  139. data/spec/component_manager_spec.rb +1259 -0
  140. data/spec/document_list_spec.rb +626 -0
  141. data/spec/document_project_spec.rb +373 -0
  142. data/spec/document_spec.rb +779 -0
  143. data/spec/editor_view_spec.rb +167 -0
  144. data/spec/external_program_plugin_spec.rb +676 -0
  145. data/spec/filtered_output_widget_spec.rb +642 -0
  146. data/spec/gui_states_handler_spec.rb +304 -0
  147. data/spec/kde_config_option_backend_spec.rb +214 -0
  148. data/spec/kde_sugar_spec.rb +101 -0
  149. data/spec/ktexteditor_wrapper_spec.rb +305 -0
  150. data/spec/output_widget_spec.rb +1703 -0
  151. data/spec/plugin_spec.rb +1393 -0
  152. data/spec/plugin_specification_reader_spec.rb +1765 -0
  153. data/spec/plugin_specification_spec.rb +401 -0
  154. data/spec/project_backend_spec.rb +172 -0
  155. data/spec/project_files_list_spec.rb +401 -0
  156. data/spec/project_list_spec.rb +511 -0
  157. data/spec/project_spec.rb +990 -0
  158. data/spec/qt_sugar_spec.rb +328 -0
  159. data/spec/settings_container_spec.rb +617 -0
  160. data/spec/settings_dialog_manager_spec.rb +773 -0
  161. data/spec/settings_dialog_spec.rb +419 -0
  162. data/spec/state_spec.rb +991 -0
  163. data/spec/utils_spec.rb +406 -0
  164. data/spec/workspace_spec.rb +869 -0
  165. data/spec/yaml_option_backend_spec.rb +246 -0
  166. metadata +284 -0
@@ -0,0 +1,10 @@
1
+ [Desktop Entry]
2
+ Name=Ruber
3
+ Comment=An IDE for the Ruby programming language written in Ruby
4
+ Comment[it]=Un IDE per il linguaggio Ruby scritto in Ruby
5
+ TryExec=ruber
6
+ Exec=ruber %u
7
+ Type=Application
8
+ Categories=Development;IDE;Qt;KDE;
9
+ Icon=ruber
10
+ MimeType=application/x-ruby
@@ -0,0 +1,174 @@
1
+ require 'spec/common'
2
+
3
+ require 'ruber/editor/document'
4
+
5
+ describe Ruber::AnnotationModel do
6
+
7
+ before do
8
+ @components = flexmock{|m| m.should_ignore_missing}
9
+ @w = Qt::Widget.new
10
+ flexmock(Ruber).should_receive(:[]).with(:components).and_return(@components).by_default
11
+ @doc = Ruber::Document.new @app
12
+ @model = Ruber::AnnotationModel.new @doc
13
+ end
14
+
15
+ it 'should inherit KTextEditor::AnnotationModel' do
16
+ @model.should be_kind_of( KTextEditor::AnnotationModel )
17
+ end
18
+
19
+ it 'should include the Enumerable module' do
20
+ Ruber::AnnotationModel.ancestors.include?(Enumerable).should be_true
21
+ end
22
+
23
+ it 'should allow to add annotation types at class level' do
24
+ b = Qt::Brush.new(Qt.yellow)
25
+ @model.class.register_annotation_type :test, b
26
+ h = @model.class.instance_variable_get(:@annotation_types)
27
+ h.should have_key(:test)
28
+ h[:test][0].value.should == b
29
+ h[:test][1].should be_null
30
+ end
31
+
32
+ it 'should raise ArgumentError when registering an already registered annotation type' do
33
+ @model.class.register_annotation_type :test
34
+ lambda{@model.class.register_annotation_type( :test)}.should raise_error( ArgumentError )
35
+ end
36
+
37
+ it 'should allow to add an annotation for an existing line using three parameters' do
38
+ @model.class.register_annotation_type :test
39
+ @doc.text = "abc\ndef\nghi"
40
+ lambda{ @model.add_annotation :test, 1, "message", "tool tip"}.should_not raise_error
41
+ @model.data(1, Qt::DisplayRole).to_string.should == 'message'
42
+ @model.data(1, Qt::ToolTipRole).to_string.should == 'tool tip'
43
+ end
44
+
45
+ it 'should allow to add an annotation for an existing line using one parameter' do
46
+ @model.class.register_annotation_type :test
47
+ @doc.text = "abc\ndef\nghi"
48
+ lambda{ @model.add_annotation Ruber::AnnotationModel::Annotation.new(:test, 1, "message", "tool tip")}.should_not raise_error
49
+ @model.data(1, Qt::DisplayRole).to_string.should == 'message'
50
+ @model.data(1, Qt::ToolTipRole).to_string.should == 'tool tip'
51
+ end
52
+
53
+ it 'should raise IndexError when an annotation is added to a nonexisting line' do
54
+ @model.class.register_annotation_type :test
55
+ @doc.text = "abc\ndef\nghi"
56
+ pending "There's something weird going on here"
57
+ lambda{ @model.add_annotation :test, 5, "message", "tool tip"}.should raise_error(IndexError)
58
+ lambda{ @model.add_annotation Ruber::AnnotationModel::Annotation.new(:test, 3, "message", "tool tip")}.should raise_error(IndexError)
59
+ end
60
+
61
+ it 'should allow to retreive the annotation for a given line' do
62
+ @model.class.register_annotation_type :test
63
+ @doc.text = "abc\ndef\ghi"
64
+ @model.add_annotation :test, 1, "message", "tool tip"
65
+ @model[1].should == Ruber::AnnotationModel::Annotation.new(:test, 1, "message", "tool tip")
66
+ @model.annotation(1).should == Ruber::AnnotationModel::Annotation.new(:test, 1, "message", "tool tip")
67
+ end
68
+
69
+ it 'should tell whether there\'s an annotation for a given line' do
70
+ @model.class.register_annotation_type :test
71
+ @doc.text = "abc\ndef\nghi"
72
+ @model.add_annotation :test, 1, "message", "tool tip"
73
+ @model.should have_annotation(1)
74
+ @model.should_not have_annotation(2)
75
+ end
76
+
77
+ it 'should tell whether there are annotations or not' do
78
+ @model.should_not have_annotations
79
+ @model.should be_empty
80
+ @model.class.register_annotation_type :test
81
+ @doc.text = "abc\ndef\ghi"
82
+ @model.add_annotation :test, 1, "message", "tool tip"
83
+ @model.should have_annotations
84
+ @model.should_not be_empty
85
+ end
86
+
87
+ it 'should allow to remove all annotations' do
88
+ @model.class.register_annotation_type :test
89
+ @doc.text = "abc\ndef\nghi"
90
+ @model.add_annotation :test, 1, "message", "tool tip"
91
+ @model.add_annotation :test, 2, "message", "tool tip"
92
+ @model.clear
93
+ @model.should be_empty
94
+ end
95
+
96
+ it 'should allow to remove the annotations for one line' do
97
+ @model.class.register_annotation_type :test
98
+ @doc.text = "abc\ndef\nghi"
99
+ @model.add_annotation :test, 1, "message", "tool tip"
100
+ @model.add_annotation :test, 2, "message", "tool tip"
101
+ @model.remove_annotation 1
102
+ @model.should_not be_empty
103
+ @model.should_not have_annotation(1)
104
+ end
105
+
106
+ it 'should allow to iterate on all annotations in order' do
107
+ @model.class.register_annotation_type :test
108
+ @doc.text = "abc\ndef\nghi"
109
+ 3.times{|i| @model.add_annotation :test, (i+1)%3, ((i+1)%3).to_s, "tool tip"}
110
+ m = flexmock('mock')
111
+ m.should_receive(:test).once.globally.ordered.with("0")
112
+ m.should_receive(:test).once.globally.ordered.with("1")
113
+ m.should_receive(:test).once.globally.ordered.with("2")
114
+ @model.each{|a| m.test(a.msg)}
115
+ end
116
+
117
+ it 'should emit the "annotations_changed()" signal when an annotation is added or removed or the model is cleared' do
118
+ @model.class.register_annotation_type :test
119
+ @doc.text = "abc\ndef\nghi"
120
+ m = flexmock
121
+ m.should_receive(:annotations_changed).times(3)
122
+ @model.connect(SIGNAL('annotations_changed()')){m.annotations_changed}
123
+ @model.add_annotation :test, 1, "message", "tool tip"
124
+ @model.remove_annotation 1
125
+ @model.remove_annotation 0
126
+ @model.block_signals(true)
127
+ @model.add_annotation :test, 2, "message", "tool tip"
128
+ @model.add_annotation :test, 1, "message", "tool tip"
129
+ @model.block_signals(false)
130
+ @model.clear
131
+ end
132
+
133
+ it 'should emit the "annotations_changed(int)" signal when an annotation is added or removed' do
134
+ @model.class.register_annotation_type :test
135
+ @doc.text = "abc\ndef\nghi"
136
+ m = flexmock
137
+ m.should_receive(:annotation_changed).once.globally.ordered.with(1)
138
+ m.should_receive(:annotation_changed).once.globally.ordered.with(1)
139
+ @model.connect(SIGNAL('annotation_changed(int)')){|i| m.annotation_changed(i)}
140
+ @model.add_annotation :test, 1, "message", "tool tip"
141
+ @model.remove_annotation 1
142
+ @model.remove_annotation 0
143
+ end
144
+
145
+ it 'should emit the "lineChanged(int)" signal when an annotation is added or removed' do
146
+ @model.class.register_annotation_type :test
147
+ @doc.text = "abc\ndef\nghi"
148
+ m = flexmock
149
+ m.should_receive(:lineChanged).once.globally.ordered.with(1)
150
+ m.should_receive(:lineChanged).once.globally.ordered.with(1)
151
+ @model.connect(SIGNAL('lineChanged(int)')){|i| m.lineChanged(i)}
152
+ @model.add_annotation :test, 1, "message", "tool tip"
153
+ @model.remove_annotation 1
154
+ @model.remove_annotation 0
155
+ end
156
+
157
+ it 'should emit the "reset()" signal when the model is cleared' do
158
+ @model.class.register_annotation_type :test
159
+ @doc.text = "abc\ndef\nghi"
160
+ m = flexmock
161
+ m.should_receive(:reset).once
162
+ @model.connect(SIGNAL('reset()')){m.reset}
163
+ @model.add_annotation :test, 1, "message", "tool tip"
164
+ @model.clear
165
+ end
166
+
167
+ after do
168
+ @model.class.instance_variable_get(:@annotation_types).clear
169
+ @doc.instance_variable_get(:@doc).closeUrl false
170
+ @doc.dispose
171
+ # @app.quit
172
+ end
173
+
174
+ end
@@ -0,0 +1,119 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+ require 'flexmock/argument_types'
4
+ require 'korundum4'
5
+ require 'kio'
6
+ require 'ostruct'
7
+ require 'facets/kernel/require_relative'
8
+
9
+ module QtEnumerable
10
+
11
+ include Enumerable
12
+
13
+ begin
14
+ alias :count_items :count
15
+ undef_method :count
16
+ rescue NoMethodError
17
+ end
18
+
19
+ end
20
+
21
+ require 'ruber/qt_sugar'
22
+ require 'ruber/kde_sugar'
23
+ require 'ruber/utils'
24
+
25
+ #Distinguish between rspec 1.* and 2.*
26
+ RSPEC = begin require 'rspec/core'
27
+ RSpec
28
+ rescue LoadError
29
+ Spec
30
+ end
31
+ (RSPEC.name == 'RSpec' ? RSPEC : RSPEC::Runner).configure do |config|
32
+ config.mock_with :flexmock
33
+ end
34
+
35
+ OS = OpenStruct
36
+
37
+ =begin rdoc
38
+ [
39
+ [dir1, [dir2, [fi1e1, file2, [dir3, file3] ]],
40
+ [dir4, file4]
41
+ ]
42
+ produces
43
+ dir1
44
+ dir2
45
+ dir3
46
+ file3
47
+ file1
48
+ file2
49
+ dir4
50
+ file4
51
+ =end
52
+ def make_dir_tree contents, base = '/tmp/', file_contents = {}
53
+ letters = ('A'..'Z').to_a + ('a'..'z').to_a
54
+ temp_dir = File.join base, 10.times.map{letters[rand(52)]}.join
55
+ dirs = []
56
+ files = []
57
+ contents = YAML.load(contents) if contents.is_a? String
58
+ mktree = lambda do |dir, current|
59
+ if current.is_a? Array
60
+ current_full = File.join(dir, current[0])
61
+ dirs << current_full
62
+ current[1..-1].each do |c|
63
+ mktree.call current_full, c
64
+ end
65
+ else files << File.join(dir, current)
66
+ end
67
+ end
68
+ contents.each {|d| mktree.call temp_dir, d}
69
+ FileUtils.mkdir temp_dir
70
+ file_contents = file_contents.map{|k, v| [File.join( temp_dir, k), v]}.to_h
71
+ dirs.each{|d| FileUtils.mkdir d}
72
+ files.each do |f|
73
+ if file_contents[f] then File.open(f, 'w'){|out| out.write file_contents[f]}
74
+ else `touch #{f}`
75
+ end
76
+ end
77
+ temp_dir
78
+ end
79
+
80
+ =begin rdoc
81
+ Returns a random string of length _length_ made of characters from 'a' to 'z'
82
+ =end
83
+ def random_string length = 5
84
+ ('a'..'z').to_a.sample(length).join
85
+ end
86
+
87
+ if RUBY_VERSION =~ /9/
88
+ RSPEC::Matchers.define :include do |it|
89
+ match do |cont|
90
+ cont.include? it
91
+ end
92
+ end
93
+ end
94
+
95
+ RSPEC::Matchers.define :have_entries do |hash|
96
+
97
+ match do |obj|
98
+ if obj.respond_to? :[]
99
+ hash.each_pair do |k, v|
100
+ return false unless obj[k] == v
101
+ end
102
+ else
103
+ hash.each_pair do |k, v|
104
+ return false unless obj.send(k) == v
105
+ end
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ RSPEC::Matchers.define :be_the_same_as do |exp|
112
+ match{|obj| obj.equal? exp}
113
+ end
114
+
115
+ data = KDE::AboutData.new "test", "", KDE::ki18n("test"), "0.0.0"
116
+ KDE::CmdLineArgs.init [], data
117
+ KDE::Application.new
118
+ at_exit{Qt::Internal.application_terminated = true}
119
+ GC.disable
@@ -0,0 +1,1259 @@
1
+ require 'spec/common'
2
+
3
+ require 'ruber/component_manager'
4
+ require 'ruber/plugin'
5
+ require 'ruber/plugin_specification'
6
+ require 'ruber/config/config'
7
+
8
+ include FlexMock::ArgumentTypes
9
+
10
+ describe 'Ruber::ComponentManager#w, when created' do
11
+
12
+ before do
13
+ @manager = Ruber::ComponentManager.new
14
+ end
15
+
16
+ it 'should add itself to the list of features' do
17
+ @manager.instance_variable_get(:@features).should == {:components => @manager}
18
+ end
19
+
20
+ it 'should add itself to the "components" hash, under the :components key' do
21
+ @manager[:components].should equal(@manager)
22
+ end
23
+
24
+ it 'should have a plugin_description method which returns the plugin description' do
25
+ res = @manager.plugin_description
26
+ res.should be_a(Ruber::PluginSpecification)
27
+ res.name.should == :components
28
+ res.features.should == [:components]
29
+ res.class_obj.should == Ruber::ComponentManager
30
+ end
31
+
32
+ it 'should have a plugin_name and component_name methods which return :components' do
33
+ @manager.plugin_name.should == :components
34
+ @manager.component_name.should == :components
35
+ end
36
+
37
+ end
38
+
39
+ describe 'Ruber::ComponentManager#[]' do
40
+
41
+ it 'should return the entry stored in the @features instance variable with the same name as the argument' do
42
+ man = Ruber::ComponentManager.new
43
+ mk = flexmock('test component')
44
+ man.instance_variable_get(:@components)[:test] = mk
45
+ features = man.instance_variable_get(:@features)
46
+ features[:x] = man
47
+ features[:test] = mk
48
+ features[:y] = mk
49
+ man[:components].should equal(man)
50
+ man[:x].should equal(man)
51
+ man[:test].should equal(mk)
52
+ man[:y].should equal(mk)
53
+ end
54
+
55
+ end
56
+
57
+ describe 'Ruber::ComponentManager#each_component' do
58
+
59
+ it 'should call the block passing each component in turn, in reverse loading order' do
60
+ man = Ruber::ComponentManager.new
61
+ components = 5.times.map{|i| flexmock(i.to_s, :component_name => i.to_s, :plugin_description => OpenStruct.new({:features => []}))}
62
+ m = flexmock do |mk|
63
+ components.reverse_each{|c| mk.should_receive(:test).once.with(c).globally.ordered}
64
+ end
65
+ man.instance_variable_get(:@components).clear
66
+ components.each{|c| man.add c}
67
+ man.each_component{|i| m.test i}
68
+ end
69
+
70
+ end
71
+
72
+ describe 'Ruber::ComponentManager#components' do
73
+
74
+ it 'should call the block passing each component in turn, in reverse loading order' do
75
+ man = Ruber::ComponentManager.new
76
+ components = 5.times.map{|i| flexmock(i.to_s, :component_name => i.to_s, :plugin_description => OpenStruct.new({:features => []}))}
77
+ man.instance_variable_get(:@components).clear
78
+ components.each{|c| man.add c}
79
+ man.components.should == components
80
+ end
81
+
82
+ end
83
+
84
+ describe 'Ruber::ComponentManager#each_plugin' do
85
+
86
+ it 'should call the block passing each plugin in turn, in reverse loading order' do
87
+ man = Ruber::ComponentManager.new
88
+ components = 5.times.map{|i| flexmock("component #{i}", :component_name => :"#{i}",:plugin_description => OpenStruct.new({:features => []}))}
89
+ m = flexmock("test")
90
+ plugins = [1, 3, 4]
91
+ components.each_with_index do |c, i|
92
+ is_plugin = plugins.include?(i)
93
+ c.should_receive(:is_a?).with(Ruber::Plugin).and_return( is_plugin)
94
+ if is_plugin then m.should_receive(:test).with(c).once.globally.ordered
95
+ else m.should_receive(:test).with(c).never
96
+ end
97
+ end
98
+ components.reverse_each{|c| man.add c}
99
+ man.each_plugin{|i| m.test i}
100
+ end
101
+
102
+ end
103
+
104
+ describe 'Ruber::ComponentManager#plugins' do
105
+
106
+ it 'should return an array containing all the loading plugins, in loading order' do
107
+ man = Ruber::ComponentManager.new
108
+ components = 5.times.map{|i| flexmock("component #{i}", :component_name => :"#{i}",:plugin_description => OpenStruct.new({:features => []}))}
109
+ m = flexmock("test")
110
+ plugins = [1, 3, 4]
111
+ components.each_with_index do |c, i|
112
+ is_plugin = plugins.include?(i)
113
+ c.should_receive(:is_a?).with(Ruber::Plugin).and_return( is_plugin)
114
+ end
115
+ components.reverse_each{|c| man.add c}
116
+ man.plugins.should == [components[4], components[3], components[1]]
117
+ end
118
+
119
+ end
120
+
121
+ describe 'Ruber::ComponentManager#add' do
122
+
123
+ before do
124
+ @manager = Ruber::ComponentManager.new
125
+ end
126
+
127
+ it 'should add the argument at the end of the component list' do
128
+ plug = flexmock('plugin', :component_name => :test, :plugin_description => OpenStruct.new({:features => []}))
129
+ @manager.add plug
130
+ @manager.instance_variable_get(:@components)[:test].should equal(plug)
131
+ @manager.instance_variable_get(:@components).keys.last.should == :test
132
+ end
133
+
134
+ it 'should add the argument to the list of features for each of its features' do
135
+ pdf = OpenStruct.new({:features => [:test, :x, :y]})
136
+ plug = flexmock('plugin', :component_name => :test, :plugin_description => pdf)
137
+ @manager.add plug
138
+ @manager.instance_variable_get(:@features).should == {:components => @manager, :test => plug, :x => plug, :y => plug}
139
+ end
140
+
141
+ end
142
+
143
+ describe 'Ruber::ComponentManager.load_component' do
144
+
145
+ before(:all) do
146
+ cls = Class.new(Qt::Object) do
147
+ attr_accessor :plugin_description
148
+ def initialize manager, pdf
149
+ super()
150
+ end
151
+
152
+ def setup;end
153
+ end
154
+ Object.const_set(:TestComponent, cls)
155
+ end
156
+
157
+ before do
158
+ @manager = Ruber::ComponentManager.new
159
+ end
160
+
161
+ after(:all) do
162
+ Object.send :remove_const, :TestComponent
163
+ end
164
+
165
+ it 'should add the component directory to KDE::StandardDirs if an instance of the application exists' do
166
+ yaml = <<-EOS
167
+ name: test
168
+ class: TestComponent
169
+ EOS
170
+ dir = File.expand_path('lib/ruber/main_window')
171
+ flexmock(KDE::Application).should_receive(:instance).and_return Qt::Object.new
172
+ #needed because resource_dirs return directory names ending in /, while
173
+ #File.expand_path doesn't
174
+ dir = File.join dir, ''
175
+ file = File.join dir, 'plugin.yaml'
176
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
177
+ @manager.load_component 'main_window'
178
+ KDE::Global.dirs.resource_dirs('pixmap').should include(dir)
179
+ KDE::Global.dirs.resource_dirs('data').should include(dir)
180
+ KDE::Global.dirs.resource_dirs('appdata').should include(dir)
181
+ end
182
+
183
+ it 'should not add the component directory to KDE::StandardDirs if no instance of the application exist' do
184
+ yaml = <<-EOS
185
+ name: test
186
+ class: TestComponent
187
+ EOS
188
+ dir = File.expand_path('lib/ruber/projects')
189
+ flexmock(KDE::Application).should_receive(:instance).and_return nil
190
+ #needed because resource_dirs return directory names ending in /, while
191
+ #File.expand_path doesn't
192
+ dir = File.join dir, ''
193
+ file = File.join dir, 'plugin.yaml'
194
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
195
+ @manager.load_component 'projects'
196
+ KDE::Global.dirs.resource_dirs('pixmap').should_not include(dir)
197
+ KDE::Global.dirs.resource_dirs('data').should_not include(dir)
198
+ KDE::Global.dirs.resource_dirs('appdata').should_not include(dir)
199
+ end
200
+
201
+
202
+ it 'should read the full PDF from the plugin.yaml file contained in the directory with the same name as the component, in the same directory as the component_manager.rb file' do
203
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
204
+ yaml = <<-EOS
205
+ name: test
206
+ class: TestComponent
207
+ EOS
208
+ pdf = Ruber::PluginSpecification.full YAML.load(yaml)
209
+ flexmock(Ruber::PluginSpecification).should_receive(:full).once.with(file).and_return pdf
210
+ @manager.load_component 'test'
211
+ end
212
+
213
+ it 'should raise SystemCallError if the file couldn\'t be found' do
214
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
215
+ lambda{@manager.load_component 'test'}.should raise_error(SystemCallError)
216
+ end
217
+
218
+ it 'should raise ArgumentError if the file isn\'t a valid YAML file' do
219
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
220
+ yaml = <<-EOS
221
+ name: {
222
+ EOS
223
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
224
+ lambda{@manager.load_component 'test'}.should raise_error(ArgumentError)
225
+ end
226
+
227
+ it 'should raise Ruber::PluginSpecification::PSFError if the file isn\'t a valid PDF, then re-raise the exception' do
228
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
229
+ yaml = '{}'
230
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
231
+ lambda{@manager.load_component 'test'}.should raise_error(Ruber::PluginSpecification::PSFError)
232
+ end
233
+
234
+ it 'should store the component directory in the "directory" attribute of the PDF' do
235
+ dir = File.expand_path 'lib/ruber/test'
236
+ file = File.join dir, 'plugin.yaml'
237
+ yaml = <<-EOS
238
+ name: test
239
+ class: TestComponent
240
+ EOS
241
+ flexmock(File).should_receive(:read).with(file).and_return yaml
242
+ pdf = Ruber::PluginSpecification.full(YAML.load(yaml))
243
+ flexmock(Ruber::PluginSpecification).should_receive(:full).with(file).once.and_return pdf
244
+ comp = TestComponent.new(@manager, pdf)
245
+ flexmock(TestComponent).should_receive(:new).once.with(@manager, pdf).and_return(comp)
246
+ @manager.load_component 'test'
247
+ end
248
+
249
+ it 'should create an instance of the class mentioned in the PDF, passing the component manager and the pdf as argument' do
250
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
251
+ yaml = <<-EOS
252
+ name: test
253
+ class: TestComponent
254
+ EOS
255
+ flexmock(File).should_receive(:read).with(file).and_return yaml
256
+ pdf = Ruber::PluginSpecification.full(YAML.load(yaml))
257
+ flexmock(Ruber::PluginSpecification).should_receive(:full).and_return pdf
258
+ comp = TestComponent.new(@manager, pdf)
259
+ flexmock(TestComponent).should_receive(:new).once.with(@manager, pdf).and_return(comp)
260
+ @manager.load_component 'test'
261
+ end
262
+
263
+ it 'should not call the load_settings method of the component if no components with name :config exist' do
264
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
265
+ yaml = <<-EOS
266
+ name: test
267
+ class: TestComponent
268
+ EOS
269
+ flexmock(File).should_receive(:read).with(file).and_return yaml
270
+ comp = TestComponent.new @manager, Ruber::PluginSpecification.full(YAML.load(yaml))
271
+ flexmock(TestComponent).should_receive(:new).once.and_return(comp)
272
+ flexmock(comp).should_receive(:send).with(:setup)
273
+ flexmock(comp).should_receive(:send).with(:load_settings).never
274
+ @manager.load_component 'test'
275
+ end
276
+
277
+ it 'should emit the "component_loaded(QObject*)" signal as the last thing' do
278
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
279
+ yaml = <<-EOS
280
+ name: test
281
+ class: TestComponent
282
+ EOS
283
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
284
+ @manager.instance_variable_get(:@components)[:config] = TestComponent.new @manager, OpenStruct.new
285
+ comp = TestComponent.new @manager, Ruber::PluginSpecification.full(YAML.load(yaml))
286
+ def comp.load_settings;end
287
+ flexmock(TestComponent).should_receive(:new).once.globally.ordered.with(@manager, Ruber::PluginSpecification).and_return(comp)
288
+ m = flexmock{|mk| mk.should_receive(:component_added).with(comp).once.globally.ordered}
289
+ @manager.connect(SIGNAL('component_loaded(QObject*)')){|c| m.component_added(c)}
290
+ @manager.load_component 'test'
291
+ end
292
+
293
+ it 'should return the component' do
294
+ file = File.expand_path 'lib/ruber/test/plugin.yaml'
295
+ yaml = <<-EOS
296
+ name: test
297
+ class: TestComponent
298
+ EOS
299
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
300
+ comp = TestComponent.new @manager, Ruber::PluginSpecification.full(YAML.load(yaml))
301
+ flexmock(TestComponent).should_receive(:new).once.with(@manager, Ruber::PluginSpecification).and_return(comp)
302
+ @manager.load_component( 'test').should equal(comp)
303
+
304
+ end
305
+
306
+ end
307
+
308
+ describe 'Ruber::ComponentManager#load_plugin' do
309
+
310
+ before(:all) do
311
+ cls = Class.new(Qt::Object) do
312
+ attr_accessor :plugin_description
313
+ def initialize pdf
314
+ super()
315
+ @plugin_description = pdf
316
+ end
317
+
318
+ define_method(:setup){}
319
+ define_method(:load_settings){}
320
+ define_method(:delayed_initialize){}
321
+ end
322
+ Object.const_set(:TestPlugin, cls)
323
+ end
324
+
325
+ before do
326
+ @manager = Ruber::ComponentManager.new
327
+ @dir = File.expand_path "~/test"
328
+ @file = File.join @dir, 'plugin.yaml'
329
+ @yaml = <<-EOS
330
+ name: test
331
+ class: TestPlugin
332
+ EOS
333
+ @pdf = Ruber::PluginSpecification.full YAML.load(@yaml)
334
+ flexmock(File).should_receive(:read).with(@file).and_return(@yaml).by_default
335
+ end
336
+
337
+ after(:all) do
338
+ Object.send :remove_const, :TestPlugin
339
+ end
340
+
341
+ it 'should add the plugin directory to KDE::StandardDirs' do
342
+ @dir = File.expand_path(File.dirname(__FILE__))
343
+ #needed because resource_dirs return directory names ending in /, while
344
+ #File.expand_path doesn't
345
+ @dir = File.join @dir, ''
346
+ @file = File.join @dir, 'plugin.yaml'
347
+ flexmock(File).should_receive(:read).with(@file).once.and_return @yaml
348
+ @manager.load_plugin @dir
349
+ KDE::Global.dirs.resource_dirs('pixmap').should include(@dir)
350
+ KDE::Global.dirs.resource_dirs('data').should include(@dir)
351
+ KDE::Global.dirs.resource_dirs('appdata').should include(@dir)
352
+ end
353
+
354
+ it 'should read the full PDF from the plugin.yaml file contained in the directory passed as argument' do
355
+ flexmock(File).should_receive(:read).with(@file).once.and_return @yaml
356
+ pdf = Ruber::PluginSpecification.full YAML.load(@yaml)
357
+ flexmock(Ruber::PluginSpecification).should_receive(:full).once.with(YAML.load(@yaml), @dir).and_return pdf
358
+ @manager.load_plugin @dir
359
+ end
360
+
361
+ it 'should raise SystemCallError if the plugin file doesn\'t exist' do
362
+ dir = File.expand_path "~/test"
363
+ file = File.join dir, 'plugin.yaml'
364
+ yaml = <<-EOS
365
+ name: test
366
+ class: TestPlugin
367
+ EOS
368
+ flexmock(File).should_receive(:read).once.with(@file).and_raise(SystemCallError.new(0))
369
+ lambda{@manager.load_plugin @dir}.should raise_error(SystemCallError)
370
+ end
371
+
372
+ it 'should raise ArgumentError if the plugin file isn\'t a valid YAML file' do
373
+ dir = File.expand_path "#{ENV['HOME']}/test"
374
+ yaml = <<-EOS
375
+ name: {
376
+ EOS
377
+ file = File.join dir, 'plugin.yaml'
378
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
379
+ lambda{@manager.load_plugin @dir}.should raise_error(ArgumentError)
380
+ end
381
+
382
+ it 'should raise Ruber::PluginSpecification::PSFError if the file isn\'t a valid PDF' do
383
+ dir = File.expand_path "#{ENV['HOME']}/test"
384
+ yaml = '{}'
385
+ file = File.join dir, 'plugin.yaml'
386
+ flexmock(File).should_receive(:read).with(file).once.and_return yaml
387
+ lambda{@manager.load_plugin @dir}.should raise_error(Ruber::PluginSpecification::PSFError)
388
+ end
389
+
390
+ it 'should create an instance of the class mentioned in the PDF, passing the PluginSpecification object as argument, and return it' do
391
+ pdf = Ruber::PluginSpecification.full(YAML.load(@yaml))
392
+ flexmock(Ruber::PluginSpecification).should_receive(:full).and_return pdf
393
+ plug = TestPlugin.new(pdf)
394
+ flexmock(TestPlugin).should_receive(:new).once.with(pdf).and_return(plug)
395
+ res = @manager.load_plugin @dir
396
+ res.should equal(plug)
397
+ end
398
+
399
+ it 'should emit the "component_loaded(QObject*)" signal, passing the plugin as argument' do
400
+ pdf = Ruber::PluginSpecification.full(YAML.load(@yaml))
401
+ flexmock(Ruber::PluginSpecification).should_receive(:full).and_return pdf
402
+ plug = TestPlugin.new(pdf)
403
+ flexmock(TestPlugin).should_receive(:new).once.with(pdf).and_return(plug)
404
+ m = flexmock{|mk| mk.should_receive(:component_loaded).once.with(plug)}
405
+ @manager.connect(SIGNAL('component_loaded(QObject*)')){|o| m.component_loaded o}
406
+ @manager.load_plugin @dir
407
+ end
408
+
409
+ it 'should emit the "feature_loaded(QString, QObject*)" signal for each feature provided by the plugin' do
410
+ pdf = Ruber::PluginSpecification.full(YAML.load(@yaml))
411
+ pdf.features << :x << :y
412
+ flexmock(Ruber::PluginSpecification).should_receive(:full).and_return pdf
413
+ plug = TestPlugin.new(pdf)
414
+ flexmock(TestPlugin).should_receive(:new).once.with(pdf).and_return(plug)
415
+ m = flexmock do |mk|
416
+ mk.should_receive(:feature_loaded).with('test', plug).once
417
+ mk.should_receive(:feature_loaded).with('x', plug).once
418
+ mk.should_receive(:feature_loaded).with('y', plug).once
419
+ end
420
+ @manager.connect(SIGNAL('feature_loaded(QString, QObject*)')){|s, o| m.feature_loaded s, o}
421
+ @manager.load_plugin @dir
422
+ end
423
+
424
+ it 'calls the new plugin\'s delayed_initialize method, after emitting the feature_loaded signals' do
425
+ pdf = Ruber::PluginSpecification.full(YAML.load(@yaml))
426
+ pdf.features << :x << :y
427
+ flexmock(Ruber::PluginSpecification).should_receive(:full).and_return pdf
428
+ plug = TestPlugin.new(pdf)
429
+ flexmock(TestPlugin).should_receive(:new).once.with(pdf).and_return(plug)
430
+ m = flexmock do |mk|
431
+ mk.should_receive(:feature_loaded).with('test', plug).once.ordered(:signals)
432
+ mk.should_receive(:feature_loaded).with('x', plug).once.ordered(:signals)
433
+ mk.should_receive(:feature_loaded).with('y', plug).once.ordered(:signals)
434
+ end
435
+ @manager.connect(SIGNAL('feature_loaded(QString, QObject*)')){|s, o| m.feature_loaded s, o}
436
+ flexmock(plug).should_receive(:delayed_initialize).once.ordered
437
+ @manager.load_plugin @dir
438
+ end
439
+
440
+ it 'calls the new plugin\'s delayed_initialize method even if it\'s private' do
441
+ pdf = Ruber::PluginSpecification.full(YAML.load(@yaml))
442
+ flexmock(Ruber::PluginSpecification).should_receive(:full).and_return pdf
443
+ plug = TestPlugin.new(pdf)
444
+ flexmock(TestPlugin).should_receive(:new).once.with(pdf).and_return(plug)
445
+ class << plug
446
+ private :delayed_initialize
447
+ end
448
+ flexmock(plug).should_receive(:delayed_initialize).once
449
+ @manager.load_plugin @dir
450
+ end
451
+
452
+ it 'should create a signal "unloading_*(QObject*)" for each feature provided by the plugin' do
453
+ pdf = Ruber::PluginSpecification.full(YAML.load(@yaml))
454
+ pdf.features << :x << :y
455
+ flexmock(Ruber::PluginSpecification).should_receive(:full).and_return pdf
456
+ plug = TestPlugin.new(pdf)
457
+ flexmock(TestPlugin).should_receive(:new).once.with(pdf).and_return(plug)
458
+ m = flexmock do |mk|
459
+ mk.should_receive(:unloading_x).with(plug).once
460
+ mk.should_receive(:unloading_y).with(plug).once
461
+ mk.should_receive(:unloading_test).with(plug).once
462
+ end
463
+ @manager.load_plugin @dir
464
+ @manager.connect(SIGNAL('unloading_x(QObject*)')){|o| m.unloading_x o}
465
+ @manager.connect(SIGNAL('unloading_y(QObject*)')){|o| m.unloading_y o}
466
+ @manager.connect(SIGNAL('unloading_test(QObject*)')){|o| m.unloading_test o}
467
+ @manager.instance_eval{emit unloading_x(plug)}
468
+ @manager.instance_eval{emit unloading_y(plug)}
469
+ @manager.instance_eval{emit unloading_test(plug)}
470
+ end
471
+
472
+ end
473
+
474
+ describe 'Ruber::ComponentManager.sort_plugins' do
475
+
476
+ it 'should sort the plugins alphabetically if no dependencies exist' do
477
+ pdfs = [
478
+ {:name => :c, :deps => []},
479
+ {:name => :a, :deps => []},
480
+ {:name => :s, :deps => []},
481
+ {:name => :m, :deps => []}
482
+ ].map{|i| OpenStruct.new i}
483
+ res = Ruber::ComponentManager.sort_plugins(pdfs)
484
+ res.map{|i| i.name}.should == [:a, :c, :m, :s]
485
+ end
486
+
487
+ it 'should return an array sorted according to dependencies' do
488
+ pdfs = [
489
+ {:name => :c, :deps => [:s, :a]},
490
+ {:name => :a, :deps => [:x]},
491
+ {:name => :s, :deps => []},
492
+ {:name => :m, :deps => [:x, :c]},
493
+ {:name => :x, :deps => []}
494
+ ].map{|i| OpenStruct.new i}
495
+ res = Ruber::ComponentManager.sort_plugins(pdfs)
496
+ res.map{|i| i.name}.should == [:s, :x, :a, :c, :m]
497
+ end
498
+
499
+ it 'should raise Ruber::ComponentManager::UnresolvedDep if a dependency can\'t be resolved' do
500
+ pdfs = [
501
+ {:name => :c, :deps => [:s, :a]},
502
+ {:name => :a, :deps => [:x]},
503
+ {:name => :s, :deps => []},
504
+ {:name => :m, :deps => [:x, :r]},
505
+ {:name => :x, :deps => []},
506
+ {:name => :l, :deps => [:r, :t]}
507
+ ].map{|i| OpenStruct.new i}
508
+ lambda{Ruber::ComponentManager.sort_plugins(pdfs)}.should raise_error(Ruber::ComponentManager::UnresolvedDep){|e| e.missing.should == {:r => [:m, :l], :t => [:l]}}
509
+ end
510
+
511
+ it 'should raise Ruber::ComponentManager::CircularDep if there are circular dependencies' do
512
+ pdfs = [
513
+ {:name => :c, :deps => [:s, :a]},
514
+ {:name => :a, :deps => [:x]},
515
+ {:name => :s, :deps => []},
516
+ {:name => :x, :deps => [:c]},
517
+ ].map{|i| OpenStruct.new i}
518
+ circ = [[:a, :x], [:x, :c], [:c, :a]]
519
+ lambda{Ruber::ComponentManager.sort_plugins(pdfs)}.should raise_error(Ruber::ComponentManager::CircularDep){|e| circ.should include(e.circular_deps)}
520
+ end
521
+
522
+ it 'should not count features passed as second argument' do
523
+ pdfs = [
524
+ {:name => :c, :deps => [:s, :a, :b]},
525
+ {:name => :a, :deps => [:x, :l]},
526
+ {:name => :s, :deps => []},
527
+ {:name => :m, :deps => [:x, :c]},
528
+ {:name => :x, :deps => [:l, :b]}
529
+ ].map{|i| OpenStruct.new i}
530
+ res = Ruber::ComponentManager.sort_plugins(pdfs, [:b, :l])
531
+ res.map{|i| i.name}.should == [:s, :x, :a, :c, :m]
532
+ end
533
+
534
+ end
535
+
536
+ describe 'Ruber::ComponentManager.resolve_features' do
537
+
538
+ it 'should return an array of pdfs where the dependencies have been changed according to the features provided by the plugins' do
539
+ pdfs = [
540
+ {:name => :c, :deps => [:s, :a, :d], :features => [:c]},
541
+ {:name => :a, :deps => [:x, :r], :features => [:a]},
542
+ {:name => :s, :deps => [], :features => [:s, :r]},
543
+ {:name => :m, :deps => [:x, :c, :d], :features => [:m]},
544
+ {:name => :x, :deps => [], :features => [:x, :d]}
545
+ ].map{|i| OpenStruct.new i}
546
+ res = Ruber::ComponentManager.resolve_features pdfs
547
+ res[0].deps.should =~ [:s, :a, :x]
548
+ res[1].deps.should =~ [:x, :s]
549
+ res[2].deps.should == []
550
+ res[3].deps.should =~ [:x, :c]
551
+ res[4].deps.should == []
552
+ end
553
+
554
+ it 'should raise Ruber::ComponentManager::UnresolvedDep if some features can\'t be found' do
555
+ pdfs = [
556
+ {:name => :c, :deps => [:s, :a, :d, :y], :features => [:c]},
557
+ {:name => :a, :deps => [:x, :r], :features => [:a]},
558
+ {:name => :s, :deps => [], :features => [:s, :r]},
559
+ {:name => :m, :deps => [:x, :c, :d, :z, :y], :features => [:m]},
560
+ {:name => :x, :deps => [], :features => [:x, :d]}
561
+ ].map{|i| OpenStruct.new i}
562
+ lambda{Ruber::ComponentManager.resolve_features pdfs}.should raise_error(Ruber::ComponentManager::UnresolvedDep){|e| e.missing.should == {:c => [:y], :m => [:z, :y]}}
563
+ end
564
+
565
+ it 'should also take into account pdfs passed as second argument in the resolution' do
566
+ pdfs = [
567
+ {:name => :c, :deps => [:s, :a, :d, :y, :k], :features => [:c]},
568
+ {:name => :a, :deps => [:x, :r], :features => [:a]},
569
+ {:name => :s, :deps => [], :features => [:s, :r]},
570
+ {:name => :m, :deps => [:x, :c, :d, :z, :y], :features => [:m]},
571
+ {:name => :x, :deps => [], :features => [:x, :d]}
572
+ ].map{|i| OpenStruct.new i}
573
+ extra = [
574
+ {:name => :A, :features => [:A, :z]},
575
+ {:name => :B, :features => [:B, :y, :k]}
576
+ ].map{|i| OpenStruct.new i}
577
+ res = Ruber::ComponentManager.resolve_features pdfs, extra
578
+ res[0].deps.should =~ [:s, :a, :x, :B]
579
+ res[1].deps.should =~ [:x, :s]
580
+ res[2].deps.should == []
581
+ res[3].deps.should =~ [:x, :c, :A, :B]
582
+ res[4].deps.should == []
583
+ end
584
+
585
+ it 'should not modifiy the pdfs passed as argument' do
586
+ pdfs = [
587
+ {:name => :c, :deps => [:s, :a, :d], :features => [:c]},
588
+ {:name => :a, :deps => [:x, :r], :features => [:a]},
589
+ {:name => :s, :deps => [], :features => [:s, :r]},
590
+ {:name => :m, :deps => [:x, :c, :d], :features => [:m]},
591
+ {:name => :x, :deps => [], :features => [:x, :d]}
592
+ ].map{|i| OpenStruct.new i}
593
+ res = Ruber::ComponentManager.resolve_features pdfs
594
+ res.each_with_index{|pl, i| pl.should_not equal(pdfs[i])}
595
+ end
596
+
597
+ end
598
+
599
+ describe 'Ruber::ComponentManager#load_plugins' do
600
+
601
+ before do
602
+ @manager = Ruber::ComponentManager.new
603
+ flexmock(Ruber).should_receive(:[]).with(:app).and_return(Qt::Object.new).by_default
604
+ flexmock(Ruber).should_receive(:[]).with(:components).and_return(@manager).by_default
605
+ flexmock(Ruber).should_receive(:[]).with(:config).and_return(nil).by_default
606
+ @data = %q{[[d1, [p1, plugin.yaml], [p2, plugin.yaml]], [d2, [p3, plugin.yaml], [p1, plugin.yaml]]]}
607
+ @tree = YAML.load @data
608
+ @dir = nil
609
+ Object.const_set :P1, Class.new(Ruber::Plugin)
610
+ Object.const_set :P2, Class.new(Ruber::Plugin)
611
+ Object.const_set :P3, Class.new(Ruber::Plugin)
612
+ end
613
+
614
+ after do
615
+ FileUtils.rm_r @dir if @dir
616
+ Object.send :remove_const, :P1
617
+ Object.send :remove_const, :P2
618
+ Object.send :remove_const, :P3
619
+ end
620
+
621
+ it 'should find the plugins in the given directories and load them in the alphabetically order if there aren\'t dependencies among them' do
622
+ contents = {
623
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1}',
624
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
625
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
626
+ }
627
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
628
+ @manager.load_plugins(%w[p2 p1 p3], %w[d1 d2].map{|d| File.join(@dir, d)})
629
+ @manager.plugins.map{|i| i.class}.should == [P1, P2, P3]
630
+ end
631
+
632
+ it 'should work with both strings and symbols' do
633
+ contents = {
634
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1}',
635
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
636
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
637
+ }
638
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
639
+ @manager.load_plugins([:p2, :p1, :p3], %w[d1 d2].map{|d| File.join(@dir, d)})
640
+ @manager.plugins.map{|i| i.class}.should == [P1, P2, P3]
641
+
642
+ end
643
+
644
+ it 'should find the plugins in the given directories and load them in dependecy order' do
645
+ contents = {
646
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1, deps: :p3}',
647
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
648
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3, deps: :p2}'
649
+ }
650
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
651
+ @manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)})
652
+ @manager.plugins.map{|i| i.class}.should == [P2, P3, P1]
653
+ end
654
+
655
+ it 'should resolve dependencies among the plugins' do
656
+ contents = {
657
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1, deps: :p4}',
658
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
659
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3, deps: :p2, features: [:p4]}'
660
+ }
661
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
662
+ @manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)})
663
+ @manager.plugins.map{|i| i.class}.should == [P2, P3, P1]
664
+ end
665
+
666
+ it 'should load the plugins using the pdfs with the unresolved dependencies' do
667
+ contents = {
668
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1, deps: :p4}',
669
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
670
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3, deps: :p2, features: [:p4]}'
671
+ }
672
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
673
+ @manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)})
674
+ @manager[:p1].plugin_description.deps.should == [:p4]
675
+ end
676
+
677
+ it 'should use the already-loaded plugins, if any, to compute dependencies' do
678
+ Object.const_set :P4, Class.new(Ruber::Plugin)
679
+ Object.const_set :P5, Class.new(Ruber::Plugin)
680
+ loaded = [{:name => :p4, :class => P4 }, {:name => :p5, :class => P5, :features => [:p6]}].map{|i| Ruber::PluginSpecification.full i}
681
+ P4.new loaded[0]
682
+ P5.new loaded[1]
683
+ contents = {
684
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1, deps: :p4}',
685
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2, deps: p6}',
686
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3, deps: :p2}'
687
+ }
688
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
689
+ @manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)})
690
+ lambda{@manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)})}.should_not raise_error
691
+ end
692
+
693
+ it 'should raise Ruber::ComponentManager::MissingPlugins if some plugins couldn\'t be found' do
694
+ contents = {
695
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1}',
696
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
697
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
698
+ }
699
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
700
+ lambda{@manager.load_plugins(%w[p1 p2 p3 p4 p5], %w[d1 d2].map{|d| File.join(@dir, d)})}.should raise_error(Ruber::ComponentManager::MissingPlugins) do |e|
701
+ e.missing.should =~ ['p4', 'p5']
702
+ end
703
+ end
704
+
705
+ it 'should raise Ruber::ComponentManager::InvalidPDF if the PDF for some plugins was invalid' do
706
+ contents = {
707
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1',
708
+ 'd1/p2/plugin.yaml' => '{class: P2}',
709
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
710
+ }
711
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
712
+ lambda{@manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)})}.should raise_error(Ruber::ComponentManager::InvalidPDF) do |e|
713
+ e.files.should =~ %w[d1/p1/plugin.yaml d1/p2/plugin.yaml].map{|f| File.join @dir, f}
714
+ end
715
+ end
716
+
717
+ it 'should raise an exception if loading a plugin raises an exception and no block is given' do
718
+ contents = {
719
+ 'd1/p1/plugin.yaml' => '{name: p1, class: X1}',
720
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
721
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
722
+ }
723
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
724
+ lambda{@manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)})}.should raise_error(NameError)
725
+ end
726
+
727
+ it 'should call the block if an exception occurs while loading a plugin and return false as soon as the block returns false' do
728
+ P1.class_eval{def initialize pdf; raise NoMethodError;end}
729
+ P3.class_eval{def initialize pdf; raise ArgumentError;end}
730
+ contents = {
731
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1}',
732
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
733
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
734
+ }
735
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
736
+ m = flexmock do |mk|
737
+ mk.should_receive(:test).with(Ruber::PluginSpecification, NoMethodError).once.and_return(true)
738
+ mk.should_receive(:test).with(Ruber::PluginSpecification, ArgumentError).once.and_return(false)
739
+ end
740
+ (@manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)}){|pl, e|m.test pl, e}).should be_false
741
+ end
742
+
743
+ it 'should call the block if an exception occurs while loading a plugin and stop loading plugins and return true if the block returns :skip' do
744
+ P1.class_eval{def initialize pdf; raise NoMethodError;end}
745
+ P2.class_eval{def initialize pdf; raise ArgumentError;end}
746
+ contents = {
747
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1}',
748
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
749
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
750
+ }
751
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
752
+ m = flexmock do |mk|
753
+ mk.should_receive(:test).with(Ruber::PluginSpecification, NoMethodError).once.and_return(true)
754
+ mk.should_receive(:test).with(Ruber::PluginSpecification, ArgumentError).once.and_return(:skip)
755
+ end
756
+ flexmock(P3).should_receive(:new).never
757
+ (@manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)}){|pl, e|m.test pl, e}).should be_true
758
+ end
759
+
760
+ it 'should call the block if an exception occurs while loading a plugin, stop calling it for following errors and return true if the block returns :silent' do
761
+ P1.class_eval{def initialize pdf; raise NoMethodError;end}
762
+ P2.class_eval{def initialize pdf; raise ArgumentError;end}
763
+ contents = {
764
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1}',
765
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
766
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
767
+ }
768
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
769
+ m = flexmock do |mk|
770
+ mk.should_receive(:test).with(Ruber::PluginSpecification, NoMethodError).once.and_return(:silent)
771
+ mk.should_receive(:test).with(Ruber::PluginSpecification, ArgumentError).never
772
+ end
773
+ (@manager.load_plugins(%w[p1 p2 p3], %w[d1 d2].map{|d| File.join(@dir, d)}){|pl, e|m.test pl, e}).should be_true
774
+ @manager.plugins.last.should be_a(P3)
775
+ end
776
+
777
+ it 'should return true if no error occurs' do
778
+ contents = {
779
+ 'd1/p1/plugin.yaml' => '{name: p1, class: P1}',
780
+ 'd1/p2/plugin.yaml' => '{name: p2, class: P2}',
781
+ 'd2/p3/plugin.yaml' => '{name: p3, class: P3}'
782
+ }
783
+ @dir = make_dir_tree(@tree, '/tmp/', contents)
784
+ @manager.load_plugins(%w[p2 p1 p3], %w[d1 d2].map{|d| File.join(@dir, d)}).should be_true
785
+ end
786
+
787
+ end
788
+
789
+ describe 'Ruber::ComponentManager#shutdown' do
790
+
791
+ before do
792
+ # @cls = Class.new(Qt::Object) do
793
+ # include Ruber::PluginLike
794
+ # def initialize pdf
795
+ # super Ruber[:app]
796
+ # initialize_plugin pdf
797
+ # end
798
+ # # def connect *args
799
+ # # end
800
+ #
801
+ # def is_a? cls
802
+ # true
803
+ # end
804
+ # end
805
+ @manager = Ruber::ComponentManager.new
806
+
807
+ flexmock(Ruber).should_receive(:[]).with(:app).and_return(Qt::Object.new).by_default
808
+ flexmock(Ruber).should_receive(:[]).with(:components).and_return(@manager).by_default
809
+ @config = Qt::Object.new
810
+ flexmock(@config).should_receive(:save_settings).by_default
811
+ flexmock(@config).should_receive(:write).by_default
812
+ flexmock(@config).should_receive(:shutdown).by_default
813
+ @manager.instance_variable_get(:@components)[:config] = @config
814
+ flexmock(Ruber).should_receive(:[]).with(:config).and_return(@config).by_default
815
+ end
816
+
817
+ it 'should call the "save_settings" method of each plugin, except for itself' do
818
+ comps = @manager.instance_variable_get(:@components)
819
+ components = 5.times.map{|i| Ruber::Plugin.new(Ruber::PluginSpecification.full({:name => "c#{i}"}))}
820
+ components.reverse_each{|o| flexmock(o).should_receive(:save_settings).once.globally.ordered}
821
+ flexmock(@manager).should_receive(:save_settings).never
822
+ @manager.shutdown
823
+ end
824
+
825
+ it 'calls the write method of the config object after the plugins\' save_settings method' do
826
+ comps = @manager.instance_variable_get(:@components)
827
+ components = 5.times.map{|i| Ruber::Plugin.new(Ruber::PluginSpecification.full({:name => "c#{i}"}))}
828
+ components.each{|o| flexmock(o).should_receive(:save_settings).once.ordered('components')}
829
+ flexmock(@config).should_receive(:write).once.ordered
830
+ @manager.shutdown
831
+ end
832
+
833
+ # it 'should emit the "unloading_*(QObject*)" for features provided by plugins' do
834
+ # comps = @manager.instance_variable_get(:@components)
835
+ # features = @manager.instance_variable_get(:@features)
836
+ # plugin_pdfs = [
837
+ # {:name => :p1, :features => [:x]},
838
+ # {:name => :p2, :features => [:y, :z]}
839
+ # ].map{|i| Ruber::PluginSpecification.full(i)}
840
+ # component_pdfs = [ {:name => :c1}, {:name => :c2} ].map{|i| Ruber::PluginSpecification.full(i)}
841
+ # components = component_pdfs.map do |c|
842
+ # comp = Ruber::Plugin.new c
843
+ # flexmock(comp).should_receive(:is_a?).with(Ruber::Plugin).and_return false
844
+ # comp
845
+ # end
846
+ # plugins = plugin_pdfs.map{|pl| Ruber::Plugin.new pl}
847
+ # (components + plugins).reverse_each{|i| flexmock(i).should_receive(:shutdown).once.globally.ordered}
848
+ # plugins.reverse_each{|pl| flexmock(pl).should_receive( :delete_later ).globally.ordered}
849
+ # @manager.class.class_eval do
850
+ # signals( *%w[p1 x p2 y z].map{|s| "unloading_#{s}(QObject*)"})
851
+ # end
852
+ # m = flexmock do |mk|
853
+ # %w[x y z].each do |x|
854
+ # mk.should_receive("unloading_#{x}".to_sym).once.with(features[x.to_sym])
855
+ # @manager.connect(SIGNAL("unloading_#{x}(QObject*)")){|o| mk.send("unloading_#{x}", o)}
856
+ # end
857
+ # end
858
+ # @manager.shutdown
859
+ # end
860
+ #
861
+ # it 'should emit the "unloading_component(QObject*)" signal for each component except itself, in reverse loading order' do
862
+ # comps = @manager.instance_variable_get(:@components)
863
+ # components = 5.times.map do |i|
864
+ # flexmock(@manager).should_receive("unloading_c#{i}")
865
+ # Ruber::Plugin.new(Ruber::PluginSpecification.full({:name => "c#{i}"}))
866
+ # end
867
+ # m = flexmock do |mk|
868
+ # components.reverse_each{|c| mk.should_receive(:test).with(c).once.globally.ordered}
869
+ # mk.should_receive(:test).once.with(@config)
870
+ # end
871
+ # @manager.connect( SIGNAL('unloading_component(QObject*)')){|o| m.test o}
872
+ # @manager.shutdown
873
+ # end
874
+ #
875
+ it 'should call the "shutdown" methods of all the components, except itself, in reverse loading order' do
876
+ comps = @manager.instance_variable_get(:@components)
877
+ components = 5.times.map do |i|
878
+ flexmock(@manager).should_receive("unloading_c#{i}")
879
+ Ruber::Plugin.new(Ruber::PluginSpecification.full({:name => "c#{i}"}))
880
+ end
881
+ components.reverse_each{|o| flexmock(o).should_receive(:shutdown).once.globally.ordered}
882
+ @manager.shutdown
883
+ end
884
+ #
885
+ # it 'should call the "delete_later" method of all plugins, after having call the "shutdown" methods' do
886
+ # cls = Class.new(Qt::Object) do
887
+ # include Ruber::PluginLike
888
+ # def initialize pdf
889
+ # super Ruber[:app]
890
+ # initialize_plugin pdf
891
+ # end
892
+ #
893
+ # def is_a? cls
894
+ # false
895
+ # end
896
+ # end
897
+ #
898
+ # components = 3.times.map{|i| cls.new(Ruber::PluginSpecification.full({:name => "c#{i}"}))}
899
+ # plugins=3.times.map{|i| Ruber::Plugin.new(Ruber::PluginSpecification.full({:name => "p#{i}"}))}
900
+ # (components + plugins).reverse_each{|i| flexmock(i).should_receive(:shutdown).once.globally.ordered}
901
+ # plugins.reverse_each{|pl| flexmock(pl).should_receive( :delete_later ).globally.ordered}
902
+ # @manager.class.class_eval do
903
+ # signals( *%w[p0 p1 p2].map{|s| "unloading_#{s}(QObject*)"})
904
+ # end
905
+ # @manager.shutdown
906
+ # end
907
+ #
908
+ # it 'should remove all plugins from the list of components' do
909
+ # cls = Class.new(Qt::Object) do
910
+ # include Ruber::PluginLike
911
+ # def initialize pdf
912
+ # super Ruber[:app]
913
+ # initialize_plugin pdf
914
+ # end
915
+ #
916
+ # def is_a? cls
917
+ # false
918
+ # end
919
+ # end
920
+ #
921
+ # components = 3.times.map{|i| cls.new(Ruber::PluginSpecification.full({:name => "c#{i}"}))}
922
+ # components.unshift @config
923
+ # plugins=3.times.map{|i| Ruber::Plugin.new(Ruber::PluginSpecification.full({:name => "p#{i}"}))}
924
+ # comps = @manager.instance_variable_get(:@components)
925
+ # (components + plugins).reverse_each{|i| flexmock(i).should_receive(:shutdown).once.globally.ordered}
926
+ # plugins.reverse_each{|pl| flexmock(pl).should_receive( :delete_later ).globally.ordered}
927
+ # @manager.class.class_eval do
928
+ # signals( *%w[p0 p1 p2].map{|s| "unloading_#{s}(QObject*)"})
929
+ # end
930
+ # @manager.shutdown
931
+ # @manager.instance_variable_get(:@components).values.sort_by{|o| o.object_id}.should == ([@manager] + components).sort_by{|o| o.object_id}
932
+ # end
933
+ #
934
+ # it 'should remove all features provided by plugins' do
935
+ # plugin_pdfs = [
936
+ # {:name => :p0},
937
+ # {:name => :p1, :features => [:x, :y]},
938
+ # {:name => :p2, :features => [:w, :z]}
939
+ # ].map{|i| Ruber::PluginSpecification.full(i)}
940
+ # component_pdfs = [
941
+ # {:name => :c0, :features => [:a, :b]},
942
+ # {:name => :c1, :features => [:c]}
943
+ # ].map{|i| Ruber::PluginSpecification.full(i)}
944
+ # cls = Class.new(Qt::Object) do
945
+ # include Ruber::PluginLike
946
+ # def initialize pdf
947
+ # super Ruber[:app]
948
+ # initialize_plugin pdf
949
+ # end
950
+ #
951
+ # def is_a? cls
952
+ # false
953
+ # end
954
+ # end
955
+ # components = component_pdfs.map{|i| cls.new(i)}
956
+ # plugins = plugin_pdfs.map{|i| Ruber::Plugin.new(i)}
957
+ # (components + plugins).reverse_each{|i| flexmock(i).should_receive(:shutdown).once.globally.ordered}
958
+ # plugins.reverse_each{|pl| flexmock(pl).should_receive( :delete_later ).globally.ordered}
959
+ # @manager.class.class_eval do
960
+ # signals( *%w[p0 p1 p2 x y w z].map{|s| "unloading_#{s}(QObject*)"})
961
+ # end
962
+ # @manager.shutdown
963
+ # @manager.instance_variable_get(:@features).should == {:components => @manager, :c0 => components[0], :a => components[0], :b => components[0], :c1 => components[1], :c => components[1]}
964
+ # end
965
+
966
+ end
967
+
968
+ describe 'Ruber::ComponentManager#unload_plugin' do
969
+
970
+ before do
971
+ @cls = Class.new(Qt::Object) do
972
+ include Ruber::PluginLike
973
+ def initialize pdf
974
+ super Ruber[:app]
975
+ initialize_plugin pdf
976
+ end
977
+ def is_a? cls
978
+ false
979
+ end
980
+ end
981
+ @manager = Ruber::ComponentManager.new
982
+ flexmock(Ruber).should_receive(:[]).with(:app).and_return(Qt::Object.new).by_default
983
+ flexmock(Ruber).should_receive(:[]).with(:components).and_return(@manager).by_default
984
+ flexmock(Ruber).should_receive(:[]).with(:config).and_return(nil).by_default
985
+
986
+ plugin_pdfs = [
987
+ {:name => :p0},
988
+ {:name => :p1, :features => [:x, :y]},
989
+ {:name => :p2, :features => [:w, :z]}
990
+ ].map{|i| Ruber::PluginSpecification.full(i)}
991
+ component_pdfs = [
992
+ {:name => :c0, :features => [:a, :b]},
993
+ {:name => :c1, :features => [:c]}
994
+ ].map{|i| Ruber::PluginSpecification.full(i)}
995
+ @components = component_pdfs.map{|i| @cls.new(i)}
996
+ @plugins = plugin_pdfs.map{|i| Ruber::Plugin.new(i)}
997
+ @manager.class.class_eval do
998
+ signals( *%w[p0 p1 p2 x y w z].map{|s| "unloading_#{s}(QObject*)"})
999
+ end
1000
+
1001
+ end
1002
+
1003
+ it 'should emit the "unloading_*(QObject*)" signal for each feature provided by the plugin' do
1004
+ m = flexmock do |mk|
1005
+ mk.should_receive(:unloading_p1).once.with(@plugins[1])
1006
+ mk.should_receive(:unloading_x).once.with(@plugins[1])
1007
+ mk.should_receive(:unloading_y).once.with(@plugins[1])
1008
+ end
1009
+ @manager.class.class_eval do
1010
+ signals( *%w[p0 p1 p2 x y w z].map{|s| "unloading_#{s}(QObject*)"})
1011
+ end
1012
+ %w[p1 x y].each{|f| @manager.connect(SIGNAL("unloading_#{f}(QObject*)")){|o| m.send("unloading_#{f}", o)}}
1013
+ @manager.unload_plugin :p1
1014
+ end
1015
+
1016
+ it 'should emit the "unloading_component(QObject*) signal passing the plugin as argument' do
1017
+ m = flexmock{|mk| mk.should_receive(:unloading_component).once.with(@plugins[1])}
1018
+ @manager.connect(SIGNAL("unloading_component(QObject*)")){|o| m.unloading_component o}
1019
+ @manager.unload_plugin :p1
1020
+ end
1021
+
1022
+ it 'should call the "unload" method of the plugin after emitting the unloading_component signal' do
1023
+ m = flexmock{|mk| mk.should_receive(:unloading_component).once.with(@plugins[1]).globally.ordered}
1024
+ @manager.connect(SIGNAL("unloading_component(QObject*)")){|o| m.unloading_component o}
1025
+ flexmock(@plugins[1]).should_receive(:unload).once.globally.ordered
1026
+ @manager.unload_plugin :p1
1027
+ end
1028
+
1029
+ it 'should call the "delete_later" method of the plugin after calling shutdown' do
1030
+ flexmock(@plugins[1]).should_receive(:shutdown).once.globally.ordered
1031
+ flexmock(@plugins[1]).should_receive(:delete_later).once.globally.ordered
1032
+ @manager.unload_plugin :p1
1033
+ end
1034
+
1035
+ it 'should remove the plugin from the list of components and its features from the list of features' do
1036
+ @manager.unload_plugin :p1
1037
+ @manager.instance_variable_get(:@features).keys.should =~ [:p0, :p2, :w, :z, :c0, :a, :b, :c1, :c, :components]
1038
+ @manager.instance_variable_get(:@components).keys.map(&:to_s).should =~ [:p0, :p2, :c0, :c1, :components].map(&:to_s)
1039
+ end
1040
+
1041
+ it 'should raise ArgumentError if the name corresponds to a component instead of a plugin' do
1042
+ lambda{@manager.unload_plugin(:c1)}.should raise_error(ArgumentError, "A component can't be unloaded")
1043
+ end
1044
+
1045
+ end
1046
+
1047
+ describe 'Ruber::ComponentManager#query_close' do
1048
+
1049
+ before do
1050
+ @manager = Ruber::ComponentManager.new
1051
+ end
1052
+
1053
+ it 'should call the query_close method of every component, except itself' do
1054
+ h = @manager.instance_variable_get(:@components)
1055
+ 5.times do |i|
1056
+ mk = flexmock(:name => :"c#{i}"){|m| m.should_receive(:query_close).once.and_return true}
1057
+ h << [mk.name, mk]
1058
+ end
1059
+ @manager.query_close
1060
+ end
1061
+
1062
+ it 'should return false as soon as one of the components\' query_close method returns false' do
1063
+ h = @manager.instance_variable_get(:@components)
1064
+ 5.times do |i|
1065
+ mk = flexmock("c#{i}", :name => "c#{i}")
1066
+ if i > 2 then mk.should_receive(:query_close).once.and_return true
1067
+ elsif i == 2 then mk.should_receive(:query_close).once.and_return false
1068
+ else mk.should_receive(:query_close).never
1069
+ end
1070
+ h << [mk.name, mk]
1071
+ end
1072
+ @manager.query_close.should be_false
1073
+ end
1074
+
1075
+ it 'should return true if all components\' query_close methods return true' do
1076
+ h = @manager.instance_variable_get(:@components)
1077
+ 5.times do |i|
1078
+ mk = flexmock(:name => :"c#{i}"){|m| m.should_receive(:query_close).once.and_return true}
1079
+ h << [mk.name, mk]
1080
+ end
1081
+ @manager.query_close.should be_true
1082
+ end
1083
+
1084
+ end
1085
+
1086
+ describe Ruber::ComponentManager do
1087
+
1088
+ before do
1089
+ @manager = Ruber::ComponentManager.new
1090
+ end
1091
+
1092
+ describe '#session_data' do
1093
+
1094
+ it 'calls the session_data of each other component' do
1095
+ h = @manager.instance_variable_get(:@components)
1096
+ 5.times do |i|
1097
+ mk = flexmock(:name => i.to_s){|m| m.should_receive(:session_data).once.with_no_args.and_return({})}
1098
+ h << [mk.name, mk]
1099
+ end
1100
+ @manager.session_data
1101
+ end
1102
+
1103
+ it 'returns a hash obtained by merging the hashes returned by each components\' session_data method' do
1104
+ h = @manager.instance_variable_get(:@components)
1105
+ 5.times do |i|
1106
+ mk = flexmock(:name => i.to_s){|m| m.should_receive(:session_data).once.and_return({i.to_s => i})}
1107
+ h << [mk.name, mk]
1108
+ end
1109
+ res = @manager.session_data
1110
+ res.should == {'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4}
1111
+ end
1112
+
1113
+ end
1114
+
1115
+ describe '#restore_session' do
1116
+
1117
+ it 'calls the restore_session of each other component passing it the argument' do
1118
+ cfg = KDE::ConfigGroup.new
1119
+ h = @manager.instance_variable_get(:@components)
1120
+ 5.times do |i|
1121
+ mk = flexmock(:name => i.to_s){|m| m.should_receive(:restore_session).once.with(cfg)}
1122
+ h << [mk.name, mk]
1123
+ end
1124
+ @manager.restore_session cfg
1125
+ end
1126
+
1127
+ end
1128
+
1129
+
1130
+ end
1131
+
1132
+ describe 'Ruber::ComponentManager.find_plugins' do
1133
+
1134
+ before do
1135
+ @manager = Ruber::ComponentManager.new
1136
+ end
1137
+
1138
+ it 'should return a hash containing the full path of the plugin directories in the given directories as values and the plugin names as keys, if the second argument is false' do
1139
+ dir = make_dir_tree YAML.load(%q{[[d1, [p1, plugin.yaml], [p2, plugin.yaml]], [d2, [p3, plugin.yaml], [p4, plugin.yaml]]]})
1140
+ exp = {:p1 => 'd1/p1', :p2 => 'd1/p2', :p3 => 'd2/p3', :p4 => 'd2/p4'}.map{|p, d| [p, File.join(dir, d)]}.to_h
1141
+ dirs = %w[d1 d2].map{|d| File.join dir, d}
1142
+ Ruber::ComponentManager.find_plugins(dirs).should == exp
1143
+ Ruber::ComponentManager.find_plugins(dirs, false).should == exp
1144
+ FileUtils.rm_r dir
1145
+ end
1146
+
1147
+ it 'should return a hash containing the names of the plugins (as symbols) as keys and the corresponding PluginSpecification (with the directory set to the plugin file) as values' do
1148
+ contents = {
1149
+ 'd1/p1/plugin.yaml' => 'name: p1',
1150
+ 'd1/p2/plugin.yaml' => 'name: p2',
1151
+ 'd2/p3/plugin.yaml' => 'name: p3',
1152
+ 'd2/p4/plugin.yaml' => 'name: p4',
1153
+ }
1154
+ dir = make_dir_tree YAML.load(%q{[[d1, [p1, plugin.yaml], [p2, plugin.yaml]], [d2, [p3, plugin.yaml], [p4, plugin.yaml]]]}), '/tmp', contents
1155
+ exp = %w[d1/p1 d1/p2 d2/p3 d2/p4].map do |d|
1156
+ pdf = File.join d, 'plugin.yaml'
1157
+ data = YAML.load contents[pdf]
1158
+ [File.basename(d).to_sym, Ruber::PluginSpecification.intro(data)]
1159
+ end.to_h
1160
+ dirs = %w[d1 d2].map{|d| File.join dir, d}
1161
+ res = Ruber::ComponentManager.find_plugins(dirs, true)
1162
+ res.should == exp
1163
+ res[:p1].directory.should == File.join(dirs[0], 'p1')
1164
+ res[:p2].directory.should == File.join(dirs[0], 'p2')
1165
+ res[:p3].directory.should == File.join(dirs[1], 'p3')
1166
+ res[:p4].directory.should == File.join(dirs[1], 'p4')
1167
+ FileUtils.rm_r dir
1168
+ end
1169
+
1170
+ it 'should return only the file in the earliest directory, if more than one directory contain the same plugin' do
1171
+ dir = make_dir_tree YAML.load(%q{[[d1, [p1, plugin.yaml], [p2, plugin.yaml]], [d2, [p3, plugin.yaml], [p1, plugin.yaml]]]})
1172
+ exp = {:p1 => 'd1/p1', :p2 => 'd1/p2', :p3 => 'd2/p3'}.map{|p, d| [p, File.join(dir, d)]}.to_h
1173
+ dirs = %w[d1 d2].map{|d| File.join dir, d}
1174
+ Ruber::ComponentManager.find_plugins(dirs).should == exp
1175
+ Ruber::ComponentManager.find_plugins(dirs, false).should == exp
1176
+ FileUtils.rm_r dir
1177
+ contents = {
1178
+ 'd1/p1/plugin.yaml' => 'name: p1',
1179
+ 'd1/p2/plugin.yaml' => 'name: p2',
1180
+ 'd2/p3/plugin.yaml' => 'name: p3',
1181
+ 'd2/p1/plugin.yaml' => 'name: p1',
1182
+ }
1183
+ dir = make_dir_tree YAML.load(%q{[[d1, [p1, plugin.yaml], [p2, plugin.yaml]], [d2, [p3, plugin.yaml], [p1, plugin.yaml]]]}), '/tmp', contents
1184
+ exp = %w[d1/p1 d1/p2 d2/p3].map do |d|
1185
+ pdf = File.join d, 'plugin.yaml'
1186
+ data = YAML.load contents[pdf]
1187
+ [File.basename(d).to_sym, Ruber::PluginSpecification.intro(data)]
1188
+ end.to_h
1189
+ dirs = %w[d1 d2].map{|d| File.join dir, d}
1190
+ Ruber::ComponentManager.find_plugins(dirs, true).should == exp
1191
+ FileUtils.rm_r dir
1192
+ end
1193
+
1194
+ end
1195
+
1196
+ describe 'Ruber::ComponentManager.fill_dependencies' do
1197
+
1198
+ it 'should return an empty array if plugins passed as first argument only don\'t have dependencies' do
1199
+ pdfs = [{:name => :p1}, {:name => :p2}, {:name => :p3}, {:name => :p4}].map{|pl| Ruber::PluginSpecification.intro(pl)}
1200
+ Ruber::ComponentManager.fill_dependencies(pdfs[0..1], pdfs).should == []
1201
+ end
1202
+
1203
+ it 'should return an empty array if the plugins passed as first argument only depend on features having the names of other of those plugins' do
1204
+ pdfs = [{:name => :p1, :deps => :p2}, {:name => :p2, :deps => :p3}, {:name => :p3}, {:name => :p4}].map{|pl| Ruber::PluginSpecification.intro(pl)}
1205
+ Ruber::ComponentManager.fill_dependencies(pdfs[0..2], pdfs).should == []
1206
+ end
1207
+
1208
+ it 'should return an emtpy arra if the plugins passed as first argument only depend on features provided by of other plugins in the first argument' do
1209
+ pdfs = [{:name => :p1, :deps => :f2}, {:name => :p2, :deps => :f3, :features => :f2}, {:name => :p3, :features => [:f3, :f5]}, {:name => :p4}].map{|pl| Ruber::PluginSpecification.intro(pl)}
1210
+ Ruber::ComponentManager.fill_dependencies(pdfs[0..2], pdfs).should == []
1211
+ end
1212
+
1213
+ it 'should return an array containing the names of the plugins in the second argument whose name matches dependencies in the first argument not satisfied otherwise' do
1214
+ pdfs = [{:name => :p1, :deps => [:f2, :p4]}, {:name => :p2, :deps => :p3, :features => :f2}, {:name => :p3}, {:name => :p4 }, {:name => :p5 }].map{|pl| Ruber::PluginSpecification.intro(pl)}
1215
+ Ruber::ComponentManager.fill_dependencies(pdfs[0..1], pdfs).map{|i| i.to_s}.should =~ [:p3, :p4].map{|i| i.to_s}
1216
+ end
1217
+
1218
+ it 'should return an array containing the dependencies\' dependencies, if any' do
1219
+ pdfs = [
1220
+ {:name => :p1, :deps => [:f2, :p4]},
1221
+ {:name => :p2, :deps => :p3, :features => :f2},
1222
+ {:name => :p3, :deps => :p6},
1223
+ {:name => :p4, :deps => :p5 },
1224
+ {:name => :p5 },
1225
+ {:name => :p6}
1226
+ ]
1227
+ pdfs.map!{|pl| Ruber::PluginSpecification.intro(pl)}
1228
+ Ruber::ComponentManager.fill_dependencies(pdfs[0..1], pdfs).map{|i| i.to_s}.should =~ [:p3, :p4, :p5, :p6].map{|i| i.to_s}
1229
+ end
1230
+
1231
+ it 'should not add a plugin with the same name of a feature as a dependecy if another plugin already provides that feature' do
1232
+ pdfs = [
1233
+ {:name => :p1, :deps => [:f2, :p4]},
1234
+ {:name => :p2, :deps => :p3, :features => :f2},
1235
+ {:name => :p3, :deps => :p6, :features => :p5},
1236
+ {:name => :p4, :deps => :p5 },
1237
+ {:name => :p5, :deps => :p7 },
1238
+ {:name => :p6},
1239
+ {:name => :p7}
1240
+ ]
1241
+ pdfs.map!{|pl| Ruber::PluginSpecification.intro(pl)}
1242
+ Ruber::ComponentManager.fill_dependencies(pdfs[0..1], pdfs).map{|i| i.to_s}.should =~ [:p3, :p4, :p6].map{|i| i.to_s}
1243
+ end
1244
+
1245
+ it 'should raise UnresolvedDep if some dependencies can\'t be resolved' do
1246
+ pdfs = [
1247
+ {:name => :p1, :deps => [:f2, :p4, :p7]},
1248
+ {:name => :p2, :deps => :p3, :features => :f2},
1249
+ {:name => :p3, :deps => :p6, :features => :p5},
1250
+ {:name => :p4, :deps => :p5 },
1251
+ {:name => :p5, :deps => :p7 },
1252
+ ]
1253
+ pdfs.map!{|pl| Ruber::PluginSpecification.intro(pl)}
1254
+ lambda{Ruber::ComponentManager.fill_dependencies(pdfs[0..1], pdfs)}.should raise_error(Ruber::ComponentManager::UnresolvedDep) do |e|
1255
+ e.missing.should == {:p7 => [:p1], :p6 => [:p3]}
1256
+ end
1257
+ end
1258
+
1259
+ end