From 1ee6c622bb7a2a6c6a52bfa0340d380faba4ba42 Mon Sep 17 00:00:00 2001
From: Andrei Kislichenko <android.2net@gmail.com>
Date: Thu, 11 Jul 2024 00:00:00 -0400
Subject: [PATCH] make FullNmae & Affiliation comparable

---
 lib/relaton_bib/contributor.rb            |  18 +++
 lib/relaton_bib/forename.rb               |   4 +
 lib/relaton_bib/formatted_string.rb       |   4 +
 lib/relaton_bib/full_name.rb              |   5 +
 lib/relaton_bib/image.rb                  |   6 +
 lib/relaton_bib/localized_string.rb       |   6 +
 lib/relaton_bib/organization.rb           |  10 ++
 lib/relaton_bib/version.rb                |   2 +-
 spec/relaton_bib/contributor_spec.rb      |  69 ++++++++++++
 spec/relaton_bib/formatted_string_spec.rb |  12 ++
 spec/relaton_bib/full_name_spec.rb        | 131 ++++++++++++++++++++++
 spec/relaton_bib/organization_spec.rb     |  43 ++++++-
 12 files changed, 308 insertions(+), 2 deletions(-)
 create mode 100644 spec/relaton_bib/full_name_spec.rb

diff --git a/lib/relaton_bib/contributor.rb b/lib/relaton_bib/contributor.rb
index 46df3e6..91bf3ac 100644
--- a/lib/relaton_bib/contributor.rb
+++ b/lib/relaton_bib/contributor.rb
@@ -30,6 +30,12 @@ def initialize(**args) # rubocop:disable Metrics/CyclomaticComplexity
       @formatted_address = args[:formatted_address] unless args[:city] && args[:country]
     end
 
+    def ==(other)
+      street == other.street && city == other.city && state == other.state &&
+        country == other.country && postcode == other.postcode &&
+        formatted_address == other.formatted_address
+    end
+
     # @param doc [Nokogiri::XML::Document]
     def to_xml(doc) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
       doc.address do
@@ -100,6 +106,10 @@ def initialize(type:, value:, subtype: nil)
       @value    = value
     end
 
+    def ==(other)
+      type == other.type && subtype == other.subtype && value == other.value
+    end
+
     # @param builder [Nokogiri::XML::Document]
     def to_xml(builder)
       node = builder.send type, value
@@ -144,6 +154,10 @@ def initialize(organization: nil, name: nil, description: [])
       @description  = description
     end
 
+    def ==(other)
+      name == other.name && organization == other.organization && description == other.description
+    end
+
     # @param opts [Hash]
     # @option opts [Nokogiri::XML::Builder] :builder XML builder
     # @option opts [String] :lang language
@@ -224,6 +238,10 @@ def initialize(url: nil, contact: [])
       @contact = contact
     end
 
+    def ==(other)
+      uri == other.uri && contact == other.contact
+    end
+
     # Returns url.
     # @return [String]
     def url
diff --git a/lib/relaton_bib/forename.rb b/lib/relaton_bib/forename.rb
index 3081c46..a09edcd 100644
--- a/lib/relaton_bib/forename.rb
+++ b/lib/relaton_bib/forename.rb
@@ -16,6 +16,10 @@ def initialize(content: nil, language: [], script: [], initial: nil)
       super content, language, script
     end
 
+    def ==(other)
+      super && initial == other.initial
+    end
+
     def to_s
       content.nil? ? initial : super
     end
diff --git a/lib/relaton_bib/formatted_string.rb b/lib/relaton_bib/formatted_string.rb
index 831901e..dca7561 100644
--- a/lib/relaton_bib/formatted_string.rb
+++ b/lib/relaton_bib/formatted_string.rb
@@ -25,6 +25,10 @@ def initialize(content: "", language: nil, script: nil, format: "text/plain")
       super(content, language, script)
     end
 
+    def ==(other)
+      super && format == other.format
+    end
+
     # @param builder [Nokogiri::XML::Builder]
     def to_xml(builder)
       builder.parent["format"] = format if format
diff --git a/lib/relaton_bib/full_name.rb b/lib/relaton_bib/full_name.rb
index 79c3c2c..8063054 100644
--- a/lib/relaton_bib/full_name.rb
+++ b/lib/relaton_bib/full_name.rb
@@ -37,6 +37,11 @@ def initialize(**args) # rubocop:disable Metrics/AbcSize
       @completename = args[:completename]
     end
 
+    def ==(other)
+      surname == other.surname && abbreviation == other.abbreviation && completename == other.completename &&
+        forename == other.forename && initials == other.initials && addition == other.addition && prefix == other.prefix
+    end
+
     # @param opts [Hash]
     # @option opts [Nokogiri::XML::Builder] :builder XML builder
     # @option opts [String] :lang language
