@@ -6,10 +6,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
66
77import toolsListUntyped from "@/components/ToolsView/testData/toolsList.json" ;
88import toolsListInPanelUntyped from "@/components/ToolsView/testData/toolsListInPanel.json" ;
9- import { setMockConfig } from "@/composables/__mocks__/config" ;
9+ import { resetMockConfig , setMockConfig } from "@/composables/__mocks__/config" ;
1010import { type Tool , type ToolSection , useToolStore } from "@/stores/toolStore" ;
1111import { useUserStore } from "@/stores/userStore" ;
1212
13+ import ToolRequestForm from "../Tool/ToolRequestForm.vue" ;
1314import ToolBox from "./ToolBox.vue" ;
1415
1516vi . mock ( "@/composables/config" ) ;
@@ -328,3 +329,147 @@ describe("ToolBox search", () => {
328329 expect ( labels ) . toEqual ( EXPECTED_LABELS ) ;
329330 } ) ;
330331} ) ;
332+
333+ /** Helper to mount a default (non-favorites) ToolBox with pre-loaded tool data.
334+ * Pass `anonymous=true` to simulate an unauthenticated (anonymous) user.
335+ * An anonymous user is any non-null object without an `email` property.
336+ */
337+ async function mountDefaultToolBox ( pinia : ReturnType < typeof createPinia > , anonymous = false ) {
338+ setActivePinia ( pinia ) ;
339+
340+ const toolStore = useToolStore ( ) ;
341+ toolStore . toolsById = toToolsById ( toolsList ) ;
342+ toolStore . toolSections = { default : toolsListInPanel } ;
343+ toolStore . defaultPanelView = "default" ;
344+ toolStore . currentPanelView = "default" ;
345+
346+ const userStore = useUserStore ( ) ;
347+ // isAnonymous is a computed value driven by currentUser.
348+ // null → isAnonymous=false (registered/logged-in); object without email → isAnonymous=true
349+ userStore . currentUser = anonymous ? ( { id : "anon" } as any ) : null ;
350+ userStore . currentPreferences = { favorites : { tools : [ ] } } ;
351+
352+ const wrapper = mount ( ToolBox as object , {
353+ pinia,
354+ localVue,
355+ router,
356+ propsData : { useSearchWorker : false } ,
357+ } ) ;
358+ await flushPromises ( ) ;
359+ return wrapper ;
360+ }
361+
362+ describe ( "ToolBox — Request a Tool button" , ( ) => {
363+ beforeEach ( ( ) => {
364+ vi . useFakeTimers ( ) ;
365+ setMockConfig ( { toolbox_auto_sort : true , enable_tool_request_form : true } ) ;
366+ } ) ;
367+
368+ afterEach ( ( ) => {
369+ vi . useRealTimers ( ) ;
370+ resetMockConfig ( ) ;
371+ setMockConfig ( { toolbox_auto_sort : true } ) ;
372+ } ) ;
373+
374+ it ( "is hidden when search returns results" , async ( ) => {
375+ const pinia = createPinia ( ) ;
376+ const wrapper = await mountDefaultToolBox ( pinia ) ;
377+
378+ const input = wrapper . find ( "input.search-query" ) ;
379+ await input . setValue ( "Filter" ) ;
380+ vi . advanceTimersByTime ( 250 ) ;
381+ await flushPromises ( ) ;
382+
383+ expect ( wrapper . find ( '[data-description="request tool button"]' ) . exists ( ) ) . toBe ( false ) ;
384+ } ) ;
385+
386+ it ( "is visible when search returns no results" , async ( ) => {
387+ const pinia = createPinia ( ) ;
388+ const wrapper = await mountDefaultToolBox ( pinia ) ;
389+
390+ const input = wrapper . find ( "input.search-query" ) ;
391+ await input . setValue ( "xyznonexistenttool123" ) ;
392+ vi . advanceTimersByTime ( 250 ) ;
393+ await flushPromises ( ) ;
394+
395+ expect ( wrapper . find ( ".alert-warning" ) . exists ( ) ) . toBe ( true ) ;
396+ expect ( wrapper . find ( '[data-description="request tool button"]' ) . exists ( ) ) . toBe ( true ) ;
397+ } ) ;
398+
399+ it ( "is hidden when enable_tool_request_form config is false" , async ( ) => {
400+ setMockConfig ( { toolbox_auto_sort : true , enable_tool_request_form : false } ) ;
401+ const pinia = createPinia ( ) ;
402+ const wrapper = await mountDefaultToolBox ( pinia ) ;
403+
404+ const input = wrapper . find ( "input.search-query" ) ;
405+ await input . setValue ( "xyznonexistenttool123" ) ;
406+ vi . advanceTimersByTime ( 250 ) ;
407+ await flushPromises ( ) ;
408+
409+ expect ( wrapper . find ( ".alert-warning" ) . exists ( ) ) . toBe ( true ) ;
410+ expect ( wrapper . find ( '[data-description="request tool button"]' ) . exists ( ) ) . toBe ( false ) ;
411+ } ) ;
412+
413+ it ( "is hidden for anonymous users even when search returns no results" , async ( ) => {
414+ const pinia = createPinia ( ) ;
415+ const wrapper = await mountDefaultToolBox ( pinia , /* anonymous= */ true ) ;
416+
417+ const input = wrapper . find ( "input.search-query" ) ;
418+ await input . setValue ( "xyznonexistenttool123" ) ;
419+ vi . advanceTimersByTime ( 250 ) ;
420+ await flushPromises ( ) ;
421+
422+ expect ( wrapper . find ( ".alert-warning" ) . exists ( ) ) . toBe ( true ) ;
423+ expect ( wrapper . find ( '[data-description="request tool button"]' ) . exists ( ) ) . toBe ( false ) ;
424+ } ) ;
425+
426+ it ( "is hidden in workflow mode even when search returns no results" , async ( ) => {
427+ const pinia = createPinia ( ) ;
428+ setActivePinia ( pinia ) ;
429+
430+ const toolStore = useToolStore ( ) ;
431+ toolStore . toolsById = toToolsById ( toolsList ) ;
432+ toolStore . toolSections = { default : toolsListInPanel } ;
433+ toolStore . defaultPanelView = "default" ;
434+ toolStore . currentPanelView = "default" ;
435+
436+ const userStore = useUserStore ( ) ;
437+ userStore . currentUser = null ;
438+ userStore . currentPreferences = { favorites : { tools : [ ] } } ;
439+
440+ const wrapper = mount ( ToolBox as object , {
441+ pinia,
442+ localVue,
443+ router,
444+ propsData : { workflow : true , useSearchWorker : false } ,
445+ } ) ;
446+ await flushPromises ( ) ;
447+
448+ const input = wrapper . find ( "input.search-query" ) ;
449+ await input . setValue ( "xyznonexistenttool123" ) ;
450+ vi . advanceTimersByTime ( 250 ) ;
451+ await flushPromises ( ) ;
452+
453+ expect ( wrapper . find ( ".alert-warning" ) . exists ( ) ) . toBe ( true ) ;
454+ expect ( wrapper . find ( '[data-description="request tool button"]' ) . exists ( ) ) . toBe ( false ) ;
455+ } ) ;
456+
457+ it ( "opens the tool request form modal when clicked" , async ( ) => {
458+ const pinia = createPinia ( ) ;
459+ const wrapper = await mountDefaultToolBox ( pinia ) ;
460+
461+ const input = wrapper . find ( "input.search-query" ) ;
462+ await input . setValue ( "xyznonexistenttool123" ) ;
463+ vi . advanceTimersByTime ( 250 ) ;
464+ await flushPromises ( ) ;
465+
466+ const button = wrapper . find ( '[data-description="request tool button"]' ) ;
467+ expect ( button . exists ( ) ) . toBe ( true ) ;
468+
469+ await button . trigger ( "click" ) ;
470+ await flushPromises ( ) ;
471+
472+ const requestForm = wrapper . findComponent ( ToolRequestForm ) ;
473+ expect ( requestForm . props ( "show" ) ) . toBe ( true ) ;
474+ } ) ;
475+ } ) ;
0 commit comments