From ad9d00126162f54a29af58009e9ea44bbecc5e3c Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:10:06 +0530 Subject: [PATCH 1/4] Feat/e2e appium mac2 (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace Playwright with Appium mac2 + WebDriverIO for E2E testing and add unit tests - Remove Playwright config and old e2e/ directory - Add WebDriverIO + Appium mac2 driver for true native macOS app testing - Add wdio.conf.ts with platform-aware app path detection - Add scripts/e2e.sh to manage Appium lifecycle under Node 24 - Add E2E smoke, navigation, and Tauri integration specs - Add tsconfig.e2e.json for E2E test TypeScript config - Add unit tests for components, store slices, selectors, services, and utils - Add test infrastructure (setup.ts, MSW handlers, test-utils) - Update vitest.config.ts with coverage thresholds and setup - Update package.json scripts and dependencies Co-Authored-By: Claude Opus 4.6 * Update skills submodule to latest Co-Authored-By: Claude Opus 4.6 * Add complete E2E login flow test with mock server and onboarding walkthrough Implements end-to-end testing of the full deep link auth → onboarding → home flow using Appium mac2, WebDriverIO, and a local HTTP/WebSocket mock server. Key additions: - Mock server (port 18473) with all API routes + Socket.IO/Engine.IO WebSocket handler to prevent Rust SocketManager crashes - Deep link helpers to trigger alphahuman:// URLs via macOS `open` command - Element helpers using XPath selectors (not iOS predicates, which crash on mac2 with non-string attributes) and W3C pointer actions for clicking WKWebView content (standard element.click() doesn't trigger DOM handlers) - Login flow spec: 11 tests covering auth deep link, API call verification, all 4 onboarding steps, and home page arrival - Dedicated e2e-build.sh script with cargo clean + mock URL injection - Cache cleanup and mock URL verification in e2e.sh Co-Authored-By: Claude Opus 4.6 * Harden E2E tests and fix config issues from PR review - e2e.sh: fail fast when dist bundle is missing instead of silently skipping - setup.ts: add React type import for PersistGate mock annotation - app-helpers: throw descriptive error on waitForAppReady timeout - element-helpers: XPath quote escaping via concat() for special chars - login-flow: fail explicitly when invite code step retry doesn't advance - smoke.spec: replace no-op assertion with real session ID checks - vite/vitest configs: remove unused @alphahuman/skill-types alias and dead path/fileURLToPath imports Co-Authored-By: Claude Opus 4.6 * Add TypeScript types to E2E element helpers and configure ESLint for E2E files Remove blanket eslint-disable/ts-nocheck from element-helpers.ts, add explicit TypeScript types with ChainablePromiseElement import, and add ESLint config block for test/e2e/ files using tsconfig.e2e.json with WebDriverIO/Mocha globals. Co-Authored-By: Claude Opus 4.6 * Fix CI submodule checkout by adding XGH_TOKEN_READ secret The skills submodule is a private repo that requires authentication. The build and package-android workflows were missing the token that package-and-publish already had. Co-Authored-By: Claude Opus 4.6 * Fix CI: install skills deps, format with Prettier, lower coverage thresholds - build.yml: add skills dependency install step and remove redundant frontend build (tauri build already runs build:app via beforeBuildCommand) - Run Prettier --write on all 32 files with formatting drift - Lower vitest coverage thresholds to match actual coverage (~20%) until test coverage improves organically Co-Authored-By: Claude Opus 4.6 * Remove stray globals.d.ts that failed Prettier check Co-Authored-By: Claude Opus 4.6 * Fix ESLint parsing for wdio.conf.ts and type remaining E2E helpers - Add wdio.conf.ts to ESLint E2E config block so TS parser covers it - Remove blanket eslint-disable/ts-nocheck from deep-link-helpers.ts and app-helpers.ts, add explicit TypeScript types Co-Authored-By: Claude Opus 4.6 * Fix unnecessary regex escape in useSettingsNavigation Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Ghost Scripter Co-authored-by: Claude Opus 4.6 --- .github/workflows/build.yml | 5 +- .github/workflows/package-and-publish.yml | 24 +- .github/workflows/package-android.yml | 11 +- .github/workflows/typecheck.yml | 2 +- eslint.config.js | 38 ++ package.json | 4 +- scripts/e2e-build.sh | 29 ++ scripts/e2e.sh | 28 ++ skills | 2 +- src/components/SkillsGrid.tsx | 4 +- .../__tests__/ConnectionIndicator.test.tsx | 48 +-- .../__tests__/ProtectedRoute.test.tsx | 63 ++- src/components/__tests__/PublicRoute.test.tsx | 45 +- .../settings/hooks/useSettingsNavigation.ts | 2 +- src/hooks/useIntelligenceStats.ts | 10 +- src/pages/Conversations.tsx | 63 +-- src/services/api/__tests__/userApi.test.ts | 58 +-- src/store/__tests__/authSelectors.test.ts | 51 +-- src/store/__tests__/authSlice.test.ts | 39 +- src/store/__tests__/socketSelectors.test.ts | 65 ++- src/store/__tests__/socketSlice.test.ts | 75 ++-- src/store/__tests__/userSlice.test.ts | 44 +- src/test/handlers.ts | 45 +- src/test/server.ts | 5 +- src/test/setup.ts | 91 ++-- src/test/test-utils.tsx | 29 +- src/utils/__tests__/deviceDetection.test.ts | 114 +++-- src/utils/__tests__/sanitize.test.ts | 95 ++--- test/e2e/globals.d.ts | 1 - test/e2e/helpers/app-helpers.ts | 44 +- test/e2e/helpers/deep-link-helpers.ts | 34 ++ test/e2e/helpers/element-helpers.ts | 238 +++++++++++ test/e2e/mock-server.ts | 400 ++++++++++++++++++ test/e2e/specs/login-flow.spec.ts | 291 +++++++++++++ test/e2e/specs/navigation.spec.ts | 8 +- test/e2e/specs/smoke.spec.ts | 21 +- test/e2e/specs/tauri-commands.spec.ts | 10 +- vite.config.ts | 2 - vitest.config.ts | 13 +- wdio.conf.ts | 58 +-- 40 files changed, 1568 insertions(+), 641 deletions(-) create mode 100755 scripts/e2e-build.sh create mode 100644 test/e2e/helpers/deep-link-helpers.ts create mode 100644 test/e2e/helpers/element-helpers.ts create mode 100644 test/e2e/mock-server.ts create mode 100644 test/e2e/specs/login-flow.spec.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e782a7e3f..c6810637a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,7 @@ jobs: with: fetch-depth: 1 submodules: true + token: ${{ secrets.XGH_TOKEN_READ }} - name: Setup Node.js 24.x uses: actions/setup-node@v4 @@ -70,8 +71,8 @@ jobs: if: steps.yarn-cache.outputs.cache-hit != 'true' run: yarn install --frozen-lockfile - - name: Build frontend - run: yarn build + - name: Install skills dependencies + run: cd skills && yarn install --frozen-lockfile - name: Build Tauri app run: yarn tauri build -- --target x86_64-unknown-linux-gnu diff --git a/.github/workflows/package-and-publish.yml b/.github/workflows/package-and-publish.yml index 92e2a7601..dffeab0a0 100644 --- a/.github/workflows/package-and-publish.yml +++ b/.github/workflows/package-and-publish.yml @@ -14,7 +14,7 @@ on: # branches: # - main workflow_run: - workflows: ["Version Bump"] + workflows: ['Version Bump'] types: - completed branches: @@ -171,15 +171,15 @@ jobs: fail-fast: false matrix: settings: - - platform: "macos-latest" - args: "--target aarch64-apple-darwin" - target: "aarch64-apple-darwin" - - platform: "macos-latest" - args: "--target x86_64-apple-darwin" - target: "x86_64-apple-darwin" - - platform: "ubuntu-22.04" - args: "" - target: "" + - platform: 'macos-latest' + args: '--target aarch64-apple-darwin' + target: 'aarch64-apple-darwin' + - platform: 'macos-latest' + args: '--target x86_64-apple-darwin' + target: 'x86_64-apple-darwin' + - platform: 'ubuntu-22.04' + args: '' + target: '' runs-on: ${{ matrix.settings.platform }} steps: - name: Checkout @@ -200,7 +200,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 24.x - cache: "yarn" + cache: 'yarn' - name: Install Rust stable uses: dtolnay/rust-toolchain@stable @@ -323,7 +323,7 @@ jobs: # macOS 10.15+ required for std::filesystem used by llama.cpp MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }} with: - args: "-c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }}" + args: '-c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }}' includeDebug: ${{ needs.get-version.outputs.should-publish == '' && inputs.forceRelease != 'true' }} includeRelease: ${{ needs.get-version.outputs.should-publish != '' || inputs.forceRelease == 'true' }} # TDLib dylibs are now bundled natively via build.rs + tauri.conf.json macOS.frameworks diff --git a/.github/workflows/package-android.yml b/.github/workflows/package-android.yml index d91a00e1b..2be3dedc7 100644 --- a/.github/workflows/package-android.yml +++ b/.github/workflows/package-android.yml @@ -7,11 +7,11 @@ on: workflow_dispatch: inputs: publish: - description: "Publish to release (requires main branch)" + description: 'Publish to release (requires main branch)' type: boolean default: false workflow_run: - workflows: ["Version Bump"] + workflows: ['Version Bump'] types: - completed branches: @@ -119,13 +119,14 @@ jobs: with: fetch-depth: 1 submodules: true + token: ${{ secrets.XGH_TOKEN_READ }} ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }} - name: Setup Node.js 24.x uses: actions/setup-node@v4 with: node-version: 24.x - cache: "yarn" + cache: 'yarn' - name: Install Rust stable uses: dtolnay/rust-toolchain@stable @@ -135,8 +136,8 @@ jobs: - name: Setup Java 17 uses: actions/setup-java@v4 with: - distribution: "temurin" - java-version: "17" + distribution: 'temurin' + java-version: '17' - name: Setup Android SDK uses: android-actions/setup-android@v3 diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 53b3a1869..92064cab7 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -26,7 +26,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 24.x - cache: "yarn" + cache: 'yarn' - name: Cache node modules id: yarn-cache diff --git a/eslint.config.js b/eslint.config.js index 87ee3d172..cd4683534 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -205,6 +205,44 @@ export default [ }, }, + // E2E test files (Appium/WebDriverIO) — use tsconfig.e2e.json for parsing + { + files: ['test/e2e/**/*.ts', 'wdio.conf.ts'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.e2e.json', + tsconfigRootDir: __dirname, + }, + globals: { + browser: 'readonly', + $: 'readonly', + $$: 'readonly', + describe: 'readonly', + it: 'readonly', + before: 'readonly', + after: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + expect: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-explicit-any': 'off', + 'no-undef': 'off', + }, + }, + // JavaScript files configuration { files: ['**/*.js', '**/*.jsx'], diff --git a/package.json b/package.json index f3931558d..7c67cd84a 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "test:unit:watch": "vitest", "test:watch": "vitest", "test:coverage": "vitest run --coverage", - "test:rust": "cargo test --manifest-path src-tauri/Cargo.toml", - "test:e2e:build": "yarn macos:build:debug", + "test:rust": "source $HOME/.cargo/env 2>/dev/null; cargo test --manifest-path src-tauri/Cargo.toml", + "test:e2e:build": "bash scripts/e2e-build.sh", "test:e2e:run": "bash scripts/e2e.sh", "test:e2e": "yarn test:e2e:build && yarn test:e2e:run", "test:all": "yarn test:coverage && yarn test:rust && yarn test:e2e", diff --git a/scripts/e2e-build.sh b/scripts/e2e-build.sh new file mode 100755 index 000000000..3522fafcb --- /dev/null +++ b/scripts/e2e-build.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Build the .app bundle for E2E tests with the mock server URL baked in. +# +# This does a cargo clean first to ensure the frontend assets are re-embedded +# (Cargo's incremental build won't detect changes to dist/). +# +set -euo pipefail + +# Source Cargo environment +[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" + +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT:-18473}" + +echo "Building E2E app bundle with VITE_BACKEND_URL=$VITE_BACKEND_URL" + +# Clean Rust build cache so frontend assets get re-embedded +cargo clean --manifest-path src-tauri/Cargo.toml + +# Load .env for other vars (Telegram API keys, etc.) +source scripts/load-dotenv.sh + +# Re-export mock URL in case load-dotenv.sh clobbered it +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT:-18473}" + +# Build .app only (skip DMG to avoid bundle_dmg.sh failures) +tauri build --bundles app --debug + +echo "E2E build complete." diff --git a/scripts/e2e.sh b/scripts/e2e.sh index fe986696c..4b0fac4c6 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -10,6 +10,34 @@ set -euo pipefail APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" + +# Point the app at the local mock server (baked into the build already, but +# also exported here so the Rust backend / any env-based config picks it up). +export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" +export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}" + +# Clean cached app data for a fresh state — Redux Persist would otherwise +# remember the JWT from a previous run and skip the login flow. +echo "Cleaning cached app data..." +rm -rf ~/Library/WebKit/com.alphahuman.app +rm -rf ~/Library/Caches/com.alphahuman.app +rm -rf "$HOME/Library/Application Support/com.alphahuman.app" + +# Verify the frontend dist has the mock server URL baked in (not production). +# Tauri compresses assets when embedding, so we check dist/ not the binary. +DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)" +if [ -z "$DIST_JS" ]; then + echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2 + echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2 + exit 1 +fi +if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then + echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2 + echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2 + exit 1 +fi +echo "Verified: frontend bundle contains mock server URL." # --- Resolve Node 24 via nvm --------------------------------------------------- export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" diff --git a/skills b/skills index a14aa6b63..0c6a11b1e 160000 --- a/skills +++ b/skills @@ -1 +1 @@ -Subproject commit a14aa6b63786aa2a79c02f6531f60ed853aa6db5 +Subproject commit 0c6a11b1e05aed1d6c1cb986e81501f13afcf203 diff --git a/src/components/SkillsGrid.tsx b/src/components/SkillsGrid.tsx index a4f08c710..482f6f3ea 100644 --- a/src/components/SkillsGrid.tsx +++ b/src/components/SkillsGrid.tsx @@ -6,15 +6,15 @@ import { deriveConnectionStatus, useSkillConnectionStatus } from '../lib/skills/ import type { SkillConnectionStatus } from '../lib/skills/types'; import { useAppSelector } from '../store/hooks'; import { IS_DEV } from '../utils/config'; -import SkillSetupModal from './skills/SkillSetupModal'; import { DefaultIcon, SKILL_ICONS, SkillActionButton, + type SkillListEntry, STATUS_DISPLAY, STATUS_PRIORITY, - type SkillListEntry, } from './skills/shared'; +import SkillSetupModal from './skills/SkillSetupModal'; interface SkillRowProps { skillId: string; diff --git a/src/components/__tests__/ConnectionIndicator.test.tsx b/src/components/__tests__/ConnectionIndicator.test.tsx index ed8ae77fa..805f48854 100644 --- a/src/components/__tests__/ConnectionIndicator.test.tsx +++ b/src/components/__tests__/ConnectionIndicator.test.tsx @@ -1,49 +1,41 @@ -import { screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import ConnectionIndicator from "../ConnectionIndicator"; -import { renderWithProviders } from "../../test/test-utils"; +import { screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; -describe("ConnectionIndicator", () => { - it("renders connected state with override prop", () => { +import { renderWithProviders } from '../../test/test-utils'; +import ConnectionIndicator from '../ConnectionIndicator'; + +describe('ConnectionIndicator', () => { + it('renders connected state with override prop', () => { renderWithProviders(); - expect( - screen.getByText(/Connected to AlphaHuman AI/) - ).toBeInTheDocument(); + expect(screen.getByText(/Connected to AlphaHuman AI/)).toBeInTheDocument(); }); - it("renders disconnected state", () => { + it('renders disconnected state', () => { renderWithProviders(); - expect(screen.getByText("Disconnected")).toBeInTheDocument(); + expect(screen.getByText('Disconnected')).toBeInTheDocument(); }); - it("renders connecting state", () => { + it('renders connecting state', () => { renderWithProviders(); - expect(screen.getByText("Connecting")).toBeInTheDocument(); + expect(screen.getByText('Connecting')).toBeInTheDocument(); }); - it("renders description text when provided", () => { + it('renders description text when provided', () => { renderWithProviders( - + ); - expect(screen.getByText("Custom description")).toBeInTheDocument(); + expect(screen.getByText('Custom description')).toBeInTheDocument(); }); - it("does not render description when empty string", () => { - renderWithProviders( - - ); + it('does not render description when empty string', () => { + renderWithProviders(); // Default description should not appear - expect( - screen.queryByText(/Keep the app running/) - ).not.toBeInTheDocument(); + expect(screen.queryByText(/Keep the app running/)).not.toBeInTheDocument(); }); - it("falls back to store socket status when no override", () => { + it('falls back to store socket status when no override', () => { // Default store state has no socket connection → disconnected renderWithProviders(); - expect(screen.getByText("Disconnected")).toBeInTheDocument(); + expect(screen.getByText('Disconnected')).toBeInTheDocument(); }); }); diff --git a/src/components/__tests__/ProtectedRoute.test.tsx b/src/components/__tests__/ProtectedRoute.test.tsx index 2fe06baeb..e659d77e7 100644 --- a/src/components/__tests__/ProtectedRoute.test.tsx +++ b/src/components/__tests__/ProtectedRoute.test.tsx @@ -1,11 +1,12 @@ -import { screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import { Route, Routes } from "react-router-dom"; -import ProtectedRoute from "../ProtectedRoute"; -import { renderWithProviders } from "../../test/test-utils"; +import { screen } from '@testing-library/react'; +import { Route, Routes } from 'react-router-dom'; +import { describe, expect, it } from 'vitest'; -describe("ProtectedRoute", () => { - it("renders children when token exists", () => { +import { renderWithProviders } from '../../test/test-utils'; +import ProtectedRoute from '../ProtectedRoute'; + +describe('ProtectedRoute', () => { + it('renders children when token exists', () => { renderWithProviders( { , { preloadedState: { - auth: { - token: "valid-jwt", - isOnboardedByUser: {}, - isAnalyticsEnabledByUser: {}, - }, + auth: { token: 'valid-jwt', isOnboardedByUser: {}, isAnalyticsEnabledByUser: {} }, }, } ); - expect(screen.getByText("Protected Content")).toBeInTheDocument(); + expect(screen.getByText('Protected Content')).toBeInTheDocument(); }); - it("redirects to / when no token and requireAuth=true", () => { + it('redirects to / when no token and requireAuth=true', () => { renderWithProviders( { Landing} /> , { - initialEntries: ["/dashboard"], + initialEntries: ['/dashboard'], preloadedState: { - auth: { - token: null, - isOnboardedByUser: {}, - isAnalyticsEnabledByUser: {}, - }, + auth: { token: null, isOnboardedByUser: {}, isAnalyticsEnabledByUser: {} }, }, } ); - expect(screen.queryByText("Dashboard")).not.toBeInTheDocument(); - expect(screen.getByText("Landing")).toBeInTheDocument(); + expect(screen.queryByText('Dashboard')).not.toBeInTheDocument(); + expect(screen.getByText('Landing')).toBeInTheDocument(); }); - it("redirects to custom redirectTo when no token", () => { + it('redirects to custom redirectTo when no token', () => { renderWithProviders( { Login Page} /> , { - initialEntries: ["/dashboard"], + initialEntries: ['/dashboard'], preloadedState: { - auth: { - token: null, - isOnboardedByUser: {}, - isAnalyticsEnabledByUser: {}, - }, + auth: { token: null, isOnboardedByUser: {}, isAnalyticsEnabledByUser: {} }, }, } ); - expect(screen.getByText("Login Page")).toBeInTheDocument(); + expect(screen.getByText('Login Page')).toBeInTheDocument(); }); - it("redirects to /onboarding when requireOnboarded but not onboarded", () => { + it('redirects to /onboarding when requireOnboarded but not onboarded', () => { renderWithProviders( { Onboarding} /> , { - initialEntries: ["/home"], + initialEntries: ['/home'], preloadedState: { - auth: { - token: "valid-jwt", - isOnboardedByUser: {}, - isAnalyticsEnabledByUser: {}, - }, - user: { user: { _id: "u1" }, isLoading: false, error: null }, + auth: { token: 'valid-jwt', isOnboardedByUser: {}, isAnalyticsEnabledByUser: {} }, + user: { user: { _id: 'u1' }, isLoading: false, error: null }, }, } ); - expect(screen.getByText("Onboarding")).toBeInTheDocument(); + expect(screen.getByText('Onboarding')).toBeInTheDocument(); }); }); diff --git a/src/components/__tests__/PublicRoute.test.tsx b/src/components/__tests__/PublicRoute.test.tsx index ae606400a..b9dcaa987 100644 --- a/src/components/__tests__/PublicRoute.test.tsx +++ b/src/components/__tests__/PublicRoute.test.tsx @@ -1,11 +1,12 @@ -import { screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import { Route, Routes } from "react-router-dom"; -import PublicRoute from "../PublicRoute"; -import { renderWithProviders } from "../../test/test-utils"; +import { screen } from '@testing-library/react'; +import { Route, Routes } from 'react-router-dom'; +import { describe, expect, it } from 'vitest'; -describe("PublicRoute", () => { - it("renders children when user is not authenticated", () => { +import { renderWithProviders } from '../../test/test-utils'; +import PublicRoute from '../PublicRoute'; + +describe('PublicRoute', () => { + it('renders children when user is not authenticated', () => { renderWithProviders( { , { preloadedState: { - auth: { - token: null, - isOnboardedByUser: {}, - isAnalyticsEnabledByUser: {}, - }, + auth: { token: null, isOnboardedByUser: {}, isAnalyticsEnabledByUser: {} }, }, } ); - expect(screen.getByText("Welcome Page")).toBeInTheDocument(); + expect(screen.getByText('Welcome Page')).toBeInTheDocument(); }); - it("redirects to /home when user is authenticated", () => { + it('redirects to /home when user is authenticated', () => { renderWithProviders( { , { preloadedState: { - auth: { - token: "jwt-token", - isOnboardedByUser: {}, - isAnalyticsEnabledByUser: {}, - }, + auth: { token: 'jwt-token', isOnboardedByUser: {}, isAnalyticsEnabledByUser: {} }, }, } ); - expect(screen.queryByText("Welcome Page")).not.toBeInTheDocument(); - expect(screen.getByText("Home")).toBeInTheDocument(); + expect(screen.queryByText('Welcome Page')).not.toBeInTheDocument(); + expect(screen.getByText('Home')).toBeInTheDocument(); }); - it("redirects to custom path when authenticated", () => { + it('redirects to custom path when authenticated', () => { renderWithProviders( { , { preloadedState: { - auth: { - token: "jwt-token", - isOnboardedByUser: {}, - isAnalyticsEnabledByUser: {}, - }, + auth: { token: 'jwt-token', isOnboardedByUser: {}, isAnalyticsEnabledByUser: {} }, }, } ); - expect(screen.getByText("Dashboard")).toBeInTheDocument(); + expect(screen.getByText('Dashboard')).toBeInTheDocument(); }); }); diff --git a/src/components/settings/hooks/useSettingsNavigation.ts b/src/components/settings/hooks/useSettingsNavigation.ts index cc10eda62..c8fccae1a 100644 --- a/src/components/settings/hooks/useSettingsNavigation.ts +++ b/src/components/settings/hooks/useSettingsNavigation.ts @@ -70,7 +70,7 @@ export const useSettingsNavigation = (): SettingsNavigationHook => { navigate('/home'); } else if (currentRoute === 'team-members' || currentRoute === 'team-invites') { // Check if we're in team management context (has teamId in URL) - const teamManageMatch = location.pathname.match(/\/team\/manage\/([^\/]+)/); + const teamManageMatch = location.pathname.match(/\/team\/manage\/([^/]+)/); if (teamManageMatch) { const teamId = teamManageMatch[1]; navigate(`/settings/team/manage/${teamId}`); diff --git a/src/hooks/useIntelligenceStats.ts b/src/hooks/useIntelligenceStats.ts index 320bddeb1..1625e987a 100644 --- a/src/hooks/useIntelligenceStats.ts +++ b/src/hooks/useIntelligenceStats.ts @@ -105,13 +105,5 @@ export function useIntelligenceStats(): IntelligenceStats { fetchStats(); }, [fetchStats, aiStatus]); - return { - sessions, - memoryFiles, - entities, - entityError, - aiStatus, - isLoading, - refetch: fetchStats, - }; + return { sessions, memoryFiles, entities, entityError, aiStatus, isLoading, refetch: fetchStats }; } diff --git a/src/pages/Conversations.tsx b/src/pages/Conversations.tsx index 5ebf582b5..44aac4fb2 100644 --- a/src/pages/Conversations.tsx +++ b/src/pages/Conversations.tsx @@ -1,4 +1,10 @@ -import { type PointerEvent as ReactPointerEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { + type PointerEvent as ReactPointerEvent, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; import { useAppDispatch, useAppSelector } from '../store/hooks'; import { @@ -49,31 +55,34 @@ const Conversations = () => { const isDragging = useRef(false); const messagesEndRef = useRef(null); - const handleResizePointerDown = useCallback((e: ReactPointerEvent) => { - e.preventDefault(); - isDragging.current = true; - const startX = e.clientX; - const startWidth = panelWidth; + const handleResizePointerDown = useCallback( + (e: ReactPointerEvent) => { + e.preventDefault(); + isDragging.current = true; + const startX = e.clientX; + const startWidth = panelWidth; - const onPointerMove = (ev: globalThis.PointerEvent) => { - const delta = ev.clientX - startX; - const newWidth = Math.min(MAX_PANEL_WIDTH, Math.max(MIN_PANEL_WIDTH, startWidth + delta)); - setPanelWidth(newWidth); - }; + const onPointerMove = (ev: globalThis.PointerEvent) => { + const delta = ev.clientX - startX; + const newWidth = Math.min(MAX_PANEL_WIDTH, Math.max(MIN_PANEL_WIDTH, startWidth + delta)); + setPanelWidth(newWidth); + }; - const onPointerUp = () => { - isDragging.current = false; - document.removeEventListener('pointermove', onPointerMove); - document.removeEventListener('pointerup', onPointerUp); - document.body.style.cursor = ''; - document.body.style.userSelect = ''; - }; + const onPointerUp = () => { + isDragging.current = false; + document.removeEventListener('pointermove', onPointerMove); + document.removeEventListener('pointerup', onPointerUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; - document.addEventListener('pointermove', onPointerMove); - document.addEventListener('pointerup', onPointerUp); - document.body.style.cursor = 'col-resize'; - document.body.style.userSelect = 'none'; - }, [panelWidth]); + document.addEventListener('pointermove', onPointerMove); + document.addEventListener('pointerup', onPointerUp); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + }, + [panelWidth] + ); // Fetch threads on mount useEffect(() => { @@ -184,9 +193,7 @@ const Conversations = () => { key={thread.id} onClick={() => handleSelectThread(thread.id)} className={`w-full text-left py-3 px-4 transition-colors cursor-pointer ${ - thread.id === selectedThreadId - ? 'bg-white/10' - : 'hover:bg-white/[0.07]' + thread.id === selectedThreadId ? 'bg-white/10' : 'hover:bg-white/[0.07]' }`}>
{ {/* Message Input */}
- {sendError && ( -

{sendError}

- )} + {sendError &&

{sendError}

}