sass 3.7.4 → 4.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (257) hide show
  1. checksums.yaml +13 -5
  2. data/.yardopts +1 -1
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/CONTRIBUTING.md +1 -146
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +25 -39
  7. data/Rakefile +274 -0
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/lib/sass.rb +3 -3
  11. data/lib/sass/cache_stores/filesystem.rb +2 -2
  12. data/lib/sass/cache_stores/memory.rb +5 -4
  13. data/lib/sass/callbacks.rb +2 -2
  14. data/lib/sass/css.rb +12 -12
  15. data/lib/sass/engine.rb +44 -62
  16. data/lib/sass/environment.rb +7 -35
  17. data/lib/sass/error.rb +14 -14
  18. data/lib/sass/exec/base.rb +14 -3
  19. data/lib/sass/exec/sass_convert.rb +6 -20
  20. data/lib/sass/exec/sass_scss.rb +29 -5
  21. data/lib/sass/features.rb +2 -3
  22. data/lib/sass/importers/filesystem.rb +6 -11
  23. data/lib/sass/logger.rb +3 -8
  24. data/lib/sass/logger/base.rb +2 -19
  25. data/lib/sass/plugin.rb +2 -3
  26. data/lib/sass/plugin/compiler.rb +67 -48
  27. data/lib/sass/plugin/configuration.rb +3 -3
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/rack.rb +3 -3
  30. data/lib/sass/plugin/staleness_checker.rb +3 -3
  31. data/lib/sass/railtie.rb +1 -1
  32. data/lib/sass/script.rb +3 -3
  33. data/lib/sass/script/css_parser.rb +15 -5
  34. data/lib/sass/script/functions.rb +121 -337
  35. data/lib/sass/script/lexer.rb +36 -102
  36. data/lib/sass/script/parser.rb +153 -529
  37. data/lib/sass/script/tree/funcall.rb +34 -42
  38. data/lib/sass/script/tree/interpolation.rb +26 -171
  39. data/lib/sass/script/tree/list_literal.rb +8 -23
  40. data/lib/sass/script/tree/map_literal.rb +2 -2
  41. data/lib/sass/script/tree/node.rb +3 -3
  42. data/lib/sass/script/tree/operation.rb +16 -43
  43. data/lib/sass/script/tree/string_interpolation.rb +43 -64
  44. data/lib/sass/script/tree/variable.rb +1 -1
  45. data/lib/sass/script/value.rb +0 -2
  46. data/lib/sass/script/value/arg_list.rb +1 -1
  47. data/lib/sass/script/value/base.rb +9 -27
  48. data/lib/sass/script/value/color.rb +18 -26
  49. data/lib/sass/script/value/helpers.rb +18 -44
  50. data/lib/sass/script/value/list.rb +14 -35
  51. data/lib/sass/script/value/map.rb +2 -2
  52. data/lib/sass/script/value/number.rb +16 -26
  53. data/lib/sass/script/value/string.rb +1 -30
  54. data/lib/sass/scss.rb +2 -0
  55. data/lib/sass/scss/css_parser.rb +3 -7
  56. data/lib/sass/scss/parser.rb +78 -196
  57. data/lib/sass/scss/rx.rb +14 -7
  58. data/lib/sass/scss/script_lexer.rb +15 -0
  59. data/lib/sass/scss/script_parser.rb +25 -0
  60. data/lib/sass/scss/static_parser.rb +55 -38
  61. data/lib/sass/selector.rb +10 -7
  62. data/lib/sass/selector/abstract_sequence.rb +12 -15
  63. data/lib/sass/selector/comma_sequence.rb +6 -24
  64. data/lib/sass/selector/pseudo.rb +6 -19
  65. data/lib/sass/selector/sequence.rb +16 -14
  66. data/lib/sass/selector/simple.rb +7 -9
  67. data/lib/sass/selector/simple_sequence.rb +12 -16
  68. data/lib/sass/shared.rb +1 -1
  69. data/lib/sass/source/map.rb +9 -7
  70. data/lib/sass/source/position.rb +4 -4
  71. data/lib/sass/stack.rb +3 -23
  72. data/lib/sass/tree/charset_node.rb +1 -1
  73. data/lib/sass/tree/comment_node.rb +1 -1
  74. data/lib/sass/tree/function_node.rb +3 -2
  75. data/lib/sass/tree/node.rb +3 -5
  76. data/lib/sass/tree/prop_node.rb +58 -49
  77. data/lib/sass/tree/rule_node.rb +8 -15
  78. data/lib/sass/tree/visitors/check_nesting.rb +23 -19
  79. data/lib/sass/tree/visitors/convert.rb +13 -15
  80. data/lib/sass/tree/visitors/cssize.rb +15 -4
  81. data/lib/sass/tree/visitors/deep_copy.rb +2 -2
  82. data/lib/sass/tree/visitors/extend.rb +14 -10
  83. data/lib/sass/tree/visitors/perform.rb +18 -29
  84. data/lib/sass/tree/visitors/set_options.rb +2 -2
  85. data/lib/sass/tree/visitors/to_css.rb +47 -77
  86. data/lib/sass/util.rb +311 -98
  87. data/lib/sass/util/cross_platform_random.rb +19 -0
  88. data/lib/sass/util/multibyte_string_scanner.rb +133 -127
  89. data/lib/sass/util/normalized_map.rb +8 -1
  90. data/lib/sass/util/ordered_hash.rb +192 -0
  91. data/lib/sass/version.rb +6 -2
  92. data/test/sass/cache_test.rb +131 -0
  93. data/test/sass/callbacks_test.rb +61 -0
  94. data/test/sass/compiler_test.rb +236 -0
  95. data/test/sass/conversion_test.rb +2171 -0
  96. data/test/sass/css2sass_test.rb +526 -0
  97. data/test/sass/data/hsl-rgb.txt +319 -0
  98. data/test/sass/encoding_test.rb +219 -0
  99. data/test/sass/engine_test.rb +3400 -0
  100. data/test/sass/exec_test.rb +86 -0
  101. data/test/sass/extend_test.rb +1719 -0
  102. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  103. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  104. data/test/sass/functions_test.rb +1984 -0
  105. data/test/sass/importer_test.rb +421 -0
  106. data/test/sass/logger_test.rb +58 -0
  107. data/test/sass/mock_importer.rb +49 -0
  108. data/test/sass/more_results/more1.css +9 -0
  109. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  110. data/test/sass/more_results/more_import.css +29 -0
  111. data/test/sass/more_templates/_more_partial.sass +2 -0
  112. data/test/sass/more_templates/more1.sass +23 -0
  113. data/test/sass/more_templates/more_import.sass +11 -0
  114. data/test/sass/plugin_test.rb +556 -0
  115. data/test/sass/results/alt.css +4 -0
  116. data/test/sass/results/basic.css +9 -0
  117. data/test/sass/results/cached_import_option.css +3 -0
  118. data/test/sass/results/compact.css +5 -0
  119. data/test/sass/results/complex.css +86 -0
  120. data/test/sass/results/compressed.css +1 -0
  121. data/test/sass/results/expanded.css +19 -0
  122. data/test/sass/results/filename_fn.css +3 -0
  123. data/test/sass/results/if.css +3 -0
  124. data/test/sass/results/import.css +31 -0
  125. data/test/sass/results/import_charset.css +5 -0
  126. data/test/sass/results/import_charset_1_8.css +5 -0
  127. data/test/sass/results/import_charset_ibm866.css +5 -0
  128. data/test/sass/results/import_content.css +1 -0
  129. data/test/sass/results/line_numbers.css +49 -0
  130. data/test/sass/results/mixins.css +95 -0
  131. data/test/sass/results/multiline.css +24 -0
  132. data/test/sass/results/nested.css +22 -0
  133. data/test/sass/results/options.css +1 -0
  134. data/test/sass/results/parent_ref.css +13 -0
  135. data/test/sass/results/script.css +16 -0
  136. data/test/sass/results/scss_import.css +31 -0
  137. data/test/sass/results/scss_importee.css +2 -0
  138. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  139. data/test/sass/results/subdir/subdir.css +3 -0
  140. data/test/sass/results/units.css +11 -0
  141. data/test/sass/results/warn.css +0 -0
  142. data/test/sass/results/warn_imported.css +0 -0
  143. data/test/sass/script_conversion_test.rb +306 -0
  144. data/test/sass/script_test.rb +1206 -0
  145. data/test/sass/scss/css_test.rb +1281 -0
  146. data/test/sass/scss/rx_test.rb +160 -0
  147. data/test/sass/scss/scss_test.rb +4147 -0
  148. data/test/sass/scss/test_helper.rb +37 -0
  149. data/test/sass/source_map_test.rb +1055 -0
  150. data/test/sass/superselector_test.rb +210 -0
  151. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  152. data/test/sass/templates/_double_import_loop2.sass +1 -0
  153. data/test/sass/templates/_filename_fn_import.scss +11 -0
  154. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  155. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  156. data/test/sass/templates/_imported_content.sass +3 -0
  157. data/test/sass/templates/_partial.sass +2 -0
  158. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  159. data/test/sass/templates/alt.sass +16 -0
  160. data/test/sass/templates/basic.sass +23 -0
  161. data/test/sass/templates/bork1.sass +2 -0
  162. data/test/sass/templates/bork2.sass +2 -0
  163. data/test/sass/templates/bork3.sass +2 -0
  164. data/test/sass/templates/bork4.sass +2 -0
  165. data/test/sass/templates/bork5.sass +3 -0
  166. data/test/sass/templates/cached_import_option.scss +3 -0
  167. data/test/sass/templates/compact.sass +17 -0
  168. data/test/sass/templates/complex.sass +305 -0
  169. data/test/sass/templates/compressed.sass +15 -0
  170. data/test/sass/templates/double_import_loop1.sass +1 -0
  171. data/test/sass/templates/expanded.sass +17 -0
  172. data/test/sass/templates/filename_fn.scss +18 -0
  173. data/test/sass/templates/if.sass +11 -0
  174. data/test/sass/templates/import.sass +12 -0
  175. data/test/sass/templates/import_charset.sass +9 -0
  176. data/test/sass/templates/import_charset_1_8.sass +6 -0
  177. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  178. data/test/sass/templates/import_content.sass +4 -0
  179. data/test/sass/templates/importee.less +2 -0
  180. data/test/sass/templates/importee.sass +19 -0
  181. data/test/sass/templates/line_numbers.sass +13 -0
  182. data/test/sass/templates/mixin_bork.sass +5 -0
  183. data/test/sass/templates/mixins.sass +76 -0
  184. data/test/sass/templates/multiline.sass +20 -0
  185. data/test/sass/templates/nested.sass +25 -0
  186. data/test/sass/templates/nested_bork1.sass +2 -0
  187. data/test/sass/templates/nested_bork2.sass +2 -0
  188. data/test/sass/templates/nested_bork3.sass +2 -0
  189. data/test/sass/templates/nested_bork4.sass +2 -0
  190. data/test/sass/templates/nested_import.sass +2 -0
  191. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  192. data/test/sass/templates/options.sass +2 -0
  193. data/test/sass/templates/parent_ref.sass +25 -0
  194. data/test/sass/templates/same_name_different_ext.sass +2 -0
  195. data/test/sass/templates/same_name_different_ext.scss +1 -0
  196. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  197. data/test/sass/templates/script.sass +101 -0
  198. data/test/sass/templates/scss_import.scss +12 -0
  199. data/test/sass/templates/scss_importee.scss +1 -0
  200. data/test/sass/templates/single_import_loop.sass +1 -0
  201. data/test/sass/templates/subdir/import_up1.scss +1 -0
  202. data/test/sass/templates/subdir/import_up2.scss +1 -0
  203. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  204. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  205. data/test/sass/templates/subdir/subdir.sass +6 -0
  206. data/test/sass/templates/units.sass +11 -0
  207. data/test/sass/templates/warn.sass +3 -0
  208. data/test/sass/templates/warn_imported.sass +4 -0
  209. data/test/sass/test_helper.rb +8 -0
  210. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  211. data/test/sass/util/normalized_map_test.rb +51 -0
  212. data/test/sass/util/subset_map_test.rb +91 -0
  213. data/test/sass/util_test.rb +438 -0
  214. data/test/sass/value_helpers_test.rb +179 -0
  215. data/test/test_helper.rb +110 -0
  216. data/vendor/listen/CHANGELOG.md +1 -0
  217. data/vendor/listen/CONTRIBUTING.md +38 -0
  218. data/vendor/listen/Gemfile +20 -0
  219. data/vendor/listen/Guardfile +8 -0
  220. data/vendor/listen/LICENSE +20 -0
  221. data/vendor/listen/README.md +349 -0
  222. data/vendor/listen/Rakefile +5 -0
  223. data/vendor/listen/Vagrantfile +96 -0
  224. data/vendor/listen/lib/listen.rb +54 -0
  225. data/vendor/listen/lib/listen/adapter.rb +327 -0
  226. data/vendor/listen/lib/listen/adapters/bsd.rb +75 -0
  227. data/vendor/listen/lib/listen/adapters/darwin.rb +48 -0
  228. data/vendor/listen/lib/listen/adapters/linux.rb +81 -0
  229. data/vendor/listen/lib/listen/adapters/polling.rb +58 -0
  230. data/vendor/listen/lib/listen/adapters/windows.rb +91 -0
  231. data/vendor/listen/lib/listen/directory_record.rb +406 -0
  232. data/vendor/listen/lib/listen/listener.rb +323 -0
  233. data/vendor/listen/lib/listen/turnstile.rb +32 -0
  234. data/vendor/listen/lib/listen/version.rb +3 -0
  235. data/vendor/listen/listen.gemspec +28 -0
  236. data/vendor/listen/spec/listen/adapter_spec.rb +149 -0
  237. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  238. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  239. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  240. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  241. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  242. data/vendor/listen/spec/listen/directory_record_spec.rb +1250 -0
  243. data/vendor/listen/spec/listen/listener_spec.rb +258 -0
  244. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  245. data/vendor/listen/spec/listen_spec.rb +67 -0
  246. data/vendor/listen/spec/spec_helper.rb +25 -0
  247. data/vendor/listen/spec/support/adapter_helper.rb +666 -0
  248. data/vendor/listen/spec/support/directory_record_helper.rb +57 -0
  249. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  250. data/vendor/listen/spec/support/listeners_helper.rb +179 -0
  251. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  252. metadata +217 -76
  253. data/extra/sass-spec-ref.sh +0 -40
  254. data/lib/sass/deprecation.rb +0 -55
  255. data/lib/sass/logger/delayed.rb +0 -50
  256. data/lib/sass/script/value/callable.rb +0 -25
  257. data/lib/sass/script/value/function.rb +0 -19
