cukehead 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.
@@ -0,0 +1,69 @@
1
+ require 'rexml/document'
2
+ require 'cukehead/feature_node'
3
+
4
+ module Cukehead
5
+
6
+ class FreemindReader
7
+
8
+ def initialize(filename = nil)
9
+ @mmdoc = nil
10
+ read_file filename unless filename.nil?
11
+ end
12
+
13
+ # Loads the text from the specified file into a new REXML::Document
14
+ #
15
+ def read_file(filename)
16
+ File.open(filename, "r") {|f| load_xml(f.read)}
17
+ end
18
+
19
+ # Loads the given XML string into a new REXML::Document.
20
+ #
21
+ def load_xml(xml)
22
+ @mmdoc = REXML::Document.new(xml)
23
+ end
24
+
25
+ # Returns the first <i>REXML::Element</i> containing a TEXT attribute
26
+ # that matches 'Cucumber features:'.
27
+ # Returns nil if no match is found.
28
+ #
29
+ def cucumber_features_node
30
+ # XPath is case-sensitive so I'm using the translate function as
31
+ # described in a blog post titled "Performing a Case In-sensitive
32
+ # search in an XML Document" by Harish Ranganathan
33
+ # http://geekswithblogs.net/ranganh/archive/2005/09/12/53520.aspx
34
+ #
35
+ # There may be functions in XPath version 2 that provide a better way
36
+ # to do case-insensitive search but as of this writing REXML only
37
+ # implements XPath 1.0.
38
+ #
39
+ # Because the search is for a specific string, only those characters
40
+ # need translated to lower case.
41
+ REXML::XPath.first(@mmdoc, '//node[translate(attribute::TEXT, "CUMBERFATS", "cumberfats")="cucumber features:"]')
42
+ end
43
+
44
+
45
+ def get_feature_nodes
46
+ node = cucumber_features_node
47
+ feature_nodes = []
48
+ node.each {|e|
49
+ if e.is_a? REXML::Element
50
+ text = e.attributes["TEXT"]
51
+ feature_nodes << FeatureNode.new(e) if text =~ /^feature:.*/i
52
+ end
53
+ } unless node.nil?
54
+ feature_nodes
55
+ end
56
+
57
+
58
+ def get_features
59
+ result = Hash.new
60
+ feature_nodes = get_feature_nodes
61
+ feature_nodes.each {|feature|
62
+ result[feature.filename] = feature.to_text
63
+ }
64
+ result
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,21 @@
1
+ module Cukehead
2
+
3
+ class FreemindWriter
4
+
5
+ def add_newline_after_tags(xml)
6
+ t = ""
7
+ xml.each_char {|c| c == ">" ? t << c + "\n" : t << c}
8
+ return t
9
+ end
10
+
11
+
12
+ def write_mm(filename, mmxml)
13
+ s = add_newline_after_tags mmxml
14
+ File.open(filename, 'w') do |f|
15
+ f.write(s)
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,275 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'cukehead/app'
3
+
4
+ describe "Cukehead application" do
5
+
6
+ before do
7
+ @testdata_dir = File.dirname(__FILE__) + '/../testdata'
8
+ @features_dir = @testdata_dir + '/project1/features'
9
+ File.directory?(@features_dir).should be_true
10
+ File.directory?($testing_tmp).should be_true
11
+ @test_mm_filename = File.join $testing_tmp, 'app_spec_test.mm'
12
+ File.delete @test_mm_filename if File.exists? @test_mm_filename
13
+ File.exists?(@test_mm_filename).should be_false
14
+ @app = Cukehead::App.new
15
+ end
16
+
17
+
18
+ it "reads a set of Cucumber features and create a FreeMind mind map" do
19
+ @app.features_path = @features_dir
20
+ target = @test_mm_filename
21
+ @app.mindmap_filename = target
22
+ @app.send :read_features
23
+ result = @app.send :write_mindmap
24
+ result.should be_true
25
+ File.exists?(target).should be_true
26
+ end
27
+
28
+
29
+ it "looks for feature files in a 'features' sub-directory of the current directory by default" do
30
+ @app.features_path.should eql Dir.getwd + '/features'
31
+ end
32
+
33
+
34
+ it "accepts a features path to specify where to look for feature files" do
35
+ dir = File.join $testing_tmp, 'features'
36
+ @app.features_path = dir
37
+ @app.features_path.should eql dir
38
+ end
39
+
40
+
41
+ it "creates 'cukehead-output.mm' in a 'mm' sub-directory of the current directory by default" do
42
+ @app.mindmap_filename.should eql Dir.getwd + '/mm/cukehead-output.mm'
43
+ end
44
+
45
+
46
+ it "accepts a mind map file name to override the default" do
47
+ @app.mindmap_filename = @test_mm_filename
48
+ @app.mindmap_filename.should eql @test_mm_filename
49
+ end
50
+
51
+
52
+ it "creates the output directory if it does not exist" do
53
+ outdir = File.join $testing_tmp, 'app_test_dir_create'
54
+ FileUtils.remove_dir(outdir) if File.directory? outdir
55
+ File.directory?(outdir).should be_false
56
+ fn = File.join outdir, 'test.mm'
57
+ @app.features_path = @features_dir
58
+ @app.mindmap_filename = fn
59
+ @app.send :read_features
60
+ @app.send :write_mindmap
61
+ File.directory?(outdir).should be_true
62
+ end
63
+
64
+
65
+ it "does not overwrite an existing mind map file" do
66
+ @app.features_path = @features_dir
67
+ target = @test_mm_filename
68
+ @app.mindmap_filename = target
69
+ File.open(target, 'w') {|f| f.write("###")}
70
+ File.exists?(target).should be_true
71
+
72
+ @app.send :read_features
73
+ @app.send :write_mindmap
74
+
75
+ File.open(target, 'r') {|f|
76
+ s = f.readline
77
+ s.should eql "###"
78
+ }
79
+ end
80
+
81
+
82
+ it "accepts an overwrite option that allows it to replace an exiting mind map file" do
83
+ @app.features_path = @features_dir
84
+ target = @test_mm_filename
85
+ @app.mindmap_filename = target
86
+ File.open(target, 'w') {|f| f.write("###")}
87
+ File.exists?(target).should be_true
88
+
89
+ @app.send :read_features
90
+
91
+ @app.do_overwrite = true
92
+ @app.send :write_mindmap
93
+ File.open(target, 'r') {|f|
94
+ s = f.readline
95
+ s.should_not eql "###"
96
+ }
97
+ end
98
+
99
+
100
+ it "accepts an optional file name of an existing mind map" do
101
+ @app.mindmap_template_filename = 'test.mm'
102
+ end
103
+
104
+
105
+ it "adds feature nodes under an existing 'Cucumber features:' node" do
106
+ @app.features_path = @features_dir
107
+ target = File.join $testing_tmp, 'app_spec_insert_test.mm'
108
+ @app.mindmap_filename = target
109
+ source_mm = File.join(@testdata_dir, 'insert_test.mm')
110
+ @app.mindmap_template_filename = source_mm
111
+
112
+ @app.send :read_features
113
+
114
+ @app.do_overwrite = true
115
+ @app.send :write_mindmap
116
+ mm = ''
117
+ File.open(target, 'r') {|f|
118
+ mm = f.readlines
119
+ }
120
+ # *!* Wow. This spec is ugly. There must be a better way.
121
+ doc = REXML::Document.new(mm.join)
122
+ doc.should_not be_nil
123
+ node = REXML::XPath.first(doc, '//node[attribute::TEXT="Here"]')
124
+ node.should_not be_nil
125
+ child = node.elements.first
126
+ child.to_s.should match /.*Cucumber features:.*/
127
+ grandchild = child.elements[2]
128
+ grandchild.to_s.should match /.*Test feature.*/
129
+ end
130
+
131
+
132
+ it "reads files in the features directory in order by name" do
133
+ fdir = File.join $testing_tmp, 'features'
134
+ FileUtils.remove_dir(fdir) if File.directory? fdir
135
+ File.directory?(fdir).should be_false
136
+ FileUtils.mkdir(fdir)
137
+ # This may not be a good test because it is possible Dir[] will return the
138
+ # files in sorted order even though they are written out of order here.
139
+ File.open(File.join(fdir, 'carrots.feature'), 'w') {|f|f.write("Feature: Carrots\n")}
140
+ File.open(File.join(fdir, 'apples.feature'), 'w') {|f|f.write("Feature: Apples\n")}
141
+ File.open(File.join(fdir, 'bananas.feature'), 'w') {|f|f.write("Feature: Bananas\n")}
142
+ @app.features_path = fdir
143
+ @app.send :read_features
144
+ xml = @app.feature_reader.freemind_xml
145
+ xml.should match /.*Apples.*Bananas.*Carrots.*/m
146
+ end
147
+
148
+ end
149
+
150
+
151
+ describe "Cukehead application (generating features from mind map)" do
152
+
153
+ before do
154
+ # Define temporary mind map file and make sure the file does not exist.
155
+ @test_mm_filename = File.join $testing_tmp, 'app_spec_test.mm'
156
+ File.delete @test_mm_filename if File.exists? @test_mm_filename
157
+ File.exists?(@test_mm_filename).should be_false
158
+
159
+ # Define temporary input directory and create test mind map files.
160
+ @in_dir = File.join $testing_tmp, 'app_cuke_test_input'
161
+ FileUtils.mkdir(@in_dir) unless File.directory? @in_dir
162
+ @in_filename_1 = File.join $testing_tmp, 'test1.mm'
163
+ File.open(@in_filename_1, 'w') {|f| f.write($testing_freemind_data)}
164
+ File.exists?(@in_filename_1).should be_true
165
+ @in_filename_2 = File.join $testing_tmp, 'test2.mm'
166
+ File.open(@in_filename_2, 'w') {|f| f.write($testing_freemind_data_2)}
167
+ File.exists?(@in_filename_2).should be_true
168
+
169
+ # Define temporary output directory and make sure it does not exist.
170
+ @out_dir = File.join $testing_tmp, 'app_cuke_test_output'
171
+ FileUtils.remove_dir(@out_dir) if File.directory? @out_dir
172
+ File.directory?(@out_dir).should be_false
173
+
174
+ # Create an app instance to test.
175
+ @app = Cukehead::App.new
176
+ end
177
+
178
+
179
+ it "looks for a single file matching *.mm in a 'mm' subdirectory of the current directory by default" do
180
+ result = @app.send :default_mm_search_path
181
+ result.should eql Dir.getwd + '/mm/*.mm'
182
+ end
183
+
184
+
185
+ it "accepts the name of the mind map file to read overriding the default" do
186
+ # Uses mindmap_filename attribute as source for optional mind map template
187
+ # file in 'map' mode and as destination in 'cuke' mode.
188
+ @app.mindmap_filename = @test_mm_filename
189
+ @app.mindmap_filename.should eql @test_mm_filename
190
+ end
191
+
192
+
193
+ it "creates features in a 'features' sub-directory of the current directory by default" do
194
+ @app.features_path.should eql Dir.getwd + '/features'
195
+ end
196
+
197
+
198
+ it "accepts a features path to specify where to write feature files" do
199
+ # Uses features_path attribute as source directory in 'map' mode and as
200
+ # destination in 'cuke' mode.
201
+ dir = File.join $testing_tmp, 'output', 'features'
202
+ @app.features_path = dir
203
+ @app.features_path.should eql dir
204
+ end
205
+
206
+
207
+ it "creates the output directory if it does not exist" do
208
+ @app.mindmap_filename = @in_filename_1
209
+ @app.features_path = @out_dir
210
+ @app.send :read_mindmap
211
+ @app.send :write_features
212
+
213
+ File.directory?(@out_dir).should be_true
214
+ end
215
+
216
+
217
+ it "writes a file for each feature in the mind map" do
218
+ @app.mindmap_filename = @in_filename_2
219
+ @app.features_path = @out_dir
220
+ @app.send :read_mindmap
221
+ @app.send :write_features
222
+ a = []
223
+ Dir[File.join(@out_dir, '*')].each {|fn| a << File.basename(fn)}
224
+ a.should have(2).files
225
+ a.should include('first_feature.feature', 'second_feature.feature')
226
+ end
227
+
228
+
229
+ it "does not write any files if any one of the files to be written already exists" do
230
+ FileUtils.mkdir(@out_dir)
231
+ fn = File.join @out_dir, 'second_feature.feature'
232
+ File.open(fn, 'w') {|f| f.write('###')}
233
+ File.exists?(fn).should be_true
234
+ @app.mindmap_filename = @in_filename_2
235
+ @app.features_path = @out_dir
236
+ @app.send :read_mindmap
237
+ @app.send :write_features
238
+ @app.errors.should have(1).error
239
+ @app.errors.first.to_s.should match /exists/i
240
+ a = Dir[File.join(@out_dir, '*')]
241
+ a.should have(1).file
242
+ File.open(fn, 'r') {|t| t.readline.should eql '###' }
243
+ end
244
+
245
+
246
+ it "accepts an overwrite option that allows it to replace exiting files" do
247
+ FileUtils.mkdir(@out_dir)
248
+ fn = File.join @out_dir, 'second_feature.feature'
249
+ File.open(fn, 'w') {|f| f.write('###')}
250
+ File.exists?(fn).should be_true
251
+ @app.mindmap_filename = @in_filename_2
252
+ @app.features_path = @out_dir
253
+ @app.do_overwrite = true
254
+ @app.send :read_mindmap
255
+ @app.send :write_features
256
+ @app.errors.should have(0).errors
257
+ a = Dir[File.join(@out_dir, '*')]
258
+ a.should have(2).files
259
+ File.open(fn, 'r') {|t| t.readline.should_not eql '###' }
260
+ end
261
+
262
+ it "returns an error message if no features were found in the mind map" do
263
+ File.open(@test_mm_filename, 'w') {|f| f.write($testing_freemind_data_nocukes)}
264
+ File.exists?(@test_mm_filename).should be_true
265
+ @app.mindmap_filename = @test_mm_filename
266
+ @app.features_path = @out_dir
267
+ @app.do_overwrite = true
268
+ @app.send :read_mindmap
269
+ @app.send :write_features
270
+ @app.errors.should have(2).errors
271
+ @app.errors[0].to_s.should match /No Cucumber features found/
272
+ @app.errors[1].to_s.should match /may be missing a "Cucumber features:" node/
273
+ end
274
+
275
+ end
@@ -0,0 +1,104 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ def run_cukehead(params = '')
4
+ cmdline = params.empty? ? $cukehead_bin : $cukehead_bin + ' ' + params
5
+ puts "DEBUG: run$ #{cmdline}"
6
+ result = `#{cmdline}`
7
+ puts "DEBUG: result='#{result}'"
8
+ result
9
+ end
10
+
11
+
12
+ describe "cukehead" do
13
+
14
+ before do
15
+ @testdata_dir = File.dirname(__FILE__) + '/../testdata'
16
+ @features_dir = @testdata_dir + '/project1/features'
17
+ @source_mm = @testdata_dir + '/project2/mm/testdata.mm'
18
+ File.directory?(@features_dir).should be_true
19
+ File.directory?($testing_tmp).should be_true
20
+ end
21
+
22
+
23
+ it "should run" do
24
+ result = system("#{$cukehead_bin}")
25
+ result.should be_true
26
+ end
27
+
28
+
29
+ it "should display a help message if no command line arguments are given" do
30
+ result = run_cukehead
31
+ result.should match /.*Usage:.*/
32
+ end
33
+
34
+
35
+ it "should include available commands and options in the help message" do
36
+ result = run_cukehead
37
+ result.should match /\bmap\b/
38
+ result.should match /\bcuke\b/
39
+ result.should match /.*--help\b/
40
+ result.should include ' -h '
41
+ result.should match /.*--overwrite\b/
42
+ result.should include ' -o '
43
+ result.should include ' --mm-filename '
44
+ result.should include ' -m '
45
+ result.should include ' --features-path '
46
+ result.should include ' -f '
47
+ result.should include ' --source-mm '
48
+ result.should include ' -s '
49
+ end
50
+
51
+
52
+ it "should display a help message if '--help' command line argument is given" do
53
+ result = run_cukehead '--help'
54
+ result.should match /.*Usage:.*/
55
+ end
56
+
57
+
58
+ it "should read features and create a mind map if 'map' command line argument is given" do
59
+ params = 'map -o'
60
+ params << ' --features-path ' + @features_dir
61
+ params << ' --mm-filename ' + File.join($testing_tmp, 'test-output.mm')
62
+ result = run_cukehead params
63
+ result.should match /^Reading.*Writing.*/m
64
+ end
65
+
66
+
67
+ it "shows an error message and does not overwrite an existing mind map file" do
68
+ output_filename = File.join $testing_tmp, 'cukehead_spec_test.mm'
69
+ File.open(output_filename, 'w') {|f| f.write("###")}
70
+ File.exists?(output_filename).should be_true
71
+
72
+ params = 'map -f ' + @features_dir + ' -m ' + output_filename
73
+ result = run_cukehead params
74
+ result.should match /.*already exists.*/
75
+
76
+ File.open(output_filename, 'r') {|f|
77
+ s = f.readline
78
+ s.should eql "###"
79
+ }
80
+ end
81
+
82
+
83
+ it "complains about unknown command line options." do
84
+ result = run_cukehead '--unknown-option'
85
+ result.should match /.*error.*unrecognized.*/im
86
+ end
87
+
88
+
89
+ it "complains about unknown command line arguments." do
90
+ result = run_cukehead 'UnknownArgument'
91
+ result.should match /.*error.*unknown.*/im
92
+ end
93
+
94
+
95
+ it "should read a mind map and write features if 'cuke' command is given" do
96
+ params = 'cuke -o'
97
+ params << ' --mm-filename ' + @source_mm
98
+ params << ' --features-path ' + File.join($testing_tmp, 'features')
99
+ result = run_cukehead params
100
+ result.should match /^Reading.*Writing.*/m
101
+ end
102
+
103
+
104
+ end