@art-md/constructs 0.0.1 → 0.0.3
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/package.json +2 -2
- package/src/factories/constructs/Document/factory/createDocument.test.ts +17 -0
- package/src/factories/constructs/Document/factory/createDocument.ts +11 -0
- package/src/factories/constructs/Document/factory/types.ts +5 -0
- package/src/factories/constructs/Document/index.ts +3 -0
- package/src/factories/constructs/FieldBlock/factory/createFieldBlock.test.ts +26 -0
- package/src/factories/constructs/FieldBlock/factory/createFieldBlock.ts +15 -0
- package/src/factories/constructs/FieldBlock/factory/types.ts +8 -0
- package/src/factories/constructs/FieldBlock/index.ts +4 -0
- package/src/factories/constructs/FieldBlock/types.ts +11 -0
- package/src/factories/constructs/FieldInline/factory/createFieldInline.test.ts +26 -0
- package/src/factories/constructs/FieldInline/factory/createFieldInline.ts +15 -0
- package/src/factories/constructs/FieldInline/factory/types.ts +8 -0
- package/src/factories/constructs/FieldInline/index.ts +4 -0
- package/src/factories/constructs/FieldInline/types.ts +11 -0
- package/src/factories/constructs/NaturalBlock/factory/createNaturalBlock.test.ts +36 -0
- package/src/factories/constructs/NaturalBlock/factory/createNaturalBlock.ts +25 -0
- package/src/factories/constructs/NaturalBlock/factory/types.ts +13 -0
- package/src/factories/constructs/NaturalBlock/index.ts +4 -0
- package/src/factories/constructs/NaturalBlock/types.ts +14 -0
- package/src/factories/constructs/NaturalExpression/factory/createNaturalExpression.test.ts +22 -0
- package/src/factories/constructs/NaturalExpression/factory/createNaturalExpression.ts +13 -0
- package/src/factories/constructs/NaturalExpression/factory/types.ts +8 -0
- package/src/factories/constructs/NaturalExpression/index.ts +4 -0
- package/src/factories/constructs/NaturalExpression/types.ts +9 -0
- package/src/factories/constructs/SectionBlock/factory/createSectionBlock.test.ts +30 -0
- package/src/factories/constructs/SectionBlock/factory/createSectionBlock.ts +15 -0
- package/src/factories/constructs/SectionBlock/factory/types.ts +11 -0
- package/src/factories/constructs/SectionBlock/index.ts +4 -0
- package/src/factories/constructs/SectionBlock/types.ts +12 -0
- package/src/factories/constructs/Tag/factory/createTag.test.ts +10 -0
- package/src/factories/constructs/Tag/factory/createTag.ts +7 -0
- package/src/factories/constructs/Tag/factory/types.ts +3 -0
- package/src/factories/constructs/Tag/index.ts +4 -0
- package/src/factories/constructs/Tag/types.ts +6 -0
- package/src/factories/index.ts +9 -0
- package/src/factories/public.ts +47 -0
- package/src/factories/types.ts +36 -0
- package/src/index.ts +3 -0
- package/src/parser/constructs/FieldBlock/createFieldBlockParser.test.ts +13 -0
- package/src/parser/constructs/FieldBlock/createFieldBlockParser.ts +9 -0
- package/src/parser/constructs/FieldBlock/index.ts +1 -0
- package/src/parser/constructs/FieldBlock/private/helpers/createFieldBlockFromNode.test.ts +63 -0
- package/src/parser/constructs/FieldBlock/private/helpers/createFieldBlockFromNode.ts +33 -0
- package/src/parser/constructs/FieldBlock/private/helpers/onBeforeConstruct.test.ts +30 -0
- package/src/parser/constructs/FieldBlock/private/helpers/onBeforeConstruct.ts +19 -0
- package/src/parser/constructs/FieldBlock/private/index.ts +2 -0
- package/src/parser/constructs/FieldBlock/private/integrator/createFieldBlockIntegrator.test.ts +23 -0
- package/src/parser/constructs/FieldBlock/private/integrator/createFieldBlockIntegrator.ts +13 -0
- package/src/parser/constructs/FieldBlock/private/processor/createFieldBlockProcessor.test.ts +63 -0
- package/src/parser/constructs/FieldBlock/private/processor/createFieldBlockProcessor.ts +21 -0
- package/src/parser/constructs/FieldInline/createFieldInlineParser.test.ts +12 -0
- package/src/parser/constructs/FieldInline/createFieldInlineParser.ts +8 -0
- package/src/parser/constructs/FieldInline/index.ts +1 -0
- package/src/parser/constructs/FieldInline/private/helpers/createFieldInlineFromNode.test.ts +100 -0
- package/src/parser/constructs/FieldInline/private/helpers/createFieldInlineFromNode.ts +51 -0
- package/src/parser/constructs/FieldInline/private/helpers/trimFieldEdges.test.ts +48 -0
- package/src/parser/constructs/FieldInline/private/helpers/trimFieldEdges.ts +18 -0
- package/src/parser/constructs/FieldInline/private/index.ts +1 -0
- package/src/parser/constructs/FieldInline/private/processor/createFieldInlineProcessor.test.ts +57 -0
- package/src/parser/constructs/FieldInline/private/processor/createFieldInlineProcessor.ts +21 -0
- package/src/parser/constructs/NaturalBlock/createNaturalBlockParser.test.ts +12 -0
- package/src/parser/constructs/NaturalBlock/createNaturalBlockParser.ts +8 -0
- package/src/parser/constructs/NaturalBlock/index.ts +1 -0
- package/src/parser/constructs/NaturalBlock/private/helpers/createNaturalBlockFromNode.test.ts +101 -0
- package/src/parser/constructs/NaturalBlock/private/helpers/createNaturalBlockFromNode.ts +51 -0
- package/src/parser/constructs/NaturalBlock/private/index.ts +1 -0
- package/src/parser/constructs/NaturalBlock/private/processor/createNaturalBlockProcessor.test.ts +24 -0
- package/src/parser/constructs/NaturalBlock/private/processor/createNaturalBlockProcessor.ts +10 -0
- package/src/parser/constructs/SectionBlock/createSectionBlockParser.test.ts +13 -0
- package/src/parser/constructs/SectionBlock/createSectionBlockParser.ts +9 -0
- package/src/parser/constructs/SectionBlock/index.ts +1 -0
- package/src/parser/constructs/SectionBlock/private/constants.test.ts +17 -0
- package/src/parser/constructs/SectionBlock/private/constants.ts +1 -0
- package/src/parser/constructs/SectionBlock/private/helpers/createSectionBlockFromNode.test.ts +58 -0
- package/src/parser/constructs/SectionBlock/private/helpers/createSectionBlockFromNode.ts +28 -0
- package/src/parser/constructs/SectionBlock/private/helpers/findParentSection.test.ts +44 -0
- package/src/parser/constructs/SectionBlock/private/helpers/findParentSection.ts +14 -0
- package/src/parser/constructs/SectionBlock/private/index.ts +2 -0
- package/src/parser/constructs/SectionBlock/private/integrator/createSectionBlockIntegrator.test.ts +60 -0
- package/src/parser/constructs/SectionBlock/private/integrator/createSectionBlockIntegrator.ts +31 -0
- package/src/parser/constructs/SectionBlock/private/processor/createSectionBlockProcessor.test.ts +34 -0
- package/src/parser/constructs/SectionBlock/private/processor/createSectionBlockProcessor.ts +15 -0
- package/src/parser/document/createArtDocumentFromNode.test.ts +23 -0
- package/src/parser/document/createArtDocumentFromNode.ts +10 -0
- package/src/parser/document/index.ts +1 -0
- package/src/parser/fields/constants.test.ts +23 -0
- package/src/parser/fields/constants.ts +1 -0
- package/src/parser/fields/index.ts +3 -0
- package/src/parser/fields/isFieldStrong.test.ts +27 -0
- package/src/parser/fields/isFieldStrong.ts +9 -0
- package/src/parser/fields/stripStrong.test.ts +38 -0
- package/src/parser/fields/stripStrong.ts +12 -0
- package/src/parser/index.ts +8 -0
- package/src/parser/mdast/index.ts +1 -0
- package/src/parser/mdast/rawSlice.test.ts +43 -0
- package/src/parser/mdast/rawSlice.ts +8 -0
- package/src/parser/naturalExpression/createNaturalExpressionFromNode.test.ts +40 -0
- package/src/parser/naturalExpression/createNaturalExpressionFromNode.ts +25 -0
- package/src/parser/naturalExpression/index.ts +1 -0
- package/src/parser/public.ts +16 -0
- package/src/parser/tags/extractTags.test.ts +85 -0
- package/src/parser/tags/extractTags.ts +23 -0
- package/src/parser/tags/index.ts +1 -0
- package/src/parser/types.ts +21 -0
- package/src/serializer/constructs/Document/createDocumentToMdast.test.ts +22 -0
- package/src/serializer/constructs/Document/createDocumentToMdast.ts +15 -0
- package/src/serializer/constructs/Document/index.ts +1 -0
- package/src/serializer/constructs/FieldBlock/createFieldBlockToMdast.test.ts +36 -0
- package/src/serializer/constructs/FieldBlock/createFieldBlockToMdast.ts +26 -0
- package/src/serializer/constructs/FieldBlock/index.ts +1 -0
- package/src/serializer/constructs/FieldInline/createFieldInlineToMdast.test.ts +38 -0
- package/src/serializer/constructs/FieldInline/createFieldInlineToMdast.ts +27 -0
- package/src/serializer/constructs/FieldInline/index.ts +1 -0
- package/src/serializer/constructs/NaturalBlock/createNaturalBlockToMdast.test.ts +122 -0
- package/src/serializer/constructs/NaturalBlock/createNaturalBlockToMdast.ts +30 -0
- package/src/serializer/constructs/NaturalBlock/index.ts +1 -0
- package/src/serializer/constructs/NaturalExpression/createNaturalExpressionToMdast.test.ts +29 -0
- package/src/serializer/constructs/NaturalExpression/createNaturalExpressionToMdast.ts +15 -0
- package/src/serializer/constructs/NaturalExpression/index.ts +1 -0
- package/src/serializer/constructs/SectionBlock/createSectionBlockToMdast.test.ts +83 -0
- package/src/serializer/constructs/SectionBlock/createSectionBlockToMdast.ts +29 -0
- package/src/serializer/constructs/SectionBlock/index.ts +1 -0
- package/src/serializer/index.ts +8 -0
- package/src/serializer/public.ts +18 -0
- package/src/serializer/tags/index.ts +2 -0
- package/src/serializer/tags/tagToMdast.test.ts +10 -0
- package/src/serializer/tags/tagToMdast.ts +7 -0
- package/src/serializer/tags/tagsToMdast.test.ts +19 -0
- package/src/serializer/tags/tagsToMdast.ts +10 -0
- package/src/serializer/types.ts +10 -0
- package/src/test/helpers/constructs/FieldBlock/createFieldBlockFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/FieldBlock/createFieldBlockMock.ts +17 -0
- package/src/test/helpers/constructs/FieldBlock/makeFieldBlockFixture.ts +16 -0
- package/src/test/helpers/constructs/FieldBlock/makeFieldBlockMock.ts +16 -0
- package/src/test/helpers/constructs/FieldInline/createFieldInlineFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/FieldInline/createFieldInlineMock.ts +17 -0
- package/src/test/helpers/constructs/FieldInline/makeFieldInlineFixture.ts +16 -0
- package/src/test/helpers/constructs/FieldInline/makeFieldInlineMock.ts +16 -0
- package/src/test/helpers/constructs/NaturalBlock/createNaturalBlockFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/NaturalBlock/createNaturalBlockMock.ts +17 -0
- package/src/test/helpers/constructs/NaturalBlock/makeNaturalBlockFixture.ts +22 -0
- package/src/test/helpers/constructs/NaturalBlock/makeNaturalBlockMock.ts +22 -0
- package/src/test/helpers/constructs/NaturalExpression/makeNaturalExpressionFixture.ts +18 -0
- package/src/test/helpers/constructs/NaturalExpression/makeNaturalExpressionMock.ts +18 -0
- package/src/test/helpers/constructs/SectionBlock/createSectionBlockFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/SectionBlock/createSectionBlockMock.ts +17 -0
- package/src/test/helpers/constructs/SectionBlock/makeSectionBlockFixture.ts +20 -0
- package/src/test/helpers/constructs/Tag/makeTagFixture.ts +10 -0
- package/src/test/helpers/constructs/index.ts +14 -0
- package/src/test/helpers/index.ts +2 -0
- package/src/test/helpers/parsers/fields/stripStrongMock.ts +11 -0
- package/src/test/helpers/parsers/index.ts +5 -0
- package/src/test/helpers/parsers/mdast/rawSliceMock.ts +9 -0
- package/src/test/helpers/parsers/natural-expression/createNaturalExpressionFromNodeMock.ts +16 -0
- package/src/test/helpers/parsers/tags/extractTagsMock.ts +12 -0
- package/src/test/helpers/parsers/tags/tagToMdastMock.ts +9 -0
- package/dist/esm/index.js +0 -3135
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { makeParserVisitContextMock } from '@art-md/primitives/src/test/helpers';
|
|
2
|
+
import type { Paragraph } from 'mdast';
|
|
3
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { createFieldBlock } from '../../../../../factories';
|
|
7
|
+
|
|
8
|
+
import { createFieldBlockFromNode } from './createFieldBlockFromNode';
|
|
9
|
+
|
|
10
|
+
vi.mock('../../../../../factories/constructs/FieldBlock/factory/createFieldBlock', async () => {
|
|
11
|
+
const { createFieldBlockMock } = await import('../../../../../test/helpers');
|
|
12
|
+
return createFieldBlockMock();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('createFieldBlockFromNode', () => {
|
|
16
|
+
it('WHEN text does not start with strong returns null', () => {
|
|
17
|
+
const markdown = 'Foo **Purpose:**';
|
|
18
|
+
const tree = fromMarkdown(markdown);
|
|
19
|
+
const paragraph = tree.children[0] as Paragraph;
|
|
20
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
21
|
+
|
|
22
|
+
const result = createFieldBlockFromNode(paragraph, context);
|
|
23
|
+
|
|
24
|
+
expect(result).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('WHEN text after the strong is not empty returns null', () => {
|
|
28
|
+
const markdown = '**Purpose:** leftover';
|
|
29
|
+
const tree = fromMarkdown(markdown);
|
|
30
|
+
const paragraph = tree.children[0] as Paragraph;
|
|
31
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
32
|
+
|
|
33
|
+
const result = createFieldBlockFromNode(paragraph, context);
|
|
34
|
+
|
|
35
|
+
expect(result).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('WHEN creating a FieldBlock from a paragraph', () => {
|
|
39
|
+
const markdown = '**Purpose:**';
|
|
40
|
+
const tree = fromMarkdown(markdown);
|
|
41
|
+
const paragraph = tree.children[0] as Paragraph;
|
|
42
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
43
|
+
|
|
44
|
+
const result = createFieldBlockFromNode(paragraph, context);
|
|
45
|
+
|
|
46
|
+
expect(createFieldBlock).toHaveBeenCalledWith({ name: 'Purpose', tags: [] });
|
|
47
|
+
expect(result).toBe(vi.mocked(createFieldBlock).mock.results[0]?.value);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('WHEN trailing tags follow the strong passes them to createFieldBlock', () => {
|
|
51
|
+
const markdown = '**Purpose:** (#purpose)';
|
|
52
|
+
const tree = fromMarkdown(markdown);
|
|
53
|
+
const paragraph = tree.children[0] as Paragraph;
|
|
54
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
55
|
+
|
|
56
|
+
createFieldBlockFromNode(paragraph, context);
|
|
57
|
+
|
|
58
|
+
expect(createFieldBlock).toHaveBeenCalledWith({
|
|
59
|
+
name: 'Purpose',
|
|
60
|
+
tags: [{ construct: 'Tag', name: 'purpose' }],
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ParserVisitContext } from '@art-md/primitives';
|
|
2
|
+
import { nodePosition } from '@art-md/primitives';
|
|
3
|
+
import type { Paragraph, Strong } from 'mdast';
|
|
4
|
+
|
|
5
|
+
import { type FieldBlock, createFieldBlock } from '../../../../../factories';
|
|
6
|
+
import { stripStrong } from '../../../../fields';
|
|
7
|
+
import { rawSlice } from '../../../../mdast';
|
|
8
|
+
import { extractTags } from '../../../../tags';
|
|
9
|
+
|
|
10
|
+
export function createFieldBlockFromNode(
|
|
11
|
+
paragraph: Paragraph,
|
|
12
|
+
context: ParserVisitContext,
|
|
13
|
+
): FieldBlock | null {
|
|
14
|
+
const strong = paragraph.children[0] as Strong;
|
|
15
|
+
const paragraphRaw = rawSlice(paragraph, context);
|
|
16
|
+
const strongRaw = rawSlice(strong, context);
|
|
17
|
+
|
|
18
|
+
const afterStrong = paragraphRaw.slice(strongRaw.length);
|
|
19
|
+
const { tags, stripped } = extractTags(afterStrong);
|
|
20
|
+
if (stripped.trim().length > 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const inner = stripStrong(strong, context);
|
|
24
|
+
const colonIndex = inner.indexOf(':');
|
|
25
|
+
|
|
26
|
+
const field = createFieldBlock({
|
|
27
|
+
name: inner.slice(0, colonIndex).trim(),
|
|
28
|
+
tags,
|
|
29
|
+
});
|
|
30
|
+
field.position = nodePosition(paragraph);
|
|
31
|
+
|
|
32
|
+
return field;
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { onBeforeConstruct } from './onBeforeConstruct';
|
|
4
|
+
|
|
5
|
+
describe('onBeforeConstruct', () => {
|
|
6
|
+
it('FOR a non-boundary construct returns the same context', () => {
|
|
7
|
+
const context = { parent: vi.fn() } as never;
|
|
8
|
+
|
|
9
|
+
const result = onBeforeConstruct({ construct: 'NaturalBlock' } as never, context);
|
|
10
|
+
|
|
11
|
+
expect(result).toBe(context);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('FOR a boundary construct without a parent returns the same context', () => {
|
|
15
|
+
const context = { parent: vi.fn(() => undefined) } as never;
|
|
16
|
+
|
|
17
|
+
const result = onBeforeConstruct({ construct: 'FieldBlock' } as never, context);
|
|
18
|
+
|
|
19
|
+
expect(result).toBe(context);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('FOR a boundary construct with a parent returns the parent context', () => {
|
|
23
|
+
const parent = { construct: 'Document' } as never;
|
|
24
|
+
const context = { parent: vi.fn(() => parent) } as never;
|
|
25
|
+
|
|
26
|
+
const result = onBeforeConstruct({ construct: 'SectionBlock' } as never, context);
|
|
27
|
+
|
|
28
|
+
expect(result).toBe(parent);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConstructBase, ParserVisitContext } from '@art-md/primitives';
|
|
2
|
+
|
|
3
|
+
const FIELD_BLOCK_BOUNDARIES = new Set(['FieldBlock', 'FieldInline', 'SectionBlock']);
|
|
4
|
+
|
|
5
|
+
export function onBeforeConstruct(
|
|
6
|
+
construct: ConstructBase,
|
|
7
|
+
context: ParserVisitContext,
|
|
8
|
+
): ParserVisitContext {
|
|
9
|
+
if (!FIELD_BLOCK_BOUNDARIES.has(construct.construct)) {
|
|
10
|
+
return context;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const parent = context.parent();
|
|
14
|
+
if (!parent) {
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return parent;
|
|
19
|
+
}
|
package/src/parser/constructs/FieldBlock/private/integrator/createFieldBlockIntegrator.test.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { makeParserVisitContextMock } from '@art-md/primitives/src/test/helpers';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { makeFieldBlockFixture } from '../../../../../test/helpers';
|
|
5
|
+
import { onBeforeConstruct } from '../helpers/onBeforeConstruct';
|
|
6
|
+
|
|
7
|
+
import { createFieldBlockIntegrator } from './createFieldBlockIntegrator';
|
|
8
|
+
|
|
9
|
+
describe('createFieldBlockIntegrator', () => {
|
|
10
|
+
it('WHEN integrating captures the construct and returns a child context', () => {
|
|
11
|
+
const integrator = createFieldBlockIntegrator();
|
|
12
|
+
const context = makeParserVisitContextMock();
|
|
13
|
+
const nextContext = makeParserVisitContextMock();
|
|
14
|
+
vi.mocked(context.childContext).mockReturnValue(nextContext);
|
|
15
|
+
const field = makeFieldBlockFixture();
|
|
16
|
+
|
|
17
|
+
const result = integrator.integrate(context, {} as never, field as never);
|
|
18
|
+
|
|
19
|
+
expect(context.captureChildConstruct).toHaveBeenCalledWith(field);
|
|
20
|
+
expect(context.childContext).toHaveBeenCalledWith(field, onBeforeConstruct);
|
|
21
|
+
expect(result).toBe(nextContext);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FieldBlock } from '../../../../../factories';
|
|
2
|
+
import type { ConstructIntegrator } from '../../../../types';
|
|
3
|
+
import { onBeforeConstruct } from '../helpers/onBeforeConstruct';
|
|
4
|
+
|
|
5
|
+
export function createFieldBlockIntegrator(): ConstructIntegrator {
|
|
6
|
+
return {
|
|
7
|
+
integrate(context, _node, construct) {
|
|
8
|
+
const field = construct as FieldBlock;
|
|
9
|
+
context.captureChildConstruct(field);
|
|
10
|
+
return context.childContext(field, onBeforeConstruct);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { makeParserVisitContextMock } from '@art-md/primitives/src/test/helpers';
|
|
2
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createFieldBlockFromNodeMock } from '../../../../../test/helpers';
|
|
6
|
+
import { createFieldBlockFromNode } from '../helpers/createFieldBlockFromNode';
|
|
7
|
+
|
|
8
|
+
import { createFieldBlockProcessor } from './createFieldBlockProcessor';
|
|
9
|
+
|
|
10
|
+
vi.mock('../helpers/createFieldBlockFromNode', async () => {
|
|
11
|
+
return createFieldBlockFromNodeMock();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('createFieldBlockProcessor', () => {
|
|
15
|
+
it('WHEN called returns a processor function', () => {
|
|
16
|
+
const processor = createFieldBlockProcessor();
|
|
17
|
+
|
|
18
|
+
expect(processor.captureNode).toBeInstanceOf(Function);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('FOR non-paragraph nodes returns null', () => {
|
|
22
|
+
const processor = createFieldBlockProcessor();
|
|
23
|
+
const context = makeParserVisitContextMock({ markdown: 'Hello' });
|
|
24
|
+
|
|
25
|
+
const result = processor.captureNode(context, { type: 'heading', children: [] } as never);
|
|
26
|
+
|
|
27
|
+
expect(result).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('FOR paragraph with no children returns null', () => {
|
|
31
|
+
const processor = createFieldBlockProcessor();
|
|
32
|
+
const context = makeParserVisitContextMock({ markdown: 'Hello' });
|
|
33
|
+
|
|
34
|
+
const result = processor.captureNode(context, { type: 'paragraph', children: [] } as never);
|
|
35
|
+
|
|
36
|
+
expect(result).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('WHEN first child is not a field strong returns null', () => {
|
|
40
|
+
const processor = createFieldBlockProcessor();
|
|
41
|
+
const markdown = 'Hello world';
|
|
42
|
+
const tree = fromMarkdown(markdown);
|
|
43
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
44
|
+
const paragraph = tree.children[0] as never;
|
|
45
|
+
|
|
46
|
+
const result = processor.captureNode(context, paragraph);
|
|
47
|
+
|
|
48
|
+
expect(result).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('WHEN first child is a field strong returns the createFieldBlockFromNode result', () => {
|
|
52
|
+
const processor = createFieldBlockProcessor();
|
|
53
|
+
const markdown = '**Name:** value';
|
|
54
|
+
const tree = fromMarkdown(markdown);
|
|
55
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
56
|
+
const paragraph = tree.children[0] as never;
|
|
57
|
+
|
|
58
|
+
const result = processor.captureNode(context, paragraph);
|
|
59
|
+
|
|
60
|
+
expect(createFieldBlockFromNode).toHaveBeenCalledWith(paragraph, context);
|
|
61
|
+
expect(result).toMatchObject({ construct: 'FieldBlock', name: 'Test' });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Paragraph } from 'mdast';
|
|
2
|
+
|
|
3
|
+
import { isFieldStrong } from '../../../../fields';
|
|
4
|
+
import type { ConstructProcessor } from '../../../../types';
|
|
5
|
+
import { createFieldBlockFromNode } from '../helpers/createFieldBlockFromNode';
|
|
6
|
+
|
|
7
|
+
export function createFieldBlockProcessor(): ConstructProcessor {
|
|
8
|
+
return {
|
|
9
|
+
captureNode(context, node) {
|
|
10
|
+
if (node.type !== 'paragraph') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const paragraph = node as Paragraph;
|
|
14
|
+
const first = paragraph.children[0];
|
|
15
|
+
if (first === undefined || !isFieldStrong(first, context)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return createFieldBlockFromNode(paragraph, context);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createFieldInlineParser } from './createFieldInlineParser';
|
|
4
|
+
|
|
5
|
+
describe('createFieldInlineParser', () => {
|
|
6
|
+
it('WHEN called returns a parser with FieldInline name, processor and factory', () => {
|
|
7
|
+
const parser = createFieldInlineParser();
|
|
8
|
+
|
|
9
|
+
expect(parser.name).toBe('FieldInline');
|
|
10
|
+
expect(parser.processor).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ConstructParserFactory } from '../../types';
|
|
2
|
+
|
|
3
|
+
import { createFieldInlineProcessor } from './private';
|
|
4
|
+
|
|
5
|
+
export const createFieldInlineParser: ConstructParserFactory = () => ({
|
|
6
|
+
name: 'FieldInline',
|
|
7
|
+
processor: createFieldInlineProcessor(),
|
|
8
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createFieldInlineParser } from './createFieldInlineParser';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { makeParserVisitContextMock } from '@art-md/primitives/src/test/helpers';
|
|
2
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createFieldInline } from '../../../../../factories';
|
|
6
|
+
|
|
7
|
+
import { createFieldInlineFromNode } from './createFieldInlineFromNode';
|
|
8
|
+
|
|
9
|
+
vi.mock('../../../../../factories/constructs/FieldInline/factory/createFieldInline', async () => {
|
|
10
|
+
const { createFieldInlineMock } = await import('../../../../../test/helpers');
|
|
11
|
+
return createFieldInlineMock();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('createFieldInlineFromNode', () => {
|
|
15
|
+
it('WHEN nothing but whitespace follows the strong returns null', () => {
|
|
16
|
+
const markdown = '**Name:** ';
|
|
17
|
+
const tree = fromMarkdown(markdown);
|
|
18
|
+
const paragraph = tree.children[0] as never;
|
|
19
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
20
|
+
|
|
21
|
+
const result = createFieldInlineFromNode(paragraph, context);
|
|
22
|
+
|
|
23
|
+
expect(result).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('WHEN only tags follow the strong returns null', () => {
|
|
27
|
+
const markdown = '**Name:** (#tag)';
|
|
28
|
+
const tree = fromMarkdown(markdown);
|
|
29
|
+
const paragraph = tree.children[0] as never;
|
|
30
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
31
|
+
|
|
32
|
+
const result = createFieldInlineFromNode(paragraph, context);
|
|
33
|
+
|
|
34
|
+
expect(result).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('WHEN capturing inline field values from paragraph siblings after the label', () => {
|
|
38
|
+
const markdown = '**Greeting:** Hello world.';
|
|
39
|
+
const tree = fromMarkdown(markdown);
|
|
40
|
+
const paragraph = tree.children[0] as never;
|
|
41
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
42
|
+
|
|
43
|
+
const result = createFieldInlineFromNode(paragraph, context);
|
|
44
|
+
|
|
45
|
+
expect(createFieldInline).toHaveBeenCalledWith({
|
|
46
|
+
name: 'Greeting',
|
|
47
|
+
children: [
|
|
48
|
+
expect.objectContaining({
|
|
49
|
+
construct: 'NaturalExpression',
|
|
50
|
+
type: 'text',
|
|
51
|
+
value: 'Hello world.',
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
tags: [],
|
|
55
|
+
});
|
|
56
|
+
expect(result).toBe(vi.mocked(createFieldInline).mock.results[0]?.value);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('WHEN converting preserves inline child types in the field children', () => {
|
|
60
|
+
const markdown = '**Remote:** `git@example.com`';
|
|
61
|
+
const tree = fromMarkdown(markdown);
|
|
62
|
+
const paragraph = tree.children[0] as never;
|
|
63
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
64
|
+
|
|
65
|
+
createFieldInlineFromNode(paragraph, context);
|
|
66
|
+
|
|
67
|
+
expect(createFieldInline).toHaveBeenCalledWith({
|
|
68
|
+
name: 'Remote',
|
|
69
|
+
children: [
|
|
70
|
+
expect.objectContaining({
|
|
71
|
+
construct: 'NaturalExpression',
|
|
72
|
+
type: 'inlineCode',
|
|
73
|
+
value: 'git@example.com',
|
|
74
|
+
}),
|
|
75
|
+
],
|
|
76
|
+
tags: [],
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('WHEN extracting tags and strips it from the last text child', () => {
|
|
81
|
+
const markdown = '**Greeting:** Hello there (#friend)';
|
|
82
|
+
const tree = fromMarkdown(markdown);
|
|
83
|
+
const paragraph = tree.children[0] as never;
|
|
84
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
85
|
+
|
|
86
|
+
createFieldInlineFromNode(paragraph, context);
|
|
87
|
+
|
|
88
|
+
expect(createFieldInline).toHaveBeenCalledWith({
|
|
89
|
+
name: 'Greeting',
|
|
90
|
+
children: [
|
|
91
|
+
expect.objectContaining({
|
|
92
|
+
construct: 'NaturalExpression',
|
|
93
|
+
type: 'text',
|
|
94
|
+
value: 'Hello there',
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
tags: [{ construct: 'Tag', name: 'friend' }],
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ParserVisitContext } from '@art-md/primitives';
|
|
2
|
+
import { nodePosition } from '@art-md/primitives';
|
|
3
|
+
import type { Paragraph, Strong } from 'mdast';
|
|
4
|
+
|
|
5
|
+
import { type FieldInline, type Tag, createFieldInline } from '../../../../../factories';
|
|
6
|
+
import { stripStrong } from '../../../../fields';
|
|
7
|
+
import { rawSlice } from '../../../../mdast';
|
|
8
|
+
import { createNaturalExpressionFromNode } from '../../../../naturalExpression';
|
|
9
|
+
import { extractTags } from '../../../../tags';
|
|
10
|
+
|
|
11
|
+
import { trimFieldEdges } from './trimFieldEdges';
|
|
12
|
+
|
|
13
|
+
export function createFieldInlineFromNode(
|
|
14
|
+
paragraph: Paragraph,
|
|
15
|
+
context: ParserVisitContext,
|
|
16
|
+
): FieldInline | null {
|
|
17
|
+
const strong = paragraph.children[0] as Strong;
|
|
18
|
+
const paragraphRaw = rawSlice(paragraph, context);
|
|
19
|
+
const strongRaw = rawSlice(strong, context);
|
|
20
|
+
|
|
21
|
+
const afterStrong = paragraphRaw.slice(strongRaw.length);
|
|
22
|
+
const { stripped } = extractTags(afterStrong);
|
|
23
|
+
if (stripped.trim().length === 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const inner = stripStrong(strong, context);
|
|
27
|
+
const colonIndex = inner.indexOf(':');
|
|
28
|
+
|
|
29
|
+
const expressions = paragraph.children
|
|
30
|
+
.slice(1)
|
|
31
|
+
.map(child => createNaturalExpressionFromNode(child));
|
|
32
|
+
const children = trimFieldEdges(expressions);
|
|
33
|
+
const last = children[children.length - 1];
|
|
34
|
+
let tags: Tag[] = [];
|
|
35
|
+
if (last?.type === 'text' && typeof last.value === 'string') {
|
|
36
|
+
const extracted = extractTags(last.value);
|
|
37
|
+
if (extracted.tags.length) {
|
|
38
|
+
tags = extracted.tags;
|
|
39
|
+
last.value = extracted.stripped;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const field = createFieldInline({
|
|
44
|
+
name: inner.slice(0, colonIndex).trim(),
|
|
45
|
+
children,
|
|
46
|
+
tags,
|
|
47
|
+
});
|
|
48
|
+
field.position = nodePosition(paragraph);
|
|
49
|
+
|
|
50
|
+
return field;
|
|
51
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { makeNaturalExpressionFixture } from '../../../../../test/helpers';
|
|
4
|
+
|
|
5
|
+
import { trimFieldEdges } from './trimFieldEdges';
|
|
6
|
+
|
|
7
|
+
describe('trimFieldEdges', () => {
|
|
8
|
+
it('WHEN the edge texts have surrounding whitespace trims them', () => {
|
|
9
|
+
const expressions = [makeNaturalExpressionFixture({ value: ' hello ' })];
|
|
10
|
+
|
|
11
|
+
const result = trimFieldEdges(expressions);
|
|
12
|
+
|
|
13
|
+
expect(result).toEqual([makeNaturalExpressionFixture({ value: 'hello' })]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('WHEN there are no text nodes leaves them untouched', () => {
|
|
17
|
+
const expressions = [
|
|
18
|
+
makeNaturalExpressionFixture({ type: 'inlineCode', value: 'a' }),
|
|
19
|
+
makeNaturalExpressionFixture({ type: 'inlineCode', value: 'b' }),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const result = trimFieldEdges(expressions);
|
|
23
|
+
|
|
24
|
+
expect(result).toEqual(expressions);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('WHEN the first text is only whitespace removes it', () => {
|
|
28
|
+
const expressions = [
|
|
29
|
+
makeNaturalExpressionFixture({ value: ' ' }),
|
|
30
|
+
makeNaturalExpressionFixture({ type: 'inlineCode', value: 'a' }),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const result = trimFieldEdges(expressions);
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual([makeNaturalExpressionFixture({ type: 'inlineCode', value: 'a' })]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('WHEN the last text is only whitespace removes it', () => {
|
|
39
|
+
const expressions = [
|
|
40
|
+
makeNaturalExpressionFixture({ type: 'inlineCode', value: 'a' }),
|
|
41
|
+
makeNaturalExpressionFixture({ value: ' ' }),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const result = trimFieldEdges(expressions);
|
|
45
|
+
|
|
46
|
+
expect(result).toEqual([makeNaturalExpressionFixture({ type: 'inlineCode', value: 'a' })]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { NaturalExpression } from '../../../../../factories';
|
|
2
|
+
|
|
3
|
+
export function trimFieldEdges(value: NaturalExpression[]): typeof value {
|
|
4
|
+
const trimmed = [...value];
|
|
5
|
+
const first = trimmed[0];
|
|
6
|
+
const last = trimmed[trimmed.length - 1];
|
|
7
|
+
|
|
8
|
+
if (first?.type === 'text' && typeof first?.value === 'string')
|
|
9
|
+
first.value = first.value.replace(/^\s+/, '');
|
|
10
|
+
if (last?.type === 'text' && typeof last?.value === 'string')
|
|
11
|
+
last.value = last.value.replace(/\s+$/, '');
|
|
12
|
+
|
|
13
|
+
return trimmed.filter(
|
|
14
|
+
expression =>
|
|
15
|
+
expression.type !== 'text' ||
|
|
16
|
+
(typeof expression.value === 'string' && expression.value.length > 0),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './processor/createFieldInlineProcessor';
|
package/src/parser/constructs/FieldInline/private/processor/createFieldInlineProcessor.test.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { makeParserVisitContextMock } from '@art-md/primitives/src/test/helpers';
|
|
2
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createFieldInlineFromNodeMock } from '../../../../../test/helpers';
|
|
6
|
+
import { createFieldInlineFromNode } from '../helpers/createFieldInlineFromNode';
|
|
7
|
+
|
|
8
|
+
import { createFieldInlineProcessor } from './createFieldInlineProcessor';
|
|
9
|
+
|
|
10
|
+
vi.mock('../helpers/createFieldInlineFromNode', async () => {
|
|
11
|
+
return createFieldInlineFromNodeMock();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('createFieldInlineProcessor', () => {
|
|
15
|
+
it('FOR non-paragraph nodes returns null', () => {
|
|
16
|
+
const processor = createFieldInlineProcessor();
|
|
17
|
+
const context = makeParserVisitContextMock({ markdown: 'Hello world' });
|
|
18
|
+
|
|
19
|
+
const result = processor.captureNode(context, { type: 'heading', children: [] } as never);
|
|
20
|
+
|
|
21
|
+
expect(result).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('FOR paragraphs with no children returns null', () => {
|
|
25
|
+
const processor = createFieldInlineProcessor();
|
|
26
|
+
const context = makeParserVisitContextMock({ markdown: 'Hello world' });
|
|
27
|
+
|
|
28
|
+
const result = processor.captureNode(context, { type: 'paragraph', children: [] } as never);
|
|
29
|
+
|
|
30
|
+
expect(result).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('WHEN the first child is not a field strong returns null', () => {
|
|
34
|
+
const processor = createFieldInlineProcessor();
|
|
35
|
+
const markdown = 'Hello world';
|
|
36
|
+
const tree = fromMarkdown(markdown);
|
|
37
|
+
const paragraph = tree.children[0] as never;
|
|
38
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
39
|
+
|
|
40
|
+
const result = processor.captureNode(context, paragraph);
|
|
41
|
+
|
|
42
|
+
expect(result).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('WHEN the first child is a field strong returns the createFieldInlineFromNode result', () => {
|
|
46
|
+
const processor = createFieldInlineProcessor();
|
|
47
|
+
const markdown = '**Name:** value';
|
|
48
|
+
const tree = fromMarkdown(markdown);
|
|
49
|
+
const paragraph = tree.children[0] as never;
|
|
50
|
+
const context = makeParserVisitContextMock({ markdown });
|
|
51
|
+
|
|
52
|
+
const result = processor.captureNode(context, paragraph);
|
|
53
|
+
|
|
54
|
+
expect(createFieldInlineFromNode).toHaveBeenCalledWith(paragraph, context);
|
|
55
|
+
expect(result).toMatchObject({ construct: 'FieldInline', name: 'Test' });
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Paragraph } from 'mdast';
|
|
2
|
+
|
|
3
|
+
import { isFieldStrong } from '../../../../fields';
|
|
4
|
+
import type { ConstructProcessor } from '../../../../types';
|
|
5
|
+
import { createFieldInlineFromNode } from '../helpers/createFieldInlineFromNode';
|
|
6
|
+
|
|
7
|
+
export function createFieldInlineProcessor(): ConstructProcessor {
|
|
8
|
+
return {
|
|
9
|
+
captureNode(context, node) {
|
|
10
|
+
if (node.type !== 'paragraph') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const paragraph = node as Paragraph;
|
|
14
|
+
const first = paragraph.children[0];
|
|
15
|
+
if (first === undefined || !isFieldStrong(first, context)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return createFieldInlineFromNode(paragraph, context);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createNaturalBlockParser } from './createNaturalBlockParser';
|
|
4
|
+
|
|
5
|
+
describe('createNaturalBlockParser', () => {
|
|
6
|
+
it('WHEN called returns a parser with NaturalBlock name, processor and factory', () => {
|
|
7
|
+
const parser = createNaturalBlockParser();
|
|
8
|
+
|
|
9
|
+
expect(parser.name).toBe('NaturalBlock');
|
|
10
|
+
expect(parser.processor).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ConstructParserFactory } from '../../types';
|
|
2
|
+
|
|
3
|
+
import { createNaturalBlockProcessor } from './private';
|
|
4
|
+
|
|
5
|
+
export const createNaturalBlockParser: ConstructParserFactory = () => ({
|
|
6
|
+
name: 'NaturalBlock',
|
|
7
|
+
processor: createNaturalBlockProcessor(),
|
|
8
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createNaturalBlockParser } from './createNaturalBlockParser';
|