# -*- coding: utf-8 -*- require 'spec_helper' module CiteProc describe 'CiteProc Names' do let(:poe) { Name.new(:family => 'Poe', :given => 'Edgar Allen') } let(:joe) { Name.new(:given => 'Joe') } let(:plato) { Name.new(:given => 'Plato') } let(:aristotle) { Name.new(:given => 'Ἀριστοτέλης') } let(:dostoyevksy) { Name.new(:given => 'Фёдор Михайлович', :family => 'Достоевский') } let(:utf) { Name.new( :given => 'Gérard', :'dropping-particle' => 'de', :'non-dropping-particle' => 'la', :family => 'Martinière', :suffix => 'III') } let(:markup) { Name.new( :given => 'Gérard', :'dropping-particle' => 'de', :'non-dropping-particle' => 'la', :family => 'Martinière', :suffix => 'III') } let(:japanese) { Name.new( "family" => "穂積", "given" => "陳重") } let(:saunders) { Name.new("family" => "Saunders", "given" => "John Bertrand de Cusance Morant") } let(:humboldt) { Name.new( "family" => "Humboldt", "given" => "Alexander", "dropping-particle" => "von") } let(:van_gogh) { Name.new( "family" => "Gogh", "given" => "Vincent", "non-dropping-particle" => "van") } let(:jr) { Name.new( "family" => "Stephens", "given" => "James", "suffix" => "Jr.") } let(:frank) { Name.new( "family" => "Bennett", "given" => "Frank G.", "suffix" => "Jr.", "comma-suffix" => "true") } let(:ramses) { Name.new( :family => 'Ramses', :given => 'Horatio', :suffix => 'III') } describe Name do it { is_expected.not_to be_nil } describe 'formatting options' do it 'does not always demote particle by default' do expect(Name.new.always_demote_particle?).to be false expect(Name.new.always_demote_non_dropping_particle?).to be false end it 'does not demote particle by default' do expect(Name.new.demote_particle?).to be false expect(Name.new.demote_non_dropping_particle?).to be false end it 'does not demote particle in sort order by default' do expect(Name.new.sort_order!.demote_particle?).to be false expect(Name.new.sort_order!.demote_non_dropping_particle?).to be false end it 'always demotes particle if option is set' do expect(Name.new({}, :'demote-non-dropping-particle' => 'display-and-sort').always_demote_particle?).to be true expect(Name.new({}, :'demote-non-dropping-particle' => 'display-and-sort').always_demote_non_dropping_particle?).to be true end it 'demotes particle in sort order if option is set to sort-only' do expect(Name.new({}, :'demote-non-dropping-particle' => 'display-and-sort').sort_order!.demote_particle?).to be true end it 'never demotes particle by default' do expect(Name.new.never_demote_particle?).to be true expect(Name.new.never_demote_non_dropping_particle?).to be true end it 'is not in sort order by default' do expect(Name.new.sort_order?).to be false end it 'uses the long form by default' do expect(Name.new).to be_long_form end it 'does not use short form by default' do expect(Name.new).not_to be_short_form end end describe '#initials' do let(:name) { Name.new(nil, :'initialize-with' => '. ') } it 'returns the given name initials' do name.given = 'Edgar A' expect(name.initials).to eq('E. A.') name.options[:initialize] = 'false' expect(name.initials).to eq('Edgar A.') end describe 'private helpers' do it '#initials_of initializes the given string' do expect(name.send(:initials_of, 'James T.')).to eq('J. T.') expect(name.send(:initials_of, 'JT')).to eq('J. T.') expect(name.send(:initials_of, 'James T')).to eq('J. T.') expect(name.send(:initials_of, 'Jean-Luc')).to eq('J.-L.') expect(name.send(:initials_of, 'Vérité Äpfel')).to eq('V. Ä.') name.initialize_without_hyphen! expect(name.send(:initials_of, 'Jean-Luc')).to eq('J. L.') name.options[:'initialize-with'] = '.' expect(name.send(:initials_of, 'James T.')).to eq('J.T.') expect(name.send(:initials_of, 'James T')).to eq('J.T.') expect(name.send(:initials_of, 'Jean-Luc')).to eq('J.L.') name.options[:'initialize-with-hyphen'] = true expect(name.send(:initials_of, 'Jean-Luc')).to eq('J.-L.') end it '#initialize_existing_only initializes only current initials' do expect(name.send(:existing_initials_of, 'James T. Kirk')).to eq('James T. Kirk') expect(name.send(:existing_initials_of, 'James T.Kirk')).to eq('James T. Kirk') expect(name.send(:existing_initials_of, 'James T')).to eq('James T.') expect(name.send(:existing_initials_of, 'Jean-Luc')).to eq('Jean-Luc') expect(name.send(:existing_initials_of, 'J.-L.M.')).to eq('J.-L. M.') expect(name.send(:existing_initials_of, 'J-L')).to eq('J.-L.') expect(name.send(:existing_initials_of, 'J-LM')).to eq('J.-L. M.') expect(name.send(:existing_initials_of, 'JT')).to eq('J. T.') end end end describe 'constructing' do describe '.new' do it 'accepts a symbolized hash' do expect(Name.new(:family => 'Doe').format).to eq('Doe') end it 'accepts a stringified hash' do expect(Name.new('family' => 'Doe').format).to eq('Doe') end end end describe '#dup' do it 'returns a new name copied by value' do expect(poe.dup.upcase!.format).not_to eq(poe.format) end end describe 'script awareness' do it 'english names are romanesque' do expect(frank).to be_romanesque end it 'ancient greek names are romanesque' do expect(aristotle).to be_romanesque end it 'russian names are romanesque' do expect(dostoyevksy).to be_romanesque end it 'japanese names are not romanesque' do expect(japanese).not_to be_romanesque end it 'german names are romanesque' do expect(Name.new(:given => 'Friedrich', :family => 'Hölderlin')).to be_romanesque end it 'french names are romanesque' do expect(utf).to be_romanesque end it 'markup does not interfere with romanesque test' do expect(markup).to be_romanesque end end describe 'literals' do it 'is a literal if the literal attribute is set' do expect(Name.new(:literal => 'GNU/Linux')).to be_literal end it 'is not literal by default' do expect(Name.new).not_to be_literal end it 'is literal even if other name parts are set' do expect(Name.new(:family => 'Tux', :literal => 'GNU/Linux')).to be_literal end end describe 'in-place manipulation (bang! methods)' do it 'delegates to string for family name' do expect(plato.swapcase!.format).to eq('pLATO') end it 'delegates to string for given name' do expect(humboldt.gsub!(/^Alex\w*/, 'Wilhelm').format).to eq('Wilhelm von Humboldt') end it 'delegates to string for dropping particle' do expect(humboldt.upcase!.dropping_particle).to eq('VON') end it 'delegates to string for non dropping particle' do expect(van_gogh.upcase!.non_dropping_particle).to eq('VAN') end it 'delegates to string for suffix' do expect(frank.sub!(/jr./i, 'Sr.').format).to eq('Frank G. Bennett, Sr.') end it 'returns the name object' do expect(poe.upcase!).to be_a(Name) end end describe '#format' do it 'returns an empty string by default' do expect(Name.new.format).to be_empty end it 'returns the last name if only last name is set' do expect(Name.new(:family => 'Doe').format).to eq('Doe') end it 'returns the first name if only the first name is set' do expect(Name.new(:given => 'John').format).to eq('John') end it 'prints japanese names using static ordering' do expect(japanese.format).to eq('穂積 陳重') end it 'returns the literal if the name is a literal' do Name.new(:literal => 'GNU/Linux').format == 'GNU/Linux' end it 'returns the name in display order by default' do expect(Name.new(:family => 'Doe', :given => 'John').format).to eq('John Doe') end it 'returns the name in sort order if the sort order option is active' do expect(Name.new(:family => 'Doe', :given => 'John').sort_order!.format).to eq('Doe, John') end it 'returns the full given name' do expect(saunders.format).to eq('John Bertrand de Cusance Morant Saunders') end it 'includes dropping particles' do expect(humboldt.format).to eq('Alexander von Humboldt') end it 'includes non dropping particles' do expect(van_gogh.format).to eq('Vincent van Gogh') end it 'includes suffices' do expect(jr.format).to eq('James Stephens Jr.') end it 'uses the comma suffix option' do expect(frank.format).to eq('Frank G. Bennett, Jr.') end it 'prints unicode characters' do expect(utf.format).to eq("Gérard de la Martinière III") end it 'prints russian names normally' do expect(dostoyevksy.format).to eq('Фёдор Михайлович Достоевский') end describe 'when static ordering is active' do it 'always prints the family name first' do expect(poe.static_order!.format).to eq('Poe Edgar Allen') end end describe 'when the sort order option is active' do it 'returns an empty string by default' do expect(Name.new.sort_order!.format).to be_empty end it 'returns the last name if only last name is set' do expect(Name.new({:family => 'Doe'}, { :'name-as-sort-order' => true }).format).to eq('Doe') end it 'returns the first name if only the first name is set' do expect(Name.new(:given => 'John').sort_order!.format).to eq('John') end it 'prints japanese names using static ordering' do expect(japanese.sort_order!.format).to eq('穂積 陳重') end it 'returns the literal if the name is a literal' do Name.new(:literal => 'GNU/Linux').sort_order!.format == 'GNU/Linux' end it 'uses comma for suffix if comma suffix is set' do expect(frank.sort_order!.format).to eq('Bennett, Frank G., Jr.') end it 'also uses comma for suffix if comma suffix is *not* set' do expect(jr.sort_order!.format).to eq('Stephens, James, Jr.') end it 'for normal names it prints them as "family, given"' do expect(poe.sort_order!.format).to eq('Poe, Edgar Allen') end it 'particles come after given name by default' do expect(van_gogh.sort_order!.format).to eq('van Gogh, Vincent') end it 'particles come after given name if demote option is active' do expect(van_gogh.sort_order!.demote_particle!.format).to eq('Gogh, Vincent van') end it 'dropping particles come after given name' do expect(humboldt.sort_order!.format).to eq('Humboldt, Alexander von') end it 'by default if all parts are set they are returned as "particle family, first dropping-particle, suffix"' do expect(utf.sort_order!.format).to eq('la Martinière, Gérard de, III') end end end describe '#sort_order' do it 'returns only a single token for literal names' do expect(Name.new(:literal => 'ACME Corp.').sort_order.size).to eq(1) end it 'strips leading "the" off literal names' do expect(Name.new(:literal => 'The ACME Corp.').sort_order[0]).to eq('ACME Corp.') end it 'strips leading "a" off literal names' do expect(Name.new(:literal => 'A Company').sort_order[0]).to eq('Company') end it 'strips leading "an" off literal names' do expect(Name.new(:literal => 'an ACME Corp.').sort_order[0]).to eq('ACME Corp.') end it 'strips leading "l\'" off literal names' do expect(Name.new(:literal => "L'Augustine").sort_order[0]).to eq('Augustine') end it 'always returns four tokens for non literal names' do expect(poe.sort_order.size).to eq(4) expect(joe.sort_order.size).to eq(4) expect(aristotle.sort_order.size).to eq(4) expect(utf.sort_order.size).to eq(4) expect(frank.sort_order.size).to eq(4) expect(japanese.sort_order.size).to eq(4) end it 'demotes non dropping particles if option is set' do expect(van_gogh.demote_particle!.sort_order).to eq(['Gogh', 'van', 'Vincent', '']) end it 'does not demote non dropping particles by default' do expect(van_gogh.sort_order).to eq(['van Gogh', '', 'Vincent', '']) end it 'does not demote non dropping particles by default but dropping particles are demoted' do expect(utf.sort_order).to eq(['la Martinière', 'de', 'Gérard', 'III']) end it 'demotes dropping particles' do expect(humboldt.sort_order).to eq(['Humboldt', 'von', 'Alexander', '']) end it 'combines non dropping particles with family name if option demote-non-dropping-particles is not active' do expect(van_gogh.never_demote_particle!.sort_order).to eq(['van Gogh', '', 'Vincent', '']) end end describe 'sorting' do it 'sorts by sort order by default' do expect([poe, utf, joe, plato].sort).to eq([joe, plato, utf, poe]) end end end describe Names do let(:gang_of_four) { Names.parse!('Erich Gamma and Richard Helm and Ralph Johnson and John Vlissides') } it { is_expected.not_to be nil } it { is_expected.not_to be_numeric } describe 'constructing' do it 'accepts a single name' do expect { Names.new(joe) }.not_to raise_error end it 'accepts a single name as hash' do expect(Names.new(:given => 'Jim').names.size).to eq(1) end it 'accepts two names' do expect(Names.new(joe, poe).names.size).to eq(2) end it 'accepts two names as hash' do expect(Names.new({:given => 'Jim'}, {:family => 'Jameson'}).names.size).to eq(2) end it 'accepts an array of names' do expect(Names.new([joe, poe]).names.size).to eq(2) end end describe 'parsing' do it 'accepts a single name as a string' do expect(Names.parse('Edgar A. Poe').names.size).to eq(1) end it 'accepts multiple names as a string' do expect(Names.parse('Edgar A. Poe and Hawthorne, Nathaniel and Herman Melville').names.size).to eq(3) end it 'parses the passed-in family names' do expect(Names.parse('Edgar A. Poe and Hawthorne, Nathaniel and Herman Melville').map { |n| n.values_at(:family) }.flatten).to eq(%w{ Poe Hawthorne Melville }) end it '#parse returns nil on error' do expect(Names.parse(23)).to be_nil end it '#parse! raises an error on bad input' do expect { Names.parse!('23') }.to raise_error(ParseError) end end describe '#strip_markup' do it 'strips markup off string representation' do expect(Names.new(markup).strip_markup).to eq(utf.to_s) end it 'when using the bang! version, strips markup off each name part' do expect(Names.new(markup).strip_markup![0]).to eq(utf) end end describe 'bang! methods' do it 'delegate to the individual names and return self' do expect(Names.new(poe, plato, joe).upcase!.map(&:given)).to eq(['EDGAR ALLEN', 'PLATO', 'JOE']) end end [:never, :always, :contextually].each do |setting| setter = "delimiter_#{setting}_precedes_last!" predicate = "delimiter_#{setting}_precedes_last?" describe "##{setter}" do it 'sets the delimiter precedes last option accordingly' do expect(Names.new.send(setter).send(predicate)).to eq(true) end end end describe '#delimiter_precedes_last' do it 'returns false by default' do expect(Names.new(joe)).not_to be_delimiter_precedes_last end it 'returns false by default for a single name' do expect(Names.new(joe)).not_to be_delimiter_precedes_last end it 'returns false by default for two names' do expect(Names.new(joe, poe)).not_to be_delimiter_precedes_last end it 'returns true for two names when option set to always' do expect(Names.new(joe, poe).delimiter_always_precedes_last!).to be_delimiter_precedes_last end it 'returns true by default for three names' do expect(Names.new(joe, poe, plato)).to be_delimiter_precedes_last end it 'returns false for three names when option set to :never' do expect(Names.new(joe, poe, plato).delimiter_never_precedes_last!).not_to be_delimiter_precedes_last end end describe '#to_bibtex' do describe 'when there is only a single name' do it 'prints the name in sort order' do expect(Names.new(poe).to_bibtex).to eq('Poe, Edgar Allen') end end describe 'when there are two or more names' do it 'prints the names in sort order connected with the word "and"' do expect(Names.new(poe, plato, humboldt).to_bibtex).to eq('Poe, Edgar Allen and Plato and Humboldt, Alexander von') end end end describe '#to_s' do describe 'when the number of names exceeds the et-al-min option' do before do gang_of_four.options[:'et-al-min'] = 3 gang_of_four.options[:'et-al-use-first'] = 2 gang_of_four.options[:'et-al'] = 'FOO' end it 'prints only the et-al-use-first names' do expect(gang_of_four.to_s).to match(/gamma.+helm/i) expect(gang_of_four.to_s).not_to match(/johnson|vlissides/i) end it 'adds et-al at the end' do expect(gang_of_four.to_s).to end_with('FOO') end it 'adds the delimiter before et-al when multiple names are printed' do expect(gang_of_four.to_s).to end_with(', FOO') end it 'does not add the delimiter before et-al when only one name is printed' do gang_of_four.options[:'et-al-use-first'] = 1 expect(gang_of_four.to_s).not_to end_with(', FOO') end end it 'squeezes multiple whitespace between delimiter and connector' do expect(Names.new(poe, humboldt, van_gogh, joe).to_s).not_to match(/\s{2}/) end end describe '#each' do it 'returns an enumerator when no block given' do expect(gang_of_four.each).to respond_to(:each) end end describe '#to_citeproc' do it 'returns a list of hashes' do expect(gang_of_four.to_citeproc.map(&:class).uniq).to eq([Hash]) end end describe 'sorting' do it 'accepts other Names instance' do expect(Names.new(poe, plato) <=> Names.new(plato)).to equal(1) expect(Names.new(plato) <=> Names.new(poe, plato)).to equal(-1) end it 'accepts other list of names' do expect(Names.new(poe, plato) <=> [plato]).to equal(1) expect(Names.new(plato) <=> [poe, plato]).to equal(-1) end end describe '#inspect' do it 'returns a string' do expect(gang_of_four.inspect).to be_a(String) end end end end end