Unverified 提交 3d942916 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

feat: resolve merge conflicts for app commands (#2538)

## Summary - Resolve stash merge conflicts in `app_handlers.ts` — use `updateAppCommands` handler without `withLock` - Resolve stash merge conflicts in `app_commands.spec.ts` — consolidate into a single E2E test covering validation, cancel, configure/edit/clear flows - Include updated `test_helper.ts` and `package-lock.json` ## Test plan - [ ] Verify `npm run ts` passes (type checks) - [ ] Verify `npm test` passes (unit tests) - [ ] Run app commands E2E test: `PLAYWRIGHT_HTML_OPEN=never npm run e2e -- --grep "configure app commands"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2538" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Removes per-app locking from `updateAppCommands`, which could allow concurrent updates to race and overwrite command values. Other changes are test and dependency/lockfile adjustments with limited production impact. > > **Overview** > Improves the app-commands E2E coverage by consolidating previously separate tests into a single `configure app commands` flow that now exercises **required-field validation**, **cancel**, **save/edit**, and **clear-to-default** behavior end-to-end. > > Updates the Playwright helper `waitForToastWithText` to use the shared `Timeout.MEDIUM` default (instead of a fixed 5s) to reduce CI flakiness. > > In `app_handlers.ts`, removes `withLock` usage from the `updateAppCommands` IPC handler while keeping the same trimming + “both-or-neither” validation before persisting commands. Also bumps the app version to `0.36.0` and refreshes `package-lock.json` accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4dfd2931629fee1d89af85a227c97981788fa0a9. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Resolved merge conflicts and finalized the app commands flow. Simplified the updateAppCommands handler and consolidated E2E coverage to reduce flakiness. - **Refactors** - updateAppCommands no longer uses withLock; validates paired commands and updates the DB directly. - Merged app commands E2E into a single test covering validation, cancel, and configure/edit/clear flows. - Standardized toast waiting to Timeout.MEDIUM in test_helper to reduce flakes. - Updated package-lock.json to 0.36.0. <sup>Written for commit 4dfd2931629fee1d89af85a227c97981788fa0a9. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> Co-authored-by: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
上级 3eb2c5e8
...@@ -13,9 +13,49 @@ test("configure app commands", async ({ po }) => { ...@@ -13,9 +13,49 @@ test("configure app commands", async ({ po }) => {
po.page.getByText("Using default install and start commands"), po.page.getByText("Using default install and start commands"),
).toBeVisible(); ).toBeVisible();
// --- Validation: both commands are required ---
// Click to configure custom commands // Click to configure custom commands
await po.page.getByTestId("configure-app-commands").click(); await po.page.getByTestId("configure-app-commands").click();
// Fill in only install command (leaving start command empty)
await po.page.getByTestId("install-command-input").fill("npm install");
// Verify validation message appears
await expect(
po.page.getByText("Both commands are required when customizing."),
).toBeVisible();
// Verify save button is disabled
const saveButton = po.page.getByTestId("save-app-commands");
await expect(saveButton).toBeDisabled();
// Now fill in both commands
await po.page.getByTestId("start-command-input").fill("npm run dev");
// Validation message should disappear
await expect(
po.page.getByText("Both commands are required when customizing."),
).not.toBeVisible();
// Save button should be enabled
await expect(saveButton).toBeEnabled();
// --- Cancel editing ---
// Cancel instead of saving
await po.page.getByTestId("cancel-edit-app-commands").click();
// Verify we're back to default state (commands were not saved)
await expect(
po.page.getByText("Using default install and start commands"),
).toBeVisible();
// --- Configure, edit, and clear commands ---
// Click to configure custom commands again
await po.page.getByTestId("configure-app-commands").click();
// Fill in custom install command // Fill in custom install command
await po.page.getByTestId("install-command-input").click(); await po.page.getByTestId("install-command-input").click();
await po.page.getByTestId("install-command-input").fill("npm install"); await po.page.getByTestId("install-command-input").fill("npm install");
...@@ -64,60 +104,3 @@ test("configure app commands", async ({ po }) => { ...@@ -64,60 +104,3 @@ test("configure app commands", async ({ po }) => {
po.page.getByText("Using default install and start commands"), po.page.getByText("Using default install and start commands"),
).toBeVisible(); ).toBeVisible();
}); });
test("app commands validation - both required", async ({ po }) => {
// Create an app first
await po.sendPrompt("tc=1");
// Navigate to configure panel
await po.selectPreviewMode("configure");
// Click to configure custom commands
await po.page.getByTestId("configure-app-commands").click();
// Fill in only install command (leaving start command empty)
await po.page.getByTestId("install-command-input").fill("npm install");
// Verify validation message appears
await expect(
po.page.getByText("Both commands are required when customizing."),
).toBeVisible();
// Verify save button is disabled (via the validation state)
const saveButton = po.page.getByTestId("save-app-commands");
await expect(saveButton).toBeDisabled();
// Now fill in both commands
await po.page.getByTestId("start-command-input").fill("npm run dev");
// Validation message should disappear
await expect(
po.page.getByText("Both commands are required when customizing."),
).not.toBeVisible();
// Save button should be enabled
await expect(saveButton).toBeEnabled();
});
test("app commands - cancel editing", async ({ po }) => {
// Create an app first
await po.sendPrompt("tc=1");
// Navigate to configure panel
await po.selectPreviewMode("configure");
// Click to configure custom commands
await po.page.getByTestId("configure-app-commands").click();
// Fill in commands
await po.page.getByTestId("install-command-input").fill("npm install");
await po.page.getByTestId("start-command-input").fill("npm run dev");
// Cancel instead of saving
await po.page.getByTestId("cancel-edit-app-commands").click();
// Verify we're back to default state (commands were not saved)
await expect(
po.page.getByText("Using default install and start commands"),
).toBeVisible();
});
...@@ -1453,7 +1453,7 @@ export class PageObject { ...@@ -1453,7 +1453,7 @@ export class PageObject {
await this.page.waitForSelector(selector, { timeout }); await this.page.waitForSelector(selector, { timeout });
} }
async waitForToastWithText(text: string, timeout = 5000) { async waitForToastWithText(text: string, timeout = Timeout.MEDIUM) {
await this.page.waitForSelector(`[data-sonner-toast]:has-text("${text}")`, { await this.page.waitForSelector(`[data-sonner-toast]:has-text("${text}")`, {
timeout, timeout,
}); });
......
差异被折叠。
...@@ -182,4 +182,4 @@ ...@@ -182,4 +182,4 @@
"node": ">=20" "node": ">=20"
}, },
"productName": "dyad" "productName": "dyad"
} }
\ No newline at end of file
...@@ -1837,35 +1837,33 @@ export function registerAppHandlers() { ...@@ -1837,35 +1837,33 @@ export function registerAppHandlers() {
createTypedHandler(appContracts.updateAppCommands, async (_, params) => { createTypedHandler(appContracts.updateAppCommands, async (_, params) => {
const { appId, installCommand, startCommand } = params; const { appId, installCommand, startCommand } = params;
return withLock(appId, async () => { const app = await db.query.apps.findFirst({
const app = await db.query.apps.findFirst({ where: eq(apps.id, appId),
where: eq(apps.id, appId), });
});
if (!app) { if (!app) {
throw new Error("App not found"); throw new Error("App not found");
} }
const trimmedInstall = installCommand?.trim() || null; const trimmedInstall = installCommand?.trim() || null;
const trimmedStart = startCommand?.trim() || null; const trimmedStart = startCommand?.trim() || null;
// Both commands must be provided together, or both must be null // Both commands must be provided together, or both must be null
if ((trimmedInstall === null) !== (trimmedStart === null)) { if ((trimmedInstall === null) !== (trimmedStart === null)) {
throw new Error( throw new Error(
"Both install and start commands are required when customizing", "Both install and start commands are required when customizing",
); );
} }
await db await db
.update(apps) .update(apps)
.set({ .set({
installCommand: trimmedInstall, installCommand: trimmedInstall,
startCommand: trimmedStart, startCommand: trimmedStart,
}) })
.where(eq(apps.id, appId)); .where(eq(apps.id, appId));
logger.info(`Updated commands for app ${appId}`); logger.info(`Updated commands for app ${appId}`);
});
}); });
createTypedHandler(appContracts.changeAppLocation, async (_, params) => { createTypedHandler(appContracts.changeAppLocation, async (_, params) => {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论