hiptest/hiptest-publisher

View on GitHub
lib/hiptest-publisher/nodes.rb

Summary

Maintainability
D
2 days
Test Coverage
require 'set'

require 'hiptest-publisher/string'
require 'hiptest-publisher/utils'
require 'hiptest-publisher/renderer'

module Hiptest
  module Nodes
    class Node
      attr_reader :children, :parent
      attr_writer :parent

      def pretty_print_instance_variables
        super - [:@parent] # do not overload pry output
      end

      def render(rendering_context)
        return Hiptest::Renderer.render(self, rendering_context)
      end

      def each_sub_nodes(*types, deep: false)
        return to_enum(:each_sub_nodes, *types, deep: deep) unless block_given?
        path = [self]
        parsed_nodes_id = Set.new

        until path.empty?
          current_node = path.shift

          if current_node.is_a?(Node)
            next if parsed_nodes_id.include? current_node.object_id
            parsed_nodes_id << current_node.object_id

            if types.empty? || types.any? {|type| current_node.is_a?(type)}
              yield current_node
              next unless deep
            end
            current_node.children.each_value {|item| path << item}
          elsif current_node.is_a?(Array)
            current_node.each {|item| path << item}
          end
        end
      end

      def each_direct_children
        children.each_value do |child|
          if child.is_a? Hiptest::Nodes::Node
            yield child
          elsif child.is_a? Array
            child.each {|c| yield c if c.is_a? Hiptest::Nodes::Node }
          end
        end
      end

      def ==(other)
        other.class == self.class && other.children == @children
      end

      def project
        project = self
        while project && !project.is_a?(Hiptest::Nodes::Project)
          project = project.parent
        end
        project
      end

      def kind
        node_kinds[self.class] ||= begin
          self.class.name.split('::').last.downcase
        end
      end

      def flat_string
        flat_childs = children.map do |key, value|
          "#{key}: #{flatten_child(value)}"
        end.join(", ")
        "<#{self.class.name} [#{flat_childs}]>"
      end

      private

      def node_kinds
        @@node_kinds ||= {}
      end

      def flatten_child(child)
        return child.flat_string if child.is_a?(Node)
        if child.is_a?(Array)
          return child.map {|item| flatten_child(item)}
        end
        child.to_s
      end
    end

    class Literal < Node
      def initialize(value)
        super()
        @children = {value: value}
      end
    end

    class NullLiteral < Node
      def initialize
        super()
        @children = {}
      end
    end

    class StringLiteral < Literal
    end

    class NumericLiteral < Literal
    end

    class BooleanLiteral < Literal
    end

    class Variable < Node
      def initialize(name)
        super()
        @children = {name: name}
      end
    end

    class Symbol < Node
      def initialize(value, delimiter)
        super()
        @children = {delimiter: delimiter, value: value}
      end
    end

    class Property < Node
      def initialize(key, value)
        super()
        @children = {key: key, value: value}
      end
    end

    class Field < Node
      def initialize(base, name)
        super()
        @children = {base: base, name: name}
      end
    end

    class Index < Node
      def initialize(base, expression)
        super()
        @children = {base: base, expression: expression}
      end
    end

    class BinaryExpression < Node
      def initialize(left, operator, right)
        super()
        @children = {operator: operator, left: left, right: right}
      end
    end

    class UnaryExpression < Node
      def initialize(operator, expression)
        super()
        @children = {operator: operator, expression: expression}
      end
    end

    class Parenthesis < Node
      def initialize(content)
        super()
        @children = {content: content}
      end
    end

    class List < Node
      def initialize(items)
        super()
        @children = {items: items}
      end
    end

    class Dict < Node
      def initialize(items)
        super()
        @children = {items: items}
      end
    end

    class Template < Node
      def initialize(chunks)
        super()
        @children = {chunks: chunks}
      end
    end

    class Assign < Node
      def initialize(to, value)
        super()
        @children = {to: to, value: value}
      end
    end

    class Argument < Node
      def initialize(name, value)
        super()
        @children = {name: name, value: value}
      end

      def free_text?
        @children[:name] == "__free_text"
      end

      def datatable?
        @children[:name] == "__datatable"
      end
    end

    class Call < Node
      attr_reader :chunks, :extra_inlined_arguments
      attr_writer :chunks, :extra_inlined_arguments

      def initialize(actionword, arguments = [], annotation = nil)
        super()
        annotation = nil if annotation == ""
        @children = {actionword: actionword, arguments: arguments, all_arguments: arguments, annotation: annotation}

        @chunks = []
        @extra_inlined_arguments = []
      end

      def free_text_arg
        children[:arguments].find(&:free_text?)
      end

      def datatable_arg
        children[:arguments].find(&:datatable?)
      end
    end

    class IfThen < Node
      def initialize(condition, then_, else_ = [])
        super()
        @children = {condition: condition, then: then_, else: else_}
      end
    end

    class Step < Node
      def initialize(key, value)
        super()
        @children = {key: key, value: value}
      end
    end

    class While < Node
      def initialize(condition, body)
        super()
        @children = {condition: condition, body: body}
      end
    end

    class Tag < Node
      def initialize(key, value = nil)
        super()
        @children = {key: key, value: value}
      end

      def to_s
        "#{@children[:key]}#{@children[:value].nil? ? '' : ':' + @children[:value]}"
      end
    end

    class Parameter < Node
      def initialize(name, default = nil)
        super()
        @children = {name: name, default: default}
      end

      def type
        if @children[:type].nil?
          'String'
        else
          @children[:type].to_s
        end
      end

      def free_text?
        @children[:name] == "__free_text"
      end

      def datatable?
        @children[:name] == "__datatable"
      end
    end

    class Item < Node
      attr_reader :variables, :non_valued_parameters, :valued_parameters

      def initialize(name, tags = [], description = '', parameters = [], body = [])
        super()
        @children = {
          name: name,
          tags: tags,
          description: description,
          parameters: parameters,
          body: body
        }
      end

      def declared_variables_names
        p_names = children[:parameters].map {|p| p.children[:name]}
        each_sub_nodes(Hiptest::Nodes::Variable).map do |var|
          v_name = var.children[:name]
          p_names.include?(v_name) ? nil : v_name
        end.uniq.compact
      end

      def add_tags(tags)
        existing = @children[:tags].map(&:to_s)

        tags.each do |tag|
          next if existing.include? tag.to_s

          existing << tag.to_s
          @children[:tags] << tag
        end
      end
    end

    class Actionword < Item
      attr_reader :chunks, :extra_inlined_parameters, :uniq_name
      attr_writer :chunks, :extra_inlined_parameters, :uniq_name

      def initialize(name, tags = [], parameters = [], body = [], uid = nil, description = '')
        super(name, tags, description, parameters, body)
        @children[:uid] = uid

        @chunks = []
        @extra_inlined_parameters = []
        @uniq_name = name
      end

      def must_be_implemented?
        @children[:body].empty? || @children[:body].map {|step| step.class}.compact.include?(Hiptest::Nodes::Step)
      end
    end

    class LibraryActionword < Item
      attr_reader :chunks, :extra_inlined_parameters, :uniq_name
      attr_writer :chunks, :extra_inlined_parameters, :uniq_name

      def initialize(name, tags = [], parameters = [], body = [], uid = nil, description = '')
        super(name, tags, description, parameters, body)
        @children[:uid] = uid

        @chunks = []
        @extra_inlined_parameters = []
        @uniq_name = name
      end

      def must_be_implemented?
        @children[:body].empty? || @children[:body].map {|step| step.class}.compact.include?(Hiptest::Nodes::Step)
      end
    end

    class Scenario < Item
      attr_reader :folder_uid, :order_in_parent

      def initialize(name, description = '', tags = [], parameters = [], body = [], folder_uid = nil, datatable = Datatable.new, order_in_parent = 0)
        super(name, tags, description, parameters, body)
        @children[:datatable] = datatable

        @folder_uid = folder_uid
        @order_in_parent = order_in_parent
      end

      def set_uid(uid)
        @children[:uid] = uid
      end

      def folder
        project && project.children[:test_plan] && project.children[:test_plan].find_folder_by_uid(folder_uid)
      end
    end

    class Test < Node
      def initialize(name, description = '', tags = [], body = [])
        super()

        @children = {
          name: name,
          description: description,
          tags: tags,
          body: body
        }
      end

      def folder
        nil
      end
    end

    class Datatable < Node
      def initialize(datasets = [])
        super()

        @children = {
          datasets: datasets
        }
      end
    end

    class Dataset < Node
      def initialize(name, arguments = [], uid = nil)
        super()

        @children = {
          name: name,
          uid: uid,
          arguments: arguments
        }
      end

      def set_test_snapshot_uid(uid)
        @children[:test_snapshot_uid] = uid
      end
    end

    class Actionwords < Node
      attr_reader :to_implement, :no_implement
      def initialize(actionwords = [])
        super()
        @children = {actionwords: actionwords}
        mark_actionwords_for_implementation
        index_actionwords
      end

      def find_actionword(name)
        return @actionwords_index[name]
      end

      private
      def mark_actionwords_for_implementation
        @to_implement = []
        @no_implement = []

        @children[:actionwords].each do |aw|
          if aw.must_be_implemented?
            @to_implement << aw
          else
            @no_implement << aw
          end
        end
      end

      def index_actionwords
        @actionwords_index = {}

        @children[:actionwords].each do |aw|
          @actionwords_index[aw.children[:name]] = aw
        end
      end
    end

    class Scenarios < Node
      def initialize(scenarios = [])
        super()
        @children = {scenarios: scenarios}
        scenarios.each {|sc| sc.parent = self}
      end
    end

    class Tests < Node
      def initialize(tests = [])
        super()
        @children = {tests: tests}
        tests.each {|test| test.parent = self}
      end
    end

    class Folder < Node
      attr_reader :uid, :parent_uid, :order_in_parent

      def initialize(uid, parent_uid, name, description, tags = [], order_in_parent = 0, body = [])
        super()
        @uid = uid
        @parent_uid = parent_uid
        @order_in_parent = order_in_parent

        @children = {
          name: name,
          description: description,
          subfolders: [],
          scenarios: [],
          tags: tags,
          body: body
        }
      end

      def root?
        parent_uid == nil
      end

      def folder
        root? ? nil : parent
      end

      def ancestors
        ancestors = []

        current_ancestor = folder
        until current_ancestor.nil?
          ancestors << current_ancestor
          current_ancestor = current_ancestor.folder
        end

        ancestors
      end
    end

    class TestPlan < Node
      def initialize(folders = [])
        super()
        @uids_mapping = {}
        @children = {
          root_folder: nil,
          folders: folders
        }
      end

      def organize_folders
        @children[:root_folder] = @children[:folders].find(&:root?)
        @children[:root_folder].parent = self if @children[:root_folder]

        @children[:folders].each do |folder|
          @uids_mapping[folder.uid] = folder
        end

        @children[:folders].each do |folder|
          next if folder.root?

          parent = find_folder_by_uid(folder.parent_uid) || @children[:root_folder]
          folder.parent = parent
          parent.children[:subfolders] << folder unless parent.children[:subfolders].include?(folder)
        end
      end

      def find_folder_by_uid(uid)
        return @uids_mapping[uid]
      end
    end

    class Libraries < Node
      def initialize(libraries = [])
        super()
        @children = {
          libraries: libraries
        }
      end
    end

    class Library < Node
      def initialize(name = 'default_library', library_actionwords = [])
        super()
        @children = {
          name: name,
          library_actionwords: library_actionwords
        }
      end
    end

    class Project < Node
      def initialize(name, description = '', test_plan = TestPlan.new, scenarios = Scenarios.new, actionwords = Actionwords.new, tests = Tests.new, libraries = Libraries.new)
        super()
        test_plan.parent = self if test_plan.respond_to?(:parent=)
        scenarios.parent = self
        tests.parent = self

        @children = {
          name: name,
          description: description,
          test_plan: test_plan,
          scenarios: scenarios,
          actionwords: actionwords,
          tests: tests,
          libraries: libraries
        }
      end

      def has_libraries?
        !children[:libraries].children[:libraries].empty?
      end

      def assign_scenarios_to_folders
        @children[:scenarios].children[:scenarios].each do |scenario|
          folder = @children[:test_plan].find_folder_by_uid(scenario.folder_uid)
          next if folder.nil?

          folder.children[:scenarios] << scenario
        end
      end
    end
  end
end