From 6dfe0f5dcb4622c3e9283224107326981d82e1ad Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Sat, 2 Nov 2013 14:32:44 +0100 Subject: [PATCH 1/5] ability to provide line number for spec in the same manner as rspec, the line number may be provided directly after the file name or by explicitely passing a cli argument --- lib/guard/jasmine/cli.rb | 6 ++++++ lib/guard/jasmine/runner.rb | 22 ++++++++++++++++---- spec/guard/jasmine/cli_spec.rb | 10 +++++++++ spec/guard/jasmine/runner_spec.rb | 34 +++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/lib/guard/jasmine/cli.rb b/lib/guard/jasmine/cli.rb index 76caa26..1737ffc 100644 --- a/lib/guard/jasmine/cli.rb +++ b/lib/guard/jasmine/cli.rb @@ -65,6 +65,11 @@ class CLI < Thor aliases: '-d', desc: 'The directory with the Jasmine specs' + method_option :line_number, + type: :numeric, + aliases: '-l', + desc: 'The line which identifies the spec to be run' + method_option :url, type: :string, aliases: '-u', @@ -166,6 +171,7 @@ def spec(*paths) runner_options = {} runner_options[:port] = options.port || CLI.find_free_server_port runner_options[:spec_dir] = options.spec_dir || (File.exists?(File.join('spec', 'javascripts')) ? File.join('spec', 'javascripts') : 'spec') + runner_options[:line_number] = options.line_number runner_options[:server] = options.server.to_sym == :auto ? ::Guard::Jasmine::Server.detect_server(runner_options[:spec_dir]) : options.server.to_sym runner_options[:server_mount] = options.mount || (defined?(JasmineRails) ? '/specs' : '/jasmine') runner_options[:jasmine_url] = options.url || "http://localhost:#{ runner_options[:port] }#{ options.server.to_sym == :jasmine_gem ? '/' : runner_options[:server_mount] }" diff --git a/lib/guard/jasmine/runner.rb b/lib/guard/jasmine/runner.rb index ea274ab..905546b 100644 --- a/lib/guard/jasmine/runner.rb +++ b/lib/guard/jasmine/runner.rb @@ -155,10 +155,24 @@ def query_string_for_suite(file, options) query_string = '' - File.foreach(file) do |line| - if line =~ /describe\s*[("']+(.*?)["')]+/ - query_string = "?spec=#{ $1 }" - break + if options[:line_number].to_s =~ /\A(\d+)$/ || file =~/\A[^:]+:(\d+)$/ + line_number = $1.to_i + + spec = File.readlines(file)[0, line_number]. + map(&:strip). + select { |x| x.start_with?('it') || x.start_with?('describe') }. + map { |x| x[/['"](.+?)['"]/, 1] }. + join(' ') + + if spec && !spec.empty? + query_string = "?spec=#{ spec }" + end + else + File.foreach(file) do |line| + if line =~ /describe\s*[("']+(.*?)["')]+/ + query_string = "?spec=#{ $1 }" + break + end end end diff --git a/spec/guard/jasmine/cli_spec.rb b/spec/guard/jasmine/cli_spec.rb index 39542a9..0a0e91e 100644 --- a/spec/guard/jasmine/cli_spec.rb +++ b/spec/guard/jasmine/cli_spec.rb @@ -45,6 +45,11 @@ cli.start(['spec', '--spec-dir', 'specs']) end + it 'sets the line number' do + runner.should_receive(:run).with(anything(), hash_including(line_number: 1)).and_return [true, []] + cli.start(['spec', '--line-number', 1]) + end + it 'detects the server type' do server.should_receive(:detect_server).with('specs') cli.start(['spec', '--spec-dir', 'specs']) @@ -274,6 +279,11 @@ cli.start(['spec']) end + it 'sets the line number' do + runner.should_receive(:run).with(anything(), hash_including(line_number: nil)).and_return [true, []] + cli.start(['spec']) + end + it 'disables the focus mode' do runner.should_receive(:run).with(anything(), hash_including(focus: false)).and_return [true, []] cli.start(['spec', '-f', 'false']) diff --git a/spec/guard/jasmine/runner_spec.rb b/spec/guard/jasmine/runner_spec.rb index 772df8c..1e2858a 100644 --- a/spec/guard/jasmine/runner_spec.rb +++ b/spec/guard/jasmine/runner_spec.rb @@ -211,6 +211,40 @@ end end + context 'when passed a line number' do + before do + File.stub(:readlines).and_return([ + 'describe "TestContext", ->', + ' it "does something", ->', + ' # some assertion' + ]) + end + + context 'with the spec file name' do + it 'executes the example for line number on example' do + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee:2'], defaults) + end + + it 'executes the example for line number within example' do + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee:3'], defaults) + end + + it 'executes all examples within describe' do + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee:1'], defaults) + end + end + + context 'with the cli argument' do + it 'executes the example for line number on example' do + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee'], defaults.merge(line_number: 2)) + end + end + end + context 'when passed the spec directory' do it 'requests all jasmine specs from the server' do IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine\" 60000 failure true failure failure false true ''", "r:UTF-8") From 410c2a78849e14b72d11cfbd631d4a9961da4784 Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Sun, 3 Nov 2013 17:46:59 +0100 Subject: [PATCH 2/5] don't add up all 'it' examples --- lib/guard/jasmine/runner.rb | 9 +++++++-- spec/guard/jasmine/runner_spec.rb | 14 ++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/guard/jasmine/runner.rb b/lib/guard/jasmine/runner.rb index 905546b..efee0e9 100644 --- a/lib/guard/jasmine/runner.rb +++ b/lib/guard/jasmine/runner.rb @@ -41,7 +41,7 @@ def run(paths, options = { }) notify_start_message(paths, options) results = paths.inject([]) do |results, file| - results << evaluate_response(run_jasmine_spec(file, options), file, options) if File.exist?(file) + results << evaluate_response(run_jasmine_spec(file, options), file, options) if File.exist?(file[/\A(.+?)(:\d+)?$/, 1]) results end.compact @@ -158,9 +158,14 @@ def query_string_for_suite(file, options) if options[:line_number].to_s =~ /\A(\d+)$/ || file =~/\A[^:]+:(\d+)$/ line_number = $1.to_i - spec = File.readlines(file)[0, line_number]. + groups = File.readlines(file[/\A(.+?):\d+$/, 1])[0, line_number]. map(&:strip). select { |x| x.start_with?('it') || x.start_with?('describe') }. + group_by { |x| x[0,2] } + + spec = groups["de"] + spec << groups["it"][-1] if groups["it"] + spec = spec. map { |x| x[/['"](.+?)['"]/, 1] }. join(' ') diff --git a/spec/guard/jasmine/runner_spec.rb b/spec/guard/jasmine/runner_spec.rb index 1e2858a..769bbb4 100644 --- a/spec/guard/jasmine/runner_spec.rb +++ b/spec/guard/jasmine/runner_spec.rb @@ -216,19 +216,21 @@ File.stub(:readlines).and_return([ 'describe "TestContext", ->', ' it "does something", ->', + ' # some assertion', + ' it "does something else", ->', ' # some assertion' ]) end context 'with the spec file name' do it 'executes the example for line number on example' do - IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something\" 60000 failure true failure failure false true ''", "r:UTF-8") - runner.run(['spec/javascripts/a.js.coffee:2'], defaults) + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee:4'], defaults) end it 'executes the example for line number within example' do - IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something\" 60000 failure true failure failure false true ''", "r:UTF-8") - runner.run(['spec/javascripts/a.js.coffee:3'], defaults) + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee:5'], defaults) end it 'executes all examples within describe' do @@ -239,8 +241,8 @@ context 'with the cli argument' do it 'executes the example for line number on example' do - IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something\" 60000 failure true failure failure false true ''", "r:UTF-8") - runner.run(['spec/javascripts/a.js.coffee'], defaults.merge(line_number: 2)) + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee'], defaults.merge(line_number: 4)) end end end From b13bb2bd475e0e6bd0b265cded7edd250a9c81db Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Sun, 3 Nov 2013 22:00:01 +0100 Subject: [PATCH 3/5] split up query_string_for_suite --- lib/guard/jasmine/runner.rb | 104 +++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/lib/guard/jasmine/runner.rb b/lib/guard/jasmine/runner.rb index efee0e9..2c3b14c 100644 --- a/lib/guard/jasmine/runner.rb +++ b/lib/guard/jasmine/runner.rb @@ -41,7 +41,7 @@ def run(paths, options = { }) notify_start_message(paths, options) results = paths.inject([]) do |results, file| - results << evaluate_response(run_jasmine_spec(file, options), file, options) if File.exist?(file[/\A(.+?)(:\d+)?$/, 1]) + results << evaluate_response(run_jasmine_spec(file, options), file, options) if File.exist?(file_and_line_number_parts(file)[0]) results end.compact @@ -141,9 +141,7 @@ def phantomjs_script end # The suite name must be extracted from the spec that - # will be run. This is done by parsing from the head of - # the spec file until the first `describe` function is - # found. + # will be run. # # @param [String] file the spec file # @param [Hash] options the options for the execution @@ -153,35 +151,91 @@ def phantomjs_script def query_string_for_suite(file, options) return '' if file == options[:spec_dir] - query_string = '' + query_string = query_string_for_suite_from_line_number(file, options) + + unless query_string + query_string = query_string_for_suite_from_first_describe(file, options) + end + + query_string = query_string ? "?spec=#{ query_string }" : '' - if options[:line_number].to_s =~ /\A(\d+)$/ || file =~/\A[^:]+:(\d+)$/ - line_number = $1.to_i + URI.encode(query_string) + end + + # When providing a line number by either the option or by + # a number directly after the file name the suite is extracted + # fromt the corresponding line number in the file. + # + # @param [String] file the spec file + # @param [Hash] options the options for the execution + # @option options [Fixnum] :line_number the line number to run + # @return [String] the suite name + # + def query_string_for_suite_from_line_number(file, options) + file_name, line_number = file_and_line_number_parts(file) + line_number ||= options[:line_number] - groups = File.readlines(file[/\A(.+?):\d+$/, 1])[0, line_number]. - map(&:strip). - select { |x| x.start_with?('it') || x.start_with?('describe') }. + if line_number + groups = it_and_describe_lines(file_name)[0, line_number]. group_by { |x| x[0,2] } - spec = groups["de"] - spec << groups["it"][-1] if groups["it"] - spec = spec. - map { |x| x[/['"](.+?)['"]/, 1] }. - join(' ') + specs = groups["de"] + specs << groups["it"][-1] if groups["it"] + specs.map { |x| spec_title(x) }.join(' ') + end + end - if spec && !spec.empty? - query_string = "?spec=#{ spec }" - end - else - File.foreach(file) do |line| - if line =~ /describe\s*[("']+(.*?)["')]+/ - query_string = "?spec=#{ $1 }" - break - end + # The suite name must be extracted from the spec that + # will be run. This is done by parsing from the head of + # the spec file until the first `describe` function is + # found. + # + # @param [String] file the spec file + # @param [Hash] options the options for the execution + # @return [String] the suite name + # + def query_string_for_suite_from_first_describe(file, options) + File.foreach(file) do |line| + if line =~ /describe\s*[("']+(.*?)["')]+/ + return $1 end end + end - URI.encode(query_string) + # Splits the file name into the physical file name + # and the line number if present. E.g.: + # 'some_spec.js.coffee:10' -> ['some_spec.js.coffee', 10]. + # + # If the line number is missing the second part of the + # returned array is `nil`. + # + # @param [String] file the spec file + # @return [Array] `[file_name, line_number]` + # + def file_and_line_number_parts(file) + match = file.match(/\A(.+?)(?::(\d+))?$/) + [match[1], match[2].nil? ? nil : match[2].to_i] + end + + # Returns all lines of the file that are either a + # 'describe' or a 'it' declaration. + # + # @param [String] file the spec file + # @Return [Array] the line contents + # + def it_and_describe_lines(file) + File.readlines(file). + map(&:strip). + select { |x| x.start_with?('it') || x.start_with?('describe') } + end + + # Extracts the title of a 'description' or a 'it' declaration. + # + # @param [String] the line content + # @return [String] the extracted title + # + def spec_title(line) + line[/['"](.+?)['"]/, 1] end # Evaluates the JSON response that the PhantomJS script From 21b4471b93a1700290b77f43b3a832203144e4a6 Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Sun, 3 Nov 2013 22:02:56 +0100 Subject: [PATCH 4/5] update readme with add line-number option --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eb2f651..23c9e79 100644 --- a/README.md +++ b/README.md @@ -672,6 +672,7 @@ Options: -b, [--bin=BIN] # The location of the PhantomJS binary -d, [--spec-dir=SPEC_DIR] # The directory with the Jasmine specs # Default: spec/javascripts + -l, [--line-number=N] # The line which identifies the spec to be run -u, [--url=URL] # The url of the Jasmine test runner_options # Default: nil -t, [--timeout=N] # The maximum time in seconds to wait for the spec From 1e2892bd24cce392560b15abce13c9796a11ffbb Mon Sep 17 00:00:00 2001 From: Alexis Reigel Date: Wed, 6 Nov 2013 22:54:09 +0100 Subject: [PATCH 5/5] aggregate spec tree by indentation --- lib/guard/jasmine/runner.rb | 26 ++++++++++++++++---------- spec/guard/jasmine/runner_spec.rb | 27 ++++++++++++++++----------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/lib/guard/jasmine/runner.rb b/lib/guard/jasmine/runner.rb index 2c3b14c..e49c9b9 100644 --- a/lib/guard/jasmine/runner.rb +++ b/lib/guard/jasmine/runner.rb @@ -176,12 +176,17 @@ def query_string_for_suite_from_line_number(file, options) line_number ||= options[:line_number] if line_number - groups = it_and_describe_lines(file_name)[0, line_number]. - group_by { |x| x[0,2] } + lines = it_and_describe_lines(file_name, 0, line_number) + last = lines.pop - specs = groups["de"] - specs << groups["it"][-1] if groups["it"] - specs.map { |x| spec_title(x) }.join(' ') + last_indentation = last[/^\s*/].length + # keep only lines with lower indentation + lines.delete_if { |x| x[/^\s*/].length >= last_indentation } + # remove all 'it' + lines.delete_if { |x| x =~ /^\s*it/ } + + lines << last + lines.map { |x| spec_title(x) }.join(' ') end end @@ -213,7 +218,7 @@ def query_string_for_suite_from_first_describe(file, options) # @return [Array] `[file_name, line_number]` # def file_and_line_number_parts(file) - match = file.match(/\A(.+?)(?::(\d+))?$/) + match = file.match(/^(.+?)(?::(\d+))?$/) [match[1], match[2].nil? ? nil : match[2].to_i] end @@ -221,12 +226,13 @@ def file_and_line_number_parts(file) # 'describe' or a 'it' declaration. # # @param [String] file the spec file + # @param [Numeric] from the first line in the range + # @param [Numeric] to the last line in the range # @Return [Array] the line contents # - def it_and_describe_lines(file) - File.readlines(file). - map(&:strip). - select { |x| x.start_with?('it') || x.start_with?('describe') } + def it_and_describe_lines(file, from, to) + File.readlines(file)[from, to]. + select { |x| x =~ /^\s*(it|describe)/ } end # Extracts the title of a 'description' or a 'it' declaration. diff --git a/spec/guard/jasmine/runner_spec.rb b/spec/guard/jasmine/runner_spec.rb index 769bbb4..176fa66 100644 --- a/spec/guard/jasmine/runner_spec.rb +++ b/spec/guard/jasmine/runner_spec.rb @@ -214,23 +214,28 @@ context 'when passed a line number' do before do File.stub(:readlines).and_return([ - 'describe "TestContext", ->', - ' it "does something", ->', - ' # some assertion', - ' it "does something else", ->', - ' # some assertion' + 'describe "TestContext", ->', # 1 + ' describe "Inner TestContext", ->', # 2 + ' describe "Unrelated TestContext", ->', # 3 + ' it "does something", ->', # 4 + ' # some code', # 5 + ' # some assertion', # 6 + ' it "does something else", ->', # 7 + ' # some assertion', # 8 + ' it "does something a lot else", ->', # 9 + ' # some assertion' # 10 ]) end context 'with the spec file name' do it 'executes the example for line number on example' do - IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") - runner.run(['spec/javascripts/a.js.coffee:4'], defaults) + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20Inner%20TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee:7'], defaults) end it 'executes the example for line number within example' do - IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") - runner.run(['spec/javascripts/a.js.coffee:5'], defaults) + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20Inner%20TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee:8'], defaults) end it 'executes all examples within describe' do @@ -241,8 +246,8 @@ context 'with the cli argument' do it 'executes the example for line number on example' do - IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") - runner.run(['spec/javascripts/a.js.coffee'], defaults.merge(line_number: 4)) + IO.should_receive(:popen).with("#{ phantomjs_command } \"http://localhost:8888/jasmine?spec=TestContext%20Inner%20TestContext%20does%20something%20else\" 60000 failure true failure failure false true ''", "r:UTF-8") + runner.run(['spec/javascripts/a.js.coffee'], defaults.merge(line_number: 7)) end end end