-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbowlingKata.vim
993 lines (790 loc) · 29.4 KB
/
bowlingKata.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
" vim script to animate the Bowling KATA in javascript inside the vim editor
"
" this script require snipMate. snipMate provide the Mocha code Snippet.
"
" Maintainer: Etienne Rossignon
" Contributor: Etienne Rossignon
" LastModifed:
"
" to run from the vim command line:
"
"
" rules : http://www.ihsa.org/documents/bw/BW-Rulebook.pdf
" http://en.wikipedia.org/wiki/Ten-pin_bowling
"
" echo input(" sfile = " . expand("<sfile>",""))
" echo input(" Current file is ".expand("%:p:h"),"")
execute "source " . expand("<sfile>:p:h") . "/runMocha.vim"
let &makeprg="mocha -R tap"
" Error: bar
" at Object.foo [as _onTimeout] (/Users/Felix/.vim/bundle/vim-nodejs-errorformat/test.js:2:9)
let &errorformat = '%AError: %m' . ','
let &errorformat .= '%Z%*[\ ]%m (%f:%l:%c)' . ','
" at Object.foo [as _onTimeout] (/Users/Felix/.vim/bundle/vim-nodejs-errorformat/test.js:2:9)
let &errorformat .= '%*[\ ]%m (%f:%l:%c)' . ','
" /Users/Felix/.vim/bundle/vim-nodejs-errorformat/test.js:2
" throw new Error('bar');
" ^
let &errorformat .= '%A%f:%l,%Z%p%m' . ','
" Ignore everything else
"ER let &errorformat .= '%-G%.%#'
function! HookCoreFilesIntoQuickfixWindow()
let files = getqflist()
for i in files
let filename = bufname(i.bufnr)
" Non-existing file in the quickfix list, assume a core file
if !filereadable(filename)
" Open a new split / buffer for loading this core file
execute 'split ' filename
" Make this buffer modifiable
set modifiable
" Set the buffer options
setlocal buftype=nofile bufhidden=hide
" Clear all previous buffer contents
execute ':1,%d'
" Load the node.js core file (thanks @izs for pointing this out!)
execute 'read !node -e "process.binding(\"natives\").' expand('%:r') '"'
" Delete the first line, always empty for some reason
execute ':1d'
" Tell vim to treat this buffer as a JS file
set filetype=javascript
" No point in making this file writable
setlocal nomodifiable
" Point our quickfix entry to this (our current) buffer
let i.bufnr = bufnr("%")
" Close the split, so our little hack stays in the background
close
endif
endfor
" call setqflist(files)
endfunction
au QuickfixCmdPost make call HookCoreFilesIntoQuickfixWindow()
let rnd = localtime() % 0x10000
function! Random()
let g:rnd = (g:rnd * 31421 + 6927) % 0x10000
return g:rnd
endfun
function! Choose(n) " 0 n within
return (Random() * a:n) / 0x10000
endfun
function! Rnd(m)
return Choose(a:m)
endfunction
function! Wait(mil)
redraw
let timetowait = float2nr(a:mil) . " m"
"xx echo timetowait
exe 'sleep '.timetowait
return ""
endfunction
function! W()
return Wait(Rnd(6)+5)
endfunction
"
"
"
function! SlowType(text)
let chars = split(a:text, '.\zs')
let a = ''
for c in chars
let a .= "\<C-R>=W()\<C-M>" . c
endfor
return a
endfunction
function! FakeTyping(text)
set nocindent
setlocal nocindent
let chars = split(a:text, '.\zs')
for c in chars
execute "normal zza" . c . "\<esc>"
call W()
redraw
endfor
endfunction
let bowlingKataPause = -1
function! Pause(msg)
redraw!
if (g:bowlingKataPause == -1)
let a = input(a:msg . " ( press enter to continue )"," ")
else
echo a:msg
sleep 1000 m
endif
return ""
endfunction
set makeprg=mocha\ -R\ tap
let source_file="/tmp/Bowling.js"
let test_file="/tmp/test/Bowling_test.js"
let s:triggerSnippet = "\<C-R>=TriggerSnippet()\<CR>"
function! bowlingKata#AddNewTest_it(description)
execute "normal A\n" . SlowType(" it") . s:triggerSnippet . "\<C-X>'\<esc>"
execute "normal x\<left>"
call FakeTyping(a:description)
sleep 100 m
" move down one line, where we can type the body of the test
execute "normal j"
call FakeTyping("\n")
execute "normal 0C\<esc>"
endfunction
function! bowlingKata#prepareBuffer()
setlocal expandtab
setlocal shiftwidth=2
setlocal softtabstop=2
match none
setlocal comments-=://
setlocal comments+=f://
setlocal formatoptions-=croql
setlocal noautoindent
setlocal nocindent
setlocal nosmartindent
setlocal smarttab
setlocal tabstop=2
setlocal indentexpr=
set nocindent
endfunction
function! bowlingKata#Step0()
" delete all buffers
let g:mochawin = -1 " reset mocha
silent! bufdo bdelete!
silent! execute "!rm " . g:source_file
silent! execute "!rm " . g:test_file
silent! execute "!rm " . "/tmp/mocha.mytap"
silent! execute "!del " . g:source_file
silent! execute "!del " . g:test_file
silent! execute "!del " . "c:\\tmp\\mocha.mytap"
only!
call Pause("###### Starting Bowling KATA step 1")
" run Mocha Once, this will create the testing window at the bottom
call RunMocha()
" now move the cursor to the top window and split it
execute "normal \<C-W>\<Up>"
"split windows
vsplit
call Pause(" first test should succeed but with 0 test")
"xx execute "normal \<C-W>="
" create source file on the left
enew!
call bowlingKata#prepareBuffer()
execute "write! " . g:source_file
" create test file on the right
execute "normal \<C-W>\<Right>"
enew!
call bowlingKata#prepareBuffer()
execute "write! " . g:test_file
call FakeTyping("var fs = require(\"fs\");\n")
sleep 100 m
call FakeTyping("var Bowling = require(\"../Bowling\");\n")
call FakeTyping("var should = require(\"should\");\n")
sleep 100 m
" add the first desc("...",function(){}) using the snipmate snippet
execute "normal i" . SlowType("desc") . s:triggerSnippet . "\<C-X>\<esc>"
call FakeTyping("Bowling Game")
sleep 100 m
" move down one line
execute "normal j"
sleep 100 m
" add the first test
call bowlingKata#AddNewTest_it("should score zero when constructed")
call FakeTyping("\<tab>\<tab>var game = new Bowling.Game();\n")
call FakeTyping("\<tab>\<tab>game.score.should.equal(0);\n")
redraw
write!
execute "normal \<C-W>\<Left>"
call FakeTyping("var Game = function () {\n")
call FakeTyping("\n}")
call FakeTyping("\nexports.Game = Game;")
call RunMocha()
call Pause("Test has failed : method score is not defined")
endfunction
function! bowlingKata#SwitchToSource()
execute "normal \<C-W>\<C-T>\<C-W>\<left>"
execute "buffer " . g:source_file
setlocal formatoptions-=croql
endfunction
function! bowlingKata#SwitchToTest()
" set cursor in test buffer
execute "normal \<C-W>\<C-T>\<C-W>\<right>"
execute "buffer " . g:test_file
setlocal formatoptions-=croql
endfunction
" move the cursor after the n'th test
" a.k.a after the n'th it("...",function(){...}) block
function! bowlingKata#SeekEndTest(n)
" goto top of the file
execute "0"
"
execute "normal " . a:n . "/^\\s*it(/e\<CR>%"
call CenterScreenOnCursor()
endfunction
" correct first error : score is not defined
function! bowlingKata#Step1()
call bowlingKata#SwitchToSource()
execute "normal 1/exports\<cr>i\n\<up>\n"
call Pause(" ready ?")
call FakeTyping("Game.prototype.score = function () { \n")
call FakeTyping(" return 0; \n")
call FakeTyping("}\n")
" run tests again
"
call RunMocha()
call bowlingKata#SwitchToTest()
" fix the score bug by adding ()
execute "normal /\\.score/e\<cr>a()\<esc>"
write!
" run test again
call RunMocha()
call Pause(" Tests are now OK. It's time to refactor")
endfunction
function! bowlingKata#Step2()
call bowlingKata#SwitchToTest()
set nu
" move at the end of the first it(...,{ }) block
call bowlingKata#SeekEndTest(1)
call bowlingKata#AddNewTest_it("should score 20 when all rolls hit one pin")
call FakeTyping("\t\tvar game = new Bowling.Game();\n")
call FakeTyping("\t\tfor (var i=0; i < 20 ; i++) {\n\n\t\t}\n")
execute "normal \<up>\<up>"
call FakeTyping("\t\t\tgame.roll(1);")
execute "normal \<down>\<down>"
call FakeTyping("\t\tgame.score().should.equal(20);")
call RunMocha()
call Pause(" Test failed : roll method is missing => let's add it")
call bowlingKata#SwitchToSource()
" find insertion point for new prototypes
execute "normal 1/exports\<cr>i\n\<up>\n"
call Pause(" ready ?")
call FakeTyping("Game.prototype.roll = function (pin) { \n")
call FakeTyping("\treturn 0; \n")
call FakeTyping("}\n\n")
call RunMocha()
call Pause(" Test failed : score is wrong => let's fix it")
" search score method
call bowlingKata#SwitchToSource()
execute "normal /Game = function\<CR>\<down>0C" . SlowType(" this._score = 0;") . "\<esc>"
execute "normal /prototype.score = function\<CR>\<down>0C" . SlowType(" return this._score;") . "\<esc>"
execute "normal /prototype.roll = function\<CR>\<down>0C" . SlowType(" this._score += pin;") . "\<esc>"
call RunMocha()
call Pause(" Test succeeded : => let's refactor")
echo ""
endfunction
" refactoring : pull out construction in beforeEach
function! bowlingKata#Step3()
call bowlingKata#SwitchToTest()
" highlight in red what we are refactoring
match Error /game = new Bowling.Game();/
" lets copy the var game ... line
execute "/var game = new"
" store in register 0
execute "normal \"0yy"
execute "normal /describe\<CR>\<down>0i\n" . SlowType(" ") . SlowType("var game;\n\tbeforeEach( function() {\n\t});\n") . "\n\<up>\<up>\<up>\<esc>"
" remember line where we want to move the code
let l=line(".")
redraw
echo "refactor out new Bowling.Game() " . l
sleep 1000 m
execute "g/new Bowling.Game/d"
" paste line in clipboard
execute l
put 0
" remove var
s/var //
redraw
" execute "normal 0C" . SlowType(" game = new Bowling.Game();") . "\<esc>"
call RunMocha()
call Pause(" Test is still OK" )
" reset hightlighted text
call bowlingKata#SwitchToTest()
match none
echo " end of Step 3"
endfunction
function! bowlingKata#Step4()
call bowlingKata#SwitchToTest()
" move to the end of the 2nd it(...) block
call bowlingKata#SeekEndTest(2)
call bowlingKata#AddNewTest_it("should score 25 after a SPARE followed by a 6 and a 3")
call FakeTyping("\t\tgame.roll(4);\n")
call FakeTyping("\t\tgame.roll(6); // spare\n")
call FakeTyping("\t\tgame.roll(6);\n")
call FakeTyping("\t\tgame.roll(3);\n")
call FakeTyping("\t\t// score is (10 + 6 ) + 6 + 3 = 25\n")
call FakeTyping("\t\tfor (var i=0; i < 16 ; i++) {\n\n\t\t}\n")
execute "normal \<up>\<up>"
call FakeTyping("\t\t\tgame.roll(0);")
execute "normal \<down>\<down>"
call FakeTyping("\t\tgame.score().should.equal(25);")
call RunMocha()
call Pause(" Test failed : => let's fix it")
endfunction
function! CenterScreenOnCursor()
execute "normal zz"
endfunction
" fix code with spare
function! bowlingKata#Step5()
call bowlingKata#SwitchToSource()
execute "normal /this._score = 0\<CR>0C" . SlowType("\tthis.round = 0;\n") . "\<esc>"
call FakeTyping("\tthis._roll = new Array(21);\n")
call FakeTyping("\tfor ( var i = 0 ; i < 21 ; i ++ ) {\n\t\tthis._roll[i] = 0 ;\n\t}\n")
execute "normal /return this._score\<CR>0C" . SlowType("\n") . "\<esc>"
call FakeTyping("\tvar _score = 0;\n")
call FakeTyping("\tfor (var f = 0; f < 10 ; f ++ ) {\n")
call FakeTyping("\t\t_score += this._roll[ f * 2 ] + this._roll[ f * 2 + 1 ];\n")
call FakeTyping("\t}\n")
call FakeTyping("\treturn _score;\n")
write!
call Pause(" we change our mind, change is big, lets temporarly ignore newly created test")
call bowlingKata#SwitchToTest()
call bowlingKata#disableTest("should score 25")
call RunMocha()
call Pause(" Test failed ! OOPS !")
call Pause(" we forget to refactor roll! : => let's finish refactoring")
call bowlingKata#SwitchToSource()
" search the line we want to replace
execute "normal /this._score += pin\<CR>"
call CenterScreenOnCursor()
" replace the line by an empty line
execute "normal 0C". SlowType("\n") . "\<esc>"
" add new code
call FakeTyping("\tthis._roll[ this.round ] = pin;\n")
call FakeTyping("\tthis.round += 1;\n")
call RunMocha()
call Pause(" Test succeeded : => refactoring has worked !")
call Pause(" We can re-enable the test")
call bowlingKata#SwitchToTest()
call bowlingKata#enableTest("should score 25")
call RunMocha()
call Pause(" Test is failing, lets fix it")
call bowlingKata#SwitchToSource()
execute("normal /f < 10\<CR>A\n")
call CenterScreenOnCursor()
call FakeTyping("\t\tif (this._roll[ f * 2 ] + this._roll[ f * 2 + 1] == 10 ) {\n")
call FakeTyping("\t\t\t_score += this._roll[ ( f + 1 ) * 2 ];\n")
call FakeTyping("\t\t}\n")
redraw
call RunMocha()
call Pause(" Test is now OK , let's refactor")
echo ""
endfunction
" refactor by extracting isSpare( f )
function! bowlingKata#Step6()
call bowlingKata#SwitchToSource()
" move to first line
execute "0"
" search start of if stattemtn
execute "normal /if (/e\<CR>"
call CenterScreenOnCursor()
sleep 500 m
" mark statement selection in visual mode
execute "normal v%"
" pause so that we can see
call Pause("let's factor out this code by creating a isSpare method")
" yank code that calculate if frame is spare is
execute "normal y"
" replace with new code
execute "normal gvc" . SlowType("( this.isSpare(f) )") . "\<esc>"
call Pause("")
" now move above methode definition
" search backward for prototype
execute "normal ?prototype\<CR>0i\n\<up>"
call FakeTyping("Game.prototype.isSpare = function ( f ) {\n\n}\n")
" paste code that we yanked
execute "normal \<up>\<up>i" . SlowType("\<tab>return ") . "\<esc>pA" .SlowType(";") . "\<esc>"
redraw
call RunMocha()
call Pause(" Test is now OK , refactoring was successful")
echo ""
endfunction
" adding a test case with a strike
function! bowlingKata#Step7()
call bowlingKata#SwitchToTest()
call Pause("let's create a test to verify score with a single STRIKE")
call bowlingKata#SeekEndTest(3)
call bowlingKata#AddNewTest_it("should score 28 after a STRIKE followed by a 6 and a 3")
call FakeTyping("\t\tgame.roll(10); // Strike\n")
call FakeTyping("\t\tgame.roll(6);\n")
call FakeTyping("\t\tgame.roll(3);\n")
call FakeTyping("\t\t// score is (10 + 6 + 3 ) + 6 + 3 = 28\n")
call FakeTyping("\t\tfor (var i=0; i < 16 ; i++) {\n\n\t\t}\n")
execute "normal \<up>\<up>"
call FakeTyping("\t\t\tgame.roll(0);")
execute "normal \<down>\<down>"
call FakeTyping("\t\tgame.score().should.equal(28);")
call RunMocha()
call Pause(" Test failed : => let's fix it")
echo ""
endfunction
" fix score calculation
function! bowlingKata#Step8()
call bowlingKata#SwitchToSource()
execute "normal /this.isSpare\<CR>0"
call FakeTyping("\t\tif ( this.isStrike(f) ) {\n\n\t\t} else")
execute "normal \<up>"
call FakeTyping("\t\t\t_score += this._roll[ ( f + 1 ) * 2 ] + this._roll[ ( f + 1 ) * 2 + 1];")
write!
echo " add the isStrike method"
execute "normal ?prototype.isSpare\<CR>\<up>"
call FakeTyping("Game.prototype.isStrike = function ( f ) {\n")
call FakeTyping("\treturn (this._roll[ f * 2 ] == 10 ) ;\n")
call FakeTyping("}\n")
call RunMocha()
call Pause(" Score is still wrong : => let's fix it")
echo ""
endfunction
" fix roll when strike is run
function! bowlingKata#Step9()
call bowlingKata#SwitchToSource()
execute "normal /prototype.roll\<CR>zz"
execute "normal /this.round += 1;/e\<CR>"
execute "normal A\n\<esc>"
call FakeTyping("\tif ( pin == 10 ) {\n")
call FakeTyping("\t\tthis.round += 1;\n\t}\n")
call RunMocha()
call Pause(" All tests pass : => let's refactor")
echo ""
endfunction
" refactor out the frameScore function
function! bowlingKata#Step10()
call bowlingKata#SwitchToSource()
call Pause(" let's create a function to calculate the score of a single frame")
execute "0"
execute "normal /prototype.score\<CR>\<up>0"
execute "normal zz"
call FakeTyping("\nGame.prototype.frameScore = function ( f ) {\n\t// Calculate Frame score\n\n")
call FakeTyping("}\n")
" let extract function body from score
execute "normal /if ( this.isStrike\<CR>?{\<CR>"
" enter visual mode an select to the matching }
execute "normal v%\<up>$o\<down>0<gvygv"
call Pause("we want to move this code into a dedicated method")
" delete selected text
execute "normal gvC\<esc>"
call FakeTyping("\t\t_score += this.frameScore( f );\n")
execute "normal /Calculate Frame score/e\<CR>"
call FakeTyping("\n\tvar _score = 0;\n")
call FakeTyping("\treturn _score;")
" put text before
put!
call RunMocha()
call Pause(" All tests pass : => let's keep refactoring")
echo ""
endfunction
" add roll many in test
function! bowlingKata#Step11()
call bowlingKata#SwitchToTest()
execute "0"
match Error /for(.*){.*}/
execute "normal /for (var i=\<CR>"
execute "normal V\<down>\<down>\"0ygv"
call Pause("let create a rollMany function for this code (DRY)")
execute "normal gvc\<esc>"
call FakeTyping("\t\tgame.rollMany(20,1);")
execute "normal ?describe\<CR>A\n\<esc>"
call FakeTyping("\n\tBowling.Game.prototype.rollMany = function(rolls,pins) {\n")
call FakeTyping("\t\tfor (var r = 0 ; r < rolls ; r++ ) {\n")
call FakeTyping("\t\t\tthis.roll(pins);\n")
call FakeTyping("\t\t}\n")
call FakeTyping("\t}")
call RunMocha()
call Pause(" All tests pass : => let's use rollMany elsewhere")
echo ""
" search for () block
execute "normal /for (var i=\<CR>"
" select this line + the next 2 lines and cut it
execute "normal V\<down>\<down>c\<esc>"
" write new code
call FakeTyping("\t\tgame.rollMany(16,0);")
execute "normal /for (var i=\<CR>"
execute "normal V\<down>\<down>c\<esc>"
call FakeTyping("\t\tgame.rollMany(16,0);")
call RunMocha()
call Pause(" All tests pass : => let's keep refactoring")
echo ""
endfunction
" replace the code
" the pattern can contain %% which will be replaced with sub
" for instance
" to replace this.roll[ f * 1] with func(f)
" and this.roll[ ( f + 1) * 1] with func((f+1))
" - [ ] and * are escaped
" - single space are replaced with optional white space: \\s*
"
function! bowlingKata#Refactor(oldCode, newCode, variation)
call Pause(" replacing " . a:oldCode . " with " . a:newCode . " where %% = " . a:variation)
let v = a:oldCode
let v = substitute(v,"[","\\\\[","g")
let v = substitute(v,"]","\\\\]","g")
let v = substitute(v,"*","\\\\*","g")
let v = substitute(v," ","\\\\s*","g")
let v = substitute(v,"%%",a:variation,"g")
let v = "\\m" . v
"xx call Pause(" pattern = " . v)
let n = substitute(a:newCode,"%%",a:variation,"g")
" remove redundant parenthesis
let n = substitute(n,"((","(","g")
let n = substitute(n,"))",")","g")
" hightlight search
set hlsearch
execute "normal /" . v . "\<CR>zz"
redraw
sleep 800 m
execute "s/" . v . "/" . n . "/g"
redraw
sleep 800 m
set nohlsearch
endfunction
" refactor out framePinDown()
function! bowlingKata#Step12()
call bowlingKata#SwitchToSource()
call Pause ("let's make the score calculation clearer (DRY) ! ")
call bowlingKata#Refactor("this._roll[ %% * 2 ] + this._roll[ %% * 2 + 1 ]","this.frameDownPin(%%)","f")
call bowlingKata#Refactor("this._roll[ %% * 2 ] + this._roll[ %% * 2 + 1 ]","this.frameDownPin(%%)","f")
call bowlingKata#Refactor("this._roll[ %% * 2 ] + this._roll[ %% * 2 + 1 ]","this.frameDownPin(%%)","( f + 1 )")
call Pause("this calculates the number of knocked down pin in a frame")
" add new method
execute "normal ?prototype.isStrike\<CR>\<up>"
call FakeTyping("\n/**\n")
call FakeTyping(" * returns the number of pins that have been knocked down in frame *f* \n")
call FakeTyping(" */\n")
call FakeTyping("Game.prototype.frameDownPin = function ( f ) { \n")
call FakeTyping(" return this._roll[ f * 2 ] + this._roll[ f * 2 + 1 ];\n")
call FakeTyping("}\n")
call RunMocha()
call Pause(" All tests pass : => let's keep refactoring")
echo ""
endfunction
" golden score test
function! bowlingKata#Step13()
call bowlingKata#SwitchToTest()
call Pause ("let's add the golden score test : 12 strikes in a row ( 9 strikes + 3 strikes in last frame!) ")
call bowlingKata#SeekEndTest(4)
call bowlingKata#AddNewTest_it("should score 300 after 12 STRIKES (golden score)")
call FakeTyping("\t\tgame.rollMany(12,10);\n")
call FakeTyping("\t\tgame.score().should.equal(300);")
call RunMocha()
call Pause(" Test failed : => let's fix it")
echo ""
call Pause(" In fact, we may be too anbitious, lets try a small test first")
call bowlingKata#disableTest("should score 300")
call RunMocha()
call Pause(" Test failed : => let's fix it")
call bowlingKata#SeekEndTest(4)
call bowlingKata#AddNewTest_it("should score 52 for X,X,6,2,0,...,0")
call FakeTyping("\t\tgame.roll(10);\n")
call FakeTyping("\t\tgame.roll(10);\n")
call FakeTyping("\t\tgame.roll(6);\n")
call FakeTyping("\t\tgame.roll(2);\n")
call FakeTyping("\t\tgame.rollMany(14,0);\n")
"// score 10 + 10 + 6 = 26
"// score 10 + 6 + 2 = 18
"// score 6 + 2 = 8
call FakeTyping("\t\tgame.score().should.equal(52);\n")
call RunMocha()
call Pause(" Test failed : => let's fix it")
endfunction
" fix double strike issue
function! bowlingKata#Step14()
call bowlingKata#SwitchToSource()
execute "0"
execute "normal /if ( this.isStrike(f)\<CR>A\n\<esc>"
call FakeTyping("\t\t\tif ( this.isStrike(f + 1) ) {\n")
call FakeTyping("\t\t\t\t_score += 10 + this._roll[ ( f + 2 ) * 2 ] ;\n")
call FakeTyping("\t\t\t} else {")
execute "normal \<down>>>\<esc>A\n\t\t\t}\n\<esc>"
call RunMocha()
call Pause(" Test is now OK : => let's move on")
endfunction
function! bowlingKata#enableTest(start_of_should_line)
match Error /xit(/
execute "0,$s/xit(\'\s*" . a:start_of_should_line . "/it(\'" . a:start_of_should_line . "/g"
execute "normal zz"
endfunction
function! bowlingKata#disableTest(start_of_should_line)
match Error /xit(.*$/
execute "0,$s/it(\'\s*" . a:start_of_should_line . "/xit(\'" . a:start_of_should_line . "/g"
execute "normal zz"
call Pause(" note that test has been excluded by adding a x to it")
endfunction
" uncomment and fix golden score test
function! bowlingKata#Step15()
" reenable test
call bowlingKata#SwitchToTest()
call bowlingKata#enableTest("should score 300")
redraw
call RunMocha()
call Pause("Test is still failing, let fix it")
call bowlingKata#SwitchToSource()
set hlsearch
execute "normal /if ( pin == 10/e\<CR>zz"
call FakeTyping(" && this.round<=18 ")
set nosearch
call RunMocha()
execute "0"
set hlsearch
execute "normal /var _score = 0;/e\<CR>"
call FakeTyping("\n\tif ( f == 9 ) { // last frame is special \n")
call FakeTyping("\t\t_score += this._roll[ f * 2 + 2 ];\n")
call FakeTyping("\t} else ");
execute "normal J"
redraw
set nohlsearch
call RunMocha()
endfunction
" refactor further by extracting a frame bonus method
function! bowlingKata#Step16()
call bowlingKata#SwitchToTest()
call Pause("Lets test the behavior with invalid arguments passed to roll")
call bowlingKata#SeekEndTest(4)
call bowlingKata#AddNewTest_it("should prevent to pass invalid number of pin to roll")
call FakeTyping("\t\t(function() { game.roll(-1); }).should.throw();\n")
call RunMocha()
call Pause(" Test is failing : => let's add the protection code")
endfunction
" protect against negative value in rolls
function! bowlingKata#Step17()
call bowlingKata#SwitchToSource()
" locate the start of the roll method
execute "normal /prototype.roll\<CR>A\n\<esc>"
" append some checks
call FakeTyping("\tif ( pin < 0 || pin > 10) {\n")
call FakeTyping("\t\tthrow new Error('invalid number of pin');\n")
call FakeTyping("\t}\n")
call RunMocha()
call Pause(" Test is OK ")
endfunction
" add a extra test for second roll in a frame
function! bowlingKata#Step18()
call bowlingKata#SwitchToTest()
call bowlingKata#SeekEndTest(5)
call bowlingKata#AddNewTest_it("should check that no more than 10 pins can be knocked down in a frame")
call FakeTyping("\t\tgame.roll(6);\n")
call FakeTyping("\t\t(function() {\n")
call FakeTyping("\t\t\tgame.roll(5); // 6+ 5 = 11 => Should thow !\n")
call FakeTyping("\t\t}).should.throw('number of pins cannot exceed 4');")
call RunMocha()
call Pause(" Test has failed ")
endfunction
" fix
function! bowlingKata#Step19()
call bowlingKata#SwitchToSource()
" locate the start of the roll method
execute "normal /prototype.roll\<CR>A\n\<esc>"
call FakeTyping("\tvar maxAllowedPin = 10 ; ")
execute "normal \<down>"
execute ".,.+3s/pin > 10/pin > maxAllowedPin"
execute "normal \<up>A\n\<esc>"
redraw
call FakeTyping("\tvar currentFrame = Math.ceil( ( this.round - 1 ) / 2 ) ;\n")
call FakeTyping("\tif ( this.round % 2 == 1 ) {\n")
call FakeTyping("\t\tmaxAllowedPin = 10 - this._roll[ currentFrame * 2 ];\n")
call FakeTyping("\t}\n")
" replace 10 wiht
call RunMocha()
call Pause(" we have two failing tests !, let fix the new one first")
call Pause(" we need to massage the error message")
execute ".,+10s/'invalid number of pin'/'number of pins cannot exceed ' + maxAllowedPin"
call RunMocha()
call Pause(" we have one old test failing : this is a regression !")
call Pause(" Ah ! of course ! our boundary check is not suitable for mast frame that can have 3 rolls of 10 ! ")
" locate the insertion point
execute "normal ?this.round % 2 == 1?e\<CR>"
call FakeTyping(" && currentFrame != 9")
call RunMocha()
call Pause(" Test is OK ")
endfunction
" some refactoring isLastFrame
function! bowlingKata#Step20()
call bowlingKata#SwitchToSource()
call Pause(" Let's refactor and create a isLastFrame method")
execute "normal /prototype.roll\<CR>0i\n\<esc>\<up>zz"
call FakeTyping("Game.prototype.isLastFrame = function ( f ) {\n")
call FakeTyping(" return ( f === 9 );\n")
call FakeTyping("};\n")
call bowlingKata#Refactor("currentFrame != 9","! this.isLastFrame( currentFrame )","")
call RunMocha()
call Pause(" tests are still OK")
endfunction
function! bowlingKata#All()
let g:bowlingKataPause = 0
call bowlingKata#Step0()
call bowlingKata#Step1()
call bowlingKata#Step2()
call bowlingKata#Step3()
call bowlingKata#Step4()
call bowlingKata#Step5()
call bowlingKata#Step6()
call bowlingKata#Step7()
call bowlingKata#Step8()
call bowlingKata#Step9()
call bowlingKata#Step10()
call bowlingKata#Step11()
call bowlingKata#Step12()
call bowlingKata#Step13()
call bowlingKata#Step14()
call bowlingKata#Step15()
call bowlingKata#Step16()
call bowlingKata#Step17()
call bowlingKata#Step18()
call bowlingKata#Step19()
call bowlingKata#Step20()
endfunction
function! bowlingKata#BDDStep0()
" https://github.com/alexscordellis/sensei-bowling-game/blob/master/features/score_game.feature
silent! call mkdir("/tmp/test/features/steps")
bd! bowlingGame_Score.feature
new /tmp/test/features/bowlingGame_Score.feature
0,$d
call FakeTyping("Feature: Score a bowling game\n")
call FakeTyping("\tAs a 10-pin bower\n")
call FakeTyping("\tI want a program to calculate my bowling score\n")
call FakeTyping("\tSo it can save me from doing it in my head\n")
call FakeTyping("\n")
call FakeTyping("\tScenario: Gutter Game\n")
call FakeTyping("\t\tGiven I have started a new game\n")
call FakeTyping("\t\tWhen I roll 20 gutter balls\n")
call FakeTyping("\t\tThen my score is 0 points\n")
call FakeTyping("\n")
call FakeTyping("\tScenario: Game with one spare\n")
call FakeTyping("\t\tGiven I have started a new game\n")
call FakeTyping("\t\tWhen I knock over 8 pins\n")
call FakeTyping("\t\tAnd I knock over 2 pins\n")
call FakeTyping("\t\tAnd I knock over 3 pins\n")
call FakeTyping("\t\tAnd I roll 17 gutter balls\n")
call FakeTyping("\t\tThen my score is 16 points\n")
write!
silent! call mkdir("/tmp/test/features/steps","p")
bd! bowlingGame_Score_steps.js
new /tmp/test/features/steps/bowlingGame_Score_steps.js
write!
0,$d
call FakeTyping("var Bowling = require('../../../Bowling');\n")
call FakeTyping("var should = require('should');\n")
call FakeTyping("var myStepDefinitionsWrapper = function () {\n")
call FakeTyping("\n")
call FakeTyping("\n")
call FakeTyping("};\n")
call FakeTyping("module.exports = myStepDefinitionsWrapper;\n")
execute "normal \<up>\<up>\<up>"
call FakeTyping(" this.Given(/^I have started a new game$/, function(callback) {\n\tcallback.pending();\n });\n")
execute "normal ?pending\<CR>0C\<esc>"
call FakeTyping(" this.game = new Bowling.Game();\n")
call FakeTyping(" callback();\n")
execute "normal \<down>\<down>0"
call FakeTyping(" this.When(/^I roll (\\d+) gutter balls/, function(n,callback) {\n\tcallback.pending();\n });\n")
execute "normal ?pending\<CR>0C\<esc>"
call FakeTyping(" for( var i = 0 ; i < n ; i += 1 ) { this.game.roll(0); } \n")
call FakeTyping(" callback();\n")
execute "normal \<down>\<down>0"
call FakeTyping(" this.When(/^I knock over (\\d+) pins*$/, function(n,callback) {\n\tcallback.pending();\n });\n")
execute "normal ?pending\<CR>0C\<esc>"
call FakeTyping(" this.game.roll(parseInt(n));\n")
call FakeTyping(" callback();\n")
execute "normal \<down>\<down>0"
call FakeTyping(" this.Then(/^my score is (\\d+) points$/, function (expected_score, callback) {\n\tcallback.pending();\n });\n")
execute "normal ?pending\<CR>0C\<esc>"
call FakeTyping(" this.game.score().should.equal(parseInt(expected_score));\n")
call FakeTyping(" callback();\n")
execute "normal \<down>\<down>0"
write!
cd /tmp/test
botright new
read !cucumber-js
endfunction