Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .busted
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Test configuration for busted
return {
_all = {
coverage = false,
verbose = true,
},
default = {
ROOT = { "tests/", },
pattern = "_spec.lua",
exclude = { "example_spec.lua", },
output = "TAP",
},
}
25 changes: 20 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,20 @@ This is a Neovim configuration managed as a Nix flake. The repository provides a
- Validates flake structure
- Checks `add-plugin` and `update-plugins` packages build successfully

9. **Run tests:** `nix build .#lua-tests` or `nix build .#checks.x86_64-linux.lua-tests`
- Runs Lua unit tests using busted
- Tests located in `tests/` directory
- Can also run `busted tests/` in dev shell for faster iteration

### Common Workflows

**Making changes to Lua configuration:**
1. `nix develop` - enter dev shell
2. Edit files in `plugins/nobbz/lua/nobbz/`
3. Test with `:checkhealth nobbz` in Neovim
4. `nix fmt` - format before committing
4. If adding utility functions, add unit tests in `tests/`
5. Run `busted tests/` to verify tests pass
6. `nix fmt` - format before committing

**Adding a new plugin:**
1. `nix run .#add-plugin <name> <owner/repo>`
Expand Down Expand Up @@ -104,6 +111,12 @@ This is a Neovim configuration managed as a Nix flake. The repository provides a
- `add-plugin.py` / `add-plugin.nix` - Script to add plugins via npins
- `update-plugins.py` / `update-plugins.nix` - Script to update all plugins

**`tests/`** - Lua unit tests
- `default.nix` - Flake-parts module, defines test packages and checks
- `*_spec.lua` - Test files using busted framework
- `.busted` - Busted configuration file (at repo root)
- `README.md` - Testing documentation

