mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
test: COD-30 stabilize focused test suites
This commit is contained in:
@@ -5,16 +5,22 @@ import { PORTS, KEYBOARD, SELECTORS, BODY_CLASSES, WAIT } from './helpers/consta
|
|||||||
import { createTestServer, stopTestServer } from './helpers/server.js';
|
import { createTestServer, stopTestServer } from './helpers/server.js';
|
||||||
import { createDevicePage, getBrowser, closeAllBrowsers } from './helpers/browser.js';
|
import { createDevicePage, getBrowser, closeAllBrowsers } from './helpers/browser.js';
|
||||||
import {
|
import {
|
||||||
showKeyboard, hideKeyboard,
|
showKeyboard,
|
||||||
showKeyboardViaCDP, hideKeyboardViaCDP,
|
hideKeyboard,
|
||||||
showKeyboardViaMock, hideKeyboardViaMock,
|
showKeyboardViaCDP,
|
||||||
showKeyboardViaDOM, hideKeyboardViaDOM,
|
hideKeyboardViaCDP,
|
||||||
|
showKeyboardViaMock,
|
||||||
|
hideKeyboardViaMock,
|
||||||
|
showKeyboardViaDOM,
|
||||||
|
hideKeyboardViaDOM,
|
||||||
setupViewportMock,
|
setupViewportMock,
|
||||||
} from './helpers/keyboard-sim.js';
|
} from './helpers/keyboard-sim.js';
|
||||||
import { getCDP, setVisualViewportHeight } from './helpers/cdp.js';
|
import { getCDP, setVisualViewportHeight } from './helpers/cdp.js';
|
||||||
import {
|
import {
|
||||||
assertHasClass, assertNotHasClass,
|
assertHasClass,
|
||||||
assertVisible, assertHidden,
|
assertNotHasClass,
|
||||||
|
assertVisible,
|
||||||
|
assertHidden,
|
||||||
getCSSProperty,
|
getCSSProperty,
|
||||||
} from './helpers/assertions.js';
|
} from './helpers/assertions.js';
|
||||||
import { REPRESENTATIVE_DEVICES } from './devices.js';
|
import { REPRESENTATIVE_DEVICES } from './devices.js';
|
||||||
@@ -167,9 +173,7 @@ describe('Virtual Keyboard', () => {
|
|||||||
const success = await showKeyboardViaMock(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
|
const success = await showKeyboardViaMock(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
|
||||||
expect(success).toBe(true);
|
expect(success).toBe(true);
|
||||||
|
|
||||||
const hasClass = await page.evaluate(() =>
|
const hasClass = await page.evaluate(() => document.body.classList.contains('keyboard-visible'));
|
||||||
document.body.classList.contains('keyboard-visible'),
|
|
||||||
);
|
|
||||||
expect(hasClass).toBe(true);
|
expect(hasClass).toBe(true);
|
||||||
} finally {
|
} finally {
|
||||||
await context.close();
|
await context.close();
|
||||||
@@ -280,12 +284,13 @@ describe('Virtual Keyboard', () => {
|
|||||||
expect(mainPadding).toBe('');
|
expect(mainPadding).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accessory bar has 7 action buttons', async () => {
|
it('accessory bar has the simple-mode action buttons', async () => {
|
||||||
const count = await page.evaluate(() => {
|
const actions = await page.evaluate(() => {
|
||||||
const buttons = document.querySelectorAll('.keyboard-accessory-bar [data-action]');
|
return Array.from(document.querySelectorAll('.keyboard-accessory-bar [data-action]')).map(
|
||||||
return buttons.length;
|
(button) => (button as HTMLElement).dataset.action
|
||||||
|
);
|
||||||
});
|
});
|
||||||
expect(count).toBe(7);
|
expect(actions).toEqual(['scroll-up', 'scroll-down', 'init', 'clear', 'paste', 'dismiss']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('double-tap confirm on /clear button', async () => {
|
it('double-tap confirm on /clear button', async () => {
|
||||||
@@ -321,27 +326,6 @@ describe('Virtual Keyboard', () => {
|
|||||||
expect(text).toBe('Tap again');
|
expect(text).toBe('Tap again');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('double-tap confirm on /compact button', async () => {
|
|
||||||
await showKeyboard(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
|
|
||||||
await page.waitForTimeout(WAIT.KEYBOARD_ANIMATION);
|
|
||||||
|
|
||||||
await page.evaluate(`
|
|
||||||
if (typeof app !== 'undefined') app.activeSessionId = 'test-session';
|
|
||||||
`);
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
const btn = document.querySelector('[data-action="compact"]') as HTMLElement;
|
|
||||||
btn?.click();
|
|
||||||
});
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
const confirming = await page.evaluate(() => {
|
|
||||||
const btn = document.querySelector('[data-action="compact"]');
|
|
||||||
return btn?.classList.contains('confirming') ?? false;
|
|
||||||
});
|
|
||||||
expect(confirming).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('double-tap expires after 2s', async () => {
|
it('double-tap expires after 2s', async () => {
|
||||||
await showKeyboard(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
|
await showKeyboard(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
|
||||||
await page.waitForTimeout(WAIT.KEYBOARD_ANIMATION);
|
await page.waitForTimeout(WAIT.KEYBOARD_ANIMATION);
|
||||||
|
|||||||
@@ -1256,7 +1256,7 @@ describe('Operation Lightspeed', () => {
|
|||||||
await Promise.all(ids.map((id) => deleteSession(baseUrl, id)));
|
await Promise.all(ids.map((id) => deleteSession(baseUrl, id)));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly filter SSE under concurrent session lifecycle', async () => {
|
it('should broadcast lifecycle events while filtering concurrent session terminal streams', async () => {
|
||||||
// Create 2 sessions
|
// Create 2 sessions
|
||||||
const target = await createSession(baseUrl);
|
const target = await createSession(baseUrl);
|
||||||
const other = await createSession(baseUrl);
|
const other = await createSession(baseUrl);
|
||||||
@@ -1309,15 +1309,21 @@ describe('Operation Lightspeed', () => {
|
|||||||
|
|
||||||
const events = parseSSEEvents(receivedData);
|
const events = parseSSEEvents(receivedData);
|
||||||
|
|
||||||
// Should see target's rename but not other's events
|
// session:updated is a lifecycle event broadcast to all clients; the
|
||||||
const targetUpdated = events.find((e) => e.event === 'session:updated' && (e.data as any).id === target);
|
// subscription filter applies only to high-volume terminal streams.
|
||||||
|
const updatedEvents = events.filter((e) => e.event === 'session:updated');
|
||||||
|
const targetUpdated = updatedEvents.find((e) => (e.data as any).id === target);
|
||||||
expect(targetUpdated).toBeDefined();
|
expect(targetUpdated).toBeDefined();
|
||||||
|
|
||||||
// Should NOT see other's events
|
const otherLifecycleEvents = events.filter(
|
||||||
const otherEvents = events.filter(
|
(e) => e.event !== 'init' && e.event !== 'session:terminal' && (e.data as any)?.id === other
|
||||||
(e) => ((e.data as any)?.id === other || (e.data as any)?.sessionId === other) && e.event !== 'init'
|
|
||||||
);
|
);
|
||||||
expect(otherEvents.length).toBe(0);
|
expect(otherLifecycleEvents.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const otherTerminalEvents = events.filter(
|
||||||
|
(e) => e.event === 'session:terminal' && (e.data as any)?.sessionId === other
|
||||||
|
);
|
||||||
|
expect(otherTerminalEvents.length).toBe(0);
|
||||||
|
|
||||||
await deleteSession(baseUrl, target);
|
await deleteSession(baseUrl, target);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
const res = await fetch(`${baseUrl}/api/sessions/non-existent-id`);
|
const res = await fetch(`${baseUrl}/api/sessions/non-existent-id`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
@@ -136,7 +136,8 @@ describe('Ralph Integration Tests', () => {
|
|||||||
// Verify session is gone
|
// Verify session is gone
|
||||||
const getRes = await fetch(`${baseUrl}/api/sessions/${sessionId}`);
|
const getRes = await fetch(`${baseUrl}/api/sessions/${sessionId}`);
|
||||||
const getData = await getRes.json();
|
const getData = await getRes.json();
|
||||||
expect(getData.error).toBe('Session not found');
|
expect(getRes.status).toBe(404);
|
||||||
|
expect(getData.error).toContain('not found');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create shell session', async () => {
|
it('should create shell session', async () => {
|
||||||
@@ -191,7 +192,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
const res = await fetch(`${baseUrl}/api/sessions/fake-session/ralph-state`);
|
const res = await fetch(`${baseUrl}/api/sessions/fake-session/ralph-state`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.error).toContain('not found');
|
expect(data.error).toContain('not found');
|
||||||
});
|
});
|
||||||
@@ -359,7 +360,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(400);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('INVALID_INPUT');
|
expect(data.errorCode).toBe('INVALID_INPUT');
|
||||||
});
|
});
|
||||||
@@ -372,7 +373,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
@@ -395,7 +396,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
createdSessions.push(createData.sessionId);
|
createdSessions.push(createData.sessionId);
|
||||||
|
|
||||||
// Wait for session to be ready
|
// Wait for session to be ready
|
||||||
await new Promise(r => setTimeout(r, 200));
|
await new Promise((r) => setTimeout(r, 200));
|
||||||
|
|
||||||
const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, {
|
const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -422,7 +423,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
createdSessions.push(createData.sessionId);
|
createdSessions.push(createData.sessionId);
|
||||||
|
|
||||||
// Wait for session to be ready
|
// Wait for session to be ready
|
||||||
await new Promise(r => setTimeout(r, 200));
|
await new Promise((r) => setTimeout(r, 200));
|
||||||
|
|
||||||
const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, {
|
const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -431,7 +432,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(400);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('INVALID_INPUT');
|
expect(data.errorCode).toBe('INVALID_INPUT');
|
||||||
});
|
});
|
||||||
@@ -472,7 +473,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
@@ -497,7 +498,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(400);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('INVALID_INPUT');
|
expect(data.errorCode).toBe('INVALID_INPUT');
|
||||||
});
|
});
|
||||||
@@ -536,7 +537,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
@@ -561,7 +562,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(400);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('INVALID_INPUT');
|
expect(data.errorCode).toBe('INVALID_INPUT');
|
||||||
});
|
});
|
||||||
@@ -626,7 +627,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
@@ -907,7 +908,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
@@ -991,7 +992,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
const res = await fetch(`${baseUrl}/api/sessions/fake-session/output`);
|
const res = await fetch(`${baseUrl}/api/sessions/fake-session/output`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
@@ -1021,7 +1022,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
const res = await fetch(`${baseUrl}/api/sessions/fake-session/terminal`);
|
const res = await fetch(`${baseUrl}/api/sessions/fake-session/terminal`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(404);
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false);
|
||||||
expect(data.errorCode).toBe('NOT_FOUND');
|
expect(data.errorCode).toBe('NOT_FOUND');
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-1
@@ -6,11 +6,15 @@
|
|||||||
* This means tests CANNOT kill, create, or interact with real tmux
|
* This means tests CANNOT kill, create, or interact with real tmux
|
||||||
* sessions regardless of what the test code does.
|
* sessions regardless of what the test code does.
|
||||||
*
|
*
|
||||||
* This setup file only handles mock/timer cleanup between tests.
|
* This setup file strips shell-level auth configuration that can leak from a
|
||||||
|
* running Codeman instance, then handles mock/timer cleanup between tests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { afterEach, vi } from 'vitest';
|
import { afterEach, vi } from 'vitest';
|
||||||
|
|
||||||
|
delete process.env.CODEMAN_PASSWORD;
|
||||||
|
delete process.env.CODEMAN_USERNAME;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
|||||||
Reference in New Issue
Block a user