Skip to content

Commit c74e998

Browse files
committed
Use mini_racer to test search.js
1 parent 781e643 commit c74e998

File tree

2 files changed

+325
-0
lines changed

2 files changed

+325
-0
lines changed

Gemfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ gem 'rubocop', '>= 1.31.0'
1111
gem 'gettext'
1212
gem 'prism', '>= 0.30.0'
1313
gem 'webrick'
14+
15+
platforms :ruby do
16+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2')
17+
gem 'mini_racer' # For testing the searcher.js file
18+
end
19+
end
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'helper'
4+
5+
return if RUBY_DESCRIPTION =~ /truffleruby/ || RUBY_DESCRIPTION =~ /jruby/
6+
7+
begin
8+
require 'mini_racer'
9+
rescue LoadError
10+
return
11+
end
12+
13+
# This test is a simpler setup for testing the searcher.js file without pulling all the JS dependencies.
14+
# If there are more JS functionalities to test in the future, we can move to use JS test frameworks.
15+
class RDocGeneratorJsonIndexSearcherTest < Test::Unit::TestCase
16+
def setup
17+
@context = MiniRacer::Context.new
18+
19+
# Add RegExp.escape polyfill to avoid `RegExp.escape is not a function` error
20+
@context.eval(<<~JS)
21+
RegExp.escape = function(string) {
22+
return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');
23+
};
24+
JS
25+
26+
searcher_js_path = File.expand_path(
27+
'../../lib/rdoc/generator/template/json_index/js/searcher.js',
28+
__dir__
29+
)
30+
searcher_js = File.read(searcher_js_path)
31+
@context.eval(searcher_js)
32+
end
33+
34+
def teardown
35+
@context.dispose
36+
end
37+
38+
def test_exact_match_prioritized
39+
results = run_search(
40+
query: 'string',
41+
data: {
42+
searchIndex: ['string', 'string', 'strings'],
43+
longSearchIndex: ['gem::safemarshal::elements::string', 'string', 'strings'],
44+
info: [
45+
['String', 'Gem::SafeMarshal::Elements', 'Gem/SafeMarshal/Elements/String.html', '', 'Nested String class', '', 'class'],
46+
['String', '', 'String.html', '', 'Top-level String class', '', 'class'],
47+
['Strings', '', 'Strings.html', '', 'Strings class', '', 'class']
48+
]
49+
}
50+
)
51+
52+
assert_equal 3, results.length
53+
# Top-level String should come first despite being second in the array
54+
assert_equal 'String', strip_highlights(results[0]['title'])
55+
assert_equal '', results[0]['namespace'], 'Top-level String should be prioritized over nested String'
56+
assert_equal 'String.html', results[0]['path']
57+
58+
# Nested String should come second
59+
assert_equal 'String', strip_highlights(results[1]['title'])
60+
assert_equal 'Gem::SafeMarshal::Elements', strip_highlights(results[1]['namespace'])
61+
end
62+
63+
def test_exact_method_match
64+
results = run_search(
65+
query: 'attribute',
66+
data: {
67+
searchIndex: ['attributemanager', 'attributes', 'attribute()'],
68+
longSearchIndex: ['rdoc::markup::attributemanager', 'rdoc::markup::attributes', 'rdoc::markup::attributemanager#attribute()'],
69+
info: [
70+
['AttributeManager', 'RDoc::Markup', 'RDoc/Markup/AttributeManager.html', '', 'AttributeManager class', '', 'class'],
71+
['Attributes', 'RDoc::Markup', 'RDoc/Markup/Attributes.html', '', 'Attributes class', '', 'class'],
72+
['attribute', 'RDoc::Markup::AttributeManager', 'RDoc/Markup/AttributeManager.html#method-i-attribute', '()', 'Attribute method', '', 'method']
73+
]
74+
}
75+
)
76+
77+
assert_equal 3, results.length
78+
# attribute() method should come first despite being last in the array
79+
assert_equal 'attribute', strip_highlights(results[0]['title'])
80+
assert_equal 'RDoc::Markup::AttributeManager', strip_highlights(results[0]['namespace'])
81+
end
82+
83+
def test_exact_class_beats_exact_method
84+
results = run_search(
85+
query: 'attribute',
86+
data: {
87+
searchIndex: ['attribute()', 'attribute'],
88+
longSearchIndex: ['rdoc::markup#attribute()', 'attribute'],
89+
info: [
90+
['attribute', 'RDoc::Markup', 'RDoc/Markup.html#method-i-attribute', '()', 'Attribute method', '', 'method'],
91+
['Attribute', '', 'Attribute.html', '', 'Attribute class (hypothetical)', '', 'class']
92+
]
93+
}
94+
)
95+
96+
assert_equal 2, results.length
97+
# Exact class match (Pass 0) should beat exact method match (Pass 1)
98+
assert_equal 'Attribute', strip_highlights(results[0]['title'])
99+
assert_equal '', results[0]['namespace']
100+
assert_equal 'Attribute.html', results[0]['path']
101+
102+
# Method comes second
103+
assert_equal 'attribute', strip_highlights(results[1]['title'])
104+
assert_equal 'RDoc::Markup', strip_highlights(results[1]['namespace'])
105+
end
106+
107+
def test_beginning_match
108+
results = run_search(
109+
query: 'attr',
110+
data: {
111+
searchIndex: ['attribute()', 'attributemanager', 'generator'],
112+
longSearchIndex: ['rdoc::markup#attribute()', 'rdoc::markup::attributemanager', 'rdoc::generator'],
113+
info: [
114+
['attribute', 'RDoc::Markup', 'RDoc/Markup.html#method-i-attribute', '()', 'Attribute method', '', 'method'],
115+
['AttributeManager', 'RDoc::Markup', 'RDoc/Markup/AttributeManager.html', '', 'Manager class', '', 'class'],
116+
['Generator', 'RDoc', 'RDoc/Generator.html', '', 'Generator class', '', 'class']
117+
]
118+
}
119+
)
120+
121+
assert_equal 2, results.length
122+
assert_equal 'attribute', strip_highlights(results[0]['title'])
123+
assert_equal 'AttributeManager', strip_highlights(results[1]['title'])
124+
end
125+
126+
def test_long_index_match
127+
results = run_search(
128+
query: 'rdoc::markup',
129+
data: {
130+
searchIndex: ['attributes', 'parser'],
131+
longSearchIndex: ['rdoc::markup::attributes', 'rdoc::parser'],
132+
info: [
133+
['Attributes', 'RDoc::Markup', 'RDoc/Markup/Attributes.html', '', 'Attributes class', '', 'class'],
134+
['Parser', 'RDoc', 'RDoc/Parser.html', '', 'Parser class', '', 'class']
135+
]
136+
}
137+
)
138+
139+
assert_equal 1, results.length
140+
assert_equal 'Attributes', strip_highlights(results[0]['title'])
141+
assert_equal 'RDoc::Markup', strip_highlights(results[0]['namespace'])
142+
end
143+
144+
def test_contains_match
145+
results = run_search(
146+
query: 'manager',
147+
data: {
148+
searchIndex: ['attributemanager', 'parser'],
149+
longSearchIndex: ['rdoc::markup::attributemanager', 'rdoc::parser'],
150+
info: [
151+
['AttributeManager', 'RDoc::Markup', 'RDoc/Markup/AttributeManager.html', '', 'Manager class', '', 'class'],
152+
['Parser', 'RDoc', 'RDoc/Parser.html', '', 'Parser class', '', 'class']
153+
]
154+
}
155+
)
156+
157+
assert_equal 1, results.length
158+
assert_equal 'AttributeManager', strip_highlights(results[0]['title'])
159+
end
160+
161+
def test_regexp_match
162+
results = run_search(
163+
query: 'atrbt',
164+
data: {
165+
searchIndex: ['attribute()', 'generator'],
166+
longSearchIndex: ['rdoc::markup#attribute()', 'rdoc::generator'],
167+
info: [
168+
['attribute', 'RDoc::Markup', 'RDoc/Markup.html#method-i-attribute', '()', 'Attribute method', '', 'method'],
169+
['Generator', 'RDoc', 'RDoc/Generator.html', '', 'Generator class', '', 'class']
170+
]
171+
}
172+
)
173+
174+
assert_equal 1, results.length
175+
assert_equal 'attribute', strip_highlights(results[0]['title'])
176+
end
177+
178+
def test_empty_query
179+
results = run_search(
180+
query: '',
181+
data: {
182+
searchIndex: ['string'],
183+
longSearchIndex: ['string'],
184+
info: [['String', '', 'String.html', '', 'String class', '', 'class']]
185+
}
186+
)
187+
188+
assert_equal 0, results.length
189+
end
190+
191+
def test_no_matches
192+
results = run_search(
193+
query: 'nonexistent',
194+
data: {
195+
searchIndex: ['string', 'attribute()'],
196+
longSearchIndex: ['string', 'rdoc#attribute()'],
197+
info: [
198+
['String', '', 'String.html', '', 'String class', '', 'class'],
199+
['attribute', 'RDoc', 'RDoc.html#attribute', '()', 'Attribute method', '', 'method']
200+
]
201+
}
202+
)
203+
204+
assert_equal 0, results.length
205+
end
206+
207+
def test_multiple_exact_matches
208+
results = run_search(
209+
query: 'test',
210+
data: {
211+
searchIndex: ['test', 'test', 'testing'],
212+
longSearchIndex: ['test', 'rdoc::test', 'testing'],
213+
info: [
214+
['Test', '', 'Test.html', '', 'Top-level Test', '', 'class'],
215+
['Test', 'RDoc', 'RDoc/Test.html', '', 'RDoc Test', '', 'class'],
216+
['Testing', '', 'Testing.html', '', 'Testing class', '', 'class']
217+
]
218+
}
219+
)
220+
221+
assert_equal 3, results.length
222+
# First result should be the exact match with both indexes matching
223+
assert_equal 'Test', strip_highlights(results[0]['title'])
224+
assert_equal '', results[0]['namespace']
225+
end
226+
227+
# Test case insensitive search
228+
def test_case_insensitive
229+
results = run_search(
230+
query: 'STRING',
231+
data: {
232+
searchIndex: ['string'],
233+
longSearchIndex: ['string'],
234+
info: [['String', '', 'String.html', '', 'String class', '', 'class']]
235+
}
236+
)
237+
238+
assert_equal 1, results.length
239+
assert_equal 'String', strip_highlights(results[0]['title'])
240+
end
241+
242+
def test_multi_word_query
243+
results = run_search(
244+
query: 'rdoc markup',
245+
data: {
246+
searchIndex: ['attributemanager'],
247+
longSearchIndex: ['rdoc::markup::attributemanager'],
248+
info: [['AttributeManager', 'RDoc::Markup', 'RDoc/Markup/AttributeManager.html', '', 'Manager', '', 'class']]
249+
}
250+
)
251+
252+
assert_equal 1, results.length
253+
assert_equal 'AttributeManager', results[0]['title']
254+
end
255+
256+
def test_highlighting
257+
results = run_search(
258+
query: 'string',
259+
data: {
260+
searchIndex: ['string'],
261+
longSearchIndex: ['string'],
262+
info: [['String', '', 'String.html', '', 'String class', '', 'class']]
263+
}
264+
)
265+
266+
assert_equal 1, results.length
267+
# Check that highlighting markers (unicode \u0001 and \u0002) are present
268+
assert_match(/[\u0001\u0002]/, results[0]['title'])
269+
end
270+
271+
def test_max_results_limit
272+
# Create 150 entries (more than MAX_RESULTS = 100)
273+
search_index = []
274+
long_search_index = []
275+
info = []
276+
277+
150.times do |i|
278+
search_index << "test#{i}"
279+
long_search_index << "test#{i}"
280+
info << ["Test#{i}", '', "Test#{i}.html", '', "Test class #{i}", '', 'class']
281+
end
282+
283+
results = run_search(
284+
query: 'test',
285+
data: {
286+
searchIndex: search_index,
287+
longSearchIndex: long_search_index,
288+
info: info
289+
}
290+
)
291+
292+
# Should return at most 100 results
293+
assert_operator results.length, :<=, 100
294+
end
295+
296+
private
297+
298+
def run_search(query:, data:)
299+
@context.eval("var testResults = [];")
300+
@context.eval(<<~JS)
301+
var data = #{data.to_json};
302+
var searcher = new Searcher(data);
303+
searcher.ready(function(res, isLast) {
304+
testResults = testResults.concat(res);
305+
});
306+
searcher.find(#{query.to_json});
307+
JS
308+
309+
# Give some time for async operations
310+
sleep 0.01
311+
312+
@context.eval('testResults')
313+
end
314+
315+
# Helper to strip highlighting markers from a string
316+
def strip_highlights(str)
317+
str.gsub(/[\u0001\u0002]/, '')
318+
end
319+
end

0 commit comments

Comments
 (0)