rambling-trie-opal 2.1.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 (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