Fix stale app UI (supabase) & overall E2E test infra improvements (#337)
Fixes #269
This commit is contained in:
2
e2e-tests/fixtures/add-supabase.md
Normal file
2
e2e-tests/fixtures/add-supabase.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Adding supabase...
|
||||||
|
<dyad-add-integration provider="supabase"></dyad-add-integration>
|
||||||
@@ -258,6 +258,10 @@ export class PageObject {
|
|||||||
.click();
|
.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clickBackButton() {
|
||||||
|
await this.page.getByRole("button", { name: "Back" }).click();
|
||||||
|
}
|
||||||
|
|
||||||
async sendPrompt(prompt: string) {
|
async sendPrompt(prompt: string) {
|
||||||
await this.getChatInput().click();
|
await this.getChatInput().click();
|
||||||
await this.getChatInput().fill(prompt);
|
await this.getChatInput().fill(prompt);
|
||||||
@@ -382,6 +386,10 @@ export class PageObject {
|
|||||||
await this.page.getByTestId("app-details-more-options-button").click();
|
await this.page.getByTestId("app-details-more-options-button").click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clickConnectSupabaseButton() {
|
||||||
|
await this.page.getByTestId("connect-supabase-button").click();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// Settings related
|
// Settings related
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
- paragraph: tc=add-supabase
|
||||||
|
- paragraph: Adding supabase...
|
||||||
|
- text: Integrate with supabase?
|
||||||
|
- button "Set up supabase"
|
||||||
|
- button "Retry":
|
||||||
|
- img
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
- paragraph: tc=add-supabase
|
||||||
|
- paragraph: Adding supabase...
|
||||||
|
- img
|
||||||
|
- text: Supabase integration complete
|
||||||
|
- paragraph: "This app is connected to Supabase project: Fake Supabase Project"
|
||||||
|
- paragraph: Click the chat suggestion "Keep going" to continue.
|
||||||
|
- button "Retry":
|
||||||
|
- img
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
- paragraph: tc=add-supabase
|
||||||
|
- paragraph: Adding supabase...
|
||||||
|
- text: Integrate with supabase?
|
||||||
|
- button "Set up supabase"
|
||||||
|
- button "Retry":
|
||||||
|
- img
|
||||||
25
e2e-tests/supabase_stale_ui.spec.ts
Normal file
25
e2e-tests/supabase_stale_ui.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { testSkipIfWindows } from "./helpers/test_helper";
|
||||||
|
|
||||||
|
// https://github.com/dyad-sh/dyad/issues/269
|
||||||
|
testSkipIfWindows("supabase - stale ui", async ({ po }) => {
|
||||||
|
await po.setUp();
|
||||||
|
await po.sendPrompt("tc=add-supabase");
|
||||||
|
await po.snapshotMessages();
|
||||||
|
|
||||||
|
await po.page.getByText("Set up supabase").click();
|
||||||
|
// On app details page:
|
||||||
|
await po.clickConnectSupabaseButton();
|
||||||
|
// TODO: for some reason on Windows this navigates to the main (apps) page,
|
||||||
|
// rather than the original chat page, so this test is skipped on Windows.
|
||||||
|
// However, the underlying issue is platform-agnostic, so it seems OK to test
|
||||||
|
// only on Mac.
|
||||||
|
await po.clickBackButton();
|
||||||
|
|
||||||
|
// On chat page:
|
||||||
|
await po.snapshotMessages();
|
||||||
|
|
||||||
|
// Create a second app; do NOT integrate it with Supabase, and make sure UI is correct.
|
||||||
|
await po.goToAppsTab();
|
||||||
|
await po.sendPrompt("tc=add-supabase");
|
||||||
|
await po.snapshotMessages();
|
||||||
|
});
|
||||||
@@ -202,14 +202,22 @@ export function SupabaseConnector({ appId }: { appId: number }) {
|
|||||||
<div className="flex flex-col md:flex-row items-center justify-between">
|
<div className="flex flex-col md:flex-row items-center justify-between">
|
||||||
<h2 className="text-lg font-medium">Integrations</h2>
|
<h2 className="text-lg font-medium">Integrations</h2>
|
||||||
<img
|
<img
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
IpcClient.getInstance().openExternalUrl(
|
if (settings?.isTestMode) {
|
||||||
"https://supabase-oauth.dyad.sh/api/connect-supabase/login",
|
await IpcClient.getInstance().fakeHandleSupabaseConnect({
|
||||||
);
|
appId,
|
||||||
|
fakeProjectId: "fake-project-id",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await IpcClient.getInstance().openExternalUrl(
|
||||||
|
"https://supabase-oauth.dyad.sh/api/connect-supabase/login",
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
src={isDarkMode ? connectSupabaseDark : connectSupabaseLight}
|
src={isDarkMode ? connectSupabaseDark : connectSupabaseLight}
|
||||||
alt="Connect to Supabase"
|
alt="Connect to Supabase"
|
||||||
className="w-full h-10 min-h-8 min-w-20 cursor-pointer"
|
className="w-full h-10 min-h-8 min-w-20 cursor-pointer"
|
||||||
|
data-testid="connect-supabase-button"
|
||||||
// className="h-10"
|
// className="h-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ import React from "react";
|
|||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
||||||
import { useAtomValue, atom, useAtom } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { showError } from "@/lib/toast";
|
import { showError } from "@/lib/toast";
|
||||||
import { useStreamChat } from "@/hooks/useStreamChat";
|
|
||||||
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
|
|
||||||
import { useLoadApp } from "@/hooks/useLoadApp";
|
import { useLoadApp } from "@/hooks/useLoadApp";
|
||||||
|
|
||||||
interface DyadAddIntegrationProps {
|
interface DyadAddIntegrationProps {
|
||||||
@@ -17,20 +15,15 @@ interface DyadAddIntegrationProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSetupAtom = atom(false);
|
|
||||||
|
|
||||||
export const DyadAddIntegration: React.FC<DyadAddIntegrationProps> = ({
|
export const DyadAddIntegration: React.FC<DyadAddIntegrationProps> = ({
|
||||||
node,
|
node,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { streamMessage } = useStreamChat();
|
|
||||||
const [isSetup, setIsSetup] = useAtom(isSetupAtom);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { provider } = node.properties;
|
const { provider } = node.properties;
|
||||||
const appId = useAtomValue(selectedAppIdAtom);
|
const appId = useAtomValue(selectedAppIdAtom);
|
||||||
const { app } = useLoadApp(appId);
|
const { app } = useLoadApp(appId);
|
||||||
const selectedChatId = useAtomValue(selectedChatIdAtom);
|
|
||||||
|
|
||||||
const handleSetupClick = () => {
|
const handleSetupClick = () => {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
@@ -38,7 +31,6 @@ export const DyadAddIntegration: React.FC<DyadAddIntegrationProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate({ to: "/app-details", search: { appId } });
|
navigate({ to: "/app-details", search: { appId } });
|
||||||
setIsSetup(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (app?.supabaseProjectName) {
|
if (app?.supabaseProjectName) {
|
||||||
@@ -71,36 +63,18 @@ export const DyadAddIntegration: React.FC<DyadAddIntegrationProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-green-900">
|
<div className="text-sm text-green-900">
|
||||||
This app is connected to Supabase project:{" "}
|
<p>
|
||||||
<span className="font-mono font-medium bg-green-100 px-1 py-0.5 rounded">
|
This app is connected to Supabase project:{" "}
|
||||||
{app.supabaseProjectName}
|
<span className="font-mono font-medium bg-green-100 px-1 py-0.5 rounded">
|
||||||
</span>
|
{app.supabaseProjectName}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>Click the chat suggestion "Keep going" to continue.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSetup) {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsSetup(false);
|
|
||||||
if (!selectedChatId) {
|
|
||||||
showError("No chat ID found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
streamMessage({
|
|
||||||
prompt: "OK, I've setup Supabase. Continue",
|
|
||||||
chatId: selectedChatId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="my-1"
|
|
||||||
>
|
|
||||||
Continue | I've setup {provider}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 my-2 p-3 border rounded-md bg-secondary/10">
|
<div className="flex flex-col gap-2 my-2 p-3 border rounded-md bg-secondary/10">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const CodeView = ({ loading, app }: CodeViewProps) => {
|
|||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="flex items-center p-2 border-b space-x-2">
|
<div className="flex items-center p-2 border-b space-x-2">
|
||||||
<button
|
<button
|
||||||
onClick={refreshApp}
|
onClick={() => refreshApp()}
|
||||||
className="p-1 rounded hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="p-1 rounded hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
disabled={loading || !app.id}
|
disabled={loading || !app.id}
|
||||||
title="Refresh Files"
|
title="Refresh Files"
|
||||||
|
|||||||
@@ -1,61 +1,46 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useQuery, QueryClient } from "@tanstack/react-query";
|
||||||
import { IpcClient } from "@/ipc/ipc_client";
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { currentAppAtom } from "@/atoms/appAtoms";
|
import { currentAppAtom } from "@/atoms/appAtoms";
|
||||||
|
import { App } from "@/ipc/ipc_types";
|
||||||
|
|
||||||
export function useLoadApp(appId: number | null) {
|
export function useLoadApp(appId: number | null) {
|
||||||
const [app, setApp] = useAtom(currentAppAtom);
|
const [, setApp] = useAtom(currentAppAtom);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState<Error | null>(null);
|
const {
|
||||||
|
data: appData,
|
||||||
|
isLoading: loading,
|
||||||
|
error,
|
||||||
|
refetch: refreshApp,
|
||||||
|
} = useQuery<App | null, Error>({
|
||||||
|
queryKey: ["app", appId],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (appId === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const ipcClient = IpcClient.getInstance();
|
||||||
|
return ipcClient.getApp(appId);
|
||||||
|
},
|
||||||
|
enabled: appId !== null,
|
||||||
|
meta: { showErrorToast: true },
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadApp = async () => {
|
|
||||||
if (appId === null) {
|
|
||||||
setApp(null);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const ipcClient = IpcClient.getInstance();
|
|
||||||
const appData = await ipcClient.getApp(appId);
|
|
||||||
|
|
||||||
setApp(appData);
|
|
||||||
setError(null);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error loading app ${appId}:`, error);
|
|
||||||
setError(error instanceof Error ? error : new Error(String(error)));
|
|
||||||
setApp(null);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadApp();
|
|
||||||
}, [appId]);
|
|
||||||
|
|
||||||
const refreshApp = async () => {
|
|
||||||
if (appId === null) {
|
if (appId === null) {
|
||||||
return;
|
setApp(null);
|
||||||
}
|
} else if (appData !== undefined) {
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
console.log("Refreshing app", appId);
|
|
||||||
const ipcClient = IpcClient.getInstance();
|
|
||||||
const appData = await ipcClient.getApp(appId);
|
|
||||||
console.log("App data", appData);
|
|
||||||
setApp(appData);
|
setApp(appData);
|
||||||
setError(null);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error refreshing app ${appId}:`, error);
|
|
||||||
setError(error instanceof Error ? error : new Error(String(error)));
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
}, [appId, appData, setApp]);
|
||||||
|
|
||||||
return { app, loading, error, refreshApp };
|
return { app: appData, loading, error, refreshApp };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to invalidate the app query
|
||||||
|
export const invalidateAppQuery = (
|
||||||
|
queryClient: QueryClient,
|
||||||
|
{ appId }: { appId: number | null },
|
||||||
|
) => {
|
||||||
|
return queryClient.invalidateQueries({ queryKey: ["app", appId] });
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import log from "electron-log";
|
|||||||
import { createLoggedHandler } from "./safe_handle";
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
import { readSettings } from "../../main/settings"; // Assuming settings are read this way
|
import { readSettings } from "../../main/settings"; // Assuming settings are read this way
|
||||||
import { UserBudgetInfo, UserBudgetInfoSchema } from "../ipc_types";
|
import { UserBudgetInfo, UserBudgetInfoSchema } from "../ipc_types";
|
||||||
|
import { IS_TEST_BUILD } from "../utils/test_utils";
|
||||||
|
|
||||||
const logger = log.scope("pro_handlers");
|
const logger = log.scope("pro_handlers");
|
||||||
const handle = createLoggedHandler(logger);
|
const handle = createLoggedHandler(logger);
|
||||||
@@ -13,6 +14,10 @@ export function registerProHandlers() {
|
|||||||
// This method should try to avoid throwing errors because this is auxiliary
|
// This method should try to avoid throwing errors because this is auxiliary
|
||||||
// information and isn't critical to using the app
|
// information and isn't critical to using the app
|
||||||
handle("get-user-budget", async (): Promise<UserBudgetInfo | null> => {
|
handle("get-user-budget", async (): Promise<UserBudgetInfo | null> => {
|
||||||
|
if (IS_TEST_BUILD) {
|
||||||
|
// Avoid spamming the API in E2E tests.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
logger.info("Attempting to fetch user budget information.");
|
logger.info("Attempting to fetch user budget information.");
|
||||||
|
|
||||||
const settings = readSettings();
|
const settings = readSettings();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
|
import { IS_TEST_BUILD } from "../utils/test_utils";
|
||||||
|
|
||||||
export function createLoggedHandler(logger: log.LogFunctions) {
|
export function createLoggedHandler(logger: log.LogFunctions) {
|
||||||
return (
|
return (
|
||||||
@@ -27,3 +28,11 @@ export function createLoggedHandler(logger: log.LogFunctions) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createTestOnlyLoggedHandler(logger: log.LogFunctions) {
|
||||||
|
if (!IS_TEST_BUILD) {
|
||||||
|
// Returns a no-op function for non-e2e test builds.
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
return createLoggedHandler(logger);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ import { db } from "../../db";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { apps } from "../../db/schema";
|
import { apps } from "../../db/schema";
|
||||||
import { getSupabaseClient } from "../../supabase_admin/supabase_management_client";
|
import { getSupabaseClient } from "../../supabase_admin/supabase_management_client";
|
||||||
import { createLoggedHandler } from "./safe_handle";
|
import {
|
||||||
|
createLoggedHandler,
|
||||||
|
createTestOnlyLoggedHandler,
|
||||||
|
} from "./safe_handle";
|
||||||
|
import { handleSupabaseOAuthReturn } from "../../supabase_admin/supabase_return_handler";
|
||||||
|
|
||||||
const logger = log.scope("supabase_handlers");
|
const logger = log.scope("supabase_handlers");
|
||||||
const handle = createLoggedHandler(logger);
|
const handle = createLoggedHandler(logger);
|
||||||
|
const testOnlyHandle = createTestOnlyLoggedHandler(logger);
|
||||||
|
|
||||||
export function registerSupabaseHandlers() {
|
export function registerSupabaseHandlers() {
|
||||||
handle("supabase:list-projects", async () => {
|
handle("supabase:list-projects", async () => {
|
||||||
@@ -36,4 +41,42 @@ export function registerSupabaseHandlers() {
|
|||||||
|
|
||||||
logger.info(`Removed Supabase project association for app ${app}`);
|
logger.info(`Removed Supabase project association for app ${app}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testOnlyHandle(
|
||||||
|
"supabase:fake-connect-and-set-project",
|
||||||
|
async (
|
||||||
|
event,
|
||||||
|
{ appId, fakeProjectId }: { appId: number; fakeProjectId: string },
|
||||||
|
) => {
|
||||||
|
// Call handleSupabaseOAuthReturn with fake data
|
||||||
|
handleSupabaseOAuthReturn({
|
||||||
|
token: "fake-access-token",
|
||||||
|
refreshToken: "fake-refresh-token",
|
||||||
|
expiresIn: 3600, // 1 hour
|
||||||
|
});
|
||||||
|
logger.info(
|
||||||
|
`Called handleSupabaseOAuthReturn with fake data for app ${appId} during testing.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the supabase project for the currently selected app
|
||||||
|
await db
|
||||||
|
.update(apps)
|
||||||
|
.set({
|
||||||
|
supabaseProjectId: fakeProjectId,
|
||||||
|
})
|
||||||
|
.where(eq(apps.id, appId));
|
||||||
|
logger.info(
|
||||||
|
`Set fake Supabase project ${fakeProjectId} for app ${appId} during testing.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate the deep link event
|
||||||
|
event.sender.send("deep-link-received", {
|
||||||
|
type: "supabase-oauth-return",
|
||||||
|
url: "https://supabase-oauth.dyad.sh/api/connect-supabase/login",
|
||||||
|
});
|
||||||
|
logger.info(
|
||||||
|
`Sent fake deep-link-received event for app ${appId} during testing.`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -647,6 +647,17 @@ export class IpcClient {
|
|||||||
app,
|
app,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async fakeHandleSupabaseConnect(params: {
|
||||||
|
appId: number;
|
||||||
|
fakeProjectId: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
await this.ipcRenderer.invoke(
|
||||||
|
"supabase:fake-connect-and-set-project",
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// --- End Supabase Management ---
|
// --- End Supabase Management ---
|
||||||
|
|
||||||
public async getSystemDebugInfo(): Promise<SystemDebugInfo> {
|
public async getSystemDebugInfo(): Promise<SystemDebugInfo> {
|
||||||
|
|||||||
1
src/ipc/utils/test_utils.ts
Normal file
1
src/ipc/utils/test_utils.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const IS_TEST_BUILD = process.env.E2E_TEST_BUILD === "true";
|
||||||
@@ -9,6 +9,7 @@ import log from "electron-log";
|
|||||||
import { readSettings, writeSettings } from "./main/settings";
|
import { readSettings, writeSettings } from "./main/settings";
|
||||||
import { handleSupabaseOAuthReturn } from "./supabase_admin/supabase_return_handler";
|
import { handleSupabaseOAuthReturn } from "./supabase_admin/supabase_return_handler";
|
||||||
import { handleDyadProReturn } from "./main/pro";
|
import { handleDyadProReturn } from "./main/pro";
|
||||||
|
import { IS_TEST_BUILD } from "./ipc/utils/test_utils";
|
||||||
|
|
||||||
log.errorHandler.startCatching();
|
log.errorHandler.startCatching();
|
||||||
log.eventLogger.startLogging();
|
log.eventLogger.startLogging();
|
||||||
@@ -58,7 +59,7 @@ export async function onFirstRunMaybe() {
|
|||||||
hasRunBefore: true,
|
hasRunBefore: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (process.env.E2E_TEST_BUILD) {
|
if (IS_TEST_BUILD) {
|
||||||
writeSettings({
|
writeSettings({
|
||||||
isTestMode: true,
|
isTestMode: true,
|
||||||
});
|
});
|
||||||
@@ -73,7 +74,7 @@ async function promptMoveToApplicationsFolder(): Promise<void> {
|
|||||||
// Why not in e2e tests?
|
// Why not in e2e tests?
|
||||||
// There's no way to stub this dialog in time, so we just skip it
|
// There's no way to stub this dialog in time, so we just skip it
|
||||||
// in e2e testing mode.
|
// in e2e testing mode.
|
||||||
if (process.env.E2E_TEST_BUILD) return;
|
if (IS_TEST_BUILD) return;
|
||||||
if (process.platform !== "darwin") return;
|
if (process.platform !== "darwin") return;
|
||||||
if (app.isInApplicationsFolder()) return;
|
if (app.isInApplicationsFolder()) return;
|
||||||
logger.log("Prompting user to move to applications folder");
|
logger.log("Prompting user to move to applications folder");
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { ExternalLink } from "lucide-react";
|
import { ExternalLink } from "lucide-react";
|
||||||
import { ImportAppButton } from "@/components/ImportAppButton";
|
import { ImportAppButton } from "@/components/ImportAppButton";
|
||||||
import { showError } from "@/lib/toast";
|
import { showError } from "@/lib/toast";
|
||||||
|
import { invalidateAppQuery } from "@/hooks/useLoadApp";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
// Adding an export for attachments
|
// Adding an export for attachments
|
||||||
export interface HomeSubmitOptions {
|
export interface HomeSubmitOptions {
|
||||||
@@ -47,7 +49,7 @@ export default function HomePage() {
|
|||||||
const [releaseNotesOpen, setReleaseNotesOpen] = useState(false);
|
const [releaseNotesOpen, setReleaseNotesOpen] = useState(false);
|
||||||
const [releaseUrl, setReleaseUrl] = useState("");
|
const [releaseUrl, setReleaseUrl] = useState("");
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateLastVersionLaunched = async () => {
|
const updateLastVersionLaunched = async () => {
|
||||||
if (
|
if (
|
||||||
@@ -132,6 +134,7 @@ export default function HomePage() {
|
|||||||
setSelectedAppId(result.app.id);
|
setSelectedAppId(result.app.id);
|
||||||
setIsPreviewOpen(false);
|
setIsPreviewOpen(false);
|
||||||
await refreshApps(); // Ensure refreshApps is awaited if it's async
|
await refreshApps(); // Ensure refreshApps is awaited if it's async
|
||||||
|
await invalidateAppQuery(queryClient, { appId: result.app.id });
|
||||||
posthog.capture("home:chat-submit");
|
posthog.capture("home:chat-submit");
|
||||||
navigate({ to: "/chat", search: { id: result.chatId } });
|
navigate({ to: "/chat", search: { id: result.chatId } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
|
import { IS_TEST_BUILD } from "../ipc/utils/test_utils";
|
||||||
|
|
||||||
export function getDyadAppPath(appPath: string): string {
|
export function getDyadAppPath(appPath: string): string {
|
||||||
if (process.env.E2E_TEST_BUILD) {
|
if (IS_TEST_BUILD) {
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
return path.join(electron!.app.getPath("userData"), "dyad-apps", appPath);
|
return path.join(electron!.app.getPath("userData"), "dyad-apps", appPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,12 @@ const validInvokeChannels = [
|
|||||||
"rename-branch",
|
"rename-branch",
|
||||||
"clear-session-data",
|
"clear-session-data",
|
||||||
"get-user-budget",
|
"get-user-budget",
|
||||||
] as const;
|
// Test-only channels
|
||||||
|
// These should ALWAYS be guarded with IS_TEST_BUILD in the main process.
|
||||||
|
// We can't detect with IS_TEST_BUILD in the preload script because
|
||||||
|
// it's a separate process from the main process.
|
||||||
|
"supabase:fake-connect-and-set-project",
|
||||||
|
];
|
||||||
|
|
||||||
// Add valid receive channels
|
// Add valid receive channels
|
||||||
const validReceiveChannels = [
|
const validReceiveChannels = [
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
SupabaseManagementAPIError,
|
SupabaseManagementAPIError,
|
||||||
} from "@dyad-sh/supabase-management-js";
|
} from "@dyad-sh/supabase-management-js";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
|
import { IS_TEST_BUILD } from "../ipc/utils/test_utils";
|
||||||
|
|
||||||
const logger = log.scope("supabase_management_client");
|
const logger = log.scope("supabase_management_client");
|
||||||
|
|
||||||
@@ -122,6 +123,10 @@ export async function getSupabaseClient(): Promise<SupabaseManagementAPI> {
|
|||||||
export async function getSupabaseProjectName(
|
export async function getSupabaseProjectName(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
if (IS_TEST_BUILD) {
|
||||||
|
return "Fake Supabase Project";
|
||||||
|
}
|
||||||
|
|
||||||
const supabase = await getSupabaseClient();
|
const supabase = await getSupabaseClient();
|
||||||
const projects = await supabase.getProjects();
|
const projects = await supabase.getProjects();
|
||||||
const project = projects?.find((p) => p.id === projectId);
|
const project = projects?.find((p) => p.id === projectId);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import fsAsync from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { isIgnored } from "isomorphic-git";
|
import { isIgnored } from "isomorphic-git";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
|
import { IS_TEST_BUILD } from "../ipc/utils/test_utils";
|
||||||
|
|
||||||
const logger = log.scope("utils/codebase");
|
const logger = log.scope("utils/codebase");
|
||||||
|
|
||||||
@@ -369,7 +370,7 @@ export async function extractCodebase(appPath: string): Promise<{
|
|||||||
|
|
||||||
const endTime = Date.now();
|
const endTime = Date.now();
|
||||||
logger.log("extractCodebase: time taken", endTime - startTime);
|
logger.log("extractCodebase: time taken", endTime - startTime);
|
||||||
if (process.env.E2E_TEST_BUILD) {
|
if (IS_TEST_BUILD) {
|
||||||
// Why? For some reason, file ordering is not stable on Windows.
|
// Why? For some reason, file ordering is not stable on Windows.
|
||||||
// This is a workaround to ensure stable ordering, although
|
// This is a workaround to ensure stable ordering, although
|
||||||
// ideally we'd like to sort it by modification time which is
|
// ideally we'd like to sort it by modification time which is
|
||||||
@@ -400,7 +401,7 @@ async function sortFilesByModificationTime(files: string[]): Promise<string[]> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (process.env.E2E_TEST_BUILD) {
|
if (IS_TEST_BUILD) {
|
||||||
// Why? For some reason, file ordering is not stable on Windows.
|
// Why? For some reason, file ordering is not stable on Windows.
|
||||||
// This is a workaround to ensure stable ordering, although
|
// This is a workaround to ensure stable ordering, although
|
||||||
// ideally we'd like to sort it by modification time which is
|
// ideally we'd like to sort it by modification time which is
|
||||||
|
|||||||
Reference in New Issue
Block a user