All files / json-patch/op OpSplit.ts

98.03% Statements 50/51
94.73% Branches 18/19
100% Functions 7/7
100% Lines 47/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114  17x   17x 17x 17x             17x     165x 165x   165x       27x       6x       135x 135x 135x 135x 110x 30x 30x 80x 135x       135x 50x 50x 50x 50x 5x                   5x 85x 35x 35x 35x 35x                       35x 50x 40x 40x 40x 40x                       40x 10x 5x 5x 5x       15x         15x 15x       12x 12x      
import type {CompactSplitOp, OPCODE_SPLIT} from '../codec/compact/types';
import {AbstractOp} from './AbstractOp';
import type {OperationSplit, SlateNode, SlateTextNode, SlateElementNode} from '../types';
import {find, isObjectReference, isArrayReference, type Path, formatJsonPointer} from '@jsonjoy.com/json-pointer';
import {isTextNode, isElementNode} from '../util';
import {OPCODE} from '../constants';
 
type Composable = string | number | SlateNode;
 
/**
 * @category JSON Patch Extended
 */
export class OpSplit extends AbstractOp<'split'> {
  constructor(
    path: Path,
    public readonly pos: number,
    public readonly props: object | null,
  ) {
    super(path);
  }
 
  public op() {
    return 'split' as const;
  }
 
  public code() {
    return OPCODE.split;
  }
 
  public apply(doc: unknown) {
    const ref = find(doc, this.path);
    Iif (ref.val === undefined) throw new Error('NOT_FOUND');
    const tuple = this.split(ref.val);
    if (isObjectReference(ref)) ref.obj[ref.key] = tuple;
    else if (isArrayReference(ref)) {
      ref.obj[ref.key] = tuple[0];
      ref.obj.splice(ref.key + 1, 0, tuple[1]);
    } else doc = tuple;
    return {doc, old: ref.val};
  }
 
  private split<T>(node: T): [T | Composable, T | Composable] {
    if (typeof node === 'string') {
      const {pos, props} = this;
      const before = node.slice(0, pos);
      const after = node.slice(pos);
      if (!props) return [before, after];
      const textNodes: [SlateTextNode, SlateTextNode] = [
        {
          ...props,
          text: before,
        },
        {
          ...props,
          text: after,
        },
      ];
      return textNodes;
    } else if (isTextNode(node)) {
      const {pos, props} = this;
      const before = node.text.slice(0, pos);
      const after = node.text.slice(pos);
      const textNodes: [SlateTextNode, SlateTextNode] = [
        {
          ...node,
          ...props,
          text: before,
        },
        {
          ...node,
          ...props,
          text: after,
        },
      ];
      return textNodes;
    } else if (isElementNode(node)) {
      const {pos, props} = this;
      const before = node.children.slice(0, pos);
      const after = node.children.slice(pos);
      const elementNodes: [SlateElementNode, SlateElementNode] = [
        {
          ...node,
          ...props,
          children: before,
        },
        {
          ...node,
          ...props,
          children: after,
        },
      ];
      return elementNodes;
    } else if (typeof node === 'number') {
      const {pos} = this;
      return [pos, node - pos];
    } else return [node, node];
  }
 
  public toJson(parent?: AbstractOp): OperationSplit {
    const op: OperationSplit = {
      op: 'split',
      path: formatJsonPointer(this.path),
      pos: this.pos,
    };
    if (this.props) op.props = this.props;
    return op;
  }
 
  public toCompact(parent: undefined | AbstractOp, verbose: boolean): CompactSplitOp {
    const opcode: OPCODE_SPLIT = verbose ? 'split' : OPCODE.split;
    return this.props ? [opcode, this.path, this.pos, this.props] : [opcode, this.path, this.pos];
  }
}