moxml 0.1.7 → 0.1.8

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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +5 -0
  3. data/.github/workflows/dependent-tests.yml +20 -0
  4. data/.github/workflows/docs.yml +59 -0
  5. data/.github/workflows/rake.yml +10 -10
  6. data/.github/workflows/release.yml +5 -3
  7. data/.gitignore +37 -0
  8. data/.rubocop.yml +15 -7
  9. data/.rubocop_todo.yml +238 -40
  10. data/Gemfile +14 -9
  11. data/LICENSE.md +6 -2
  12. data/README.adoc +535 -373
  13. data/Rakefile +53 -0
  14. data/benchmarks/.gitignore +6 -0
  15. data/benchmarks/generate_report.rb +550 -0
  16. data/docs/Gemfile +13 -0
  17. data/docs/_config.yml +138 -0
  18. data/docs/_guides/advanced-features.adoc +87 -0
  19. data/docs/_guides/development-testing.adoc +165 -0
  20. data/docs/_guides/index.adoc +45 -0
  21. data/docs/_guides/modifying-xml.adoc +293 -0
  22. data/docs/_guides/parsing-xml.adoc +231 -0
  23. data/docs/_guides/sax-parsing.adoc +603 -0
  24. data/docs/_guides/working-with-documents.adoc +118 -0
  25. data/docs/_pages/adapter-compatibility.adoc +369 -0
  26. data/docs/_pages/adapters/headed-ox.adoc +237 -0
  27. data/docs/_pages/adapters/index.adoc +98 -0
  28. data/docs/_pages/adapters/libxml.adoc +286 -0
  29. data/docs/_pages/adapters/nokogiri.adoc +252 -0
  30. data/docs/_pages/adapters/oga.adoc +292 -0
  31. data/docs/_pages/adapters/ox.adoc +55 -0
  32. data/docs/_pages/adapters/rexml.adoc +293 -0
  33. data/docs/_pages/best-practices.adoc +430 -0
  34. data/docs/_pages/compatibility.adoc +468 -0
  35. data/docs/_pages/configuration.adoc +251 -0
  36. data/docs/_pages/error-handling.adoc +350 -0
  37. data/docs/_pages/headed-ox-limitations.adoc +558 -0
  38. data/docs/_pages/headed-ox.adoc +1025 -0
  39. data/docs/_pages/index.adoc +35 -0
  40. data/docs/_pages/installation.adoc +141 -0
  41. data/docs/_pages/node-api-reference.adoc +50 -0
  42. data/docs/_pages/performance.adoc +36 -0
  43. data/docs/_pages/quick-start.adoc +244 -0
  44. data/docs/_pages/thread-safety.adoc +29 -0
  45. data/docs/_references/document-api.adoc +408 -0
  46. data/docs/_references/index.adoc +48 -0
  47. data/docs/_tutorials/basic-usage.adoc +268 -0
  48. data/docs/_tutorials/builder-pattern.adoc +343 -0
  49. data/docs/_tutorials/index.adoc +33 -0
  50. data/docs/_tutorials/namespace-handling.adoc +325 -0
  51. data/docs/_tutorials/xpath-queries.adoc +359 -0
  52. data/docs/index.adoc +122 -0
  53. data/examples/README.md +124 -0
  54. data/examples/api_client/README.md +424 -0
  55. data/examples/api_client/api_client.rb +394 -0
  56. data/examples/api_client/example_response.xml +48 -0
  57. data/examples/headed_ox_example/README.md +90 -0
  58. data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
  59. data/examples/rss_parser/README.md +194 -0
  60. data/examples/rss_parser/example_feed.xml +93 -0
  61. data/examples/rss_parser/rss_parser.rb +189 -0
  62. data/examples/sax_parsing/README.md +50 -0
  63. data/examples/sax_parsing/data_extractor.rb +75 -0
  64. data/examples/sax_parsing/example.xml +21 -0
  65. data/examples/sax_parsing/large_file.rb +78 -0
  66. data/examples/sax_parsing/simple_parser.rb +55 -0
  67. data/examples/web_scraper/README.md +352 -0
  68. data/examples/web_scraper/example_page.html +201 -0
  69. data/examples/web_scraper/web_scraper.rb +312 -0
  70. data/lib/moxml/adapter/base.rb +107 -28
  71. data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
  72. data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
  73. data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
  74. data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
  75. data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
  76. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
  77. data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
  78. data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
  79. data/lib/moxml/adapter/customized_ox/attribute.rb +28 -1
  80. data/lib/moxml/adapter/customized_rexml/formatter.rb +11 -6
  81. data/lib/moxml/adapter/headed_ox.rb +161 -0
  82. data/lib/moxml/adapter/libxml.rb +1548 -0
  83. data/lib/moxml/adapter/nokogiri.rb +121 -9
  84. data/lib/moxml/adapter/oga.rb +123 -12
  85. data/lib/moxml/adapter/ox.rb +282 -26
  86. data/lib/moxml/adapter/rexml.rb +127 -20
  87. data/lib/moxml/adapter.rb +21 -4
  88. data/lib/moxml/attribute.rb +6 -0
  89. data/lib/moxml/builder.rb +40 -4
  90. data/lib/moxml/config.rb +8 -3
  91. data/lib/moxml/context.rb +39 -1
  92. data/lib/moxml/doctype.rb +13 -1
  93. data/lib/moxml/document.rb +39 -6
  94. data/lib/moxml/document_builder.rb +27 -5
  95. data/lib/moxml/element.rb +71 -2
  96. data/lib/moxml/error.rb +175 -6
  97. data/lib/moxml/node.rb +94 -3
  98. data/lib/moxml/node_set.rb +34 -0
  99. data/lib/moxml/sax/block_handler.rb +194 -0
  100. data/lib/moxml/sax/element_handler.rb +124 -0
  101. data/lib/moxml/sax/handler.rb +113 -0
  102. data/lib/moxml/sax.rb +31 -0
  103. data/lib/moxml/version.rb +1 -1
  104. data/lib/moxml/xml_utils/encoder.rb +4 -4
  105. data/lib/moxml/xml_utils.rb +7 -4
  106. data/lib/moxml/xpath/ast/node.rb +159 -0
  107. data/lib/moxml/xpath/cache.rb +91 -0
  108. data/lib/moxml/xpath/compiler.rb +1768 -0
  109. data/lib/moxml/xpath/context.rb +26 -0
  110. data/lib/moxml/xpath/conversion.rb +124 -0
  111. data/lib/moxml/xpath/engine.rb +52 -0
  112. data/lib/moxml/xpath/errors.rb +101 -0
  113. data/lib/moxml/xpath/lexer.rb +304 -0
  114. data/lib/moxml/xpath/parser.rb +485 -0
  115. data/lib/moxml/xpath/ruby/generator.rb +269 -0
  116. data/lib/moxml/xpath/ruby/node.rb +193 -0
  117. data/lib/moxml/xpath.rb +37 -0
  118. data/lib/moxml.rb +5 -2
  119. data/moxml.gemspec +3 -1
  120. data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
  121. data/spec/consistency/README.md +77 -0
  122. data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
  123. data/spec/examples/README.md +75 -0
  124. data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
  125. data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
  126. data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
  127. data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
  128. data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
  129. data/spec/integration/README.md +71 -0
  130. data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
  131. data/spec/integration/headed_ox_integration_spec.rb +326 -0
  132. data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
  133. data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
  134. data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
  135. data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
  136. data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
  137. data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
  138. data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
  139. data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -2
  140. data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
  141. data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
  142. data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
  143. data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
  144. data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
  145. data/spec/moxml/README.md +41 -0
  146. data/spec/moxml/adapter/.gitkeep +0 -0
  147. data/spec/moxml/adapter/README.md +61 -0
  148. data/spec/moxml/adapter/base_spec.rb +27 -0
  149. data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
  150. data/spec/moxml/adapter/libxml_spec.rb +14 -0
  151. data/spec/moxml/adapter/ox_spec.rb +9 -8
  152. data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
  153. data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
  154. data/spec/moxml/adapter_spec.rb +16 -0
  155. data/spec/moxml/attribute_spec.rb +30 -0
  156. data/spec/moxml/builder_spec.rb +33 -0
  157. data/spec/moxml/cdata_spec.rb +31 -0
  158. data/spec/moxml/comment_spec.rb +31 -0
  159. data/spec/moxml/config_spec.rb +3 -3
  160. data/spec/moxml/context_spec.rb +28 -0
  161. data/spec/moxml/declaration_spec.rb +36 -0
  162. data/spec/moxml/doctype_spec.rb +33 -0
  163. data/spec/moxml/document_builder_spec.rb +30 -0
  164. data/spec/moxml/document_spec.rb +105 -0
  165. data/spec/moxml/element_spec.rb +143 -0
  166. data/spec/moxml/error_spec.rb +266 -22
  167. data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
  168. data/spec/moxml/namespace_spec.rb +32 -0
  169. data/spec/moxml/node_set_spec.rb +39 -0
  170. data/spec/moxml/node_spec.rb +37 -0
  171. data/spec/moxml/processing_instruction_spec.rb +34 -0
  172. data/spec/moxml/sax_spec.rb +1067 -0
  173. data/spec/moxml/text_spec.rb +31 -0
  174. data/spec/moxml/version_spec.rb +14 -0
  175. data/spec/moxml/xml_utils/.gitkeep +0 -0
  176. data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
  177. data/spec/moxml/xml_utils_spec.rb +49 -0
  178. data/spec/moxml/xpath/ast/node_spec.rb +83 -0
  179. data/spec/moxml/xpath/axes_spec.rb +296 -0
  180. data/spec/moxml/xpath/cache_spec.rb +358 -0
  181. data/spec/moxml/xpath/compiler_spec.rb +406 -0
  182. data/spec/moxml/xpath/context_spec.rb +210 -0
  183. data/spec/moxml/xpath/conversion_spec.rb +365 -0
  184. data/spec/moxml/xpath/fixtures/sample.xml +25 -0
  185. data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
  186. data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
  187. data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
  188. data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
  189. data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
  190. data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
  191. data/spec/moxml/xpath/lexer_spec.rb +488 -0
  192. data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
  193. data/spec/moxml/xpath/parser_spec.rb +364 -0
  194. data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
  195. data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
  196. data/spec/moxml/xpath_capabilities_spec.rb +199 -0
  197. data/spec/moxml/xpath_spec.rb +77 -0
  198. data/spec/performance/README.md +83 -0
  199. data/spec/performance/benchmark_spec.rb +64 -0
  200. data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +3 -1
  201. data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
  202. data/spec/performance/xpath_benchmark_spec.rb +259 -0
  203. data/spec/spec_helper.rb +58 -1
  204. data/spec/support/xml_matchers.rb +1 -1
  205. metadata +176 -34
  206. data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
  207. /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
  208. /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
  209. /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
  210. /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
  211. /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
  212. /data/spec/{support/shared_examples/text.rb → integration/shared_examples/node_wrappers/text_behavior.rb} +0 -0
