Skip to main content
Glama
tag-matching.test.ts7.54 kB
// test/tag-matching.test.ts import { describe, it } from 'node:test'; import assert from 'node:assert'; /** * Test case-insensitive tag matching * * These tests verify that tags are normalized to lowercase and matched * case-insensitively throughout the system. */ describe('Tag Matching', () => { describe('Tag normalization on parse', () => { it('should normalize uppercase tags to lowercase', () => { // Test tag extraction with various cases const testCases = [ { input: '#Dev #Learn', expected: ['dev', 'learn'] }, { input: '#DEV #LEARN', expected: ['dev', 'learn'] }, { input: '#dev #learn', expected: ['dev', 'learn'] }, { input: '#Dev #learn #TEST', expected: ['dev', 'learn', 'test'] }, ]; for (const { input, expected } of testCases) { const tags = input.match(/#\w+/g)?.map(t => t.substring(1).toLowerCase()) || []; assert.deepStrictEqual(tags, expected, `Tags "${input}" should normalize to ${JSON.stringify(expected)}`); } }); it('should handle empty tag strings', () => { const tags = ''.match(/#\w+/g)?.map(t => t.substring(1).toLowerCase()) || []; assert.deepStrictEqual(tags, []); }); it('should handle mixed case tags', () => { const input = '#MixedCase #ALLCAPS #lowercase'; const tags = input.match(/#\w+/g)?.map(t => t.substring(1).toLowerCase()) || []; assert.deepStrictEqual(tags, ['mixedcase', 'allcaps', 'lowercase']); }); it('should handle duplicate tags with different capitalization', () => { const input = '#dev #Dev #DEV #test'; const tags = input.match(/#\w+/g)?.map(t => t.substring(1).toLowerCase()) || []; // Current behavior: duplicates are NOT automatically deduplicated assert.deepStrictEqual(tags, ['dev', 'dev', 'dev', 'test'], 'Tags are normalized but not deduplicated during parsing'); // But can be deduplicated if needed const uniqueTags = [...new Set(tags)]; assert.deepStrictEqual(uniqueTags, ['dev', 'test'], 'Duplicates can be removed with Set'); }); }); describe('Tag mapping lookup', () => { it('should match tag mappings case-insensitively', () => { const tagMappings = { 'dev': 'development', 'sync': 'meeting' }; // Simulate case-insensitive lookup const testCases = [ { tag: 'dev', expected: 'development' }, { tag: 'Dev', expected: 'development' }, { tag: 'DEV', expected: 'development' }, { tag: 'sync', expected: 'meeting' }, { tag: 'SYNC', expected: 'meeting' }, ]; for (const { tag, expected } of testCases) { const normalizedTag = tag.toLowerCase(); let mappedTag = normalizedTag; // Case-insensitive lookup for (const [key, value] of Object.entries(tagMappings)) { if (key.toLowerCase() === normalizedTag) { mappedTag = value; break; } } assert.strictEqual(mappedTag, expected, `Tag "${tag}" should map to "${expected}"`); } }); it('should handle unmapped tags', () => { const tagMappings = { 'dev': 'development' }; const tag = 'unmapped'; const normalizedTag = tag.toLowerCase(); let mappedTag = normalizedTag; for (const [key, value] of Object.entries(tagMappings)) { if (key.toLowerCase() === normalizedTag) { mappedTag = value; break; } } assert.strictEqual(mappedTag, 'unmapped', 'Unmapped tags should return normalized version'); }); it('should handle mixed case in config mapping keys', () => { // Config might have mixed case keys (though lowercase is recommended) const tagMappings = { 'Dev': 'development', 'SYNC': 'meeting' }; const testCases = [ { tag: 'dev', expected: 'development' }, { tag: 'Dev', expected: 'development' }, { tag: 'DEV', expected: 'development' }, { tag: 'sync', expected: 'meeting' }, { tag: 'Sync', expected: 'meeting' }, ]; for (const { tag, expected } of testCases) { const normalizedTag = tag.toLowerCase(); let mappedTag = normalizedTag; // Case-insensitive lookup (matches implementation in summary-calculator.ts) for (const [key, value] of Object.entries(tagMappings)) { if (key.toLowerCase() === normalizedTag) { mappedTag = value.toLowerCase(); break; } } assert.strictEqual(mappedTag, expected, `Tag "${tag}" should map to "${expected}" even with mixed case config keys`); } }); }); describe('Commitment matching', () => { it('should match commitments case-insensitively', () => { const commitments = { 'development': { limit: 20 }, 'meeting': { limit: 5 }, 'total': { limit: 25 } }; const testCases = [ 'development', 'Development', 'DEVELOPMENT', 'meeting', 'Meeting', 'MEETING' ]; for (const tag of testCases) { const normalizedTag = tag.toLowerCase(); let found = false; for (const commitmentKey of Object.keys(commitments)) { if (commitmentKey.toLowerCase() === normalizedTag) { found = true; break; } } assert.strictEqual(found, true, `Tag "${tag}" should match a commitment`); } }); }); describe('Project tag matching', () => { it('should match project tags case-insensitively', () => { const projectTags = ['frontend', 'backend', 'testing']; const normalizedProjectTags = projectTags.map(t => t.toLowerCase()); const testCases = [ { tag: 'frontend', expected: true }, { tag: 'Frontend', expected: true }, { tag: 'FRONTEND', expected: true }, { tag: 'backend', expected: true }, { tag: 'Backend', expected: true }, { tag: 'unknown', expected: false }, ]; for (const { tag, expected } of testCases) { const normalizedTag = tag.toLowerCase(); const found = normalizedProjectTags.includes(normalizedTag); assert.strictEqual(found, expected, `Tag "${tag}" should ${expected ? 'match' : 'not match'} project tags`); } }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/markwharton/time-tracking-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server