From 1ac8d09ce10f51a42d1a6c4d0540ad49db364274 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Tue, 16 Jan 2024 17:14:53 -1000 Subject: [PATCH] fix sidebar --- library/src/containers/Sidebar/Sidebar.tsx | 85 ++++++---------- .../Sidebar/__tests__/SideBar.test.tsx | 97 +++++++++++++++++++ library/src/helpers/__tests__/sidebar.test.ts | 46 +++++++++ library/src/helpers/sidebar.ts | 46 +++++++++ 4 files changed, 218 insertions(+), 56 deletions(-) create mode 100644 library/src/containers/Sidebar/__tests__/SideBar.test.tsx create mode 100644 library/src/helpers/__tests__/sidebar.test.ts create mode 100644 library/src/helpers/sidebar.ts diff --git a/library/src/containers/Sidebar/Sidebar.tsx b/library/src/containers/Sidebar/Sidebar.tsx index 92b3d2df8..793f61d95 100644 --- a/library/src/containers/Sidebar/Sidebar.tsx +++ b/library/src/containers/Sidebar/Sidebar.tsx @@ -6,6 +6,7 @@ import { PUBLISH_LABEL_DEFAULT_TEXT, SUBSCRIBE_LABEL_DEFAULT_TEXT, } from '../../constants'; +import { TagObject, filterObjectsByTags } from '../../helpers/sidebar'; const SidebarContext = React.createContext<{ setShowSidebar: React.Dispatch>; @@ -168,52 +169,11 @@ export const Sidebar: React.FunctionComponent = () => { ); }; -interface TagObject { - name: string; - object: { tags?: () => Array<{ name: () => string }> }; - data: T; -} - -function filterObjectsByTags( - tags: string[], - objects: Array>, -): { tagged: Map; untagged: TagObject[] } { - const taggedObjects = new Set(); - const tagged = new Map(); - - tags.forEach(tag => { - const taggedForTag: TagObject[] = []; - objects.forEach(obj => { - const object = obj.object; - if (typeof object.tags !== 'function') { - return; - } - - const objectTags = (object.tags() || []).map(t => t.name()); - const hasTag = objectTags.includes(tag); - if (hasTag) { - taggedForTag.push(obj); - taggedObjects.add(obj); - } - }); - tagged.set(tag, taggedForTag); - }); - - const untagged: TagObject[] = []; - objects.forEach(obj => { - if (!taggedObjects.has(obj)) { - untagged.push(obj); - } - }); - - return { tagged, untagged }; -} - const ServersList: React.FunctionComponent = () => { const sidebarConfig = useConfig().sidebar; const asyncapi = useSpec(); const servers = asyncapi.servers().all(); - const showServers = sidebarConfig?.showServers || 'byDefault'; + const showServers = sidebarConfig?.showServers ?? 'byDefault'; if (showServers === 'byDefault') { return ( @@ -227,7 +187,12 @@ const ServersList: React.FunctionComponent = () => { let specTagNames: string[]; if (showServers === 'bySpecTags') { - specTagNames = (asyncapi.info().tags() || []).map(tag => tag.name()); + specTagNames = ( + asyncapi + .info() + .tags() + .all() ?? [] + ).map(tag => tag.name()); } else { const serverTagNamesSet = new Set(); servers.forEach(server => { @@ -238,7 +203,7 @@ const ServersList: React.FunctionComponent = () => { const serializedServers: TagObject[] = servers.map(server => ({ name: server.id(), - object: server, + tags: server.tags(), data: {}, })); const { tagged, untagged } = filterObjectsByTags( @@ -273,7 +238,7 @@ const OperationsList: React.FunctionComponent = () => { const sidebarConfig = useConfig().sidebar; const asyncapi = useSpec(); const operations = asyncapi.operations().all(); - const showOperations = sidebarConfig?.showOperations || 'byDefault'; + const showOperations = sidebarConfig?.showOperations ?? 'byDefault'; const processedOperations: Array { if (operation.isSend()) { processedOperations.push({ name: `publish-${operation.id()}`, - object: operation, + tags: operation.tags(), data: { - channelName: channelAddress || '', + channelName: channelAddress ?? '', kind: 'publish', - summary: operation.summary() || '', + summary: operation.summary() ?? '', }, }); } if (operation.isReceive()) { processedOperations.push({ name: `subscribe-${operation.id()}`, - object: operation, + tags: operation.tags(), data: { - channelName: channelAddress || '', + channelName: channelAddress ?? '', kind: 'subscribe', - summary: operation.summary() || '', + summary: operation.summary() ?? '', }, }); } @@ -320,11 +285,19 @@ const OperationsList: React.FunctionComponent = () => { let operationTagNames: string[]; if (showOperations === 'bySpecTags') { - operationTagNames = (asyncapi.info().tags() || []).map(tag => tag.name()); + operationTagNames = ( + asyncapi + .info() + .tags() + .all() ?? [] + ).map(tag => tag.name()); } else { const operationTagNamesSet = new Set(); operations.forEach(operation => { - operation.tags().forEach(t => operationTagNamesSet.add(t.name())); + operation + .tags() + .all() + .forEach(t => operationTagNamesSet.add(t.name())); }); operationTagNames = Array.from(operationTagNamesSet); } @@ -374,9 +347,9 @@ const OperationItem: React.FunctionComponent = ({ const isPublish = kind === 'publish'; let label: string = ''; if (isPublish) { - label = config.publishLabel || PUBLISH_LABEL_DEFAULT_TEXT; + label = config.publishLabel ?? PUBLISH_LABEL_DEFAULT_TEXT; } else { - label = config.subscribeLabel || SUBSCRIBE_LABEL_DEFAULT_TEXT; + label = config.subscribeLabel ?? SUBSCRIBE_LABEL_DEFAULT_TEXT; } return ( @@ -394,7 +367,7 @@ const OperationItem: React.FunctionComponent = ({ > {label} - {summary || channelName} + {summary ?? channelName} ); diff --git a/library/src/containers/Sidebar/__tests__/SideBar.test.tsx b/library/src/containers/Sidebar/__tests__/SideBar.test.tsx new file mode 100644 index 000000000..9c6b8da5a --- /dev/null +++ b/library/src/containers/Sidebar/__tests__/SideBar.test.tsx @@ -0,0 +1,97 @@ +/** + * @jest-environment jsdom + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { Sidebar } from '../Sidebar'; +import { ConfigContext, SpecificationContext } from '../../../contexts'; +import asyncapi from '../../../__tests__/docs/v3/streetlights-kafka.json'; +import { Parser } from '../../../helpers'; +import { AsyncAPIDocumentInterface } from '@asyncapi/parser'; +describe('Sidebar component', () => { + let parsed: AsyncAPIDocumentInterface; + beforeAll(async () => { + const parsedDoc = await Parser.parse(asyncapi, {}); + expect(parsedDoc.error).toBeUndefined(); + expect(parsedDoc.asyncapi).toBeDefined(); + parsed = parsedDoc.asyncapi!; + }); + test('should render sidebar with showOperations: byDefault', async () => { + render( + + + + + , + ); + }); + test('should render sidebar with showOperations: byOperationsTags', async () => { + render( + + + + + , + ); + }); + test('should render sidebar with showOperations: bySpecTags', async () => { + render( + + + + + , + ); + }); + test('should render sidebar with showServers: byDefault', async () => { + render( + + + + + , + ); + }); + test('should render sidebar with showServers: byServersTags', async () => { + render( + + + + + , + ); + }); + test('should render sidebar with showServers: bySpecTags', async () => { + render( + + + + + , + ); + }); + test('should render with showOperations: byDefault, showServers: byDefault', async () => { + render( + + + + + , + ); + }); +}); diff --git a/library/src/helpers/__tests__/sidebar.test.ts b/library/src/helpers/__tests__/sidebar.test.ts new file mode 100644 index 000000000..a108585b2 --- /dev/null +++ b/library/src/helpers/__tests__/sidebar.test.ts @@ -0,0 +1,46 @@ +import { OperationInterface, TagV2, TagsV2 } from '@asyncapi/parser'; +import { TagObject, filterObjectsByTags } from '../sidebar'; + +describe('sidebar', () => { + describe('.filterObjectsByTags', () => { + test('should handle empty objects and find nothing', () => { + const tagsToFind = ['test']; + const objects: TagObject[] = []; + const filteredTags = filterObjectsByTags(tagsToFind, objects); + expect(filteredTags.tagged.size).toEqual(0); + expect(filteredTags.untagged.length).toEqual(0); + }); + test('should handle find one instance', () => { + const tagsToFind = ['test']; + const tagsToSearch = new TagsV2([new TagV2({ name: 'test' })]); + const objects: TagObject[] = [ + { data: {}, name: '', tags: tagsToSearch }, + ]; + const filteredTags = filterObjectsByTags(tagsToFind, objects); + expect(filteredTags.tagged.size).toEqual(1); + expect(filteredTags.untagged.length).toEqual(0); + }); + test('should handle find multiple instances', () => { + const tagsToFind = ['test']; + const obj1 = { + data: {}, + name: '', + tags: new TagsV2([new TagV2({ name: 'test' })]), + }; + const obj2 = { + data: {}, + name: '', + tags: new TagsV2([new TagV2({ name: 'none' })]), + }; + const obj3 = { + data: {}, + name: '', + tags: new TagsV2([new TagV2({ name: 'test' })]), + }; + const objects: TagObject[] = [obj1, obj2, obj3]; + const filteredTags = filterObjectsByTags(tagsToFind, objects); + expect(filteredTags.tagged.size).toEqual(2); + expect(filteredTags.untagged.length).toEqual(1); + }); + }); +}); diff --git a/library/src/helpers/sidebar.ts b/library/src/helpers/sidebar.ts new file mode 100644 index 000000000..b6d4ef085 --- /dev/null +++ b/library/src/helpers/sidebar.ts @@ -0,0 +1,46 @@ +import { TagsInterface } from '@asyncapi/parser'; + +export interface TagObject { + name: string; + tags: TagsInterface; + data: T; +} +export type SortedReturnType = { + tagged: Map; + untagged: TagObject[]; +}; + +/** + * Filter an array of objects by certain tags + */ +export function filterObjectsByTags( + tags: string[], + objects: TagObject[], +): SortedReturnType { + const taggedObjects = new Set(); + const tagged = new Map(); + tags.forEach(tag => { + const taggedForTag: TagObject[] = []; + objects.forEach(obj => { + const tags = obj.tags; + const nameTags = (tags.all() ?? []).map(t => t.name()); + const hasTag = nameTags.includes(tag); + if (hasTag) { + taggedForTag.push(obj); + taggedObjects.add(obj); + } + }); + if (taggedForTag.length > 0) { + tagged.set(tag, taggedForTag); + } + }); + + const untagged: TagObject[] = []; + objects.forEach(obj => { + if (!taggedObjects.has(obj)) { + untagged.push(obj); + } + }); + + return { tagged, untagged }; +}