_oh.test.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
  2. import * as router from "react-router";
  3. import { createRoutesStub } from "react-router";
  4. import { screen, waitFor, within } from "@testing-library/react";
  5. import { renderWithProviders } from "test-utils";
  6. import userEvent from "@testing-library/user-event";
  7. import MainApp from "#/routes/_oh/route";
  8. import i18n from "#/i18n";
  9. import * as CaptureConsent from "#/utils/handle-capture-consent";
  10. import OpenHands from "#/api/open-hands";
  11. describe("frontend/routes/_oh", () => {
  12. const RouteStub = createRoutesStub([{ Component: MainApp, path: "/" }]);
  13. const { userIsAuthenticatedMock, settingsAreUpToDateMock } = vi.hoisted(
  14. () => ({
  15. userIsAuthenticatedMock: vi.fn(),
  16. settingsAreUpToDateMock: vi.fn(),
  17. }),
  18. );
  19. beforeAll(() => {
  20. vi.mock("#/utils/user-is-authenticated", () => ({
  21. userIsAuthenticated: userIsAuthenticatedMock.mockReturnValue(true),
  22. }));
  23. vi.mock("#/services/settings", async (importOriginal) => ({
  24. ...(await importOriginal<typeof import("#/services/settings")>()),
  25. settingsAreUpToDate: settingsAreUpToDateMock,
  26. }));
  27. });
  28. afterEach(() => {
  29. vi.clearAllMocks();
  30. localStorage.clear();
  31. });
  32. it("should render", async () => {
  33. renderWithProviders(<RouteStub />);
  34. await screen.findByTestId("root-layout");
  35. });
  36. it("should render the AI config modal if settings are not up-to-date", async () => {
  37. settingsAreUpToDateMock.mockReturnValue(false);
  38. renderWithProviders(<RouteStub />);
  39. await screen.findByTestId("ai-config-modal");
  40. });
  41. it("should not render the AI config modal if the settings are up-to-date", async () => {
  42. settingsAreUpToDateMock.mockReturnValue(true);
  43. renderWithProviders(<RouteStub />);
  44. await waitFor(() => {
  45. expect(screen.queryByTestId("ai-config-modal")).not.toBeInTheDocument();
  46. });
  47. });
  48. it("should render and capture the user's consent if oss mode", async () => {
  49. const user = userEvent.setup();
  50. const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
  51. const handleCaptureConsentSpy = vi.spyOn(
  52. CaptureConsent,
  53. "handleCaptureConsent",
  54. );
  55. getConfigSpy.mockResolvedValue({
  56. APP_MODE: "oss",
  57. GITHUB_CLIENT_ID: "test-id",
  58. POSTHOG_CLIENT_KEY: "test-key",
  59. });
  60. renderWithProviders(<RouteStub />);
  61. // The user has not consented to tracking
  62. const consentForm = await screen.findByTestId("user-capture-consent-form");
  63. expect(handleCaptureConsentSpy).not.toHaveBeenCalled();
  64. expect(localStorage.getItem("analytics-consent")).toBeNull();
  65. const submitButton = within(consentForm).getByRole("button", {
  66. name: /confirm preferences/i,
  67. });
  68. await user.click(submitButton);
  69. // The user has now consented to tracking
  70. expect(handleCaptureConsentSpy).toHaveBeenCalledWith(true);
  71. expect(localStorage.getItem("analytics-consent")).toBe("true");
  72. expect(
  73. screen.queryByTestId("user-capture-consent-form"),
  74. ).not.toBeInTheDocument();
  75. });
  76. it("should not render the user consent form if saas mode", async () => {
  77. const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
  78. getConfigSpy.mockResolvedValue({
  79. APP_MODE: "saas",
  80. GITHUB_CLIENT_ID: "test-id",
  81. POSTHOG_CLIENT_KEY: "test-key",
  82. });
  83. renderWithProviders(<RouteStub />);
  84. await waitFor(() => {
  85. expect(
  86. screen.queryByTestId("user-capture-consent-form"),
  87. ).not.toBeInTheDocument();
  88. });
  89. });
  90. it("should not render the user consent form if the user has already made a decision", async () => {
  91. localStorage.setItem("analytics-consent", "true");
  92. renderWithProviders(<RouteStub />);
  93. await waitFor(() => {
  94. expect(
  95. screen.queryByTestId("user-capture-consent-form"),
  96. ).not.toBeInTheDocument();
  97. });
  98. });
  99. // TODO: Likely failing due to how tokens are now handled in context. Move to e2e tests
  100. it.skip("should render a new project button if a token is set", async () => {
  101. localStorage.setItem("token", "test-token");
  102. const { rerender } = renderWithProviders(<RouteStub />);
  103. await screen.findByTestId("new-project-button");
  104. localStorage.removeItem("token");
  105. rerender(<RouteStub />);
  106. await waitFor(() => {
  107. expect(
  108. screen.queryByTestId("new-project-button"),
  109. ).not.toBeInTheDocument();
  110. });
  111. });
  112. // TODO: Move to e2e tests
  113. it.skip("should update the i18n language when the language settings change", async () => {
  114. const changeLanguageSpy = vi.spyOn(i18n, "changeLanguage");
  115. const { rerender } = renderWithProviders(<RouteStub />);
  116. // The default language is English
  117. expect(changeLanguageSpy).toHaveBeenCalledWith("en");
  118. localStorage.setItem("LANGUAGE", "es");
  119. rerender(<RouteStub />);
  120. expect(changeLanguageSpy).toHaveBeenCalledWith("es");
  121. rerender(<RouteStub />);
  122. // The language has not changed, so the spy should not have been called again
  123. expect(changeLanguageSpy).toHaveBeenCalledTimes(2);
  124. });
  125. // FIXME: logoutCleanup has been replaced with a hook
  126. it.skip("should call logoutCleanup after a logout", async () => {
  127. const user = userEvent.setup();
  128. localStorage.setItem("ghToken", "test-token");
  129. // const logoutCleanupSpy = vi.spyOn(LogoutCleanup, "logoutCleanup");
  130. renderWithProviders(<RouteStub />);
  131. const userActions = await screen.findByTestId("user-actions");
  132. const userAvatar = within(userActions).getByTestId("user-avatar");
  133. await user.click(userAvatar);
  134. const logout = within(userActions).getByRole("button", { name: /logout/i });
  135. await user.click(logout);
  136. // expect(logoutCleanupSpy).toHaveBeenCalled();
  137. expect(localStorage.getItem("ghToken")).toBeNull();
  138. });
  139. });