feat: update metronome track layout and improve test infrastructure
- Change metronome track to horizontal layout: VU meter on left, volume slider on right - Add three dots menu button without diagnostics circle (metronome doesn't need diagnostics) - Move Close functionality to three dots menu dropdown - Update test selectors to match new layout (.track-title, .track-menu-button) - Add jamClient mocking in tests to prevent hanging on native client calls - Increase test timeout to 60s to accommodate session creation time - Configure global test setup to use existing user accounts instead of creating new ones - Add conditional fake client support in JamClientContext for test environments Tests: 3 passed, 2 skipped (as expected) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a2be53bc0d
commit
3887f4fdda
|
|
@ -3,8 +3,6 @@ import { useMixersContext } from '../../context/MixersContext';
|
|||
import { useJamClient } from '../../context/JamClientContext';
|
||||
import SessionTrackVU from './SessionTrackVU';
|
||||
import SessionTrackGain from './SessionTrackGain';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import './JKSessionMyTrack.css';
|
||||
|
||||
const JKSessionMetronome = ({
|
||||
|
|
@ -13,6 +11,7 @@ const JKSessionMetronome = ({
|
|||
}) => {
|
||||
const mixerHelper = useMixersContext();
|
||||
const jamClient = useJamClient();
|
||||
const [showMenu, setShowMenu] = React.useState(false);
|
||||
|
||||
console.log('JKSessionMetronome mixers:', mixers);
|
||||
|
||||
|
|
@ -27,8 +26,12 @@ const JKSessionMetronome = ({
|
|||
|
||||
return (
|
||||
<div className={trackClasses}>
|
||||
<div className="disabled-track-overlay" />
|
||||
<div className="session-track-contents">
|
||||
{/* Metronome Title */}
|
||||
<div className="track-title" style={{ textAlign: 'center', marginBottom: '16px', fontSize: '0.9rem', fontWeight: '500', color: '#666' }}>
|
||||
Metronome
|
||||
</div>
|
||||
|
||||
{/* Metronome Icon */}
|
||||
<div
|
||||
className="track-instrument"
|
||||
|
|
@ -36,14 +39,20 @@ const JKSessionMetronome = ({
|
|||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: '8px'
|
||||
marginBottom: '12px'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
height="45"
|
||||
height="55"
|
||||
src="/assets/content/icon_metronome.png"
|
||||
width="45"
|
||||
width="55"
|
||||
alt="metronome"
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
padding: '8px',
|
||||
backgroundColor: '#f5f5f5',
|
||||
border: '2px solid #ddd'
|
||||
}}
|
||||
onError={(e) => {
|
||||
// Fallback if metronome icon doesn't exist
|
||||
e.target.src = "/assets/content/icon_recording.png";
|
||||
|
|
@ -51,8 +60,14 @@ const JKSessionMetronome = ({
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
{/* 0db Label */}
|
||||
<div style={{ textAlign: 'center', fontSize: '0.75rem', color: '#666', marginBottom: '8px' }}>
|
||||
0db
|
||||
</div>
|
||||
|
||||
{/* Track Controls */}
|
||||
<div className="track-controls">
|
||||
{/* VU Meter and Gain Control - Horizontal */}
|
||||
<div className="vu-and-gain" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<SessionTrackVU
|
||||
orientation="horizontal"
|
||||
|
|
@ -70,37 +85,29 @@ const JKSessionMetronome = ({
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Close Button */}
|
||||
{/* Track Buttons - Three Dots Menu */}
|
||||
<div className="track-buttons">
|
||||
<div className="track-menu-container">
|
||||
<button
|
||||
className="btn-close-track"
|
||||
onClick={handleClose}
|
||||
title="Close Metronome"
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '4px 8px',
|
||||
fontSize: '1.2rem',
|
||||
color: '#6c757d',
|
||||
transition: 'color 0.2s'
|
||||
}}
|
||||
onMouseEnter={(e) => e.target.style.color = '#495057'}
|
||||
onMouseLeave={(e) => e.target.style.color = '#6c757d'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTimes} />
|
||||
</button>
|
||||
<div className="track-menu-button" onClick={() => setShowMenu(!showMenu)}>
|
||||
...
|
||||
</div>
|
||||
{showMenu && (
|
||||
<div className="track-menu">
|
||||
<div
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
setShowMenu(false);
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<br className="clearall" />
|
||||
</div>
|
||||
|
||||
{/* Track Info */}
|
||||
<div className="track-info" style={{ marginTop: '8px', textAlign: 'center' }}>
|
||||
<div className="track-name" style={{ fontSize: '0.875rem', fontWeight: '600', color: '#495057' }}>
|
||||
Metronome
|
||||
</div>
|
||||
</div>
|
||||
<br className="clearall" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,25 +22,25 @@ export const JamClientProvider = ({ children }) => {
|
|||
|
||||
const proxyRef = useRef(null);
|
||||
|
||||
// if (process.env.NODE_ENV === 'development') {
|
||||
// const fakeJamClientMessages = new FakeJamClientMessages();
|
||||
// const proxy = new FakeJamClientProxy(app, fakeJamClientMessages); // Pass appropriate parameters
|
||||
// proxyRef.current = proxy.init();
|
||||
// // For testing purposes, we can add some fake recordings
|
||||
// const fakeJamClientRecordings = new FakeJamClientRecordings(app, proxyRef.current, fakeJamClientMessages);
|
||||
// proxyRef.current.SetFakeRecordingImpl(fakeJamClientRecordings);
|
||||
// } else {
|
||||
// if (!proxyRef.current) {
|
||||
// const proxy = new JamClientProxy(app, console, globalObject);
|
||||
// proxyRef.current = proxy.init();
|
||||
// }
|
||||
// }
|
||||
if (process.env.NODE_ENV === 'test' || process.env.REACT_APP_USE_FAKE_CLIENT === 'true') {
|
||||
const fakeJamClientMessages = new FakeJamClientMessages();
|
||||
const proxy = new FakeJamClientProxy(app, fakeJamClientMessages); // Pass appropriate parameters
|
||||
proxyRef.current = proxy.init();
|
||||
// For testing purposes, we can add some fake recordings
|
||||
const fakeJamClientRecordings = new FakeJamClientRecordings(app, proxyRef.current, fakeJamClientMessages);
|
||||
proxyRef.current.SetFakeRecordingImpl(fakeJamClientRecordings);
|
||||
} else {
|
||||
if (!proxyRef.current) {
|
||||
const proxy = new JamClientProxy(app, console, globalObject);
|
||||
proxyRef.current = proxy.init();
|
||||
}
|
||||
}
|
||||
|
||||
if (!proxyRef.current) {
|
||||
const proxy = new JamClientProxy(app, console, globalObject);
|
||||
proxyRef.current = proxy.init();
|
||||
window.jamClient = proxyRef.current; // TODO: Expose jamClient globally for debugging. This is temporarily added. Remove it later.
|
||||
}
|
||||
window.jamClient = proxyRef.current; // TODO: Expose jamClient globally for debugging. This is temporarily added. Remove it later.
|
||||
|
||||
// Register metronome callback with jamClient when it's available
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -23,19 +23,19 @@ async function globalSetup(config: FullConfig) {
|
|||
const browser = await chromium.launch();
|
||||
|
||||
const page1 = await browser.newPage();
|
||||
//signup user1
|
||||
await signup(page1, user1.email, user1.password, user1.first_name, user1.last_name)
|
||||
// Skip signup - use existing user1 account
|
||||
// await signup(page1, user1.email, user1.password, user1.first_name, user1.last_name)
|
||||
// ... log in user1
|
||||
await login(page1, user1.email, user1.password)
|
||||
await login(page1, user1.email, user1.password)
|
||||
await page1.context().storageState({ path: 'test/storageState/user1.json' });
|
||||
|
||||
const page2 = await browser.newPage();
|
||||
//signup user2
|
||||
await signup(page2, user2.email, user2.password, user2.first_name, user2.last_name)
|
||||
// Skip signup - use existing user2 account
|
||||
// await signup(page2, user2.email, user2.password, user2.first_name, user2.last_name)
|
||||
// ... log in
|
||||
await login(page2, user2.email, user2.password)
|
||||
await page2.context().storageState({ path: 'test/storageState/user2.json' });
|
||||
|
||||
await login(page2, user2.email, user2.password)
|
||||
await page2.context().storageState({ path: 'test/storageState/user2.json' });
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,27 +13,84 @@ import { APIInterceptor } from '../utils/api-interceptor';
|
|||
* - Closing metronome
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper function to mock jamClient methods
|
||||
*/
|
||||
async function mockJamClient(page: Page) {
|
||||
await page.evaluate(() => {
|
||||
// Create mock that logs and returns resolved promises
|
||||
const mockMethods = {
|
||||
SessionOpenMetronome: async (...args: any[]) => {
|
||||
console.log('[MOCK] SessionOpenMetronome called', args);
|
||||
return true;
|
||||
},
|
||||
SessionSetMetronome: async (...args: any[]) => {
|
||||
console.log('[MOCK] SessionSetMetronome called', args);
|
||||
return true;
|
||||
},
|
||||
SessionStopPlay: async (...args: any[]) => {
|
||||
console.log('[MOCK] SessionStopPlay called', args);
|
||||
return true;
|
||||
},
|
||||
SessionCloseMetronome: async (...args: any[]) => {
|
||||
console.log('[MOCK] SessionCloseMetronome called', args);
|
||||
return true;
|
||||
},
|
||||
SetVURefreshRate: async (...args: any[]) => {
|
||||
console.log('[MOCK] SetVURefreshRate called', args);
|
||||
return true;
|
||||
},
|
||||
SessionRegisterCallback: async (...args: any[]) => {
|
||||
console.log('[MOCK] SessionRegisterCallback called', args);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Replace jamClient methods if it exists
|
||||
if ((window as any).jamClient) {
|
||||
Object.assign((window as any).jamClient, mockMethods);
|
||||
console.log('[MOCK] jamClient methods replaced');
|
||||
} else {
|
||||
// Create mock jamClient if it doesn't exist
|
||||
(window as any).jamClient = mockMethods;
|
||||
console.log('[MOCK] jamClient created');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to open metronome in session
|
||||
*/
|
||||
async function openMetronome(page: Page) {
|
||||
console.log('[TEST] Mocking jamClient...');
|
||||
// Mock jamClient before opening metronome
|
||||
await mockJamClient(page);
|
||||
|
||||
console.log('[TEST] Clicking Open button...');
|
||||
// Click the "Open" button
|
||||
await page.locator('button:has-text("Open")').click();
|
||||
|
||||
console.log('[TEST] Waiting for dropdown menu...');
|
||||
// Wait for dropdown menu
|
||||
await page.waitForSelector('.dropdown-menu.show');
|
||||
|
||||
console.log('[TEST] Clicking Metronome in dropdown...');
|
||||
// Click "Metronome..." in dropdown
|
||||
await page.locator('button.dropdown-item:has-text("Metronome")').click();
|
||||
|
||||
console.log('[TEST] Waiting for metronome to open...');
|
||||
// Wait for metronome to open (either popup window or modal)
|
||||
await page.waitForTimeout(1000);
|
||||
console.log('[TEST] Metronome open complete');
|
||||
}
|
||||
|
||||
test.describe('Metronome Controls', () => {
|
||||
let apiInterceptor: APIInterceptor;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Increase timeout for these tests
|
||||
test.setTimeout(60000);
|
||||
|
||||
// Set up API interceptor
|
||||
apiInterceptor = new APIInterceptor();
|
||||
await apiInterceptor.intercept(page);
|
||||
|
|
@ -41,6 +98,9 @@ test.describe('Metronome Controls', () => {
|
|||
// Login and join a session
|
||||
await loginToJamUI(page);
|
||||
await createAndJoinSession(page);
|
||||
|
||||
// Mock jamClient after session is created
|
||||
await mockJamClient(page);
|
||||
});
|
||||
|
||||
test('should open metronome controls', async ({ page }) => {
|
||||
|
|
@ -54,11 +114,11 @@ test.describe('Metronome Controls', () => {
|
|||
const metronomeTrack = page.locator('.metronomeTrack');
|
||||
await expect(metronomeTrack).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// Verify metronome heading is present
|
||||
await expect(metronomeTrack.locator('h5:has-text("Metronome")')).toBeVisible();
|
||||
// Verify metronome title is present
|
||||
await expect(metronomeTrack.locator('.track-title:has-text("Metronome")')).toBeVisible();
|
||||
|
||||
// Verify close button is present
|
||||
await expect(metronomeTrack.locator('a:has-text("Close")')).toBeVisible();
|
||||
// Verify three dots menu button is present
|
||||
await expect(metronomeTrack.locator('.track-menu-button')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow adjusting BPM with slider in popup', async ({ page, context }) => {
|
||||
|
|
@ -101,8 +161,14 @@ test.describe('Metronome Controls', () => {
|
|||
const metronomeTrack = page.locator('.metronomeTrack');
|
||||
await expect(metronomeTrack).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// Click close button
|
||||
await metronomeTrack.locator('a:has-text("Close")').click();
|
||||
// Click three dots menu button to open menu
|
||||
await metronomeTrack.locator('.track-menu-button').click();
|
||||
|
||||
// Wait for menu to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click close in the menu
|
||||
await metronomeTrack.locator('.track-menu div:has-text("Close")').click();
|
||||
|
||||
// Wait for close operation
|
||||
await page.waitForTimeout(2000);
|
||||
|
|
|
|||
Loading…
Reference in New Issue