@@ -0,0 +1,358 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Moxml::XPath::Cache do
6
+ let(:cache) { described_class.new(3) } # Small cache for testing
7
+
8
+ describe "#initialize" do
9
+ it "creates a cache with default size" do
10
+ default_cache = described_class.new
11
+ expect(default_cache.size).to eq(0)
12
+ end
13
+
14
+ it "creates a cache with specified size" do
15
+ custom_cache = described_class.new(10)
16
+ expect(custom_cache.size).to eq(0)
17
+ end
18
+ end
19
+
20
+ describe "#get_or_set" do
21
+ it "calls block and caches result on cache miss" do
22
+ call_count = 0
23
+
24
+ result1 = cache.get_or_set("key1") do
25
+ call_count += 1
26
+ "value1"
27
+ end
28
+
29
+ expect(result1).to eq("value1")
30
+ expect(call_count).to eq(1)
31
+ expect(cache.size).to eq(1)
32
+ end
33
+
34
+ it "returns cached value on cache hit without calling block" do
35
+ call_count = 0
36
+
37
+ cache.get_or_set("key1") { "value1" }
38
+
39
+ result = cache.get_or_set("key1") do
40
+ call_count += 1
41
+ "should not be called"
42
+ end
43
+
44
+ expect(result).to eq("value1")
45
+ expect(call_count).to eq(0)
46
+ end
47
+
48
+ it "updates access order on cache hit" do
49
+ cache.set("key1", "value1")
50
+ cache.set("key2", "value2")
51
+ cache.set("key3", "value3")
52
+
53
+ # Access key1 to make it most recently used
54
+ cache.get_or_set("key1") { "unused" }
55
+
56
+ # Add key4, which should evict key2 (least recently used)
57
+ cache.set("key4", "value4")
58
+
59
+ expect(cache.key?("key1")).to be(true)
60
+ expect(cache.key?("key2")).to be(false)
61
+ expect(cache.key?("key3")).to be(true)
62
+ expect(cache.key?("key4")).to be(true)
63
+ end
64
+ end
65
+
66
+ describe "#set" do
67
+ it "adds a new key-value pair to the cache" do
68
+ cache.set("key1", "value1")
69
+
70
+ expect(cache.size).to eq(1)
71
+ expect(cache.get("key1")).to eq("value1")
72
+ end
73
+
74
+ it "updates existing key and moves it to end" do
75
+ cache.set("key1", "value1")
76
+ cache.set("key2", "value2")
77
+ cache.set("key3", "value3")
78
+
79
+ # Update key1
80
+ cache.set("key1", "new_value1")
81
+
82
+ # Add key4, should evict key2 (now least recently used)
83
+ cache.set("key4", "value4")
84
+
85
+ expect(cache.get("key1")).to eq("new_value1")
86
+ expect(cache.key?("key2")).to be(false)
87
+ expect(cache.key?("key3")).to be(true)
88
+ expect(cache.key?("key4")).to be(true)
89
+ end
90
+
91
+ it "evicts least recently used entry when cache is full" do
92
+ cache.set("key1", "value1")
93
+ cache.set("key2", "value2")
94
+ cache.set("key3", "value3")
95
+
96
+ expect(cache.size).to eq(3)
97
+
98
+ # Add key4, should evict key1 (least recently used)
99
+ cache.set("key4", "value4")
100
+
101
+ expect(cache.size).to eq(3)
102
+ expect(cache.key?("key1")).to be(false)
103
+ expect(cache.key?("key4")).to be(true)
104
+ end
105
+
106
+ it "returns the set value" do
107
+ result = cache.set("key1", "value1")
108
+ expect(result).to eq("value1")
109
+ end
110
+ end
111
+
112
+ describe "#get" do
113
+ it "returns value for existing key" do
114
+ cache.set("key1", "value1")
115
+
116
+ expect(cache.get("key1")).to eq("value1")
117
+ end
118
+
119
+ it "returns nil for non-existent key" do
120
+ expect(cache.get("nonexistent")).to be_nil
121
+ end
122
+
123
+ it "updates access order on get" do
124
+ cache.set("key1", "value1")
125
+ cache.set("key2", "value2")
126
+ cache.set("key3", "value3")
127
+
128
+ # Access key1 to make it most recently used
129
+ cache.get("key1")
130
+
131
+ # Add key4, should evict key2 (now least recently used)
132
+ cache.set("key4", "value4")
133
+
134
+ expect(cache.key?("key1")).to be(true)
135
+ expect(cache.key?("key2")).to be(false)
136
+ end
137
+ end
138
+
139
+ describe "#clear" do
140
+ it "removes all entries from the cache" do
141
+ cache.set("key1", "value1")
142
+ cache.set("key2", "value2")
143
+ cache.set("key3", "value3")
144
+
145
+ expect(cache.size).to eq(3)
146
+
147
+ cache.clear
148
+
149
+ expect(cache.size).to eq(0)
150
+ expect(cache.key?("key1")).to be(false)
151
+ expect(cache.key?("key2")).to be(false)
152
+ expect(cache.key?("key3")).to be(false)
153
+ end
154
+
155
+ it "works on empty cache" do
156
+ empty_cache = described_class.new
157
+ expect { empty_cache.clear }.not_to raise_error
158
+ expect(empty_cache.size).to eq(0)
159
+ end
160
+ end
161
+
162
+ describe "#size" do
163
+ it "returns 0 for empty cache" do
164
+ expect(cache.size).to eq(0)
165
+ end
166
+
167
+ it "returns correct size as items are added" do
168
+ expect(cache.size).to eq(0)
169
+
170
+ cache.set("key1", "value1")
171
+ expect(cache.size).to eq(1)
172
+
173
+ cache.set("key2", "value2")
174
+ expect(cache.size).to eq(2)
175
+
176
+ cache.set("key3", "value3")
177
+ expect(cache.size).to eq(3)
178
+ end
179
+
180
+ it "does not exceed maximum size" do
181
+ cache.set("key1", "value1")
182
+ cache.set("key2", "value2")
183
+ cache.set("key3", "value3")
184
+ cache.set("key4", "value4")
185
+
186
+ expect(cache.size).to eq(3)
187
+ end
188
+ end
189
+
190
+ describe "#key?" do
191
+ it "returns true for existing keys" do
192
+ cache.set("key1", "value1")
193
+
194
+ expect(cache.key?("key1")).to be(true)
195
+ end
196
+
197
+ it "returns false for non-existent keys" do
198
+ expect(cache.key?("nonexistent")).to be(false)
199
+ end
200
+
201
+ it "returns false after key is evicted" do
202
+ cache.set("key1", "value1")
203
+ cache.set("key2", "value2")
204
+ cache.set("key3", "value3")
205
+ cache.set("key4", "value4") # Evicts key1
206
+
207
+ expect(cache.key?("key1")).to be(false)
208
+ end
209
+ end
210
+
211
+ describe "LRU eviction behavior" do
212
+ it "evicts least recently set item" do
213
+ cache.set("key1", "value1")
214
+ cache.set("key2", "value2")
215
+ cache.set("key3", "value3")
216
+
217
+ # key1 is least recently set
218
+ cache.set("key4", "value4")
219
+
220
+ expect(cache.key?("key1")).to be(false)
221
+ expect(cache.key?("key2")).to be(true)
222
+ expect(cache.key?("key3")).to be(true)
223
+ expect(cache.key?("key4")).to be(true)
224
+ end
225
+
226
+ it "evicts least recently accessed item" do
227
+ cache.set("key1", "value1")
228
+ cache.set("key2", "value2")
229
+ cache.set("key3", "value3")
230
+
231
+ # Access key1 and key2, making key3 least recently used
232
+ cache.get("key1")
233
+ cache.get("key2")
234
+
235
+ cache.set("key4", "value4")
236
+
237
+ expect(cache.key?("key1")).to be(true)
238
+ expect(cache.key?("key2")).to be(true)
239
+ expect(cache.key?("key3")).to be(false)
240
+ expect(cache.key?("key4")).to be(true)
241
+ end
242
+
243
+ it "handles interleaved sets and gets" do
244
+ cache.set("key1", "value1")
245
+ cache.set("key2", "value2")
246
+ cache.get("key1") # key1 becomes most recent
247
+ cache.set("key3", "value3")
248
+
249
+ # key2 is least recently used
250
+ cache.set("key4", "value4")
251
+
252
+ expect(cache.key?("key1")).to be(true)
253
+ expect(cache.key?("key2")).to be(false)
254
+ expect(cache.key?("key3")).to be(true)
255
+ expect(cache.key?("key4")).to be(true)
256
+ end
257
+
258
+ it "handles get_or_set in LRU order" do
259
+ cache.get_or_set("key1") { "value1" }
260
+ cache.get_or_set("key2") { "value2" }
261
+ cache.get_or_set("key3") { "value3" }
262
+
263
+ # Access key1 to make it recent
264
+ cache.get_or_set("key1") { "unused" }
265
+
266
+ # key2 is now least recent
267
+ cache.get_or_set("key4") { "value4" }
268
+
269
+ expect(cache.key?("key1")).to be(true)
270
+ expect(cache.key?("key2")).to be(false)
271
+ expect(cache.key?("key3")).to be(true)
272
+ expect(cache.key?("key4")).to be(true)
273
+ end
274
+ end
275
+
276
+ describe "edge cases" do
277
+ it "handles cache with size 1" do
278
+ tiny_cache = described_class.new(1)
279
+
280
+ tiny_cache.set("key1", "value1")
281
+ expect(tiny_cache.size).to eq(1)
282
+
283
+ tiny_cache.set("key2", "value2")
284
+ expect(tiny_cache.size).to eq(1)
285
+ expect(tiny_cache.key?("key1")).to be(false)
286
+ expect(tiny_cache.key?("key2")).to be(true)
287
+ end
288
+
289
+ it "handles nil values" do
290
+ cache.set("key1", nil)
291
+
292
+ expect(cache.key?("key1")).to be(true)
293
+ expect(cache.get("key1")).to be_nil
294
+ end
295
+
296
+ it "handles various key types" do
297
+ cache.set("string_key", "value1")
298
+ cache.set(:symbol_key, "value2")
299
+ cache.set(123, "value3")
300
+
301
+ expect(cache.get("string_key")).to eq("value1")
302
+ expect(cache.get(:symbol_key)).to eq("value2")
303
+ expect(cache.get(123)).to eq("value3")
304
+ end
305
+
306
+ it "handles various value types" do
307
+ cache.set("key1", "string")
308
+ cache.set("key2", 42)
309
+ cache.set("key3", [1, 2, 3])
310
+
311
+ expect(cache.get("key1")).to eq("string")
312
+ expect(cache.get("key2")).to eq(42)
313
+ expect(cache.get("key3")).to eq([1, 2, 3])
314
+ end
315
+
316
+ it "handles updating same key multiple times" do
317
+ cache.set("key1", "value1")
318
+ cache.set("key1", "value2")
319
+ cache.set("key1", "value3")
320
+
321
+ expect(cache.size).to eq(1)
322
+ expect(cache.get("key1")).to eq("value3")
323
+ end
324
+
325
+ it "handles large cache size" do
326
+ large_cache = described_class.new(1000)
327
+
328
+ 500.times do |i|
329
+ large_cache.set("key#{i}", "value#{i}")
330
+ end
331
+
332
+ expect(large_cache.size).to eq(500)
333
+ expect(large_cache.get("key0")).to eq("value0")
334
+ expect(large_cache.get("key499")).to eq("value499")
335
+ end
336
+ end
337
+
338
+ describe "default size constant" do
339
+ it "has DEFAULT_SIZE constant" do
340
+ expect(described_class::DEFAULT_SIZE).to eq(1000)
341
+ end
342
+
343
+ it "uses DEFAULT_SIZE when no size specified" do
344
+ default_cache = described_class.new
345
+
346
+ # Fill with DEFAULT_SIZE + 1 items
347
+ (described_class::DEFAULT_SIZE + 1).times do |i|
348
+ default_cache.set("key#{i}", "value#{i}")
349
+ end
350
+
351
+ # Should have exactly DEFAULT_SIZE items
352
+ expect(default_cache.size).to eq(described_class::DEFAULT_SIZE)
353
+
354
+ # First item should be evicted
355
+ expect(default_cache.key?("key0")).to be(false)
356
+ end
357
+ end
358
+ end