Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions .github/actions/smart-e2e-selection/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ inputs:
outputs:
ai_e2e_test_tags:
description: 'E2E test tags to run (JSON array format)'
value: ${{ steps.ai-analysis.outputs.ai_e2e_test_tags }}
value: ${{ steps.final-outputs.outputs.ai_e2e_test_tags }}
ai_confidence:
description: 'AI confidence score (0-100)'
value: ${{ steps.ai-analysis.outputs.ai_confidence }}
value: ${{ steps.final-outputs.outputs.ai_confidence }}

runs:
using: 'composite'
Expand Down Expand Up @@ -130,17 +130,31 @@ runs:
echo "⏭️ Skipping AI analysis - $SKIP_REASON"
else
echo "✅ Running AI analysis for PR #$PR_NUMBER"
# The script will generate the GH output variables
node .github/scripts/e2e-smart-selection.mjs
# The script will generate the GH output variables - don't fail if script errors
node .github/scripts/e2e-smart-selection.mjs || echo "⚠️ AI analysis script failed, using fallback"
fi

- name: Set final outputs with fallback
id: final-outputs
if: always()
shell: bash
run: |
TAGS='${{ steps.ai-analysis.outputs.ai_e2e_test_tags }}'
if [[ -n "$TAGS" ]]; then
printf 'ai_e2e_test_tags=%s\n' "$TAGS" >> "$GITHUB_OUTPUT"
else
echo 'ai_e2e_test_tags=["ALL"]' >> "$GITHUB_OUTPUT"
fi
echo "ai_confidence=${{ steps.ai-analysis.outputs.ai_confidence }}" >> "$GITHUB_OUTPUT"

- name: Display AI Analysis Outputs
if: always()
shell: bash
run: |
echo "📊 Final GitHub Action Outputs:"
echo "================================"
echo "ai_e2e_test_tags: ${{ steps.ai-analysis.outputs.ai_e2e_test_tags }}"
echo "ai_confidence: ${{ steps.ai-analysis.outputs.ai_confidence }}"
echo "ai_e2e_test_tags: ${{ steps.final-outputs.outputs.ai_e2e_test_tags }}"
echo "ai_confidence: ${{ steps.final-outputs.outputs.ai_confidence }}"
echo "================================"

- name: Delete previous comments
Expand Down Expand Up @@ -191,14 +205,10 @@ runs:
COMMENT_BODY="⏭️ **Smart E2E selection skipped** - ${SKIP_REASON}

All E2E tests pre-selected."
elif [ -f "$COMMENT_FILE" ]; then
COMMENT_BODY=$(cat "$COMMENT_FILE")
else
# Read analysis results from file
if [ -f "$COMMENT_FILE" ]; then
COMMENT_BODY=$(cat "$COMMENT_FILE")
else
echo "⚠️ PR comment file not found: $COMMENT_FILE - using default message"
COMMENT_BODY="AI analysis completed but results file was not generated."
fi
COMMENT_BODY="⚠️ AI analysis was inconclusive. Selecting all E2E tests as a fallback, if applicable based on changed files."
fi

