Files
3x-ui/frontend/src/test/language-scope.test.ts
T
Alireza Ahmadi e2184777a0 fix(frontend): make the panel usable with screen readers (#6907)
* fix(frontend): make the panel usable with screen readers

* fix(frontend): keep mobile tab names and the inbound expiry hint

Review of #6907 found two gaps in the screen-reader pass:

- On phone-width screens catTabLabel renders only the icon and names the
  tab by putting aria-label on it, but callers now pass that icon with
  aria-hidden for the desktop label, so every Settings, Xray and Host
  category tab lost its name under TalkBack. catTabLabel now clears
  aria-hidden on the icon it uses as the name.
- DateTimePicker forwarded id and aria-label but not aria-describedby,
  so the inbound expiry hint ("leave blank to never expire") was never
  attached. It now forwards it on both the Gregorian and Jalali input.
2026-10-23 18:29:47 +02:00

54 lines
2.1 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
describe('subscription language scope', () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it('initializes lazily and changes the subscription language without changing the panel', async () => {
vi.resetModules();
const utils = await import('@/utils');
const cookies = new Map<string, string>([['lang', 'en-US']]);
vi.spyOn(utils.CookieManager, 'getCookie').mockImplementation(
(name) => cookies.get(name) ?? '',
);
vi.spyOn(utils.CookieManager, 'setCookie').mockImplementation((name, value) => {
cookies.set(name, value);
});
const getLanguage = vi.spyOn(utils.LanguageManager, 'getLanguage');
const reload = vi.fn();
vi.stubGlobal('window', { navigator: { language: 'en-US' }, location: { reload } });
vi.stubGlobal('document', { documentElement: {} });
const { readyI18n } = await import('@/i18n/react');
expect(getLanguage).not.toHaveBeenCalled();
await readyI18n('subscription');
expect(cookies.get('subLang')).toBe('en-US');
utils.LanguageManager.setLanguage('fa-IR', 'subscription');
expect(cookies.get('lang')).toBe('en-US');
expect(cookies.get('subLang')).toBe('fa-IR');
expect(reload).toHaveBeenCalledOnce();
const dateTimeFormat = vi.spyOn(Intl, 'DateTimeFormat').mockImplementation(function (
locale?: Intl.LocalesArgument,
) {
return { format: () => String(locale) } as Intl.DateTimeFormat;
} as typeof Intl.DateTimeFormat);
expect(utils.IntlUtil.formatDate(0, 'gregorian', 'fa-IR')).toBe('fa-IR');
expect(dateTimeFormat).toHaveBeenLastCalledWith('fa-IR', expect.any(Object));
});
it('does not resolve the language for empty or invalid dates', async () => {
const utils = await import('@/utils');
const getLanguage = vi.spyOn(utils.LanguageManager, 'getLanguage').mockReturnValue('en-US');
expect(utils.IntlUtil.formatDate(null)).toBe('');
expect(utils.IntlUtil.formatDate(undefined)).toBe('');
expect(utils.IntlUtil.formatDate('not-a-date')).toBe('');
expect(getLanguage).not.toHaveBeenCalled();
});
});