import { describe, expect, it } from 'bun:test'; import { renderToStaticMarkup } from 'react-dom/server'; import { InlineNotice } from '../src/appNotice'; import { I18nProvider, supportedLocales, translate } from '../src/i18n'; import { appNoticeReducer, initialAppNoticeState, type AppNotice, type AppNoticeState, } from '../src/services/appNotice'; const notice: AppNotice = { owner: 'api-page', source: 'app.nav.api', message: 'Connection enabled', tone: 'success', }; const renderNotice = (state: AppNoticeState) => renderToStaticMarkup( {}} />, ); describe('统一操作提示', () => { it('关闭操作反馈的文案覆盖所有支持的语言', () => { for (const locale of supportedLocales) { expect(translate(locale, 'app.notice.dismiss').length).toBeGreaterThan(0); } }); it('新结果替换旧结果,不叠加悬浮提示', () => { const first = appNoticeReducer(initialAppNoticeState, { type: 'show', notice }); const next = appNoticeReducer(first, { type: 'show', notice: { ...notice, message: 'Connection disabled' }, }); expect(next.notice?.message).toBe('Connection disabled'); expect(next.revision).toBe(2); expect(first.notice?.message).toBe('Connection enabled'); }); it('重复的操作结果也产生新的可播报版本', () => { const first = appNoticeReducer(initialAppNoticeState, { type: 'show', notice }); const next = appNoticeReducer(first, { type: 'show', notice }); expect(next.revision).toBe(first.revision + 1); expect(next.notice).toEqual(notice); }); it('错误和成功都保留结果,用户关闭后才清空', () => { for (const tone of ['success', 'error', 'info'] as const) { const shown = appNoticeReducer(initialAppNoticeState, { type: 'show', notice: { ...notice, tone } }); expect(shown.notice?.tone).toBe(tone); expect(appNoticeReducer(shown, { type: 'dismiss' }).notice).toBeNull(); } }); it('一个页面清理自己的状态时不会关闭其他页面的结果', () => { const shown = appNoticeReducer(initialAppNoticeState, { type: 'show', notice }); expect(appNoticeReducer(shown, { type: 'dismiss', owner: 'oauth-page' })).toBe(shown); expect(appNoticeReducer(shown, { type: 'dismiss', owner: 'api-page' }).notice).toBeNull(); }); it('空消息只清除当前来源,不显示空白成功提示', () => { const shown = appNoticeReducer(initialAppNoticeState, { type: 'show', notice }); expect(appNoticeReducer(shown, { type: 'show', notice: { ...notice, message: ' ' } }).notice).toBeNull(); expect(appNoticeReducer(shown, { type: 'show', notice: { ...notice, owner: 'another-page', message: '' }, })).toBe(shown); }); it('无消息时不渲染任何空白栏或占位文字', () => { expect(renderNotice(initialAppNoticeState)).toBe(''); expect(renderNotice({ notice: { ...notice, message: ' ' }, revision: 1 })).toBe(''); }); it('操作结果包含来源、完整消息和可访问的关闭按钮', () => { const html = renderNotice(appNoticeReducer(initialAppNoticeState, { type: 'show', notice })); expect(html).toContain('action-feedback inline-notice success'); expect(html).toContain('API Access'); expect(html).toContain('Connection enabled'); expect(html).toContain('role="status"'); expect(html).toContain('aria-live="polite"'); expect(html).toContain('aria-atomic="true"'); expect(html).toContain('aria-label="Dismiss operation feedback"'); expect(html).not.toContain('config-toast'); expect(html).not.toContain(' { const html = renderNotice({ notice: { ...notice, source: undefined }, revision: 1 }); expect(html).toContain('Connection enabled'); expect(html).not.toContain('action-feedback-source'); }); it('长错误保留全文、换行且转义外部内容', () => { const message = `Failure: ${'Detailed error'.repeat(160)}\n`; const html = renderNotice(appNoticeReducer(initialAppNoticeState, { type: 'show', notice: { ...notice, message, tone: 'error' }, })); expect(html).toContain('action-feedback inline-notice error'); expect(html).toContain('role="alert"'); expect(html).toContain('aria-live="assertive"'); expect(html).toContain('Detailed error'.repeat(160)); expect(html).toContain('\n<script>'); expect(html).not.toContain('