diff --git a/lib/relaton_bib/image.rb b/lib/relaton_bib/image.rb
index 9aabf79..0279123 100644
--- a/lib/relaton_bib/image.rb
+++ b/lib/relaton_bib/image.rb
@@ -29,6 +29,12 @@ def initialize(id:, src:, mimetype:, **args)
       @longdesc = args[:longdesc]
     end
 
+    def ==(other)
+      other.is_a?(Image) && id == other.id && src == other.src && mimetype == other.mimetype &&
+        filename == other.filename && width == other.width && height == other.height &&
+        alt == other.alt && title == other.title && longdesc == other.longdesc
+    end
+
     #
     # Converts the image object to XML format.
     #
diff --git a/lib/relaton_bib/localized_string.rb b/lib/relaton_bib/localized_string.rb
index d4ead9e..da9d531 100644
--- a/lib/relaton_bib/localized_string.rb
+++ b/lib/relaton_bib/localized_string.rb
@@ -37,6 +37,12 @@ def initialize(content, language = nil, script = nil) # rubocop:disable Metrics/
                  end
     end
 
+    def ==(other)
+      return false unless other.is_a? LocalizedString
+
+      content == other.content && language == other.language && script == other.script
+    end
+
     #
     # String representation.
     #
diff --git a/lib/relaton_bib/organization.rb b/lib/relaton_bib/organization.rb
index b929546..636793e 100644
--- a/lib/relaton_bib/organization.rb
+++ b/lib/relaton_bib/organization.rb
@@ -25,6 +25,10 @@ def initialize(type, value)
       @value = value
     end
 
+    def ==(other)
+      type == other.type && value == other.value
+    end
+
     # @param builder [Nokogiri::XML::Builder]
     def to_xml(builder)
       builder.identifier(value, type: type)
@@ -90,6 +94,12 @@ def initialize(**args) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
       @logo = args[:logo]
     end
 
+    def ==(other)
+      name == other.name && abbreviation == other.abbreviation &&
+        subdivision == other.subdivision && identifier == other.identifier &&
+        logo == other.logo && super
+    end
+
     # @param opts [Hash]
     # @option opts [Nokogiri::XML::Builder] :builder XML builder
     # @option opts [String] :lang language
diff --git a/lib/relaton_bib/version.rb b/lib/relaton_bib/version.rb
index 51e4d83..f6d7e21 100644
--- a/lib/relaton_bib/version.rb
+++ b/lib/relaton_bib/version.rb
@@ -1,3 +1,3 @@
 module RelatonBib
-  VERSION = "1.19.0".freeze
+  VERSION = "1.19.1".freeze
 end
diff --git a/spec/relaton_bib/contributor_spec.rb b/spec/relaton_bib/contributor_spec.rb
index 7e32818..d4237aa 100644
--- a/spec/relaton_bib/contributor_spec.rb
+++ b/spec/relaton_bib/contributor_spec.rb
@@ -3,6 +3,20 @@
     expect { RelatonBib::Address.new }.to raise_error(ArgumentError)
   end
 
+  context "==" do
+    it "same content" do
+      contrib = RelatonBib::Address.new(formatted_address: "formatted address")
+      other = RelatonBib::Address.new(formatted_address: "formatted address")
+      expect(contrib).to eq other
+    end
+
+    it "different content" do
+      contrib = RelatonBib::Address.new(formatted_address: "formatted address")
+      other = RelatonBib::Address.new(formatted_address: "other formatted address")
+      expect(contrib).not_to eq other
+    end
+  end
+
   context "render formatted address" do
     let(:contrib) { RelatonBib::Address.new(formatted_address: "formatted address") }
 
@@ -21,6 +35,48 @@
       expect(contrib.to_asciibib).to eq "address.formatted_address:: formatted address\n"
     end
   end
