ruber 0.0.1.1

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