citeproc-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/README.md +78 -0
  2. data/lib/citeproc.rb +100 -0
  3. data/lib/citeproc/bibliography.rb +57 -0
  4. data/lib/citeproc/data.rb +149 -0
  5. data/lib/citeproc/date.rb +133 -0
  6. data/lib/citeproc/formatter.rb +38 -0
  7. data/lib/citeproc/item.rb +53 -0
  8. data/lib/citeproc/name.rb +284 -0
  9. data/lib/citeproc/processor.rb +166 -0
  10. data/lib/citeproc/selector.rb +61 -0
  11. data/lib/citeproc/variable.rb +82 -0
  12. data/lib/citeproc/version.rb +3 -0
  13. data/lib/csl/locale.rb +223 -0
  14. data/lib/csl/node.rb +72 -0
  15. data/lib/csl/nodes.rb +1364 -0
  16. data/lib/csl/renderer.rb +88 -0
  17. data/lib/csl/sort.rb +53 -0
  18. data/lib/csl/style.rb +110 -0
  19. data/lib/csl/term.rb +124 -0
  20. data/lib/extensions/core.rb +43 -0
  21. data/lib/plugins/filters/bibtex.rb +12 -0
  22. data/lib/plugins/formats/default.rb +134 -0
  23. data/lib/plugins/formats/html.rb +67 -0
  24. data/lib/support/attributes.rb +99 -0
  25. data/lib/support/tree.rb +80 -0
  26. data/resource/locale/locales-af-ZA.xml +304 -0
  27. data/resource/locale/locales-ar-AR.xml +304 -0
  28. data/resource/locale/locales-bg-BG.xml +304 -0
  29. data/resource/locale/locales-ca-AD.xml +304 -0
  30. data/resource/locale/locales-cs-CZ.xml +304 -0
  31. data/resource/locale/locales-da-DK.xml +304 -0
  32. data/resource/locale/locales-de-AT.xml +304 -0
  33. data/resource/locale/locales-de-CH.xml +304 -0
  34. data/resource/locale/locales-de-DE.xml +332 -0
  35. data/resource/locale/locales-el-GR.xml +303 -0
  36. data/resource/locale/locales-en-US.xml +313 -0
  37. data/resource/locale/locales-es-ES.xml +304 -0
  38. data/resource/locale/locales-et-EE.xml +304 -0
  39. data/resource/locale/locales-fr-FR.xml +304 -0
  40. data/resource/locale/locales-he-IL.xml +304 -0
  41. data/resource/locale/locales-hu-HU.xml +304 -0
  42. data/resource/locale/locales-is-IS.xml +304 -0
  43. data/resource/locale/locales-it-IT.xml +304 -0
  44. data/resource/locale/locales-ja-JP.xml +304 -0
  45. data/resource/locale/locales-kh-KH.xml +303 -0
  46. data/resource/locale/locales-ko-KR.xml +304 -0
  47. data/resource/locale/locales-mn-MN.xml +304 -0
  48. data/resource/locale/locales-nb-NO.xml +304 -0
  49. data/resource/locale/locales-nl-NL.xml +304 -0
  50. data/resource/locale/locales-nn-NO.xml +304 -0
  51. data/resource/locale/locales-pl-PL.xml +304 -0
  52. data/resource/locale/locales-pt-BR.xml +304 -0
  53. data/resource/locale/locales-pt-PT.xml +304 -0
  54. data/resource/locale/locales-ro-RO.xml +304 -0
  55. data/resource/locale/locales-ru-RU.xml +304 -0
  56. data/resource/locale/locales-sk-SK.xml +304 -0
  57. data/resource/locale/locales-sl-SI.xml +304 -0
  58. data/resource/locale/locales-sr-RS.xml +304 -0
  59. data/resource/locale/locales-sv-SE.xml +304 -0
  60. data/resource/locale/locales-th-TH.xml +304 -0
  61. data/resource/locale/locales-tr-TR.xml +304 -0
  62. data/resource/locale/locales-uk-UA.xml +304 -0
  63. data/resource/locale/locales-vi-VN.xml +304 -0
  64. data/resource/locale/locales-zh-CN.xml +304 -0
  65. data/resource/locale/locales-zh-TW.xml +304 -0
  66. data/resource/schema/csl-categories.rnc +39 -0
  67. data/resource/schema/csl-data.rnc +98 -0
  68. data/resource/schema/csl-terms.rnc +106 -0
  69. data/resource/schema/csl-types.rnc +39 -0
  70. data/resource/schema/csl-variables.rnc +182 -0
  71. data/resource/schema/csl.rnc +941 -0
  72. data/resource/style/acta-materialia-x.csl +128 -0
  73. data/resource/style/advanced-engineering-materials-x.csl +121 -0
  74. data/resource/style/ama.csl +185 -0
  75. data/resource/style/ama2-x.csl +179 -0
  76. data/resource/style/apa-x.csl +324 -0
  77. data/resource/style/apa.csl +254 -0
  78. data/resource/style/apsa-x.csl +163 -0
  79. data/resource/style/apsa.csl +176 -0
  80. data/resource/style/asa-x.csl +203 -0
  81. data/resource/style/asa.csl +216 -0
  82. data/resource/style/asm-journals-x.csl +131 -0
  83. data/resource/style/bibtex-x2.csl +175 -0
  84. data/resource/style/bluebook-demo-x.csl +392 -0
  85. data/resource/style/bluebook-demo.csl +942 -0
  86. data/resource/style/chicago-author-date-listing.csl +434 -0
  87. data/resource/style/chicago-author-date.csl +369 -0
  88. data/resource/style/chicago-fullnote-bibliography-bb.csl +928 -0
  89. data/resource/style/chicago-fullnote-bibliography.csl +695 -0
  90. data/resource/style/chicago-note-bibliography.csl +446 -0
  91. data/resource/style/chicago-note.csl +388 -0
  92. data/resource/style/greek-chicago-x.csl +1182 -0
  93. data/resource/style/harvard1-institution-italic.csl +190 -0
  94. data/resource/style/harvard1.csl +181 -0
  95. data/resource/style/ieee.csl +129 -0
  96. data/resource/style/mhra-x.csl +312 -0
  97. data/resource/style/mhra.csl +390 -0
  98. data/resource/style/mhra_note_without_bibliography-x.csl +330 -0
  99. data/resource/style/mhra_note_without_bibliography.csl +338 -0
  100. data/resource/style/mla-x.csl +178 -0
  101. data/resource/style/mla.csl +189 -0
  102. data/resource/style/nature-x.csl +81 -0
  103. data/resource/style/nature.csl +88 -0
  104. data/resource/style/nlm.csl +117 -0
  105. data/spec/citeproc/bibliography_spec.rb +45 -0
  106. data/spec/citeproc/citeproc_spec.rb +76 -0
  107. data/spec/citeproc/date_spec.rb +85 -0
  108. data/spec/citeproc/formatter_spec.rb +101 -0
  109. data/spec/citeproc/item_spec.rb +71 -0
  110. data/spec/citeproc/name_spec.rb +30 -0
  111. data/spec/citeproc/processor_spec.rb +61 -0
  112. data/spec/citeproc/selector_spec.rb +82 -0
  113. data/spec/citeproc/variable_spec.rb +69 -0
  114. data/spec/csl/locale_spec.rb +208 -0
  115. data/spec/csl/node_spec.rb +25 -0
  116. data/spec/csl/nodes_spec.rb +140 -0
  117. data/spec/csl/style_spec.rb +62 -0
  118. data/spec/csl/term_spec.rb +56 -0
  119. data/spec/fixtures/dates.yaml +80 -0
  120. data/spec/fixtures/names.yaml +115 -0
  121. data/spec/fixtures/nodes.yaml +245 -0
  122. data/spec/spec_helper.rb +18 -0
  123. data/spec/support/attributes_spec.rb +39 -0
  124. data/spec/support/tree_spec.rb +163 -0
  125. metadata +264 -0
