about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/orgparse/tests
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/orgparse/tests')
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.org17
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.py33
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.org29
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.py73
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.org31
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.py44
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.org5
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.py13
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.org7
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.py11
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.org9
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.py23
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/data/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/test_data.py154
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/test_date.py42
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/test_hugedata.py30
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/test_misc.py299
-rw-r--r--.venv/lib/python3.12/site-packages/orgparse/tests/test_rich.py89
19 files changed, 909 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/__init__.py b/.venv/lib/python3.12/site-packages/orgparse/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.org b/.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.org
new file mode 100644
index 00000000..3a373334
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.org
@@ -0,0 +1,17 @@
+#+STARTUP: hidestars
+#+SEQ_TODO: TODO1 TODO2 TODO3 TODO4
+
+* TODO1 Heading 0 						       :TAG1:
+** TODO2 Heading 1 						       :TAG2:
+*** TODO3 Heading 2 						       :TAG3:
+**** TODO4 Heading 3 						       :TAG4:
+     CLOSED: [2010-08-06 Fri 21:45]
+** Heading 4
+** Heading 5
+* Heading 6 							       :TAG2:
+** Heading 7 							       :TAG2:
+*** Heading 8
+***** Heading 9 						  :TAG3:TAG4:
+**** Heading 10 						       :TAG1:
+** Heading 11
+* Heading 12
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.py b/.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.py
new file mode 100644
index 00000000..c0b23d1d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/00_simple.py
@@ -0,0 +1,33 @@
+from typing import Any, Dict, Set
+
+
+def nodedict(i, level, todo=None, shallow_tags=set([]), tags=set([])) -> Dict[str, Any]:
+    return dict(
+        heading="Heading {0}".format(i),
+        level=level,
+        todo=todo,
+        shallow_tags=shallow_tags,
+        tags=tags,
+    )
+
+
+def tags(nums) -> Set[str]:
+    return set(map('TAG{0}'.format, nums))
+
+
+data = [
+    nodedict(i, *vals) for (i, vals) in enumerate([  # type: ignore[misc]
+        [1, 'TODO1', tags([1])   , tags(range(1, 2))],
+        [2, 'TODO2', tags([2])   , tags(range(1, 3))],
+        [3, 'TODO3', tags([3])   , tags(range(1, 4))],
+        [4, 'TODO4', tags([4])   , tags(range(1, 5))],
+        [2, None   , tags([])    , tags([1])        ],
+        [2, None   , tags([])    , tags([1])        ],
+        [1, None   , tags([2])   , tags([2])        ],
+        [2, None   , tags([2])   , tags([2])        ],
+        [3, None   , tags([])    , tags([2])        ],
+        [5, None   , tags([3, 4]), tags([2, 3, 4])  ],
+        [4, None   , tags([1])   , tags([1, 2])     ],
+        [2, None   , tags([])    , tags([2])        ],
+        [1],
+    ])]
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.org b/.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.org
new file mode 100644
index 00000000..99e202b5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.org
@@ -0,0 +1,29 @@
+#+STARTUP: hidestars
+* DONE [#A] A node with a lot of attributes
+  SCHEDULED: <2010-08-06 Fri> DEADLINE: <2010-08-10 Tue> CLOSED: [2010-08-08 Sun 18:00]
+  CLOCK: [2010-08-08 Sun 17:40]--[2010-08-08 Sun 17:50] =>  0:10
+  CLOCK: [2010-08-08 Sun 17:00]--[2010-08-08 Sun 17:30] =>  0:30
+  :PROPERTIES:
+  :Effort: 1:10
+  :END:
+  - <2010-08-16 Mon> DateList
+  - <2010-08-07 Sat>--<2010-08-08 Sun>
+  - <2010-08-09 Mon 00:30>--<2010-08-10 Tue 13:20> RangeList
+  - <2019-08-10 Sat 16:30-17:30> TimeRange
+* A node without any attributed
+* DONE [#A] A node with a lot of attributes
+  SCHEDULED: <2010-08-06 Fri> DEADLINE: <2010-08-10 Tue> CLOSED: [2010-08-08 Sun 18:00]
+  CLOCK: [2010-08-08 Sun 17:40]--[2010-08-08 Sun 17:50] =>  0:10
+  CLOCK: [2010-08-08 Sun 17:00]--[2010-08-08 Sun 17:30] =>  0:30
+  :PROPERTIES:
+  :Effort: 1:10
+  :END:
+  - <2010-08-16 Mon> DateList
+  - <2010-08-07 Sat>--<2010-08-08 Sun>
+  - <2010-08-09 Mon 00:30>--<2010-08-10 Tue 13:20> RangeList
+  - <2019-08-10 Sat 16:30-17:30> TimeRange
+* range in deadline
+DEADLINE: <2019-09-06 Fri 10:00--11:20>
+  body
+* node with a second line but no date
+body
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.py b/.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.py
new file mode 100644
index 00000000..d4555dea
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/01_attributes.py
@@ -0,0 +1,73 @@
+from typing import Dict, Any
+
+from orgparse.date import (
+    OrgDate, OrgDateScheduled, OrgDateDeadline, OrgDateClosed,
+    OrgDateClock,
+)
+
+Raw = Dict[str, Any]
+
+node1: Raw = dict(
+    heading="A node with a lot of attributes",
+    priority='A',
+    scheduled=OrgDateScheduled((2010, 8, 6)),
+    deadline=OrgDateDeadline((2010, 8, 10)),
+    closed=OrgDateClosed((2010, 8, 8, 18, 0)),
+    clock=[
+        OrgDateClock((2010, 8, 8, 17, 40), (2010, 8, 8, 17, 50), 10),
+        OrgDateClock((2010, 8, 8, 17, 00), (2010, 8, 8, 17, 30), 30),
+        ],
+    properties=dict(Effort=70),
+    datelist=[OrgDate((2010, 8, 16))],
+    rangelist=[
+        OrgDate((2010, 8, 7), (2010, 8, 8)),
+        OrgDate((2010, 8, 9, 0, 30), (2010, 8, 10, 13, 20)),
+        OrgDate((2019, 8, 10, 16, 30, 0), (2019, 8, 10, 17, 30, 0)),
+        ],
+    body="""\
+  - <2010-08-16 Mon> DateList
+  - <2010-08-07 Sat>--<2010-08-08 Sun>
+  - <2010-08-09 Mon 00:30>--<2010-08-10 Tue 13:20> RangeList
+  - <2019-08-10 Sat 16:30-17:30> TimeRange"""
+)
+
+node2: Raw = dict(
+    heading="A node without any attributed",
+    priority=None,
+    scheduled=OrgDateScheduled(None),
+    deadline=OrgDateDeadline(None),
+    closed=OrgDateClosed(None),
+    clock=[],
+    properties={},
+    datelist=[],
+    rangelist=[],
+    body="",
+)
+
+node3: Raw = dict(
+    heading="range in deadline",
+    priority=None,
+    scheduled=OrgDateScheduled(None),
+    deadline=OrgDateDeadline((2019, 9, 6, 10, 0), (2019, 9, 6, 11, 20)),
+    closed=OrgDateClosed(None),
+    clock=[],
+    properties={},
+    datelist=[],
+    rangelist=[],
+    body="  body",
+)
+
+node4: Raw = dict(
+    heading="node with a second line but no date",
+    priority=None,
+    scheduled=OrgDateScheduled(None),
+    deadline=OrgDateDeadline(None),
+    closed=OrgDateClosed(None),
+    clock=[],
+    properties={},
+    datelist=[],
+    rangelist=[],
+    body="body",
+)
+
+data = [node1, node2, node1, node3, node4]
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.org b/.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.org
new file mode 100644
index 00000000..5f4b6fb2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.org
@@ -0,0 +1,31 @@
+* G0-H1
+
+* G1-H1
+** G1-H2
+*** G1-H3
+
+* G2-H1
+*** G2-H2
+** G2-H3
+
+* G3-H1
+**** G3-H2
+** G3-H3
+
+* G4-H1
+**** G4-H2
+*** G4-H3
+** G4-H4
+
+* G5-H1
+** G5-H2
+*** G5-H3
+** G5-H4
+
+* G6-H1
+** G6-H2
+**** G6-H3
+*** G6-H4
+** G6-H5
+
+* G7-H1
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.py b/.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.py
new file mode 100644
index 00000000..80a8e779
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/02_tree_struct.py
@@ -0,0 +1,44 @@
+from typing import Any, Dict
+
+
+def nodedict(parent, children=[], previous=None, next=None) -> Dict[str, Any]:
+    return dict(parent_heading=parent,
+                children_heading=children,
+                previous_same_level_heading=previous,
+                next_same_level_heading=next)
+
+
+data = [nodedict(*args) for args in [
+    # G0
+    (None, [], None, 'G1-H1'),
+    # G1
+    (None, ['G1-H2'], 'G0-H1', 'G2-H1'),
+    ('G1-H1', ['G1-H3']),
+    ('G1-H2',),
+    # G2
+    (None, ['G2-H2', 'G2-H3'], 'G1-H1', 'G3-H1'),
+    ('G2-H1',),
+    ('G2-H1',),
+    # G3
+    (None, ['G3-H2', 'G3-H3'], 'G2-H1', 'G4-H1'),
+    ('G3-H1',),
+    ('G3-H1',),
+    # G4
+    (None, ['G4-H2', 'G4-H3', 'G4-H4'], 'G3-H1', 'G5-H1'),
+    ('G4-H1',),
+    ('G4-H1',),
+    ('G4-H1',),
+    # G5
+    (None, ['G5-H2', 'G5-H4'], 'G4-H1', 'G6-H1'),
+    ('G5-H1', ['G5-H3'], None, 'G5-H4'),
+    ('G5-H2',),
+    ('G5-H1', [], 'G5-H2'),
+    # G6
+    (None, ['G6-H2', 'G6-H5'], 'G5-H1', 'G7-H1'),
+    ('G6-H1', ['G6-H3', 'G6-H4'], None, 'G6-H5'),
+    ('G6-H2',),
+    ('G6-H2',),
+    ('G6-H1', [], 'G6-H2'),
+    # G7
+    (None, [], 'G6-H1'),
+]]
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.org b/.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.org
new file mode 100644
index 00000000..9e9c79b5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.org
@@ -0,0 +1,5 @@
+* TODO Pay the rent
+  DEADLINE: <2005-10-01 Sat +1m>
+  - State "DONE"  from "TODO"  [2005-09-01 Thu 16:10]
+  - State "DONE"  from "TODO"  [2005-08-01 Mon 19:44]
+  - State "DONE"  from "TODO"  [2005-07-01 Fri 17:27]
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.py b/.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.py
new file mode 100644
index 00000000..18cfe121
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/03_repeated_tasks.py
@@ -0,0 +1,13 @@
+from orgparse.date import OrgDateRepeatedTask, OrgDateDeadline
+
+
+data = [dict(
+    heading='Pay the rent',
+    todo='TODO',
+    deadline=OrgDateDeadline((2005, 10, 1)),
+    repeated_tasks=[
+        OrgDateRepeatedTask((2005, 9, 1, 16, 10, 0), 'TODO', 'DONE'),
+        OrgDateRepeatedTask((2005, 8, 1, 19, 44, 0), 'TODO', 'DONE'),
+        OrgDateRepeatedTask((2005, 7, 1, 17, 27, 0), 'TODO', 'DONE'),
+    ]
+)]
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.org b/.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.org
new file mode 100644
index 00000000..e89ec262
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.org
@@ -0,0 +1,7 @@
+* LOGBOOK drawer test
+  :LOGBOOK:
+  CLOCK: [2012-10-26 Fri 16:01]
+  CLOCK: [2012-10-26 Fri 14:50]--[2012-10-26 Fri 15:00] =>  0:10
+  CLOCK: [2012-10-26 Fri 14:30]--[2012-10-26 Fri 14:40] =>  0:10
+  CLOCK: [2012-10-26 Fri 14:10]--[2012-10-26 Fri 14:20] =>  0:10
+  :END:
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.py b/.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.py
new file mode 100644
index 00000000..457c5fa1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/04_logbook.py
@@ -0,0 +1,11 @@
+from orgparse.date import OrgDateClock
+
+data = [dict(
+    heading='LOGBOOK drawer test',
+    clock=[
+        OrgDateClock((2012, 10, 26, 16, 1)),
+        OrgDateClock((2012, 10, 26, 14, 50), (2012, 10, 26, 15, 00)),
+        OrgDateClock((2012, 10, 26, 14, 30), (2012, 10, 26, 14, 40)),
+        OrgDateClock((2012, 10, 26, 14, 10), (2012, 10, 26, 14, 20)),
+    ]
+)]
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.org b/.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.org
new file mode 100644
index 00000000..651d7e09
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.org
@@ -0,0 +1,9 @@
+* Node 0                                                                :tag:
+* Node 1                                                               :@tag:
+* Node 2                                                          :tag1:tag2:
+* Node 3                                                                  :_:
+* Node 4                                                                  :@:
+* Node 5                                                                 :@_:
+* Node 6                                                              :_tag_:
+* Heading: :with:colon:                                                 :tag:
+* unicode                                                      :ёж:tag:háček:
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.py b/.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.py
new file mode 100644
index 00000000..52aee638
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/05_tags.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+def nodedict(i, tags):
+    return dict(
+        heading="Node {0}".format(i),
+        tags=set(tags),
+    )
+
+
+data = [
+    nodedict(i, *vals) for (i, vals) in enumerate([
+        [["tag"]],
+        [["@tag"]],
+        [["tag1", "tag2"]],
+        [["_"]],
+        [["@"]],
+        [["@_"]],
+        [["_tag_"]],
+    ])] + [
+        dict(heading='Heading: :with:colon:', tags=set(["tag"])),
+    ] + [
+        dict(heading='unicode', tags=set(['ёж', 'tag', 'háček'])),
+    ]
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/data/__init__.py b/.venv/lib/python3.12/site-packages/orgparse/tests/data/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/data/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/test_data.py b/.venv/lib/python3.12/site-packages/orgparse/tests/test_data.py
new file mode 100644
index 00000000..f315878e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/test_data.py
@@ -0,0 +1,154 @@
+from glob import glob
+import os
+from pathlib import Path
+import pickle
+
+from .. import load, loads
+
+import pytest
+
+DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+
+
+def load_data(path):
+    """Load data from python file"""
+    ns = {}  # type: ignore
+    # read_bytes() and compile hackery to avoid encoding issues (e.g. see 05_tags)
+    exec(compile(Path(path).read_bytes(), path, 'exec'), ns)
+    return ns['data']
+
+
+def value_from_data_key(node, key):
+    """
+    Helper function for check_data. Get value from Orgnode by key.
+    """
+    if key == 'tags_inher':
+        return node.tags
+    elif key == 'children_heading':
+        return [c.heading for c in node.children]
+    elif key in ('parent_heading',
+                 'previous_same_level_heading',
+                 'next_same_level_heading',
+                 ):
+        othernode = getattr(node, key.rsplit('_', 1)[0])
+        if othernode and not othernode.is_root():
+            return othernode.heading
+        else:
+            return
+    else:
+        return getattr(node, key)
+
+
+def data_path(dataname, ext):
+    return os.path.join(DATADIR, '{0}.{1}'.format(dataname, ext))
+
+
+def get_datanames():
+    for oname in sorted(glob(os.path.join(DATADIR, '*.org'))):
+        yield os.path.splitext(os.path.basename(oname))[0]
+
+
+@pytest.mark.parametrize('dataname', get_datanames())
+def test_data(dataname):
+    """
+    Compare parsed data from 'data/*.org' and its correct answer 'data/*.py'
+    """
+    oname = data_path(dataname, "org")
+    data = load_data(data_path(dataname, "py"))
+    root = load(oname)
+
+    for (i, (node, kwds)) in enumerate(zip(root[1:], data)):
+        for key in kwds:
+            val = value_from_data_key(node, key)
+            assert kwds[key] == val, 'check value of {0}-th node of key "{1}" from "{2}".\n\nParsed:\n{3}\n\nReal:\n{4}'.format(i, key, dataname, val, kwds[key])
+            assert type(kwds[key]) == type(val), 'check type of {0}-th node of key "{1}" from "{2}".\n\nParsed:\n{3}\n\nReal:\n{4}'.format(i, key, dataname, type(val), type(kwds[key]))
+
+    assert root.env.filename == oname
+
+
+@pytest.mark.parametrize('dataname', get_datanames())
+def test_picklable(dataname):
+    oname = data_path(dataname, "org")
+    root = load(oname)
+    pickle.dumps(root)
+
+
+
+def test_iter_node():
+    root = loads("""
+* H1
+** H2
+*** H3
+* H4
+** H5
+""")
+    node = root[1]
+    assert node.heading == 'H1'
+
+    by_iter = [n.heading for n in node]
+    assert by_iter == ['H1', 'H2', 'H3']
+
+
+def test_commented_headings_do_not_appear_as_children():
+    root = loads("""\
+* H1
+#** H2
+** H3
+#* H4
+#** H5
+* H6
+""")
+    assert root.linenumber == 1
+    top_level = root.children
+    assert len(top_level) == 2
+
+    h1 = top_level[0]
+    assert h1.heading == "H1"
+    assert h1.get_body() == "#** H2"
+    assert h1.linenumber == 1
+
+    [h3] = h1.children
+    assert h3.heading == "H3"
+    assert h3.get_body() == "#* H4\n#** H5"
+    assert h3.linenumber == 3
+
+    h6 = top_level[1]
+    assert h6.heading == "H6"
+    assert len(h6.children) == 0
+    assert h6.linenumber == 6
+
+
+def test_commented_clock_entries_are_ignored_by_node_clock():
+    root = loads("""\
+* Heading
+# * Floss
+# SCHEDULED: <2019-06-22 Sat 08:30 .+1w>
+# :LOGBOOK:
+# CLOCK: [2019-06-04 Tue 16:00]--[2019-06-04 Tue 17:00] =>  1:00
+# :END:
+""")
+    [node] = root.children[0]
+    assert node.heading == "Heading"
+    assert node.clock == []
+
+
+def test_commented_scheduled_marker_is_ignored_by_node_scheduled():
+    root = loads("""\
+* Heading
+# SCHEDULED: <2019-06-22 Sat 08:30 .+1w>
+""")
+    [node] = root.children[0]
+    assert node.heading == "Heading"
+    assert node.scheduled.start is None
+
+
+def test_commented_property_is_ignored_by_node_get_property():
+    root = loads("""\
+* Heading
+# :PROPERTIES:
+# :PROPER-TEA: backup
+# :END:
+""")
+    [node] = root.children[0]
+    assert node.heading == "Heading"
+    assert node.get_property("PROPER-TEA") is None
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/test_date.py b/.venv/lib/python3.12/site-packages/orgparse/tests/test_date.py
new file mode 100644
index 00000000..0f39575b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/test_date.py
@@ -0,0 +1,42 @@
+from orgparse.date import OrgDate, OrgDateScheduled, OrgDateDeadline, OrgDateClock, OrgDateClosed
+import datetime
+
+
+def test_date_as_string() -> None:
+
+    testdate = datetime.date(2021, 9, 3)
+    testdate2 = datetime.date(2021, 9, 5)
+    testdatetime = datetime.datetime(2021, 9, 3, 16, 19, 13)
+    testdatetime2 = datetime.datetime(2021, 9, 3, 17, 0, 1)
+    testdatetime_nextday = datetime.datetime(2021, 9, 4, 0, 2, 1)
+
+    assert str(OrgDate(testdate)) == "<2021-09-03 Fri>"
+    assert str(OrgDate(testdatetime)) == "<2021-09-03 Fri 16:19>"
+    assert str(OrgDate(testdate, active=False)) == "[2021-09-03 Fri]"
+    assert str(OrgDate(testdatetime, active=False)) == "[2021-09-03 Fri 16:19]"
+
+    assert str(OrgDate(testdate, testdate2)) == "<2021-09-03 Fri>--<2021-09-05 Sun>"
+    assert str(OrgDate(testdate, testdate2)) == "<2021-09-03 Fri>--<2021-09-05 Sun>"
+    assert str(OrgDate(testdatetime, testdatetime2)) == "<2021-09-03 Fri 16:19--17:00>"
+    assert str(OrgDate(testdate, testdate2, active=False)) == "[2021-09-03 Fri]--[2021-09-05 Sun]"
+    assert str(OrgDate(testdate, testdate2, active=False)) == "[2021-09-03 Fri]--[2021-09-05 Sun]"
+    assert str(OrgDate(testdatetime, testdatetime2, active=False)) == "[2021-09-03 Fri 16:19--17:00]"
+
+    assert str(OrgDateScheduled(testdate)) == "<2021-09-03 Fri>"
+    assert str(OrgDateScheduled(testdatetime)) == "<2021-09-03 Fri 16:19>"
+    assert str(OrgDateDeadline(testdate)) == "<2021-09-03 Fri>"
+    assert str(OrgDateDeadline(testdatetime)) == "<2021-09-03 Fri 16:19>"
+    assert str(OrgDateClosed(testdate)) == "[2021-09-03 Fri]"
+    assert str(OrgDateClosed(testdatetime)) == "[2021-09-03 Fri 16:19]"
+
+    assert str(OrgDateClock(testdatetime, testdatetime2)) == "[2021-09-03 Fri 16:19]--[2021-09-03 Fri 17:00]"
+    assert str(OrgDateClock(testdatetime, testdatetime_nextday)) == "[2021-09-03 Fri 16:19]--[2021-09-04 Sat 00:02]"
+    assert str(OrgDateClock(testdatetime)) == "[2021-09-03 Fri 16:19]"
+
+
+def test_date_as_datetime() -> None:
+    testdate = (2021, 9, 3)
+    testdatetime = (2021, 9, 3, 16, 19, 13)
+
+    assert OrgDate._as_datetime(datetime.date(*testdate)) == datetime.datetime(*testdate, 0, 0, 0)
+    assert OrgDate._as_datetime(datetime.datetime(*testdatetime)) == datetime.datetime(*testdatetime)
\ No newline at end of file
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/test_hugedata.py b/.venv/lib/python3.12/site-packages/orgparse/tests/test_hugedata.py
new file mode 100644
index 00000000..f7248ca7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/test_hugedata.py
@@ -0,0 +1,30 @@
+import pickle
+
+from .. import loadi
+
+
+def generate_org_lines(num_top_nodes, depth=3, nodes_per_level=1, _level=1):
+    if depth == 0:
+        return
+    for i in range(num_top_nodes):
+        yield ("*" * _level) + ' {0}-th heading of level {1}'.format(i, _level)
+        for child in generate_org_lines(
+                nodes_per_level, depth - 1, nodes_per_level, _level + 1):
+            yield child
+
+
+def num_generate_org_lines(num_top_nodes, depth=3, nodes_per_level=1):
+    if depth == 0:
+        return 0
+    return num_top_nodes * (
+        1 + num_generate_org_lines(
+            nodes_per_level, depth - 1, nodes_per_level))
+
+
+def test_picklable() -> None:
+    num = 1000
+    depth = 3
+    nodes_per_level = 1
+    root = loadi(generate_org_lines(num, depth, nodes_per_level))
+    assert sum(1 for _ in root) == num_generate_org_lines(num, depth, nodes_per_level) + 1
+    pickle.dumps(root)  # should not fail
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/test_misc.py b/.venv/lib/python3.12/site-packages/orgparse/tests/test_misc.py
new file mode 100644
index 00000000..4cd73e4c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/test_misc.py
@@ -0,0 +1,299 @@
+from .. import load, loads
+from ..node import OrgEnv
+from orgparse.date import OrgDate
+
+
+def test_empty_heading() -> None:
+    root = loads('''
+* TODO :sometag:
+  has no heading but still a todo?
+  it's a bit unclear, but seems to be highligted by emacs..
+''')
+    [h] = root.children
+    assert h.todo == 'TODO'
+    assert h.heading == ''
+    assert h.tags == {'sometag'}
+
+
+def test_root() -> None:
+    root = loads('''
+#+STARTUP: hidestars
+Whatever
+# comment
+* heading 1
+    '''.strip())
+    assert len(root.children) == 1
+    # todo not sure if should strip special comments??
+    assert root.body.endswith('Whatever\n# comment')
+    assert root.heading == ''
+
+
+def test_stars():
+    # https://github.com/karlicoss/orgparse/issues/7#issuecomment-533732660
+    root = loads("""
+* Heading with text (A)
+
+The following line is not a heading, because it begins with a
+star but has no spaces afterward, just a newline:
+
+*
+
+** Subheading with text (A1)
+
+*this_is_just*
+
+ *some_bold_text*
+
+This subheading is a child of (A).
+
+The next heading has no text, but it does have a space after
+the star, so it's a heading:
+
+* 
+
+This text is under the "anonymous" heading above, which would be (B).
+
+** Subheading with text (B1)
+
+This subheading is a child of the "anonymous" heading (B), not of heading (A).
+    """)
+    [h1, h2] = root.children
+    assert h1.heading == 'Heading with text (A)'
+    assert h2.heading == ''
+
+
+def test_parse_custom_todo_keys():
+    todo_keys = ['TODO', 'CUSTOM1', 'ANOTHER_KEYWORD']
+    done_keys = ['DONE', 'A']
+    filename = '<string>'  # default for loads
+    content = """
+* TODO Heading with a default todo keyword
+
+* DONE Heading with a default done keyword
+
+* CUSTOM1 Heading with a custom todo keyword
+
+* ANOTHER_KEYWORD Heading with a long custom todo keyword
+
+* A Heading with a short custom done keyword
+    """
+
+    env = OrgEnv(todos=todo_keys, dones=done_keys, filename=filename)
+    root = loads(content, env=env)
+
+    assert root.env.all_todo_keys == ['TODO', 'CUSTOM1',
+                                      'ANOTHER_KEYWORD', 'DONE', 'A']
+    assert len(root.children) == 5
+    assert root.children[0].todo == 'TODO'
+    assert root.children[1].todo == 'DONE'
+    assert root.children[2].todo == 'CUSTOM1'
+    assert root.children[3].todo == 'ANOTHER_KEYWORD'
+    assert root.children[4].todo == 'A'
+
+
+def test_add_custom_todo_keys():
+    todo_keys = ['CUSTOM_TODO']
+    done_keys = ['CUSTOM_DONE']
+    filename = '<string>'  # default for loads
+    content = """#+TODO: COMMENT_TODO | COMMENT_DONE 
+    """
+
+    env = OrgEnv(filename=filename)
+    env.add_todo_keys(todos=todo_keys, dones=done_keys)
+
+    # check that only the custom keys are know before parsing
+    assert env.all_todo_keys == ['CUSTOM_TODO', 'CUSTOM_DONE']
+
+    # after parsing, all keys are set
+    root = loads(content, filename, env)
+    assert root.env.all_todo_keys == ['CUSTOM_TODO', 'COMMENT_TODO',
+                                      'CUSTOM_DONE', 'COMMENT_DONE']
+
+def test_get_file_property() -> None:
+     content = """#+TITLE:   Test: title
+     * Node 1
+     test 1
+     * Node 2
+     test 2
+     """
+
+     # after parsing, all keys are set
+     root = loads(content)
+     assert root.get_file_property('Nosuchproperty') is None
+     assert root.get_file_property_list('TITLE') == ['Test: title']
+     # also it's case insensitive
+     assert root.get_file_property('title') == 'Test: title'
+     assert root.get_file_property_list('Nosuchproperty') == []
+
+
+def test_get_file_property_multivalued() -> None:
+     content = """ #+TITLE: Test
+     #+OTHER: Test title
+     #+title: alternate title
+
+     * Node 1
+     test 1
+     * Node 2
+     test 2
+     """
+
+     # after parsing, all keys are set
+     root = loads(content)
+     import pytest
+
+     assert root.get_file_property_list('TITLE') == ['Test', 'alternate title']
+     with pytest.raises(RuntimeError):
+         # raises because there are multiple of them
+         root.get_file_property('TITLE')
+
+
+def test_filetags_are_tags() -> None:
+    content = '''
+#+FILETAGS: :f1:f2:
+
+* heading :h1:
+** child :f2:
+    '''.strip()
+    root = loads(content)
+    # breakpoint()
+    assert root.tags == {'f1', 'f2'}
+    child = root.children[0].children[0]
+    assert child.tags == {'f1', 'f2', 'h1'}
+
+
+def test_load_filelike() -> None:
+    import io
+    stream = io.StringIO('''
+* heading1
+* heading 2
+''')
+    root = load(stream)
+    assert len(root.children) == 2
+    assert root.env.filename == '<file-like>'
+
+
+def test_level_0_properties() -> None:
+    content = '''
+foo bar
+
+:PROPERTIES:
+:PROP-FOO: Bar
+:PROP-BAR: Bar bar
+:END:
+
+* heading :h1:
+:PROPERTIES:
+:HEADING-PROP: foo
+:END:
+** child :f2:
+    '''.strip()
+    root = loads(content)
+    assert root.get_property('PROP-FOO') == 'Bar'
+    assert root.get_property('PROP-BAR') == 'Bar bar'
+    assert root.get_property('PROP-INVALID') is None
+    assert root.get_property('HEADING-PROP') is None
+    assert root.children[0].get_property('HEADING-PROP') == 'foo'
+
+
+def test_level_0_timestamps() -> None:
+    content = '''
+foo bar
+
+  - <2010-08-16 Mon> DateList
+  - <2010-08-07 Sat>--<2010-08-08 Sun>
+  - <2010-08-09 Mon 00:30>--<2010-08-10 Tue 13:20> RangeList
+  - <2019-08-10 Sat 16:30-17:30> TimeRange"
+
+* heading :h1:
+** child :f2:
+    '''.strip()
+    root = loads(content)
+    assert root.datelist == [OrgDate((2010, 8, 16))]
+    assert root.rangelist == [
+        OrgDate((2010, 8, 7), (2010, 8, 8)),
+        OrgDate((2010, 8, 9, 0, 30), (2010, 8, 10, 13, 20)),
+        OrgDate((2019, 8, 10, 16, 30, 0), (2019, 8, 10, 17, 30, 0)),
+    ]
+
+def test_date_with_cookies() -> None:
+    testcases = [
+        ('<2010-06-21 Mon +1y>',
+         "OrgDate((2010, 6, 21), None, True, ('+', 1, 'y'))"),
+        ('<2005-10-01 Sat +1m>',
+         "OrgDate((2005, 10, 1), None, True, ('+', 1, 'm'))"),
+        ('<2005-10-01 Sat +1m -3d>',
+         "OrgDate((2005, 10, 1), None, True, ('+', 1, 'm'), ('-', 3, 'd'))"),
+        ('<2005-10-01 Sat -3d>',
+         "OrgDate((2005, 10, 1), None, True, None, ('-', 3, 'd'))"),
+        ('<2008-02-10 Sun ++1w>',
+         "OrgDate((2008, 2, 10), None, True, ('++', 1, 'w'))"),
+        ('<2008-02-08 Fri 20:00 ++1d>',
+         "OrgDate((2008, 2, 8, 20, 0, 0), None, True, ('++', 1, 'd'))"),
+        ('<2019-04-05 Fri 08:00 .+1h>',
+         "OrgDate((2019, 4, 5, 8, 0, 0), None, True, ('.+', 1, 'h'))"),
+        ('[2019-04-05 Fri 08:00 .+1h]',
+         "OrgDate((2019, 4, 5, 8, 0, 0), None, False, ('.+', 1, 'h'))"),
+        ('<2007-05-16 Wed 12:30 +1w>',
+         "OrgDate((2007, 5, 16, 12, 30, 0), None, True, ('+', 1, 'w'))"),
+    ]
+    for (input, expected) in testcases:
+        root = loads(input)
+        output = root[0].datelist[0]
+        assert str(output) == input
+        assert repr(output) == expected
+    testcases = [
+        ('<2006-11-02 Thu 20:00-22:00 +1w>',
+         "OrgDate((2006, 11, 2, 20, 0, 0), (2006, 11, 2, 22, 0, 0), True, ('+', 1, 'w'))"),
+        ('<2006-11-02 Thu 20:00--22:00 +1w>',
+         "OrgDate((2006, 11, 2, 20, 0, 0), (2006, 11, 2, 22, 0, 0), True, ('+', 1, 'w'))"),
+    ]
+    for (input, expected) in testcases:
+        root = loads(input)
+        output = root[0].rangelist[0]
+        assert str(output) == "<2006-11-02 Thu 20:00--22:00 +1w>"
+        assert repr(output) == expected
+    # DEADLINE and SCHEDULED
+    testcases2 = [
+        ('* TODO Pay the rent\nDEADLINE: <2005-10-01 Sat +1m>',
+         "<2005-10-01 Sat +1m>",
+         "OrgDateDeadline((2005, 10, 1), None, True, ('+', 1, 'm'))"),
+        ('* TODO Pay the rent\nDEADLINE: <2005-10-01 Sat +1m -3d>',
+         "<2005-10-01 Sat +1m -3d>",
+         "OrgDateDeadline((2005, 10, 1), None, True, ('+', 1, 'm'), ('-', 3, 'd'))"),
+        ('* TODO Pay the rent\nDEADLINE: <2005-10-01 Sat -3d>',
+         "<2005-10-01 Sat -3d>",
+         "OrgDateDeadline((2005, 10, 1), None, True, None, ('-', 3, 'd'))"),
+        ('* TODO Pay the rent\nDEADLINE: <2005-10-01 Sat ++1m>',
+         "<2005-10-01 Sat ++1m>",
+         "OrgDateDeadline((2005, 10, 1), None, True, ('++', 1, 'm'))"),
+        ('* TODO Pay the rent\nDEADLINE: <2005-10-01 Sat .+1m>',
+         "<2005-10-01 Sat .+1m>",
+         "OrgDateDeadline((2005, 10, 1), None, True, ('.+', 1, 'm'))"),
+    ]
+    for (input, expected_str, expected_repr) in testcases2:
+        root = loads(input)
+        output = root[1].deadline
+        assert str(output) == expected_str
+        assert repr(output) == expected_repr
+    testcases2 = [
+        ('* TODO Pay the rent\nSCHEDULED: <2005-10-01 Sat +1m>',
+         "<2005-10-01 Sat +1m>",
+         "OrgDateScheduled((2005, 10, 1), None, True, ('+', 1, 'm'))"),
+        ('* TODO Pay the rent\nSCHEDULED: <2005-10-01 Sat +1m -3d>',
+         "<2005-10-01 Sat +1m -3d>",
+         "OrgDateScheduled((2005, 10, 1), None, True, ('+', 1, 'm'), ('-', 3, 'd'))"),
+        ('* TODO Pay the rent\nSCHEDULED: <2005-10-01 Sat -3d>',
+         "<2005-10-01 Sat -3d>",
+         "OrgDateScheduled((2005, 10, 1), None, True, None, ('-', 3, 'd'))"),
+        ('* TODO Pay the rent\nSCHEDULED: <2005-10-01 Sat ++1m>',
+         "<2005-10-01 Sat ++1m>",
+         "OrgDateScheduled((2005, 10, 1), None, True, ('++', 1, 'm'))"),
+        ('* TODO Pay the rent\nSCHEDULED: <2005-10-01 Sat .+1m>',
+         "<2005-10-01 Sat .+1m>",
+         "OrgDateScheduled((2005, 10, 1), None, True, ('.+', 1, 'm'))"),
+    ]
+    for (input, expected_str, expected_repr) in testcases2:
+        root = loads(input)
+        output = root[1].scheduled
+        assert str(output) == expected_str
+        assert repr(output) == expected_repr
diff --git a/.venv/lib/python3.12/site-packages/orgparse/tests/test_rich.py b/.venv/lib/python3.12/site-packages/orgparse/tests/test_rich.py
new file mode 100644
index 00000000..7fb911b9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/orgparse/tests/test_rich.py
@@ -0,0 +1,89 @@
+'''
+Tests for rich formatting: tables etc.
+'''
+from .. import load, loads
+from ..extra import Table
+
+import pytest
+
+
+def test_table() -> None:
+    root = loads('''
+|       |           |     |
+|       | "heading" |     |
+|       |           |     |
+|-------+-----------+-----|
+| reiwf | fef       |     |
+|-------+-----------+-----|
+|-------+-----------+-----|
+| aba   | caba      | 123 |
+| yeah  |           |   X |
+
+    |------------------------+-------|
+    | when                   | count |
+    | datetime               | int   |
+    |------------------------+-------|
+    |                        | -1    |
+    | [2020-11-05 Thu 23:44] |       |
+    | [2020-11-06 Fri 01:00] | 1     |
+    |------------------------+-------|
+
+some irrelevant text
+
+| simple |
+|--------|
+| value1 |
+| value2 |
+    ''')
+
+    [gap1, t1, gap2, t2, gap3, t3, gap4] = root.body_rich
+
+    t1 = Table(root._lines[1:10])
+    t2 = Table(root._lines[11:19])
+    t3 = Table(root._lines[22:26])
+
+    assert ilen(t1.blocks) == 4
+    assert list(t1.blocks)[2] == []
+    assert ilen(t1.rows) == 6
+
+    with pytest.raises(RuntimeError):
+        list(t1.as_dicts) # not sure what should it be
+
+    assert ilen(t2.blocks) == 2
+    assert ilen(t2.rows) == 5
+    assert list(t2.rows)[3] == ['[2020-11-05 Thu 23:44]', '']
+
+
+    assert ilen(t3.blocks) == 2
+    assert list(t3.rows) == [['simple'], ['value1'], ['value2']]
+    assert t3.as_dicts.columns == ['simple']
+    assert list(t3.as_dicts) == [{'simple': 'value1'}, {'simple': 'value2'}]
+
+
+def test_table_2() -> None:
+    root = loads('''
+* item
+
+#+tblname: something
+| date                 | value | comment                       |
+|----------------------+-------+-------------------------------|
+| 14.04.17             |  11   | aaaa                          |
+| May 26 2017 08:00    |  12   | what + about + pluses?        |
+| May 26 09:00 - 10:00 |  13   | time is                       |
+
+    some comment
+
+#+BEGIN_SRC python :var fname="plot.png" :var table=something :results file
+fig.savefig(fname)
+return fname
+#+END_SRC
+
+#+RESULTS:
+[[file:plot.png]]
+''')
+    [_, t, _] = root.children[0].body_rich
+    assert ilen(t.as_dicts) == 3
+
+
+def ilen(x) -> int:
+    return len(list(x))