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
60 changes: 60 additions & 0 deletions tests/computeScore.debug.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createTempDir, cleanupTempDir, createTempJsonlPath, createTempDbPath } from './test-utils.js';
import { WorklogDatabase } from '../src/database.js';

describe('computeScore debug helpers', () => {
let tempDir: string;
let dbPath: string;
let jsonlPath: string;
let db: WorklogDatabase;

beforeEach(() => {
tempDir = createTempDir();
dbPath = createTempDbPath(tempDir);
jsonlPath = createTempJsonlPath(tempDir);
db = new WorklogDatabase('TEST', dbPath, jsonlPath, true, true);
});

afterEach(() => {
db.close();
cleanupTempDir(tempDir);
});

it('computeScore applies only the in-progress boost (not ancestor boost) when item is in-progress', async () => {
const highOpen = db.create({ title: 'High open item', priority: 'high' });
// ensure deterministic ordering
await new Promise(resolve => setTimeout(resolve, 10));
const parent = db.create({ title: 'In-progress parent', priority: 'medium', status: 'in-progress' });
const child = db.create({ title: 'In-progress child', priority: 'medium', status: 'in-progress', parentId: parent.id });

// Build ancestorsOfInProgress set using the same logic as sortItemsByScore
const items = db.getAll();
const ancestorsOfInProgress = new Set<string>();
for (const it of items) {
if (it.status === 'in-progress') {
let currentParentId = it.parentId ?? null;
let depth = 0;
while (currentParentId && depth < 50) {
ancestorsOfInProgress.add(currentParentId);
const p = db.get(currentParentId);
currentParentId = p?.parentId ?? null;
depth++;
}
}
}

const now = Date.now();
const compute = (item: any) => (db as any).computeScore(item, now, 'ignore', ancestorsOfInProgress);

// Compute additive baseline by forcing multiplier to 1 (status=open and no ancestor boost)
const additiveParent = (db as any).computeScore({ ...parent, status: 'open' }, now, 'ignore', new Set());
const finalParent = compute(parent);

// Sanity: child is in-progress so parent is included in ancestorsOfInProgress
expect(ancestorsOfInProgress.has(parent.id)).toBe(true);

// finalParent should equal additiveParent * 1.5 (in-progress boost) and not * 1.25
expect(finalParent).toBeCloseTo(additiveParent * 1.5, 6);
expect(finalParent).not.toBeCloseTo(additiveParent * 1.25, 6);
});
});
60 changes: 60 additions & 0 deletions tests/computeScore.nonstack.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createTempDir, cleanupTempDir, createTempJsonlPath, createTempDbPath } from './test-utils.js';
import { WorklogDatabase } from '../src/database.js';

describe('computeScore non-stacking unit', () => {
let tempDir: string;
let dbPath: string;
let jsonlPath: string;
let db: WorklogDatabase;

beforeEach(() => {
tempDir = createTempDir();
dbPath = createTempDbPath(tempDir);
jsonlPath = createTempJsonlPath(tempDir);
db = new WorklogDatabase('TEST', dbPath, jsonlPath, true, true);
});

afterEach(() => {
db.close();
cleanupTempDir(tempDir);
});

it('applies only the in-progress boost (not ancestor boost) when item is in-progress', async () => {
const highOpen = db.create({ title: 'High open item', priority: 'high' });
// ensure deterministic ordering
await new Promise(resolve => setTimeout(resolve, 10));
const parent = db.create({ title: 'In-progress parent', priority: 'medium', status: 'in-progress' });
const child = db.create({ title: 'In-progress child', priority: 'medium', status: 'in-progress', parentId: parent.id });

// Build ancestorsOfInProgress set using the same logic as sortItemsByScore
const items = db.getAll();
const ancestorsOfInProgress = new Set<string>();
for (const it of items) {
if (it.status === 'in-progress') {
let currentParentId = it.parentId ?? null;
let depth = 0;
while (currentParentId && depth < 50) {
ancestorsOfInProgress.add(currentParentId);
const p = db.get(currentParentId);
currentParentId = p?.parentId ?? null;
depth++;
}
}
}

const now = Date.now();
const compute = (item: any) => (db as any).computeScore(item, now, 'ignore', ancestorsOfInProgress);

// Compute additive baseline by forcing multiplier to 1 (status=open and no ancestor boost)
const additiveParent = (db as any).computeScore({ ...parent, status: 'open' }, now, 'ignore', new Set());
const finalParent = compute(parent);

// Sanity: child is in-progress so parent is included in ancestorsOfInProgress
expect(ancestorsOfInProgress.has(parent.id)).toBe(true);

// finalParent should equal additiveParent * 1.5 (in-progress boost) and not * 1.25
expect(finalParent).toBeCloseTo(additiveParent * 1.5, 6);
expect(finalParent).not.toBeCloseTo(additiveParent * 1.25, 6);
});
});
36 changes: 5 additions & 31 deletions tests/tui/tui-github-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,35 +292,9 @@ describe('TUI G key (shift+G) GitHub action', () => {
expect(result.toastMessage).toContain('Opening GitHub issue');
});

it('shows failure toast when both open and clipboard fail for mapped item', async () => {
const ctx = createTuiTestContext();

const id = ctx.utils.createSampleItem({ tags: [] });
const item = ctx.utils.getDatabase().get(id);
if (item) {
(item as any).githubIssueNumber = 99;
}

// Import the TUI wrapper directly and call it with mocked deps that
// simulate both browser-open and clipboard failing.
const { default: githubActionHelper } = await import('../../src/tui/github-action-helper.js');

await githubActionHelper({
item,
screen: { program: { write: vi.fn() }, render: vi.fn() },
db: ctx.utils.getDatabase(),
showToast: (m: string) => ctx.toast.show(m),
resolveGithubConfig: () => ({ repo: 'owner/test-repo' }),
upsertIssuesFromWorkItems: vi.fn(),
list: { selected: 0 },
refreshFromDatabase: vi.fn(),
// Force open to fail — note fsImpl is not passed (undefined) which
// is fine; the underlying openUrlInBrowser defaults to real fs.
// We need openUrl mock that returns false to simulate browser failure.
});

const msg = ctx.toast.lastMessage();
// Should show a toast (not crash) — either success or fallback
expect(msg).toBeTruthy();
});
// NOTE: The test that simulated both browser-open and clipboard failure
// was removed because it launches the system browser on some setups and
// interferes with local development. If you want to re-add it later,
// prefer mocking `openUrl`/`openUrlInBrowser` or set a TEST env guard in
// `src/utils/open-url.ts` so the browser isn't launched during tests.
});
Loading