diff --git a/jam-ui/src/components/client/JKSessionMetronome.js b/jam-ui/src/components/client/JKSessionMetronome.js
index ce75c9810..a2ae5b776 100644
--- a/jam-ui/src/components/client/JKSessionMetronome.js
+++ b/jam-ui/src/components/client/JKSessionMetronome.js
@@ -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 (
-
+ {/* Metronome Title */}
+
+ Metronome
+
+
{/* Metronome Icon */}

{
// Fallback if metronome icon doesn't exist
e.target.src = "/assets/content/icon_recording.png";
@@ -51,8 +60,14 @@ const JKSessionMetronome = ({
/>
- {/* Controls */}
+ {/* 0db Label */}
+
+ 0db
+
+
+ {/* Track Controls */}
+ {/* VU Meter and Gain Control - Horizontal */}
- {/* Close Button */}
+ {/* Track Buttons - Three Dots Menu */}
-
+
setShowMenu(!showMenu)}>
+ ...
+
+ {showMenu && (
+
+
{
+ handleClose();
+ setShowMenu(false);
+ }}
+ >
+ Close
+
+
+ )}
+
-
- {/* Track Info */}
-
+
);
diff --git a/jam-ui/src/context/JamClientContext.js b/jam-ui/src/context/JamClientContext.js
index f9cf5bca4..0b2d11b6e 100644
--- a/jam-ui/src/context/JamClientContext.js
+++ b/jam-ui/src/context/JamClientContext.js
@@ -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(() => {
diff --git a/jam-ui/test/config/global-setup.ts b/jam-ui/test/config/global-setup.ts
index 1326d32da..e3e3eb799 100644
--- a/jam-ui/test/config/global-setup.ts
+++ b/jam-ui/test/config/global-setup.ts
@@ -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();
}
diff --git a/jam-ui/test/metronome/metronome-controls.spec.ts b/jam-ui/test/metronome/metronome-controls.spec.ts
index 32feb10f9..76f10ce0c 100644
--- a/jam-ui/test/metronome/metronome-controls.spec.ts
+++ b/jam-ui/test/metronome/metronome-controls.spec.ts
@@ -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);