.claude/skills/testing/SKILL.md
Writes and maintains tests for the Which Key Lazy IntelliJ plugin. Use when adding new tests, fixing failing tests, or verifying that pure-logic components work correctly. Covers config parsing, tree building, icon resolution, key notation, and ideavimrc parsing.
npx skillsauth add brandonkramer/which-key-lazy testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a test specialist for the Which Key Lazy plugin — a LazyVim-style which-key popup for IdeaVim. Your job is to write, run, and maintain tests for the plugin's pure-logic components.
src/test/kotlin/lazyideavim/whichkeylazy/
Mirror the main source structure:
src/test/kotlin/lazyideavim/whichkeylazy/
config/
DefaultConfigTest.kt — bundled JSON template validity
WhichKeyConfigTest.kt — config loading, binding conversion
ideavim/
IdeaVimRcParserTest.kt — .ideavimrc file parsing
IdeaVimTreeBuilderTest.kt — trie-to-KeyNode tree building
DefaultGroupNamesTest.kt — group name lookups
ui/
IconResolverTest.kt — keyword matching, AllIcons paths
model/
KeyBindingTest.kt — data class serialization round-trips
These are pure-logic components with no IDE dependencies:
| Component | What to test |
|---|---|
| DefaultConfig.JSON_TEMPLATE | Deserializes without errors; settings have expected defaults; all binding entries are valid (groups have children, actions have actionIds) |
| WhichKeyConfig.load() | Parses valid JSON; handles missing file (falls back to defaults); handles malformed JSON (returns failure); correctly converts groups vs actions |
| IdeaVimRcParser.parse() | Parses nmap, nnoremap, let, set commands; handles comments and blank lines; extracts leader key; handles <Action>(...) and :action ... <CR> patterns; ignores imap/vmap/xmap when parsing normal-mode mappings |
| IdeaVimTreeBuilder.buildTree() | Flat mappings → nested tree; single-key and multi-key sequences; group detection (nodes with children); WhichKeyDesc overrides |
| DefaultGroupNames.getDefault() | Known keys return expected descriptions; unknown keys return null |
| IconResolver.resolveGroupIcon() | Keyword matching ("+git" → branch icon); config override takes priority; unknown descriptions fall back to folder icon; case-insensitive matching |
| IconResolver.resolveIconByKeyword() | Keyword shorthand ("git", "debug"); AllIcons path resolution ("AllIcons.Actions.Undo"); invalid paths return null |
| Data model serialization | WhichKeyRoot, WhichKeySettings, BindingEntry, OverrideEntry round-trip through JSON correctly |
These require a running IDE or IdeaVim and are better tested manually with ./gradlew runIde:
IdeaVimApiReader — needs IdeaVim runtime injectorWhichKeyPopup / PopupGrid / BreadcrumbBar — Swing UI renderingActionExecutor — needs IntelliJ ActionManagerMappingLookup — needs IdeaVim KeyMappingWhichKeyAction / WhichKeyVimExtension — needs IDE contextConfigFileWatcher — filesystem eventspackage lazyideavim.whichkeylazy.config
import org.junit.Assert.*
import org.junit.Test
class DefaultConfigTest {
@Test
fun `template deserializes without errors`() {
val result = WhichKeyConfig.load()
assertTrue("Default config should parse successfully", result.isSuccess)
}
@Test
fun `default settings have expected values`() {
val result = WhichKeyConfig.load().getOrThrow()
assertEquals(200, result.settings.delay)
assertEquals(5, result.settings.maxColumns)
assertEquals("bottom-right", result.settings.position)
}
}
Only use BasePlatformTestCase if you need IntelliJ services (ActionManager, icons, etc.). Most tests should be plain JUnit 4.
package lazyideavim.whichkeylazy.ui
import com.intellij.testFramework.fixtures.BasePlatformTestCase
class IconResolverPlatformTest : BasePlatformTestCase() {
fun testResolveActionIcon() {
// ActionManager is available in platform tests
val icon = IconResolver.resolveActionIcon("GotoFile")
assertNotNull("GotoFile should have an icon", icon)
}
}
Important notes for BasePlatformTestCase:
test (no @Test annotation)super.setUp() / super.tearDown()tearDown() must have super.tearDown() in a finally blockThe parser works on strings, no IDE needed:
@Test
fun `parses leader key from let command`() {
val input = """
let mapleader=" "
nmap <leader>ff <Action>(GotoFile)
""".trimIndent()
val result = IdeaVimRcParser.parse(input)
assertEquals(' ', result.leaderChar)
}
@Test
fun `parses action mappings`() {
val input = """
let mapleader=" "
nmap <leader>ff <Action>(GotoFile)
nmap <leader>gg <Action>(ActivateCommitToolWindow)
""".trimIndent()
val result = IdeaVimRcParser.parse(input)
assertEquals(2, result.mappings.size)
}
# Run all tests
./gradlew test
# Run a specific test class
./gradlew test --tests "lazyideavim.whichkeylazy.config.DefaultConfigTest"
# Run a specific test method
./gradlew test --tests "lazyideavim.whichkeylazy.config.DefaultConfigTest.template deserializes without errors"
# Run tests with console output
./gradlew test --console=plain
# Compile tests without running
./gradlew compileTestKotlin
The project's build.gradle.kts already includes test dependencies:
intellijPlatform {
testFramework(TestFrameworkType.Platform)
}
testImplementation(libs.junit) // JUnit 4.13.2
If you see NoClassDefFoundError: org/opentest4j/AssertionFailedError, add:
testImplementation("org.opentest4j:opentest4j:1.3.0")
BasePlatformTestCase — most testable code is pure logic`parses leader from let command`()<Tab>, <C-n>, |, \).ideavimrc snippets, real action IDs, real JSON configstest: add DefaultConfig template validation tests
Verify the bundled JSON_TEMPLATE deserializes correctly and all
binding entries have valid structure (groups have children,
actions have actionIds).
test: add IdeaVimRcParser tests for leader key extraction
Cover space leader, backslash default, and various mapping
formats including <Action>() and :action patterns.
development
Keeps Which Key Lazy documentation in sync with code changes. Use this skill when you need to verify documentation accuracy after code changes, or when checking if documentation (README.md, CLAUDE.md) matches the current codebase. The skill can work bidirectionally - from docs to code verification, or from code changes to documentation updates.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------