citeproc-ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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