diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..0f93b77 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,30 @@ +name: Tests + +on: + push: + branches: [ main, claude/* ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Run tests + run: python -m unittest discover -s tests -p 'test*.py' -v diff --git a/README.md b/README.md index 343aed9..51670d3 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,20 @@ with open('output.nsv', 'w') as f: writer.write_row(['row2cell1', 'row2cell2', 'row2cell3']) ``` -## Running Tests +## Development + +### Running Tests + +**Important**: Always run tests from the project root to test local code changes (not the installed package): ```bash -cd tests -python -m unittest +python -m unittest discover -s tests -p 'test*.py' -v +``` + +Alternatively, install in editable mode: + +```bash +pip install -e . ``` Must cover diff --git a/nsv/core.py b/nsv/core.py index 9befe4e..72a4be2 100644 --- a/nsv/core.py +++ b/nsv/core.py @@ -1,13 +1,13 @@ -from typing import Iterable +from typing import Iterable, List from .reader import Reader from .writer import Writer -def load(file_obj) -> list[list[str]]: +def load(file_obj) -> List[List[str]]: """Load NSV data from a file-like object.""" return list(Reader(file_obj)) -def loads(s: str) -> list[list[str]]: +def loads(s: str) -> List[List[str]]: """Load NSV data from a string.""" data = [] row = [] diff --git a/tests/samples/escape_edge_cases.nsv b/tests/samples/escape_edge_cases.nsv new file mode 100644 index 0000000..12cff58 --- /dev/null +++ b/tests/samples/escape_edge_cases.nsv @@ -0,0 +1,7 @@ +\\n +\\\n +\\\\n + +\\\\ +\n\n + diff --git a/tests/samples/trailing_backslash.nsv b/tests/samples/trailing_backslash.nsv new file mode 100644 index 0000000..1acad5c --- /dev/null +++ b/tests/samples/trailing_backslash.nsv @@ -0,0 +1,13 @@ +yo\ +shouln'ta\ +be\ +doing\ +this\ + +\ +or +\ +should +\ +ya + diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 775bc8d..64ce60d 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -20,6 +20,17 @@ def test_special_characters(self): rows = nsv.load(f) self.assertEqual(SAMPLES_DATA['special_chars'], rows) + def test_trailing_backslash(self): + """Test handling of special characters in field values.""" + expected = [ + ['yo', 'shouln\'ta', 'be', 'doing', 'this'], + ['', 'or', '', 'should', '', 'ya'], + ] + file_path = os.path.join(SAMPLES_DIR, 'trailing_backslash.nsv') + with open(file_path, 'r') as f: + rows = nsv.load(f) + self.assertEqual(expected, rows) + # def test_numeric_values(self): # """Test handling of numeric values.""" # data = [ diff --git a/tests/test_utils.py b/tests/test_utils.py index 79b087a..5c6f107 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -29,6 +29,10 @@ [], ], 'multiline_encoded': [["line1\nline2", "r1c2", "r1c3"], ["anotherline1\nline2\nline3", "r2c2"]], + 'escape_edge_cases': [ + ["\\n", "\\\n", "\\\\n"], + ["\\\\", "\n\n"], + ], }