From 8228d7e6bf0a9cfdb19c2df53c47c5f7185e9c22 Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 10 Dec 2025 18:21:49 -0300 Subject: [PATCH 1/3] test: add nutest unit tests for internal commands Add comprehensive nutest-based unit tests for core internal functions in commands.nu including find-code-blocks, match-action, extract-fence-options, mark-code-block, clean-markdown, toggle-output-fences, and utility functions. This provides automated testing infrastructure for numd's parsing and transformation pipeline. --- tests/test_commands.nu | 336 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 tests/test_commands.nu diff --git a/tests/test_commands.nu b/tests/test_commands.nu new file mode 100644 index 0000000..2c65a6d --- /dev/null +++ b/tests/test_commands.nu @@ -0,0 +1,336 @@ +use std assert +use std/testing * + +# Import all functions from commands.nu (including internals not re-exported via mod.nu) +use ../numd/commands.nu * + +# ============================================================================= +# Tests for find-code-blocks +# ============================================================================= + +@test +def "find-code-blocks detects nushell block" [] { + let result = "```nushell\necho hello\n```" | find-code-blocks + + assert equal ($result | length) 1 + assert equal $result.0.row_type "```nushell" + assert equal $result.0.action "execute" +} + +@test +def "find-code-blocks detects nu block" [] { + let result = "```nu\nls\n```" | find-code-blocks + + assert equal ($result | length) 1 + assert equal $result.0.row_type "```nu" + assert equal $result.0.action "execute" +} + +@test +def "find-code-blocks handles no-run option" [] { + let result = "```nushell no-run\necho hello\n```" | find-code-blocks + + assert equal $result.0.action "print-as-it-is" +} + +@test +def "find-code-blocks handles text blocks" [] { + let result = "Some text\nMore text" | find-code-blocks + + assert equal ($result | length) 1 + assert equal $result.0.row_type "text" + assert equal $result.0.action "print-as-it-is" +} + +@test +def "find-code-blocks handles mixed content" [] { + let md = "# Header + +```nushell +ls +``` + +Some text + +```nu no-run +echo skip +```" + + let result = $md | find-code-blocks + + assert equal ($result | length) 4 + assert equal ($result | where action == "execute" | length) 1 + assert equal ($result | where action == "print-as-it-is" | length) 3 +} + +# ============================================================================= +# Tests for match-action +# ============================================================================= + +@test +def "match-action returns execute for nushell" [] { + assert equal (match-action "```nushell") "execute" +} + +@test +def "match-action returns execute for nu" [] { + assert equal (match-action "```nu") "execute" +} + +@test +def "match-action returns print-as-it-is for no-run" [] { + assert equal (match-action "```nushell no-run") "print-as-it-is" +} + +@test +def "match-action returns delete for output-numd" [] { + assert equal (match-action "```output-numd") "delete" +} + +@test +def "match-action returns print-as-it-is for text" [] { + assert equal (match-action "text") "print-as-it-is" +} + +@test +def "match-action returns print-as-it-is for other languages" [] { + assert equal (match-action "```python") "print-as-it-is" + assert equal (match-action "```rust") "print-as-it-is" +} + +# ============================================================================= +# Tests for extract-fence-options +# ============================================================================= + +@test +def "extract-fence-options parses single option" [] { + let result = "```nu no-run" | extract-fence-options + + assert equal $result ["no-run"] +} + +@test +def "extract-fence-options parses multiple options" [] { + let result = "```nushell try, no-output" | extract-fence-options + + assert equal ($result | length) 2 + assert ("try" in $result) + assert ("no-output" in $result) +} + +@test +def "extract-fence-options expands short options" [] { + let result = "```nu t, O" | extract-fence-options + + assert ("try" in $result) + assert ("no-output" in $result) +} + +@test +def "extract-fence-options handles empty options" [] { + let result = "```nushell" | extract-fence-options + + assert equal ($result | length) 0 +} + +# ============================================================================= +# Tests for convert-short-options +# ============================================================================= + +@test +def "convert-short-options expands O" [] { + assert equal (convert-short-options "O") "no-output" +} + +@test +def "convert-short-options expands N" [] { + assert equal (convert-short-options "N") "no-run" +} + +@test +def "convert-short-options expands t" [] { + assert equal (convert-short-options "t") "try" +} + +@test +def "convert-short-options expands n" [] { + assert equal (convert-short-options "n") "new-instance" +} + +@test +def "convert-short-options keeps long options unchanged" [] { + assert equal (convert-short-options "no-output") "no-output" + assert equal (convert-short-options "try") "try" +} + +# ============================================================================= +# Tests for mark-code-block +# ============================================================================= + +@test +def "mark-code-block generates open marker" [] { + assert equal (mark-code-block 5) "#code-block-marker-open-5" +} + +@test +def "mark-code-block generates close marker" [] { + assert equal (mark-code-block 3 --end) "#code-block-marker-close-3" +} + +# ============================================================================= +# Tests for clean-markdown +# ============================================================================= + +@test +def "clean-markdown removes empty output blocks" [] { + let input = "text\n```output-numd\n \n```\nmore" + let result = $input | clean-markdown + + assert ($result !~ "output-numd") +} + +@test +def "clean-markdown collapses multiple newlines" [] { + let input = "text\n\n\n\nmore" + let result = $input | clean-markdown + + assert ($result !~ "\n{3,}") +} + +@test +def "clean-markdown removes trailing spaces" [] { + let input = "text \nmore \n" + let result = $input | clean-markdown + + assert ($result !~ " \n") +} + +@test +def "clean-markdown ensures single trailing newline" [] { + let input = "text\n\n\n" + let result = $input | clean-markdown + + # Result should be "text\n" - single trailing newline + assert equal $result "text\n" +} + +# ============================================================================= +# Tests for toggle-output-fences +# ============================================================================= + +@test +def "toggle-output-fences converts output format" [] { + let input = "```nu\n123\n```\n\nOutput:\n\n```\n456\n```" + let result = $input | toggle-output-fences + + assert ($result =~ "output-numd") + assert ($result !~ "Output:") +} + +@test +def "toggle-output-fences converts back" [] { + let input = "```nu\n123\n```\n```output-numd\n456\n```" + let result = $input | toggle-output-fences --back + + assert ($result =~ "Output:") + assert ($result !~ "output-numd") +} + +# ============================================================================= +# Tests for escape-special-characters-and-quote +# ============================================================================= + +@test +def "escape-special-characters-and-quote escapes quotes" [] { + let result = 'hello "world"' | escape-special-characters-and-quote + + assert equal $result '"hello \"world\""' +} + +@test +def "escape-special-characters-and-quote escapes backslashes" [] { + let result = 'path\to\file' | escape-special-characters-and-quote + + assert equal $result '"path\\to\\file"' +} + +# ============================================================================= +# Tests for check-print-append +# ============================================================================= + +@test +def "check-print-append returns true for simple commands" [] { + assert equal (check-print-append "ls") true + assert equal (check-print-append "echo hello") true +} + +@test +def "check-print-append returns false for let statements" [] { + assert equal (check-print-append "let a = 1") false + assert equal (check-print-append "let a = ls") false +} + +@test +def "check-print-append returns false for mut statements" [] { + assert equal (check-print-append "mut a = 1") false +} + +@test +def "check-print-append returns false for def statements" [] { + assert equal (check-print-append "def foo [] {}") false +} + +@test +def "check-print-append returns false for semicolon ending" [] { + assert equal (check-print-append "ls;") false +} + +@test +def "check-print-append returns false for print ending" [] { + assert equal (check-print-append "ls | print") false +} + +# ============================================================================= +# Tests for modify-path +# ============================================================================= + +@test +def "modify-path adds prefix" [] { + let result = "file.nu" | modify-path --prefix "test_" + + assert equal $result "test_file.nu" +} + +@test +def "modify-path adds suffix" [] { + let result = "file.nu" | modify-path --suffix "_backup" + + assert equal $result "file_backup.nu" +} + +@test +def "modify-path changes extension" [] { + let result = "file.nu" | modify-path --extension ".md" + + assert equal $result "file.nu.md" +} + +@test +def "modify-path combines all options" [] { + let result = "file.nu" | modify-path --prefix "pre_" --suffix "_suf" --extension ".md" + + assert equal $result "pre_file_suf.nu.md" +} + +# ============================================================================= +# Tests for generate-timestamp +# ============================================================================= + +@test +def "generate-timestamp returns correct format" [] { + let result = generate-timestamp + + # Format should be YYYYMMDD_HHMMSS (15 chars) + assert equal ($result | str length) 15 + assert ($result =~ '^\d{8}_\d{6}$') +} From d4ef53210416082f1e9867bc7c5b4840fa669577 Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 10 Dec 2025 18:25:54 -0300 Subject: [PATCH 2/3] refactor: restructure testing commands to support both unit and integration tests - Rename original `main testing` to `main testing-integration` - Add new `main testing-unit` command that runs nutest unit tests - Add new `main testing` command that runs both unit and integration tests - All commands support --json flag for structured output --- toolkit.nu | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/toolkit.nu b/toolkit.nu index 4a98b46..958eafb 100644 --- a/toolkit.nu +++ b/toolkit.nu @@ -3,8 +3,30 @@ use $numdinternals [ modify-path ] export def main [] { } +# Run all tests (unit tests + integration tests) export def 'main testing' [ --json # output results as JSON for external consumption +] { + let unit = main testing-unit + let integration = main testing-integration + + {unit: $unit integration: $integration} + | if $json { to json } else { } +} + +# Run unit tests using nutest +export def 'main testing-unit' [ + --json # output results as JSON for external consumption +] { + use ../nutest/nutest + + nutest run-tests --path tests/ --returns summary --display terminal + | if $json { to json } else { } +} + +# Run integration tests (execute example markdown files) +export def 'main testing-integration' [ + --json # output results as JSON for external consumption ] { use numd From e42a478eea57c4370f86d8c023f691bb17ce8808 Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 10 Dec 2025 18:28:20 -0300 Subject: [PATCH 3/3] docs: update testing documentation with new command structure Add documentation for the new testing commands (testing, testing-unit, testing-integration) in both CLAUDE.md and README.md. Clarifies that unit tests use nutest framework and integration tests run example markdown files. --- CLAUDE.md | 30 +++++++++++++++++++++--------- README.md | 23 +++++++++++++++++++++-- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4175239..22a0e3e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,21 +59,33 @@ Blocks support options in the infostring (e.g., ` ```nushell try, no-output `): ## Testing -The `toolkit.nu testing` command: +```nushell +# Run all tests (unit + integration) +nu toolkit.nu testing + +# Run only unit tests (nutest-based, tests internal functions) +nu toolkit.nu testing-unit + +# Run only integration tests (executes example markdown files) +nu toolkit.nu testing-integration + +# All commands support --json for CI +nu toolkit.nu testing --json +``` + +### Unit Tests (`tests/`) + +Unit tests use [nutest](https://github.com/vyadh/nutest) framework. Tests import internal functions via `use ../numd/commands.nu *` to test parsing and transformation logic directly. + +### Integration Tests (`z_examples/`) + +The `testing-integration` command: 1. Runs all example files in `z_examples/` through numd 2. Generates stripped `.nu` versions in `z_examples/99_strip_markdown/` 3. Reports Levenshtein distance and diff stats to detect changes Example files serve as integration tests - use both the Levenshtein stats and `git diff` to verify changes. -```bash -# Run tests with JSON output (for external tools/CI) -nu toolkit.nu testing --json - -# Check actual file changes after testing -git diff -``` - ### Expected Non-Zero Diffs Some files legitimately differ on each run due to: diff --git a/README.md b/README.md index ce73b98..6776cef 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ Output: # => ╰──────────────────name───────────────────┴─type─╯ > sys host | get boot_time -# => Fri Dec 5 00:43:02 2025 +# => Fri Dec 5 01:08:37 2025 > 2 + 2 # => 4 @@ -242,10 +242,29 @@ Output: Nushell Markdown documents used together with Git could often serve as a convenient way to test custom and built-in Nushell commands. -Testing of the `numd` module itself is done via the `testing` command in `tools.nu` in the root repository folder: whatever changes are made in the module - it could be easily seen if they break anything (both by the Levenshtein distance metric or by `git diff` of the updated example files versus their initial versions) . Please, feel free to try it on your own. +Testing of the `numd` module is done via `toolkit.nu`: ```nushell no-run +# Run all tests (unit + integration) > nu toolkit.nu testing + +# Run only unit tests (uses nutest framework) +> nu toolkit.nu testing-unit + +# Run only integration tests (executes example markdown files) +> nu toolkit.nu testing-integration +``` + +### Unit tests + +Unit tests in `tests/` use the [nutest](https://github.com/vyadh/nutest) framework to test internal functions like `find-code-blocks`, `match-action`, `extract-fence-options`, etc. + +### Integration tests + +Integration tests run all example files in `z_examples/` through numd and report changes via Levenshtein distance. Whatever changes are made in the module - it can be easily seen if they break anything (both by the Levenshtein distance metric or by `git diff` of the updated example files versus their initial versions). + +```nushell no-run +> nu toolkit.nu testing-integration # => ╭───────────────────────────────────────────────┬─────────────────┬───────────────────┬────────────┬──────────────┬─────╮ # => │ filename │ nushell_blocks │ levenshtein_dist │ diff_lines │ diff_words │ ... │ # => ├───────────────────────────────────────────────┼─────────────────┼───────────────────┼────────────┼──────────────┼─────┤