hiptest/hiptest-publisher

View on GitHub
lib/hiptest-publisher/node_modifiers/gherkin_adder.rb

Summary

Maintainability
B
4 hrs
Test Coverage
A
100%
require 'hiptest-publisher/indexers/actionword_indexer'
require 'hiptest-publisher/indexers/library_actionword_indexer'
require 'hiptest-publisher/nodes'

module Hiptest
  module NodeModifiers
    class GherkinAdder
      def self.add(project)
        self.new(project).update_calls
      end

      def initialize(project)
        @project = project
        @indexer = ActionwordIndexer.new(project)
        @library_indexer = LibraryActionwordIndexer.new(project)
        @annotations_counter = AnnotationsCounter.new

        @special_params = ['__free_text', '__datatable']
      end

      def update_calls
        @project.each_sub_nodes(Hiptest::Nodes::Scenario, Hiptest::Nodes::Actionword, Hiptest::Nodes::Test, Hiptest::Nodes::Folder) do |item|
          @last_annotation = nil
          item.each_sub_nodes(Hiptest::Nodes::Call) do |call|
            set_call_chunks(call)
            call.children[:gherkin_text] ||= "#{text_annotation(call)} #{prettified(call)}"

            if actionword = get_actionword(call)
              if actionword.is_a?(Hiptest::Nodes::LibraryActionword)
                call.children[:library_name] = actionword.parent.children[:name]
              end
              @annotations_counter.increment(actionword, code_annotation(call))
              set_actionwords_chunks(actionword)

              actionword.children[:gherkin_pattern] ||= pattern(actionword)
              actionword.children[:parameters_ordered_by_pattern] ||= order_parameters_by_pattern(actionword)
            end
          end
        end

        @annotations_counter.actionwords.each do |actionword|
          actionword.children[:gherkin_annotation] = @annotations_counter.most_used_annotation(actionword) || "Given"
          actionword.children[:gherkin_used_annotations] = @annotations_counter.all_used_annotations(actionword) || ['Given']
        end
      end

      def annotation(call)
        call.children[:annotation].capitalize if call.children[:annotation]
      end

      def text_annotation(call)
        annotation(call) || "*"
      end

      def code_annotation(call)
        call_annotation = annotation(call)
        if call_annotation
          if ["And", "But"].include?(call_annotation)
            call_annotation = @last_annotation || "Given"
          end
          @last_annotation = call_annotation
        end
      end

      def prettified(call)
        base = call.chunks.map {|chunk| chunk[:value]}.join("\"").strip
        call.extra_inlined_arguments.each do |chunk|
          base += " \"#{chunk[:value]}\""
        end

        base
      end

      def set_call_chunks(call)
        all_arguments = all_valued_arguments_for(call)
        inline_parameter_names = []
        extra_inlined_arguments = []

        call.chunks = []
        call.extra_inlined_arguments = []

        actionword_name = call.is_a?(Hiptest::Nodes::Call) ? call.children[:actionword] : call.children[:actionword_name]
        call_chunks = actionword_name.split("\"", -1)

        call_chunks.each_slice(2) do |text, inline_parameter_name|
          call.chunks << {
            value: text,
            is_argument: false
          }

          if all_arguments.has_key?(inline_parameter_name)
            inline_parameter_names << inline_parameter_name.clone
            value = all_arguments[inline_parameter_name]
            inline_parameter_name.replace(value)

            call.chunks << {
              value: inline_parameter_name,
              is_argument: true
            }
          else
           call.chunks << {
              value: inline_parameter_name,
              is_argument: false
            } unless inline_parameter_name.nil?
          end
        end

        missing_parameter_names = all_arguments.keys - inline_parameter_names - @special_params
        call.extra_inlined_arguments = missing_parameter_names.map do |missing_parameter_name|
          {
            value: all_arguments[missing_parameter_name],
            is_argument: true
          }
        end
      end

      def pattern(actionword)
        patterned = actionword.chunks.map {|chunk| chunk[:value]}.join("\"")
        actionword.extra_inlined_parameters.each do |param|
          patterned += " \"#{param[:value]}\""
        end

        "^#{patterned.strip}$"
      end

      def set_actionwords_chunks(actionword)
        name = actionword.uniq_name
        actionword_parameters = evaluated_map(actionword.children[:parameters])
        name_chunks = name.split("\"", -1)
        inline_parameter_names = []

        actionword.chunks = []
        actionword.extra_inlined_parameters = []

        name_chunks.each_slice(2) do |text, inline_parameter_name|
          actionword.chunks << {
            value: text.gsub(/[.|()\\.+*?\[\]{}^$]/) { |c| "\\#{c}" },
            is_parameter: false
          }

          inline_parameter_names << inline_parameter_name if inline_parameter_name
          if actionword_parameters.has_key?(inline_parameter_name)
            actionword.chunks << {
              value: "(.*)",
              name: inline_parameter_name,
              is_parameter: true
            }
          else
            actionword.chunks << {
              value: inline_parameter_name,
              is_parameter: false
            } if inline_parameter_name
          end
        end
        missing_parameter_names = actionword_parameters.keys - inline_parameter_names - @special_params

        missing_parameter_names.each do |missing_parameter_name|
          actionword.extra_inlined_parameters << {
            value: "(.*)",
            name: missing_parameter_name,
            is_parameter: true
          }
        end
      end

      def order_parameters_by_pattern(actionword)
        inline_parameter_names = actionword.children[:name].scan(/\"(.*?)\"/).flatten
        actionword_parameters = {}
        actionword.children[:parameters].map {|p| actionword_parameters[p.children[:name]] = p}

        missing_parameter_names = actionword_parameters.keys - inline_parameter_names - @special_params
        [inline_parameter_names, missing_parameter_names, @special_params].flatten.map do |name|
          actionword_parameters[name]
        end.compact
      end

      def all_valued_arguments_for(call)
        evaluated_call_arguments = evaluated_map(call.children[:arguments])
        evaluated_actionword_parameters = evaluated_map(get_actionword_parameters(call))
        names = evaluated_actionword_parameters.keys

        hash_array = names.map { |name|
          value = evaluated_call_arguments[name] || evaluated_actionword_parameters[name] || ""
          [name, value]
        }
        Hash[hash_array]
      end

      def get_actionword_parameters(call)
        actionword = get_actionword(call)
        actionword && actionword.children[:parameters] || []
      end

      def get_actionword(call)
        actionword = @library_indexer.get_index(call.children[:actionword])

        if actionword.nil?
          actionword = @indexer.get_index(call.children[:actionword])
        end

        actionword && actionword[:actionword] || nil
      end

      def evaluated_map(named_values)
        hash_array = named_values.map do |named_value|
          name = named_value.children[:name]
          value = evaluate(named_value.children[:value] || named_value.children[:default])
          [name, value]
        end
        Hash[hash_array]
      end

      def evaluate(value)
        if value.nil?
          nil
        elsif Hiptest::Nodes::Variable === value
          "<#{value.children[:name]}>"
        elsif value.children[:chunks]
          value.children[:chunks].map {|chunk| evaluate(chunk) }.join('')
        elsif value.children[:value]
          value.children[:value]
        else
          nil
        end
      end
    end

    class AnnotationsCounter
      def initialize
        @counts_by_actionword = Hash.new {|counts, actionword| counts[actionword] = Hash.new(0) }
      end

      def actionwords
        @counts_by_actionword.keys
      end

      def increment(actionword, annotation)
        counts = @counts_by_actionword[actionword]
        counts[annotation] += 1 if annotation
      end

      def most_used_annotation(actionword)
        max = @counts_by_actionword[actionword].values.max
        @counts_by_actionword[actionword].key(max)
      end

      def all_used_annotations(actionword)
        @counts_by_actionword[actionword].keys
      end
    end
  end
end