mrcjkb / neotest-haskell

Neotest adapter for Haskell (cabal or stack) with support for Sydtest, Hspec and Tasty
GNU General Public License v2.0
50 stars 4 forks source link
haskell hspec neovim neovim-plugin sydtest tasty testing treesitter


neotest-haskell

Report Bug

A neotest adapter for Haskell.

🦥

[![Neovim][neovim-shield]][neovim-url] [![Lua][lua-shield]][lua-url] [![Haskell][haskell-shield]][haskell-url] [![Nix][nix-shield]][nix-url] [![GPL2 License][license-shield]][license-url] [![Issues][issues-shield]][issues-url] [![Build Status][ci-shield]][ci-url] [![LuaRocks][luarocks-shield]][luarocks-url] [![All Contributors](https://img.shields.io/badge/all_contributors-9-purple.svg?style=for-the-badge)](#contributors-)

Quick links

Features

https://user-images.githubusercontent.com/12857160/224197351-8ca64bd5-8d89-4689-8c40-18d1d018896e.mp4

Installation

See also: neotest installation instructions.

The following example uses lazy.nvim:

{
  'nvim-neotest/neotest',
  dependencies = {
    -- ...,
    'mrcjkb/neotest-haskell',
    'nvim-lua/plenary.nvim',
  }
}

Configuration

Make sure the Haskell parser for tree-sitter is installed, you can do so via nvim-treesitter like so:

require('nvim-treesitter.configs').setup {
  ensure_installed = {
    'haskell',
    --...,
  },
}

Add neotest-haskell to your neotest adapters:

require('neotest').setup {
  -- ...,
  adapters = {
    -- ...,
    require('neotest-haskell')
  },
}

You can also pass a config to the setup. The following are the defaults:

require('neotest').setup {
  adapters = {
    require('neotest-haskell') {
      -- Default: Use stack if possible and then try cabal
      build_tools = { 'stack', 'cabal' },
      -- Default: Check for tasty first and then try hspec
      frameworks = { 'tasty', 'hspec', 'sydtest' },
    },
  },
}

[!NOTE]

If you were to use build_tools = { 'cabal', 'stack' }, then cabal will almost always be chosen, because almost all stack projects can be built with cabal.

Alternately, you can pair each test framework with a list of modules, used to identify the respective framework in a test file:

require('neotest').setup {
  adapters = {
    require('neotest-haskell') {
      frameworks = {
        { framework = 'tasty', modules = { 'Test.Tasty', 'MyTestModule' }, },
        'hspec',
        'sydtest',
      },
    },
  },
}

This can be useful if you have test files that do not import one of the default modules used for framework identification:

Advanced configuration

This plugin uses tree-sitter queries in files that match <runtimepath>/queries/haskell/<framework>-positions.scm

For example, to add position queries for this plugin for tasty, without having to fork this plugin, you can add them to $XDG_CONFIG_HOME/nvim/after/queries/haskell/tasty-positions.scm.

[!NOTE]

Examples

module FixtureSpec ( spec ) where
import Test.Hspec
import Test.Hspec.QuickCheck
import Control.Exception ( evaluate )

spec :: Spec
spec = describe "Prelude.head" $ do
  it "returns the first element of a list" $ head [23 ..] `shouldBe` (23 :: Int)

  prop "returns the first element of an *arbitrary* list" $ \x xs ->
    head (x : xs) `shouldBe` (x :: Int)

  describe "Empty list" $
    it "throws an exception if used with an empty list"
      $             evaluate (head [])
      `shouldThrow` anyException

In the above listing, calling :lua require('neotest').run.run() with the cursor on the line...

  describe "Empty list" $

...will run the tests with the following Cabal command:

# Assuming a Cabal package called "my_package"
cabal test my_package --test-option -m --test-option "/Prelude.head/Empty list/"

...or with the following Stack command:

# Assuming a Stack package called "my_package"
stack test my_package --ta "--match \"/Prelude.head/Empty list/\""

...which will run the "throws an exception if used with an empty list" test.

Calling :lua require('neotest').run.run() with the cursor on the line...

spec = describe "Prelude.head" $ do

...will run the tests with the following Cabal command:

# Assuming a Cabal package called "my_package"
cabal test my_package --test-option -m --test-option "/Prelude.head/"

...or with the following Stack command:

# Assuming a Stack package called "my_package"
stack test my_package --ta "--match \"/Prelude.head/\""

...which will run all tests in the module.

TODO

See issues.

Troubleshooting

To run a health check, run :checkhealth neotest-haskell in Neovim.

Limitations

Recommendations

Here are some other plugins I recommend for Haskell development:

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Perigord
Perigord

💻
Sebastian Witte
Sebastian Witte

💻 🚇 📖
Andy Bell
Andy Bell

💻
Tom Sydney Kerckhove
Tom Sydney Kerckhove

🧑‍🏫
Nadeem Bitar
Nadeem Bitar

🐛
Mango The Fourth
Mango The Fourth

🐛
Hécate Moonlight
Hécate Moonlight

🐛
Amaan Qureshi
Amaan Qureshi

💻
Brad Sherman
Brad Sherman

💻

This project follows the all-contributors specification. Contributions of any kind welcome!