rambling-trie-opal 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/Guardfile +10 -0
  4. data/LICENSE +26 -0
  5. data/README.md +301 -0
  6. data/Rakefile +15 -0
  7. data/lib/rambling-trie.rb +3 -0
  8. data/lib/rambling/trie.rb +119 -0
  9. data/lib/rambling/trie/comparable.rb +19 -0
  10. data/lib/rambling/trie/compressible.rb +16 -0
  11. data/lib/rambling/trie/compressor.rb +64 -0
  12. data/lib/rambling/trie/configuration.rb +16 -0
  13. data/lib/rambling/trie/configuration/properties.rb +75 -0
  14. data/lib/rambling/trie/configuration/provider_collection.rb +122 -0
  15. data/lib/rambling/trie/container.rb +226 -0
  16. data/lib/rambling/trie/enumerable.rb +29 -0
  17. data/lib/rambling/trie/inspectable.rb +39 -0
  18. data/lib/rambling/trie/invalid_operation.rb +15 -0
  19. data/lib/rambling/trie/nodes.rb +18 -0
  20. data/lib/rambling/trie/nodes/compressed.rb +98 -0
  21. data/lib/rambling/trie/nodes/missing.rb +12 -0
  22. data/lib/rambling/trie/nodes/node.rb +183 -0
  23. data/lib/rambling/trie/nodes/raw.rb +82 -0
  24. data/lib/rambling/trie/readers.rb +15 -0
  25. data/lib/rambling/trie/readers/plain_text.rb +18 -0
  26. data/lib/rambling/trie/serializers.rb +18 -0
  27. data/lib/rambling/trie/serializers/file.rb +27 -0
  28. data/lib/rambling/trie/serializers/marshal.rb +48 -0
  29. data/lib/rambling/trie/serializers/yaml.rb +55 -0
  30. data/lib/rambling/trie/serializers/zip.rb +74 -0
  31. data/lib/rambling/trie/stringifyable.rb +26 -0
  32. data/lib/rambling/trie/version.rb +8 -0
  33. data/rambling-trie-opal.gemspec +36 -0
  34. data/spec/assets/test_words.en_US.txt +23 -0
  35. data/spec/assets/test_words.es_DO.txt +24 -0
  36. data/spec/integration/rambling/trie_spec.rb +87 -0
  37. data/spec/lib/rambling/trie/comparable_spec.rb +97 -0
  38. data/spec/lib/rambling/trie/compressor_spec.rb +108 -0
  39. data/spec/lib/rambling/trie/configuration/properties_spec.rb +57 -0
  40. data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +149 -0
  41. data/spec/lib/rambling/trie/container_spec.rb +591 -0
  42. data/spec/lib/rambling/trie/enumerable_spec.rb +42 -0
  43. data/spec/lib/rambling/trie/inspectable_spec.rb +56 -0
  44. data/spec/lib/rambling/trie/nodes/compressed_spec.rb +37 -0
  45. data/spec/lib/rambling/trie/nodes/node_spec.rb +9 -0
  46. data/spec/lib/rambling/trie/nodes/raw_spec.rb +179 -0
  47. data/spec/lib/rambling/trie/readers/plain_text_spec.rb +16 -0
  48. data/spec/lib/rambling/trie/serializers/file_spec.rb +13 -0
  49. data/spec/lib/rambling/trie/serializers/marshal_spec.rb +12 -0
  50. data/spec/lib/rambling/trie/serializers/yaml_spec.rb +12 -0
  51. data/spec/lib/rambling/trie/serializers/zip_spec.rb +28 -0
  52. data/spec/lib/rambling/trie/stringifyable_spec.rb +85 -0
  53. data/spec/lib/rambling/trie_spec.rb +182 -0
  54. data/spec/spec_helper.rb +37 -0
  55. data/spec/support/config.rb +15 -0
  56. data/spec/support/helpers/add_word.rb +20 -0
  57. data/spec/support/helpers/one_line_heredoc.rb +11 -0
  58. data/spec/support/shared_examples/a_compressible_trie.rb +40 -0
  59. data/spec/support/shared_examples/a_serializable_trie.rb +30 -0
  60. data/spec/support/shared_examples/a_serializer.rb +37 -0
  61. data/spec/support/shared_examples/a_trie_data_structure.rb +31 -0
  62. data/spec/support/shared_examples/a_trie_node.rb +127 -0
  63. data/spec/support/shared_examples/a_trie_node_implementation.rb +152 -0
  64. data/spec/tmp/.gitkeep +0 -0
  65. metadata +179 -0
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Comparable do
6
+ describe '#==' do
7
+ let(:node_1) { Rambling::Trie::Nodes::Raw.new }
8
+ let(:node_2) { Rambling::Trie::Nodes::Raw.new }
9
+
10
+ context 'when the nodes do not have the same letter' do
11
+ before do
12
+ node_1.letter = :a
13
+ node_2.letter = :b
14
+ end
15
+
16
+ it 'returns false' do
17
+ expect(node_1).not_to eq node_2
18
+ end
19
+ end
20
+
21
+ context 'when the nodes have the same letter and no children' do
22
+ before do
23
+ node_1.letter = :a
24
+ node_2.letter = :a
25
+ end
26
+
27
+ it 'returns true' do
28
+ expect(node_1).to eq node_2
29
+ end
30
+ end
31
+
32
+ context 'when the nodes have the same letter and are terminal' do
33
+ before do
34
+ node_1.letter = :a
35
+ node_1.terminal!
36
+
37
+ node_2.letter = :a
38
+ node_2.terminal!
39
+ end
40
+
41
+ it 'returns true' do
42
+ expect(node_1).to eq node_2
43
+ end
44
+ end
45
+
46
+ context 'when the nodes have the same letter and are not terminal' do
47
+ before do
48
+ node_1.letter = :a
49
+ node_2.letter = :a
50
+ end
51
+
52
+ it 'returns true' do
53
+ expect(node_1).to eq node_2
54
+ end
55
+ end
56
+
57
+ context 'when the nodes have the same letter but are not both terminal' do
58
+ before do
59
+ node_1.letter = :a
60
+ node_1.terminal!
61
+
62
+ node_2.letter = :a
63
+ end
64
+ it 'returns false' do
65
+ expect(node_1).not_to eq node_2
66
+ end
67
+ end
68
+
69
+ context 'when the nodes have the same letter and the same children' do
70
+ before do
71
+ node_1.letter = :t
72
+ add_words node_1, %w(hese hree hings)
73
+
74
+ node_2.letter = :t
75
+ add_words node_2, %w(hese hree hings)
76
+ end
77
+
78
+ it 'returns true' do
79
+ expect(node_1).to eq node_2
80
+ end
81
+ end
82
+
83
+ context 'when the nodes have the same letter but different children' do
84
+ before do
85
+ node_1.letter = :t
86
+ add_words node_1, %w(hese wo)
87
+
88
+ node_2.letter = :t
89
+ add_words node_2, %w(hese hree hings)
90
+ end
91
+
92
+ it 'returns false' do
93
+ expect(node_1).not_to eq node_2
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Compressor do
6
+ let(:compressor) { Rambling::Trie::Compressor.new }
7
+
8
+ describe '#compress' do
9
+ let(:node) { Rambling::Trie::Nodes::Raw.new }
10
+
11
+ it 'compresses the node' do
12
+ add_words node, %w(a few words hello hell)
13
+ compressed = compressor.compress node
14
+
15
+ expect(compressed.children_tree.keys).to eq %i(a f w h)
16
+ end
17
+
18
+ context 'with at least one word' do
19
+ before do
20
+ add_words node, %w(all the words)
21
+ end
22
+
23
+ it 'keeps the node letter nil' do
24
+ compressed = compressor.compress node
25
+
26
+ expect(compressed.letter).to be_nil
27
+ end
28
+ end
29
+
30
+ context 'with a single word' do
31
+ before do
32
+ add_word node, 'all'
33
+ end
34
+
35
+ it 'compresses into a single node without children' do
36
+ compressed = compressor.compress node
37
+
38
+ expect(compressed[:a].letter).to eq :all
39
+ expect(compressed[:a].children.size).to eq 0
40
+ expect(compressed[:a]).to be_terminal
41
+ expect(compressed[:a]).to be_compressed
42
+ end
43
+ end
44
+
45
+ context 'with two words' do
46
+ before do
47
+ add_words node, %w(all ask)
48
+ end
49
+
50
+ it 'compresses into corresponding three nodes' do
51
+ compressed = compressor.compress node
52
+
53
+ expect(compressed[:a].letter).to eq :a
54
+ expect(compressed[:a].children.size).to eq 2
55
+
56
+ expect(compressed[:a][:l].letter).to eq :ll
57
+ expect(compressed[:a][:s].letter).to eq :sk
58
+
59
+ expect(compressed[:a][:l].children.size).to eq 0
60
+ expect(compressed[:a][:s].children.size).to eq 0
61
+
62
+ expect(compressed[:a][:l]).to be_terminal
63
+ expect(compressed[:a][:s]).to be_terminal
64
+
65
+ expect(compressed[:a][:l]).to be_compressed
66
+ expect(compressed[:a][:s]).to be_compressed
67
+ end
68
+ end
69
+
70
+ it 'reassigns the parent nodes correctly' do
71
+ add_words node, %w(repay rest repaint)
72
+ compressed = compressor.compress node
73
+
74
+ expect(compressed[:r].letter).to eq :re
75
+ expect(compressed[:r].parent).to eq compressed
76
+ expect(compressed[:r].children.size).to eq 2
77
+
78
+ expect(compressed[:r][:p].letter).to eq :pa
79
+ expect(compressed[:r][:p].parent).to eq compressed[:r]
80
+ expect(compressed[:r][:p].children.size).to eq 2
81
+
82
+ expect(compressed[:r][:s].letter).to eq :st
83
+ expect(compressed[:r][:s].parent).to eq compressed[:r]
84
+ expect(compressed[:r][:s].children.size).to eq 0
85
+
86
+ expect(compressed[:r][:p][:y].letter).to eq :y
87
+ expect(compressed[:r][:p][:y].parent).to eq compressed[:r][:p]
88
+ expect(compressed[:r][:p][:y].children.size).to eq 0
89
+
90
+ expect(compressed[:r][:p][:i].letter).to eq :int
91
+ expect(compressed[:r][:p][:i].parent).to eq compressed[:r][:p]
92
+ expect(compressed[:r][:p][:i].children.size).to eq 0
93
+ end
94
+
95
+ it 'does not compress terminal nodes' do
96
+ add_words node, %w(you your yours)
97
+ compressed = compressor.compress node
98
+
99
+ expect(compressed[:y].letter).to eq :you
100
+
101
+ expect(compressed[:y][:r].letter).to eq :r
102
+ expect(compressed[:y][:r]).to be_compressed
103
+
104
+ expect(compressed[:y][:r][:s].letter).to eq :s
105
+ expect(compressed[:y][:r][:s]).to be_compressed
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Configuration::Properties do
6
+ let(:properties) { Rambling::Trie::Configuration::Properties.new }
7
+
8
+ describe '.new' do
9
+ it 'configures the serializers' do
10
+ serializers = properties.serializers
11
+
12
+ expect(serializers.formats).to match_array %i(marshal yaml yml)
13
+ expect(serializers.providers.to_a).to match_array [
14
+ [:marshal, Rambling::Trie::Serializers::Marshal],
15
+ [:yaml, Rambling::Trie::Serializers::Yaml],
16
+ [:yml, Rambling::Trie::Serializers::Yaml],
17
+ # [:zip, Rambling::Trie::Serializers::Zip],
18
+ ]
19
+ end
20
+
21
+ it 'configures the readers' do
22
+ readers = properties.readers
23
+
24
+ expect(readers.formats).to match_array %i(txt)
25
+ expect(readers.providers.to_a).to match_array [
26
+ [:txt, Rambling::Trie::Readers::PlainText],
27
+ ]
28
+ end
29
+
30
+ it 'configures the compressor' do
31
+ compressor = properties.compressor
32
+ expect(compressor).to be_instance_of Rambling::Trie::Compressor
33
+ end
34
+
35
+ it 'configures the root_builder' do
36
+ root = properties.root_builder.call
37
+ expect(root).to be_instance_of Rambling::Trie::Nodes::Raw
38
+ end
39
+ end
40
+
41
+ describe '#reset' do
42
+ before do
43
+ properties.serializers.add :test, 'test'
44
+ properties.readers.add :test, 'test'
45
+ end
46
+
47
+ it 'resets the serializers and readers to initial values' do
48
+ expect(properties.serializers.formats).to include :test
49
+ expect(properties.readers.formats).to include :test
50
+
51
+ properties.reset
52
+
53
+ expect(properties.serializers.formats).not_to include :test
54
+ expect(properties.readers.formats).not_to include :test
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Configuration::ProviderCollection do
6
+ let(:configured_default) { nil }
7
+ let(:configured_providers) do
8
+ { one: first_provider, two: second_provider }
9
+ end
10
+
11
+ let(:first_provider) { double :first_provider }
12
+ let(:second_provider) { double :second_provider }
13
+
14
+ let(:provider_collection) do
15
+ Rambling::Trie::Configuration::ProviderCollection.new(
16
+ :provider,
17
+ configured_providers,
18
+ configured_default,
19
+ )
20
+ end
21
+
22
+ describe '.new' do
23
+ it 'has a name' do
24
+ expect(provider_collection.name).to eq :provider
25
+ end
26
+
27
+ it 'has the given providers' do
28
+ expect(provider_collection.providers)
29
+ .to eq one: first_provider, two: second_provider
30
+ end
31
+
32
+ it 'has a default provider' do
33
+ expect(provider_collection.default).to eq first_provider
34
+ end
35
+
36
+ context 'when a default is provided' do
37
+ let(:configured_default) { second_provider }
38
+
39
+ it 'has that as the default provider' do
40
+ expect(provider_collection.default).to eq second_provider
41
+ end
42
+ end
43
+ end
44
+
45
+ describe 'aliases and delegates' do
46
+ let(:providers) { provider_collection.providers }
47
+
48
+ before do
49
+ allow(providers) .to receive_messages(
50
+ :[] => 'value',
51
+ keys: %i(a b),
52
+ )
53
+ end
54
+
55
+ it 'delegates `#[]` to providers' do
56
+ expect(provider_collection[:key]).to eq 'value'
57
+ expect(providers).to have_received(:[]).with :key
58
+ end
59
+
60
+ it 'aliases `#formats` to `providers#keys`' do
61
+ expect(provider_collection.formats).to eq %i(a b)
62
+ expect(providers).to have_received :keys
63
+ end
64
+ end
65
+
66
+ describe '#add' do
67
+ let(:provider) { double :provider }
68
+
69
+ before do
70
+ provider_collection.add :three, provider
71
+ end
72
+
73
+ it 'adds a new provider' do
74
+ expect(provider_collection.providers[:three]).to eq provider
75
+ end
76
+ end
77
+
78
+ describe '#default=' do
79
+ let(:other_provider) { double :other_provider }
80
+
81
+ context 'when the given value is in the providers list' do
82
+ it 'changes the default provider' do
83
+ provider_collection.default = second_provider
84
+ expect(provider_collection.default).to eq second_provider
85
+ end
86
+ end
87
+
88
+ context 'when the given value is not in the providers list' do
89
+ it 'raises an error and keeps the default provider' do
90
+ expect { provider_collection.default = other_provider }
91
+ .to raise_error(ArgumentError)
92
+ .and(not_change { provider_collection.default })
93
+ end
94
+
95
+ it 'raises an ArgumentError' do
96
+ expect { provider_collection.default = other_provider }
97
+ .to raise_error ArgumentError
98
+ end
99
+ end
100
+
101
+ context 'when the providers list is empty' do
102
+ let(:configured_providers) { {} }
103
+
104
+ it 'accepts nil' do
105
+ provider_collection.default = nil
106
+ expect(provider_collection.default).to be_nil
107
+ end
108
+
109
+ it 'raises an ArgumentError for any other provider' do
110
+ expect do
111
+ provider_collection.default = other_provider
112
+ end.to raise_error ArgumentError
113
+ expect(provider_collection.default).to be_nil
114
+ end
115
+ end
116
+ end
117
+
118
+ describe '#resolve' do
119
+ context 'when the file extension is one of the providers' do
120
+ it 'returns the corresponding provider' do
121
+ expect(provider_collection.resolve 'hola.one').to eq first_provider
122
+ expect(provider_collection.resolve 'hola.two').to eq second_provider
123
+ end
124
+ end
125
+
126
+ context 'when the file extension is not one of the providers' do
127
+ it 'returns the default provider' do
128
+ expect(provider_collection.resolve 'hola.unknown').to eq first_provider
129
+ expect(provider_collection.resolve 'hola').to eq first_provider
130
+ end
131
+ end
132
+ end
133
+
134
+ describe '#reset' do
135
+ let(:configured_default) { second_provider }
136
+ let(:provider) { double :provider }
137
+
138
+ before do
139
+ provider_collection.add :three, provider
140
+ provider_collection.default = provider
141
+ end
142
+
143
+ it 'resets to back to the initially configured values' do
144
+ provider_collection.reset
145
+ expect(provider_collection[:three]).to be_nil
146
+ expect(provider_collection.default).to eq second_provider
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,591 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Rambling::Trie::Container do
6
+ let(:container) { Rambling::Trie::Container.new root, compressor }
7
+ let(:compressor) { Rambling::Trie::Compressor.new }
8
+ let(:root) { Rambling::Trie::Nodes::Raw.new }
9
+
10
+ describe '.new' do
11
+ it 'uses the provided node as root' do
12
+ expect(container.root).to be root
13
+ end
14
+
15
+ context 'with a block' do
16
+ it 'yields the container' do
17
+ yielded = nil
18
+
19
+ container = Rambling::Trie::Container.new root, compressor do |c|
20
+ yielded = c
21
+ end
22
+
23
+ expect(yielded).to be container
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#add' do
29
+ it 'adds the word to the root node' do
30
+ add_word container, 'hello'
31
+
32
+ expect(root.children.size).to eq 1
33
+ expect(root.to_a).to eq %w(hello)
34
+ end
35
+ end
36
+
37
+ describe '#concat' do
38
+ it 'adds all the words to the root node' do
39
+ container.concat %w(other words)
40
+
41
+ expect(root.children.size).to eq 2
42
+ expect(root.to_a).to eq %w(other words)
43
+ end
44
+
45
+ it 'returns all the corresponding nodes' do
46
+ nodes = container.concat %w(other words)
47
+
48
+ expect(nodes.first.letter).to eq :o
49
+ expect(nodes.last.letter).to eq :w
50
+ end
51
+ end
52
+
53
+ describe '#compress!' do
54
+ let(:node) { Rambling::Trie::Nodes::Compressed.new }
55
+
56
+ before do
57
+ allow(compressor).to receive(:compress).and_return node
58
+
59
+ add_word root, 'yes'
60
+ node[:yes] = Rambling::Trie::Nodes::Compressed.new
61
+ end
62
+
63
+ it 'compresses the trie using the compressor' do
64
+ container.compress!
65
+
66
+ expect(compressor).to have_received(:compress).with root
67
+ end
68
+
69
+ it 'changes to the root returned by the compressor' do
70
+ container.compress!
71
+
72
+ expect(container.root).not_to eq root
73
+ expect(container.root).to eq node
74
+ end
75
+
76
+ it 'returns itself' do
77
+ expect(container.compress!).to eq container
78
+ end
79
+
80
+ it 'does not compress multiple times' do
81
+ container.compress!
82
+ allow(node).to receive(:compressed?).and_return(true)
83
+
84
+ container.compress!
85
+ expect(compressor).to have_received(:compress).once
86
+ end
87
+ end
88
+
89
+ describe '#compress' do
90
+ let(:node) { Rambling::Trie::Nodes::Compressed.new }
91
+
92
+ before do
93
+ allow(compressor).to receive(:compress).and_return node
94
+
95
+ add_word root, 'yes'
96
+ node[:yes] = Rambling::Trie::Nodes::Compressed.new
97
+ end
98
+
99
+ it 'compresses the trie using the compressor' do
100
+ container.compress
101
+
102
+ expect(compressor).to have_received(:compress).with root
103
+ end
104
+
105
+ it 'returns a container with the new root' do
106
+ new_container = container.compress
107
+
108
+ expect(new_container.root).not_to eq root
109
+ expect(new_container.root).to eq node
110
+ end
111
+
112
+ it 'returns a new container' do
113
+ expect(container.compress).not_to eq container
114
+ end
115
+
116
+ it 'can compress multiple times' do
117
+ container.compress
118
+ container.compress
119
+
120
+ expect(compressor).to have_received(:compress).twice
121
+ end
122
+
123
+ it 'cannot compress the result' do
124
+ new_container = container.compress
125
+ new_container.compress
126
+
127
+ expect(compressor).to have_received(:compress).once
128
+ end
129
+ end
130
+
131
+ describe '#word?' do
132
+ let(:root) do
133
+ double :root,
134
+ compressed?: compressed,
135
+ word?: nil
136
+ end
137
+
138
+ context 'for an uncompressed root' do
139
+ let(:compressed) { true }
140
+
141
+ it 'calls the root with the word characters' do
142
+ container.word? 'words'
143
+ expect(root).to have_received(:word?).with %w(w o r d s)
144
+ end
145
+ end
146
+
147
+ context 'for a compressed root' do
148
+ let(:compressed) { false }
149
+
150
+ it 'calls the root with the full word' do
151
+ container.word? 'words'
152
+ expect(root).to have_received(:word?).with %w(w o r d s)
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '#partial_word?' do
158
+ let(:root) do
159
+ double :root,
160
+ compressed?: compressed,
161
+ partial_word?: nil
162
+ end
163
+
164
+ context 'for an uncompressed root' do
165
+ let(:compressed) { true }
166
+
167
+ it 'calls the root with the word characters' do
168
+ container.partial_word? 'words'
169
+ expect(root).to have_received(:partial_word?).with %w(w o r d s)
170
+ end
171
+ end
172
+
173
+ context 'for a compressed root' do
174
+ let(:compressed) { false }
175
+
176
+ it 'calls the root with the word characters' do
177
+ container.partial_word? 'words'
178
+ expect(root).to have_received(:partial_word?).with %w(w o r d s)
179
+ end
180
+ end
181
+ end
182
+
183
+ describe 'delegates and aliases' do
184
+ before do
185
+ allow(root).to receive_messages(
186
+ :[] => nil,
187
+ add: nil,
188
+ as_word: nil,
189
+ children: nil,
190
+ children_tree: nil,
191
+ compressed?: nil,
192
+ each: nil,
193
+ key?: nil,
194
+ inspect: nil,
195
+ letter: nil,
196
+ parent: nil,
197
+ partial_word?: nil,
198
+ scan: nil,
199
+ size: nil,
200
+ to_s: nil,
201
+ word?: nil,
202
+ )
203
+ end
204
+
205
+ it 'aliases `#include?` to `#word?`' do
206
+ container.include? 'words'
207
+ expect(root).to have_received(:word?).with %w(w o r d s)
208
+ end
209
+
210
+ it 'aliases `#match?` to `#partial_word?`' do
211
+ container.match? 'words'
212
+ expect(root).to have_received(:partial_word?).with %w(w o r d s)
213
+ end
214
+
215
+ it 'aliases `#words` to `#scan`' do
216
+ container.words 'hig'
217
+ expect(root).to have_received(:scan).with %w(h i g)
218
+ end
219
+
220
+ it 'aliases `#<<` to `#add`' do
221
+ container << 'words'
222
+ expect(root).to have_received(:add).with %i(s d r o w)
223
+ end
224
+
225
+ it 'delegates `#[]` to the root node' do
226
+ container[:yep]
227
+ expect(root).to have_received(:[]).with :yep
228
+ end
229
+
230
+ it 'delegates `#children` to the root node' do
231
+ container.children
232
+ expect(root).to have_received :children
233
+ end
234
+
235
+ it 'delegates `#children_tree` to the root node' do
236
+ container.children_tree
237
+ expect(root).to have_received :children_tree
238
+ end
239
+
240
+ it 'delegates `#compressed?` to the root node' do
241
+ container.compressed?
242
+ expect(root).to have_received :compressed?
243
+ end
244
+
245
+ it 'delegates `#key?` to the root node' do
246
+ container.key? :yup
247
+ expect(root).to have_received(:key?).with :yup
248
+ end
249
+
250
+ it 'aliases `#has_key?` to `#key?`' do
251
+ container.has_key? :yup
252
+ expect(root).to have_received(:key?).with :yup
253
+ end
254
+
255
+ it 'aliases `#has_letter?` to `#has_key?`' do
256
+ container.has_letter? :yup
257
+ expect(root).to have_received(:key?).with :yup
258
+ end
259
+
260
+ it 'delegates `#inspect` to the root node' do
261
+ container.inspect
262
+ expect(root).to have_received :inspect
263
+ end
264
+
265
+ it 'delegates `#size` to the root node' do
266
+ container.size
267
+ expect(root).to have_received :size
268
+ end
269
+ end
270
+
271
+ describe '#compress!' do
272
+ it 'gets a new root from the compressor' do
273
+ container.compress!
274
+
275
+ expect(container.root).not_to be root
276
+ expect(container.root).to be_compressed
277
+ expect(root).not_to be_compressed
278
+ end
279
+
280
+ it 'generates a new root with the words from the passed root' do
281
+ words = %w(a few words hello hell)
282
+
283
+ add_words container, words
284
+ container.compress!
285
+
286
+ words.each do |word|
287
+ expect(container).to include word
288
+ end
289
+ end
290
+
291
+ describe 'and trying to add a word' do
292
+ it 'raises an error' do
293
+ add_words container, %w(repay rest repaint)
294
+ container.compress!
295
+
296
+ expect do
297
+ add_word container, 'restaurant'
298
+ end.to raise_error Rambling::Trie::InvalidOperation
299
+ end
300
+ end
301
+ end
302
+
303
+ describe '#word?' do
304
+ context 'word is contained' do
305
+ before do
306
+ add_words container, %w(hello high)
307
+ end
308
+
309
+ it 'matches the whole word' do
310
+ expect(container.word? 'hello').to be true
311
+ expect(container.word? 'high').to be true
312
+ end
313
+
314
+ context 'and the root has been compressed' do
315
+ before do
316
+ container.compress!
317
+ end
318
+
319
+ it 'matches the whole word' do
320
+ expect(container.word? 'hello').to be true
321
+ expect(container.word? 'high').to be true
322
+ end
323
+ end
324
+ end
325
+
326
+ context 'word is not contained' do
327
+ before do
328
+ add_word container, 'hello'
329
+ end
330
+
331
+ it 'does not match the whole word' do
332
+ expect(container.word? 'halt').to be false
333
+ expect(container.word? 'al').to be false
334
+ end
335
+
336
+ context 'and the root has been compressed' do
337
+ before do
338
+ container.compress!
339
+ end
340
+
341
+ it 'does not match the whole word' do
342
+ expect(container.word? 'halt').to be false
343
+ expect(container.word? 'al').to be false
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ describe '#partial_word?' do
350
+ context 'word is contained' do
351
+ before do
352
+ add_words container, %w(hello high)
353
+ end
354
+
355
+ it 'matches part of the word' do
356
+ expect(container.partial_word? 'hell').to be true
357
+ expect(container.partial_word? 'hig').to be true
358
+ end
359
+
360
+ context 'and the root has been compressed' do
361
+ before do
362
+ container.compress!
363
+ end
364
+
365
+ it 'matches part of the word' do
366
+ expect(container.partial_word? 'h').to be true
367
+ expect(container.partial_word? 'he').to be true
368
+ expect(container.partial_word? 'hell').to be true
369
+ expect(container.partial_word? 'hello').to be true
370
+ expect(container.partial_word? 'hi').to be true
371
+ expect(container.partial_word? 'hig').to be true
372
+ expect(container.partial_word? 'high').to be true
373
+ end
374
+ end
375
+ end
376
+
377
+ shared_examples_for 'a non matching tree' do
378
+ it 'does not match any part of the word' do
379
+ %w(ha hal al).each do |word|
380
+ expect(container.partial_word? word).to be false
381
+ end
382
+ end
383
+ end
384
+
385
+ context 'word is not contained' do
386
+ before do
387
+ add_word container, 'hello'
388
+ end
389
+
390
+ context 'and the root is uncompressed' do
391
+ it_behaves_like 'a non matching tree'
392
+ end
393
+
394
+ context 'and the root has been compressed' do
395
+ it_behaves_like 'a non matching tree'
396
+ end
397
+ end
398
+ end
399
+
400
+ describe '#scan' do
401
+ context 'words that match are not contained' do
402
+ before do
403
+ add_words container, %w(hi hello high hell highlight histerical)
404
+ end
405
+
406
+ it 'returns an array with the words that match' do
407
+ expect(container.scan 'hi').to eq %w(hi high highlight histerical)
408
+ expect(container.scan 'hig').to eq %w(high highlight)
409
+ end
410
+
411
+ context 'and the root has been compressed' do
412
+ before do
413
+ container.compress!
414
+ end
415
+
416
+ it 'returns an array with the words that match' do
417
+ expect(container.scan 'hi').to eq %w(hi high highlight histerical)
418
+ expect(container.scan 'hig').to eq %w(high highlight)
419
+ end
420
+ end
421
+ end
422
+
423
+ context 'words that match are not contained' do
424
+ before do
425
+ add_word container, 'hello'
426
+ end
427
+
428
+ it 'returns an empty array' do
429
+ expect(container.scan 'hi').to eq %w()
430
+ end
431
+
432
+ context 'and the root has been compressed' do
433
+ before do
434
+ container.compress!
435
+ end
436
+
437
+ it 'returns an empty array' do
438
+ expect(container.scan 'hi').to eq %w()
439
+ end
440
+ end
441
+ end
442
+ end
443
+
444
+ describe '#words_within' do
445
+ before do
446
+ add_words container, %w(one word and other words)
447
+ end
448
+
449
+ context 'phrase does not contain any words' do
450
+ it 'returns an empty array' do
451
+ expect(container.words_within 'xyz').to match_array []
452
+ end
453
+
454
+ context 'and the node is compressed' do
455
+ before do
456
+ container.compress!
457
+ end
458
+
459
+ it 'returns an empty array' do
460
+ expect(container.words_within 'xyz').to match_array []
461
+ end
462
+ end
463
+ end
464
+
465
+ context 'phrase contains one word at the start of the phrase' do
466
+ it 'returns an array with the word found in the phrase' do
467
+ expect(container.words_within 'word').to match_array %w(word)
468
+ expect(container.words_within 'wordxyz').to match_array %w(word)
469
+ end
470
+
471
+ context 'and the node is compressed' do
472
+ before do
473
+ container.compress!
474
+ end
475
+
476
+ it 'returns an array with the word found in the phrase' do
477
+ expect(container.words_within 'word').to match_array %w(word)
478
+ expect(container.words_within 'wordxyz').to match_array %w(word)
479
+ end
480
+ end
481
+ end
482
+
483
+ context 'phrase contains one word at the end of the phrase' do
484
+ it 'returns an array with the word found in the phrase' do
485
+ expect(container.words_within 'xyz word').to match_array %w(word)
486
+ end
487
+
488
+ context 'and the node is compressed' do
489
+ before do
490
+ container.compress!
491
+ end
492
+
493
+ it 'returns an array with the word found in the phrase' do
494
+ expect(container.words_within 'xyz word').to match_array %w(word)
495
+ end
496
+ end
497
+ end
498
+
499
+ context 'phrase contains a few words' do
500
+ it 'returns an array with all words found in the phrase' do
501
+ expect(container.words_within 'xyzword otherzxyone')
502
+ .to match_array %w(word other one)
503
+ end
504
+
505
+ context 'and the node is compressed' do
506
+ before do
507
+ container.compress!
508
+ end
509
+
510
+ it 'returns an array with all words found in the phrase' do
511
+ expect(container.words_within 'xyzword otherzxyone')
512
+ .to match_array %w(word other one)
513
+ end
514
+ end
515
+ end
516
+ end
517
+
518
+ describe '#words_within?' do
519
+ before do
520
+ add_words container, %w(one word and other words)
521
+ end
522
+
523
+ context 'phrase does not contain any words' do
524
+ it 'returns false' do
525
+ expect(container.words_within? 'xyz').to be false
526
+ end
527
+ end
528
+
529
+ context 'phrase contains any word' do
530
+ it 'returns true' do
531
+ expect(container.words_within? 'xyz words').to be true
532
+ expect(container.words_within? 'xyzone word').to be true
533
+ end
534
+ end
535
+ end
536
+
537
+ describe '#==' do
538
+ context 'when the root nodes are the same' do
539
+ let(:other_container) do
540
+ Rambling::Trie::Container.new container.root, compressor
541
+ end
542
+
543
+ it 'returns true' do
544
+ expect(container).to eq other_container
545
+ end
546
+ end
547
+
548
+ context 'when the root nodes are not the same' do
549
+ let(:other_root) { Rambling::Trie::Nodes::Raw.new }
550
+ let(:other_container) do
551
+ Rambling::Trie::Container.new other_root, compressor
552
+ end
553
+
554
+ before do
555
+ add_word other_container, 'hola'
556
+ end
557
+
558
+ it 'returns false' do
559
+ expect(container).not_to eq other_container
560
+ end
561
+ end
562
+ end
563
+
564
+ describe '#each' do
565
+ before do
566
+ add_words container, %w(yes no why)
567
+ end
568
+
569
+ it 'returns an enumerator when no block is given' do
570
+ expect(container.each).to be_instance_of Enumerator
571
+ end
572
+
573
+ it 'iterates through all words contained' do
574
+ expect(container.each.to_a).to eq %w(yes no why)
575
+ end
576
+ end
577
+
578
+ describe '#inspect' do
579
+ before do
580
+ add_words container, %w(a few words hello hell)
581
+ end
582
+
583
+ it 'returns the container class name plus the root inspection' do
584
+ expect(container.inspect).to eq one_line <<~CONTAINER
585
+ #<Rambling::Trie::Container root: #<Rambling::Trie::Nodes::Raw letter: nil,
586
+ terminal: nil,
587
+ children: [:a, :f, :w, :h]>>
588
+ CONTAINER
589
+ end
590
+ end
591
+ end