+
+  context "render address" do
+    let(:contrib) do
+      RelatonBib::Address.new(
+        street: ["street1", "street2"],
+        city: "city",
+        state: "state",
+        country: "country",
+        postcode: "postcode",
+      )
+    end
+
+    it "as XML" do
+      xml = Nokogiri::XML::Builder.new { |b| contrib.to_xml(b) }.doc.root
+      street = xml.xpath("/address/street").map(&:text)
+      expect(street).to eq %w[street1 street2]
+      expect(xml.xpath("/address/city").text).to eq "city"
+      expect(xml.xpath("/address/state").text).to eq "state"
+      expect(xml.xpath("/address/country").text).to eq "country"
+      expect(xml.xpath("/address/postcode").text).to eq "postcode"
+    end
+
+    it "as Hash" do
+      hash = contrib.to_hash
+      expect(hash["address"]["street"]).to eq %w[street1 street2]
+      expect(hash["address"]["city"]).to eq "city"
+      expect(hash["address"]["state"]).to eq "state"
+      expect(hash["address"]["country"]).to eq "country"
+      expect(hash["address"]["postcode"]).to eq "postcode"
+    end
+
+    it "as AsciiBib" do
+      expect(contrib.to_asciibib).to eq <<~ASCIIBIB
+        address.street:: street1
+        address.street:: street2
+        address.city:: city
+        address.state:: state
+        address.country:: country
+        address.postcode:: postcode
+      ASCIIBIB
+    end
+  end
 end
 
 describe RelatonBib::Affiliation do
@@ -32,6 +88,19 @@
     described_class.new(organization: org, name: name, description: description)
   end
 
+  context "==" do
+    it "same content" do
+      other = described_class.new(organization: org, name: name, description: [desc])
+      expect(subject).to eq other
+    end
+
+    it "different content" do
+      name = RelatonBib::LocalizedString.new("Other", "en")
+      other = described_class.new(organization: org, name: name, description: [desc])
+      expect(subject).not_to eq other
+    end
+  end
+
   context "render affiliation" do
     context "with all fields" do
       it "as XML" do
diff --git a/spec/relaton_bib/formatted_string_spec.rb b/spec/relaton_bib/formatted_string_spec.rb
index 7c6d6ec..93aeb24 100644
--- a/spec/relaton_bib/formatted_string_spec.rb
+++ b/spec/relaton_bib/formatted_string_spec.rb
@@ -7,6 +7,18 @@
       XML
     end
 
+    context "==" do
+      it "same content" do
+        other = RelatonBib::FormattedString.new content: subject.content, language: "en", script: "Latn", format: "text/html"
+        expect(subject).to eq other
+      end
+
+      it "different content" do
+        other = RelatonBib::FormattedString.new content: "other", language: "en", script: "Latn", format: "text/html"
+        expect(subject).not_to eq other
+      end
+    end
+
     context "escape" do
       it "&" do
         xml = Nokogiri::XML::Builder.new do |b|
