spec/parser_spec.rb in society-1.1.1 vs spec/parser_spec.rb in society-1.2.0

- old
+ new

@@ -1,37 +1,228 @@ require 'spec_helper' describe Society::Parser do + let(:source) { "class Ship; end" } + let(:nested_source) { "module Ship; class Anchor; end; end;" } + let(:inherited_source) { "class Carrier < Ship; end" } + let(:namespaced_source) { "class Ship::Anchor; end" } - describe "::for_files" do + describe "::for_source" do - it "returns a parser object" do - expect(Society::Parser.for_files('./spec/fixtures/for_parser_spec').class.name).to eq("Society::Parser") + context "listing nodes" do + it "returns a parser object" do + expect(Society::Parser.for_source(source).class.name).to eq("Society::Parser") + end + + it "will accept multiple source strings" do + expect(Society::Parser.for_source(source, nested_source).class.name).to eq("Society::Parser") + end + + it "parses simple source trees" do + expect(Society::Parser.for_source(source).classes).to eq(["Ship"]) + end + + it "parses source trees with inheritance" do + expect(Society::Parser.for_source(inherited_source).classes).to eq(["Carrier"]) + end + + it "parses namespaced nodes" do + expect(Society::Parser.for_source(nested_source).classes).to eq(["Ship", "Ship::Anchor"]) + expect(Society::Parser.for_source(namespaced_source).classes).to eq(["Ship::Anchor"]) + end + + it "tracks if a node is a class or module" do + parser = Society::Parser.for_source(nested_source) + expect(parser.graph.select { |node| node.name == "Ship" }.first.type).to eq(:module) + expect(parser.graph.select { |node| node.name == "Ship::Anchor" }.first.type).to eq(:class) + end + end - it "initializes the parser with an Analyzer object" do - expect(Society::Parser.for_files('./spec/fixtures/for_parser_spec').analyzer.class.name).to eq("Analyst::Parser") + context "detecting edges" do + let(:source) { "class Ship; def initialize; @engine = Engine.new; end; end; class Engine; end;" } + let(:namespaced_source) { "class Ship; def initialize; @engine = Engine::Diesel.new; end; end; class Engine::Diesel; end" } + let(:unknown_edge_source) { "class Ship; def initialize; @engine = Engine::UFO.new; end; end" } + + it "detects simple edges for classes" do + graph = Society::Parser.for_source(source).graph + edge = graph.first.edges.first + expect(edge.class.name).to eq("Society::Edge") + expect(graph.first.edges.map(&:to)).to eq(["Engine"]) + end + + it "detects simple edges for namespaced classes" do + expect(Society::Parser.for_source(namespaced_source).graph.first.edges.map(&:to)).to eq(["Engine::Diesel"]) + end + + it "rejects edges which point to nodes of which it is unaware" do + expect(Society::Parser.for_source(unknown_edge_source).graph.first.edges.empty?).to eq(true) + end end - end + context "detecting activerecord edges from belongs_to, has_many and through" do + let(:source) { + <<-CODE + class Assembly < ActiveRecord::Base + has_many :manifests + has_many :parts, through: :manifests + end - describe "::for_source" do + class Manifest < ActiveRecord::Base + belongs_to :assembly + belongs_to :part + end - let(:source) { "class Ship; end" } + class Part < ActiveRecord::Base + has_many :manifests + has_many :assemblies, through: :manifests + end + CODE + } + let(:parser) { Society::Parser.for_source(source) } + let(:assembly) { parser.graph.detect {|c| c.name == "Assembly" } } + let(:manifest) { parser.graph.detect {|c| c.name == "Manifest" } } + let(:part) { parser.graph.detect {|c| c.name == "Part" } } + + it "records `has_many` and `has_many through:` associations" do + expect(assembly.edges.map(&:to).sort).to match_array %w(Manifest Part) + expect(part.edges.map(&:to).sort).to match_array %w(Assembly Manifest) + end + + it "records `belongs_to` associations" do + expect(manifest.edges.map(&:to).sort).to match_array %w(Assembly Part) + end + end + + context "detecting activerecord edges with `class_name` specified" do + let(:source) {<<-CODE + class Post < ActiveRecord::Base + belongs_to :author, :class_name => "User" + belongs_to :editor, :class_name => "User" + end + + class User < ActiveRecord::Base + has_many :authored_posts, :foreign_key => "author_id", :class_name => "Post" + has_many :edited_posts, :foreign_key => "editor_id", :class_name => "Post" + end + CODE + } + + let(:parser) { Society::Parser.for_source(source) } + let(:user) { parser.graph.detect {|c| c.name == "User" } } + let(:post) { parser.graph.detect {|c| c.name == "Post" } } + + it "records belongs_to's" do + expect(post.edges.map(&:to)).to match_array %w(User) + expect(post.edges.map(&:weight)).to match_array [2] + end + + it "records has_many's" do + expect(user.edges.map(&:to)).to match_array %w(Post) + expect(user.edges.map(&:weight)).to match_array [2] + end + end + + context "detecting activerecord edges with `class_name` or `source` specified" do + let(:source) { <<-CODE + class Post < ActiveRecord::Base + has_many :post_authorings, :foreign_key => :authored_post_id + has_many :authors, :through => :post_authorings, :source => :post_author + belongs_to :editor, :class_name => "User" + end + + class User < ActiveRecord::Base + has_many :post_authorings, :foreign_key => :post_author_id + has_many :authored_posts, :through => :post_authorings + has_many :edited_posts, :foreign_key => :editor_id, :class_name => "Post" + end + + class PostAuthoring < ActiveRecord::Base + belongs_to :post_author, :class_name => "User" + belongs_to :authored_post, :class_name => "Post" + end + CODE + } + + let(:parser) { Society::Parser.for_source(source) } + let(:user) { parser.graph.detect {|c| c.name == "User" } } + let(:post) { parser.graph.detect {|c| c.name == "Post" } } + let(:post_authoring) { parser.graph.detect {|c| c.name == "PostAuthoring" } } + + it "handles `source` and `class_name` correctly" do + expect(post.edges.map(&:to).sort).to match_array %w(PostAuthoring User) + expect(post.edges.sort_by(&:to).map(&:weight)).to match_array [1, 2] + end + + it "handles `through` and `class_name` correctly" do + expect(user.edges.map(&:to).sort).to match_array %w(PostAuthoring Post) + expect(user.edges.sort_by(&:to).map(&:weight)).to match_array [1, 2] + end + + it "handles `class_name` correctly" do + expect(post_authoring.edges.map(&:to).sort).to match_array %w(Post User) + end + end + + context "detecting activerecord edges with polymorphic associations" do + let(:source) { <<-CODE + class Picture < ActiveRecord::Base + belongs_to :imageable, polymorphic: true + end + + class Employee < ActiveRecord::Base + has_many :pictures, as: :imageable + end + + class Product < ActiveRecord::Base + has_many :pictures, as: :imageable + end + CODE + } + + let(:parser) { Society::Parser.for_source(source) } + let(:picture) { parser.graph.detect {|c| c.name == "Picture" } } + let(:employee) { parser.graph.detect {|c| c.name == "Employee" } } + let(:product) { parser.graph.detect {|c| c.name == "Product" } } + + it "records the `polymorphic: true` side of the association" do + expect(picture.edges.map(&:to).sort).to match_array %w(Employee Product) + end + + it "records the `as: :something` side of the asociation" do + expect(employee.edges.map(&:to).sort).to match_array %w(Picture) + expect(product.edges.map(&:to).sort).to match_array %w(Picture) + end + + end + + end + + describe "::for_files" do + it "returns a parser object" do - expect(Society::Parser.for_source(source).class.name).to eq("Society::Parser") + expect(Society::Parser.for_files('./spec/fixtures/for_parser_spec').class.name).to eq("Society::Parser") + expect(Society::Parser.for_files('./spec/fixtures/for_parser_spec').classes).to eq(["Whaler"]) end - it "initializes the parser with an Analyzer object" do - expect(Society::Parser.for_source(source).analyzer.class.name).to eq("Analyst::Parser") + end + + describe "#initialize" do + it "returns a parser object" do + expect(Society::Parser.new([]).class.name).to eq("Society::Parser") end + end + describe "#graph" do + it "returns a graph object" do + expect(Society::Parser.new([]).graph.class.name).to eq("Society::ObjectGraph") + end end describe "#report" do - let(:parser) { Society::Parser.new(nil) } + let(:parser) { Society::Parser.new([]) } let(:formatter) { double.as_null_object } before do allow(parser).to receive(:json_data).and_return('{"json": "is kewl"}') end