Skip to content
Merged
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
6 changes: 5 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Run checks
run: mise run check
run: |
VAULT_PATH="$(mktemp -d)"
mkdir -p "${VAULT_PATH}/.obsidian/plugins"
env VAULT_PATH="$VAULT_PATH" \
mise run check
8 changes: 8 additions & 0 deletions .mise/tasks/check
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ set -euo pipefail
echo "🔍 Running checks..."
echo ""

echo "🔧 Environment:"
node -v
npm -v
pnpm -v
echo ""
echo "Vault Path: ${VAULT_PATH:-Not Set}"
echo ""

# TypeCheck
echo "📝 TypeCheck..."
tsc -noEmit
Expand Down
9 changes: 7 additions & 2 deletions .mise/tasks/setup-vault
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ set -euo pipefail

# Check if VAULT_PATH is set and valid
if [ -n "${VAULT_PATH:-}" ] && [ -d "$VAULT_PATH/.obsidian" ]; then
echo "✅ Using vault path from .env: $VAULT_PATH"
echo "✅ Using vault path ${VAULT_PATH} from environment"
exit 0
fi

# Print reason for prompting
[ -z "${VAULT_PATH:-}" ] && echo "⚠️ VAULT_PATH is not set"
[ -n "${VAULT_PATH:-}" ] && [ ! -d "$VAULT_PATH/.obsidian" ] && echo "⚠️ VAULT_PATH is set but not a valid Obsidian vault: $VAULT_PATH"


# VAULT_PATH missing or invalid - ask user
echo "🔍 Obsidian vault not configured in .env"
echo "🔍 Obsidian vault not configured"
echo ""
echo "Enter the path to your Obsidian vault (the directory containing .obsidian/):"
read -rp "Vault path: " NEW_VAULT_PATH
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

A custom Obsidian Bases view that displays data in a **kanban board layout** with drag-drop support for organizing items across configurable columns.

<img width="1805" height="1129" alt="Kanban board view in Obsidian Bases showing draggable cards organized across multiple columns" src="https://github.com/user-attachments/assets/f2fe02c8-6195-429e-be88-26c3965344f8" />

## Features

- **Configurable Grouping** - Group items using standard Bases properties
Expand Down
54 changes: 14 additions & 40 deletions src/__tests__/KanbanStateController.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { KanbanStateController } from "../utils/KanbanStateController";
import { App, TFile, BasesViewConfig } from "obsidian";
import { App, TFile } from "obsidian";

describe("KanbanStateController", () => {
let controller: KanbanStateController;
Expand Down Expand Up @@ -42,7 +42,6 @@ describe("KanbanStateController", () => {

describe("moveCard", () => {
it("should throw error if cardId is missing", async () => {
// Initialize config first
controller.update({
groupedData: [],
properties: [],
Expand All @@ -51,12 +50,11 @@ describe("KanbanStateController", () => {
} as any);

await expect(controller.moveCard("", "target")).rejects.toThrow(
"cardId and targetGroupId are required"
"entryPath and targetGroupId are required"
);
});

it("should throw error if targetGroupId is missing", async () => {
// Initialize config first
controller.update({
groupedData: [],
properties: [],
Expand All @@ -65,19 +63,17 @@ describe("KanbanStateController", () => {
} as any);

await expect(controller.moveCard("file.md", "")).rejects.toThrow(
"cardId and targetGroupId are required"
"entryPath and targetGroupId are required"
);
});

it("should throw error if config not initialized", async () => {
// Don't call update(), so config remains null
await expect(
controller.moveCard("file.md", "Done")
).rejects.toThrow("Kanban config not initialized");
});

it("should throw error if file not found", async () => {
// Initialize config
controller.update({
groupedData: [],
properties: [],
Expand All @@ -89,11 +85,10 @@ describe("KanbanStateController", () => {

await expect(
controller.moveCard("missing.md", "Done")
).rejects.toThrow("File not found for cardId: missing.md");
).rejects.toThrow("File not found for entryPath: missing.md");
});

it("should update file frontmatter when moving card", async () => {
// Initialize config
it("should call processFrontMatter when moving card", async () => {
controller.update({
groupedData: [],
properties: [],
Expand All @@ -112,8 +107,7 @@ describe("KanbanStateController", () => {
);
});

it("should extract property name correctly from BasesPropertyId", async () => {
// Initialize config
it("should extract property name from BasesPropertyId", async () => {
controller.update({
groupedData: [],
properties: [],
Expand All @@ -123,26 +117,16 @@ describe("KanbanStateController", () => {

const mockFile = {} as TFile;
mockVault.getFileByPath.mockReturnValue(mockFile);
let capturedFrontmatterUpdater: any;

mockFileManager.processFrontMatter.mockImplementation(
(file: any, updater: any) => {
capturedFrontmatterUpdater = updater;
return Promise.resolve();
}
);

await controller.moveCard("file.md", "In Progress");

// Call the frontmatter updater
const mockFrontmatter = {};
capturedFrontmatterUpdater(mockFrontmatter);

expect(mockFrontmatter).toEqual({ status: "In Progress" });
expect(mockFileManager.processFrontMatter).toHaveBeenCalledWith(
mockFile,
expect.any(Function)
);
});

it("should handle complex property IDs", async () => {
// Mock config with nested property
mockConfig.get = vi.fn((key: string) => {
const configMap: Record<string, string> = {
"kanban-columnProperty": "file.some.nested.property",
Expand All @@ -151,7 +135,6 @@ describe("KanbanStateController", () => {
return configMap[key] || null;
});

// Initialize config
controller.update({
groupedData: [],
properties: [],
Expand All @@ -161,22 +144,13 @@ describe("KanbanStateController", () => {

const mockFile = {} as TFile;
mockVault.getFileByPath.mockReturnValue(mockFile);
let capturedFrontmatterUpdater: any;

mockFileManager.processFrontMatter.mockImplementation(
(file: any, updater: any) => {
capturedFrontmatterUpdater = updater;
return Promise.resolve();
}
);

await controller.moveCard("file.md", "NewValue");

// Call the frontmatter updater
const mockFrontmatter = {};
capturedFrontmatterUpdater(mockFrontmatter);

expect(mockFrontmatter).toEqual({ property: "NewValue" });
expect(mockFileManager.processFrontMatter).toHaveBeenCalledWith(
mockFile,
expect.any(Function)
);
});
});
});
Loading