diff --git a/spec/relaton_bib/full_name_spec.rb b/spec/relaton_bib/full_name_spec.rb
new file mode 100644
index 0000000..9116b6b
--- /dev/null
+++ b/spec/relaton_bib/full_name_spec.rb
@@ -0,0 +1,131 @@
+describe RelatonBib::FullName do
+  context "using name parts" do
+    subject do
+      described_class.new(
+        surname: RelatonBib::LocalizedString.new("Doe"),
+        abbreviation: RelatonBib::LocalizedString.new("DJ"),
+        forename: [RelatonBib::Forename.new(content: "John", initial: "J")],
+        initials: RelatonBib::LocalizedString.new("J.D."),
+        addition: [RelatonBib::LocalizedString.new("Jr.")],
+        prefix: [RelatonBib::LocalizedString.new("Dr.")],
+      )
+    end
+
+    context "==" do
+      it "same content" do
+        other = described_class.new(
+          surname: RelatonBib::LocalizedString.new("Doe"),
+          abbreviation: RelatonBib::LocalizedString.new("DJ"),
+          forename: [RelatonBib::Forename.new(content: "John", initial: "J")],
+          initials: RelatonBib::LocalizedString.new("J.D."),
+          addition: [RelatonBib::LocalizedString.new("Jr.")],
+          prefix: [RelatonBib::LocalizedString.new("Dr.")],
+        )
+        expect(subject).to eq other
+      end
+
+      it "different content" do
+        other = described_class.new(
+          surname: RelatonBib::LocalizedString.new("Doe"),
+          abbreviation: RelatonBib::LocalizedString.new("DJ"),
+          forename: [RelatonBib::Forename.new(content: "John", initial: "J")],
+          initials: RelatonBib::LocalizedString.new("J.D."),
+          prefix: [RelatonBib::LocalizedString.new("Dr.")],
+        )
+        expect(subject).not_to eq other
+      end
+    end
+
+    it "to_xml" do
+      builder = Nokogiri::XML::Builder.new
+      subject.to_xml(builder: builder)
+      expect(builder.to_xml).to be_equivalent_to <<~XML
+        <name>
+          <abbreviation>DJ</abbreviation>
+          <prefix>Dr.</prefix>
+          <forename initial="J">John</forename>
+          <formatted-initials>J.D.</formatted-initials>
+          <surname>Doe</surname>
+          <addition>Jr.</addition>
+        </name>
+      XML
+    end
+
+    it "to_hash" do
+      expect(subject.to_hash).to eq(
+        "abbreviation" => { "content" => "DJ" },
+        "given" => {
+          "forename" => [{ "content" => "John", "initial" => "J" }],
+          "formatted_initials" => { "content" => "J.D." },
+        },
+        "surname" => { "content" => "Doe" },
+        "addition" => [{ "content" => "Jr." }],
+        "prefix" => [{ "content" => "Dr." }],
+      )
+    end
+
+    it "to_asciibib" do
+      expect(subject.to_asciibib("name")).to eq <<~ASCIIBIB
+        name.name.abbreviation:: DJ
+        name.given.forename:: John
+        name.given.forename.initial:: J
+        name.given.formatted-initials:: J.D.
+        name.name.surname:: Doe
+        name.name.addition:: Jr.
+        name.name.prefix:: Dr.
+      ASCIIBIB
+    end
+  end
+
+  context "using completename" do
+    subject do
+      described_class.new(
+        completename: RelatonBib::LocalizedString.new("John Doe"),
+      )
+    end
+
+    context "==" do
+      it "same content" do
+        other = described_class.new(
+          completename: RelatonBib::LocalizedString.new("John Doe"),
+        )
+        expect(subject).to eq other
+      end
+
+      it "different content" do
+        other = described_class.new(
+          completename: RelatonBib::LocalizedString.new("Jane Doe"),
+        )
+        expect(subject).not_to eq other
+      end
+    end
+
+    it "to_xml" do
+      builder = Nokogiri::XML::Builder.new
+      subject.to_xml(builder: builder, lang: "en")
+      expect(builder.to_xml).to be_equivalent_to <<~XML
+        <name>
+          <completename>John Doe</completename>
+        </name>
+      XML
+    end
+
+    it "to_hash" do
+      expect(subject.to_hash).to eq(
+        "completename" => { "content" => "John Doe" },
+      )
+    end
+
+    it "to_asciibib" do
+      expect(subject.to_asciibib("name")).to eq <<~ASCIIBIB
+        name.name.completename:: John Doe
+      ASCIIBIB
+    end
+  end
+
+  it "raise ArgumentError" do
+    expect do
+      described_class.new
+    end.to raise_error ArgumentError, "Should be given :surname or :completename"
+  end
+end
diff --git a/spec/relaton_bib/organization_spec.rb b/spec/relaton_bib/organization_spec.rb
index 5494dda..dd06f51 100644
--- a/spec/relaton_bib/organization_spec.rb
+++ b/spec/relaton_bib/organization_spec.rb
@@ -1,4 +1,45 @@
-RSpec.describe RelatonBib::OrgIdentifier do
+describe RelatonBib::Organization do
+  subject do
+    described_class.new(
+      name: "Org",
+      abbreviation: "ORG",
+      subdivision: [RelatonBib::LocalizedString.new("Subdivision", "en")],
+      url: "http://example.com",
+      identifier: [RelatonBib::OrgIdentifier.new("uri", "http://example.com")],
+      contact: [RelatonBib::Contact.new(type: "work", value: "http://example.com")],
+      logo: RelatonBib::Image.new(id: "IMG", src: "http://example.com/logo.png", mimetype: "image/png")
+    )
+  end
+
+  context "==" do
+    it "same content" do
+      other = described_class.new(
+        name: "Org",
+        abbreviation: "ORG",
+        subdivision: [RelatonBib::LocalizedString.new("Subdivision", "en")],
+        url: "http://example.com",
+        identifier: [RelatonBib::OrgIdentifier.new("uri", "http://example.com")],
+        contact: [RelatonBib::Contact.new(type: "work", value: "http://example.com")],
+        logo: RelatonBib::Image.new(id: "IMG", src: "http://example.com/logo.png", mimetype: "image/png")
+      )
+      expect(subject).to eq other
+    end
+
+    it "different content" do
+      other = described_class.new(
+        name: "Org",
+        abbreviation: "ORG",
+        subdivision: [RelatonBib::LocalizedString.new("Subdivision", "en")],
+        url: "http://example.com",
+        identifier: [RelatonBib::OrgIdentifier.new("uri", "http://example.com")],
+        contact: [RelatonBib::Contact.new(type: "work", value: "http://example.com")]
+      )
+      expect(subject).not_to eq other
+    end
+  end
+end
+
+describe RelatonBib::OrgIdentifier do
   # it "raises invalid type argument error" do
   #   expect { RelatonBib::OrgIdentifier.new "type", "value" }.to raise_error ArgumentError
   # end