@@ -0,0 +1,117 @@
1
+ <style
2
+ xmlns="http://purl.org/net/xbiblio/csl"
3
+ class="in-text"
4
+ xml:lang="en">
5
+ <info>
6
+ <title>National Library of Medicine</title>
7
+ <id>http://www.zotero.org/styles/nlm</id>
8
+ <link href="http://www.zotero.org/styles/nlm" />
9
+ <author>
10
+ <name>Michael Berkowitz</name>
11
+ <email>mberkowi@gmu.edu</email>
12
+ </author>
13
+ <category term="generic-base" />
14
+ <category term="numeric" />
15
+ <updated />
16
+ </info>
17
+ <macro name="author">
18
+ <names suffix=". " variable="author">
19
+ <name delimiter=", " delimiter-precedes-last="always" initialize-with="" name-as-sort-order="all" sort-separator=" " />
20
+ </names>
21
+ </macro>
22
+ <macro name="editor">
23
+ <names suffix=", editor(s). " variable="editor">
24
+ <name delimiter=", " delimiter-precedes-last="always" initialize-with="" name-as-sort-order="all" sort-separator=" " />
25
+ </names>
26
+ </macro>
27
+ <macro name="publisher">
28
+ <text suffix=": " variable="publisher-place" />
29
+ <text suffix="; " variable="publisher" />
30
+ <date variable="issued">
31
+ <date-part name="year" suffix=". " />
32
+ </date>
33
+ </macro>
34
+ <macro name="access">
35
+ <group delimiter=" ">
36
+ <group delimiter=" " prefix="[" suffix="]">
37
+ <text term="cited" text-case="lowercase" />
38
+ <date suffix=" " variable="accessed">
39
+ <date-part name="year" />
40
+ <date-part form="short" name="month" prefix=" " />
41
+ <date-part name="day" prefix=" " />
42
+ </date>
43
+ </group>
44
+ <group>
45
+ <text value="Available from: " />
46
+ <text variable="URL" />
47
+ </group>
48
+ </group>
49
+ </macro>
50
+ <macro name="title">
51
+ <group delimiter=" ">
52
+ <text variable="title" />
53
+ <choose>
54
+ <if variable="URL">
55
+ <text prefix="[" suffix="]" term="internet" text-case="capitalize-first" />
56
+ </if>
57
+ </choose>
58
+ </group>
59
+ </macro>
60
+ <macro name="edition">
61
+ <choose>
62
+ <if is-numeric="edition">
63
+ <group delimiter=" ">
64
+ <number form="ordinal" variable="edition" />
65
+ <text form="short" suffix="." term="edition" />
66
+ </group>
67
+ </if>
68
+ <else>
69
+ <text suffix="." variable="edition" />
70
+ </else>
71
+ </choose>
72
+ </macro>
73
+ <citation
74
+ collapse="citation-number">
75
+ <sort>
76
+ <key variable="citation-number" />
77
+ </sort>
78
+ <layout delimiter="; " prefix="(" suffix=")">
79
+ <text variable="citation-number" />
80
+ </layout>
81
+ </citation>
82
+ <bibliography
83
+ second-field-align="true">
84
+ <layout>
85
+ <text suffix=". " variable="citation-number" />
86
+ <text macro="author" />
87
+ <text macro="title" suffix=". " />
88
+ <choose>
89
+ <if type="book">
90
+ <text macro="edition" prefix=" " suffix=" " />
91
+ <text macro="publisher" prefix=" " />
92
+ </if>
93
+ <else-if type="chapter">
94
+ <group prefix=" " suffix=". ">
95
+ <text suffix=": " term="in" text-case="capitalize-first" />
96
+ <text macro="editor" />
97
+ <text variable="container-title" />
98
+ </group>
99
+ <text macro="publisher" prefix=" " />
100
+ <text prefix=" p. " suffix="." variable="page" />
101
+ </else-if>
102
+ <else>
103
+ <text form="short" suffix=". " variable="container-title" />
104
+ <date suffix=";" variable="issued">
105
+ <date-part name="year" suffix=" " />
106
+ <date-part form="short" name="month" suffix=" " />
107
+ <date-part name="day" />
108
+ </date>
109
+ <text variable="volume" />
110
+ <text prefix="(" suffix="):" variable="issue" />
111
+ <text suffix="." variable="page" />
112
+ </else>
113
+ </choose>
114
+ <text macro="access" />
115
+ </layout>
116
+ </bibliography>
117
+ </style>
@@ -0,0 +1,45 @@
1
+ module CiteProc
2
+ describe Bibliography do
3
+
4
+ let(:json) {'[{"test":"test","bibliography-errors":[]},["1","2"]]'}
5
+
6
+ describe '#new' do
7
+ it { should_not be_nil }
8
+
9
+ it 'accepts a JSON string' do
10
+ Bibliography.new(json).should_not be_empty
11
+ end
12
+
13
+ it 'accepts a data array' do
14
+ Bibliography.new([1,2,3]).data.should == [1,2,3]
15
+ end
16
+
17
+ it 'accepts an options hash' do
18
+ Bibliography.new('test'=>'test').options['test'].should == 'test'
19
+ end
20
+
21
+ it 'accepts an options hash with errors' do
22
+ Bibliography.new('bibliography-errors'=>[1,2,3]).errors.should == [1,2,3]
23
+ end
24
+ end
25
+
26
+ describe '#to_json' do
27
+ it 'returns valid JSON when empty' do
28
+ JSON.parse(Bibliography.new.to_json).should_not be_nil
29
+ end
30
+ it 'returns valid JSON with data' do
31
+ JSON.parse(Bibliography.new([1,2,3]).to_json).should_not be_nil
32
+ end
33
+ it 'returns valid JSON with options' do
34
+ JSON.parse(Bibliography.new({'test'=>'test'}).to_json).should_not be_nil
35
+ end
36
+ it 'returns valid JSON with options and data' do
37
+ JSON.parse(Bibliography.new([{'test'=>'test'}, [1,2]]).to_json).should_not be_nil
38
+ end
39
+ it 'supports round-trips' do
40
+ Bibliography.new(json).to_json.should == json
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # This generates RSpec tests from the JSON test cases of the citeproc-tests
3
+ # suite.
4
+ #
5
+
6
+
7
+ #
8
+ # Filter applied to each test to decide whether we should run it. Use this
9
+ # to stay sane while the Processor is not feature-complete!
10
+ #
11
+ def filter(file, fixture)
12
+ # return ['affix_InterveningEmpty.json'].include?(File.basename(file))
13
+ # File.basename(file) =~ /bugreports_greek/i
14
+ # File.basename(file) =~ /sort_stripmark/i
15
+ # return File.basename(file) =~ /^date_rawparsesimpledate/i
16
+ true
17
+ end
18
+
19
+ module CiteProc
20
+
21
+ describe 'citeproc' do
22
+
23
+ let(:proc) { Processor.new }
24
+
25
+ Test::Fixtures::Processor.each_pair do |file, fixture|
26
+
27
+ tokens = File.basename(file).split(/_|\.json/)
28
+
29
+ describe tokens[0].downcase do
30
+
31
+ name = tokens[1].gsub(/([[:lower:]])([[:upper:]])/, '\1 \2').downcase
32
+
33
+ it name do
34
+ pending if tokens[0] =~ /^(position|disambiguate|integration|flipflop|collapse|parallel)/
35
+
36
+ proc.style = fixture['csl']
37
+ proc.import(fixture['input'])
38
+ proc.format = :html
39
+
40
+ proc.add_abbreviations(fixture['abbreviations']) if fixture['abbreviations']
41
+
42
+ case fixture['mode']
43
+
44
+ when 'citation'
45
+ # citations => process_citation_cluster
46
+ # citation_items || :all => make_citation_cluster
47
+ data = fixture['citation_items']
48
+
49
+ unless data
50
+ result = proc.cite((fixture['bibentries'] && fixture['bibentries'].last) || :all).map { |d| d[1] }.join
51
+ else
52
+ result = data.map { |d| proc.cite(d).map { |c| c[1] }.join }.join("\n")
53
+ end
54
+
55
+ when 'bibliography'
56
+ result = proc.bibliography(fixture['bibsection'] || :all).to_s
57
+
58
+ when 'bibliography-header'
59
+ pending('not yet implemented')
60
+
61
+ when 'bibliography-nosort'
62
+ pending('not yet implemented')
63
+
64
+ else
65
+ CiteProc.log.warn "unkown processor mode: #{fixture['mode']}"
66
+ pending('not yet implemented')
67
+ end
68
+
69
+ result.should == fixture['result']
70
+
71
+ end
72
+ end if filter(file, fixture)
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ module CiteProc
4
+
5
+ describe Date do
6
+
7
+ describe '#new' do
8
+ it { should_not be_nil }
9
+ end
10
+
11
+ describe '.create' do
12
+ it 'should accept parameters and return a new instance' do
13
+ Date.create('date-parts' => [[2001, 1]]).year.should == 2001
14
+ end
15
+ end
16
+
17
+ describe '#sort' do
18
+
19
+ let(:ad2k) { Date.create('date-parts' => [[2000]])}
20
+ let(:may) { Date.create('date-parts' => [[2000, 5]])}
21
+ let(:first_of_may) { Date.create('date-parts' => [[2000, 5, 1]])}
22
+
23
+ let(:bc100) { Date.create('date-parts' => [[-100]]) }
24
+ let(:bc50) { Date.create('date-parts' => [[-50]]) }
25
+ let(:ad50) { Date.create('date-parts' => [[50]]) }
26
+ let(:ad100) { Date.create('date-parts' => [[100]]) }
27
+
28
+ it 'dates with more date-parts will come after those with fewer parts' do
29
+ (ad2k < may && may < first_of_may).should be true
30
+ end
31
+
32
+ it 'negative years are sorted inversely' do
33
+ [ad50, bc100, bc50, ad100].sort.map(&:year).should == [-100, -50, 50, 100]
34
+ end
35
+ end
36
+
37
+ Test::Fixtures::Dates.keys.each do |feature|
38
+ describe feature do
39
+ Test::Fixtures::Dates[feature].each do |example|
40
+
41
+ it example['it'] do
42
+ dates = example['dates'].map { |date| Date.new(date) }
43
+ expected = example['expected']
44
+ options = example['options']
45
+
46
+ result = case feature
47
+ when 'display'
48
+ dates.map { |date| date.display(options) }
49
+ when 'json-api'
50
+ dates.map(&:to_json)
51
+ end
52
+
53
+ result.should == expected
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+
60
+ describe 'literal dates' do
61
+
62
+ it 'is not literal by default' do
63
+ Date.new.should_not be_literal
64
+ end
65
+
66
+ it 'is literal if it contains only a literal field' do
67
+ Date.create(:literal => 'foo').should be_literal
68
+ end
69
+
70
+ it 'is literal if it contains a literal field' do
71
+ Date.create('date-parts' => [[2000]], :literal => 'foo').should be_literal
72
+ end
73
+ end
74
+
75
+ describe 'uncertain dates' do
76
+ it 'are uncertain' do
77
+ Date.new({ 'date-parts' => [[-225]], 'circa' => '1' }).should be_uncertain
78
+ Date.new { |d| d.parts = [[-225]]; d.circa = true }.should be_uncertain
79
+ end
80
+ end
81
+
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,101 @@
1
+ module CiteProc
2
+ describe Formatter do
3
+
4
+ it { should_not be_nil }
5
+
6
+ describe '#format' do
7
+ it 'returns the current format by default' do
8
+ Formatter.new.format.should be_a_kind_of(Format::Default)
9
+ end
10
+
11
+ it 'applies the current format if arguments are given' do
12
+ Formatter.new.format('foo').should == 'foo'
13
+ end
14
+ end
15
+
16
+ describe '#apply' do
17
+
18
+ it 'returns an empty string by default' do
19
+ Formatter.new.apply.should == ''
20
+ end
21
+
22
+ context 'using the default style' do
23
+
24
+ let(:format) { Formatter.new }
25
+
26
+ it 'returns the given string if there are no attributes' do
27
+ format.apply('foo') == 'foo'
28
+ end
29
+
30
+ it 'returns the formatted string (prefix)' do
31
+ format.apply('foo', 'prefix' => '@').should == '@foo'
32
+ end
33
+
34
+ it 'returns the formatted string (suffix)' do
35
+ format.apply('foo', 'suffix' => '@').should == 'foo@'
36
+ end
37
+
38
+ it 'returns the formatted string (affixes)' do
39
+ format.apply('foo', 'prefix' => '@', 'suffix' => '@').should == '@foo@'
40
+ end
41
+
42
+ it 'returns the formatted string (display)' do
43
+ format.apply('foo', 'display' => 'block').should == 'foo'
44
+ format.apply('foo', 'display' => 'inline').should == 'foo'
45
+ format.apply('foo', 'display' => 'right-inline').should == 'foo'
46
+ format.apply('foo', 'display' => 'left-margin').should == 'foo'
47
+ end
48
+
49
+ it 'returns the formatted string (strip periods)' do
50
+ format.apply('foo', 'strip-periods' => 'true').should == 'foo'
51
+ format.apply('foo.', 'strip-periods' => 'true').should == 'foo'
52
+ format.apply('f.oo.', 'strip-periods' => 'true').should == 'f oo'
53
+ format.apply('.foo.', 'strip-periods' => 'true').should == 'foo'
54
+ format.apply('foo...', 'strip-periods' => 'true').should == 'foo'
55
+ end
56
+
57
+ it 'returns the formatted string (font-style)' do
58
+ format.apply('foo', 'font-style' => 'normal').should == 'foo'
59
+ format.apply('foo', 'font-style' => 'italic').should == 'foo'
60
+ format.apply('foo', 'font-style' => 'italics').should == 'foo'
61
+ format.apply('foo', 'font-style' => 'oblique').should == 'foo'
62
+ end
63
+
64
+ it 'returns the formatted string (font-variant)' do
65
+ format.apply('foo', 'font-variant' => 'normal').should == 'foo'
66
+ format.apply('foo', 'font-variant' => 'small-caps').should == 'foo'
67
+ end
68
+
69
+ it 'returns the formatted string (font-weight)' do
70
+ format.apply('foo', 'font-weight' => 'normal').should == 'foo'
71
+ format.apply('foo', 'font-weight' => 'bold').should == 'foo'
72
+ format.apply('foo', 'font-weight' => 'light').should == 'foo'
73
+ end
74
+
75
+ it 'returns the formatted string (text-decoration)' do
76
+ format.apply('foo', 'text-decoration' => 'none').should == 'foo'
77
+ format.apply('foo', 'text-decoration' => 'underline').should == 'foo'
78
+ end
79
+
80
+ it 'returns the formatted string (vertical-align)' do
81
+ format.apply('foo', 'vertical-align' => 'baseline').should == 'foo'
82
+ format.apply('foo', 'vertical-align' => 'sub').should == 'foo'
83
+ format.apply('foo', 'vertical-align' => 'sup').should == 'foo'
84
+ end
85
+
86
+ it 'returns the formatted string (text-case)' do
87
+ format.apply('foo', 'text-case' => 'none').should == 'foo'
88
+ format.apply('foo Foo FOO', 'text-case' => 'lowercase').should == 'foo foo foo'
89
+ format.apply('foo Foo FoO', 'text-case' => 'uppercase').should == 'FOO FOO FOO'
90
+ format.apply('foo foo foo', 'text-case' => 'capitalize-first').should == 'Foo foo foo'
91
+ format.apply('foo FOO fOo', 'text-case' => 'capitalize-all').should == 'Foo Foo Foo'
92
+ format.apply('the short story of foo and bar', 'text-case' => 'title').should == 'The Short Story of Foo and Bar'
93
+ format.apply('the short story of FOO and BAR', 'text-case' => 'title').should == 'The Short Story of FOO and BAR'
94
+ format.apply('the short story of foo and bar', 'text-case' => 'sentence').should == 'The short story of foo and bar'
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module CiteProc
4
+ describe Item do
5
+
6
+ let(:item) { Item.new }
7
+
8
+ describe '#new' do
9
+ it { should_not be_nil }
10
+ end
11
+
12
+ describe '.create' do
13
+ it 'accepts parameters and returns a new item containing the parameters' do
14
+ pending #Item.new(:author => 'Poe, Edgar A.').author.family == 'Poe'
15
+ end
16
+ end
17
+
18
+ describe '#add_observers' do
19
+ it 'adds a new observer to the empty list of observers' do
20
+ expect {
21
+ item.add_observer(stub('observer', :update => nil))
22
+ }.to change { item.count_observers }.from(0).to(1)
23
+ end
24
+
25
+ it 'adds a new observers to the internal list of observers' do
26
+ expect {
27
+ item.add_observer(stub('observer1', :update => nil))
28
+ item.add_observer(stub('observer2', :update => nil))
29
+ item.add_observer(stub('observer3', :update => nil))
30
+ }.to change { item.count_observers }.from(0).to(3)
31
+ end
32
+ end
33
+
34
+ describe '#delete_observers' do
35
+ let(:observer) { stub('observer', :update => nil) }
36
+
37
+ it 'raises no error if the list of observers is empty' do
38
+ item.delete_observer(observer)
39
+ end
40
+
41
+ it 'does not alter the list of observers if the given observer is not currently observing the item' do
42
+ item.add_observer(observer)
43
+ item.count_observers.should == 1
44
+ item.delete_observer(stub('observer2'))
45
+ item.count_observers.should == 1
46
+ end
47
+
48
+ it 'removes the given observer from the list of observers' do
49
+ item.add_observer(observer)
50
+ expect {
51
+ item.delete_observer(observer)
52
+ }.to change { item.count_observers }.from(1).to(0)
53
+ end
54
+ end
55
+
56
+ describe '#[]' do
57
+ it 'returns the value for the given key' do
58
+ item['foo'] = 'foo'
59
+ item['foo'].should == 'foo'
60
+ end
61
+
62
+ it 'notifies observers on every variable access' do
63
+ observer = stub('observer')
64
+ observer.should_receive(:update).once
65
+ item.add_observer(observer)
66
+ item['foo']
67
+ end
68
+ end
69
+
70
+ end
71
+ end