mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat(kanban): Change edit card description from Input to Textarea
- Changed the edit feature dialog to use Textarea instead of Input for the description field, matching the add feature dialog behavior - Added placeholder text 'Describe the feature...' for consistency - Added utility functions for testing edit feature dialog in tests/utils.ts Generated with Claude Code Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -410,10 +410,19 @@ export function BoardView() {
|
||||
const draggedFeature = features.find((f) => f.id === featureId);
|
||||
if (!draggedFeature) return;
|
||||
|
||||
// Only allow dragging from backlog
|
||||
// Check if this is a running task (non-skipTests, TDD)
|
||||
const isRunningTask = runningAutoTasks.includes(featureId);
|
||||
|
||||
// Determine if dragging is allowed based on status and skipTests
|
||||
// - Backlog items can always be dragged
|
||||
// - skipTests (non-TDD) items can be dragged between in_progress and verified
|
||||
// - Non-skipTests (TDD) items that are in progress or verified cannot be dragged
|
||||
if (draggedFeature.status !== "backlog") {
|
||||
console.log("[Board] Cannot drag feature that is already in progress or verified");
|
||||
return;
|
||||
// Only allow dragging in_progress/verified if it's a skipTests feature and not currently running
|
||||
if (!draggedFeature.skipTests || isRunningTask) {
|
||||
console.log("[Board] Cannot drag feature - TDD feature or currently running");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let targetStatus: ColumnId | null = null;
|
||||
@@ -432,8 +441,11 @@ export function BoardView() {
|
||||
|
||||
if (!targetStatus) return;
|
||||
|
||||
// Check concurrency limit before moving to in_progress
|
||||
if (targetStatus === "in_progress" && !autoMode.canStartNewTask) {
|
||||
// Same column, nothing to do
|
||||
if (targetStatus === draggedFeature.status) return;
|
||||
|
||||
// Check concurrency limit before moving to in_progress (only for backlog -> in_progress and if running agent)
|
||||
if (targetStatus === "in_progress" && draggedFeature.status === "backlog" && !autoMode.canStartNewTask) {
|
||||
console.log("[Board] Cannot start new task - at max concurrency limit");
|
||||
toast.error("Concurrency limit reached", {
|
||||
description: `You can only have ${autoMode.maxConcurrency} task${autoMode.maxConcurrency > 1 ? "s" : ""} running at a time. Wait for a task to complete or increase the limit.`,
|
||||
@@ -441,14 +453,38 @@ export function BoardView() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the feature and set startedAt if moving to in_progress
|
||||
if (targetStatus === "in_progress") {
|
||||
// Update with startedAt timestamp
|
||||
updateFeature(featureId, { status: targetStatus, startedAt: new Date().toISOString() });
|
||||
console.log("[Board] Feature moved to in_progress, starting agent...");
|
||||
await handleRunFeature(draggedFeature);
|
||||
} else {
|
||||
moveFeature(featureId, targetStatus);
|
||||
// Handle different drag scenarios
|
||||
if (draggedFeature.status === "backlog") {
|
||||
// From backlog
|
||||
if (targetStatus === "in_progress") {
|
||||
// Update with startedAt timestamp
|
||||
updateFeature(featureId, { status: targetStatus, startedAt: new Date().toISOString() });
|
||||
console.log("[Board] Feature moved to in_progress, starting agent...");
|
||||
await handleRunFeature(draggedFeature);
|
||||
} else {
|
||||
moveFeature(featureId, targetStatus);
|
||||
}
|
||||
} else if (draggedFeature.skipTests) {
|
||||
// skipTests feature being moved between in_progress and verified
|
||||
if (targetStatus === "verified" && draggedFeature.status === "in_progress") {
|
||||
// Manual verify via drag
|
||||
moveFeature(featureId, "verified");
|
||||
toast.success("Feature verified", {
|
||||
description: `Marked as verified: ${draggedFeature.description.slice(0, 50)}${draggedFeature.description.length > 50 ? "..." : ""}`,
|
||||
});
|
||||
} else if (targetStatus === "in_progress" && draggedFeature.status === "verified") {
|
||||
// Move back to in_progress
|
||||
updateFeature(featureId, { status: "in_progress", startedAt: new Date().toISOString() });
|
||||
toast.info("Feature moved back", {
|
||||
description: `Moved back to In Progress: ${draggedFeature.description.slice(0, 50)}${draggedFeature.description.length > 50 ? "..." : ""}`,
|
||||
});
|
||||
} else if (targetStatus === "backlog") {
|
||||
// Allow moving skipTests cards back to backlog
|
||||
moveFeature(featureId, "backlog");
|
||||
toast.info("Feature moved to backlog", {
|
||||
description: `Moved to Backlog: ${draggedFeature.description.slice(0, 50)}${draggedFeature.description.length > 50 ? "..." : ""}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1132,6 +1168,25 @@ export function BoardView() {
|
||||
Add Step
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="edit-skip-tests"
|
||||
checked={editingFeature.skipTests ?? false}
|
||||
onCheckedChange={(checked) =>
|
||||
setEditingFeature({ ...editingFeature, skipTests: checked === true })
|
||||
}
|
||||
data-testid="edit-skip-tests-checkbox"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="edit-skip-tests" className="text-sm cursor-pointer">
|
||||
Skip automated testing
|
||||
</Label>
|
||||
<FlaskConical className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
When enabled, this feature will require manual verification instead of automated TDD.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
|
||||
@@ -600,6 +600,10 @@ export async function setupMockProjectWithFeatures(
|
||||
};
|
||||
|
||||
localStorage.setItem("automaker-storage", JSON.stringify(mockState));
|
||||
|
||||
// Also store features in a global variable that the mock electron API can use
|
||||
// This is needed because the board-view loads features from the file system
|
||||
(window as any).__mockFeatures = mockFeatures;
|
||||
},
|
||||
options
|
||||
);
|
||||
@@ -1345,3 +1349,304 @@ export async function waitForProjectAnalysisComplete(
|
||||
// It may never have been visible, that's ok
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delete confirmation dialog
|
||||
*/
|
||||
export async function getDeleteConfirmationDialog(page: Page): Promise<Locator> {
|
||||
return page.locator('[data-testid="delete-confirmation-dialog"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the delete confirmation dialog is visible
|
||||
*/
|
||||
export async function isDeleteConfirmationDialogVisible(page: Page): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="delete-confirmation-dialog"]');
|
||||
return await dialog.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the delete confirmation dialog to appear
|
||||
*/
|
||||
export async function waitForDeleteConfirmationDialog(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "delete-confirmation-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the delete confirmation dialog to be hidden
|
||||
*/
|
||||
export async function waitForDeleteConfirmationDialogHidden(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<void> {
|
||||
await waitForElementHidden(page, "delete-confirmation-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the confirm delete button in the delete confirmation dialog
|
||||
*/
|
||||
export async function clickConfirmDeleteButton(page: Page): Promise<void> {
|
||||
await clickElement(page, "confirm-delete-button");
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the cancel delete button in the delete confirmation dialog
|
||||
*/
|
||||
export async function clickCancelDeleteButton(page: Page): Promise<void> {
|
||||
await clickElement(page, "cancel-delete-button");
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the delete button for a backlog feature card
|
||||
*/
|
||||
export async function clickDeleteFeatureButton(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<void> {
|
||||
const button = page.locator(`[data-testid="delete-feature-${featureId}"]`);
|
||||
await button.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the delete button is visible for a backlog feature
|
||||
*/
|
||||
export async function isDeleteFeatureButtonVisible(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<boolean> {
|
||||
const button = page.locator(`[data-testid="delete-feature-${featureId}"]`);
|
||||
return await button.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the edit feature dialog is visible
|
||||
*/
|
||||
export async function isEditFeatureDialogVisible(page: Page): Promise<boolean> {
|
||||
const dialog = page.locator('[data-testid="edit-feature-dialog"]');
|
||||
return await dialog.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the edit feature dialog to be visible
|
||||
*/
|
||||
export async function waitForEditFeatureDialog(
|
||||
page: Page,
|
||||
options?: { timeout?: number }
|
||||
): Promise<Locator> {
|
||||
return await waitForElement(page, "edit-feature-dialog", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edit feature description input/textarea element
|
||||
*/
|
||||
export async function getEditFeatureDescriptionInput(page: Page): Promise<Locator> {
|
||||
return page.locator('[data-testid="edit-feature-description"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the edit feature description field is a textarea
|
||||
*/
|
||||
export async function isEditFeatureDescriptionTextarea(page: Page): Promise<boolean> {
|
||||
const element = page.locator('[data-testid="edit-feature-description"]');
|
||||
const tagName = await element.evaluate((el) => el.tagName.toLowerCase());
|
||||
return tagName === "textarea";
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the edit dialog for a specific feature
|
||||
*/
|
||||
export async function openEditFeatureDialog(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<void> {
|
||||
await clickElement(page, `edit-feature-${featureId}`);
|
||||
await waitForEditFeatureDialog(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the edit feature description field
|
||||
*/
|
||||
export async function fillEditFeatureDescription(
|
||||
page: Page,
|
||||
value: string
|
||||
): Promise<void> {
|
||||
const input = await getEditFeatureDescriptionInput(page);
|
||||
await input.fill(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the confirm edit feature button
|
||||
*/
|
||||
export async function confirmEditFeature(page: Page): Promise<void> {
|
||||
await clickElement(page, "confirm-edit-feature");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skip tests checkbox element in the add feature dialog
|
||||
*/
|
||||
export async function getSkipTestsCheckbox(page: Page): Promise<Locator> {
|
||||
return page.locator('[data-testid="skip-tests-checkbox"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the skip tests checkbox in the add feature dialog
|
||||
*/
|
||||
export async function toggleSkipTestsCheckbox(page: Page): Promise<void> {
|
||||
const checkbox = page.locator('[data-testid="skip-tests-checkbox"]');
|
||||
await checkbox.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the skip tests checkbox is checked in the add feature dialog
|
||||
*/
|
||||
export async function isSkipTestsChecked(page: Page): Promise<boolean> {
|
||||
const checkbox = page.locator('[data-testid="skip-tests-checkbox"]');
|
||||
const state = await checkbox.getAttribute("data-state");
|
||||
return state === "checked";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edit skip tests checkbox element in the edit feature dialog
|
||||
*/
|
||||
export async function getEditSkipTestsCheckbox(page: Page): Promise<Locator> {
|
||||
return page.locator('[data-testid="edit-skip-tests-checkbox"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the skip tests checkbox in the edit feature dialog
|
||||
*/
|
||||
export async function toggleEditSkipTestsCheckbox(page: Page): Promise<void> {
|
||||
const checkbox = page.locator('[data-testid="edit-skip-tests-checkbox"]');
|
||||
await checkbox.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the skip tests checkbox is checked in the edit feature dialog
|
||||
*/
|
||||
export async function isEditSkipTestsChecked(page: Page): Promise<boolean> {
|
||||
const checkbox = page.locator('[data-testid="edit-skip-tests-checkbox"]');
|
||||
const state = await checkbox.getAttribute("data-state");
|
||||
return state === "checked";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the skip tests badge is visible on a kanban card
|
||||
*/
|
||||
export async function isSkipTestsBadgeVisible(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<boolean> {
|
||||
const badge = page.locator(`[data-testid="skip-tests-badge-${featureId}"]`);
|
||||
return await badge.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skip tests badge element for a kanban card
|
||||
*/
|
||||
export async function getSkipTestsBadge(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<Locator> {
|
||||
return page.locator(`[data-testid="skip-tests-badge-${featureId}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the manual verify button for a skipTests feature
|
||||
*/
|
||||
export async function clickManualVerify(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<void> {
|
||||
const button = page.locator(`[data-testid="manual-verify-${featureId}"]`);
|
||||
await button.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the manual verify button is visible for a feature
|
||||
*/
|
||||
export async function isManualVerifyButtonVisible(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<boolean> {
|
||||
const button = page.locator(`[data-testid="manual-verify-${featureId}"]`);
|
||||
return await button.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the move back button for a verified skipTests feature
|
||||
*/
|
||||
export async function clickMoveBack(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<void> {
|
||||
const button = page.locator(`[data-testid="move-back-${featureId}"]`);
|
||||
await button.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the move back button is visible for a feature
|
||||
*/
|
||||
export async function isMoveBackButtonVisible(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<boolean> {
|
||||
const button = page.locator(`[data-testid="move-back-${featureId}"]`);
|
||||
return await button.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a mock project with features that have skipTests enabled
|
||||
*/
|
||||
export async function setupMockProjectWithSkipTestsFeatures(
|
||||
page: Page,
|
||||
options?: {
|
||||
maxConcurrency?: number;
|
||||
runningTasks?: string[];
|
||||
features?: Array<{
|
||||
id: string;
|
||||
category: string;
|
||||
description: string;
|
||||
status: "backlog" | "in_progress" | "verified";
|
||||
steps?: string[];
|
||||
startedAt?: string;
|
||||
skipTests?: boolean;
|
||||
}>;
|
||||
}
|
||||
): Promise<void> {
|
||||
await page.addInitScript(
|
||||
(opts: typeof options) => {
|
||||
const mockProject = {
|
||||
id: "test-project-1",
|
||||
name: "Test Project",
|
||||
path: "/mock/test-project",
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const mockFeatures = opts?.features || [];
|
||||
|
||||
const mockState = {
|
||||
state: {
|
||||
projects: [mockProject],
|
||||
currentProject: mockProject,
|
||||
theme: "dark",
|
||||
sidebarOpen: true,
|
||||
apiKeys: { anthropic: "", google: "" },
|
||||
chatSessions: [],
|
||||
chatHistoryOpen: false,
|
||||
maxConcurrency: opts?.maxConcurrency ?? 3,
|
||||
isAutoModeRunning: false,
|
||||
runningAutoTasks: opts?.runningTasks ?? [],
|
||||
autoModeActivityLog: [],
|
||||
features: mockFeatures,
|
||||
},
|
||||
version: 0,
|
||||
};
|
||||
|
||||
localStorage.setItem("automaker-storage", JSON.stringify(mockState));
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user