diff --git a/app/api/config/route.ts b/app/api/config/route.ts new file mode 100644 index 0000000..d790dd6 --- /dev/null +++ b/app/api/config/route.ts @@ -0,0 +1,8 @@ +import { NextResponse } from 'next/server'; +import { APK_TABS } from '@/lib/constants'; + +export async function GET() { + return NextResponse.json({ + tabs: APK_TABS, + }); +} diff --git a/app/download/ApkList.tsx b/app/download/ApkList.tsx index b09610c..a86b9b0 100644 --- a/app/download/ApkList.tsx +++ b/app/download/ApkList.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import useSWR from 'swr'; import { type ApkEnvironment } from '@/lib/apk-parser'; import ApkTable from './ApkTable'; @@ -22,16 +22,26 @@ interface ApkListProps { commitId: string; } -const TABS: ApkEnvironment[] = ['dev', 'product', 'sandbox', 'other']; - export default function ApkList({ project, version, commitId }: ApkListProps) { + const [configuredTabs, setConfiguredTabs] = useState([]); const [activeTab, setActiveTab] = useState('product'); + // Fetch config on mount + useEffect(() => { + fetch('/api/config') + .then(res => res.json()) + .then(data => setConfiguredTabs(data.tabs)) + .catch(() => setConfiguredTabs(['dev', 'sandbox'])); // fallback + }, []); + const { data: apks, isLoading, error } = useSWR( `/api/apks?project=${project}&version=${version}&commit=${commitId}`, fetcher ); + // Build tabs: product -> configured -> other + const TABS: ApkEnvironment[] = ['product', ...configuredTabs.filter(t => t !== 'product' && t !== 'other') as ApkEnvironment[], 'other']; + if (isLoading) { return (
diff --git a/app/download/__tests__/ApkList.test.tsx b/app/download/__tests__/ApkList.test.tsx index 6921df5..baf00e0 100644 --- a/app/download/__tests__/ApkList.test.tsx +++ b/app/download/__tests__/ApkList.test.tsx @@ -1,10 +1,14 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import ApkList from '../ApkList'; import useSWR from 'swr'; jest.mock('swr'); +// Mock fetch for config API +const mockFetch = jest.fn(); +global.fetch = mockFetch; + describe('ApkList', () => { const mockApks = [ { @@ -39,20 +43,34 @@ describe('ApkList', () => { isLoading: false, error: null, }); + // Mock config API response + mockFetch.mockResolvedValue({ + json: () => Promise.resolve({ tabs: ['dev', 'sandbox'] }), + }); }); - it('should render all 4 tabs', () => { - render(); - - expect(screen.getByText(/dev/)).toBeInTheDocument(); - expect(screen.getByText(/product/)).toBeInTheDocument(); - expect(screen.getByText(/sandbox/)).toBeInTheDocument(); - expect(screen.getByText(/other/)).toBeInTheDocument(); + afterEach(() => { + mockFetch.mockClear(); }); - it('should switch tabs correctly', () => { + it('should render all 4 tabs', async () => { render(); + await waitFor(() => { + expect(screen.getByText(/dev/)).toBeInTheDocument(); + expect(screen.getByText(/product/)).toBeInTheDocument(); + expect(screen.getByText(/sandbox/)).toBeInTheDocument(); + expect(screen.getByText(/other/)).toBeInTheDocument(); + }); + }); + + it('should switch tabs correctly', async () => { + render(); + + await waitFor(() => { + expect(screen.getByText(/dev/)).toBeInTheDocument(); + }); + const devTab = screen.getByText(/dev/); fireEvent.click(devTab);