Skip to content

Commit

Permalink
feat(block-node): add getFragments method (#54)
Browse files Browse the repository at this point in the history
* feat(block-node): add getFragments method

* fix(lint): apply linter

* fix(formatting-node): fix doc

Co-authored-by: George Berezhnoy <[email protected]>

* feat(text-node): filter fragments in getFragments method

* feat(editor-document): add .getFragments() method

* feat(editor-document-model): add .getFragments() method

---------

Co-authored-by: George Berezhnoy <[email protected]>
  • Loading branch information
ilyamore88 and gohabereg authored Nov 16, 2023
1 parent 241e5f4 commit 0ff02e2
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/model/src/EditorJSModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('EditorJSModel', () => {
'removeText',
'format',
'unformat',
'getFragments',
];
const ownProperties = Object.getOwnPropertyNames(EditorJSModel.prototype);

Expand Down
14 changes: 14 additions & 0 deletions packages/model/src/EditorJSModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,18 @@ export class EditorJSModel {
public unformat(...parameters: Parameters<EditorDocument['unformat']>): ReturnType<EditorDocument['unformat']> {
return this.#document.unformat(...parameters);
}

/**
* Returns fragments for the specified block, range, and inline tool
*
* @param parameters - getFragments method parameters
* @param parameters.blockIndex - index of the block
* @param parameters.dataKey - key of the data
* @param [parameters.tool] - name of the Inline Tool to remove
* @param [parameters.start] - start char index of the range
* @param [parameters.end] - end char index of the range
*/
public getFragments(...parameters: Parameters<EditorDocument['getFragments']>): ReturnType<EditorDocument['getFragments']> {
return this.#document.getFragments(...parameters);
}
}
112 changes: 112 additions & 0 deletions packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BlockNode, createDataKey } from './index.js';
import { BlockChildType } from './types/index.js';
import { NODE_TYPE_HIDDEN_PROP } from './consts.js';
import type { InlineFragment } from '../inline-fragments/index.js';
import { createInlineToolName } from '../inline-fragments/index.js';
import { ValueNode } from '../ValueNode/index.js';

describe('BlockNode integration tests', () => {
Expand Down Expand Up @@ -102,6 +104,116 @@ describe('BlockNode integration tests', () => {
});
});

describe('.getFragments()', () => {
it('should return empty array if there is no fragments in the passed range', () => {
const testRangeStart = 0;
const testRangeEnd = 5;
const dataKey = createDataKey('1a2b');

const node = new BlockNode({
name: 'blockNode',
data: {
[dataKey]: {
[NODE_TYPE_HIDDEN_PROP]: BlockChildType.Text,
value: 'value',
},
},
});

const fragments = node.getFragments(
dataKey,
testRangeStart,
testRangeEnd,
createInlineToolName('inlineTool')
);

expect(fragments).toEqual([]);
});

it('should return all fragments for the passed range', () => {
const boldFragmentStart = 0;
const boldFragmentEnd = 5;
const italicFragmentStart = 3;
const italicFragmentEnd = 10;

const testRangeStart = 2;
const testRangeEnd = 7;

const fragments: InlineFragment[] = [
{
tool: createInlineToolName('bold'),
range: [boldFragmentStart, boldFragmentEnd],
},
{
tool: createInlineToolName('italic'),
range: [italicFragmentStart, italicFragmentEnd],
},
];

const dataKey = createDataKey('1a2b');

const node = new BlockNode({
name: 'blockNode',
data: {
[dataKey]: {
[NODE_TYPE_HIDDEN_PROP]: BlockChildType.Text,
value: 'Test text for checking the fragments',
fragments,
},
},
});

const result = node.getFragments(dataKey, testRangeStart, testRangeEnd);

expect(result)
.toEqual(fragments);
});

it('should return fragments for the passed range and tool', () => {
const boldFragmentStart = 0;
const boldFragmentEnd = 5;
const italicFragmentStart = 3;
const italicFragmentEnd = 10;

const testRangeStart = 2;
const testRangeEnd = 7;

const fragments: InlineFragment[] = [
{
tool: createInlineToolName('bold'),
range: [boldFragmentStart, boldFragmentEnd],
},
{
tool: createInlineToolName('italic'),
range: [italicFragmentStart, italicFragmentEnd],
},
];

const dataKey = createDataKey('1a2b');

const node = new BlockNode({
name: 'blockNode',
data: {
[dataKey]: {
[NODE_TYPE_HIDDEN_PROP]: BlockChildType.Text,
value: 'Test text for checking the fragments',
fragments,
},
},
});

const result = node.getFragments(
dataKey,
testRangeStart,
testRangeEnd,
createInlineToolName('italic')
);

expect(result)
.toEqual([ fragments[1] ]);
});
});

describe('.data', () => {
it('should return the data associated with this block node', () => {
// Arrange
Expand Down
57 changes: 56 additions & 1 deletion packages/model/src/entities/BlockNode/BlockNode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ValueNode } from '../ValueNode/index.js';

import type { EditorDocument } from '../EditorDocument';
import type { ValueNodeConstructorParameters } from '../ValueNode';
import type { InlineToolData, InlineToolName } from '../inline-fragments';
import type { InlineFragment, InlineToolData, InlineToolName } from '../inline-fragments';
import { TextNode } from '../inline-fragments/index.js';
import type { BlockNodeData, BlockNodeDataSerialized } from './types';
import { BlockChildType } from './types/index.js';
Expand Down Expand Up @@ -1050,6 +1050,61 @@ describe('BlockNode', () => {
});
});

describe('.getFragments()', () => {
it('should call .getFragments() method of the TextNode', () => {
const spy = jest.spyOn(TextNode.prototype, 'getFragments');
const node = createBlockNodeWithData({
text: {
[NODE_TYPE_HIDDEN_PROP]: BlockChildType.Text,
value: '',
fragments: [],
},
});

node.getFragments(createDataKey('text'), 0, 0, 'bold' as InlineToolName);

expect(spy)
.toHaveBeenCalledWith(0, 0, 'bold' as InlineToolName);
});

it('should return all fragments for the passed range', () => {
const boldFragmentStart = 0;
const boldFragmentEnd = 5;
const italicFragmentStart = 3;
const italicFragmentEnd = 10;

const testRangeStart = 2;
const testRangeEnd = 7;

const fragments: InlineFragment[] = [
{
tool: 'bold' as InlineToolName,
range: [boldFragmentStart, boldFragmentEnd],
},
{
tool: 'italic' as InlineToolName,
range: [italicFragmentStart, italicFragmentEnd],
},
];

jest.spyOn(TextNode.prototype, 'getFragments')
.mockImplementation(() => fragments);

const node = createBlockNodeWithData({
text: {
[NODE_TYPE_HIDDEN_PROP]: BlockChildType.Text,
value: 'Test text for checking the fragments',
fragments,
},
});

const result = node.getFragments(createDataKey('text'), testRangeStart, testRangeEnd);

expect(result)
.toEqual(fragments);
});
});

describe('working with TextNode events', () => {
let node: BlockNode;
let textNode: TextNode;
Expand Down
7 changes: 7 additions & 0 deletions packages/model/src/entities/BlockNode/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,11 @@ export class BlockNode extends EventBus {
public get serialized(): void {
return;
}

/**
* Mock method
*/
public getFragments(): void {
return;
}
}
30 changes: 22 additions & 8 deletions packages/model/src/entities/BlockNode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ import { BlockTune, createBlockTuneName } from '../BlockTune/index.js';
import type {
BlockNodeConstructorParameters,
BlockNodeData,
BlockNodeSerialized,
BlockNodeDataSerialized,
BlockNodeDataSerializedValue,
ChildNode,
BlockNodeDataValue,
BlockNodeSerialized,
BlockToolName,
ChildNode,
DataKey
} from './types';
import {
createBlockToolName,
createDataKey,
BlockChildType
} from './types/index.js';
import { BlockChildType, createBlockToolName, createDataKey } from './types/index.js';
import { ValueNode } from '../ValueNode/index.js';
import type { InlineToolData, InlineToolName, TextNodeSerialized } from '../inline-fragments';
import type { InlineFragment, InlineToolData, InlineToolName, TextNodeSerialized } from '../inline-fragments';
import { TextNode } from '../inline-fragments/index.js';
import { get, has } from '../../utils/keypath.js';
import { NODE_TYPE_HIDDEN_PROP } from './consts.js';
Expand Down Expand Up @@ -245,6 +241,24 @@ export class BlockNode extends EventBus {
node.unformat(tool, start, end);
}

/**
* Returns all fragments of the text node by range
* If the name of the Inline Tool is passed, then only fragments of this Inline Tool will be returned
*
* @param dataKey - key of the data
* @param [start] - start char index of the range
* @param [end] - end char index of the range
* @param [tool] - name of the Inline Tool
* @throws {Error} if data with passed key does not exist
*/
public getFragments(dataKey: DataKey, start?: number, end?: number, tool?: InlineToolName): InlineFragment[] {
this.#validateKey(dataKey, TextNode);

const node = get<TextNode>(this.#data, dataKey as string)!;

return node.getFragments(start, end, tool);
}

/**
* Initializes BlockNode with passed block data
*
Expand Down
16 changes: 16 additions & 0 deletions packages/model/src/entities/EditorDocument/EditorDocument.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1003,4 +1003,20 @@ describe('EditorDocument', () => {
.toHaveBeenCalled();
});
});

describe('.getFragments()', () => {
it('should call BlockNode method with passed parameters', () => {
const document = createEditorDocumentWithSomeBlocks();
const blockIndex = 1;
const dataKey = 'text' as DataKey;
const start = 5;
const end = 10;
const tool = 'bold' as InlineToolName;
const spy = jest.spyOn(document.getBlock(blockIndex), 'getFragments');

document.getFragments(blockIndex, dataKey, start, end, tool);

expect(spy).toHaveBeenCalledWith(dataKey, start, end, tool);
});
});
});
15 changes: 14 additions & 1 deletion packages/model/src/entities/EditorDocument/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { DataKey } from '../BlockNode';
import { BlockNode } from '../BlockNode/index.js';
import type { EditorDocumentSerialized, EditorDocumentConstructorParameters, Properties } from './types';
import type { BlockTuneName } from '../BlockTune';
import type { InlineToolData, InlineToolName } from '../inline-fragments';
import type { InlineFragment, InlineToolData, InlineToolName } from '../inline-fragments';
import { IoCContainer, TOOLS_REGISTRY } from '../../IoC/index.js';
import { ToolsRegistry } from '../../tools/index.js';
import type { BlockNodeSerialized } from '../BlockNode/types';
Expand Down Expand Up @@ -288,6 +288,19 @@ export class EditorDocument extends EventBus {
};
}

/**
* Returns array of InlineFragment objects for the specified range
*
* @param blockIndex - index of the block
* @param dataKey - key of the data
* @param [start] - start char index of the range
* @param [end] - end char index of the range
* @param [tool] - name of the Inline Tool to filter by
*/
public getFragments(blockIndex: number, dataKey: DataKey, start?: number, end?: number, tool?: InlineToolName): InlineFragment[] {
return this.#children[blockIndex].getFragments(dataKey, start, end, tool);
}

/**
* Listens to BlockNode events and bubbles them to the EditorDocument
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ export class FormattingInlineNode extends ParentInlineNode implements InlineNode

/**
* Returns inline fragments for node from the specified character range
*
* If start and/or end is specified, method will return partial fragments for the specified range
* Always return full fragments even if one is not fully covered by the passed range
*
* @param [start] - start char index of the range, by default 0
* @param [end] - end char index of the range, by default length of the text value
Expand All @@ -76,7 +75,7 @@ export class FormattingInlineNode extends ParentInlineNode implements InlineNode

const currentFragment: InlineFragment = {
tool: this.tool,
range: [start, end],
range: [0, this.length],
};

if (this.data) {
Expand Down
Loading

1 comment on commit 0ff02e2

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for ./packages/model

St.
Category Percentage Covered / Total
🟢 Statements 100% 588/588
🟢 Branches 99.27% 136/137
🟢 Functions 99.33% 149/150
🟢 Lines 100% 563/563

Test suite run success

346 tests passing in 19 suites.

Report generated by 🧪jest coverage report action from 0ff02e2

Please sign in to comment.