Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ exports[`getPatternFlyMcpResources should return multiple organized facets: prop
"keywordsIndex",
"keywordsMap",
"pathIndex",
"uriIndex",
"hashIndex",
"byPath",
"byUri",
"byVersion",
Expand Down
34 changes: 0 additions & 34 deletions src/__tests__/__snapshots__/patternFly.search.test.ts.snap

This file was deleted.

23 changes: 23 additions & 0 deletions src/__tests__/patternFly.getResources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,27 @@ describe('getPatternFlyMcpResources', () => {
it('should have a memoized property', async () => {
expect(getPatternFlyMcpResources).toHaveProperty('memo');
});

it('should have lowercased index keys', async () => {
const result = await getPatternFlyMcpResources();

expect(Array.from(result.pathIndex.keys()).some(key => /[A-Z]/.test(key))).toBe(false);
expect(Array.from(result.uriIndex.keys()).some(key => /[A-Z]/.test(key))).toBe(false);
expect(Array.from(result.hashIndex.keys()).some(key => /[A-Z]/.test(key))).toBe(false);
});

it('should generate unique hash IDs for pathless component entries', async () => {
const result = await getPatternFlyMcpResources();
const entries = Array.from(result.resources.values()).flatMap(resource => resource.entries);
const ids = entries.map(entry => entry.id);
const pathlessEntries = entries.filter(entry => !entry.path);

// Confirm that IDs generally exist.
expect(ids.length).toBeGreaterThan(0);

const pathlessEntryIds = new Set(pathlessEntries.map(entry => entry.id));

// Confirm that all pathless entries have unique IDs.
expect(pathlessEntryIds.size).toBe(pathlessEntries.length);
});
});
218 changes: 157 additions & 61 deletions src/__tests__/patternFly.search.test.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,83 @@
import { filterPatternFly, searchPatternFly } from '../patternFly.search';

describe('filterPatternFly', () => {
const mockResources = new Map([
['button', {
name: 'button',
groupId: 'button-group-id',
entries: [
{ id: 'btn-v6-react', name: 'button', version: 'v6', section: 'components', category: 'action', groupId: 'button-group-id' },
{ id: 'btn-v5-react', name: 'button', version: 'v5', section: 'components', category: 'action', groupId: 'button-group-id' }
],
versions: {
v6: {
isSchemasAvailable: true,
uri: 'patternfly://docs/button?version=v6',
uriSchemas: 'patternfly://schemas/button?version=v6',
uriSchemasId: 'button-group-id'
},
v5: {
isSchemasAvailable: false,
uri: 'patternfly://docs/button?version=v5'
}
}
}],
['modal', {
name: 'modal',
entries: [
{ name: 'modal', section: 'components', category: 'view', version: 'v6' }
]
}]
]);

it.each([
{
description: 'all filter',
filters: undefined
description: 'all entries, undefined',
filters: undefined,
expectedNames: ['button', 'button', 'modal']
},
{
description: 'all entries, empty object',
filters: {},
expectedNames: ['button', 'button', 'modal']
},
{
description: 'by version',
filters: { version: 'v5' },
expectedNames: ['button']
},
{
description: 'name, button',
filters: { name: 'button' },
expectedNames: ['button', 'button']
},
{
description: 'all filter empty object',
filters: {}
description: 'name, modal',
filters: { name: 'modal' },
expectedNames: ['modal']
},
{
description: 'all filter empty object',
filters: { version: 'v5' }
description: 'name, hash',
filters: { name: 'btn-v6-react' },
expectedNames: ['button']
},
{
description: 'section, components',
filters: { section: 'components' }
filters: { section: 'components' },
expectedNames: ['button', 'button', 'modal']
},
{
description: 'category, accessibility',
filters: { category: 'accessibility' }
description: 'category, action',
filters: { category: 'action' },
expectedNames: ['button', 'button']
}
])('should attempt to return filtered results, $description', async ({ filters }) => {
const result = await filterPatternFly(filters as any);
])('should return filtered results, $description', async ({ filters, expectedNames }) => {
const result = await filterPatternFly(filters as any, mockResources as any);

expect(result.byEntry.length).toBeGreaterThanOrEqual(0);
expect(Array.from(result.byResource).length).toBeGreaterThanOrEqual(0);
expect(result.byEntry.map(result => result.name)).toEqual(expectedNames);
});

it('should attempt to filter number results', async () => {
it('should filter number results', async () => {
const result = await filterPatternFly(
{ section: 1 } as any,
new Map([['loremIpsum', { entries: [{ section: 1 }, { section: 'dolor' }] }]]) as any
Expand All @@ -41,77 +89,125 @@ describe('filterPatternFly', () => {
});

describe('searchPatternFly', () => {
const mockMcpResources = {
resources: new Map([
['button', {
name: 'button',
groupId: 'btn-group',
entries: [
{ id: 'btn-v6-hash', name: 'button', version: 'v6', section: 'components', category: 'action', groupId: 'btn-group' },
{ id: 'btn-v5-hash', name: 'button', version: 'v5', section: 'components', category: 'action', groupId: 'btn-group' }
],
versions: {
v6: { uri: 'patternfly://docs/button?version=v6', isSchemasAvailable: true },
v5: { uri: 'patternfly://docs/button?version=v5', isSchemasAvailable: false }
}
}],
['modal', {
name: 'modal',
groupId: 'mdl-group',
entries: [{ id: 'mdl-v6-hash', name: 'modal', version: 'v6', section: 'components', category: 'view', groupId: 'mdl-group' }],
versions: { v6: { uri: 'patternfly://docs/modal?version=v6', isSchemasAvailable: true } }
}]
]),
keywordsIndex: [
'button',
'modal',
'btn-v6-hash',
'mdl-v6-hash',
'patternfly://docs/button',
'patternfly://docs/modal'
],
keywordsMap: new Map([
['button', new Map([['v6', ['button']], ['v5', ['button']]])],
['modal', new Map([['v6', ['modal']]])],
['btn-v6-hash', new Map([['v6', ['button']]])],
['mdl-v6-hash', new Map([['v6', ['modal']]])],
['patternfly://docs/button', new Map([['v6', ['button']], ['v5', ['button']]])],
['patternfly://docs/modal', new Map([['v6', ['modal']]])]
]),
latestVersion: 'v6'
};

const mockOptions = { mcpResources: Promise.resolve(mockMcpResources) as any };

it.each([
{
description: 'wildcard search',
search: '*'
description: 'exact match',
search: 'button',
expectedLength: 1,
expectedName: 'button',
expectedType: 'exact'
},
{
description: 'all search',
search: 'all'
description: 'partial prefix',
search: 'but',
expectedLength: 1,
expectedName: 'button',
expectedType: 'prefix'
},
{
description: 'empty all search',
search: ''
}
])('should attempt to return an array of all available results, $description', async ({ search }) => {
const { searchResults, ...rest } = await searchPatternFly(search, undefined, { allowWildCardAll: true });

expect(searchResults.length).toBeGreaterThan(0);
expect(Object.keys(rest)).toMatchSnapshot('keys');
});

it.each([
description: 'partial suffix',
search: 'ton',
expectedLength: 1,
expectedName: 'button',
expectedType: 'suffix'
},
{
description: 'exact match',
search: 'react',
matchType: 'exact'
description: 'partial contains',
search: 'utto',
expectedLength: 1,
expectedName: 'button',
expectedType: 'contains'
},
{
description: 'partial prefix match',
search: 're',
matchType: 'prefix'
description: 'patternfly:// URI',
search: 'patternfly://docs/modal',
expectedLength: 1,
expectedName: 'modal',
expectedType: 'exact'
},
{
description: 'partial suffix match',
search: 'act',
matchType: 'suffix'
description: 'hash entry id without filter',
search: 'btn-v6-hash',
options: {},
expectedLength: 2,
expectedName: 'button',
expectedType: 'exact'
},
{
description: 'partial contains match',
search: 'eac',
matchType: 'contains'
description: 'version filter',
search: 'button',
filters: { version: 'v5' },
expectedLength: 1,
expectedName: 'button',
expectedType: 'exact'
}
])('should attempt to match components and keywords, $description', async ({ search, matchType }) => {
const { searchResults } = await searchPatternFly(search);
])('should return search results, $description', async ({ search, filters, options, expectedLength, expectedName, expectedType }) => {
const { searchResults } = await searchPatternFly(search, { ...filters }, { ...options, ...mockOptions });

expect(searchResults.find(({ matchType: returnMatchType }) => returnMatchType === matchType)).toEqual(expect.objectContaining({
query: expect.stringMatching(search)
}));
expect(searchResults?.length).toBe(expectedLength);
expect(searchResults?.[0]?.matchType).toBe(expectedType);
expect(searchResults?.[0]?.name).toBe(expectedName);
});

it.each([
{
description: 'version',
search: 'about modal',
filters: { version: 'v5' }
description: 'wildcard search',
search: '*'
},
{
description: 'section',
search: 'popover',
filters: { section: 'components' }
description: 'all search',
search: 'all'
},
{
description: 'category',
search: '*',
filters: { category: 'grammar' },
options: { allowWildCardAll: true }
description: 'empty all search',
search: ''
}
])('should allow filtering, $description', async ({ search, filters, options }) => {
const { searchResults, totalResults, totalPotentialMatches } = await searchPatternFly(search, filters, options || {});
])('should return an array of all available results, $description', async ({ search }) => {
const { searchResults } = await searchPatternFly(search, undefined, { allowWildCardAll: true, ...mockOptions });

expect(searchResults.length).toBeGreaterThanOrEqual(0);
expect(totalResults).toBeGreaterThanOrEqual(searchResults.length);
expect(totalPotentialMatches).toBeGreaterThanOrEqual(totalResults);
expect(searchResults?.length).toBe(2);
expect(searchResults?.[0]?.matchType).toBe('all');
});
});
2 changes: 1 addition & 1 deletion src/__tests__/resource.patternFlyComponentsIndex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('resourceCallback', () => {
variables: {
category: 'accessibility'
},
expected: '?category=accessibility'
expected: 'category=accessibility'
}
])('should return context content, $description', async ({ variables, expected }) => {
const result = await resourceCallback(undefined as any, variables);
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/resource.patternFlyDocsIndex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,22 +211,22 @@ describe('resourceCallback', () => {
variables: {
category: 'accessibility'
},
expected: '?category=accessibility'
expected: 'category=accessibility'
},
{
description: 'section',
variables: {
section: 'components'
},
expected: '?section=components'
expected: 'section=components'
},
{
description: 'category and section',
variables: {
category: 'accessibility',
section: 'components'
},
expected: '?category=accessibility&section=components'
expected: 'category=accessibility&section=components'
}
])('should return context content, $description', async ({ variables, expected }) => {
const result = await resourceCallback(undefined as any, variables);
Expand Down
7 changes: 7 additions & 0 deletions src/__tests__/resource.patternFlySchemasTemplate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ describe('resourceCallback', () => {
name: 'button',
version: 'v6'
}
},
{
description: 'with hashed button name',
variables: {
name: 'ffcfb1b9b852a17ccb5b2adc12e3edd4a4ee41cb',
version: 'v6'
}
}
])('should attempt to return resource content, $description', async ({ variables }) => {
const mockContent = '$schema';
Expand Down
Loading
Loading