repo_description: Self-hosted collection of powerful web-based tools for everyday tasks. No ads, no tracking, just fast, accessible utilities right from your browser!
repo_topics: alternative converter data-manipulation developer-tools devtools frontend good-first-issue image-manipulation image-processing javascript pdf-manipulation productivity react self-hosted swissarmyknife tools typescript video-manipulation webapp website
code: import { describe, it, expect } from 'vitest';
  import type { TFunction } from 'i18next';
  import { filterTools } from './index';
  import type { DefinedTool } from './defineTool';
  import type { FullI18nKey, I18nNamespaces } from '../i18n';
  
  const mergePdfTool = {
    type: 'pdf',
    path: 'pdf/merge-pdf',
    name: 'pdf:mergePdf.title' as FullI18nKey,
    description: 'pdf:mergePdf.description' as FullI18nKey,
    shortDescription: 'pdf:mergePdf.shortDescription' as FullI18nKey,
    icon: 'icon',
    keywords: [
      'pdf',
      'merge',
      'extract',
      'pages',
      'combine',
      'document',
      'join',
      'append'
    ],
    component: (() => null) as unknown,
    userTypes: ['generalUsers']
  } as unknown as DefinedTool;
  
  const base64Tool = {
    type: 'string',
    path: 'string/base64',
    name: 'string:base64.title' as FullI18nKey,
    description: 'string:base64.description' as FullI18nKey,
    shortDescription: 'string:base64.shortDescription' as FullI18nKey,
    icon: 'icon',
    keywords: ['b64'],
    component: (() => null) as unknown,
    userTypes: ['generalUsers', 'developers']
  } as unknown as DefinedTool;
  
  const otherPdfTool = {
    type: 'pdf',
    path: 'pdf/other-pdf',
    name: 'pdf:otherPdf.title' as FullI18nKey,
    description: 'pdf:otherPdf.description' as FullI18nKey,
    shortDescription: 'pdf:otherPdf.shortDescription' as FullI18nKey,
    icon: 'icon',
    keywords: [],
    component: (() => null) as unknown,
    userTypes: ['generalUsers']
  } as unknown as DefinedTool;
  type NamespacedT = TFunction<I18nNamespaces[]>;
  
  const enTranslations: Record<string, string> = {
    'pdf:mergePdf.title': 'Merge PDF',
    'pdf:mergePdf.description': 'Merge multiple PDF files into one document',
    'pdf:mergePdf.shortDescription': 'Merge PDF files',
    'pdf:otherPdf.title': 'Document helper',
    'pdf:otherPdf.description': 'Helper for pdf tasks',
    'pdf:otherPdf.shortDescription': 'PDF helper',
    'string:base64.title': 'Base64 Encoder/Decoder',
    'string:base64.description': 'Encode or decode Base64 text',
    'string:base64.shortDescription': 'Convert text to and from Base64'
  };
  
  const esTranslations: Record<string, string> = {
    'pdf:mergePdf.title': 'Unir PDF',
    'pdf:mergePdf.description': 'Unir varios archivos PDF en un solo documento',
    'pdf:mergePdf.shortDescription': 'Unir archivos PDF'
  };
  
  const makeT = (dict: Record<string, string>): NamespacedT =>
    ((key: string) => dict[key] ?? key) as unknown as NamespacedT;
  
  describe('filterTools token-based search', () => {
    const tools = [mergePdfTool, base64Tool, otherPdfTool];
    const tEn = makeT(enTranslations);
    const tEs = makeT(esTranslations);
  
    it('returns all tools when query is empty or whitespace', () => {
      expect(filterTools(tools, '', [], tEn)).toEqual(tools);
      expect(filterTools(tools, '   ', [], tEn)).toEqual(tools);
    });
  
    it('matches tools regardless of word order and extra whitespace', () => {
      const result1 = filterTools(tools, 'pdf merge', [], tEn);
      const result2 = filterTools(tools, '   merge   pdf  ', [], tEn);
  
      expect(result1).toContain(mergePdfTool);
      expect(result2).toContain(mergePdfTool);
    });
  
    it('matches base64 tool with different queries and is case-insensitive', () => {
      expect(filterTools(tools, 'Base64', [], tEn)).toContain(base64Tool);
      expect(filterTools(tools, 'base64 encoder', [], tEn)).toContain(base64Tool);
    });
  
    it('matches tools using keyword-based English synonyms', () => {
      const result = filterTools(tools, 'pdf join', [], tEn);
      expect(result).toContain(mergePdfTool);
    });
  
    it('matches base64 tool with spaced and short-form variants', () => {
      const resultBase64Spaced = filterTools(tools, 'base 64', [], tEn);
      const resultB64 = filterTools(tools, 'b64', [], tEn);
  
      expect(resultBase64Spaced).toContain(base64Tool);
      expect(resultB64).toContain(base64Tool);
    });
  
    it('ignores trailing spaces', () => {
      const result = filterTools(tools, 'pdf merge   ', [], tEn);
      expect(result).toContain(mergePdfTool);
    });
  
    it('works with non-English localized strings', () => {
      const result = filterTools(tools, 'unir pdf', [], tEs);
      expect(result).toContain(mergePdfTool);
    });
  
    it('tolerates single-character typos in query tokens', () => {
      const result = filterTools(tools, 'merhe pdf', [], tEn);
      expect(result[0]).toBe(mergePdfTool);
    });
  
    it('ranks tools with title matches above description-only matches', () => {
      const result = filterTools(tools, 'pdf', [], tEn);
      expect(result[0]).toBe(mergePdfTool);
    });
  });
  