cukecooker 0.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.
- data/README.rdoc +24 -0
- data/bin/cukecooker +581 -0
- metadata +67 -0
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
== Cukecooker
|
2
|
+
|
3
|
+
Cukecooker aids you in writing Cucumber scenarios.
|
4
|
+
|
5
|
+
It generates an HTML with your project's step definitions and lets you
|
6
|
+
use them to write scenarios in a comfortable way, with autocomplete
|
7
|
+
and text inputs for group matches.
|
8
|
+
|
9
|
+
=== Usage
|
10
|
+
|
11
|
+
In your project root, which should contain a features folder, run this
|
12
|
+
in a terminal:
|
13
|
+
|
14
|
+
cukecooker
|
15
|
+
|
16
|
+
Or:
|
17
|
+
|
18
|
+
cukecooker path/to/your/project
|
19
|
+
|
20
|
+
If everything went well you should see
|
21
|
+
|
22
|
+
Done! Now open cukecooker-project.html in a browser.
|
23
|
+
|
24
|
+
Go ahead, try it!
|
data/bin/cukecooker
ADDED
@@ -0,0 +1,581 @@
|
|
1
|
+
StepRegex = %r(\s*(?:When|Given|Then).*/\^(.+)\$\/\s*do(\s*\|.*\|)?)
|
2
|
+
|
3
|
+
steps = ""
|
4
|
+
|
5
|
+
# Checks that dir exists and is a directory
|
6
|
+
def check_dir(dir)
|
7
|
+
unless File.exists?(dir)
|
8
|
+
puts "Error: #{dir} does not exist"
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
|
12
|
+
unless File.directory?(dir)
|
13
|
+
puts "Error: #{dir} is not a directory"
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get command line directory or default to current
|
19
|
+
dir = ARGV[0] || "."
|
20
|
+
dir = File.expand_path(dir)
|
21
|
+
check_dir dir
|
22
|
+
|
23
|
+
# Asume project name is the name of the given directory.
|
24
|
+
# If it's src, go one level up
|
25
|
+
name = File.basename dir
|
26
|
+
if name == 'src'
|
27
|
+
name = File.basename File.dirname(dir)
|
28
|
+
end
|
29
|
+
|
30
|
+
dir = "#{dir}/features/step_definitions"
|
31
|
+
check_dir dir
|
32
|
+
|
33
|
+
step_files = Dir["#{dir}/**/*.rb"]
|
34
|
+
step_files.each do |step_file|
|
35
|
+
lines = File.readlines step_file
|
36
|
+
lines.each do |line|
|
37
|
+
next unless match = StepRegex.match(line)
|
38
|
+
|
39
|
+
regexp = match[1]
|
40
|
+
next if regexp.empty?
|
41
|
+
|
42
|
+
steps << "," unless steps.empty?
|
43
|
+
if match[2]
|
44
|
+
parameters = match[2].strip[1 ... -1].split(',').map!{|x| "'#{x.strip.gsub('_', ' ')}'"}.join(',') if match[2]
|
45
|
+
steps << "[/^#{regexp}$/, [#{parameters}]]"
|
46
|
+
else
|
47
|
+
steps << "[/^#{regexp}$/]"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if steps.empty?
|
53
|
+
puts "Error: no step definitions found in #{dir}/features/step_definitions"
|
54
|
+
puts " Be sure to run cukecooker in a directory which contains a features/step_definitions directory"
|
55
|
+
puts " or give cukecooker the path to that directory."
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
|
59
|
+
OrRegexp = '/\(\?\:.*?\|.*?\)/'
|
60
|
+
OptionalRegexp = '/.\?/'
|
61
|
+
OptionalWithParenRegexp = '/\(\?\:.*?\)\?/'
|
62
|
+
CaptureRegepx = '/\(.+?\)/'
|
63
|
+
CaptureWithQuotesRegepx = '/"\(.+?\)"/'
|
64
|
+
RegexpRegexp = '/\\\\\//'
|
65
|
+
|
66
|
+
File.open("cukecooker-#{name}.html", "w") do |file|
|
67
|
+
file.write <<EOF
|
68
|
+
<html>
|
69
|
+
<head>
|
70
|
+
<title>cukecooker - #{name}</title>
|
71
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" type="text/javascript"></script>
|
72
|
+
<script type="text/javascript">
|
73
|
+
// The original step definitions, extracted from the step definition files.
|
74
|
+
// Each element in the array is an array whose first element is the regular expression
|
75
|
+
// and the next element is an (optional) array containing the parameters given in the do block.
|
76
|
+
originalSteps = [#{steps}];
|
77
|
+
|
78
|
+
// The expanded steps (splitted by ors or optional regexps).
|
79
|
+
// Each element in the array is a hash with the following properties:
|
80
|
+
// regexp: the regular expression to deal with
|
81
|
+
// params: an array of parameters that were given in the do block
|
82
|
+
// regexpReplaced: the regular expression with groups replaced with params
|
83
|
+
steps = [];
|
84
|
+
|
85
|
+
// The index of the selected step.
|
86
|
+
selectedStepIndex = 0;
|
87
|
+
|
88
|
+
// The indices of the li's that are shown.
|
89
|
+
liIndices = [];
|
90
|
+
|
91
|
+
// The selected step once the user pressed enter.
|
92
|
+
buildingStep = null;
|
93
|
+
|
94
|
+
// The current action entered by the user
|
95
|
+
currentAction = "Given ";
|
96
|
+
|
97
|
+
// The last action entered by the user
|
98
|
+
lastAction = '';
|
99
|
+
|
100
|
+
// Is the current action different than the last action?
|
101
|
+
lastActionChanged = false;
|
102
|
+
|
103
|
+
// The text that was in #stepMatch before a keyup event was fired
|
104
|
+
lastStepMatchText = '';
|
105
|
+
|
106
|
+
// Is the user creating the first step in the scenario?
|
107
|
+
firstStepInScenario = true;
|
108
|
+
|
109
|
+
allLiIndices = [];
|
110
|
+
|
111
|
+
// HACK: keyup event if fired twice... why? :'-(
|
112
|
+
justBuiltAStep = false;
|
113
|
+
|
114
|
+
$(function() {
|
115
|
+
$steps = $("#steps");
|
116
|
+
$stepBuilder = $("#stepBuilder");
|
117
|
+
$scenario = $("#scenario");
|
118
|
+
$stepMatch = $("#stepMatch");
|
119
|
+
$stepMatch_static = $("#stepMatch_static");
|
120
|
+
$explanationStep = $("#explanationStep");
|
121
|
+
$explanationStepBuilder = $("#explanationStepBuilder");
|
122
|
+
|
123
|
+
// Push all elements into array
|
124
|
+
function pushAll(array, elements) {
|
125
|
+
for(var i = 0; i < elements.length; i++)
|
126
|
+
array.push(elements[i]);
|
127
|
+
}
|
128
|
+
|
129
|
+
// Split the regexp into their expansions.
|
130
|
+
// For example:
|
131
|
+
// "(?:a|b) c" --> ["a c", "b c"]
|
132
|
+
// "(?:a )?b" --> ["a b", "b"]
|
133
|
+
// "(?:a )?(?:b|c) d" --> ["a b d", "a c d", "b d", "c d"]
|
134
|
+
function splitRegexp(regexp) {
|
135
|
+
var results = [];
|
136
|
+
|
137
|
+
// Find an OR expression
|
138
|
+
var match = regexp.match(#{OrRegexp});
|
139
|
+
if (match) {
|
140
|
+
match = match[0];
|
141
|
+
var idx = regexp.indexOf(match);
|
142
|
+
// Remove (?: ... )
|
143
|
+
match = match.substring(3, match.length - 1);
|
144
|
+
var pieces = match.split("\|");
|
145
|
+
for(var i = 0; i < pieces.length; i++) {
|
146
|
+
var before = regexp.substring(0, idx);
|
147
|
+
var after = regexp.substring(idx + match.length + 4);
|
148
|
+
var newRegexp = before + pieces[i] + after;
|
149
|
+
pushAll(results, splitRegexp(newRegexp));
|
150
|
+
}
|
151
|
+
return results;
|
152
|
+
}
|
153
|
+
|
154
|
+
// Find optional expression with parenthesis
|
155
|
+
match = regexp.match(#{OptionalWithParenRegexp});
|
156
|
+
if (match) {
|
157
|
+
match = match[0];
|
158
|
+
var idx = regexp.indexOf(match);
|
159
|
+
var before = regexp.substring(0, idx);
|
160
|
+
var after = regexp.substring(idx + match.length);
|
161
|
+
// This is without the surrounding (?: ... )?
|
162
|
+
var middle = match.substring(3, match.length - 2);
|
163
|
+
var regexpWithout = before + after;
|
164
|
+
var regexpWith = before + middle + after;
|
165
|
+
pushAll(results, splitRegexp(regexpWithout));
|
166
|
+
pushAll(results, splitRegexp(regexpWith));
|
167
|
+
return results;
|
168
|
+
}
|
169
|
+
|
170
|
+
// Find optional expression without parenthesis
|
171
|
+
match = regexp.match(#{OptionalRegexp});
|
172
|
+
if (match) {
|
173
|
+
match = match[0];
|
174
|
+
var idx = regexp.indexOf(match);
|
175
|
+
var before = regexp.substring(0, idx);
|
176
|
+
var after = regexp.substring(idx + match.length);
|
177
|
+
// This is without the ?
|
178
|
+
var middle = match.substring(0, match.length - 1);
|
179
|
+
var regexpWithout = before + after;
|
180
|
+
var regexpWith = before + middle + after;
|
181
|
+
pushAll(results, splitRegexp(regexpWithout));
|
182
|
+
pushAll(results, splitRegexp(regexpWith));
|
183
|
+
return results;
|
184
|
+
}
|
185
|
+
|
186
|
+
// Replace \/ with /
|
187
|
+
while(regexp.indexOf("\\\\/") >= 0) {
|
188
|
+
regexp = regexp.replace(#{RegexpRegexp}, "/");
|
189
|
+
}
|
190
|
+
|
191
|
+
return [regexp];
|
192
|
+
}
|
193
|
+
|
194
|
+
// Processes each group in the step regexp with the given callback. The callback
|
195
|
+
// receives the index of the matched group and must return a replacement
|
196
|
+
// for it.
|
197
|
+
function processGroups(step, callback) {
|
198
|
+
paramIdx = 0;
|
199
|
+
regexp = step.regexp;
|
200
|
+
while(true) {
|
201
|
+
m = regexp.match(#{CaptureRegepx});
|
202
|
+
if (!m) break;
|
203
|
+
|
204
|
+
m = m[0];
|
205
|
+
idx = regexp.indexOf(m);
|
206
|
+
regexp = regexp.substring(0, idx) + callback(paramIdx) + regexp.substring(idx + m.length);
|
207
|
+
paramIdx++;
|
208
|
+
}
|
209
|
+
return regexp;
|
210
|
+
}
|
211
|
+
|
212
|
+
// Transforms 'foo "(...)" bar' into 'foo (...) bar'
|
213
|
+
function removeQuotedGroups(step) {
|
214
|
+
regexp = step.regexp;
|
215
|
+
while(true) {
|
216
|
+
m = regexp.match(#{CaptureWithQuotesRegepx});
|
217
|
+
if (!m) break;
|
218
|
+
|
219
|
+
m = m[0];
|
220
|
+
idx = regexp.indexOf(m);
|
221
|
+
regexp = regexp.substring(0, idx) + m.substring(1, m.length -1) + regexp.substring(idx + m.length);
|
222
|
+
}
|
223
|
+
return regexp;
|
224
|
+
}
|
225
|
+
|
226
|
+
// Appends the step just built to the scenario div.
|
227
|
+
function appendToScenario() {
|
228
|
+
replacement = processGroups(buildingStep, function(idx) {
|
229
|
+
return '<span class="param">' + $("#p" + idx).val() + '</span>';
|
230
|
+
});
|
231
|
+
replacement = "<strong>" + currentAction + "</strong>" + replacement;
|
232
|
+
if (currentAction == "And ") {
|
233
|
+
replacement = " " + replacement;
|
234
|
+
} else if (lastActionChanged && !firstStepInScenario) {
|
235
|
+
replacement = "<br/>" + replacement;
|
236
|
+
}
|
237
|
+
regexp = buildingStep.regexp;
|
238
|
+
if (regexp[regexp.length - 1] == ':') {
|
239
|
+
if (currentAction == "And ") {
|
240
|
+
replacement += '<br/> """<br/> TODO: text or table goes here <br/> """';
|
241
|
+
} else {
|
242
|
+
replacement += '<br/>"""<br/>TODO: text or table goes here<br/>"""';
|
243
|
+
}
|
244
|
+
}
|
245
|
+
firstStepInScenario = false;
|
246
|
+
justBuiltAStep = true;
|
247
|
+
$scenario.append(replacement + "<br/>");
|
248
|
+
}
|
249
|
+
|
250
|
+
// Prepares the page for building steps (input texts for parameters)
|
251
|
+
function prepareBuildStep() {
|
252
|
+
buildingStep = steps[currentLiIndex()];
|
253
|
+
|
254
|
+
// Set the current action and see if it changed from the last one
|
255
|
+
if (lastAction == currentAction && currentAction != "And ") {
|
256
|
+
currentAction = "And ";
|
257
|
+
lastActionChanged = false;
|
258
|
+
} else {
|
259
|
+
lastActionChanged = true;
|
260
|
+
}
|
261
|
+
lastAction = currentAction;
|
262
|
+
|
263
|
+
groupsCount = 0;
|
264
|
+
processGroups(buildingStep, function(idx) {
|
265
|
+
groupsCount++;
|
266
|
+
return '';
|
267
|
+
});
|
268
|
+
|
269
|
+
if (groupsCount == 0) {
|
270
|
+
appendToScenario();
|
271
|
+
searchAgain();
|
272
|
+
} else {
|
273
|
+
$explanationStep.hide();
|
274
|
+
$explanationStepBuilder.show();
|
275
|
+
$stepMatch.attr('readonly', 'readonly');
|
276
|
+
$stepMatch.val(currentAction + buildingStep.regexpReplaced);
|
277
|
+
$steps.hide();
|
278
|
+
$stepBuilder.show();
|
279
|
+
|
280
|
+
regexpWithoutQuotes = removeQuotedGroups(buildingStep);
|
281
|
+
originalRegexp = buildingStep.regexp;
|
282
|
+
buildingStep.regexp = regexpWithoutQuotes;
|
283
|
+
|
284
|
+
html = '<table><tr><td><nobr><strong>' + currentAction + "</strong>";
|
285
|
+
html += processGroups(buildingStep, function(idx) {
|
286
|
+
return '</nobr></td><td width="100"><input type="text" id="p' + idx + '" class="complete"></td><td><nobr>';
|
287
|
+
});
|
288
|
+
html += '</td></tr><tr align="center"><td>';
|
289
|
+
for(var i = 0; i < groupsCount; i++) {
|
290
|
+
html += '</td><td class="param">' + buildingStep.params[i] + "</td><td>";
|
291
|
+
}
|
292
|
+
html += "</td></tr></table>";
|
293
|
+
$stepBuilder.html(html);
|
294
|
+
|
295
|
+
$("#p0").focus();
|
296
|
+
|
297
|
+
buildingStep.regexp = originalRegexp;
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
// Resets everything except the scenario div to search a new step.
|
302
|
+
function searchAgain() {
|
303
|
+
selectedStepIndex = 0;
|
304
|
+
liIndices = allLiIndices;
|
305
|
+
$explanationStep.show();
|
306
|
+
$explanationStepBuilder.hide();
|
307
|
+
$stepMatch.attr('readonly', '');
|
308
|
+
$steps.show();
|
309
|
+
$stepBuilder.hide();
|
310
|
+
$stepsLi.show();
|
311
|
+
$stepsLi.removeClass("selected");
|
312
|
+
stepLi(0).addClass("selected");
|
313
|
+
$stepMatch.val("");
|
314
|
+
$stepMatch.focus();
|
315
|
+
$steps.scrollTop(0);
|
316
|
+
}
|
317
|
+
|
318
|
+
// Returns the index of the currently selected <li>
|
319
|
+
function currentLiIndex() {
|
320
|
+
return liIndices[selectedStepIndex];
|
321
|
+
}
|
322
|
+
|
323
|
+
// Returns the selected <li> as a jQuery object
|
324
|
+
function currentLi() {
|
325
|
+
return $($stepsLi[currentLiIndex()]);
|
326
|
+
}
|
327
|
+
|
328
|
+
// Returns a step <li> as a jQuery object
|
329
|
+
function stepLi(idx) {
|
330
|
+
return $($stepsLi[idx]);
|
331
|
+
}
|
332
|
+
|
333
|
+
// Split original steps and create the steps array
|
334
|
+
for(var i = 0; i < originalSteps.length; i++) {
|
335
|
+
originalStep = originalSteps[i];
|
336
|
+
step = originalStep[0].toString();
|
337
|
+
// Remove /^ and $/
|
338
|
+
s = step.substring(2, step.length - 2);
|
339
|
+
splits = splitRegexp(s);
|
340
|
+
for(var j = 0; j < splits.length; j++) {
|
341
|
+
split = splits[j];
|
342
|
+
|
343
|
+
newStep = {}
|
344
|
+
newStep.regexp = split
|
345
|
+
newStep.params = originalStep.length == 1 ? [] : originalStep[1]
|
346
|
+
newStep.regexpReplaced = processGroups(newStep, function(idx) {
|
347
|
+
return newStep.params[idx];
|
348
|
+
});
|
349
|
+
|
350
|
+
steps.push(newStep);
|
351
|
+
|
352
|
+
allLiIndices.push(allLiIndices.length);
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
liIndices = allLiIndices;
|
357
|
+
|
358
|
+
// Sort steps according to regexps, alphabetically
|
359
|
+
steps.sort(function(a, b) {
|
360
|
+
x = a.regexp.toLowerCase();
|
361
|
+
y = b.regexp.toLowerCase();
|
362
|
+
return x < y ? -1 : (x > y ? 1 : 0);
|
363
|
+
});
|
364
|
+
|
365
|
+
// Write the steps in the <li>s
|
366
|
+
for(var i = 0; i < steps.length; i++) {
|
367
|
+
step = steps[i];
|
368
|
+
replacement = processGroups(step, function(idx) {
|
369
|
+
return '<span class="param">' + step.params[idx] + '</span>';
|
370
|
+
});
|
371
|
+
$steps.append("<li>" + replacement + "</li>");
|
372
|
+
}
|
373
|
+
|
374
|
+
$stepsLi = $steps.find("li");
|
375
|
+
|
376
|
+
// Highlight the first selected step
|
377
|
+
stepLi(0).addClass("selected");
|
378
|
+
|
379
|
+
// Focus the text input to write the match
|
380
|
+
$stepMatch.focus();
|
381
|
+
|
382
|
+
// When pressing a key in the step match input, filter the steps
|
383
|
+
$stepMatch.keyup(function(ev) {
|
384
|
+
if (justBuiltAStep) {
|
385
|
+
justBuiltAStep = false;
|
386
|
+
return;
|
387
|
+
}
|
388
|
+
|
389
|
+
text = $stepMatch.val();
|
390
|
+
|
391
|
+
// If the text didn't change and it's not enter, do nothing
|
392
|
+
if (text == lastStepMatchText && ev.keyCode != 13)
|
393
|
+
return true;
|
394
|
+
|
395
|
+
// These are the arrow keys, home, end, etc. We can ignore them.
|
396
|
+
if (ev.keyCode >= 33 && ev.keyCode <= 40) {
|
397
|
+
return true;
|
398
|
+
}
|
399
|
+
|
400
|
+
lastStepMatchText = text;
|
401
|
+
|
402
|
+
// If the user pressed enter, selected the step
|
403
|
+
if (ev.keyCode == 13) {
|
404
|
+
prepareBuildStep();
|
405
|
+
return false;
|
406
|
+
}
|
407
|
+
|
408
|
+
// Unselect the current step
|
409
|
+
currentLi().removeClass("selected");
|
410
|
+
|
411
|
+
// See if we the text starts with an action and remove it
|
412
|
+
foundMatch = false;
|
413
|
+
if (text.match(/^when /i)) {
|
414
|
+
text = text.substring(5);
|
415
|
+
currentAction = "When ";
|
416
|
+
}
|
417
|
+
if (text.match(/^then /i)) {
|
418
|
+
text = text.substring(5);
|
419
|
+
currentAction = "Then ";
|
420
|
+
}
|
421
|
+
if (text.match(/^given /i)) {
|
422
|
+
text = text.substring(6);
|
423
|
+
currentAction = "Given ";
|
424
|
+
}
|
425
|
+
if (text.match(/^and /i)) {
|
426
|
+
text = text.substring(4);
|
427
|
+
currentAction = "And ";
|
428
|
+
}
|
429
|
+
|
430
|
+
// Do the filtering
|
431
|
+
text = eval("/^" + text + "/i");
|
432
|
+
liIndices = [];
|
433
|
+
|
434
|
+
selectedStepIndex = -1;
|
435
|
+
for(var i = 0; i < steps.length; i++) {
|
436
|
+
step = steps[i];
|
437
|
+
$stepLi = stepLi(i);
|
438
|
+
if (step.regexpReplaced.match(text)) {
|
439
|
+
$stepLi.show();
|
440
|
+
if (selectedStepIndex == -1) {
|
441
|
+
$stepLi.addClass("selected");
|
442
|
+
selectedStepIndex = 0;
|
443
|
+
}
|
444
|
+
liIndices.push(i);
|
445
|
+
} else {
|
446
|
+
$stepLi.hide();
|
447
|
+
}
|
448
|
+
}
|
449
|
+
});
|
450
|
+
|
451
|
+
// When pressing up or down on the stepMatch input,
|
452
|
+
// change the selected step
|
453
|
+
$stepMatch.keydown(function(ev) {
|
454
|
+
if (selectedStepIndex == -1) return;
|
455
|
+
|
456
|
+
// Up, Down, PageUp or PageDown
|
457
|
+
if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 33 || ev.keyCode == 34) {
|
458
|
+
currentLi().removeClass("selected");
|
459
|
+
|
460
|
+
var increment = 0;
|
461
|
+
switch(ev.keyCode) {
|
462
|
+
case 33: increment = -10; break;
|
463
|
+
case 34: increment = 10; break;
|
464
|
+
case 38: increment = -1; break;
|
465
|
+
case 40: increment = 1; break;
|
466
|
+
}
|
467
|
+
|
468
|
+
selectedStepIndex += increment;
|
469
|
+
|
470
|
+
if (selectedStepIndex < 0) selectedStepIndex = 0;
|
471
|
+
if (selectedStepIndex > liIndices.length - 1) selectedStepIndex = liIndices.length - 1;
|
472
|
+
|
473
|
+
current = currentLi();
|
474
|
+
current.addClass("selected");
|
475
|
+
$steps.scrollTop(0);
|
476
|
+
$steps.scrollTop(current.position().top - 90);
|
477
|
+
return false;
|
478
|
+
}
|
479
|
+
|
480
|
+
return true;
|
481
|
+
});
|
482
|
+
|
483
|
+
// When hovering an <li>, highglight it
|
484
|
+
$stepsLi.mouseenter(function(ev) {
|
485
|
+
currentLi().removeClass("selected");
|
486
|
+
selectedStepIndex = $stepsLi.index(this);
|
487
|
+
currentLi().addClass("selected");
|
488
|
+
});
|
489
|
+
|
490
|
+
// When clicking an <li>, build it
|
491
|
+
$stepsLi.click(function(ev) {
|
492
|
+
selectedStepIndex = $stepsLi.index(this);
|
493
|
+
prepareBuildStep();
|
494
|
+
});
|
495
|
+
|
496
|
+
// When pressing enter or tab in the step building inputs, go
|
497
|
+
// to the next one or write to scenario if it's the last one
|
498
|
+
$(".complete").live("keyup", function(ev) {
|
499
|
+
// When pressing ESC, go back to search
|
500
|
+
if (ev.keyCode == 27) {
|
501
|
+
searchAgain();
|
502
|
+
return false;
|
503
|
+
}
|
504
|
+
|
505
|
+
// Enter
|
506
|
+
if (ev.keyCode == 13) {
|
507
|
+
$this = $(this);
|
508
|
+
value = $this.val();
|
509
|
+
if (value == '')
|
510
|
+
return false;
|
511
|
+
|
512
|
+
id = $this.attr("id").substring(1);
|
513
|
+
id = parseInt(id) + 1;
|
514
|
+
$next = $("#p" + id);
|
515
|
+
if ($next.length > 0) {
|
516
|
+
$next.focus();
|
517
|
+
} else {
|
518
|
+
appendToScenario();
|
519
|
+
searchAgain();
|
520
|
+
}
|
521
|
+
return false;
|
522
|
+
}
|
523
|
+
return true;
|
524
|
+
});
|
525
|
+
|
526
|
+
// Clear the scenario
|
527
|
+
$("#clear").click(function() {
|
528
|
+
$scenario.html("");
|
529
|
+
searchAgain();
|
530
|
+
firstStepInScenario = true;
|
531
|
+
currentAction = "Given ";
|
532
|
+
});
|
533
|
+
});
|
534
|
+
</script>
|
535
|
+
<style>
|
536
|
+
body { font-family: "Helvetica Neue",Arial,Helvetica,sans-serif; font-size:85%; height:80% }
|
537
|
+
table { font-size:100%; }
|
538
|
+
ul { list-style-type:none; margin:0px; padding:0px;}
|
539
|
+
li { padding: 4px; cursor: pointer;}
|
540
|
+
h3 { padding: 0px; margin:0px; }
|
541
|
+
#steps, #stepBuilder { height: 40%; position: relative; overflow: auto; border: 1px solid black; background-color: #DFDFEF; margin-top: 20px;}
|
542
|
+
#scenarioContainer { margin-top:20px; border: 1px solid black; padding:10px; background-color: #EFEF99; }
|
543
|
+
#scenario { font-size: 105%; padding-left: 20px; margin-top: 10px;}
|
544
|
+
#container { min-height: 100%; position: relative; }
|
545
|
+
#separator { height: 5%; }
|
546
|
+
#logo { position: absolute; bottom: 0px; right: 4px; height: 20px; width: 100%; text-align: right; font-weight:bold;}
|
547
|
+
.param { font-weight: bold; color: blue;}
|
548
|
+
.selected { background-color: #BBE; padding:4px;}
|
549
|
+
.complete { width:100%; }
|
550
|
+
</style>
|
551
|
+
</head>
|
552
|
+
<body>
|
553
|
+
<div id="container">
|
554
|
+
<h3 id="explanationStep">
|
555
|
+
Write a step as you would write it in cucumber (including given, when, then, and) and press enter when you have selected one.
|
556
|
+
</h3>
|
557
|
+
<h3 id="explanationStepBuilder" style="display:none">
|
558
|
+
Now fill in the fields for the step (press tab or enter to change between fields, or escape to search another step).
|
559
|
+
</h3>
|
560
|
+
<input type="text" id="stepMatch" size="100" />
|
561
|
+
<ul id="steps">
|
562
|
+
</ul>
|
563
|
+
<div id="stepBuilder" style="display:none">
|
564
|
+
</div>
|
565
|
+
<div id="scenarioContainer">
|
566
|
+
<h3>Scenario <a href="javascript:void(0)" id="clear" style="margin-left:10px">clear</a></h3>
|
567
|
+
<div id="scenario">
|
568
|
+
</div>
|
569
|
+
</div>
|
570
|
+
</div>
|
571
|
+
<div id="separator">
|
572
|
+
</div>
|
573
|
+
<div id="logo">
|
574
|
+
#{name} - cukecooker :)
|
575
|
+
</div>
|
576
|
+
</body>
|
577
|
+
</html>
|
578
|
+
EOF
|
579
|
+
end
|
580
|
+
|
581
|
+
puts "Done! Now open cukecooker-#{name}.html in a browser."
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cukecooker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 9
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: "0.1"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Ary Borenszweig
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-08 00:00:00 -03:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: aborenszweig@manas.com.ar
|
23
|
+
executables:
|
24
|
+
- cukecooker
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- bin/cukecooker
|
31
|
+
- README.rdoc
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: https://github.com/asterite/cukecooker
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- .
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.7
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Write cucumber scenarios with aid
|
66
|
+
test_files: []
|
67
|
+
|