mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(media hosting): disable unreachable providers
This commit is contained in:
+57
-4
@@ -17,11 +17,18 @@ vi.mock('@capacitor/core', () => ({
|
||||
Capacitor: { getPlatform: () => mockGetPlatform() },
|
||||
}));
|
||||
|
||||
const providerAvailabilityRef = vi.hoisted(() => ({
|
||||
value: {} as Record<string, 'unknown' | 'checking' | 'available' | 'unavailable'>,
|
||||
}));
|
||||
vi.mock('../../../../hooks/use-provider-availability', () => ({
|
||||
useProviderAvailability: () => providerAvailabilityRef.value,
|
||||
}));
|
||||
|
||||
const mockSetUploadMode = vi.fn();
|
||||
const mockSetPreferredProvider = vi.fn();
|
||||
const uploadModeRef = vi.hoisted(() => ({ value: 'random' as 'random' | 'preferred' | 'none' }));
|
||||
const preferredProviderRef = vi.hoisted(() => ({
|
||||
value: 'catbox' as 'catbox' | 'imgur',
|
||||
value: 'catbox' as 'catbox' | 'imgur' | 'imgbb',
|
||||
}));
|
||||
vi.mock('../../../../stores/use-media-hosting-store', async (importOriginal) => {
|
||||
const mod = await importOriginal<typeof import('../../../../stores/use-media-hosting-store')>();
|
||||
@@ -51,6 +58,7 @@ describe('MediaHostingSettings', () => {
|
||||
vi.clearAllMocks();
|
||||
uploadModeRef.value = 'random';
|
||||
preferredProviderRef.value = 'catbox';
|
||||
providerAvailabilityRef.value = {};
|
||||
mockGetPlatform.mockReturnValue('web');
|
||||
(window as unknown as { electronApi?: unknown }).electronApi = undefined;
|
||||
container = document.createElement('div');
|
||||
@@ -99,18 +107,63 @@ describe('MediaHostingSettings', () => {
|
||||
expect(mockSetUploadMode).toHaveBeenCalledWith('none');
|
||||
});
|
||||
|
||||
it('clicking provider in preferred mode calls setPreferredProvider', async () => {
|
||||
it('disables unsupported providers on Android', async () => {
|
||||
uploadModeRef.value = 'preferred';
|
||||
preferredProviderRef.value = 'catbox';
|
||||
mockGetPlatform.mockReturnValue('android');
|
||||
render();
|
||||
const imgurRadio = container.querySelector<HTMLInputElement>('input[value="imgur"]');
|
||||
expect(imgurRadio).not.toBeNull();
|
||||
expect(imgurRadio?.disabled).toBe(false);
|
||||
expect(imgurRadio?.disabled).toBe(true);
|
||||
await act(async () => {
|
||||
imgurRadio?.click();
|
||||
});
|
||||
expect(mockSetPreferredProvider).toHaveBeenCalledWith('imgur');
|
||||
expect(mockSetPreferredProvider).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clicking supported provider in preferred mode calls setPreferredProvider', async () => {
|
||||
uploadModeRef.value = 'preferred';
|
||||
preferredProviderRef.value = 'imgur';
|
||||
mockGetPlatform.mockReturnValue('android');
|
||||
render();
|
||||
const catboxRadio = container.querySelector<HTMLInputElement>('input[value="catbox"]');
|
||||
expect(catboxRadio).not.toBeNull();
|
||||
expect(catboxRadio?.disabled).toBe(false);
|
||||
await act(async () => {
|
||||
catboxRadio?.click();
|
||||
});
|
||||
expect(mockSetPreferredProvider).toHaveBeenCalledWith('catbox');
|
||||
});
|
||||
|
||||
it('enables ImgBB on Android', async () => {
|
||||
uploadModeRef.value = 'preferred';
|
||||
preferredProviderRef.value = 'catbox';
|
||||
mockGetPlatform.mockReturnValue('android');
|
||||
render();
|
||||
const imgbbRadio = container.querySelector<HTMLInputElement>('input[value="imgbb"]');
|
||||
expect(imgbbRadio).not.toBeNull();
|
||||
expect(imgbbRadio?.disabled).toBe(false);
|
||||
await act(async () => {
|
||||
imgbbRadio?.click();
|
||||
});
|
||||
expect(mockSetPreferredProvider).toHaveBeenCalledWith('imgbb');
|
||||
});
|
||||
|
||||
it('disables providers that are unavailable from this network', async () => {
|
||||
uploadModeRef.value = 'preferred';
|
||||
preferredProviderRef.value = 'catbox';
|
||||
providerAvailabilityRef.value = { imgbb: 'unavailable' };
|
||||
mockGetPlatform.mockReturnValue('android');
|
||||
render();
|
||||
const imgbbRadio = container.querySelector<HTMLInputElement>('input[value="imgbb"]');
|
||||
expect(imgbbRadio).not.toBeNull();
|
||||
expect(imgbbRadio?.disabled).toBe(true);
|
||||
expect(imgbbRadio?.closest('label')?.title).toBe('media_hosting_provider_unavailable');
|
||||
expect(container.textContent).toContain('media_hosting_provider_unavailable');
|
||||
await act(async () => {
|
||||
imgbbRadio?.click();
|
||||
});
|
||||
expect(mockSetPreferredProvider).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clicking Random updates store via setUploadMode', async () => {
|
||||
|
||||
@@ -2,7 +2,8 @@ import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useMediaHostingStore, { MEDIA_HOSTING_PROVIDERS } from '../../../stores/use-media-hosting-store';
|
||||
import type { ProviderId } from '../../../lib/media-hosting/types';
|
||||
import { isWebRuntime } from '../../../lib/media-hosting/show-upload-controls';
|
||||
import { getMediaHostingRuntime } from '../../../lib/media-hosting/show-upload-controls';
|
||||
import { useProviderAvailability } from '../../../hooks/use-provider-availability';
|
||||
import styles from '../interface-settings/interface-settings.module.css';
|
||||
|
||||
const RADIO_NAME = 'media-hosting-provider';
|
||||
@@ -14,7 +15,9 @@ const MediaHostingSettings = () => {
|
||||
const preferredProvider = useMediaHostingStore((state) => state.preferredProvider);
|
||||
const setUploadMode = useMediaHostingStore((state) => state.setUploadMode);
|
||||
const setPreferredProvider = useMediaHostingStore((state) => state.setPreferredProvider);
|
||||
const isWeb = isWebRuntime();
|
||||
const runtime = getMediaHostingRuntime();
|
||||
const isWeb = runtime === 'web';
|
||||
const availability = useProviderAvailability(runtime);
|
||||
|
||||
return (
|
||||
<div className={styles.interfaceSettings}>
|
||||
@@ -40,25 +43,30 @@ const MediaHostingSettings = () => {
|
||||
</label>
|
||||
{uploadMode === 'preferred' && (
|
||||
<div role='radiogroup' aria-label={t('media_hosting_preferred_provider_label')}>
|
||||
{MEDIA_HOSTING_PROVIDERS.map((provider) => (
|
||||
<div key={provider.id} className={styles.setting}>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
name={`${RADIO_NAME}-provider`}
|
||||
value={provider.id}
|
||||
checked={preferredProvider === provider.id}
|
||||
onChange={() => setPreferredProvider(provider.id as ProviderId)}
|
||||
disabled={isWeb}
|
||||
/>
|
||||
{provider.label} (
|
||||
<a href={provider.homepageUrl} target='_blank' rel='noopener noreferrer'>
|
||||
{provider.homepageUrl}
|
||||
</a>
|
||||
)
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
{MEDIA_HOSTING_PROVIDERS.map((provider) => {
|
||||
const providerUnavailable = availability[provider.id] === 'unavailable';
|
||||
const providerDisabled = isWeb || !provider.supportedRuntimes.includes(runtime) || providerUnavailable;
|
||||
return (
|
||||
<div key={provider.id} className={styles.setting}>
|
||||
<label title={providerUnavailable ? t('media_hosting_provider_unavailable') : undefined}>
|
||||
<input
|
||||
type='radio'
|
||||
name={`${RADIO_NAME}-provider`}
|
||||
value={provider.id}
|
||||
checked={preferredProvider === provider.id}
|
||||
onChange={() => setPreferredProvider(provider.id as ProviderId)}
|
||||
disabled={providerDisabled}
|
||||
/>
|
||||
{provider.label} (
|
||||
<a href={provider.homepageUrl} target='_blank' rel='noopener noreferrer'>
|
||||
{provider.homepageUrl}
|
||||
</a>
|
||||
)
|
||||
</label>
|
||||
{providerUnavailable && <div className={styles.settingTip}>{t('media_hosting_provider_unavailable')}</div>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user