@@ -0,0 +1,258 @@
1
+ require 'spec_helper'
2
+
3
+ describe Listen::Listener do
4
+ let(:adapter) { double(Listen::Adapter, :start => true).as_null_object }
5
+ let(:watched_directories) { [File.dirname(__FILE__), File.expand_path('../..', __FILE__)] }
6
+
7
+ before do
8
+ Listen::Adapter.stub(:select_and_initialize) { adapter }
9
+ # Don't build a record of the files inside the base directory.
10
+ Listen::DirectoryRecord.any_instance.stub(:build)
11
+ Kernel.stub(:warn)
12
+ end
13
+ subject { described_class.new(watched_directories) }
14
+
15
+ it_should_behave_like 'a listener to changes on a file-system'
16
+
17
+ describe '#initialize' do
18
+ context 'listening to a single directory' do
19
+ let(:watched_directory) { File.dirname(__FILE__) }
20
+ let(:watched_directories) { nil }
21
+ subject { described_class.new(watched_directory) }
22
+
23
+ it 'sets the directories' do
24
+ subject.directories.should eq [watched_directory]
25
+ end
26
+
27
+ context 'with no options' do
28
+ it 'sets the option for using relative paths in the callback to false' do
29
+ subject.instance_variable_get(:@use_relative_paths).should eq false
30
+ end
31
+ end
32
+
33
+ context 'with :relative_paths => false' do
34
+ it 'sets the option for using relative paths in the callback to false' do
35
+ listener = described_class.new(watched_directory, :relative_paths => false)
36
+ listener.instance_variable_get(:@use_relative_paths).should eq false
37
+ end
38
+ end
39
+
40
+ context 'with :relative_paths => true' do
41
+ it 'sets the option for using relative paths in the callback to true' do
42
+ listener = described_class.new(watched_directory, :relative_paths => true)
43
+ listener.instance_variable_get(:@use_relative_paths).should eq true
44
+ end
45
+ end
46
+ end
47
+
48
+ context 'listening to multiple directories' do
49
+ subject { described_class.new(watched_directories) }
50
+
51
+ it 'sets the directories' do
52
+ subject.directories.should eq watched_directories
53
+ end
54
+
55
+ context 'with no options' do
56
+ it 'sets the option for using relative paths in the callback to false' do
57
+ subject.instance_variable_get(:@use_relative_paths).should eq false
58
+ end
59
+ end
60
+
61
+ context 'with :relative_paths => false' do
62
+ it 'sets the option for using relative paths in the callback to false' do
63
+ listener = described_class.new(watched_directories, :relative_paths => false)
64
+ listener.instance_variable_get(:@use_relative_paths).should eq false
65
+ end
66
+ end
67
+
68
+ context 'with :relative_paths => true' do
69
+ it 'warns' do
70
+ Kernel.should_receive(:warn).with("[Listen warning]: #{Listen::Listener::RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE}")
71
+ listener = described_class.new(watched_directories, :relative_paths => true)
72
+ end
73
+
74
+ it 'sets the option for using relative paths in the callback to false' do
75
+ listener = described_class.new(watched_directories, :relative_paths => true)
76
+ listener.instance_variable_get(:@use_relative_paths).should eq false
77
+ end
78
+ end
79
+ end
80
+
81
+ context 'with a directory' do
82
+ let(:watched_directory) { File.dirname(__FILE__) }
83
+ it 'converts the passed path into an absolute path - #21' do
84
+ described_class.new(File.join(watched_directory, '..')).directories.should eq [File.expand_path('..', watched_directory)]
85
+ end
86
+ end
87
+
88
+ context 'with custom options' do
89
+ let(:watched_directory) { File.dirname(__FILE__) }
90
+ let(:adapter_class) { double('adapter class') }
91
+
92
+ let(:options) do
93
+ {
94
+ :ignore => /\.ssh/, :filter => [/.*\.rb/, /.*\.md/],
95
+ :latency => 0.5, :force_polling => true, :relative_paths => true,
96
+ :force_adapter => adapter_class
97
+ }
98
+ end
99
+ subject { described_class.new(watched_directory, options) }
100
+
101
+ it 'passes the custom ignored paths to the directory record' do
102
+ subject.directories_records.each do |directory_record|
103
+ directory_record.ignoring_patterns.should include /\.ssh/
104
+ end
105
+ end
106
+
107
+ it 'passes the custom filters to the directory record' do
108
+ subject.directories_records.each do |directory_record|
109
+ directory_record.filtering_patterns.should =~ [/.*\.rb/,/.*\.md/]
110
+ end
111
+ end
112
+
113
+ it 'sets adapter_options' do
114
+ subject.instance_variable_get(:@adapter_options).should eq(:latency => 0.5, :force_polling => true, :force_adapter => adapter_class)
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#start' do
120
+ it 'selects and initializes an adapter' do
121
+ Listen::Adapter.should_receive(:select_and_initialize).with(watched_directories, {}) { adapter }
122
+ subject.start
123
+ end
124
+
125
+ it 'builds the directory record' do
126
+ subject.directories_records.each do |directory_record|
127
+ directory_record.should_receive(:build)
128
+ end
129
+ subject.start
130
+ end
131
+ end
132
+
133
+ context 'with a started listener' do
134
+ before do
135
+ subject.stub(:initialize_adapter) { adapter }
136
+ subject.start
137
+ end
138
+
139
+ describe '#unpause' do
140
+ it 'rebuilds the directory record' do
141
+ subject.directories_records.each do |directory_record|
142
+ directory_record.should_receive(:build)
143
+ end
144
+ subject.unpause
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '#ignore'do
150
+ it 'delegates the work to the directory record' do
151
+ subject.directories_records.each do |directory_record|
152
+ directory_record.should_receive(:ignore).with 'some_directory'
153
+ end
154
+ subject.ignore 'some_directory'
155
+ end
156
+ end
157
+
158
+ describe '#ignore!'do
159
+ it 'delegates the work to the directory record' do
160
+ subject.directories_records.each do |directory_record|
161
+ directory_record.should_receive(:ignore!).with 'some_directory'
162
+ end
163
+ subject.ignore! 'some_directory'
164
+ end
165
+ end
166
+
167
+ describe '#filter' do
168
+ it 'delegates the work to the directory record' do
169
+ subject.directories_records.each do |directory_record|
170
+ directory_record.should_receive(:filter).with /\.txt$/
171
+ end
172
+ subject.filter /\.txt$/
173
+ end
174
+ end
175
+
176
+ describe '#filter!' do
177
+ it 'delegates the work to the directory record' do
178
+ subject.directories_records.each do |directory_record|
179
+ directory_record.should_receive(:filter!).with /\.txt$/
180
+ end
181
+ subject.filter! /\.txt$/
182
+ end
183
+ end
184
+
185
+ describe '#on_change' do
186
+ let(:directories) { %w{dir1 dir2 dir3} }
187
+ let(:changes) { {:modified => [], :added => [], :removed => []} }
188
+ let(:callback) { Proc.new { @called = true } }
189
+
190
+ before do
191
+ @called = false
192
+ subject.stub(:fetch_records_changes => changes)
193
+ end
194
+
195
+ it 'fetches the changes of all directories records' do
196
+ subject.unstub(:fetch_records_changes)
197
+
198
+ subject.directories_records.each do |record|
199
+ record.should_receive(:fetch_changes).with(directories, an_instance_of(Hash)).and_return(changes)
200
+ end
201
+ subject.on_change(directories)
202
+ end
203
+
204
+ context "with a callback raising an exception" do
205
+ let(:callback) { Proc.new { raise 'foo' } }
206
+
207
+ before do
208
+ subject.change(&callback)
209
+ subject.stub(:fetch_records_changes => { :modified => ['foo'], :added => [], :removed => [] } )
210
+ end
211
+
212
+ it "stops the adapter and warns" do
213
+ Kernel.should_receive(:warn).with("[Listen warning]: Change block raise an execption: foo")
214
+ Kernel.should_receive(:warn).with(/^Backtrace:.*/)
215
+ subject.on_change(directories)
216
+ end
217
+
218
+ end
219
+
220
+ context 'with no changes to report' do
221
+ if RUBY_VERSION[/^1.8/]
222
+ it 'does not run the callback' do
223
+ subject.change(&callback)
224
+ subject.on_change(directories)
225
+ @called.should be_false
226
+ end
227
+ else
228
+ it 'does not run the callback' do
229
+ callback.should_not_receive(:call)
230
+ subject.change(&callback)
231
+ subject.on_change(directories)
232
+ end
233
+ end
234
+ end
235
+
236
+ context 'with changes to report' do
237
+ let(:changes) do
238
+ {
239
+ :modified => %w{path1}, :added => [], :removed => %w{path2}
240
+ }
241
+ end
242
+
243
+ if RUBY_VERSION[/^1.8/]
244
+ it 'runs the callback passing it the changes' do
245
+ subject.change(&callback)
246
+ subject.on_change(directories)
247
+ @called.should be_true
248
+ end
249
+ else
250
+ it 'runs the callback passing it the changes' do
251
+ callback.should_receive(:call).with(changes[:modified], changes[:added], changes[:removed])
252
+ subject.change(&callback)
253
+ subject.on_change(directories)
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ def run_in_two_threads(proc1, proc2)
4
+ t1 = Thread.new &proc1
5
+ sleep test_latency # t1 must run before t2
6
+ t2 = Thread.new { proc2.call; Thread.kill t1 }
7
+ t2.join(test_latency * 2)
8
+ ensure
9
+ Thread.kill t1 if t1
10
+ Thread.kill t2 if t2
11
+ end
12
+
13
+ describe Listen::Turnstile do
14
+ before { @called = false }
15
+
16
+ describe '#wait' do
17
+ context 'without a signal' do
18
+ it 'blocks one thread indefinitely' do
19
+ run_in_two_threads lambda {
20
+ subject.wait
21
+ @called = true
22
+ }, lambda {
23
+ sleep test_latency
24
+ }
25
+ @called.should be_false
26
+ end
27
+ end
28
+
29
+ context 'with a signal' do
30
+ it 'blocks one thread until it recieves a signal from another thread' do
31
+ run_in_two_threads lambda {
32
+ subject.wait
33
+ @called = true
34
+ }, lambda {
35
+ subject.signal
36
+ sleep test_latency
37
+ }
38
+ @called.should be_true
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#signal' do
44
+ context 'without a wait-call before' do
45
+ it 'does nothing' do
46
+ run_in_two_threads lambda {
47
+ subject.signal
48
+ @called = true
49
+ }, lambda {
50
+ sleep test_latency
51
+ }
52
+ @called.should be_true
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Listen do
4
+ describe '#to' do
5
+ let(:listener) { double(Listen::Listener) }
6
+ let(:listener_class) { Listen::Listener }
7
+ before { listener_class.stub(:new => listener) }
8
+
9
+ context 'with one path to listen to' do
10
+ context 'without options' do
11
+ it 'creates an instance of Listener' do
12
+ listener_class.should_receive(:new).with('/path')
13
+ described_class.to('/path')
14
+ end
15
+ end
16
+
17
+ context 'with options' do
18
+ it 'creates an instance of Listener with the passed params' do
19
+ listener_class.should_receive(:new).with('/path', :filter => '**/*')
20
+ described_class.to('/path', :filter => '**/*')
21
+ end
22
+ end
23
+
24
+ context 'without a block' do
25
+ it 'returns the listener' do
26
+ described_class.to('/path', :filter => '**/*').should eq listener
27
+ end
28
+ end
29
+
30
+ context 'with a block' do
31
+ it 'starts the listener after creating it' do
32
+ listener.should_receive(:start)
33
+ described_class.to('/path', :filter => '**/*') { |modified, added, removed| }
34
+ end
35
+ end
36
+ end
37
+
38
+ context 'with multiple paths to listen to' do
39
+ context 'without options' do
40
+ it 'creates an instance of Listener' do
41
+ listener_class.should_receive(:new).with('path1', 'path2')
42
+ described_class.to('path1', 'path2')
43
+ end
44
+ end
45
+
46
+ context 'with options' do
47
+ it 'creates an instance of Listener with the passed params' do
48
+ listener_class.should_receive(:new).with('path1', 'path2', :filter => '**/*')
49
+ described_class.to('path1', 'path2', :filter => '**/*')
50
+ end
51
+ end
52
+
53
+ context 'without a block' do
54
+ it 'returns a Listener instance created with the passed params' do
55
+ described_class.to('path1', 'path2', :filter => '**/*').should eq listener
56
+ end
57
+ end
58
+
59
+ context 'with a block' do
60
+ it 'starts a Listener instance after creating it with the passed params' do
61
+ listener.should_receive(:start)
62
+ described_class.to('path1', 'path2', :filter => '**/*') { |modified, added, removed| }
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'coveralls'
3
+ Coveralls.wear!
4
+
5
+ require 'listen'
6
+
7
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
8
+
9
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
+ RSpec.configure do |config|
11
+ config.color_enabled = true
12
+ config.order = :random
13
+ config.filter_run :focus => true
14
+ config.treat_symbols_as_metadata_keys_with_true_values = true
15
+ config.run_all_when_everything_filtered = true
16
+ config.filter_run_excluding :broken => true
17
+ config.fail_fast = true
18
+ end
19
+
20
+ def test_latency
21
+ 0.1
22
+ end
23
+
24
+ # Crash loud in tests!
25
+ Thread.abort_on_exception = true
@@ -0,0 +1,666 @@
1
+ # Adapter watch
2
+ #
3
+ # @param [Listen::Listener] listener the adapter listener
4
+ # @param [String] path the path to watch
5
+ #
6
+ def watch(listener, expected_changes, *paths)
7
+ sleep 0.05 # allow file/creation to be done (!)
8
+
9
+ callback = lambda do |changed_directories, options|
10
+ @called = true
11
+ listener.on_change(changed_directories)
12
+ end
13
+ @adapter = Listen::Adapter.select_and_initialize(paths, { :latency => test_latency }, &callback)
14
+ @adapter.stub(:start_poller) { nil }
15
+
16
+ forced_stop = false
17
+ prevent_deadlock = lambda do
18
+ sleep(10)
19
+ puts 'Forcing stop'
20
+ @adapter.stop
21
+ forced_stop = true
22
+ end
23
+
24
+ @adapter.start
25
+
26
+ yield
27
+
28
+ t = Thread.new(&prevent_deadlock)
29
+ @adapter.wait_for_changes(expected_changes)
30
+
31
+ unless forced_stop
32
+ Thread.kill(t)
33
+ @adapter.report_changes
34
+ end
35
+ ensure
36
+ unless forced_stop
37
+ Thread.kill(t) if t
38
+ @adapter.stop
39
+ end
40
+ end
41
+
42
+ shared_examples_for 'a filesystem adapter' do
43
+ subject { described_class.new(File.dirname(__FILE__), &Proc.new {}) }
44
+
45
+ describe '#start' do
46
+ before { Kernel.stub(:warn) }
47
+ after { subject.stop }
48
+
49
+ it 'do not block the current thread after starting the workers' do
50
+ @called = false
51
+ t = Thread.new { subject.start; @called = true }
52
+ sleep(test_latency * 3)
53
+ Thread.kill(t) if t
54
+ @called.should be_true
55
+ end
56
+
57
+ context 'with the blocking hash option set to false' do
58
+ subject { described_class.new(File.dirname(__FILE__), { :blocking => false }, &Proc.new {}) }
59
+
60
+ it 'does not block the current thread after starting the workers' do
61
+ @called = false
62
+ t = Thread.new { subject.start; @called = true }
63
+ sleep(test_latency * 3)
64
+ Thread.kill(t) if t
65
+ @called.should be_true
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#start!' do
71
+ before { Kernel.stub(:warn) }
72
+ after { subject.stop }
73
+
74
+ it 'blocks the current thread after starting the workers' do
75
+ @called = false
76
+ t = Thread.new { subject.start!; @called = true }
77
+ sleep(test_latency * 3)
78
+ Thread.kill(t) if t
79
+ @called.should be_false
80
+ end
81
+
82
+ context 'with the blocking hash option set to false' do
83
+ subject { described_class.new(File.dirname(__FILE__), { :blocking => true }, &Proc.new {}) }
84
+
85
+ it 'blocks the current thread after starting the workers' do
86
+ @called = false
87
+ t = Thread.new { subject.start!; @called = true }
88
+ sleep(test_latency * 3)
89
+ Thread.kill(t) if t
90
+ @called.should be_false
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#started?' do
96
+ context 'with a new adapter' do
97
+ it 'returns false' do
98
+ subject.should_not be_started
99
+ end
100
+ end
101
+
102
+ context 'with a stopped adapter' do
103
+ before { subject.start; subject.stop }
104
+
105
+ it 'returns false' do
106
+ subject.should_not be_started
107
+ end
108
+ end
109
+
110
+ context 'with a started adapter' do
111
+ before { subject.start }
112
+ after { subject.stop }
113
+
114
+ it 'returns true' do
115
+ subject.should be_started
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ shared_examples_for 'an adapter that call properly listener#on_change' do |*args|
122
+ options = (args.first && args.first.is_a?(Hash)) ? args.first : {}
123
+ let(:listener) { double(Listen::Listener) }
124
+ before { described_class.stub(:works?) { true } }
125
+
126
+ context 'single file operations' do
127
+ context 'when a file is created' do
128
+ it 'detects the added file' do
129
+ fixtures do |path|
130
+ listener.should_receive(:on_change).once.with do |array|
131
+ array.should include(path)
132
+ end
133
+
134
+ watch(listener, 1, path) do
135
+ touch 'new_file.rb'
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'given a symlink', :unless => windows? do
141
+ it 'detects the added file' do
142
+ fixtures do |path|
143
+ listener.should_receive(:on_change).once.with do |array|
144
+ array.should include(path)
145
+ end
146
+
147
+ touch 'new_file.rb'
148
+
149
+ watch(listener, 1, path) do
150
+ ln_s 'new_file.rb', 'new_file_symlink.rb'
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ context 'given a new created directory' do
157
+ it 'detects the added file' do
158
+ fixtures do |path|
159
+ listener.should_receive(:on_change).once.with do |array|
160
+ array.should include(path, "#{path}/a_directory")
161
+ end
162
+
163
+ watch(listener, 2, path) do
164
+ mkdir 'a_directory'
165
+ # Needed for INotify, because of :recursive rb-inotify custom flag?
166
+ sleep 0.05
167
+ touch 'a_directory/new_file.rb'
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ context 'given an existing directory' do
174
+ it 'detects the added file' do
175
+ fixtures do |path|
176
+ listener.should_receive(:on_change).once.with do |array|
177
+ array.should include("#{path}/a_directory")
178
+ end
179
+
180
+ mkdir 'a_directory'
181
+
182
+ watch(listener, 1, path) do
183
+ touch 'a_directory/new_file.rb'
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ context 'given a directory with subdirectories' do
190
+ it 'detects the added file' do
191
+ fixtures do |path|
192
+ listener.should_receive(:on_change).once.with do |array|
193
+ array.should include("#{path}/a_directory/subdirectory")
194
+ end
195
+
196
+ mkdir_p 'a_directory/subdirectory'
197
+
198
+ watch(listener, 1, path) do
199
+ touch 'a_directory/subdirectory/new_file.rb'
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ context 'when a file is modified' do
207
+ it 'detects the modified file' do
208
+ fixtures do |path|
209
+ listener.should_receive(:on_change).once.with do |array|
210
+ array.should include(path)
211
+ end
212
+
213
+ touch 'existing_file.txt'
214
+
215
+ watch(listener, 1, path) do
216
+ touch 'existing_file.txt'
217
+ end
218
+ end
219
+ end
220
+
221
+ context 'given a symlink', :unless => windows? do
222
+ it 'detects the modified file' do
223
+ fixtures do |path|
224
+ listener.should_receive(:on_change).once.with do |array|
225
+ array.should include(path)
226
+ end
227
+
228
+ touch 'existing_file.rb'
229
+ ln_s 'existing_file.rb', 'existing_file_symlink.rb'
230
+
231
+ watch(listener, 1, path) do
232
+ touch 'existing_file.rb'
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ context 'given a hidden file' do
239
+ it 'detects the modified file' do
240
+ fixtures do |path|
241
+ listener.should_receive(:on_change).once.with do |array|
242
+ array.should include(path)
243
+ end
244
+
245
+ touch '.hidden'
246
+
247
+ watch(listener, 1, path) do
248
+ touch '.hidden'
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ context 'given a file mode change', :unless => windows? do
255
+ it 'does not detect the mode change' do
256
+ fixtures do |path|
257
+ listener.should_receive(:on_change).once.with do |array|
258
+ array.should include(path)
259
+ end
260
+
261
+ touch 'run.rb'
262
+
263
+ watch(listener, 1, path) do
264
+ chmod 0777, 'run.rb'
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ context 'given an existing directory' do
271
+ it 'detects the modified file' do
272
+ fixtures do |path|
273
+ listener.should_receive(:on_change).once.with do |array|
274
+ array.should include("#{path}/a_directory")
275
+ end
276
+
277
+ mkdir 'a_directory'
278
+ touch 'a_directory/existing_file.txt'
279
+
280
+ watch(listener, 1, path) do
281
+ touch 'a_directory/existing_file.txt'
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ context 'given a directory with subdirectories' do
288
+ it 'detects the modified file' do
289
+ fixtures do |path|
290
+ listener.should_receive(:on_change).once.with do |array|
291
+ array.should include("#{path}/a_directory/subdirectory")
292
+ end
293
+
294
+ mkdir_p 'a_directory/subdirectory'
295
+ touch 'a_directory/subdirectory/existing_file.txt'
296
+
297
+ watch(listener, 1, path) do
298
+ touch 'a_directory/subdirectory/new_file.rb'
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
304
+
305
+ context 'when a file is moved' do
306
+ it 'detects the file move' do
307
+ fixtures do |path|
308
+ listener.should_receive(:on_change).once.with do |array|
309
+ array.should include(path)
310
+ end
311
+
312
+ touch 'move_me.txt'
313
+
314
+ watch(listener, 1, path) do
315
+ mv 'move_me.txt', 'new_name.txt'
316
+ end
317
+ end
318
+ end
319
+
320
+ context 'given a symlink', :unless => windows? do
321
+ it 'detects the file move' do
322
+ fixtures do |path|
323
+ listener.should_receive(:on_change).once.with do |array|
324
+ array.should include(path)
325
+ end
326
+
327
+ touch 'move_me.rb'
328
+ ln_s 'move_me.rb', 'move_me_symlink.rb'
329
+
330
+ watch(listener, 1, path) do
331
+ mv 'move_me_symlink.rb', 'new_symlink.rb'
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ context 'given an existing directory' do
338
+ it 'detects the file move into the directory' do
339
+ fixtures do |path|
340
+ listener.should_receive(:on_change).once.with do |array|
341
+ array.should include(path, "#{path}/a_directory")
342
+ end
343
+
344
+ mkdir 'a_directory'
345
+ touch 'move_me.txt'
346
+
347
+ watch(listener, 2, path) do
348
+ mv 'move_me.txt', 'a_directory/move_me.txt'
349
+ end
350
+ end
351
+ end
352
+
353
+ it 'detects a file move out of the directory' do
354
+ fixtures do |path|
355
+ listener.should_receive(:on_change).once.with do |array|
356
+ array.should include(path, "#{path}/a_directory")
357
+ end
358
+
359
+ mkdir 'a_directory'
360
+ touch 'a_directory/move_me.txt'
361
+
362
+ watch(listener, 2, path) do
363
+ mv 'a_directory/move_me.txt', 'i_am_here.txt'
364
+ end
365
+ end
366
+ end
367
+
368
+ it 'detects a file move between two directories' do
369
+ fixtures do |path|
370
+ listener.should_receive(:on_change).once.with do |array|
371
+ array.should include("#{path}/from_directory", "#{path}/to_directory")
372
+ end
373
+
374
+ mkdir 'from_directory'
375
+ touch 'from_directory/move_me.txt'
376
+ mkdir 'to_directory'
377
+
378
+ watch(listener, 2, path) do
379
+ mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
380
+ end
381
+ end
382
+ end
383
+ end
384
+
385
+ context 'given a directory with subdirectories' do
386
+ it 'detects files movements between subdirectories' do
387
+ fixtures do |path|
388
+ listener.should_receive(:on_change).once.with do |array|
389
+ array.should include("#{path}/a_directory/subdirectory", "#{path}/b_directory/subdirectory")
390
+ end
391
+
392
+ mkdir_p 'a_directory/subdirectory'
393
+ mkdir_p 'b_directory/subdirectory'
394
+ touch 'a_directory/subdirectory/move_me.txt'
395
+
396
+ watch(listener, 2, path) do
397
+ mv 'a_directory/subdirectory/move_me.txt', 'b_directory/subdirectory'
398
+ end
399
+ end
400
+ end
401
+ end
402
+ end
403
+
404
+ context 'when a file is deleted' do
405
+ it 'detects the file removal' do
406
+ fixtures do |path|
407
+ listener.should_receive(:on_change).once.with do |array|
408
+ array.should include(path)
409
+ end
410
+
411
+ touch 'unnecessary.txt'
412
+
413
+ watch(listener, 1, path) do
414
+ rm 'unnecessary.txt'
415
+ end
416
+ end
417
+ end
418
+
419
+ context 'given a symlink', :unless => windows? do
420
+ it 'detects the file removal' do
421
+ fixtures do |path|
422
+ listener.should_receive(:on_change).once.with do |array|
423
+ array.should include(path)
424
+ end
425
+
426
+ touch 'unnecessary.rb'
427
+ ln_s 'unnecessary.rb', 'unnecessary_symlink.rb'
428
+
429
+ watch(listener, 1, path) do
430
+ rm 'unnecessary_symlink.rb'
431
+ end
432
+ end
433
+ end
434
+ end
435
+
436
+ context 'given an existing directory' do
437
+ it 'detects the file removal' do
438
+ fixtures do |path|
439
+ listener.should_receive(:on_change).once.with do |array|
440
+ array.should include("#{path}/a_directory")
441
+ end
442
+
443
+ mkdir 'a_directory'
444
+ touch 'a_directory/do_not_use.rb'
445
+
446
+ watch(listener, 1, path) do
447
+ rm 'a_directory/do_not_use.rb'
448
+ end
449
+ end
450
+ end
451
+ end
452
+
453
+ context 'given a directory with subdirectories' do
454
+ it 'detects the file removal' do
455
+ fixtures do |path|
456
+ listener.should_receive(:on_change).once.with do |array|
457
+ array.should include("#{path}/a_directory/subdirectory")
458
+ end
459
+
460
+ mkdir_p 'a_directory/subdirectory'
461
+ touch 'a_directory/subdirectory/do_not_use.rb'
462
+
463
+ watch(listener, 1, path) do
464
+ rm 'a_directory/subdirectory/do_not_use.rb'
465
+ end
466
+ end
467
+ end
468
+ end
469
+ end
470
+ end
471
+
472
+ context 'multiple file operations' do
473
+ it 'detects the added files' do
474
+ fixtures do |path|
475
+ listener.should_receive(:on_change).once.with do |array|
476
+ array.should include(path, "#{path}/a_directory")
477
+ end
478
+
479
+ watch(listener, 2, path) do
480
+ touch 'a_file.rb'
481
+ touch 'b_file.rb'
482
+ mkdir 'a_directory'
483
+ # Needed for INotify, because of :recursive rb-inotify custom flag?
484
+ # Also needed for the osx adapter
485
+ sleep 0.05
486
+ touch 'a_directory/a_file.rb'
487
+ touch 'a_directory/b_file.rb'
488
+ end
489
+ end
490
+ end
491
+
492
+ it 'detects the modified files' do
493
+ fixtures do |path|
494
+ listener.should_receive(:on_change).once.with do |array|
495
+ array.should include(path, "#{path}/a_directory")
496
+ end
497
+
498
+ touch 'a_file.rb'
499
+ touch 'b_file.rb'
500
+ mkdir 'a_directory'
501
+ touch 'a_directory/a_file.rb'
502
+ touch 'a_directory/b_file.rb'
503
+
504
+ watch(listener, 2, path) do
505
+ touch 'b_file.rb'
506
+ touch 'a_directory/a_file.rb'
507
+ end
508
+ end
509
+ end
510
+
511
+ it 'detects the removed files' do
512
+ fixtures do |path|
513
+ listener.should_receive(:on_change).once.with do |array|
514
+ array.should include(path, "#{path}/a_directory")
515
+ end
516
+
517
+ touch 'a_file.rb'
518
+ touch 'b_file.rb'
519
+ mkdir 'a_directory'
520
+ touch 'a_directory/a_file.rb'
521
+ touch 'a_directory/b_file.rb'
522
+
523
+ watch(listener, 2, path) do
524
+ rm 'b_file.rb'
525
+ rm 'a_directory/a_file.rb'
526
+ end
527
+ end
528
+ end
529
+ end
530
+
531
+ context 'single directory operations' do
532
+ it 'detects a moved directory' do
533
+ fixtures do |path|
534
+ listener.should_receive(:on_change).once.with do |array|
535
+ array.should include(path)
536
+ end
537
+
538
+ mkdir 'a_directory'
539
+ touch 'a_directory/a_file.rb'
540
+ touch 'a_directory/b_file.rb'
541
+
542
+ watch(listener, 1, path) do
543
+ mv 'a_directory', 'renamed'
544
+ end
545
+ end
546
+ end
547
+
548
+ it 'detects a removed directory' do
549
+ fixtures do |path|
550
+ listener.should_receive(:on_change).once.with do |array|
551
+ array.should include(path, "#{path}/a_directory")
552
+ end
553
+
554
+ mkdir 'a_directory'
555
+ touch 'a_directory/a_file.rb'
556
+ touch 'a_directory/b_file.rb'
557
+
558
+ watch(listener, 2, path) do
559
+ rm_rf 'a_directory'
560
+ end
561
+ end
562
+ end
563
+ end
564
+
565
+ context "paused adapter" do
566
+ context 'when a file is created' do
567
+ it "doesn't detects the added file" do
568
+ fixtures do |path|
569
+ watch(listener, 1, path) do # The expected changes param is set to one!
570
+ @adapter.paused = true
571
+ touch 'new_file.rb'
572
+ end
573
+ @called.should be_nil
574
+ end
575
+ end
576
+ end
577
+ end
578
+
579
+ context "when multiple directories are listened to" do
580
+ context 'when files are added to one of multiple directories' do
581
+ it 'detects added files' do
582
+ fixtures(2) do |path1, path2|
583
+ listener.should_receive(:on_change).once.with do |array|
584
+ array.should include(path2)
585
+ end
586
+
587
+ watch(listener, 1, path1, path2) do
588
+ touch "#{path2}/new_file.rb"
589
+ end
590
+ end
591
+ end
592
+ end
593
+
594
+ context 'when files are added to multiple directories' do
595
+ it 'detects added files' do
596
+ fixtures(2) do |path1, path2|
597
+ listener.should_receive(:on_change).once.with do |array|
598
+ array.should include(path1, path2)
599
+ end
600
+
601
+ watch(listener, 2, path1, path2) do
602
+ touch "#{path1}/new_file.rb"
603
+ touch "#{path2}/new_file.rb"
604
+ end
605
+ end
606
+ end
607
+ end
608
+
609
+ context 'given a new and an existing directory on multiple directories' do
610
+ it 'detects the added file' do
611
+ fixtures(2) do |path1, path2|
612
+ listener.should_receive(:on_change).once.with do |array|
613
+ array.should include(path2, "#{path1}/a_directory", "#{path2}/b_directory")
614
+ end
615
+
616
+ mkdir "#{path1}/a_directory"
617
+
618
+ watch(listener, 3, path1, path2) do
619
+ mkdir "#{path2}/b_directory"
620
+ # Needed for INotify
621
+ sleep 0.05
622
+ touch "#{path1}/a_directory/new_file.rb"
623
+ touch "#{path2}/b_directory/new_file.rb"
624
+ end
625
+ end
626
+ end
627
+ end
628
+
629
+ context 'when a file is moved between the multiple watched directories' do
630
+ it 'detects the movements of the file' do
631
+ fixtures(3) do |path1, path2, path3|
632
+ listener.should_receive(:on_change).once.with do |array|
633
+ array.should include("#{path1}/from_directory", path2, "#{path3}/to_directory")
634
+ end
635
+
636
+ mkdir "#{path1}/from_directory"
637
+ touch "#{path1}/from_directory/move_me.txt"
638
+ mkdir "#{path3}/to_directory"
639
+
640
+ watch(listener, 3, path1, path2, path3) do
641
+ mv "#{path1}/from_directory/move_me.txt", "#{path2}/move_me.txt"
642
+ mv "#{path2}/move_me.txt", "#{path3}/to_directory/move_me.txt"
643
+ end
644
+ end
645
+ end
646
+ end
647
+
648
+ context 'when files are deleted from the multiple watched directories' do
649
+ it 'detects the files removal' do
650
+ fixtures(2) do |path1, path2|
651
+ listener.should_receive(:on_change).once.with do |array|
652
+ array.should include(path1, path2)
653
+ end
654
+
655
+ touch "#{path1}/unnecessary.txt"
656
+ touch "#{path2}/unnecessary.txt"
657
+
658
+ watch(listener, 2, path1, path2) do
659
+ rm "#{path1}/unnecessary.txt"
660
+ rm "#{path2}/unnecessary.txt"
661
+ end
662
+ end
663
+ end
664
+ end
665
+ end
666
+ end