diff --git a/.gitignore b/.gitignore index fd3dbb57..4ab89bfe 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,8 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# Playwright +/playwright-report/ +/test-results/ +/dev.log diff --git a/bun.lock b/bun.lock index fa5118f8..2aeafddf 100644 --- a/bun.lock +++ b/bun.lock @@ -39,7 +39,6 @@ "@upstash/redis": "^1.35.0", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", - "QCX": ".", "ai": "^4.3.19", "build": "^0.1.4", "class-variance-authority": "^0.7.1", @@ -80,6 +79,7 @@ "zod": "^3.23.8", }, "devDependencies": { + "@playwright/test": "^1.56.1", "@types/cookie": "^0.6.0", "@types/mapbox-gl": "^3.4.1", "@types/node": "^20.17.30", @@ -428,6 +428,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@playwright/test": ["@playwright/test@1.56.1", "", { "dependencies": { "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" } }, "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -1032,8 +1034,6 @@ "@vercel/speed-insights": ["@vercel/speed-insights@1.2.0", "", { "peerDependencies": { "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-y9GVzrUJ2xmgtQlzFP2KhVRoCglwfRQgjyfY607aU0hh0Un6d0OUyrJkjuAlsV18qR4zfoFPs/BiIj9YDS6Wzw=="], - "QCX": ["QCX@file:", { "dependencies": { "@ai-sdk/amazon-bedrock": "^1.1.6", "@ai-sdk/anthropic": "^1.2.12", "@ai-sdk/google": "^1.2.22", "@ai-sdk/openai": "^1.3.24", "@ai-sdk/xai": "^1.2.18", "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^5.0.1", "@mapbox/mapbox-gl-draw": "^1.5.0", "@modelcontextprotocol/sdk": "^1.13.0", "@radix-ui/react-alert-dialog": "^1.1.10", "@radix-ui/react-avatar": "^1.1.6", "@radix-ui/react-checkbox": "^1.2.2", "@radix-ui/react-collapsible": "^1.1.7", "@radix-ui/react-dialog": "^1.1.10", "@radix-ui/react-dropdown-menu": "^2.1.11", "@radix-ui/react-label": "^2.1.4", "@radix-ui/react-radio-group": "^1.3.4", "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slider": "^1.3.2", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-switch": "^1.2.2", "@radix-ui/react-tabs": "^1.1.9", "@radix-ui/react-toast": "^1.2.11", "@radix-ui/react-tooltip": "^1.2.3", "@smithery/cli": "^1.2.5", "@smithery/sdk": "^1.0.4", "@supabase/ssr": "^0.3.0", "@supabase/supabase-js": "^2.0.0", "@tailwindcss/typography": "^0.5.16", "@turf/turf": "^7.2.0", "@types/mapbox__mapbox-gl-draw": "^1.4.8", "@types/pg": "^8.15.4", "@upstash/redis": "^1.35.0", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", "QCX": ".", "ai": "^4.3.19", "build": "^0.1.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cookie": "^0.6.0", "dotenv": "^16.5.0", "drizzle-kit": "^0.31.1", "drizzle-orm": "^0.29.0", "embla-carousel-react": "^8.6.0", "exa-js": "^1.6.13", "framer-motion": "^12.15.0", "katex": "^0.16.22", "lottie-react": "^2.4.1", "lucide-react": "^0.507.0", "mapbox-gl": "^3.11.0", "next": "^15.3.3", "next-themes": "^0.3.0", "open-codex": "^0.1.30", "pg": "^8.16.2", "radix-ui": "^1.3.4", "react": "^19.1.0", "react-dom": "^19.1.0", "react-hook-form": "^7.56.2", "react-icons": "^5.5.0", "react-markdown": "^9.1.0", "react-textarea-autosize": "^8.5.9", "react-toastify": "^10.0.6", "rehype-external-links": "^3.0.0", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "smithery": "^0.5.2", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "use-mcp": "^0.0.9", "uuid": "^9.0.0", "zod": "^3.23.8" }, "devDependencies": { "@types/cookie": "^0.6.0", "@types/mapbox-gl": "^3.4.1", "@types/node": "^20.17.30", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@types/uuid": "^9.0.0", "cross-env": "^7.0.3", "eslint": "^8.57.1", "eslint-config-next": "^14.2.28", "postcss": "^8.5.3", "tailwindcss": "^3.4.17", "typescript": "^5.8.3" } }], - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -1462,7 +1462,7 @@ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -2012,6 +2012,10 @@ "pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="], + "playwright": ["playwright@1.56.1", "", { "dependencies": { "playwright-core": "1.56.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw=="], + + "playwright-core": ["playwright-core@1.56.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ=="], + "point-in-polygon": ["point-in-polygon@1.1.0", "", {}, "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw=="], "point-in-polygon-hao": ["point-in-polygon-hao@1.2.4", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ=="], @@ -2568,8 +2572,6 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "QCX/QCX": ["QCX@file:.", {}], - "ai/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="], @@ -2584,6 +2586,8 @@ "boxen/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cli-table3/@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index b0bf2166..f5506e22 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -130,6 +130,7 @@ export const ChatPanel = forwardRef(({ messages, i variant={'secondary'} className="rounded-full bg-secondary/80 group transition-all hover:scale-105 pointer-events-auto" onClick={() => handleClear()} + data-testid="new-chat-button" > New @@ -155,7 +156,7 @@ export const ChatPanel = forwardRef(({ messages, i {selectedFile.name} - @@ -191,6 +192,7 @@ export const ChatPanel = forwardRef(({ messages, i 'absolute top-1/2 transform -translate-y-1/2 left-3' )} onClick={handleAttachmentClick} + data-testid="attachment-button" > @@ -204,6 +206,7 @@ export const ChatPanel = forwardRef(({ messages, i placeholder="Explore" spellCheck={false} value={input} + data-testid="chat-input" className={cn( 'resize-none w-full min-h-12 rounded-fill border border-input pl-14 pr-12 pt-3 pb-1 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', isMobile @@ -247,6 +250,7 @@ export const ChatPanel = forwardRef(({ messages, i )} disabled={input.length === 0 && !selectedFile} aria-label="Send message" + data-testid="chat-submit" > diff --git a/components/header.tsx b/components/header.tsx index 010ba9a8..bd68ba94 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -38,13 +38,13 @@ export const Header = () => { -
- diff --git a/components/history-item.tsx b/components/history-item.tsx index 4040e3f8..12f101b0 100644 --- a/components/history-item.tsx +++ b/components/history-item.tsx @@ -59,6 +59,7 @@ const HistoryItem: React.FC = ({ chat }) => { 'flex flex-col hover:bg-muted cursor-pointer p-2 rounded border', isActive ? 'bg-muted/70 border-border' : 'border-transparent' )} + data-testid={`history-item-${chat.id}`} >
{chat.title} diff --git a/components/history.tsx b/components/history.tsx index 7cc8047b..621c8f9e 100644 --- a/components/history.tsx +++ b/components/history.tsx @@ -27,11 +27,12 @@ export function History({ location }: HistoryProps) { className={cn({ 'rounded-full text-foreground/30': location === 'sidebar' })} + data-testid="history-button" > {location === 'header' ? : } - + diff --git a/components/map-toggle.tsx b/components/map-toggle.tsx index 1ad35f59..0139db0f 100644 --- a/components/map-toggle.tsx +++ b/components/map-toggle.tsx @@ -17,19 +17,19 @@ export function MapToggle() { return ( - - {setMapType(MapToggleEnum.RealTimeMode)}}> + {setMapType(MapToggleEnum.RealTimeMode)}} data-testid="map-mode-live"> Live - {setMapType(MapToggleEnum.FreeMode)}}> + {setMapType(MapToggleEnum.FreeMode)}} data-testid="map-mode-mymaps"> My Maps - {setMapType(MapToggleEnum.DrawingMode)}}> + {setMapType(MapToggleEnum.DrawingMode)}} data-testid="map-mode-draw"> Draw & Measure diff --git a/components/mobile-icons-bar.tsx b/components/mobile-icons-bar.tsx index ce41c7d4..993caa76 100644 --- a/components/mobile-icons-bar.tsx +++ b/components/mobile-icons-bar.tsx @@ -17,6 +17,7 @@ import { import { History } from '@/components/history' import { MapToggle } from './map-toggle' import { ModeToggle } from './mode-toggle' +import { ProfileToggle } from './profile-toggle' import { useCalendarToggle } from './calendar-toggle-context' interface MobileIconsBarProps { @@ -35,26 +36,24 @@ export const MobileIconsBar: React.FC = ({ onAttachmentClic return (
- - + - - - - - diff --git a/components/mode-toggle.tsx b/components/mode-toggle.tsx index a5dbc2e7..d991efcb 100644 --- a/components/mode-toggle.tsx +++ b/components/mode-toggle.tsx @@ -25,7 +25,7 @@ export function ModeToggle() { return ( - - handleSectionChange(ProfileToggleEnum.Account)}> + handleSectionChange(ProfileToggleEnum.Account)} data-testid="profile-account"> Account - handleSectionChange(ProfileToggleEnum.Settings)}> + handleSectionChange(ProfileToggleEnum.Settings)} data-testid="profile-settings"> Settings - handleSectionChange(ProfileToggleEnum.Appearance)}> + handleSectionChange(ProfileToggleEnum.Appearance)} data-testid="profile-appearance"> Appearance - handleSectionChange(ProfileToggleEnum.Security)}> + handleSectionChange(ProfileToggleEnum.Security)} data-testid="profile-security"> Security diff --git a/components/sidebar/chat-history-client.tsx b/components/sidebar/chat-history-client.tsx index eeb959b3..9190dea7 100644 --- a/components/sidebar/chat-history-client.tsx +++ b/components/sidebar/chat-history-client.tsx @@ -129,7 +129,7 @@ export function ChatHistoryClient({}: ChatHistoryClientProps) {
- @@ -142,13 +142,14 @@ export function ChatHistoryClient({}: ChatHistoryClientProps) { - setIsAlertDialogOpen(false)}>Cancel + setIsAlertDialogOpen(false)} data-testid="clear-history-cancel">Cancel { event.preventDefault(); handleClearHistory(); }} + data-testid="clear-history-confirm" > {isClearPending ? : 'Clear'} diff --git a/mapbox_mcp/hooks.ts b/mapbox_mcp/hooks.ts index 326056db..06342b3f 100644 --- a/mapbox_mcp/hooks.ts +++ b/mapbox_mcp/hooks.ts @@ -8,7 +8,7 @@ type Tool = { name: string; // Add other properties as needed based on your usage }; -import { getModel } from 'QCX/lib/utils'; +import { getModel } from '@/lib/utils'; // Types for location and mapping data interface LocationResult { diff --git a/package.json b/package.json index 2e98b940..585377fc 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@playwright/test": "^1.56.1", "@types/cookie": "^0.6.0", "@types/mapbox-gl": "^3.4.1", "@types/node": "^20.17.30", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..16c7219e --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,42 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:3000', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + ], + webServer: { + command: process.env.CI ? 'bun run build && bun run start' : 'bun run dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + timeout: 600000, + }, +}); diff --git a/tests/chat.spec.ts b/tests/chat.spec.ts new file mode 100644 index 00000000..10b027ac --- /dev/null +++ b/tests/chat.spec.ts @@ -0,0 +1,46 @@ +import { test, expect } from '@playwright/test'; +import * as path from 'path'; + +test.describe('Chat functionality', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await page.waitForSelector('[data-testid="chat-input"]'); + }); + + test('should allow a user to send a message and see the response', async ({ page }) => { + await page.fill('[data-testid="chat-input"]', 'Hello, world!'); + await page.click('[data-testid="chat-submit"]'); + + const userMessage = page.locator('div.items-end'); + await expect(userMessage).toBeVisible(); + + const botMessage = page.locator('div.items-start'); + await expect(botMessage.last()).toBeVisible({ timeout: 15000 }); + }); + + test('should allow a user to attach a file', async ({ page }, testInfo) => { + const filePath = testInfo.outputPath('test-file.txt'); + await require('fs').promises.writeFile(filePath, 'This is a test file.'); + + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.click('[data-testid="attachment-button"]'); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(filePath); + + await expect(page.locator('text=test-file.txt')).toBeVisible(); + + await page.click('[data-testid="clear-attachment-button"]'); + await expect(page.locator('text=test-file.txt')).not.toBeVisible(); + }); + + test('should start a new chat', async ({ page }) => { + await page.fill('[data-testid="chat-input"]', 'First message'); + await page.click('[data-testid="chat-submit"]'); + + const userMessage = page.locator('div.items-end'); + await expect(userMessage).toBeVisible(); + + await page.click('[data-testid="new-chat-button"]'); + await expect(userMessage).not.toBeVisible(); + }); +}); diff --git a/tests/header.spec.ts b/tests/header.spec.ts new file mode 100644 index 00000000..55065420 --- /dev/null +++ b/tests/header.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Header and Navigation', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); + + test('should toggle the theme', async ({ page }) => { + await page.click('[data-testid="theme-toggle"]'); + await page.click('[data-testid="theme-dark"]'); + const html = page.locator('html'); + await expect(html).toHaveClass(/(^|\s)dark(\s|$)/); + + await page.click('[data-testid="theme-toggle"]'); + await page.click('[data-testid="theme-light"]'); + await expect(html).not.toHaveClass(/(^|\s)dark(\s|$)/); + }); + + test('should open the profile menu', async ({ page }) => { + await page.click('[data-testid="profile-toggle"]'); + const accountMenu = page.locator('[data-testid="profile-account"]'); + await expect(accountMenu).toBeVisible(); + }); + + test('should open the calendar', async ({ page }) => { + await page.click('[data-testid="calendar-toggle"]'); + const calendar = page.locator('[data-testid="calendar-notepad"]'); + await expect(calendar).toBeVisible(); + }); +}); diff --git a/tests/map.spec.ts b/tests/map.spec.ts new file mode 100644 index 00000000..595c4939 --- /dev/null +++ b/tests/map.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Map functionality', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + // Wait for the map to be loaded + await page.waitForSelector('.mapboxgl-canvas'); + }); + + test('should toggle the map mode', async ({ page }) => { + await page.click('[data-testid="map-toggle"]'); + await page.click('[data-testid="map-mode-draw"]'); + // Add an assertion here to verify that the map is in drawing mode + // For example, by checking for the presence of a drawing control + const drawControl = page.locator('.mapboxgl-ctrl-draw-btn'); + await expect(drawControl).toBeVisible(); + }); + + test('should zoom in and out using map controls', async ({ page }) => { + const hasMap = await page.evaluate(() => Boolean((window as any).map)); + if (!hasMap) test.skip(true, 'Map instance not available on window for E2E'); + + const getZoom = () => page.evaluate(() => (window as any).map.getZoom()); + + const initialZoom = await getZoom(); + await page.click('.mapboxgl-ctrl-zoom-in'); + await page.waitForFunction(() => (window as any).map.getZoom() > initialZoom); + const zoomedInZoom = await getZoom(); + expect(zoomedInZoom).toBeGreaterThan(initialZoom); + + await page.click('.mapboxgl-ctrl-zoom-out'); + await page.waitForFunction(() => (window as any).map.getZoom() < zoomedInZoom); + const zoomedOutZoom = await getZoom(); + expect(zoomedOutZoom).toBeLessThan(zoomedInZoom); + }); +}); diff --git a/tests/mobile.spec.ts b/tests/mobile.spec.ts new file mode 100644 index 00000000..d506f16e --- /dev/null +++ b/tests/mobile.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Mobile UI', () => { + test.use({ viewport: { width: 375, height: 667 } }); // iPhone 8 + + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); + + test('should interact with the mobile icons bar', async ({ page }) => { + // Test a few buttons on the mobile icons bar + await page.click('[data-testid="mobile-new-chat-button"]'); + // Add an assertion to verify the action, e.g., the chat is cleared + const userMessage = page.locator('div.items-end'); + await expect(userMessage).not.toBeVisible(); + + await page.click('[data-testid="profile-toggle"]'); + // Add an assertion to verify the profile menu opens + const accountMenu = page.locator('[data-testid="profile-account"]'); + await expect(accountMenu).toBeVisible(); + }); + + test('should have a disabled submit button', async ({ page }) => { + const submitButton = page.locator('[data-testid="mobile-submit-button"]'); + await expect(submitButton).toBeDisabled(); + }); +}); diff --git a/tests/sidebar.spec.ts b/tests/sidebar.spec.ts new file mode 100644 index 00000000..5b31a1d7 --- /dev/null +++ b/tests/sidebar.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Sidebar and Chat History', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await page.waitForSelector('[data-testid="chat-input"]'); + }); + + test('should open the history panel', async ({ page }) => { + await page.click('[data-testid="history-button"]'); + const historyPanel = page.locator('[data-testid="history-panel"]'); + await expect(historyPanel).toBeVisible(); + }); + + test('should clear the chat history', async ({ page }) => { + // First, send a message to create a history item + await page.fill('[data-testid="chat-input"]', 'Create history'); + await page.click('[data-testid="chat-submit"]'); + await page.waitForSelector('[data-testid^="history-item-"]'); + + // Now, open the history panel and clear the history + await page.click('[data-testid="history-button"]'); + await page.click('[data-testid="clear-history-button"]'); + await page.click('[data-testid="clear-history-confirm"]'); + + // Verify that the history is empty + const historyItem = page.locator('[data-testid^="history-item-"]'); + await expect(historyItem).not.toBeVisible(); + }); +});