@@ -207,6 +207,138 @@ def test_semantic_search_exists_drop_warning():
207207 assert out == ["Item:OSW1" ]
208208
209209
210+ @pytest .mark .parametrize (
211+ "query, expected" ,
212+ [
213+ ("[[HasType::Category:Item]]" , None ),
214+ ("[[HasType::Category:Item]]|limit=2" , 2 ),
215+ ("[[HasType::Category:Item]]|?Name|limit=2|offset=5" , 2 ),
216+ ("[[HasType::Category:Item]]| limit = 2 " , 2 ),
217+ ("[[HasType::Category:Item]]|Limit=2" , 2 ),
218+ # SMW honours the last one, so that is the effective limit
219+ ("[[HasType::Category:Item]]|limit=2|limit=7" , 7 ),
220+ # a limit inside a condition is a value, not a parameter
221+ ("[[HasText::limit=2]]" , None ),
222+ ("[[HasText::a||limit=2]]" , None ),
223+ # a printout named 'limit', not the parameter
224+ ("[[HasType::Category:Item]]|?limit" , None ),
225+ # a printout parameter, which limits that printout and not the query
226+ ("[[HasType::Category:Item]]|?Has subobject|+limit=3" , None ),
227+ # not a number, so there is no limit to honour
228+ ("[[HasType::Category:Item]]|limit=all" , None ),
229+ ],
230+ )
231+ def test_get_query_limit (query , expected ):
232+ assert wt .get_query_limit (query ) == expected
233+
234+
235+ def test_semantic_search_appends_the_default_limit ():
236+ site = MagicMock ()
237+ site .api .return_value = _ask_result ("Item:OSW1" )
238+
239+ wt .semantic_search (site , "[[HasType::Category:Item]]" )
240+
241+ assert site .api .call_args .kwargs ["query" ] == (
242+ "[[HasType::Category:Item]]|limit=1000"
243+ )
244+
245+
246+ def test_semantic_search_keeps_a_limit_the_caller_wrote ():
247+ """Appending the default would override it, since SMW honours the last one."""
248+ site = MagicMock ()
249+ site .api .return_value = _ask_result ("Item:OSW1" )
250+
251+ wt .semantic_search (site , "[[HasType::Category:Item]]|limit=2" )
252+
253+ assert site .api .call_args .kwargs ["query" ] == "[[HasType::Category:Item]]|limit=2"
254+
255+
256+ def test_semantic_search_query_limit_beats_the_search_param_limit ():
257+ site = MagicMock ()
258+ site .api .return_value = _ask_result ("Item:OSW1" )
259+
260+ wt .semantic_search (
261+ site , wt .SearchParam (query = "[[HasType::Category:Item]]|limit=2" , limit = 500 )
262+ )
263+
264+ assert site .api .call_args .kwargs ["query" ] == "[[HasType::Category:Item]]|limit=2"
265+
266+
267+ def test_semantic_search_truncation_warning_uses_the_query_limit ():
268+ """The caller's limit is the one the results were truncated at."""
269+ titles = [f"Item:OSW{ i } " for i in range (2 )]
270+ site = MagicMock ()
271+ site .api .return_value = _ask_result (* titles )
272+
273+ with pytest .warns (UserWarning , match = "requested limit of 2" ):
274+ out = wt .semantic_search (site , "[[HasType::Category:Item]]|limit=2" )
275+
276+ assert sorted (out ) == sorted (titles )
277+
278+
279+ def test_semantic_search_limit_none_sends_no_limit ():
280+ """None asks for no limit at all, leaving the wiki to apply its own."""
281+ site = MagicMock ()
282+ site .api .return_value = _ask_result ("Item:OSW1" )
283+
284+ wt .semantic_search (
285+ site , wt .SearchParam (query = "[[HasType::Category:Item]]" , limit = None )
286+ )
287+
288+ assert site .api .call_args .kwargs ["query" ] == "[[HasType::Category:Item]]"
289+
290+
291+ def test_semantic_search_limit_none_keeps_a_limit_the_caller_wrote ():
292+ site = MagicMock ()
293+ site .api .return_value = _ask_result ("Item:OSW1" )
294+
295+ wt .semantic_search (
296+ site , wt .SearchParam (query = "[[HasType::Category:Item]]|limit=2" , limit = None )
297+ )
298+
299+ assert site .api .call_args .kwargs ["query" ] == "[[HasType::Category:Item]]|limit=2"
300+
301+
302+ def test_semantic_search_limit_none_does_not_warn_about_truncation ():
303+ """With no limit in force the result count says nothing about truncation."""
304+ titles = [f"Item:OSW{ i } " for i in range (5 )]
305+ site = MagicMock ()
306+ site .api .return_value = _ask_result (* titles )
307+
308+ with warnings .catch_warnings (record = True ) as caught :
309+ warnings .simplefilter ("always" )
310+ out = wt .semantic_search (
311+ site , wt .SearchParam (query = "[[HasType::Category:Item]]" , limit = None )
312+ )
313+
314+ assert not any ("truncated" in str (w .message ) for w in caught )
315+ assert sorted (out ) == sorted (titles )
316+
317+
318+ def test_semantic_search_no_truncation_warning_for_a_zero_limit ():
319+ """'limit=0' asks for no results, so meeting it is not truncation."""
320+ site = MagicMock ()
321+ site .api .return_value = _ask_result_empty ()
322+
323+ with warnings .catch_warnings (record = True ) as caught :
324+ warnings .simplefilter ("always" )
325+ wt .semantic_search (site , "[[HasType::Category:Item]]|limit=0" )
326+
327+ assert not any ("truncated" in str (w .message ) for w in caught )
328+
329+
330+ def test_semantic_search_no_truncation_warning_below_the_query_limit ():
331+ titles = [f"Item:OSW{ i } " for i in range (2 )]
332+ site = MagicMock ()
333+ site .api .return_value = _ask_result (* titles )
334+
335+ with warnings .catch_warnings (record = True ) as caught :
336+ warnings .simplefilter ("always" )
337+ wt .semantic_search (site , "[[HasType::Category:Item]]|limit=50" )
338+
339+ assert not any ("truncated" in str (w .message ) for w in caught )
340+
341+
210342def _prefixsearch_result (* titles ):
211343 """Build a minimal MediaWiki ``prefixsearch`` API result dict."""
212344 return {
0 commit comments