221 lines
7.0 KiB
TypeScript
221 lines
7.0 KiB
TypeScript
import * as React from 'react';
|
||
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||
|
||
const mockHost = (
|
||
type: string,
|
||
props?: Record<string, unknown>,
|
||
...children: React.ReactNode[]
|
||
) => React.createElement(type as React.ElementType, props, ...children);
|
||
|
||
function flushPromises(ms = 0) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
const {
|
||
nativeShareMock,
|
||
toastErrorMock,
|
||
userInfoRef,
|
||
} = vi.hoisted(() => ({
|
||
nativeShareMock: vi.fn(),
|
||
toastErrorMock: vi.fn(),
|
||
userInfoRef: {
|
||
current: {
|
||
inviteUrl: 'https://example.com/invite/abc',
|
||
},
|
||
},
|
||
}));
|
||
|
||
vi.mock('react-native', () => ({
|
||
Image: (props: Record<string, unknown> & { children?: React.ReactNode }) =>
|
||
mockHost('Image', props, props.children),
|
||
ImageBackground: (props: Record<string, unknown> & { children?: React.ReactNode }) =>
|
||
mockHost('ImageBackground', props, props.children),
|
||
Pressable: (props: Record<string, unknown> & { children?: React.ReactNode }) =>
|
||
mockHost('Pressable', props, props.children),
|
||
Share: {
|
||
share: nativeShareMock,
|
||
},
|
||
StyleSheet: {
|
||
absoluteFillObject: {},
|
||
create: <T extends Record<string, unknown>>(styles: T) => styles,
|
||
},
|
||
View: (props: Record<string, unknown> & { children?: React.ReactNode }) =>
|
||
mockHost('View', props, props.children),
|
||
}));
|
||
|
||
vi.mock('react-native-safe-area-context', () => ({
|
||
SafeAreaView: (props: Record<string, unknown> & { children?: React.ReactNode }) =>
|
||
mockHost('SafeAreaView', props, props.children),
|
||
}));
|
||
|
||
vi.mock('@telescope/shim-text', () => ({
|
||
__esModule: true,
|
||
default: (props: Record<string, unknown> & { children?: React.ReactNode }) =>
|
||
mockHost('Text', props, props.children),
|
||
}));
|
||
|
||
vi.mock('@telescope/toast', () => ({
|
||
useToast: () => ({
|
||
error: toastErrorMock,
|
||
}),
|
||
}));
|
||
|
||
vi.mock('@telescope/user-info', () => ({
|
||
useUserInfo: () => ({
|
||
userInfo: userInfoRef.current,
|
||
}),
|
||
}));
|
||
|
||
vi.mock('../../../src/assets', () => ({
|
||
appAssets: {
|
||
xingyunAuthBackground: 'xingyun-auth-bg',
|
||
xingyunShareTop: 'xingyun-share-top',
|
||
},
|
||
}));
|
||
|
||
vi.mock('../../../src/theme', () => ({
|
||
theme: {
|
||
colors: {
|
||
white: '#ffffff',
|
||
},
|
||
},
|
||
}));
|
||
|
||
vi.mock('../../../src/components/AppPrimitives', () => ({
|
||
AppStatusBar: (props: Record<string, unknown>) => mockHost('AppStatusBar', props),
|
||
}));
|
||
|
||
vi.mock('../../../src/components/AppIcons', () => ({
|
||
AppIcon: (props: Record<string, unknown>) => mockHost('AppIcon', props),
|
||
}));
|
||
|
||
vi.mock('../../../src/navigation/AppScreenHeader', () => ({
|
||
AppPageHeader: (props: Record<string, unknown>) => mockHost('AppPageHeader', props),
|
||
}));
|
||
|
||
describe('screens/main/SharePosterScreen', () => {
|
||
const navigation = {
|
||
goBack: vi.fn(),
|
||
};
|
||
|
||
beforeEach(() => {
|
||
vi.clearAllMocks();
|
||
userInfoRef.current = {
|
||
inviteUrl: 'https://example.com/invite/abc',
|
||
};
|
||
nativeShareMock.mockResolvedValue(undefined);
|
||
});
|
||
|
||
it('点击分享按钮会调用系统分享并带上邀请链接', async () => {
|
||
const { SharePosterScreen } = await import('../../../src/screens/main/SharePosterScreen');
|
||
let renderer!: ReactTestRenderer;
|
||
|
||
await act(async () => {
|
||
renderer = create(<SharePosterScreen navigation={navigation} />);
|
||
});
|
||
|
||
const shareButton = renderer.root.findAllByType('Pressable' as React.ElementType).find(
|
||
node => node
|
||
.findAllByType('Text' as React.ElementType)
|
||
.some(text => text.props.children === '通过链接分享'),
|
||
);
|
||
|
||
await act(async () => {
|
||
shareButton?.props.onPress();
|
||
await flushPromises(10);
|
||
});
|
||
|
||
expect(nativeShareMock).toHaveBeenCalledWith({
|
||
message:
|
||
'我正在使用行云网络服务,邀请你一起体验稳定连接:https://example.com/invite/abc',
|
||
});
|
||
});
|
||
|
||
it('没有邀请链接时会分享 App Store 地址', async () => {
|
||
userInfoRef.current = {
|
||
inviteUrl: '',
|
||
};
|
||
const { SharePosterScreen } = await import('../../../src/screens/main/SharePosterScreen');
|
||
let renderer!: ReactTestRenderer;
|
||
|
||
await act(async () => {
|
||
renderer = create(<SharePosterScreen navigation={navigation} />);
|
||
});
|
||
|
||
const shareButton = renderer.root.findAllByType('Pressable' as React.ElementType).find(
|
||
node => node
|
||
.findAllByType('Text' as React.ElementType)
|
||
.some(text => text.props.children === '通过链接分享'),
|
||
);
|
||
|
||
await act(async () => {
|
||
shareButton?.props.onPress();
|
||
await flushPromises(10);
|
||
});
|
||
|
||
expect(nativeShareMock).toHaveBeenCalledWith({
|
||
message:
|
||
'我正在使用行云网络服务,邀请你一起体验稳定连接:https://apps.apple.com/cn/app/%E8%A1%8C%E4%BA%91%E5%8A%A0%E9%80%9F%E5%99%A8-%E5%85%A8%E7%90%83%E7%BD%91%E7%BB%9C%E5%8A%A0%E9%80%9F/id6751933882',
|
||
});
|
||
});
|
||
|
||
it('顶部导航会复用统一的 AppPageHeader', async () => {
|
||
const { SharePosterScreen } = await import('../../../src/screens/main/SharePosterScreen');
|
||
let renderer!: ReactTestRenderer;
|
||
|
||
await act(async () => {
|
||
renderer = create(<SharePosterScreen navigation={navigation} />);
|
||
});
|
||
|
||
const header = renderer.root.findByType('AppPageHeader' as React.ElementType);
|
||
expect(header.props.title).toBe('分享有奖');
|
||
expect(header.props.tone).toBe('dark');
|
||
expect(header.props.showBackButton).toBe(true);
|
||
});
|
||
|
||
it('渲染 XINY 风格的分享页内容', async () => {
|
||
const { SharePosterScreen } = await import('../../../src/screens/main/SharePosterScreen');
|
||
let renderer!: ReactTestRenderer;
|
||
|
||
await act(async () => {
|
||
renderer = create(<SharePosterScreen navigation={navigation} />);
|
||
});
|
||
|
||
expect(renderer.root.findByType('ImageBackground' as React.ElementType).props.source)
|
||
.toBe('xingyun-auth-bg');
|
||
expect(renderer.root.findByType('Image' as React.ElementType).props.source)
|
||
.toBe('xingyun-share-top');
|
||
|
||
const texts = renderer.root.findAllByType('Text' as React.ElementType);
|
||
expect(texts.some(node => node.props.children === '推荐朋友,领永久会员')).toBe(true);
|
||
expect(texts.some(node => node.props.children === '通过链接分享')).toBe(true);
|
||
|
||
const shareButton = renderer.root.findAllByType('Pressable' as React.ElementType).find(
|
||
node => node
|
||
.findAllByType('Text' as React.ElementType)
|
||
.some(text => text.props.children === '通过链接分享'),
|
||
);
|
||
expect(shareButton?.props.style).toMatchObject({
|
||
width: 280,
|
||
height: 48,
|
||
borderRadius: 20.5,
|
||
backgroundColor: '#757EFF',
|
||
});
|
||
});
|
||
|
||
it('作为底部 tab 页面时隐藏返回按钮', async () => {
|
||
const { SharePosterScreen } = await import('../../../src/screens/main/SharePosterScreen');
|
||
let renderer!: ReactTestRenderer;
|
||
|
||
await act(async () => {
|
||
renderer = create(
|
||
<SharePosterScreen navigation={navigation} showBackButton={false} />,
|
||
);
|
||
});
|
||
|
||
const header = renderer.root.findByType('AppPageHeader' as React.ElementType);
|
||
expect(header.props.showBackButton).toBe(false);
|
||
});
|
||
});
|