feat: initial LexAI Chrome extension scaffold
- WXT + React 18 + TypeScript setup - Background service worker (LLM API proxy) - Content script (floating toolbar + text selection) - Options page (provider/model/API key config) - Popup UI - Gitea Actions workflows (CI, preview, release) - Vitest unit tests + Playwright E2E setup - Supports: OpenAI, Anthropic, Groq, OpenRouter
This commit is contained in:
28
tests/e2e/extension.test.ts
Normal file
28
tests/e2e/extension.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('LexAI popup opens correctly', async ({ page }) => {
|
||||
// Navigate to extension popup
|
||||
await page.goto('chrome-extension://[EXTENSION_ID]/popup/index.html');
|
||||
|
||||
// Check LexAI branding is present
|
||||
await expect(page.locator('text=LexAI')).toBeVisible();
|
||||
|
||||
// Check settings button is present
|
||||
await expect(page.locator('button:has-text("Open Settings")')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Options page saves API key', async ({ page }) => {
|
||||
await page.goto('chrome-extension://[EXTENSION_ID]/options/index.html');
|
||||
|
||||
// Fill in API key
|
||||
await page.fill('input[type="password"]', 'sk-test-key');
|
||||
|
||||
// Select provider
|
||||
await page.selectOption('select:first-of-type', 'openai');
|
||||
|
||||
// Save
|
||||
await page.click('button:has-text("Save Settings")');
|
||||
|
||||
// Confirm saved
|
||||
await expect(page.locator('text=Saved!')).toBeVisible();
|
||||
});
|
||||
28
tests/unit/background.test.ts
Normal file
28
tests/unit/background.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
describe('Background Service Worker', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return error when no API key is configured', async () => {
|
||||
(chrome.storage.local.get as any).mockImplementation((keys: any, cb: any) => cb({}));
|
||||
|
||||
// Simulate the handleAnalyzeText function behavior
|
||||
const config = await new Promise<any>((resolve) =>
|
||||
chrome.storage.local.get(['provider', 'apiKey', 'model'], resolve)
|
||||
);
|
||||
|
||||
expect(config.apiKey).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should store API key correctly', async () => {
|
||||
const testConfig = { provider: 'openai', apiKey: 'sk-test123', model: 'gpt-4o-mini' };
|
||||
|
||||
await new Promise<void>((resolve) =>
|
||||
chrome.storage.local.set(testConfig, resolve)
|
||||
);
|
||||
|
||||
expect(chrome.storage.local.set).toHaveBeenCalledWith(testConfig, expect.any(Function));
|
||||
});
|
||||
});
|
||||
20
tests/unit/setup.ts
Normal file
20
tests/unit/setup.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// Mock Chrome extension APIs
|
||||
global.chrome = {
|
||||
storage: {
|
||||
local: {
|
||||
get: vi.fn((keys, cb) => cb({})),
|
||||
set: vi.fn((data, cb) => cb && cb()),
|
||||
},
|
||||
sync: {
|
||||
get: vi.fn((keys, cb) => cb({})),
|
||||
set: vi.fn((data, cb) => cb && cb()),
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
sendMessage: vi.fn(),
|
||||
onMessage: {
|
||||
addListener: vi.fn(),
|
||||
},
|
||||
openOptionsPage: vi.fn(),
|
||||
},
|
||||
} as any;
|
||||
Reference in New Issue
Block a user