**`npins/`** - Dependency pinning
- `sources.json` - Pinned sources (plugins, neovim, dependencies)
- `default.nix` - npins library (auto-generated, don't edit)
Expand Down Expand Up @@ -160,10 +173,11 @@ Before submitting changes:

1. **Format:** `nix fmt` (REQUIRED - catches style issues)
2. **Build:** `nix build` (verifies Nix evaluation and package builds)
3. **Test run:** `nix run` (launches Neovim to verify it works)
4. **Check health:** In Neovim, run `:checkhealth nobbz` (verifies programs and LSP configs)
5. **Flake check:** `nix flake check` (validates flake structure)
6. **Update instructions:** After major refactors, verify `.github/copilot-instructions.md` is still accurate
3. **Test:** `nix build .#lua-tests` (runs Lua unit tests)
4. **Test run:** `nix run` (launches Neovim to verify it works)
5. **Check health:** In Neovim, run `:checkhealth nobbz` (verifies programs and LSP configs)
6. **Flake check:** `nix flake check` (validates flake structure and runs checks)
7. **Update instructions:** After major refactors, verify `.github/copilot-instructions.md` is still accurate

**No automated CI/CD** - all validation is manual. The maintainer runs these commands before merging.

Expand All @@ -176,6 +190,7 @@ Before submitting changes:
- **Lazy loading via lz.n** - not using lazy.nvim, custom system in `lazy/init.lua`
- **Neovide supported** - GUI wrapper defined in `nvide.nix`
- **direnv integration** - `.envrc` auto-loads dev shell if direnv installed
- **Unit tests available** - `busted` framework tests core Lua modules in `tests/`

## Trust These Instructions

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@

# generated by direnv if used
.direnv

# Test artifacts
*.test.lua
coverage/
3 changes: 2 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
parts.lib.mkFlake {inherit inputs;} {
systems = ["x86_64-linux"];

imports = [./plugins ./bin];
imports = [./plugins ./bin ./tests];

perSystem = {
self',
Expand Down Expand Up @@ -42,6 +42,7 @@
pkgs.mkShell {
packages = builtins.attrValues {
inherit (pkgs) nil stylua npins alejandra basedpyright;
inherit (pkgs.luajitPackages) busted;
inherit (self'.packages) neovim neovide;
inherit emmy-lua-code-style;
};
Expand Down
21 changes: 21 additions & 0 deletions run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Quick test runner script for development
# This script is a convenience wrapper for running tests during development

set -e

echo "==> Running Lua unit tests with busted..."
echo ""

# Check if we're in a nix develop shell
if ! command -v busted &> /dev/null; then
echo "Error: busted not found in PATH"
echo "Please run 'nix develop' first to enter the development shell"
exit 1
fi

# Run tests
busted --verbose tests/

echo ""
echo "==> All tests passed! ✓"
66 changes: 66 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Lua Unit Tests

This project includes unit tests for the Lua configuration modules using [busted](https://lunarmodules.github.io/busted/).

## Running Tests

### Using Nix

The recommended way to run tests is using Nix:

```bash
# Run all tests (builds and executes tests)
nix build .#lua-tests

# Run tests manually via the test package
nix build .#checks.x86_64-linux.lua-tests

# Run tests in development
nix develop
busted tests/

# Or use the convenience script
./run-tests.sh
```

### Test Structure

Tests are located in the `tests/` directory and follow the naming convention `*_spec.lua`:

- `helpers_spec.lua` - Tests for utility helper functions
- `health_spec.lua` - Tests for the health check system
- `lazy_spec.lua` - Tests for the lazy loading system
- `example_spec.lua` - Example test patterns and templates

### Writing Tests

Tests use the busted framework with BDD-style assertions. See `example_spec.lua` for common patterns:

```lua
describe("module name", function()
describe("function name", function()
it("should do something", function()
local result = module.function()
assert.equals(expected, result)
end)
end)
end)
```

### Coverage

Tests cover:
- Core utility functions (helpers.lua)
- Health check registration and execution (health.lua)
- Lazy loading plugin system (lazy/init.lua)

### Mocking

Since these tests run outside of Neovim, vim APIs are mocked using Lua tables. See individual test files for examples.

## Future Improvements

- Add tests for more modules (lsp configurations, plugin configs)
- Add code coverage reporting
- Integrate with CI/CD if GitHub Actions is set up
- Add property-based testing for complex functions
66 changes: 66 additions & 0 deletions tests/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{self, ...}: {
perSystem = {
self',
pkgs,
npins,
lib,
...
}: let
# Test runner script
testRunner = pkgs.writeShellScriptBin "run-lua-tests" ''
export LUA_PATH="${placeholder "out"}/?.lua;${placeholder "out"}/?/init.lua;;"
${pkgs.luajitPackages.busted}/bin/busted \
--verbose \
--output=tap \
--pattern=_spec.lua \
"$@"
'';

# Build the test package
luaTests = pkgs.stdenv.mkDerivation {
name = "nobbz-vim-tests";
src = ../.;

nativeBuildInputs = [
pkgs.luajitPackages.busted
];

phases = ["unpackPhase" "checkPhase" "installPhase"];

# Copy source files and tests
unpackPhase = ''
cp -r $src/tests tests
cp -r $src/plugins/nobbz/lua lua
'';

# Run the tests
checkPhase = ''
export LUA_PATH="./?.lua;./?/init.lua;;"
${pkgs.luajitPackages.busted}/bin/busted \
--verbose \
--output=tap \
--pattern=_spec.lua \
tests/
'';

installPhase = ''
mkdir -p $out/share
echo "Tests passed" > $out/share/test-results.txt

# Install test infrastructure for reuse
mkdir -p $out/bin
ln -s ${testRunner}/bin/run-lua-tests $out/bin/
'';

meta = {
description = "Lua unit tests for nobbz-vim";
mainProgram = "run-lua-tests";
};
};
in {
packages.lua-tests = luaTests;
packages.test-runner = testRunner;

checks.lua-tests = luaTests;
};
}
91 changes: 91 additions & 0 deletions tests/example_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
-- Example test demonstrating busted testing patterns
-- This file serves as a template for adding new tests

describe("Example Test Suite", function()
-- Setup that runs before each test in this describe block
before_each(function()
-- Initialize test state here
end)

-- Cleanup that runs after each test in this describe block
after_each(function()
-- Clean up test state here
end)

describe("Basic Assertions", function()
it("should demonstrate equality assertions", function()
local expected = 42
local actual = 42
assert.equals(expected, actual)
end)

it("should demonstrate boolean assertions", function()
assert.is_true(true)
assert.is_false(false)
assert.is_nil(nil)
assert.is_not_nil("something")
end)

it("should demonstrate table assertions", function()
local table1 = { a = 1, b = 2, }
local table2 = { a = 1, b = 2, }
assert.same(table1, table2)
end)
end)

describe("Mocking Examples", function()
it("should demonstrate function mocking", function()
-- Save original function
local original_func = some_module.some_function

-- Replace with mock
some_module.some_function = function()
return "mocked value"
end

-- Test code that uses the mocked function
-- local result = code_under_test()
-- assert.equals("expected", result)

-- Restore original
some_module.some_function = original_func
end)

it("should demonstrate vim API mocking", function()
-- When testing code that uses vim APIs, mock them like this:
_G.vim = {
fn = {
executable = function() return 1 end,
},
api = {
nvim_create_autocmd = function() end,
},
}

-- Now test your code that uses vim.fn or vim.api
end)
end)

describe("Testing Module Exports", function()
it("should verify module returns expected interface", function()
-- For a module that exports functions:
-- local module = require("module_name")
-- assert.is_function(module.some_function)
-- assert.is_not_nil(module.some_value)
end)
end)

describe("Error Handling", function()
it("should test that errors are raised", function()
assert.has_error(function()
error("This should fail")
end)
end)

it("should test error messages", function()
assert.error_matches(function()
error("Expected error message")
end, "Expected error")
end)
end)
end)
Loading