@eventcatalog/core 4.11.0 → 4.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/astro-output.cjs +60 -1
- package/dist/astro-output.js +1 -1
- package/dist/{chunk-U52FCZQ6.js → chunk-2YJRF4ZX.js} +1 -1
- package/dist/chunk-634NFV5U.js +75 -0
- package/dist/{chunk-SW7SJZIX.js → chunk-AXDNSGAN.js} +1 -1
- package/dist/chunk-DHH5U5PF.js +53 -0
- package/dist/{chunk-H3324DNT.js → chunk-DSCIWCX2.js} +1 -1
- package/dist/chunk-FWXEV4LF.js +19 -0
- package/dist/{chunk-XJOKK5ZO.js → chunk-GZVDXCYF.js} +1 -1
- package/dist/{chunk-W4LDIMUI.js → chunk-RYNPEIHQ.js} +1 -1
- package/dist/{chunk-DWC5GRFX.js → chunk-YYVW4DCO.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/copy-core.cjs +88 -0
- package/dist/copy-core.d.cts +4 -0
- package/dist/copy-core.d.ts +4 -0
- package/dist/copy-core.js +8 -0
- package/dist/eventcatalog.cjs +234 -115
- package/dist/eventcatalog.js +36 -30
- package/dist/federation/federate.js +3 -3
- package/dist/federation/source-provider.js +2 -2
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/update-check.cjs +44 -0
- package/dist/update-check.d.cts +18 -0
- package/dist/update-check.d.ts +18 -0
- package/dist/update-check.js +8 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/astro.config.mjs +6 -0
- package/eventcatalog/src/plugins/empty-collection-warning.d.ts +21 -0
- package/eventcatalog/src/plugins/empty-collection-warning.mjs +107 -0
- package/eventcatalog/src/plugins/quiet-empty-collections-logger.mjs +11 -0
- package/eventcatalog/src/utils/collections/channels.ts +12 -4
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +17 -15
- package/package.json +2 -1
- package/dist/chunk-RDYYZQYU.js +0 -18
- package/eventcatalog/src/components/EnvironmentDropdown.test.ts +0 -64
- package/eventcatalog/src/components/Grids/message-link.spec.ts +0 -59
- package/eventcatalog/src/components/MDX/CodeGroup/code-group.spec.tsx +0 -89
- package/eventcatalog/src/components/MDX/Columns/columns.spec.tsx +0 -66
- package/eventcatalog/src/components/MDX/CustomProperties/custom-properties.spec.ts +0 -38
- package/eventcatalog/src/components/MDX/SchemaViewer/schema-viewer-utils.spec.ts +0 -116
- package/eventcatalog/src/components/SchemaExplorer/JSONSchemaViewer.test.tsx +0 -114
- package/eventcatalog/src/components/SchemaExplorer/ProtobufSchemaViewer.test.tsx +0 -91
- package/eventcatalog/src/components/SchemaExplorer/SchemaViewerHover.test.tsx +0 -33
- package/eventcatalog/src/components/SchemaExplorer/protobuf-validation.test.ts +0 -84
- package/eventcatalog/src/components/Search/search-utils.spec.ts +0 -173
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +0 -105
- package/eventcatalog/src/components/navigation-progress.spec.ts +0 -68
- package/eventcatalog/src/enterprise/directory/user-team-directory.spec.ts +0 -527
- package/eventcatalog/src/enterprise/fields/field-extractor.test.ts +0 -302
- package/eventcatalog/src/enterprise/fields/fields-db.test.ts +0 -185
- package/eventcatalog/src/stores/eventcatalog-store.spec.ts +0 -60
- package/eventcatalog/src/utils/collections/glob-loader.spec.ts +0 -87
- package/dist/{chunk-6K7XZAYI.js → chunk-A2RZR3U4.js} +3 -3
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getMessageLinkProps } from './message-link';
|
|
3
|
-
|
|
4
|
-
describe('getMessageLinkProps', () => {
|
|
5
|
-
it('builds a command docs url and prefers the resource name over the id', () => {
|
|
6
|
-
expect(
|
|
7
|
-
getMessageLinkProps({
|
|
8
|
-
collection: 'commands',
|
|
9
|
-
data: { id: 'order-create', name: 'Create Order', version: '0.0.1' },
|
|
10
|
-
})
|
|
11
|
-
).toEqual({
|
|
12
|
-
collection: 'commands',
|
|
13
|
-
id: 'order-create',
|
|
14
|
-
name: 'Create Order',
|
|
15
|
-
version: '0.0.1',
|
|
16
|
-
href: '/docs/commands/order-create/0.0.1',
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('builds a query docs url from a hydrated collection entry', () => {
|
|
21
|
-
expect(
|
|
22
|
-
getMessageLinkProps({
|
|
23
|
-
collection: 'queries',
|
|
24
|
-
data: { id: 'get-stock-level', name: 'Get Stock Level', version: '1.0.0' },
|
|
25
|
-
})
|
|
26
|
-
).toEqual({
|
|
27
|
-
collection: 'queries',
|
|
28
|
-
id: 'get-stock-level',
|
|
29
|
-
name: 'Get Stock Level',
|
|
30
|
-
version: '1.0.0',
|
|
31
|
-
href: '/docs/queries/get-stock-level/1.0.0',
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('does not default a missing collection to events', () => {
|
|
36
|
-
expect(getMessageLinkProps({ id: 'order-create', version: '0.0.1' })).toEqual({
|
|
37
|
-
collection: undefined,
|
|
38
|
-
id: 'order-create',
|
|
39
|
-
name: 'order-create',
|
|
40
|
-
version: '0.0.1',
|
|
41
|
-
href: undefined,
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('ignores unknown collection values instead of linking to events', () => {
|
|
46
|
-
expect(
|
|
47
|
-
getMessageLinkProps({
|
|
48
|
-
collection: 'services',
|
|
49
|
-
data: { id: 'order-service', name: 'Order Service', version: '1.0.0' },
|
|
50
|
-
})
|
|
51
|
-
).toEqual({
|
|
52
|
-
collection: undefined,
|
|
53
|
-
id: 'order-service',
|
|
54
|
-
name: 'Order Service',
|
|
55
|
-
version: '1.0.0',
|
|
56
|
-
href: undefined,
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { act } from 'react';
|
|
4
|
-
import { createRoot, type Root } from 'react-dom/client';
|
|
5
|
-
import CodeGroup from './CodeGroup';
|
|
6
|
-
import { MarkdownCodeGroup } from './MarkdownCodeGroup';
|
|
7
|
-
import { parseCodePanels } from './parse-panels';
|
|
8
|
-
import { getCodeLanguageIconUrl, getCodeLanguageName } from './code-group-icons';
|
|
9
|
-
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
|
10
|
-
const roots: Root[] = [];
|
|
11
|
-
function render(element: React.ReactNode) {
|
|
12
|
-
const container = document.createElement('div');
|
|
13
|
-
document.body.append(container);
|
|
14
|
-
const root = createRoot(container);
|
|
15
|
-
roots.push(root);
|
|
16
|
-
act(() => root.render(element));
|
|
17
|
-
return container;
|
|
18
|
-
}
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
act(() => roots.splice(0).forEach((root) => root.unmount()));
|
|
21
|
-
document.body.innerHTML = '';
|
|
22
|
-
});
|
|
23
|
-
const items = [
|
|
24
|
-
{ label: 'hello.js', language: 'js', content: <pre>first</pre> },
|
|
25
|
-
{ label: 'hello.py', language: 'python', content: <pre>second</pre> },
|
|
26
|
-
];
|
|
27
|
-
describe('React CodeGroup', () => {
|
|
28
|
-
it('switches panels with keyboard focus and synchronizes matching groups', () => {
|
|
29
|
-
const a = render(<CodeGroup items={items} />);
|
|
30
|
-
const b = render(<CodeGroup items={items} dropdown />);
|
|
31
|
-
const tabs = a.querySelectorAll<HTMLButtonElement>('[role=tab]');
|
|
32
|
-
act(() => tabs[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })));
|
|
33
|
-
expect(tabs[1].getAttribute('aria-selected')).toBe('true');
|
|
34
|
-
expect(document.activeElement).toBe(tabs[1]);
|
|
35
|
-
expect(a.querySelectorAll<HTMLElement>('[role=tabpanel]')[0].hidden).toBe(true);
|
|
36
|
-
expect(b.querySelectorAll<HTMLElement>('[role=region]')[1].hidden).toBe(false);
|
|
37
|
-
expect(b.querySelector('button')?.textContent).toBe('Python');
|
|
38
|
-
});
|
|
39
|
-
it('copies the selected code', async () => {
|
|
40
|
-
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
41
|
-
Object.assign(navigator, { clipboard: { writeText } });
|
|
42
|
-
const root = render(<CodeGroup items={items} />);
|
|
43
|
-
act(() => root.querySelectorAll<HTMLButtonElement>('[role=tab]')[1].click());
|
|
44
|
-
await act(async () => root.querySelector<HTMLButtonElement>('[aria-label="Copy code"]')!.click());
|
|
45
|
-
expect(writeText).toHaveBeenCalledWith('second');
|
|
46
|
-
expect(root.querySelector('[aria-label="Copied!"]')).not.toBeNull();
|
|
47
|
-
});
|
|
48
|
-
it('preserves highlighted Astro HTML and decodes copy newlines', async () => {
|
|
49
|
-
const parsed = parseCodePanels(
|
|
50
|
-
'<div data-code-panel="file.js" data-code-lang="js"><pre><code><span>one</span>two</code></pre><button data-code="onetwo"></button></div>'
|
|
51
|
-
);
|
|
52
|
-
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
53
|
-
Object.assign(navigator, { clipboard: { writeText } });
|
|
54
|
-
const root = render(<CodeGroup items={parsed} />);
|
|
55
|
-
expect(root.querySelector('code span')?.textContent).toBe('one');
|
|
56
|
-
await act(async () => root.querySelector<HTMLButtonElement>('[aria-label="Copy code"]')!.click());
|
|
57
|
-
expect(writeText).toHaveBeenCalledWith('one\ntwo');
|
|
58
|
-
});
|
|
59
|
-
it('accepts Markdown panels and escapes labels', () => {
|
|
60
|
-
const root = render(
|
|
61
|
-
<MarkdownCodeGroup>
|
|
62
|
-
<div data-code-panel="<script>" data-code-lang="js">
|
|
63
|
-
<pre>hello</pre>
|
|
64
|
-
</div>
|
|
65
|
-
</MarkdownCodeGroup>
|
|
66
|
-
);
|
|
67
|
-
expect(root.querySelector('[role=tab]')?.textContent).toBe('<script>');
|
|
68
|
-
expect(root.querySelector('script')).toBeNull();
|
|
69
|
-
expect(root.querySelector('pre')?.textContent).toBe('hello');
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('language names and icons', () => {
|
|
74
|
-
it.each([
|
|
75
|
-
['cs', undefined, 'C#', '/icons/languages/csharp.svg'],
|
|
76
|
-
['dotnet', undefined, '.NET', '/icons/languages/dotnet.svg'],
|
|
77
|
-
['fsharp', undefined, 'F#', '/icons/languages/fsharp.svg'],
|
|
78
|
-
['java', undefined, 'Java', '/icons/languages/java.svg'],
|
|
79
|
-
['kts', undefined, 'Kotlin', '/icons/languages/kotlin.svg'],
|
|
80
|
-
[undefined, 'Main.go', 'Go', '/icons/languages/go.svg'],
|
|
81
|
-
['powershell', undefined, 'PowerShell', undefined],
|
|
82
|
-
['xml', undefined, 'XML', undefined],
|
|
83
|
-
['avsc', undefined, 'Avro', '/icons/avro.svg'],
|
|
84
|
-
['brainfuck', undefined, 'Brainfuck', undefined],
|
|
85
|
-
])('maps %s / %s to %s', (language, label, name, icon) => {
|
|
86
|
-
expect(getCodeLanguageName(language, label)).toBe(name);
|
|
87
|
-
expect(getCodeLanguageIconUrl(language, label)).toBe(icon);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
-
import ReactMarkdown from 'react-markdown';
|
|
4
|
-
import { createRequire } from 'node:module';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import * as runtime from 'react/jsx-runtime';
|
|
7
|
-
import { getColumnsLayout } from './layout';
|
|
8
|
-
import { MarkdownColumns, MarkdownColumn } from './MarkdownColumns';
|
|
9
|
-
import { remarkMarkdownColumns } from '../../../remark-plugins/markdown-columns';
|
|
10
|
-
import { remarkMarkdownCodeGroup } from '../../../remark-plugins/markdown-code-group';
|
|
11
|
-
|
|
12
|
-
const source =
|
|
13
|
-
'<Columns cols={2} ratio="2:1">\n <Column>\n\n## Publish an order\n\nSome **explanation**.\n\n </Column>\n <Column sticky>\n\n<CodeGroup>\n\n```json OrderCreated\n{"id":"123"}\n```\n\n```js JavaScript\npublish();\n```\n\n</CodeGroup>\n\n </Column>\n</Columns>';
|
|
14
|
-
|
|
15
|
-
describe('Columns', () => {
|
|
16
|
-
it('defaults to two columns and supports one through four', () => {
|
|
17
|
-
expect(getColumnsLayout().template).toBe('repeat(2, minmax(0, 1fr))');
|
|
18
|
-
for (const count of [1, 2, 3, 4]) expect(getColumnsLayout(count).count).toBe(count);
|
|
19
|
-
});
|
|
20
|
-
it('uses valid ratios and safely falls back for invalid options', () => {
|
|
21
|
-
expect(getColumnsLayout(2, '2:1').template).toBe('minmax(0, 2fr) minmax(0, 1fr)');
|
|
22
|
-
for (const ratio of ['0:1', '-1:2', 'NaN:1', '1:2:3', '1fr:2fr', 'Infinity:1']) {
|
|
23
|
-
expect(getColumnsLayout(2, ratio).template).toBe('repeat(2, minmax(0, 1fr))');
|
|
24
|
-
}
|
|
25
|
-
for (const count of [0, 5, 1.5, NaN]) expect(getColumnsLayout(count).count).toBe(2);
|
|
26
|
-
});
|
|
27
|
-
it('renders the same API as real MDX with rich content and nested components', async () => {
|
|
28
|
-
const require = createRequire(import.meta.url);
|
|
29
|
-
const mdxRequire = createRequire(require.resolve('@astrojs/mdx'));
|
|
30
|
-
const { evaluate } = await import(pathToFileURL(mdxRequire.resolve('@mdx-js/mdx')).href);
|
|
31
|
-
const result = await evaluate(source, runtime);
|
|
32
|
-
const html = renderToStaticMarkup(
|
|
33
|
-
runtime.jsx(result.default, {
|
|
34
|
-
components: {
|
|
35
|
-
Columns: MarkdownColumns,
|
|
36
|
-
Column: MarkdownColumn,
|
|
37
|
-
CodeGroup: 'section',
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
);
|
|
41
|
-
expect(html).toContain('--ec-columns-template:minmax(0, 2fr) minmax(0, 1fr)');
|
|
42
|
-
expect(html).toContain('data-sticky="true"');
|
|
43
|
-
expect(html).toContain('<strong>explanation</strong>');
|
|
44
|
-
expect(html).toContain('language-json');
|
|
45
|
-
});
|
|
46
|
-
it('recognizes adjacent layout tags in Markdown and lets CodeGroup transform within columns', () => {
|
|
47
|
-
const html = renderToStaticMarkup(
|
|
48
|
-
<ReactMarkdown remarkPlugins={[remarkMarkdownColumns, remarkMarkdownCodeGroup]}>{source}</ReactMarkdown>
|
|
49
|
-
);
|
|
50
|
-
expect(html).toContain('data-columns="true"');
|
|
51
|
-
expect(html.match(/data-column="true"/g)).toHaveLength(2);
|
|
52
|
-
expect(html).toContain('data-cols="2"');
|
|
53
|
-
expect(html).toContain('data-ratio="2:1"');
|
|
54
|
-
expect(html).toContain('data-sticky="true"');
|
|
55
|
-
expect(html).toContain('data-code-panel="OrderCreated"');
|
|
56
|
-
expect(html).toContain('<strong>explanation</strong>');
|
|
57
|
-
expect(html).not.toContain('<Column');
|
|
58
|
-
});
|
|
59
|
-
it('preserves nested column layouts and leaves unrelated HTML escaped', () => {
|
|
60
|
-
const nested =
|
|
61
|
-
'<Columns>\n\n<Column>\n\n<Columns cols={1}>\n\n<Column>\n\nInner\n\n</Column>\n\n</Columns>\n\n</Column>\n\n</Columns>\n\n<script>alert(1)</script>';
|
|
62
|
-
const html = renderToStaticMarkup(<ReactMarkdown remarkPlugins={[remarkMarkdownColumns]}>{nested}</ReactMarkdown>);
|
|
63
|
-
expect(html.match(/data-columns="true"/g)).toHaveLength(2);
|
|
64
|
-
expect(html).not.toContain('<script>');
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { formatCustomPropertyLabel, getCustomProperties, getCustomProperty, isCustomPropertyName } from './custom-properties';
|
|
3
|
-
|
|
4
|
-
describe('custom properties MDX components', () => {
|
|
5
|
-
it('recognizes extension property names', () => {
|
|
6
|
-
expect(isCustomPropertyName('x-owner')).toBe(true);
|
|
7
|
-
expect(isCustomPropertyName('x-')).toBe(false);
|
|
8
|
-
expect(isCustomPropertyName('owner')).toBe(false);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('creates a readable label without changing uppercase text', () => {
|
|
12
|
-
expect(formatCustomPropertyLabel('x-operational-tier')).toBe('Operational Tier');
|
|
13
|
-
expect(formatCustomPropertyLabel('x-PII_classification')).toBe('PII Classification');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('extracts only custom properties and preserves their values', () => {
|
|
17
|
-
const properties = getCustomProperties({
|
|
18
|
-
id: 'payment-api',
|
|
19
|
-
'x-operational-tier': 1,
|
|
20
|
-
'x-on-call': { schedule: 'payments-primary' },
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
expect(properties).toEqual([
|
|
24
|
-
{ name: 'x-operational-tier', label: 'Operational Tier', value: 1 },
|
|
25
|
-
{ name: 'x-on-call', label: 'On Call', value: { schedule: 'payments-primary' } },
|
|
26
|
-
]);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('gets a named custom property', () => {
|
|
30
|
-
expect(getCustomProperty({ 'x-owner': 'payments' }, 'x-owner')).toEqual({
|
|
31
|
-
name: 'x-owner',
|
|
32
|
-
label: 'Owner',
|
|
33
|
-
value: 'payments',
|
|
34
|
-
});
|
|
35
|
-
expect(getCustomProperty({ owner: 'payments' }, 'owner')).toBeUndefined();
|
|
36
|
-
expect(getCustomProperty({ 'x-owner': 'payments' }, 'x-missing')).toBeUndefined();
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { resolveSchemaViewer } from './schema-viewer-utils';
|
|
3
|
-
|
|
4
|
-
const currentMessageSchema = {
|
|
5
|
-
id: 'schema:events:OrderPlaced:1.0.0:git://contracts/events/OrderPlaced.schema.json',
|
|
6
|
-
data: {
|
|
7
|
-
ref: 'git://contracts/events/OrderPlaced.schema.json',
|
|
8
|
-
format: 'jsonschema',
|
|
9
|
-
content: '{"type":"object","title":"OrderPlaced"}',
|
|
10
|
-
default: true,
|
|
11
|
-
message: {
|
|
12
|
-
collectionName: 'events',
|
|
13
|
-
id: 'OrderPlaced',
|
|
14
|
-
version: '1.0.0',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
describe('resolveSchemaViewer', () => {
|
|
20
|
-
it('uses the current message default schema when no file is set', async () => {
|
|
21
|
-
const schema = await resolveSchemaViewer({
|
|
22
|
-
id: 'OrderPlaced',
|
|
23
|
-
version: '1.0.0',
|
|
24
|
-
collection: 'events',
|
|
25
|
-
filePath: 'events/OrderPlaced/index.mdx',
|
|
26
|
-
schemaViewerProps: {},
|
|
27
|
-
collectionSchemas: [currentMessageSchema],
|
|
28
|
-
index: 0,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
expect(schema.exists).toBe(true);
|
|
32
|
-
expect(schema.schema.title).toBe('OrderPlaced');
|
|
33
|
-
expect(schema.schemaPath).toBe('git://contracts/events/OrderPlaced.schema.json');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('parses protobuf schemas from the schema collection', async () => {
|
|
37
|
-
const protoSchema = {
|
|
38
|
-
id: 'schema:events:FraudCheckCompleted:1.0.0:git://contracts/events/FraudCheckCompleted.proto',
|
|
39
|
-
data: {
|
|
40
|
-
ref: 'git://contracts/events/FraudCheckCompleted.proto',
|
|
41
|
-
format: 'protobuf',
|
|
42
|
-
content: 'syntax = "proto3";\nmessage FraudCheckCompleted {\n string transaction_id = 1;\n}',
|
|
43
|
-
default: true,
|
|
44
|
-
message: {
|
|
45
|
-
collectionName: 'events',
|
|
46
|
-
id: 'FraudCheckCompleted',
|
|
47
|
-
version: '1.0.0',
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const schema = await resolveSchemaViewer({
|
|
53
|
-
id: 'FraudCheckCompleted',
|
|
54
|
-
version: '1.0.0',
|
|
55
|
-
collection: 'events',
|
|
56
|
-
filePath: 'events/FraudCheckCompleted/index.mdx',
|
|
57
|
-
schemaViewerProps: {},
|
|
58
|
-
collectionSchemas: [protoSchema],
|
|
59
|
-
index: 0,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
expect(schema.exists).toBe(true);
|
|
63
|
-
expect(schema.isProtobufSchema).toBe(true);
|
|
64
|
-
expect(schema.schema.syntax).toBe('proto3');
|
|
65
|
-
expect(schema.schema.messages[0].name).toBe('FraudCheckCompleted');
|
|
66
|
-
expect(schema.schema.messages[0].fields[0].name).toBe('transaction_id');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('captures parse errors for invalid protobuf schemas', async () => {
|
|
70
|
-
const protoSchema = {
|
|
71
|
-
id: 'schema:events:FraudCheckCompleted:1.0.0:git://contracts/events/FraudCheckCompleted.proto',
|
|
72
|
-
data: {
|
|
73
|
-
ref: 'git://contracts/events/FraudCheckCompleted.proto',
|
|
74
|
-
format: 'protobuf',
|
|
75
|
-
content: '{"this": "is not protobuf"}',
|
|
76
|
-
default: true,
|
|
77
|
-
message: {
|
|
78
|
-
collectionName: 'events',
|
|
79
|
-
id: 'FraudCheckCompleted',
|
|
80
|
-
version: '1.0.0',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const schema = await resolveSchemaViewer({
|
|
86
|
-
id: 'FraudCheckCompleted',
|
|
87
|
-
version: '1.0.0',
|
|
88
|
-
collection: 'events',
|
|
89
|
-
filePath: 'events/FraudCheckCompleted/index.mdx',
|
|
90
|
-
schemaViewerProps: {},
|
|
91
|
-
collectionSchemas: [protoSchema],
|
|
92
|
-
index: 0,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
expect(schema.exists).toBe(false);
|
|
96
|
-
expect(schema.parseError).toBeDefined();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('does not use the schema collection when a file is set and missing', async () => {
|
|
100
|
-
const schema = await resolveSchemaViewer({
|
|
101
|
-
id: 'OrderPlaced',
|
|
102
|
-
version: '1.0.0',
|
|
103
|
-
collection: 'events',
|
|
104
|
-
filePath: 'events/OrderPlaced/index.mdx',
|
|
105
|
-
schemaViewerProps: {
|
|
106
|
-
file: 'missing-schema.json',
|
|
107
|
-
},
|
|
108
|
-
collectionSchemas: [currentMessageSchema],
|
|
109
|
-
index: 0,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
expect(schema.exists).toBe(false);
|
|
113
|
-
expect(schema.schema).toBeUndefined();
|
|
114
|
-
expect(schema.schemaKey).toBe('missing-schema.json');
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { act, createElement } from 'react';
|
|
3
|
-
import { createRoot, type Root } from 'react-dom/client';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import JSONSchemaViewer from './JSONSchemaViewer';
|
|
6
|
-
|
|
7
|
-
const constraintOnlyVariantSchema = {
|
|
8
|
-
type: 'object',
|
|
9
|
-
properties: {
|
|
10
|
-
detail: { $ref: '#/$defs/detail' },
|
|
11
|
-
},
|
|
12
|
-
$defs: {
|
|
13
|
-
detail: {
|
|
14
|
-
type: 'object',
|
|
15
|
-
properties: {
|
|
16
|
-
data: { $ref: '#/$defs/data' },
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
data: {
|
|
20
|
-
type: 'object',
|
|
21
|
-
properties: {
|
|
22
|
-
userId: { type: 'string' },
|
|
23
|
-
customerId: { type: 'string' },
|
|
24
|
-
correlationId: { type: 'string' },
|
|
25
|
-
},
|
|
26
|
-
required: ['correlationId'],
|
|
27
|
-
oneOf: [
|
|
28
|
-
{
|
|
29
|
-
title: 'Authenticated user',
|
|
30
|
-
required: ['userId'],
|
|
31
|
-
not: { required: ['customerId'] },
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
title: 'Customer account',
|
|
35
|
-
required: ['customerId'],
|
|
36
|
-
not: { required: ['userId'] },
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
describe('JSONSchemaViewer variants', () => {
|
|
44
|
-
let container: HTMLDivElement;
|
|
45
|
-
let root: Root;
|
|
46
|
-
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
49
|
-
container = document.createElement('div');
|
|
50
|
-
document.body.appendChild(container);
|
|
51
|
-
root = createRoot(container);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
afterEach(() => {
|
|
55
|
-
act(() => root.unmount());
|
|
56
|
-
container.remove();
|
|
57
|
-
vi.unstubAllGlobals();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('renders parent properties selected by constraint-only oneOf branches', () => {
|
|
61
|
-
act(() => {
|
|
62
|
-
root.render(createElement(JSONSchemaViewer, { schema: constraintOnlyVariantSchema, expand: true, search: false }));
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
expect(container.textContent).toContain('Authenticated user');
|
|
66
|
-
expect(container.textContent).toContain('userId');
|
|
67
|
-
expect(container.textContent).not.toContain('customerId');
|
|
68
|
-
expect(container.textContent).toContain('correlationId');
|
|
69
|
-
|
|
70
|
-
const selector = container.querySelector<HTMLSelectElement>('select');
|
|
71
|
-
expect(selector).not.toBeNull();
|
|
72
|
-
|
|
73
|
-
act(() => {
|
|
74
|
-
selector!.value = '1';
|
|
75
|
-
selector!.dispatchEvent(new Event('change', { bubbles: true }));
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
expect(container.textContent).toContain('customerId');
|
|
79
|
-
expect(container.textContent).not.toContain('userId');
|
|
80
|
-
expect(container.textContent).toContain('correlationId');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('applies constraint-only oneOf branches at the schema root', () => {
|
|
84
|
-
const rootSchema = constraintOnlyVariantSchema.$defs.data;
|
|
85
|
-
|
|
86
|
-
act(() => {
|
|
87
|
-
root.render(createElement(JSONSchemaViewer, { schema: rootSchema, expand: true, search: false }));
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
expect(container.textContent).toContain('userId');
|
|
91
|
-
expect(container.textContent).not.toContain('customerId');
|
|
92
|
-
expect(container.textContent).toContain('correlationId');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('does not render array item parent properties twice when variants are present', () => {
|
|
96
|
-
const arraySchema = {
|
|
97
|
-
type: 'object',
|
|
98
|
-
properties: {
|
|
99
|
-
identities: {
|
|
100
|
-
type: 'array',
|
|
101
|
-
items: constraintOnlyVariantSchema.$defs.data,
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
act(() => {
|
|
107
|
-
root.render(createElement(JSONSchemaViewer, { schema: arraySchema, expand: true, search: false }));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(container.textContent?.match(/userId/g)).toHaveLength(1);
|
|
111
|
-
expect(container.textContent).not.toContain('customerId');
|
|
112
|
-
expect(container.textContent?.match(/correlationId/g)).toHaveLength(1);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { renderToStaticMarkup } from 'react-dom/server';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { parseProtobufSchema } from '@utils/protobuf-schema';
|
|
4
|
-
import ProtobufSchemaViewer from './ProtobufSchemaViewer';
|
|
5
|
-
|
|
6
|
-
describe('ProtobufSchemaViewer', () => {
|
|
7
|
-
it('renders Protovalidate required and constraint annotations', () => {
|
|
8
|
-
const schema = parseProtobufSchema(`
|
|
9
|
-
syntax = "proto3";
|
|
10
|
-
|
|
11
|
-
enum Status {
|
|
12
|
-
STATUS_UNSPECIFIED = 0;
|
|
13
|
-
STATUS_ACTIVE = 1;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
message Journey {
|
|
17
|
-
string id = 1 [
|
|
18
|
-
(buf.validate.field).required = true,
|
|
19
|
-
(buf.validate.field).string.uuid = true
|
|
20
|
-
];
|
|
21
|
-
uint32 passenger_count = 2 [(buf.validate.field).uint32 = { gte: 1, lte: 20 }];
|
|
22
|
-
Status status = 3 [(buf.validate.field).enum = { defined_only: true, not_in: [0] }];
|
|
23
|
-
repeated string stops = 4 [(buf.validate.field).repeated = {
|
|
24
|
-
min_items: 2
|
|
25
|
-
unique: true
|
|
26
|
-
items: { string: { min_len: 3 } }
|
|
27
|
-
}];
|
|
28
|
-
planning.model.v1.TrainServiceReference train_service = 5;
|
|
29
|
-
optional string description = 6 [(datahub.v1.gdpr_rule).string = true];
|
|
30
|
-
}
|
|
31
|
-
`);
|
|
32
|
-
|
|
33
|
-
const html = renderToStaticMarkup(<ProtobufSchemaViewer schema={schema} />);
|
|
34
|
-
const statusField = html.slice(html.indexOf('>status</span>'), html.indexOf('>stops</span>'));
|
|
35
|
-
|
|
36
|
-
expect(html).toContain('required');
|
|
37
|
-
expect(html).toContain('Format');
|
|
38
|
-
expect(html).toContain('UUID');
|
|
39
|
-
expect(html).toContain('Minimum');
|
|
40
|
-
expect(html).toContain('Maximum');
|
|
41
|
-
expect(html).toContain('Defined enum values only');
|
|
42
|
-
expect(html).toContain('Excluded values');
|
|
43
|
-
expect(html).toContain('Minimum items');
|
|
44
|
-
expect(html).toContain('Unique items');
|
|
45
|
-
expect(html).toContain('Item minimum length');
|
|
46
|
-
expect(html).toContain('title="planning.model.v1.TrainServiceReference"');
|
|
47
|
-
expect(html).toContain('>TrainServiceReference</span>');
|
|
48
|
-
expect(statusField).not.toContain('STATUS_UNSPECIFIED');
|
|
49
|
-
expect(statusField).toContain('STATUS_ACTIVE');
|
|
50
|
-
expect(html).toContain('Annotation:');
|
|
51
|
-
expect(html).toContain('(datahub.v1.gdpr_rule).string');
|
|
52
|
-
expect(html).toContain('>true</code>');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('filters enum values using repeated-item and map-value constraints', () => {
|
|
56
|
-
const schema = parseProtobufSchema(`
|
|
57
|
-
syntax = "proto3";
|
|
58
|
-
|
|
59
|
-
enum Status {
|
|
60
|
-
STATUS_UNSPECIFIED = 0;
|
|
61
|
-
STATUS_ACTIVE = 1;
|
|
62
|
-
STATUS_ARCHIVED = 2;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
message Journey {
|
|
66
|
-
repeated Status statuses = 1 [(buf.validate.field).repeated = {
|
|
67
|
-
items: { enum: { in: [1] } }
|
|
68
|
-
}];
|
|
69
|
-
map<string, Status> status_by_key = 2 [(buf.validate.field).map = {
|
|
70
|
-
values: { enum: { not_in: [0, 2] } }
|
|
71
|
-
}];
|
|
72
|
-
Status status = 3 [(buf.validate.field).enum.const = 2];
|
|
73
|
-
}
|
|
74
|
-
`);
|
|
75
|
-
|
|
76
|
-
const html = renderToStaticMarkup(<ProtobufSchemaViewer schema={schema} />);
|
|
77
|
-
const repeatedField = html.slice(html.indexOf('>statuses</span>'), html.indexOf('>status_by_key</span>'));
|
|
78
|
-
const mapField = html.slice(html.indexOf('>status_by_key</span>'), html.indexOf('>status</span>'));
|
|
79
|
-
const directField = html.slice(html.indexOf('>status</span>'), html.indexOf('>Enum:</span>'));
|
|
80
|
-
|
|
81
|
-
expect(repeatedField).toContain('STATUS_ACTIVE');
|
|
82
|
-
expect(repeatedField).not.toContain('STATUS_UNSPECIFIED');
|
|
83
|
-
expect(repeatedField).not.toContain('STATUS_ARCHIVED');
|
|
84
|
-
expect(mapField).toContain('STATUS_ACTIVE');
|
|
85
|
-
expect(mapField).not.toContain('STATUS_UNSPECIFIED');
|
|
86
|
-
expect(mapField).not.toContain('STATUS_ARCHIVED');
|
|
87
|
-
expect(directField).toContain('STATUS_ARCHIVED');
|
|
88
|
-
expect(directField).not.toContain('STATUS_UNSPECIFIED');
|
|
89
|
-
expect(directField).not.toContain('STATUS_ACTIVE');
|
|
90
|
-
});
|
|
91
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { renderToStaticMarkup } from 'react-dom/server';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { parseProtobufSchema } from '@utils/protobuf-schema';
|
|
4
|
-
import AvroSchemaViewer from './AvroSchemaViewer';
|
|
5
|
-
import JSONSchemaViewer from './JSONSchemaViewer';
|
|
6
|
-
import ProtobufSchemaViewer from './ProtobufSchemaViewer';
|
|
7
|
-
|
|
8
|
-
const PROPERTY_HOVER_CLASS = 'hover:bg-[rgb(var(--ec-content-hover))]';
|
|
9
|
-
|
|
10
|
-
describe('schema viewer property hover states', () => {
|
|
11
|
-
it('adds a hover background to JSON Schema properties', () => {
|
|
12
|
-
const html = renderToStaticMarkup(
|
|
13
|
-
<JSONSchemaViewer schema={{ type: 'object', properties: { id: { type: 'string' } } }} search={false} />
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
expect(html).toContain(PROPERTY_HOVER_CLASS);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('adds a hover background to Avro fields', () => {
|
|
20
|
-
const html = renderToStaticMarkup(
|
|
21
|
-
<AvroSchemaViewer schema={{ type: 'record', name: 'Example', fields: [{ name: 'id', type: 'string' }] }} search={false} />
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
expect(html).toContain(PROPERTY_HOVER_CLASS);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('adds a hover background to Protobuf fields', () => {
|
|
28
|
-
const schema = parseProtobufSchema('syntax = "proto3"; message Example { string id = 1; }');
|
|
29
|
-
const html = renderToStaticMarkup(<ProtobufSchemaViewer schema={schema} search={false} />);
|
|
30
|
-
|
|
31
|
-
expect(html).toContain(PROPERTY_HOVER_CLASS);
|
|
32
|
-
});
|
|
33
|
-
});
|