diff --git a/examples/guide-example/src/App.tsx b/examples/guide-example/src/App.tsx
index ab59a88ce..9d3204fe8 100644
--- a/examples/guide-example/src/App.tsx
+++ b/examples/guide-example/src/App.tsx
@@ -65,6 +65,7 @@ function App() {
readyToTarget={true}
listenForUpdates={true}
colorMode={colorMode}
+ toolbar="v2"
>
Knock In-App Guide Example
diff --git a/packages/client/src/clients/guide/client.ts b/packages/client/src/clients/guide/client.ts
index a434eaa53..742c073d8 100644
--- a/packages/client/src/clients/guide/client.ts
+++ b/packages/client/src/clients/guide/client.ts
@@ -270,13 +270,15 @@ export class KnockGuideClient {
) {
const {
trackLocationFromWindow = true,
+ // TODO(KNO-11523): Remove once we ship guide toolbar v2, and offload as
+ // much debugging specific logic and responsibilities to toolbar.
+ trackDebugParams = false,
throttleCheckInterval = DEFAULT_COUNTER_INCREMENT_INTERVAL,
} = options;
const win = checkForWindow();
const location = trackLocationFromWindow ? win?.location?.href : undefined;
-
- const debug = detectDebugParams();
+ const debug = trackDebugParams ? detectDebugParams() : undefined;
this.store = new Store
({
guideGroups: [],
@@ -412,8 +414,9 @@ export class KnockGuideClient {
const params = {
...this.targetParams,
user_id: this.knock.userId,
- force_all_guides: debugState.forcedGuideKey ? true : undefined,
- preview_session_id: debugState.previewSessionId || undefined,
+ force_all_guides:
+ debugState?.forcedGuideKey || debugState?.debugging ? true : undefined,
+ preview_session_id: debugState?.previewSessionId || undefined,
};
const newChannel = this.socket.channel(this.socketChannelTopic, params);
@@ -561,6 +564,39 @@ export class KnockGuideClient {
}
}
+ setDebug(debugOpts?: Omit) {
+ this.knock.log("[Guide] .setDebug()");
+ const shouldRefetch = !this.store.state.debug?.debugging;
+
+ this.store.setState((state) => ({
+ ...state,
+ debug: { ...debugOpts, debugging: true },
+ }));
+
+ if (shouldRefetch) {
+ this.knock.log(
+ `[Guide] Start debugging, refetching guides and resubscribing to the websocket channel`,
+ );
+ this.fetch();
+ this.subscribe();
+ }
+ }
+
+ unsetDebug() {
+ this.knock.log("[Guide] .unsetDebug()");
+ const shouldRefetch = this.store.state.debug?.debugging;
+
+ this.store.setState((state) => ({ ...state, debug: undefined }));
+
+ if (shouldRefetch) {
+ this.knock.log(
+ `[Guide] Stop debugging, refetching guides and resubscribing to the websocket channel`,
+ );
+ this.fetch();
+ this.subscribe();
+ }
+ }
+
//
// Store selector
//
@@ -924,7 +960,7 @@ export class KnockGuideClient {
// Get the next unarchived step.
getStep() {
// If debugging this guide, return the first step regardless of archive status
- if (self.store.state.debug.forcedGuideKey === this.key) {
+ if (self.store.state.debug?.forcedGuideKey === this.key) {
return this.steps[0];
}
@@ -981,7 +1017,7 @@ export class KnockGuideClient {
// Append debug params
const debugState = this.store.state.debug;
- if (debugState.forcedGuideKey) {
+ if (debugState?.forcedGuideKey || debugState?.debugging) {
combinedParams.force_all_guides = true;
}
@@ -1150,8 +1186,15 @@ export class KnockGuideClient {
this.knock.log(`[Guide] Detected a location change: ${href}`);
+ if (!this.options.trackDebugParams) {
+ this.setLocation(href);
+ return;
+ }
+
+ // TODO(KNO-11523): Remove below once we ship toolbar v2.
+
// If entering debug mode, fetch all guides.
- const currentDebugParams = this.store.state.debug;
+ const currentDebugParams = this.store.state.debug || {};
const newDebugParams = detectDebugParams();
this.setLocation(href, { debug: newDebugParams });
diff --git a/packages/client/src/clients/guide/types.ts b/packages/client/src/clients/guide/types.ts
index 47e5f2093..0327d8270 100644
--- a/packages/client/src/clients/guide/types.ts
+++ b/packages/client/src/clients/guide/types.ts
@@ -202,6 +202,7 @@ export type QueryStatus = {
};
export type DebugState = {
+ debugging?: boolean;
forcedGuideKey?: string | null;
previewSessionId?: string | null;
};
@@ -218,7 +219,7 @@ export type StoreState = {
queries: Record;
location: string | undefined;
counter: number;
- debug: DebugState;
+ debug?: DebugState;
};
export type QueryFilterParams = Pick;
@@ -241,6 +242,7 @@ export type TargetParams = {
export type ConstructorOpts = {
trackLocationFromWindow?: boolean;
+ trackDebugParams?: boolean;
orderResolutionDuration?: number;
throttleCheckInterval?: number;
};
diff --git a/packages/client/test/clients/guide/guide.test.ts b/packages/client/test/clients/guide/guide.test.ts
index 2c43dbf8c..e68a710fd 100644
--- a/packages/client/test/clients/guide/guide.test.ts
+++ b/packages/client/test/clients/guide/guide.test.ts
@@ -165,7 +165,7 @@ describe("KnockGuideClient", () => {
queries: {},
location: undefined,
counter: 0,
- debug: { forcedGuideKey: null, previewSessionId: null },
+ debug: undefined,
});
});
@@ -194,7 +194,7 @@ describe("KnockGuideClient", () => {
queries: {},
location: "https://example.com",
counter: 0,
- debug: { forcedGuideKey: null, previewSessionId: null },
+ debug: undefined,
});
});
@@ -211,7 +211,7 @@ describe("KnockGuideClient", () => {
queries: {},
location: undefined,
counter: 0,
- debug: { forcedGuideKey: null, previewSessionId: null },
+ debug: undefined,
});
});
@@ -234,7 +234,7 @@ describe("KnockGuideClient", () => {
});
expect(() => {
- new KnockGuideClient(mockKnock, channelId);
+ new KnockGuideClient(mockKnock, channelId, {}, { trackDebugParams: true });
}).not.toThrow();
expect(mockLocalStorageWithErrors.setItem).toHaveBeenCalled();
@@ -2959,7 +2959,7 @@ describe("KnockGuideClient", () => {
mockKnock,
channelId,
{},
- { trackLocationFromWindow: true },
+ { trackLocationFromWindow: true, trackDebugParams: true },
);
client.store.state.debug = { forcedGuideKey: null };
@@ -3016,7 +3016,7 @@ describe("KnockGuideClient", () => {
mockKnock,
channelId,
{},
- { trackLocationFromWindow: true },
+ { trackLocationFromWindow: true, trackDebugParams: true },
);
client.store.state.debug = { forcedGuideKey: "test_guide" };
@@ -3271,4 +3271,118 @@ describe("KnockGuideClient", () => {
);
});
});
+
+ describe("setDebug", () => {
+ test("sets debug state with debugging: true", () => {
+ const client = new KnockGuideClient(mockKnock, channelId);
+ client.store.state.debug = undefined;
+
+ const fetchSpy = vi
+ .spyOn(client, "fetch")
+ .mockImplementation(() => Promise.resolve({ status: "ok" }));
+ const subscribeSpy = vi
+ .spyOn(client, "subscribe")
+ .mockImplementation(() => {});
+
+ client.setDebug();
+
+ expect(client.store.state.debug!.debugging!).toBe(true);
+
+ // calls fetch and subscribe when not already debugging
+ expect(fetchSpy).toHaveBeenCalled();
+ expect(subscribeSpy).toHaveBeenCalled();
+ });
+
+ test("sets debug state with provided options", () => {
+ const client = new KnockGuideClient(mockKnock, channelId);
+ client.store.state.debug = undefined;
+
+ vi.spyOn(client, "fetch").mockImplementation(() => Promise.resolve({ status: "ok" }));
+ vi.spyOn(client, "subscribe").mockImplementation(() => {});
+
+ client.setDebug({ forcedGuideKey: "test_guide" });
+
+ expect(client.store.state.debug!.debugging!).toBe(true);
+ expect(client.store.state.debug!.forcedGuideKey!).toBe("test_guide");
+ });
+
+ test("does not call fetch and subscribe when already debugging", () => {
+ const client = new KnockGuideClient(mockKnock, channelId);
+ client.store.state.debug = { debugging: true };
+
+ const fetchSpy = vi
+ .spyOn(client, "fetch")
+ .mockImplementation(() => Promise.resolve({ status: "ok" }));
+ const subscribeSpy = vi
+ .spyOn(client, "subscribe")
+ .mockImplementation(() => {});
+
+ client.setDebug();
+
+ expect(client.store.state.debug!.debugging!).toBe(true);
+
+ expect(fetchSpy).not.toHaveBeenCalled();
+ expect(subscribeSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("unsetDebug", () => {
+ test("sets debug state to undefined", () => {
+ const client = new KnockGuideClient(mockKnock, channelId);
+ client.store.state.debug = { debugging: true };
+
+ const fetchSpy = vi
+ .spyOn(client, "fetch")
+ .mockImplementation(() => Promise.resolve({ status: "ok" }));
+ const subscribeSpy = vi
+ .spyOn(client, "subscribe")
+ .mockImplementation(() => {});
+
+ client.unsetDebug();
+
+ expect(client.store.state.debug).toBe(undefined);
+
+ // calls fetch and subscribe when was debugging
+ expect(fetchSpy).toHaveBeenCalled();
+ expect(subscribeSpy).toHaveBeenCalled();
+ });
+
+ test("does not call fetch and subscribe when was not debugging", () => {
+ const client = new KnockGuideClient(mockKnock, channelId);
+ client.store.state.debug = undefined;
+
+ const fetchSpy = vi
+ .spyOn(client, "fetch")
+ .mockImplementation(() => Promise.resolve({ status: "ok" }));
+ const subscribeSpy = vi
+ .spyOn(client, "subscribe")
+ .mockImplementation(() => {});
+
+ client.unsetDebug();
+
+ expect(client.store.state.debug).toBe(undefined);
+
+ expect(fetchSpy).not.toHaveBeenCalled();
+ expect(subscribeSpy).not.toHaveBeenCalled();
+ });
+
+ test("does not call fetch and subscribe when debug exists but debugging is false", () => {
+ const client = new KnockGuideClient(mockKnock, channelId);
+ client.store.state.debug = { debugging: false };
+
+ const fetchSpy = vi
+ .spyOn(client, "fetch")
+ .mockImplementation(() => Promise.resolve({ status: "ok" }));
+ const subscribeSpy = vi
+ .spyOn(client, "subscribe")
+ .mockImplementation(() => {});
+
+ client.unsetDebug();
+
+ expect(client.store.state.debug).toBe(undefined);
+
+ expect(fetchSpy).not.toHaveBeenCalled();
+ expect(subscribeSpy).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx b/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx
index 0d2de3354..b69f3ba33 100644
--- a/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx
+++ b/packages/react-core/src/modules/guide/context/KnockGuideProvider.tsx
@@ -23,6 +23,7 @@ export type KnockGuideProviderProps = {
colorMode?: ColorMode;
targetParams?: KnockGuideTargetParams;
trackLocationFromWindow?: boolean;
+ trackDebugParams?: boolean;
orderResolutionDuration?: number; // in milliseconds
throttleCheckInterval?: number; // in milliseconds
};
@@ -36,6 +37,10 @@ export const KnockGuideProvider: React.FC<
colorMode = "light",
targetParams = {},
trackLocationFromWindow = true,
+ // Whether the guide client should look for debug params in url/local storage
+ // to launch guide toolbar. Set to true if using toolbar v1.
+ // TODO(KNO-11523): Remove this once we ship v2.
+ trackDebugParams = false,
// Default to 0 which works well for react apps as this "yields" to react for
// one render cyle first and close the group stage.
orderResolutionDuration = 0,
@@ -55,6 +60,7 @@ export const KnockGuideProvider: React.FC<
const knockGuideClient = React.useMemo(() => {
return new KnockGuideClient(knock, channelId, stableTargetParams, {
trackLocationFromWindow,
+ trackDebugParams,
orderResolutionDuration,
throttleCheckInterval,
});
@@ -63,6 +69,7 @@ export const KnockGuideProvider: React.FC<
channelId,
stableTargetParams,
trackLocationFromWindow,
+ trackDebugParams,
orderResolutionDuration,
throttleCheckInterval,
]);
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 80214ade0..97ee3fcfa 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -43,7 +43,6 @@ export {
Card,
CardView,
KnockGuideProvider,
- GuideToolbar as KnockGuideToolbar,
Modal,
ModalView,
} from "./modules/guide";
diff --git a/packages/react/src/modules/guide/components/GuideToolbar/index.ts b/packages/react/src/modules/guide/components/GuideToolbar/index.ts
deleted file mode 100644
index 54c13648f..000000000
--- a/packages/react/src/modules/guide/components/GuideToolbar/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { GuideToolbar } from "./GuideToolbar";
diff --git a/packages/react/src/modules/guide/components/Toolbar/KnockButton.tsx b/packages/react/src/modules/guide/components/Toolbar/KnockButton.tsx
new file mode 100644
index 000000000..e83066696
--- /dev/null
+++ b/packages/react/src/modules/guide/components/Toolbar/KnockButton.tsx
@@ -0,0 +1,51 @@
+import { Button } from "@telegraph/button";
+
+import { MAX_Z_INDEX } from "./shared";
+import "./styles.css";
+
+type Props = {
+ onClick: () => void;
+};
+
+export const KnockButton = ({ onClick }: Props) => {
+ return (
+
+ );
+};
diff --git a/packages/react/src/modules/guide/components/GuideToolbar/GuideToolbar.tsx b/packages/react/src/modules/guide/components/Toolbar/V1/V1.tsx
similarity index 57%
rename from packages/react/src/modules/guide/components/GuideToolbar/GuideToolbar.tsx
rename to packages/react/src/modules/guide/components/Toolbar/V1/V1.tsx
index cb6d3989f..c8f3e3f6c 100644
--- a/packages/react/src/modules/guide/components/GuideToolbar/GuideToolbar.tsx
+++ b/packages/react/src/modules/guide/components/Toolbar/V1/V1.tsx
@@ -6,13 +6,11 @@ import { Text } from "@telegraph/typography";
import { Minimize2, Undo2, Wrench } from "lucide-react";
import { useState } from "react";
-import "./styles.css";
+import { KnockButton } from "../KnockButton";
+import { MAX_Z_INDEX } from "../shared";
+import "../styles.css";
-// 'max' z index based on max value of a signed 32-bit int
-// Ref: https://stackoverflow.com/questions/491052/minimum-and-maximum-value-of-z-index/25461690#25461690
-const MAX_Z_INDEX = 2147483647;
-
-export const GuideToolbar = () => {
+export const V1 = () => {
const [isCollapsed, setIsCollapsed] = useState(false);
const { client } = useGuideContext();
@@ -31,46 +29,7 @@ export const GuideToolbar = () => {
};
if (isCollapsed) {
- return (
-
- );
+ return ;
}
return (
diff --git a/packages/react/src/modules/guide/components/Toolbar/V1/index.ts b/packages/react/src/modules/guide/components/Toolbar/V1/index.ts
new file mode 100644
index 000000000..f96635a89
--- /dev/null
+++ b/packages/react/src/modules/guide/components/Toolbar/V1/index.ts
@@ -0,0 +1 @@
+export { V1 as ToolbarV1 } from "./V1";
diff --git a/packages/react/src/modules/guide/components/Toolbar/V2/V2.tsx b/packages/react/src/modules/guide/components/Toolbar/V2/V2.tsx
new file mode 100644
index 000000000..b282c75f9
--- /dev/null
+++ b/packages/react/src/modules/guide/components/Toolbar/V2/V2.tsx
@@ -0,0 +1,109 @@
+import { useGuideContext, useStore } from "@knocklabs/react-core";
+import { Button } from "@telegraph/button";
+import { Box, Stack } from "@telegraph/layout";
+import { Text } from "@telegraph/typography";
+import { Minimize2, Undo2 } from "lucide-react";
+import React from "react";
+
+import { KnockButton } from "../KnockButton";
+import { MAX_Z_INDEX } from "../shared";
+import "../styles.css";
+
+import { detectToolbarParam } from "./helpers";
+
+const useInspectGuideClientStore = () => {
+ const { client } = useGuideContext();
+
+ const snapshot = useStore(client.store, (state) => {
+ return {
+ debug: state.debug,
+ };
+ });
+
+ if (!snapshot.debug?.debugging) {
+ return;
+ }
+
+ // TODO: Transform the raw client state into more useful data for debugging.
+ return {};
+};
+
+export const V2 = () => {
+ const { client } = useGuideContext();
+
+ const [isVisible, setIsVisible] = React.useState(detectToolbarParam());
+ const [isCollapsed, setIsCollapsed] = React.useState(true);
+
+ React.useEffect(() => {
+ if (!isVisible) {
+ return;
+ }
+
+ client.setDebug();
+
+ return () => {
+ client.unsetDebug();
+ };
+ }, [isVisible, client]);
+
+ const data = useInspectGuideClientStore();
+ if (!data) {
+ return null;
+ }
+
+ return (
+
+ {isCollapsed ? (
+ setIsCollapsed(false)} />
+ ) : (
+
+
+
+
+ Toolbar v2 placeholder
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
diff --git a/packages/react/src/modules/guide/components/Toolbar/V2/helpers.ts b/packages/react/src/modules/guide/components/Toolbar/V2/helpers.ts
new file mode 100644
index 000000000..09c0c7496
--- /dev/null
+++ b/packages/react/src/modules/guide/components/Toolbar/V2/helpers.ts
@@ -0,0 +1,17 @@
+import { checkForWindow } from "../../../../../modules/core";
+
+// Use this param to start Toolbar and enter into a debugging session when
+// it is present and set to true.
+const TOOLBAR_QUERY_PARAM = "knock_guide_toolbar";
+
+export const detectToolbarParam = () => {
+ const win = checkForWindow();
+ if (!win || !win.location) {
+ return false;
+ }
+
+ const urlSearchParams = new URLSearchParams(win.location.search);
+ const hasToolbarParam = urlSearchParams.get(TOOLBAR_QUERY_PARAM) === "true";
+
+ return hasToolbarParam;
+};
diff --git a/packages/react/src/modules/guide/components/Toolbar/V2/index.ts b/packages/react/src/modules/guide/components/Toolbar/V2/index.ts
new file mode 100644
index 000000000..80ecdca24
--- /dev/null
+++ b/packages/react/src/modules/guide/components/Toolbar/V2/index.ts
@@ -0,0 +1 @@
+export { V2 as ToolbarV2 } from "./V2";
diff --git a/packages/react/src/modules/guide/components/Toolbar/index.ts b/packages/react/src/modules/guide/components/Toolbar/index.ts
new file mode 100644
index 000000000..21d9f40a4
--- /dev/null
+++ b/packages/react/src/modules/guide/components/Toolbar/index.ts
@@ -0,0 +1,2 @@
+export { ToolbarV1 } from "./V1";
+export { ToolbarV2 } from "./V2";
diff --git a/packages/react/src/modules/guide/components/Toolbar/shared.ts b/packages/react/src/modules/guide/components/Toolbar/shared.ts
new file mode 100644
index 000000000..71f072fbc
--- /dev/null
+++ b/packages/react/src/modules/guide/components/Toolbar/shared.ts
@@ -0,0 +1,3 @@
+// 'max' z index based on max value of a signed 32-bit int
+// Ref: https://stackoverflow.com/questions/491052/minimum-and-maximum-value-of-z-index/25461690#25461690
+export const MAX_Z_INDEX = 2147483647;
diff --git a/packages/react/src/modules/guide/components/GuideToolbar/styles.css b/packages/react/src/modules/guide/components/Toolbar/styles.css
similarity index 100%
rename from packages/react/src/modules/guide/components/GuideToolbar/styles.css
rename to packages/react/src/modules/guide/components/Toolbar/styles.css
diff --git a/packages/react/src/modules/guide/components/index.ts b/packages/react/src/modules/guide/components/index.ts
index 8ebbde6b6..ac0527077 100644
--- a/packages/react/src/modules/guide/components/index.ts
+++ b/packages/react/src/modules/guide/components/index.ts
@@ -1,4 +1,4 @@
export { Banner, BannerView } from "./Banner";
export { Card, CardView } from "./Card";
-export { GuideToolbar } from "./GuideToolbar";
+export { ToolbarV1, ToolbarV2 } from "./Toolbar";
export { Modal, ModalView } from "./Modal";
diff --git a/packages/react/src/modules/guide/index.ts b/packages/react/src/modules/guide/index.ts
index ab8455a62..86f3b6ef5 100644
--- a/packages/react/src/modules/guide/index.ts
+++ b/packages/react/src/modules/guide/index.ts
@@ -5,6 +5,7 @@ export {
CardView,
Modal,
ModalView,
- GuideToolbar,
+ ToolbarV1,
+ ToolbarV2,
} from "./components";
export { KnockGuideProvider } from "./providers";
diff --git a/packages/react/src/modules/guide/providers/KnockGuideProvider.tsx b/packages/react/src/modules/guide/providers/KnockGuideProvider.tsx
index 2e5d0e751..2827837a0 100644
--- a/packages/react/src/modules/guide/providers/KnockGuideProvider.tsx
+++ b/packages/react/src/modules/guide/providers/KnockGuideProvider.tsx
@@ -4,17 +4,28 @@ import {
} from "@knocklabs/react-core";
import React from "react";
-import { GuideToolbar } from "../components";
+import { ToolbarV1, ToolbarV2 } from "../components";
-// Re-export the core KnockGuideProvider, so we can add React specific functionality
-// like the GuideToolbar component which shouldn't be included in other contexts (e.g. React Native).
-export const KnockGuideProvider: React.FC<
- React.PropsWithChildren
-> = ({ children, ...props }) => {
+type Props = KnockGuideProviderProps & {
+ toolbar?: "v1" | "v2";
+};
+
+// Re-export the core KnockGuideProvider, so we can add React specific
+// functionality like the Toolbar component which shouldn't be included in other
+// contexts (e.g. React Native).
+export const KnockGuideProvider: React.FC> = ({
+ children,
+ toolbar = "v1",
+ ...props
+}) => {
return (
-
+
{children}
-
+ {toolbar === "v2" ? : }
);
};
diff --git a/packages/react/test/guide/GuideToolbar.test.tsx b/packages/react/test/guide/Toolbar/V1/ToolbarV1.test.tsx
similarity index 88%
rename from packages/react/test/guide/GuideToolbar.test.tsx
rename to packages/react/test/guide/Toolbar/V1/ToolbarV1.test.tsx
index 107bce28c..d7e1f8a2a 100644
--- a/packages/react/test/guide/GuideToolbar.test.tsx
+++ b/packages/react/test/guide/Toolbar/V1/ToolbarV1.test.tsx
@@ -2,7 +2,7 @@ import "@testing-library/jest-dom/vitest";
import { fireEvent, render } from "@testing-library/react";
import { describe, expect, test, vi } from "vitest";
-import { GuideToolbar } from "../../src/modules/guide/components/GuideToolbar/GuideToolbar";
+import { ToolbarV1 } from "../../../../src/modules/guide/components/Toolbar";
const mockDebugState: { forcedGuideKey: string | null } = {
forcedGuideKey: "test-guide-key",
@@ -32,11 +32,11 @@ vi.mock("@knocklabs/react-core", async () => {
};
});
-describe("GuideToolbar", () => {
+describe("ToolbarV1", () => {
test("renders debug component when forcedGuideKey exists", () => {
mockDebugState.forcedGuideKey = "test-guide-key";
- const { getByText } = render();
+ const { getByText } = render();
expect(getByText("Debug")).toBeInTheDocument();
expect(getByText("test-guide-key")).toBeInTheDocument();
expect(getByText("Exit")).toBeInTheDocument();
@@ -45,7 +45,7 @@ describe("GuideToolbar", () => {
test("does not render when forcedGuideKey is null", () => {
mockDebugState.forcedGuideKey = null;
- const { container } = render();
+ const { container } = render();
expect(container.firstChild).toBeNull();
});
@@ -64,7 +64,7 @@ describe("GuideToolbar", () => {
mockDebugState.forcedGuideKey = "test-guide-key";
- const { getByText } = render();
+ const { getByText } = render();
const exitButton = getByText("Exit");
fireEvent.click(exitButton);