libs/text-splitters/tests/unit_tests/test_text_splitters.py PYTHON 4,376 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 4,376.
1"""Test text splitting functionality."""23from __future__ import annotations45import json6import random7import re8import string9import textwrap10from typing import TYPE_CHECKING, Any1112import pytest13from langchain_core._api import suppress_langchain_beta_warning14from langchain_core.documents import Document1516from langchain_text_splitters import (17    Language,18    RecursiveCharacterTextSplitter,19    TextSplitter,20    Tokenizer,21)22from langchain_text_splitters.base import split_text_on_tokens23from langchain_text_splitters.character import CharacterTextSplitter24from langchain_text_splitters.html import (25    HTMLHeaderTextSplitter,26    HTMLSectionSplitter,27    HTMLSemanticPreservingSplitter,28)29from langchain_text_splitters.json import RecursiveJsonSplitter30from langchain_text_splitters.jsx import JSFrameworkTextSplitter31from langchain_text_splitters.markdown import (32    ExperimentalMarkdownSyntaxTextSplitter,33    MarkdownHeaderTextSplitter,34)35from langchain_text_splitters.python import PythonCodeTextSplitter3637if TYPE_CHECKING:38    from collections.abc import Callable3940    from bs4 import Tag414243FAKE_PYTHON_TEXT = """44class Foo:4546    def bar():474849def foo():5051def testing_func():5253def bar():54"""555657def test_no_heavy_imports_on_package_load() -> None:58    """Ensure importing the package does not eagerly import heavy dependencies.5960    Runs in a fresh interpreter so the result is unaffected by modules the test61    session already imported. A `sys.meta_path` finder records any *attempt* to62    import a heavy optional dependency, so the guard holds whether or not those63    packages are installed in the current environment (a plain `sys.modules` check64    would pass vacuously when the packages are absent).65    """66    import subprocess  # noqa: PLC041567    import sys  # noqa: PLC04156869    script = textwrap.dedent(70        """71        import sys7273        blocked = {74            "nltk", "spacy", "sentence_transformers", "konlpy", "torch",75            "transformers", "tiktoken",76        }77        attempted = []7879        class _Recorder:80            def find_spec(self, name, path=None, target=None):81                if name.split(".")[0] in blocked:82                    attempted.append(name.split(".")[0])83                return None  # defer to the real finders8485        sys.meta_path.insert(0, _Recorder())86        import langchain_text_splitters  # noqa: F40187        print(",".join(sorted(set(attempted))))88        """89    )90    result = subprocess.run(  # noqa: S603  # list args, no shell; input is static91        [sys.executable, "-c", script],92        capture_output=True,93        text=True,94        check=False,95        timeout=60,96    )97    assert result.returncode == 0, (98        f"Importing langchain_text_splitters failed:\n{result.stderr}"99    )100    attempted = [p for p in result.stdout.strip().split(",") if p]101    assert not attempted, (102        f"Heavy packages imported at langchain_text_splitters load time: {attempted}"103    )104105106@pytest.mark.parametrize(107    ("module_name", "expected_message"),108    [109        ("konlpy", "pip install konlpy"),110        ("nltk", "pip install nltk"),111        ("spacy", "pip install spacy"),112        ("sentence_transformers", "pip install sentence-transformers"),113    ],114)115def test_missing_optional_dependency_raises_importerror(116    module_name: str,117    expected_message: str,118    monkeypatch: pytest.MonkeyPatch,119) -> None:120    """Each splitter raises a helpful ImportError when its optional dep is missing.121122    The missing dependency is simulated by forcing its import to fail, so the test123    is independent of whether the optional package is actually installed.124    """125    import sys  # noqa: PLC0415126127    from langchain_text_splitters.konlpy import KonlpyTextSplitter  # noqa: PLC0415128    from langchain_text_splitters.nltk import NLTKTextSplitter  # noqa: PLC0415129    from langchain_text_splitters.sentence_transformers import (  # noqa: PLC0415130        SentenceTransformersTokenTextSplitter,131    )132    from langchain_text_splitters.spacy import SpacyTextSplitter  # noqa: PLC0415133134    constructors: dict[str, Callable[[], TextSplitter]] = {135        "konlpy": KonlpyTextSplitter,136        "nltk": NLTKTextSplitter,137        "spacy": SpacyTextSplitter,138        "sentence_transformers": SentenceTransformersTokenTextSplitter,139    }140141    # `None` in sys.modules makes both `import x` and `import_module(x)` raise142    # ImportError, exercising the splitter's missing-dependency branch.143    monkeypatch.setitem(sys.modules, module_name, None)144    with pytest.raises(ImportError, match=re.escape(expected_message)):145        constructors[module_name]()146147148@pytest.mark.parametrize(149    "class_name",150    [151        "KonlpyTextSplitter",152        "NLTKTextSplitter",153        "SpacyTextSplitter",154        "SentenceTransformersTokenTextSplitter",155    ],156)157def test_lazy_getattr_resolves(class_name: str) -> None:158    """`__getattr__` resolves lazy splitter classes from the package namespace."""159    import langchain_text_splitters as lts  # noqa: PLC0415160161    try:162        cls = getattr(lts, class_name)163    except ImportError:164        pytest.skip(f"Optional dependency for {class_name} not installed")165    assert isinstance(cls, type), f"{class_name} should be a class, got {type(cls)}"166167168def test_lazy_getattr_raises_for_unknown() -> None:169    """Accessing an unknown attribute raises `AttributeError`."""170    import langchain_text_splitters as lts  # noqa: PLC0415171172    with pytest.raises(AttributeError, match="no_such_thing"):173        _ = lts.no_such_thing  # type: ignore[attr-defined]174175176def test_lightweight_splitters_remain_eagerly_accessible() -> None:177    """Lightweight splitters are still directly importable from the package."""178    import langchain_text_splitters as lts  # noqa: PLC0415179180    assert issubclass(lts.RecursiveCharacterTextSplitter, lts.TextSplitter)181    assert issubclass(lts.CharacterTextSplitter, lts.TextSplitter)182183184def test_character_text_splitter() -> None:185    """Test splitting by character count."""186    text = "foo bar baz 123"187    splitter = CharacterTextSplitter(separator=" ", chunk_size=7, chunk_overlap=3)188    output = splitter.split_text(text)189    expected_output = ["foo bar", "bar baz", "baz 123"]190    assert output == expected_output191192193def test_character_text_splitter_empty_doc() -> None:194    """Test splitting by character count doesn't create empty documents."""195    text = "foo  bar"196    splitter = CharacterTextSplitter(separator=" ", chunk_size=2, chunk_overlap=0)197    output = splitter.split_text(text)198    expected_output = ["foo", "bar"]199    assert output == expected_output200201202def test_character_text_splitter_separtor_empty_doc() -> None:203    """Test edge cases are separators."""204    text = "f b"205    splitter = CharacterTextSplitter(separator=" ", chunk_size=2, chunk_overlap=0)206    output = splitter.split_text(text)207    expected_output = ["f", "b"]208    assert output == expected_output209210211def test_character_text_splitter_long() -> None:212    """Test splitting by character count on long words."""213    text = "foo bar baz a a"214    splitter = CharacterTextSplitter(separator=" ", chunk_size=3, chunk_overlap=1)215    output = splitter.split_text(text)216    expected_output = ["foo", "bar", "baz", "a a"]217    assert output == expected_output218219220def test_character_text_splitter_short_words_first() -> None:221    """Test splitting by character count when shorter words are first."""222    text = "a a foo bar baz"223    splitter = CharacterTextSplitter(separator=" ", chunk_size=3, chunk_overlap=1)224    output = splitter.split_text(text)225    expected_output = ["a a", "foo", "bar", "baz"]226    assert output == expected_output227228229def test_character_text_splitter_longer_words() -> None:230    """Test splitting by characters when splits not found easily."""231    text = "foo bar baz 123"232    splitter = CharacterTextSplitter(separator=" ", chunk_size=1, chunk_overlap=1)233    output = splitter.split_text(text)234    expected_output = ["foo", "bar", "baz", "123"]235    assert output == expected_output236237238# edge cases239def test_character_text_splitter_no_separator_in_text() -> None:240    """Text splitting where there is no separator but a single word."""241    text = "singleword"242    splitter = CharacterTextSplitter(separator=" ", chunk_size=10, chunk_overlap=0)243    output = splitter.split_text(text)244    expected_output = ["singleword"]245    assert output == expected_output246247248def test_character_text_splitter_handle_chunksize_equal_to_chunkoverlap() -> None:249    """Text splitting safe guards when chunk size is equal chunk overlap."""250    text = "hello"251    splitter = CharacterTextSplitter(separator=" ", chunk_size=5, chunk_overlap=5)252    output = splitter.split_text(text)253    expected_output = ["hello"]254    assert output == expected_output255256257def test_character_text_splitter_empty_input() -> None:258    """Test splitting safely where there is no input to process."""259    text = ""260    splitter = CharacterTextSplitter(separator=" ", chunk_size=5, chunk_overlap=0)261    output = splitter.split_text(text)262    expected_output: list[str] = []263    assert output == expected_output264265266def test_character_text_splitter_whitespace_only() -> None:267    """Test splitting safely where there is white space."""268    text = " "269    splitter = CharacterTextSplitter(separator=" ", chunk_size=5, chunk_overlap=0)270    output = splitter.split_text(text)271    expected_output: list[str] = []272    assert output == expected_output273274275@pytest.mark.parametrize(276    ("separator", "is_separator_regex"), [(re.escape("."), True), (".", False)]277)278def test_character_text_splitter_keep_separator_regex(279    *, separator: str, is_separator_regex: bool280) -> None:281    """Test CharacterTextSplitter keep separator regex.282283    Test splitting by characters while keeping the separator284    that is a regex special character.285    """286    text = "foo.bar.baz.123"287    splitter = CharacterTextSplitter(288        separator=separator,289        chunk_size=1,290        chunk_overlap=0,291        keep_separator=True,292        is_separator_regex=is_separator_regex,293    )294    output = splitter.split_text(text)295    expected_output = ["foo", ".bar", ".baz", ".123"]296    assert output == expected_output297298299@pytest.mark.parametrize(300    ("separator", "is_separator_regex"), [(re.escape("."), True), (".", False)]301)302def test_character_text_splitter_keep_separator_regex_start(303    *, separator: str, is_separator_regex: bool304) -> None:305    """Test CharacterTextSplitter keep separator regex and put at start.306307    Test splitting by characters while keeping the separator308    that is a regex special character and placing it at the start of each chunk.309    """310    text = "foo.bar.baz.123"311    splitter = CharacterTextSplitter(312        separator=separator,313        chunk_size=1,314        chunk_overlap=0,315        keep_separator="start",316        is_separator_regex=is_separator_regex,317    )318    output = splitter.split_text(text)319    expected_output = ["foo", ".bar", ".baz", ".123"]320    assert output == expected_output321322323@pytest.mark.parametrize(324    ("separator", "is_separator_regex"), [(re.escape("."), True), (".", False)]325)326def test_character_text_splitter_keep_separator_regex_end(327    *, separator: str, is_separator_regex: bool328) -> None:329    """Test CharacterTextSplitter keep separator regex and put at end.330331    Test splitting by characters while keeping the separator332    that is a regex special character and placing it at the end of each chunk.333    """334    text = "foo.bar.baz.123"335    splitter = CharacterTextSplitter(336        separator=separator,337        chunk_size=1,338        chunk_overlap=0,339        keep_separator="end",340        is_separator_regex=is_separator_regex,341    )342    output = splitter.split_text(text)343    expected_output = ["foo.", "bar.", "baz.", "123"]344    assert output == expected_output345346347@pytest.mark.parametrize(348    ("separator", "is_separator_regex"), [(re.escape("."), True), (".", False)]349)350def test_character_text_splitter_discard_separator_regex(351    *, separator: str, is_separator_regex: bool352) -> None:353    """Test CharacterTextSplitter discard separator regex.354355    Test splitting by characters discarding the separator356    that is a regex special character.357    """358    text = "foo.bar.baz.123"359    splitter = CharacterTextSplitter(360        separator=separator,361        chunk_size=1,362        chunk_overlap=0,363        keep_separator=False,364        is_separator_regex=is_separator_regex,365    )366    output = splitter.split_text(text)367    expected_output = ["foo", "bar", "baz", "123"]368    assert output == expected_output369370371def test_recursive_character_text_splitter_keep_separators() -> None:372    split_tags = [",", "."]373    query = "Apple,banana,orange and tomato."374    # start375    splitter = RecursiveCharacterTextSplitter(376        chunk_size=10,377        chunk_overlap=0,378        separators=split_tags,379        keep_separator="start",380    )381    result = splitter.split_text(query)382    assert result == ["Apple", ",banana", ",orange and tomato", "."]383384    # end385    splitter = RecursiveCharacterTextSplitter(386        chunk_size=10,387        chunk_overlap=0,388        separators=split_tags,389        keep_separator="end",390    )391    result = splitter.split_text(query)392    assert result == ["Apple,", "banana,", "orange and tomato."]393394395def test_character_text_splitting_args() -> None:396    """Test invalid arguments."""397    with pytest.raises(398        ValueError,399        match=re.escape(400            "Got a larger chunk overlap (4) than chunk size (2), should be smaller."401        ),402    ):403        CharacterTextSplitter(chunk_size=2, chunk_overlap=4)404    for invalid_size in (0, -1):405        with pytest.raises(ValueError, match="chunk_size must be > 0, got"):406            CharacterTextSplitter(chunk_size=invalid_size)407    with pytest.raises(ValueError, match="chunk_overlap must be >= 0, got -1"):408        CharacterTextSplitter(chunk_size=2, chunk_overlap=-1)409410411def test_merge_splits() -> None:412    """Test merging splits with a given separator."""413    splitter = CharacterTextSplitter(separator=" ", chunk_size=9, chunk_overlap=2)414    splits = ["foo", "bar", "baz"]415    expected_output = ["foo bar", "baz"]416    output = splitter._merge_splits(splits, separator=" ")417    assert output == expected_output418419420def test_create_documents() -> None:421    """Test create documents method."""422    texts = ["foo bar", "baz"]423    splitter = CharacterTextSplitter(separator=" ", chunk_size=3, chunk_overlap=0)424    docs = splitter.create_documents(texts)425    expected_docs = [426        Document(page_content="foo"),427        Document(page_content="bar"),428        Document(page_content="baz"),429    ]430    assert docs == expected_docs431432433def test_create_documents_with_metadata() -> None:434    """Test create documents with metadata method."""435    texts = ["foo bar", "baz"]436    splitter = CharacterTextSplitter(separator=" ", chunk_size=3, chunk_overlap=0)437    docs = splitter.create_documents(texts, [{"source": "1"}, {"source": "2"}])438    expected_docs = [439        Document(page_content="foo", metadata={"source": "1"}),440        Document(page_content="bar", metadata={"source": "1"}),441        Document(page_content="baz", metadata={"source": "2"}),442    ]443    assert docs == expected_docs444445446@pytest.mark.parametrize(447    ("splitter", "text", "expected_docs"),448    [449        (450            CharacterTextSplitter(451                separator=" ", chunk_size=7, chunk_overlap=3, add_start_index=True452            ),453            "foo bar baz 123",454            [455                Document(page_content="foo bar", metadata={"start_index": 0}),456                Document(page_content="bar baz", metadata={"start_index": 4}),457                Document(page_content="baz 123", metadata={"start_index": 8}),458            ],459        ),460        (461            RecursiveCharacterTextSplitter(462                chunk_size=6,463                chunk_overlap=0,464                separators=["\n\n", "\n", " ", ""],465                add_start_index=True,466            ),467            "w1 w1 w1 w1 w1 w1 w1 w1 w1",468            [469                Document(page_content="w1 w1", metadata={"start_index": 0}),470                Document(page_content="w1 w1", metadata={"start_index": 6}),471                Document(page_content="w1 w1", metadata={"start_index": 12}),472                Document(page_content="w1 w1", metadata={"start_index": 18}),473                Document(page_content="w1", metadata={"start_index": 24}),474            ],475        ),476    ],477)478def test_create_documents_with_start_index(479    splitter: TextSplitter, text: str, expected_docs: list[Document]480) -> None:481    """Test create documents method."""482    docs = splitter.create_documents([text])483    assert docs == expected_docs484    for doc in docs:485        s_i = doc.metadata["start_index"]486        assert text[s_i : s_i + len(doc.page_content)] == doc.page_content487488489def test_metadata_not_shallow() -> None:490    """Test that metadatas are not shallow."""491    texts = ["foo bar"]492    splitter = CharacterTextSplitter(separator=" ", chunk_size=3, chunk_overlap=0)493    docs = splitter.create_documents(texts, [{"source": "1"}])494    expected_docs = [495        Document(page_content="foo", metadata={"source": "1"}),496        Document(page_content="bar", metadata={"source": "1"}),497    ]498    assert docs == expected_docs499    docs[0].metadata["foo"] = 1500    assert docs[0].metadata == {"source": "1", "foo": 1}501    assert docs[1].metadata == {"source": "1"}502503504def test_iterative_text_splitter_keep_separator() -> None:505    chunk_size = 5506    output = __test_iterative_text_splitter(chunk_size=chunk_size, keep_separator=True)507508    assert output == [509        "....5",510        "X..3",511        "Y...4",512        "X....5",513        "Y...",514    ]515516517def test_iterative_text_splitter_discard_separator() -> None:518    chunk_size = 5519    output = __test_iterative_text_splitter(chunk_size=chunk_size, keep_separator=False)520521    assert output == [522        "....5",523        "..3",524        "...4",525        "....5",526        "...",527    ]528529530def __test_iterative_text_splitter(531    *, chunk_size: int, keep_separator: bool532) -> list[str]:533    chunk_size += 1 if keep_separator else 0534535    splitter = RecursiveCharacterTextSplitter(536        chunk_size=chunk_size,537        chunk_overlap=0,538        separators=["X", "Y"],539        keep_separator=keep_separator,540    )541    text = "....5X..3Y...4X....5Y..."542    output = splitter.split_text(text)543    for chunk in output:544        assert len(chunk) <= chunk_size, f"Chunk is larger than {chunk_size}"545    return output546547548def test_iterative_text_splitter() -> None:549    """Test iterative text splitter."""550    text = """Hi.\n\nI'm Harrison.\n\nHow? Are? You?\nOkay then f f f f.551This is a weird text to write, but gotta test the splittingggg some how.552553Bye!\n\n-H."""554    splitter = RecursiveCharacterTextSplitter(chunk_size=10, chunk_overlap=1)555    output = splitter.split_text(text)556    expected_output = [557        "Hi.",558        "I'm",559        "Harrison.",560        "How? Are?",561        "You?",562        "Okay then",563        "f f f f.",564        "This is a",565        "weird",566        "text to",567        "write,",568        "but gotta",569        "test the",570        "splitting",571        "gggg",572        "some how.",573        "Bye!",574        "-H.",575    ]576    assert output == expected_output577578579def test_split_documents() -> None:580    """Test split_documents."""581    splitter = CharacterTextSplitter(separator="", chunk_size=1, chunk_overlap=0)582    docs = [583        Document(page_content="foo", metadata={"source": "1"}),584        Document(page_content="bar", metadata={"source": "2"}),585        Document(page_content="baz", metadata={"source": "1"}),586    ]587    expected_output = [588        Document(page_content="f", metadata={"source": "1"}),589        Document(page_content="o", metadata={"source": "1"}),590        Document(page_content="o", metadata={"source": "1"}),591        Document(page_content="b", metadata={"source": "2"}),592        Document(page_content="a", metadata={"source": "2"}),593        Document(page_content="r", metadata={"source": "2"}),594        Document(page_content="b", metadata={"source": "1"}),595        Document(page_content="a", metadata={"source": "1"}),596        Document(page_content="z", metadata={"source": "1"}),597    ]598    assert splitter.split_documents(docs) == expected_output599600601def test_python_text_splitter() -> None:602    splitter = PythonCodeTextSplitter(chunk_size=30, chunk_overlap=0)603    splits = splitter.split_text(FAKE_PYTHON_TEXT)604    split_0 = """class Foo:\n\n    def bar():"""605    split_1 = """def foo():"""606    split_2 = """def testing_func():"""607    split_3 = """def bar():"""608    expected_splits = [split_0, split_1, split_2, split_3]609    assert splits == expected_splits610611612FAKE_JSX_TEXT = """613import React from 'react';614import OtherComponent from './OtherComponent';615616function MyComponent() {617  const [count, setCount] = React.useState(0);618619  const handleClick = () => {620    setCount(count + 1);621  };622623  return (624    <div>625      <h1>Counter: {count}</h1>626      <button onClick={handleClick}>627        Increment628      </button>629      <OtherComponent />630    </div>631  );632}633634export default MyComponent;635"""636637638def test_jsx_text_splitter() -> None:639    splitter = JSFrameworkTextSplitter(chunk_size=30, chunk_overlap=0)640    splits = splitter.split_text(FAKE_JSX_TEXT)641642    expected_splits = [643        (644            "\nimport React from 'react';\n"645            "import OtherComponent from './OtherComponent';\n"646        ),647        "\nfunction MyComponent() {\n  const [count, setCount] = React.useState(0);",648        "\n\n  const handleClick = () => {\n    setCount(count + 1);\n  };",649        "return (",650        "<div>",651        "<h1>Counter: {count}</h1>\n      ",652        "<button onClick={handleClick}>\n        Increment\n      </button>\n      ",653        "<OtherComponent />\n    </div>\n  );\n}\n",654        "export default MyComponent;",655    ]656    assert [s.strip() for s in splits] == [s.strip() for s in expected_splits]657658659FAKE_VUE_TEXT = """660<template>661  <div>662    <h1>{{ title }}</h1>663    <button @click="increment">664      Count is: {{ count }}665    </button>666  </div>667</template>668669<script>670export default {671  data() {672    return {673      title: 'Counter App',674      count: 0675    }676  },677  methods: {678    increment() {679      this.count++680    }681  }682}683</script>684685<style>686button {687  color: blue;688}689</style>690"""691692693def test_vue_text_splitter() -> None:694    splitter = JSFrameworkTextSplitter(chunk_size=30, chunk_overlap=0)695    splits = splitter.split_text(FAKE_VUE_TEXT)696697    expected_splits = [698        "<template>",699        "<div>",700        "<h1>{{ title }}</h1>",701        (702            '<button @click="increment">\n      Count is: {{ count }}\n'703            "    </button>\n  </div>\n</template>"704        ),705        "<script>",706        "export",707        (708            " default {\n  data() {\n    return {\n      title: 'Counter App',\n      "709            "count: 0\n    }\n  },\n  methods: {\n    increment() {\n      "710            "this.count++\n    }\n  }\n}\n</script>"711        ),712        "<style>\nbutton {\n  color: blue;\n}\n</style>",713    ]714    assert [s.strip() for s in splits] == [s.strip() for s in expected_splits]715716717FAKE_SVELTE_TEXT = """718<script>719  let count = 0720721  function increment() {722    count += 1723  }724</script>725726<main>727  <h1>Counter App</h1>728  <button on:click={increment}>729    Count is: {count}730  </button>731</main>732733<style>734  button {735    color: blue;736  }737</style>738"""739740741def test_svelte_text_splitter() -> None:742    splitter = JSFrameworkTextSplitter(chunk_size=30, chunk_overlap=0)743    splits = splitter.split_text(FAKE_SVELTE_TEXT)744745    expected_splits = [746        "<script>\n  let count = 0",747        "\n\n  function increment() {\n    count += 1\n  }\n</script>",748        "<main>",749        "<h1>Counter App</h1>",750        "<button on:click={increment}>\n    Count is: {count}\n  </button>\n</main>",751        "<style>\n  button {\n    color: blue;\n  }\n</style>",752    ]753    assert [s.strip() for s in splits] == [s.strip() for s in expected_splits]754755756def test_jsx_splitter_separator_not_mutated_across_calls() -> None:757    """Regression test: repeated split_text() calls must not mutate separators.758759    Calling split_text() multiple times on the same JSFrameworkTextSplitter760    instance must not grow the internal separator list between calls.761762    Before the fix, self._separators was overwritten with the full expanded list763    on every invocation, so a second call would start with the already-expanded764    list and append even more separators.765    """766    splitter = JSFrameworkTextSplitter(chunk_size=30, chunk_overlap=0)767768    # Record separator count after constructing (should be 0 - no custom separators)769    initial_sep_count = len(splitter._separators)770771    # Call split_text twice; the results should be identical for identical input772    splits_first = splitter.split_text(FAKE_JSX_TEXT)773    splits_second = splitter.split_text(FAKE_JSX_TEXT)774775    assert splits_first == splits_second, (776        "split_text() must return identical results on repeated calls with the "777        "same input"778    )779    assert len(splitter._separators) == initial_sep_count, (780        "split_text() must not mutate self._separators between calls"781    )782783784CHUNK_SIZE = 16785786787def test_python_code_splitter() -> None:788    splitter = RecursiveCharacterTextSplitter.from_language(789        Language.PYTHON, chunk_size=CHUNK_SIZE, chunk_overlap=0790    )791    code = """792def hello_world():793    print("Hello, World!")794795# Call the function796hello_world()797    """798    chunks = splitter.split_text(code)799    assert chunks == [800        "def",801        "hello_world():",802        'print("Hello,',803        'World!")',804        "# Call the",805        "function",806        "hello_world()",807    ]808809810def test_golang_code_splitter() -> None:811    splitter = RecursiveCharacterTextSplitter.from_language(812        Language.GO, chunk_size=CHUNK_SIZE, chunk_overlap=0813    )814    code = """815package main816817import "fmt"818819func helloWorld() {820    fmt.Println("Hello, World!")821}822823func main() {824    helloWorld()825}826    """827    chunks = splitter.split_text(code)828    assert chunks == [829        "package main",830        'import "fmt"',831        "func",832        "helloWorld() {",833        'fmt.Println("He',834        "llo,",835        'World!")',836        "}",837        "func main() {",838        "helloWorld()",839        "}",840    ]841842843def test_rst_code_splitter() -> None:844    splitter = RecursiveCharacterTextSplitter.from_language(845        Language.RST, chunk_size=CHUNK_SIZE, chunk_overlap=0846    )847    code = """848Sample Document849===============850851Section852-------853854This is the content of the section.855856Lists857-----858859- Item 1860- Item 2861- Item 3862863Comment864*******865Not a comment866867.. This is a comment868    """869    chunks = splitter.split_text(code)870    assert chunks == [871        "Sample Document",872        "===============",873        "Section",874        "-------",875        "This is the",876        "content of the",877        "section.",878        "Lists",879        "-----",880        "- Item 1",881        "- Item 2",882        "- Item 3",883        "Comment",884        "*******",885        "Not a comment",886        ".. This is a",887        "comment",888    ]889    # Special test for special characters890    code = "harry\n***\nbabylon is"891    chunks = splitter.split_text(code)892    assert chunks == ["harry", "***\nbabylon is"]893894895def test_proto_file_splitter() -> None:896    splitter = RecursiveCharacterTextSplitter.from_language(897        Language.PROTO, chunk_size=CHUNK_SIZE, chunk_overlap=0898    )899    code = """900syntax = "proto3";901902package example;903904message Person {905    string name = 1;906    int32 age = 2;907    repeated string hobbies = 3;908}909    """910    chunks = splitter.split_text(code)911    assert chunks == [912        "syntax =",913        '"proto3";',914        "package",915        "example;",916        "message Person",917        "{",918        "string name",919        "= 1;",920        "int32 age =",921        "2;",922        "repeated",923        "string hobbies",924        "= 3;",925        "}",926    ]927928929def test_javascript_code_splitter() -> None:930    splitter = RecursiveCharacterTextSplitter.from_language(931        Language.JS, chunk_size=CHUNK_SIZE, chunk_overlap=0932    )933    code = """934function helloWorld() {935  console.log("Hello, World!");936}937938// Call the function939helloWorld();940    """941    chunks = splitter.split_text(code)942    assert chunks == [943        "function",944        "helloWorld() {",945        'console.log("He',946        "llo,",947        'World!");',948        "}",949        "// Call the",950        "function",951        "helloWorld();",952    ]953954955def test_cobol_code_splitter() -> None:956    splitter = RecursiveCharacterTextSplitter.from_language(957        Language.COBOL, chunk_size=CHUNK_SIZE, chunk_overlap=0958    )959    code = """960IDENTIFICATION DIVISION.961PROGRAM-ID. HelloWorld.962DATA DIVISION.963WORKING-STORAGE SECTION.96401 GREETING           PIC X(12)   VALUE 'Hello, World!'.965PROCEDURE DIVISION.966DISPLAY GREETING.967STOP RUN.968    """969    chunks = splitter.split_text(code)970    assert chunks == [971        "IDENTIFICATION",972        "DIVISION.",973        "PROGRAM-ID.",974        "HelloWorld.",975        "DATA DIVISION.",976        "WORKING-STORAGE",977        "SECTION.",978        "01 GREETING",979        "PIC X(12)",980        "VALUE 'Hello,",981        "World!'.",982        "PROCEDURE",983        "DIVISION.",984        "DISPLAY",985        "GREETING.",986        "STOP RUN.",987    ]988989990def test_typescript_code_splitter() -> None:991    splitter = RecursiveCharacterTextSplitter.from_language(992        Language.TS, chunk_size=CHUNK_SIZE, chunk_overlap=0993    )994    code = """995function helloWorld(): void {996  console.log("Hello, World!");997}998999// Call the function1000helloWorld();1001    """1002    chunks = splitter.split_text(code)1003    assert chunks == [1004        "function",1005        "helloWorld():",1006        "void {",1007        'console.log("He',1008        "llo,",1009        'World!");',1010        "}",1011        "// Call the",1012        "function",1013        "helloWorld();",1014    ]101510161017def test_java_code_splitter() -> None:1018    splitter = RecursiveCharacterTextSplitter.from_language(1019        Language.JAVA, chunk_size=CHUNK_SIZE, chunk_overlap=01020    )1021    code = """1022public class HelloWorld {1023    public static void main(String[] args) {1024        System.out.println("Hello, World!");1025    }1026}1027    """1028    chunks = splitter.split_text(code)1029    assert chunks == [1030        "public class",1031        "HelloWorld {",1032        "public",1033        "static void",1034        "main(String[]",1035        "args) {",1036        "System.out.prin",1037        'tln("Hello,',1038        'World!");',1039        "}\n}",1040    ]104110421043def test_kotlin_code_splitter() -> None:1044    splitter = RecursiveCharacterTextSplitter.from_language(1045        Language.KOTLIN, chunk_size=CHUNK_SIZE, chunk_overlap=01046    )1047    code = """1048class HelloWorld {1049    companion object {1050        @JvmStatic1051        fun main(args: Array<String>) {1052            println("Hello, World!")1053        }1054    }1055}1056    """1057    chunks = splitter.split_text(code)1058    assert chunks == [1059        "class",1060        "HelloWorld {",1061        "companion",1062        "object {",1063        "@JvmStatic",1064        "fun",1065        "main(args:",1066        "Array<String>)",1067        "{",1068        'println("Hello,',1069        'World!")',1070        "}\n    }",1071        "}",1072    ]107310741075def test_csharp_code_splitter() -> None:1076    splitter = RecursiveCharacterTextSplitter.from_language(1077        Language.CSHARP, chunk_size=CHUNK_SIZE, chunk_overlap=01078    )1079    code = """1080using System;1081class Program1082{1083    static void Main()1084    {1085        int age = 30; // Change the age value as needed10861087        // Categorize the age without any console output1088        if (age < 18)1089        {1090            // Age is under 181091        }1092        else if (age >= 18 && age < 65)1093        {1094            // Age is an adult1095        }1096        else1097        {1098            // Age is a senior citizen1099        }1100    }1101}1102    """11031104    chunks = splitter.split_text(code)1105    assert chunks == [1106        "using System;",1107        "class Program\n{",1108        "static void",1109        "Main()",1110        "{",1111        "int age",1112        "= 30; // Change",1113        "the age value",1114        "as needed",1115        "//",1116        "Categorize the",1117        "age without any",1118        "console output",1119        "if (age",1120        "< 18)",1121        "{",1122        "//",1123        "Age is under 18",1124        "}",1125        "else if",1126        "(age >= 18 &&",1127        "age < 65)",1128        "{",1129        "//",1130        "Age is an adult",1131        "}",1132        "else",1133        "{",1134        "//",1135        "Age is a senior",1136        "citizen",1137        "}\n    }",1138        "}",1139    ]114011411142def test_csharp_separators_no_java_keywords() -> None:1143    """C# separators should not contain Java-only keywords."""1144    splitter = RecursiveCharacterTextSplitter.from_language(1145        Language.CSHARP, chunk_size=CHUNK_SIZE, chunk_overlap=01146    )1147    # "implements" is a Java keyword; C# uses ":" for interface implementation1148    assert "\nimplements " not in splitter._separators114911501151def test_elixir_separators_no_while() -> None:1152    """Elixir has no while loop; the separator should not be present."""1153    splitter = RecursiveCharacterTextSplitter.from_language(1154        Language.ELIXIR, chunk_size=CHUNK_SIZE, chunk_overlap=01155    )1156    assert "\nwhile " not in splitter._separators115711581159def test_cpp_code_splitter() -> None:1160    splitter = RecursiveCharacterTextSplitter.from_language(1161        Language.CPP, chunk_size=CHUNK_SIZE, chunk_overlap=01162    )1163    code = """1164#include <iostream>11651166int main() {1167    std::cout << "Hello, World!" << std::endl;1168    return 0;1169}1170    """1171    chunks = splitter.split_text(code)1172    assert chunks == [1173        "#include",1174        "<iostream>",1175        "int main() {",1176        "std::cout",1177        '<< "Hello,',1178        'World!" <<',1179        "std::endl;",1180        "return 0;\n}",1181    ]118211831184def test_scala_code_splitter() -> None:1185    splitter = RecursiveCharacterTextSplitter.from_language(1186        Language.SCALA, chunk_size=CHUNK_SIZE, chunk_overlap=01187    )1188    code = """1189object HelloWorld {1190  def main(args: Array[String]): Unit = {1191    println("Hello, World!")1192  }1193}1194    """1195    chunks = splitter.split_text(code)1196    assert chunks == [1197        "object",1198        "HelloWorld {",1199        "def",1200        "main(args:",1201        "Array[String]):",1202        "Unit = {",1203        'println("Hello,',1204        'World!")',1205        "}\n}",1206    ]120712081209def test_ruby_code_splitter() -> None:1210    splitter = RecursiveCharacterTextSplitter.from_language(1211        Language.RUBY, chunk_size=CHUNK_SIZE, chunk_overlap=01212    )1213    code = """1214def hello_world1215  puts "Hello, World!"1216end12171218hello_world1219    """1220    chunks = splitter.split_text(code)1221    assert chunks == [1222        "def hello_world",1223        'puts "Hello,',1224        'World!"',1225        "end",1226        "hello_world",1227    ]122812291230def test_php_code_splitter() -> None:1231    splitter = RecursiveCharacterTextSplitter.from_language(1232        Language.PHP, chunk_size=CHUNK_SIZE, chunk_overlap=01233    )1234    code = """1235<?php1236function hello_world() {1237    echo "Hello, World!";1238}12391240hello_world();1241?>1242    """1243    chunks = splitter.split_text(code)1244    assert chunks == [1245        "<?php",1246        "function",1247        "hello_world() {",1248        "echo",1249        '"Hello,',1250        'World!";',1251        "}",1252        "hello_world();",1253        "?>",1254    ]125512561257def test_swift_code_splitter() -> None:1258    splitter = RecursiveCharacterTextSplitter.from_language(1259        Language.SWIFT, chunk_size=CHUNK_SIZE, chunk_overlap=01260    )1261    code = """1262func helloWorld() {1263    print("Hello, World!")1264}12651266helloWorld()1267    """1268    chunks = splitter.split_text(code)1269    assert chunks == [1270        "func",1271        "helloWorld() {",1272        'print("Hello,',1273        'World!")',1274        "}",1275        "helloWorld()",1276    ]127712781279def test_rust_code_splitter() -> None:1280    splitter = RecursiveCharacterTextSplitter.from_language(1281        Language.RUST, chunk_size=CHUNK_SIZE, chunk_overlap=01282    )1283    code = """1284fn main() {1285    println!("Hello, World!");1286}1287    """1288    chunks = splitter.split_text(code)1289    assert chunks == ["fn main() {", 'println!("Hello', ",", 'World!");', "}"]129012911292def test_r_code_splitter() -> None:1293    splitter = RecursiveCharacterTextSplitter.from_language(1294        Language.R, chunk_size=CHUNK_SIZE, chunk_overlap=01295    )1296    code = """1297library(dplyr)12981299my_func <- function(x) {1300    return(x + 1)1301}13021303if (TRUE) {1304    print("Hello")1305}1306    """1307    chunks = splitter.split_text(code)1308    assert chunks == [1309        "library(dplyr)",1310        "my_func <-",1311        "function(x) {",1312        "return(x +",1313        "1)",1314        "}",1315        "if (TRUE) {",1316        'print("Hello")',1317        "}",1318    ]131913201321def test_markdown_code_splitter() -> None:1322    splitter = RecursiveCharacterTextSplitter.from_language(1323        Language.MARKDOWN, chunk_size=CHUNK_SIZE, chunk_overlap=01324    )1325    code = """1326# Sample Document13271328## Section13291330This is the content of the section.13311332## Lists13331334- Item 11335- Item 21336- Item 313371338### Horizontal lines13391340***********1341____________1342-------------------13431344#### Code blocks1345```1346This is a code block13471348# sample code1349a = 11350b = 21351```1352    """1353    chunks = splitter.split_text(code)1354    assert chunks == [1355        "# Sample",1356        "Document",1357        "## Section",1358        "This is the",1359        "content of the",1360        "section.",1361        "## Lists",1362        "- Item 1",1363        "- Item 2",1364        "- Item 3",1365        "### Horizontal",1366        "lines",1367        "***********",1368        "____________",1369        "---------------",1370        "----",1371        "#### Code",1372        "blocks",1373        "```",1374        "This is a code",1375        "block",1376        "# sample code",1377        "a = 1\nb = 2",1378        "```",1379    ]1380    # Special test for special characters1381    code = "harry\n***\nbabylon is"1382    chunks = splitter.split_text(code)1383    assert chunks == ["harry", "***\nbabylon is"]138413851386def test_latex_code_splitter() -> None:1387    splitter = RecursiveCharacterTextSplitter.from_language(1388        Language.LATEX, chunk_size=CHUNK_SIZE, chunk_overlap=01389    )1390    code = """1391Hi Harrison!1392\\chapter{1}1393"""1394    chunks = splitter.split_text(code)1395    assert chunks == ["Hi Harrison!", "\\chapter{1}"]139613971398def test_html_code_splitter() -> None:1399    splitter = RecursiveCharacterTextSplitter.from_language(1400        Language.HTML, chunk_size=60, chunk_overlap=01401    )1402    code = """1403<h1>Sample Document</h1>1404    <h2>Section</h2>1405        <p id="1234">Reference content.</p>14061407    <h2>Lists</h2>1408        <ul>1409            <li>Item 1</li>1410            <li>Item 2</li>1411            <li>Item 3</li>1412        </ul>14131414        <h3>A block</h3>1415            <div class="amazing">1416                <p>Some text</p>1417                <p>Some more text</p>1418            </div>1419    """1420    chunks = splitter.split_text(code)1421    assert chunks == [1422        "<h1>Sample Document</h1>\n    <h2>Section</h2>",1423        '<p id="1234">Reference content.</p>',1424        "<h2>Lists</h2>\n        <ul>",1425        "<li>Item 1</li>\n            <li>Item 2</li>",1426        "<li>Item 3</li>\n        </ul>",1427        "<h3>A block</h3>",1428        '<div class="amazing">',1429        "<p>Some text</p>",1430        "<p>Some more text</p>\n            </div>",1431    ]143214331434def test_md_header_text_splitter_1() -> None:1435    """Test markdown splitter by header: Case 1."""1436    markdown_document = (1437        "# Foo\n\n"1438        "    ## Bar\n\n"1439        "Hi this is Jim\n\n"1440        "Hi this is Joe\n\n"1441        " ## Baz\n\n"1442        " Hi this is Molly"1443    )1444    headers_to_split_on = [1445        ("#", "Header 1"),1446        ("##", "Header 2"),1447    ]1448    markdown_splitter = MarkdownHeaderTextSplitter(1449        headers_to_split_on=headers_to_split_on,1450    )1451    output = markdown_splitter.split_text(markdown_document)1452    expected_output = [1453        Document(1454            page_content="Hi this is Jim  \nHi this is Joe",1455            metadata={"Header 1": "Foo", "Header 2": "Bar"},1456        ),1457        Document(1458            page_content="Hi this is Molly",1459            metadata={"Header 1": "Foo", "Header 2": "Baz"},1460        ),1461    ]1462    assert output == expected_output146314641465def test_md_header_text_splitter_2() -> None:1466    """Test markdown splitter by header: Case 2."""1467    markdown_document = (1468        "# Foo\n\n"1469        "    ## Bar\n\n"1470        "Hi this is Jim\n\n"1471        "Hi this is Joe\n\n"1472        " ### Boo \n\n"1473        " Hi this is Lance \n\n"1474        " ## Baz\n\n"1475        " Hi this is Molly"1476    )14771478    headers_to_split_on = [1479        ("#", "Header 1"),1480        ("##", "Header 2"),1481        ("###", "Header 3"),1482    ]1483    markdown_splitter = MarkdownHeaderTextSplitter(1484        headers_to_split_on=headers_to_split_on,1485    )1486    output = markdown_splitter.split_text(markdown_document)1487    expected_output = [1488        Document(1489            page_content="Hi this is Jim  \nHi this is Joe",1490            metadata={"Header 1": "Foo", "Header 2": "Bar"},1491        ),1492        Document(1493            page_content="Hi this is Lance",1494            metadata={"Header 1": "Foo", "Header 2": "Bar", "Header 3": "Boo"},1495        ),1496        Document(1497            page_content="Hi this is Molly",1498            metadata={"Header 1": "Foo", "Header 2": "Baz"},1499        ),1500    ]1501    assert output == expected_output150215031504def test_md_header_text_splitter_3() -> None:1505    """Test markdown splitter by header: Case 3."""1506    markdown_document = (1507        "# Foo\n\n"1508        "    ## Bar\n\n"1509        "Hi this is Jim\n\n"1510        "Hi this is Joe\n\n"1511        " ### Boo \n\n"1512        " Hi this is Lance \n\n"1513        " #### Bim \n\n"1514        " Hi this is John \n\n"1515        " ## Baz\n\n"1516        " Hi this is Molly"1517    )15181519    headers_to_split_on = [1520        ("#", "Header 1"),1521        ("##", "Header 2"),1522        ("###", "Header 3"),1523        ("####", "Header 4"),1524    ]15251526    markdown_splitter = MarkdownHeaderTextSplitter(1527        headers_to_split_on=headers_to_split_on,1528    )1529    output = markdown_splitter.split_text(markdown_document)15301531    expected_output = [1532        Document(1533            page_content="Hi this is Jim  \nHi this is Joe",1534            metadata={"Header 1": "Foo", "Header 2": "Bar"},1535        ),1536        Document(1537            page_content="Hi this is Lance",1538            metadata={"Header 1": "Foo", "Header 2": "Bar", "Header 3": "Boo"},1539        ),1540        Document(1541            page_content="Hi this is John",1542            metadata={1543                "Header 1": "Foo",1544                "Header 2": "Bar",1545                "Header 3": "Boo",1546                "Header 4": "Bim",1547            },1548        ),1549        Document(1550            page_content="Hi this is Molly",1551            metadata={"Header 1": "Foo", "Header 2": "Baz"},1552        ),1553    ]15541555    assert output == expected_output155615571558def test_md_header_text_splitter_preserve_headers_1() -> None:1559    """Test markdown splitter by header: Preserve Headers."""1560    markdown_document = (1561        "# Foo\n\n"1562        "    ## Bat\n\n"1563        "Hi this is Jim\n\n"1564        "Hi Joe\n\n"1565        "## Baz\n\n"1566        "# Bar\n\n"1567        "This is Alice\n\n"1568        "This is Bob"1569    )1570    headers_to_split_on = [1571        ("#", "Header 1"),1572    ]1573    markdown_splitter = MarkdownHeaderTextSplitter(1574        headers_to_split_on=headers_to_split_on,1575        strip_headers=False,1576    )1577    output = markdown_splitter.split_text(markdown_document)1578    expected_output = [1579        Document(1580            page_content="# Foo  \n## Bat  \nHi this is Jim  \nHi Joe  \n## Baz",1581            metadata={"Header 1": "Foo"},1582        ),1583        Document(1584            page_content="# Bar  \nThis is Alice  \nThis is Bob",1585            metadata={"Header 1": "Bar"},1586        ),1587    ]1588    assert output == expected_output158915901591def test_md_header_text_splitter_preserve_headers_2() -> None:1592    """Test markdown splitter by header: Preserve Headers."""1593    markdown_document = (1594        "# Foo\n\n"1595        "    ## Bar\n\n"1596        "Hi this is Jim\n\n"1597        "Hi this is Joe\n\n"1598        "### Boo \n\n"1599        "Hi this is Lance\n\n"1600        "## Baz\n\n"1601        "Hi this is Molly\n"1602        "    ## Buz\n"1603        "# Bop"1604    )1605    headers_to_split_on = [1606        ("#", "Header 1"),1607        ("##", "Header 2"),1608        ("###", "Header 3"),1609    ]1610    markdown_splitter = MarkdownHeaderTextSplitter(1611        headers_to_split_on=headers_to_split_on,1612        strip_headers=False,1613    )1614    output = markdown_splitter.split_text(markdown_document)1615    expected_output = [1616        Document(1617            page_content="# Foo  \n## Bar  \nHi this is Jim  \nHi this is Joe",1618            metadata={"Header 1": "Foo", "Header 2": "Bar"},1619        ),1620        Document(1621            page_content="### Boo  \nHi this is Lance",1622            metadata={"Header 1": "Foo", "Header 2": "Bar", "Header 3": "Boo"},1623        ),1624        Document(1625            page_content="## Baz  \nHi this is Molly",1626            metadata={"Header 1": "Foo", "Header 2": "Baz"},1627        ),1628        Document(1629            page_content="## Buz",1630            metadata={"Header 1": "Foo", "Header 2": "Buz"},1631        ),1632        Document(page_content="# Bop", metadata={"Header 1": "Bop"}),1633    ]1634    assert output == expected_output163516361637@pytest.mark.parametrize("fence", [("```"), ("~~~")])1638def test_md_header_text_splitter_fenced_code_block(fence: str) -> None:1639    """Test markdown splitter by header: Fenced code block."""1640    markdown_document = (1641        f"# This is a Header\n\n{fence}\nfoo()\n# Not a header\nbar()\n{fence}"1642    )16431644    headers_to_split_on = [1645        ("#", "Header 1"),1646        ("##", "Header 2"),1647    ]16481649    markdown_splitter = MarkdownHeaderTextSplitter(1650        headers_to_split_on=headers_to_split_on,1651    )1652    output = markdown_splitter.split_text(markdown_document)16531654    expected_output = [1655        Document(1656            page_content=f"{fence}\nfoo()\n# Not a header\nbar()\n{fence}",1657            metadata={"Header 1": "This is a Header"},1658        ),1659    ]16601661    assert output == expected_output166216631664@pytest.mark.parametrize(("fence", "other_fence"), [("```", "~~~"), ("~~~", "```")])1665def test_md_header_text_splitter_fenced_code_block_interleaved(1666    fence: str, other_fence: str1667) -> None:1668    """Test markdown splitter by header: Interleaved fenced code block."""1669    markdown_document = (1670        "# This is a Header\n\n"1671        f"{fence}\n"1672        "foo\n"1673        "# Not a header\n"1674        f"{other_fence}\n"1675        "# Not a header\n"1676        f"{fence}"1677    )16781679    headers_to_split_on = [1680        ("#", "Header 1"),1681        ("##", "Header 2"),1682    ]16831684    markdown_splitter = MarkdownHeaderTextSplitter(1685        headers_to_split_on=headers_to_split_on,1686    )1687    output = markdown_splitter.split_text(markdown_document)16881689    expected_output = [1690        Document(1691            page_content=(1692                f"{fence}\nfoo\n# Not a header\n{other_fence}\n# Not a header\n{fence}"1693            ),1694            metadata={"Header 1": "This is a Header"},1695        ),1696    ]16971698    assert output == expected_output169917001701@pytest.mark.parametrize("characters", ["\ufeff"])1702def test_md_header_text_splitter_with_invisible_characters(characters: str) -> None:1703    """Test markdown splitter by header: Fenced code block."""1704    markdown_document = f"{characters}# Foo\n\nfoo()\n{characters}## Bar\n\nbar()"17051706    headers_to_split_on = [1707        ("#", "Header 1"),1708        ("##", "Header 2"),1709    ]17101711    markdown_splitter = MarkdownHeaderTextSplitter(1712        headers_to_split_on=headers_to_split_on,1713    )1714    output = markdown_splitter.split_text(markdown_document)17151716    expected_output = [1717        Document(1718            page_content="foo()",1719            metadata={"Header 1": "Foo"},1720        ),1721        Document(1722            page_content="bar()",1723            metadata={"Header 1": "Foo", "Header 2": "Bar"},1724        ),1725    ]17261727    assert output == expected_output172817291730def test_md_header_text_splitter_with_custom_headers() -> None:1731    """Test markdown splitter with custom header patterns like **Header**."""1732    markdown_document = """**Chapter 1**17331734This is the content for chapter 1.17351736***Section 1.1***17371738This is the content for section 1.1.17391740**Chapter 2**17411742This is the content for chapter 2.17431744***Section 2.1***17451746This is the content for section 2.1.1747"""17481749    headers_to_split_on = [1750        ("**", "Bold Header"),1751        ("***", "Bold Italic Header"),1752    ]17531754    custom_header_patterns = {1755        "**": 1,  # Level 1 headers1756        "***": 2,  # Level 2 headers1757    }1758    markdown_splitter = MarkdownHeaderTextSplitter(1759        headers_to_split_on=headers_to_split_on,1760        custom_header_patterns=custom_header_patterns,1761    )1762    output = markdown_splitter.split_text(markdown_document)17631764    expected_output = [1765        Document(1766            page_content="This is the content for chapter 1.",1767            metadata={"Bold Header": "Chapter 1"},1768        ),1769        Document(1770            page_content="This is the content for section 1.1.",1771            metadata={"Bold Header": "Chapter 1", "Bold Italic Header": "Section 1.1"},1772        ),1773        Document(1774            page_content="This is the content for chapter 2.",1775            metadata={"Bold Header": "Chapter 2"},1776        ),1777        Document(1778            page_content="This is the content for section 2.1.",1779            metadata={"Bold Header": "Chapter 2", "Bold Italic Header": "Section 2.1"},1780        ),1781    ]17821783    assert output == expected_output178417851786def test_md_header_text_splitter_mixed_headers() -> None:1787    """Test markdown splitter with both standard and custom headers."""1788    markdown_document = """# Standard Header 117891790Content under standard header.17911792**Custom Header 1**17931794Content under custom header.17951796## Standard Header 217971798Content under standard header 2.17991800***Custom Header 2***18011802Content under custom header 2.1803"""18041805    headers_to_split_on = [1806        ("#", "Header 1"),1807        ("##", "Header 2"),1808        ("**", "Bold Header"),1809        ("***", "Bold Italic Header"),1810    ]18111812    custom_header_patterns = {1813        "**": 1,  # Same level as #1814        "***": 2,  # Same level as ##1815    }18161817    markdown_splitter = MarkdownHeaderTextSplitter(1818        headers_to_split_on=headers_to_split_on,1819        custom_header_patterns=custom_header_patterns,1820    )1821    output = markdown_splitter.split_text(markdown_document)18221823    expected_output = [1824        Document(1825            page_content="Content under standard header.",1826            metadata={"Header 1": "Standard Header 1"},1827        ),1828        Document(1829            page_content="Content under custom header.",1830            metadata={"Bold Header": "Custom Header 1"},1831        ),1832        Document(1833            page_content="Content under standard header 2.",1834            metadata={1835                "Bold Header": "Custom Header 1",1836                "Header 2": "Standard Header 2",1837            },1838        ),1839        Document(1840            page_content="Content under custom header 2.",1841            metadata={1842                "Bold Header": "Custom Header 1",1843                "Bold Italic Header": "Custom Header 2",1844            },1845        ),1846    ]18471848    assert output == expected_output184918501851EXPERIMENTAL_MARKDOWN_DOCUMENT = (1852    "# My Header 1\n"1853    "Content for header 1\n"1854    "## Header 2\n"1855    "Content for header 2\n"1856    "### Header 3\n"1857    "Content for header 3\n"1858    "## Header 2 Again\n"1859    "This should be tagged with Header 1 and Header 2 Again\n"1860    "```python\n"1861    "def func_definition():\n"1862    "   print('Keep the whitespace consistent')\n"1863    "```\n"1864    "# Header 1 again\n"1865    "We should also split on the horizontal line\n"1866    "----\n"1867    "This will be a new doc but with the same header metadata\n\n"1868    "And it includes a new paragraph"1869)187018711872def test_experimental_markdown_syntax_text_splitter() -> None:1873    """Test experimental markdown syntax splitter."""1874    markdown_splitter = ExperimentalMarkdownSyntaxTextSplitter()1875    output = markdown_splitter.split_text(EXPERIMENTAL_MARKDOWN_DOCUMENT)18761877    expected_output = [1878        Document(1879            page_content="Content for header 1\n",1880            metadata={"Header 1": "My Header 1"},1881        ),1882        Document(1883            page_content="Content for header 2\n",1884            metadata={"Header 1": "My Header 1", "Header 2": "Header 2"},1885        ),1886        Document(1887            page_content="Content for header 3\n",1888            metadata={1889                "Header 1": "My Header 1",1890                "Header 2": "Header 2",1891                "Header 3": "Header 3",1892            },1893        ),1894        Document(1895            page_content="This should be tagged with Header 1 and Header 2 Again\n",1896            metadata={"Header 1": "My Header 1", "Header 2": "Header 2 Again"},1897        ),1898        Document(1899            page_content=(1900                "```python\ndef func_definition():\n   "1901                "print('Keep the whitespace consistent')\n```\n"1902            ),1903            metadata={1904                "Code": "python",1905                "Header 1": "My Header 1",1906                "Header 2": "Header 2 Again",1907            },1908        ),1909        Document(1910            page_content="We should also split on the horizontal line\n",1911            metadata={"Header 1": "Header 1 again"},1912        ),1913        Document(1914            page_content=(1915                "This will be a new doc but with the same header metadata\n\n"1916                "And it includes a new paragraph"1917            ),1918            metadata={"Header 1": "Header 1 again"},1919        ),1920    ]19211922    assert output == expected_output192319241925def test_experimental_markdown_syntax_text_splitter_header_configuration() -> None:1926    """Test experimental markdown syntax splitter."""1927    headers_to_split_on = [("#", "Encabezamiento 1")]19281929    markdown_splitter = ExperimentalMarkdownSyntaxTextSplitter(1930        headers_to_split_on=headers_to_split_on1931    )1932    output = markdown_splitter.split_text(EXPERIMENTAL_MARKDOWN_DOCUMENT)19331934    expected_output = [1935        Document(1936            page_content=(1937                "Content for header 1\n"1938                "## Header 2\n"1939                "Content for header 2\n"1940                "### Header 3\n"1941                "Content for header 3\n"1942                "## Header 2 Again\n"1943                "This should be tagged with Header 1 and Header 2 Again\n"1944            ),1945            metadata={"Encabezamiento 1": "My Header 1"},1946        ),1947        Document(1948            page_content=(1949                "```python\ndef func_definition():\n   "1950                "print('Keep the whitespace consistent')\n```\n"1951            ),1952            metadata={"Code": "python", "Encabezamiento 1": "My Header 1"},1953        ),1954        Document(1955            page_content="We should also split on the horizontal line\n",1956            metadata={"Encabezamiento 1": "Header 1 again"},1957        ),1958        Document(1959            page_content=(1960                "This will be a new doc but with the same header metadata\n\n"1961                "And it includes a new paragraph"1962            ),1963            metadata={"Encabezamiento 1": "Header 1 again"},1964        ),1965    ]19661967    assert output == expected_output196819691970def test_experimental_markdown_syntax_text_splitter_with_headers() -> None:1971    """Test experimental markdown syntax splitter."""1972    markdown_splitter = ExperimentalMarkdownSyntaxTextSplitter(strip_headers=False)1973    output = markdown_splitter.split_text(EXPERIMENTAL_MARKDOWN_DOCUMENT)19741975    expected_output = [1976        Document(1977            page_content="# My Header 1\nContent for header 1\n",1978            metadata={"Header 1": "My Header 1"},1979        ),1980        Document(1981            page_content="## Header 2\nContent for header 2\n",1982            metadata={"Header 1": "My Header 1", "Header 2": "Header 2"},1983        ),1984        Document(1985            page_content="### Header 3\nContent for header 3\n",1986            metadata={1987                "Header 1": "My Header 1",1988                "Header 2": "Header 2",1989                "Header 3": "Header 3",1990            },1991        ),1992        Document(1993            page_content=(1994                "## Header 2 Again\n"1995                "This should be tagged with Header 1 and Header 2 Again\n"1996            ),1997            metadata={"Header 1": "My Header 1", "Header 2": "Header 2 Again"},1998        ),1999        Document(2000            page_content=(

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.