# Build and post comment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const PerpsHomeView = () => {
conditions: [!isAnyLoading],
properties: {
[PerpsEventProperties.SCREEN_TYPE]:
PerpsEventValues.SCREEN_TYPE.HOMESCREEN,
PerpsEventValues.SCREEN_TYPE.PERPS_HOME,
[PerpsEventProperties.SOURCE]: source,
[PerpsEventProperties.HAS_PERP_BALANCE]: hasPerpBalance,
...(buttonClicked && {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,23 @@ jest.mock('../../hooks/usePerpsOrderFills', () => ({
usePerpsOrderFills: () => mockUsePerpsOrderFillsImpl(),
}));

// Mock for usePerpsMarkets that can be modified per test
const mockUsePerpsMarketsImpl = jest.fn<
ReturnType<typeof import('../../hooks/usePerpsMarkets').usePerpsMarkets>,
[]
>(() => ({
markets: [],
isLoading: false,
error: null,
refresh: jest.fn(),
isRefreshing: false,
}));

// Mock the direct import path for usePerpsMarkets
jest.mock('../../hooks/usePerpsMarkets', () => ({
usePerpsMarkets: () => mockUsePerpsMarketsImpl(),
}));

const mockRefreshMarketStats = jest.fn();
jest.mock('../../hooks/usePerpsMarketStats', () => ({
usePerpsMarketStats: () => ({
Expand Down Expand Up @@ -356,13 +373,7 @@ jest.mock('../../hooks', () => ({
closePosition: jest.fn(),
isClosing: false,
})),
usePerpsMarkets: jest.fn(() => ({
markets: [],
isLoading: false,
error: null,
refresh: jest.fn(),
isRefreshing: false,
})),
usePerpsMarkets: () => mockUsePerpsMarketsImpl(),
usePerpsTrading: jest.fn(() => ({
placeOrder: jest.fn(),
cancelOrder: jest.fn(),
Expand Down Expand Up @@ -2187,4 +2198,132 @@ describe('PerpsMarketDetailsView', () => {
expect(chart).toBeTruthy();
});
});

describe('Market data enrichment', () => {
beforeEach(() => {
// Reset to default market with maxLeverage
mockRouteParams.market = {
symbol: 'BTC',
name: 'Bitcoin',
price: '$45,000.00',
change24h: '+$1,125.00',
change24hPercent: '+2.50%',
volume: '$1.23B',
maxLeverage: '40x',
};
});

it('uses route market data when maxLeverage is present (no enrichment needed)', () => {
// Route has complete market data including maxLeverage
mockRouteParams.market = {
symbol: 'ETH',
name: 'Ethereum',
price: '$3,000.00',
change24h: '+$50.00',
change24hPercent: '+1.50%',
volume: '$500M',
maxLeverage: '25x',
};

// Mock usePerpsMarkets to return empty (should not be used)
mockUsePerpsMarketsImpl.mockReturnValue({
markets: [],
isLoading: false,
error: null,
refresh: jest.fn(),
isRefreshing: false,
});

const { getByText } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
{
state: initialState,
},
);

// Should show the route market's leverage badge
expect(getByText('25x')).toBeTruthy();
expect(getByText('ETH-USD')).toBeTruthy();
});

it('enriches market data from usePerpsMarkets when route has minimal data', async () => {
// Route has minimal market data (no maxLeverage)
mockRouteParams.market = {
symbol: 'BTC',
name: 'Bitcoin',
} as typeof mockRouteParams.market;

// Mock usePerpsMarkets to return full market with maxLeverage
mockUsePerpsMarketsImpl.mockImplementation(() => ({
markets: [
{
symbol: 'BTC',
name: 'Bitcoin',
price: '$45,000.00',
change24h: '+$1,125.00',
change24hPercent: '+2.50%',
volume: '$1.23B',
maxLeverage: '40x',
volumeNumber: 1230000000,
},
],
isLoading: false,
error: null,
refresh: jest.fn(),
isRefreshing: false,
}));

const { getByText, getByTestId } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
{
state: initialState,
},
);

// Verify the header renders with correct market symbol
expect(getByTestId('perps-market-header')).toBeTruthy();
expect(getByText('BTC-USD')).toBeTruthy();

// Should show the enriched market's leverage badge from usePerpsMarkets
await waitFor(() => {
expect(getByText('40x')).toBeTruthy();
});
});

it('gracefully handles when enrichment data is not available', () => {
// Route has minimal market data (no maxLeverage)
mockRouteParams.market = {
symbol: 'UNKNOWN',
name: 'Unknown Asset',
} as typeof mockRouteParams.market;

// Mock usePerpsMarkets to return empty (market not found)
mockUsePerpsMarketsImpl.mockReturnValue({
markets: [],
isLoading: false,
error: null,
refresh: jest.fn(),
isRefreshing: false,
});

const { getByText, queryByText } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
{
state: initialState,
},
);

// Should show the asset name but no leverage badge (since no maxLeverage available)
expect(getByText('UNKNOWN-USD')).toBeTruthy();
// No leverage badge should be shown
expect(queryByText('40x')).toBeNull();
expect(queryByText('25x')).toBeNull();
});
});
});
Loading
Loading