require 'helper.rb' module BibTeX class EntryTest < Minitest::Spec describe 'a new entry' do it "won't be nil" do _(Entry.new).wont_be_nil end end describe '#add' do it 'preserves BibTeX::Names (and other subclasses of BibTeX::Value)' do e = Entry.new e.add(:author, Names.new(Name.new(first: 'first_name'))) assert_equal e[:author].class, Names end end describe 'cross-references' do it 'has no cross-reference by default' do assert_equal false, Entry.new.has_cross_reference? end it 'is not cross-referenced by default' do assert_equal false, Entry.new.cross_referenced? _(Entry.new.cross_referenced_by).must_be_empty end describe 'given a bibliography with cross referenced entries' do before do @bib = Bibliography.parse <<-END @book{a, editor = "A", title = "A"} @incollection{a1, crossref = "a"} @incollection{b1, crossref = "b2"} @book{b, author = "B", title = "B", date = "2020", urldate = "2020"} END end describe '#has_cross_reference?' do it 'returns true if the entry has a valid cross-reference' do assert_equal true, @bib['a1'].has_cross_reference? end it 'returns false if the entry has no valid cross-reference' do assert_equal false, @bib['a'].has_cross_reference? assert_equal false, @bib['b1'].has_cross_reference? end end describe '#cross_referenced?' do it 'returns true if the entry is cross-referenced by another entry' do assert_equal true, @bib['a'].cross_referenced? end it 'returns false if the entry is not cross-referenced' do assert_equal false, @bib['a1'].cross_referenced? end end describe '#cross_referenced_by' do it 'returns a list of all entries that cross-reference this entry' do @bib['a'].cross_referenced_by.must_include(@bib['a1']) end it 'returns an empty list if there are no cross-references to this entry' do @bib['a1'].cross_referenced_by.must_be_empty end end describe '#respond_to?' do it 'takes into account the inherited attributes' do @bib['a1'].respond_to?(:title) end end describe 'resolve field values using array accessors #[]' do describe 'when a "title" is set in the entry itself' do before { @bib['a1'].title = 'A1' } it 'returns the title' do _(@bib['a1'].title).must_be :==, 'A1' end end describe 'when "title" is undefined for the entry but defined in the reference' do it 'returns the referenced title' do _(@bib['a1'].title).must_be :==, @bib['a'].title end end describe 'when "booktitle" is undefined for the entry but defined in the reference' do before { @bib['a'].booktitle = 'A Booktitle' } it 'returns the referenced booktitle' do _(@bib['a1'].booktitle).must_be :==, @bib['a'].booktitle end end describe 'when "booktitle" is undefined for the entry and the reference but the reference has a "title"' do it "returns the reference's title" do _(@bib['a1'].booktitle).must_be :==, @bib['a'].title end end it 'does not store referenced values permanently' do refute_nil @bib['a1'].booktitle assert_nil @bib['a1'].fields[:booktitle] end describe '#inherited_fields' do it 'returns an empty list by default' do Entry.new.inherited_fields.must_be_empty end it 'returns an empty list if this entry has no cross-reference' do @bib['a'].inherited_fields.must_be_empty end it 'returns an empty list if this entry has a cross-reference but the reference does not exist in the bibliography' do @bib['b1'].inherited_fields.must_be_empty end it 'returns a list of all fields not set in the field but in the reference' do @bib['a1'].inherited_fields.must_be :==, %i[booktitle editor title] end end describe '#to_citeproc' do it 'includes inherited values' do @bib['a1'].to_citeproc['container-title'].must_be :==, @bib['a'].title.to_s end it 'converts urldate' do @bib['b'].to_citeproc['accessed'].must_be :==, 'date-parts' => [[2020]] end end describe '#save_inherited_fields' do it 'copies referenced values to the entry' do @bib['a1'].title = 'a1' @bib['a1'].save_inherited_fields @bib['a1'].fields[:booktitle].must_be :==, @bib['a'].title @bib['a1'].fields[:title].wont_be :==, @bib['a'].title end end end end end describe '#names' do it 'returns an empty list by default' do Entry.new.names.must_be :==, [] end it 'returns the author (if set)' do Entry.new(author: 'A').names.must_be :==, %w[A] end it 'returns all authors (if set)' do Entry.new(author: 'A B and C D').parse_names.names.length.must_be :==, 2 end it 'returns the editor (if set)' do Entry.new(editor: 'A').names.must_be :==, %w[A] end it 'returns the translator (if set)' do Entry.new(translator: 'A').names.must_be :==, %w[A] end end describe 'month conversion' do before do @entry = Entry.new end [[:jan, 'January'], [:feb, 'February'], [:sep, 'September']].each do |m| it 'should convert english months' do @entry.month = m[1] assert_equal m[0], @entry.month.v end end [%i[jan jan], %i[feb feb], %i[sep sep]].each do |m| it 'should convert bibtex abbreviations' do @entry.month = m[1] assert_equal m[0], @entry.month.v end end it 'prints parsed months as symbols' do @entry.month = :aug assert_match(/month = aug/, @entry.content) assert_match(/month = aug/, @entry.to_s) end [[:jan, 1], [:feb, 2], [:sep, 9]].each do |m| it 'should convert numbers' do @entry.month = m[1] assert_equal m[0], @entry.month.v end it 'should convert numbers when parsing' do @entry = Entry.parse("@misc{id, month = #{m[1]}}")[0] assert_equal m[0], @entry.month.v assert_equal m[1], @entry.month_numeric end end end describe '#values_at' do it 'returns an empty array by default' do assert_equal [], Entry.new.values_at end it 'returns an empty array when given no arguments' do assert_equal [], Entry.new(title: 'foo').values_at end it 'returns a nil array if the passed in key is not set' do assert_equal [nil], Entry.new.values_at(:title) end it 'returns an array with the value of the passed in key' do assert_equal ['x'], Entry.new(title: 'x').values_at(:title) assert_equal %w[a b], Entry.new(title: 'b', year: 'a').values_at(:year, :title) end end describe '#digest' do it 'returns an empty string by default' do assert_equal '', Entry.new.digest end it 'includes type and all defined fields' do assert_equal 'book', Entry.new(bibtex_type: 'book').digest assert_equal 'book|title:foo', Entry.new(bibtex_type: 'book', title: 'foo').digest end it 'accepts a filter' do assert_equal 'book|year:2012', Entry.new(bibtex_type: 'book', title: 'foo', year: 2012).digest([:year]) end end describe 'given an entry' do before do @entry = Entry.new do |e| e.type = :book e.key = :key e.title = 'Moby Dick' e.author = 'Herman Melville' e.publisher = 'Penguin' e.address = 'New York' e.month = 'Nov' e.year = 1993 e.parse_names end end it 'supports renaming! of field attributes' do @entry.rename!(title: :foo) refute @entry.has_field?(:title) assert_equal 'Moby Dick', @entry[:foo] end it 'supports renaming of field attributes' do e = @entry.rename(title: :foo) assert @entry.has_field?(:title) refute @entry.has_field?(:foo) assert e.has_field?(:foo) refute e.has_field?(:title) assert_equal 'Moby Dick', @entry[:title] assert_equal 'Moby Dick', e[:foo] end describe 'citeproc export' do it 'supports citeproc export' do e = @entry.to_citeproc assert_equal 'book', e['type'] assert_equal 'New York', e['publisher-place'] assert_equal [1993, 11], e['issued']['date-parts'][0] assert_equal 1, e['author'].length assert_equal 'Herman', e['author'][0]['given'] assert_equal 'Melville', e['author'][0]['family'] end it 'sets both issue and number in techreports' do report = Entry.new do |r| r.type = :techreport r.number = 1 end.to_citeproc assert_equal '1', report['number'] assert_equal '1', report['issue'] end it 'uses authority and publisher for proceedings' do proceedings = Entry.new do |p| p.type = :inproceedings p.publisher = 'Publisher' p.organization = 'Organization' end.to_citeproc assert_equal 'Publisher', proceedings['publisher'] assert_equal 'Organization', proceedings['authority'] proceedings = Entry.new do |p| p.type = :inproceedings p.organization = 'Organization' end.to_citeproc assert_equal 'Organization', proceedings['publisher'] refute proceedings.key?('authority') proceedings = Entry.new do |p| p.type = :inproceedings p.publisher = 'Publisher' end.to_citeproc assert_equal 'Publisher', proceedings['publisher'] refute proceedings.key?('authority') end it 'uses event_place for conferences' do conference = Entry.new do |p| p.type = :conference p.address = 'Place' end.to_citeproc assert_equal 'Place', conference['event-place'] end it 'support literal dates in citeproc export' do @entry.year = 'Test' e = @entry.to_citeproc assert_equal({ 'literal' => 'Test' }, e['issued']) end it 'combines year, month and day in issued date' do @entry.update year: 2005, month: 5, day: 31 assert_equal [[2005, 5, 31]], @entry.to_citeproc['issued']['date-parts'] end it 'prefers biblatex syntax' do @entry.date = '2014-07-13' assert_equal [[2014, 7, 13]], @entry.to_citeproc['issued']['date-parts'] end it 'supports biblatex date ranges' do @entry.date = '2014-06-12/2014-07-13' assert_equal [[2014, 6, 12], [2014, 7, 13]], @entry.to_citeproc['issued']['date-parts'] end it 'supports biblatex partial dates' do @entry.date = '2014-07' assert_equal [[2014, 7]], @entry.to_citeproc['issued']['date-parts'] end end describe 'given a filter object or a filter name' do class SuffixB < BibTeX::Filter def apply(value) value.is_a?(::String) ? "#{value}b" : value end end before do @filter = Object.new def @filter.apply(value) value.is_a?(::String) ? value.upcase : value end end it 'supports arbitrary conversion' do @entry.convert(@filter).title.must_equal 'MOBY DICK' @entry.convert(:suffixb).title.must_equal 'Moby Dickb' end it 'supports multiple filters' do @entry.convert(@filter, :suffixb).title.must_equal 'MOBY DICKb' @entry.convert(:suffixb, @filter).title.must_equal 'MOBY DICKB' end it 'supports arbitrary in-place conversion' do @entry.convert!(@filter).title.must_equal 'MOBY DICK' end it 'supports conditional arbitrary in-place conversion' do @entry.convert!(@filter) { |k, _v| k.to_s =~ /publisher/i } assert_equal 'Moby Dick', @entry.title assert_equal 'PENGUIN', @entry.publisher end it 'supports conditional arbitrary conversion' do e = @entry.convert(@filter) { |k, _v| k.to_s =~ /publisher/i } assert_equal 'Moby Dick', e.title assert_equal 'PENGUIN', e.publisher assert_equal 'Penguin', @entry.publisher end end describe 'LaTeX filter' do before do @entry.title = 'M\\"{o}by Dick' end describe '#convert' do it 'converts LaTeX umlauts' do @entry.convert(:latex).title.must_be :==, 'Möby Dick' end it 'does not change the original entry' do e = @entry.convert(:latex) e.wont_be :==, @entry e.title.to_s.wont_be :==, @entry.title.to_s e.title.to_s.length.must_be :<, @entry.title.to_s.length end it 'does not change the author of a parsed entry' do entry = BibTeX.parse('@book{o, author = {M\\"{o}by Dick}}')[0] e = entry.convert(:latex) e.wont_be :==, entry e.author.to_s.length.must_be :<, entry.author.to_s.length end end describe '#convert!' do it 'converts LaTeX umlauts' do @entry.convert!(:latex).title.must_be :==, 'Möby Dick' end it 'changes the original entry in-place' do e = @entry.convert!(:latex) e.must_be :equal?, @entry e.title.to_s.length.must_be :==, @entry.title.to_s.length end end end end describe '#to_citeproc' do before do @entry = Entry.new do |e| e.type = :book e.key = :key e.author = 'van Beethoven, Ludwig' e.parse_names end end it 'should use non-dropping-particle by default' do assert_equal 'van', @entry.to_citeproc['author'][0]['non-dropping-particle'] end it 'should accept option to use non-dropping-particle' do assert_equal 'van', @entry.to_citeproc(particle: 'non-dropping-particle')['author'][0]['non-dropping-particle'] end describe 'howpublished' do it 'converts to "URL" when there is a url (without filters)' do bibtex = <<-END @book{noauthor_2017-xx, title = "Google", howpublished = "\\url{http://www.google.com/}" } END bibliography = Bibliography.parse(bibtex) citeproc = bibliography.to_citeproc assert_equal('http://www.google.com/', citeproc[0]['URL']) assert_nil(citeproc[0]['publisher']) end it 'converts to "URL" when there is a url (with latex)' do bibtex = <<-END @book{noauthor_2017-xx, title = "Google", howpublished = "\\url{http://www.google.com/}" } END bibliography = Bibliography.parse(bibtex, filter: :latex) citeproc = bibliography.to_citeproc assert_equal('http://www.google.com/', citeproc[0]['URL']) end end end describe '#to_s' do before do @bib = Bibliography.parse(<<-BIBINPUT, parse_months: false) @misc{foo, title = {A title}, series = {A series}, month = dec } BIBINPUT end it 'applies filters when converting to strings' do assert_equal "@misc{foo,\n"\ " title = {A title},\n"\ " series = {A series},\n"\ " month = dec\n"\ "}\n", @bib['foo'].to_s(filter: :linebreaks) end end def test_simple bib = BibTeX::Bibliography.open(Test.fixtures(:entry), debug: false) refute_nil(bib) assert_equal(BibTeX::Bibliography, bib.class) assert_equal(4, bib.data.length) assert_equal([BibTeX::Entry], bib.data.map(&:class).uniq) assert_equal('key:0', bib.data[0].key) assert_equal('key:1', bib.data[1].key) assert_equal('foo', bib.data[2].key) assert_equal(:book, bib.data[0].type) assert_equal(:article, bib.data[1].type) assert_equal(:article, bib.data[2].type) assert_equal('Poe, Edgar A.', bib.data[0][:author].to_s) assert_equal('Hawthorne, Nathaniel', bib.data[1][:author].to_s) assert_equal('2003', bib.data[0][:year]) assert_equal('2001', bib.data[1][:year]) assert_equal('American Library', bib.data[0][:publisher]) assert_equal('American Library', bib.data[1][:publisher]) assert_equal('Selected \\emph{Poetry} and `Tales\'', bib.data[0].title) assert_equal('Tales and Sketches', bib.data[1].title) end def test_ghost_methods bib = BibTeX::Bibliography.open(Test.fixtures(:entry), debug: false) assert_equal 'Poe, Edgar A.', bib[0].author.to_s expected = 'Poe, Edgar Allen' bib.data[0].author = expected assert_equal expected, bib[0].author.to_s end def test_creation_simple entry = BibTeX::Entry.new entry.type = :book entry.key = :raven entry.author = 'Poe, Edgar A.' entry.title = 'The Raven' assert_equal :book, entry.type assert_equal 'raven', entry.key assert_equal 'Poe, Edgar A.', entry.author assert_equal 'The Raven', entry.title end def test_creation_from_hash entry = BibTeX::Entry.new( bibtex_type: 'book', bibtex_key: :raven, author: 'Poe, Edgar A.', title: 'The Raven' ) assert_equal :book, entry.type assert_equal 'raven', entry.key assert_equal 'Poe, Edgar A.', entry.author assert_equal 'The Raven', entry.title end def test_creation_from_block entry = BibTeX::Entry.new do |e| e.type = :book e.key = 'raven' e.author = 'Poe, Edgar A.' e.title = 'The Raven' end assert_equal :book, entry.type assert_equal 'raven', entry.key assert_equal 'Poe, Edgar A.', entry.author assert_equal 'The Raven', entry.title end def test_sorting entries = [] entries << Entry.new(bibtex_type: 'book', bibtex_key: 'raven3', author: 'Poe, Edgar A.', title: 'The Raven') entries << Entry.new(bibtex_type: 'book', bibtex_key: 'raven2', author: 'Poe, Edgar A.', title: 'The Raven') entries << Entry.new(bibtex_type: 'book', bibtex_key: 'raven1', author: 'Poe, Edgar A.', title: 'The Raven') entries << Entry.new(bibtex_type: 'book', bibtex_key: 'raven1', author: 'Poe, Edgar A.', title: 'The Aven') entries.sort! assert_equal %w[raven1 raven1 raven2 raven3], entries.map(&:key) assert_equal ['The Aven', 'The Raven'], entries.map(&:title)[0, 2] end describe 'default keys' do before do @e1 = Entry.new(bibtex_type: 'book', author: 'Poe, Edgar A.', title: 'The Raven', editor: 'John Hopkins', year: 1996) @e2 = Entry.new(bibtex_type: 'book', title: 'The Raven', editor: 'John Hopkins', year: 1996) @e3 = Entry.new(bibtex_type: 'book', author: 'Poe, Edgar A.', title: 'The Raven', editor: 'John Hopkins') @e4 = Entry.new(bibtex_type: 'book', author: 'Poe, Edgar A.', title: 'The Raven', editor: 'John Hopkins', date: '2003-09') @e5 = Entry.new(bibtex_type: 'book', author: 'Poe, Edgar A.', title: 'The Raven', editor: 'John Hopkins', year: 'n.d.') @e6 = Entry.new(bibtex_type: 'book', author: 'Poe, Edgar A.', title: 'The Raven', editor: 'John Hopkins', year: '[2009]') end it 'should return "unknown-a" for an empty Entry' do Entry.new.key.must_be :==, 'unknown-a' end it 'should return a key made up of author-year-a if all fields are present' do @e1.key.must_be :==, 'poe1996a' end it 'should return a key made up of editor-year-a if there is no author' do @e2.key.must_be :==, 'john1996a' end it 'should return use the last name if the author/editor names have been parsed' do @e2.parse_names.key.must_be :==, 'hopkins1996a' end it 'skips the year if not present' do @e3.key.must_be :==, 'poe-a' end it 'skips the year if not numeric' do @e5.key.must_be :==, 'poe-a' end it 'extracts the year from the date field if present' do @e4.key.must_be :==, 'poe2003a' end it 'extracts the year from as a number' do @e6.key.must_be :==, 'poe2009a' end end describe 'when the entry is added to a Bibliography' do before do @e = Entry.new @bib = Bibliography.new end it 'should register itself with its key' do @bib << @e @bib.entries.keys.must_include @e.key end describe "when there is already an element registered with the entry's key" do before { @bib << Entry.new } it 'should find a suitable key' do k = @e.key @bib << @e @bib.entries.keys.must_include @e.key k.wont_be :==, @e.key end end end describe '#meet?' do before { @e = Entry.new } it 'returns true for an empty condition list' do assert @e.meet? [] assert @e.meet? [''] end it 'it returns true when all conditions hold' do refute @e.meet? ['author = Edgar'] @e.author = 'Poe, Edgar A.' refute @e.meet? ['author = Edgar'] refute @e.meet? ['author = Poe, Edgar'] assert @e.meet? ['author = Poe, Edgar A.'] assert @e.meet? ['author ^= Poe'] refute @e.meet? ['author ^= Edgar'] assert @e.meet? ['author ~= Edgar'] assert @e.meet? ['author ~= .'] assert @e.meet? ['author ~= [a-z]*'] assert @e.meet? ['author ^= P\w+'] @e.date = '2019-11-18' assert @e.meet? ['date = 2019-11-18'] assert @e.meet? ['date >= 2019-10-18'] assert @e.meet? ['date <= 2019-12-18'] assert @e.meet? ['date > 2019-10-18'] assert @e.meet? ['date < 2019-12-18'] refute @e.meet? ['date >= 2019-12-18'] refute @e.meet? ['date <= 2019-10-18'] refute @e.meet? ['date > 2019-12-18'] refute @e.meet? ['date < 2019-10-18'] refute @e.meet? ['date > 2019-11-18'] refute @e.meet? ['date < 2019-11-18'] @e['x'] = '019' assert @e.meet? ['x < 20'] assert @e.meet? ['x >= 19'] end end describe '#valid?' do before do @misc = Entry.new @book = Entry.new(bibtex_type: :book) @article = Entry.new(bibtex_type: :article) end it 'is true by default' do assert @misc.valid? end it 'is not true by default for types with mandatory fields' do refute @book.valid? refute @article.valid? end it 'is true only if all mandatory fields are present' do @book.update( author: 'Henry James', title: 'What Maisie Knew', year: 1897, publisher: 'Heineman' ) assert @book.valid? @book.delete :publisher refute @book.valid? @book.publisher = 'Heineman' assert @book.valid? @book.delete :year refute @book.valid? end end describe '#fetch' do let(:pages) { '1--2' } let(:entry) do e = Entry.new e.add(:pages, pages) end describe '(:pages)' do it 'should fetch pages' do assert_equal(entry.fetch(:pages), pages) end end end end end