* Copyright (c) 2025 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {TestSuite} from '@rnoh/testerino';
import {
SampleComponent,
SampleComponentRef,
GeneratedSampleComponentCAPI,
GeneratedSampleComponentCAPIRef,
GeneratedSampleComponentArkTSRef,
GeneratedSampleComponentArkTS,
CodegenLibSampleComponent,
ContainerView,
Blank,
BindSheetView,
} from 'react-native-sample-package';
import {useEffect, useState} from 'react';
import React from 'react';
import {Button, Effect, Ref, TestCase} from '../components';
import {IncomingData as GeneratedSampleNativeComponentArkTSCustomProps} from 'react-native-harmony-sample-package/src/specs/arkts-components/GeneratedSampleNativeComponent';
import {
IncomingData as GeneratedSampleNativeComponentCAPICustomProps,
SupportedCommandArgs as GeneratedSampleNativeComponentCAPICommandArgs,
} from 'react-native-harmony-sample-package/src/specs/cpp-components/GeneratedSampleNativeComponent';
import {Text, TextInput, View, UIManager} from 'react-native';
export function CustomNativeComponentTest() {
return (
<TestSuite name="Custom Native Component">
<ManualCustomComponentImplementationTest />
<GeneratedCustomComponentTest />
<ContainerViewTest />
<BindSheetViewTest />
</TestSuite>
);
}
function ManualCustomComponentImplementationTest() {
const [refreshKey, setRefreshKey] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setRefreshKey(prev => prev + 1);
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
return (
<TestSuite name="no codegen">
<TestCase.Example itShould="render red rectangle">
<SampleComponent backgroundColor="red" size={64} />
</TestCase.Example>
<TestCase.Example itShould="render green rectangle inside red rectangle">
<SampleComponent backgroundColor="red" size={64}>
<SampleComponent backgroundColor="green" size={32} />
</SampleComponent>
</TestCase.Example>
<TestCase.Example itShould="render red rectangle with black text">
<SampleComponent backgroundColor="red" size={64} textColor="black" />
</TestCase.Example>
<TestCase.Manual
itShould="handle custom native event when clicked"
initialState={false}
arrange={({setState}) => (
<SampleComponent
backgroundColor="red"
size={64}
onSampleClick={() => setState(true)}
/>
)}
assert={({expect, state}) => {
expect(state).to.be.true;
}}
/>
<TestCase.Example itShould="change bgColor every second">
<SampleComponent
backgroundColor={refreshKey % 2 === 0 ? 'red' : 'green'}
size={64}
/>
</TestCase.Example>
<TestCase.Example modal itShould="show and hide red rectangle">
<View style={{height: 64}}>
{refreshKey % 2 === 0 && (
<SampleComponent
backgroundColor={refreshKey % 2 === 0 ? 'red' : 'green'}
size={64}
/>
)}
</View>
</TestCase.Example>
<TestCase.Example itShould="show/hide blue rectangle">
<SampleComponent backgroundColor="red" size={64}>
<Blinker>
<SampleComponent backgroundColor="blue" size={32} />
</Blinker>
</SampleComponent>
</TestCase.Example>
<TestCase.Example itShould="toggle font size in the component below button (native commands)">
<Ref<SampleComponentRef>
render={ref => {
return (
<>
<Button
label="Toggle Font Size"
onPress={() => {
ref.current?.toggleFontSize();
}}
/>
<SampleComponent ref={ref} backgroundColor="blue" size={32} />
</>
);
}}
/>
</TestCase.Example>
<TestCase.Example itShould="not report an error when Blank is added as a child to SampleComponent">
<SampleComponent backgroundColor="blue" size={32}>
<Blank />
<Blank />
<Blank />
</SampleComponent>
</TestCase.Example>
</TestSuite>
);
}
function Blinker({children}: any) {
const [isVisible, setIsVisible] = useState(true);
return (
<React.Fragment>
<Button
label="Run"
onPress={() => {
setIsVisible(prev => !prev);
}}
/>
{isVisible && children}
</React.Fragment>
);
}
function VerifyNativeCommandExists() {
return (
<TestSuite name="verify native commands">
<TestCase.Logical
itShould="verify that native command emitNativeEvent exists"
fn={({expect}) => {
const command = UIManager.getViewManagerConfig('GeneratedSampleView')
?.Commands?.emitNativeEvent;
expect(command).to.be.eq('emitNativeEvent');
}}
/>
</TestSuite>
);
}
function GeneratedCustomComponentTest() {
return (
<TestSuite name="generated custom component">
<VerifyNativeCommandExists />
<TestSuite name="ArkTS">
<TestCase.Automated<
GeneratedSampleNativeComponentArkTSCustomProps | undefined
>
itShould="ensure equality between provided and received data"
initialState={undefined}
arrange={({setState, done}) => {
return (
<Ref<GeneratedSampleComponentArkTSRef>
render={ref => (
<GeneratedSampleComponentArkTS
ref={ref}
testProps={{
booleanTest: true,
intTest: 42,
floatTest: 42.5,
doubleTest: 42.5,
stringTest: 'foobar',
objectTest: {foo: {bar: 'baz'}},
colorTest: 'red',
arrayTest: ['foo', 'bar'],
readOnlyArrayTest: ['foo', 'bar'],
// intEnumTest: 1,
}}
onDirectEvent={(state: any) => {
setState(state);
done();
}}>
<Effect
onMount={() => {
setTimeout(() =>
ref.current?.emitNativeEvent('directEvent'),
);
}}
/>
</GeneratedSampleComponentArkTS>
)}
/>
);
}}
act={({}) => {}}
assert={async ({expect, state}) => {
expect(state?.booleanTest).to.be.true;
expect(state?.booleanWithDefaultTest).to.be.true;
expect(state?.intTest).to.be.eq(42);
expect(state?.intWithDefault).to.be.eq(42);
expect(state?.floatTest).closeTo(42.5, 0.001);
expect(state?.floatWithDefaultTest).closeTo(42.5, 0.001);
expect(state?.doubleTest).closeTo(42.5, 0.001);
expect(state?.doubleWithDefaultTest).closeTo(42.5, 0.001);
expect(state?.stringTest).to.be.eq('foobar');
expect(state?.stringWithDefaultTest).to.be.eq('foobar');
expect(state?.objectTest).to.deep.eq({foo: {bar: 'baz'}});
expect(state?.arrayTest).to.deep.eq(['foo', 'bar']);
expect(state?.readOnlyArrayTest).to.deep.eq(['foo', 'bar']);
expect(state?.stringEnumTest).to.be.eq('foo');
// expect(state?.intEnumTest).to.be.eq(1);
}}
/>
</TestSuite>
<TestSuite name="C-API">
<TestCase.Automated<
GeneratedSampleNativeComponentCAPICustomProps | undefined
>
skip={{
android: true,
harmony: {arkTs: 'C-API only test', cAPI: false},
}}
itShould="receive props and emit them back via event"
initialState={undefined}
arrange={({setState, done}) => {
return (
<Ref<GeneratedSampleComponentCAPIRef>
render={ref => (
<GeneratedSampleComponentCAPI
ref={ref}
testProps={{
booleanTest: true,
intTest: 42,
floatTest: 42.5,
doubleTest: 42.5,
stringTest: 'foobar',
colorTest: 'red',
arrayTest: ['foo', 'bar'],
readOnlyArrayTest: ['foo', 'bar'],
objectTest: {foo: {bar: 'baz'}},
}}
onDirectEvent={(state: any) => {
setState(state);
done();
}}>
<Effect
onMount={() => {
setTimeout(() =>
ref.current?.emitNativeEvent('directEvent'),
);
}}
/>
</GeneratedSampleComponentCAPI>
)}
/>
);
}}
act={() => {}}
assert={async ({expect, state}) => {
expect(state?.booleanTest).to.be.true;
expect(state?.booleanWithDefaultTest).to.be.true;
expect(state?.intTest).to.be.eq(42);
expect(state?.intWithDefault).to.be.eq(42);
expect(state?.floatTest).closeTo(42.5, 0.1);
expect(state?.floatWithDefaultTest).closeTo(42.5, 0.1);
expect(state?.doubleTest).closeTo(42.5, 0.1);
expect(state?.doubleWithDefaultTest).closeTo(42.5, 0.1);
expect(state?.stringTest).to.be.eq('foobar');
expect(state?.stringWithDefaultTest).to.be.eq('foobar');
expect(state?.arrayTest).to.deep.eq(['foo', 'bar']);
expect(state?.readOnlyArrayTest).to.deep.eq(['foo', 'bar']);
expect(state?.stringEnumTest).to.be.eq('foo');
expect(state?.colorTest).to.be.not.undefined;
expect(state?.objectTest?.foo?.bar).to.be.eq('baz');
}}
/>
<TestCase.Automated<
GeneratedSampleNativeComponentCAPICommandArgs | undefined
>
tags={['sequential']}
skip={{
android: true,
harmony: {arkTs: 'C-API only test', cAPI: false},
}}
itShould="receive command args and emit them back via event"
initialState={undefined}
arrange={({setState, done}) => {
return (
<Ref<GeneratedSampleComponentCAPIRef>
render={ref => (
<GeneratedSampleComponentCAPI
ref={ref}
hidden
testProps={{
booleanTest: true,
intTest: 0,
floatTest: 0.0,
doubleTest: 0.0,
stringTest: '',
colorTest: 'blue',
arrayTest: [],
readOnlyArrayTest: [],
objectTest: {foo: {bar: ''}},
}}
onReceivedCommandArgs={(state: any) => {
setState(state);
done();
}}>
<Effect
onMount={() => {
setTimeout(() => {
ref.current?.emitCommandArgs(
42 /* intTest */,
42.42 /* floatTest */,
42.42 /* doubleTest */,
'foobar' /* stringTest */,
true /* booleanTest */,
);
});
}}
/>
</GeneratedSampleComponentCAPI>
)}
/>
);
}}
act={({}) => {}}
assert={async ({expect, state}) => {
expect(state?.intTest).to.be.eq(42);
expect(state?.floatTest).closeTo(42.5, 0.1);
expect(state?.doubleTest).closeTo(42.5, 0.1);
expect(state?.stringTest).eq('foobar');
expect(state?.booleanTest).to.be.true;
}}
/>
</TestSuite>
<TestSuite name="generated by codegen-lib">
<TestCase.Manual
itShould="mount ArkTS component that relies on the generated code by codegen-lib, and onMount is called only once"
initialState={{
text: '',
count: 0,
}}
arrange={({setState}) => {
return (
<CodegenLibSampleComponent
style={{width: 100, height: 100}}
implementation="ArkTS"
text="ArkTS"
onMount={text => {
setState(prevState => ({
text,
count: prevState.count + 1,
}));
}}
/>
);
}}
assert={({expect, state}) => {
expect(state.text).to.be.eq('ArkTS');
expect(state.count).to.be.eq(1);
}}
/>
<TestCase.Manual
itShould="mount Cpp component that relies on the generated code by codegen-lib"
initialState={''}
arrange={({setState}) => {
return (
<CodegenLibSampleComponent
style={{width: 100, height: 100}}
implementation="Cpp"
text="Cpp"
onMount={text => {
setState(text);
}}
/>
);
}}
assert={({expect, state}) => {
expect(state).to.be.eq('Cpp');
}}
/>
</TestSuite>
</TestSuite>
);
}
function ContainerViewTest() {
const [childCount, setChildCount] = React.useState(0);
React.useEffect(() => {
const timeoutId = setTimeout(() => {
if (childCount === 0) {
setChildCount(5);
} else {
setChildCount(childCount - 1);
}
}, 1000);
return () => clearTimeout(timeoutId);
}, [childCount]);
return (
<TestSuite name="Custom container component">
<TestCase.Example
modal
itShould="display a custom component with children">
<ContainerView>
<View style={{width: 100, height: 100, backgroundColor: 'red'}} />
<Text>Content</Text>
</ContainerView>
</TestCase.Example>
<TestCase.Example itShould="maintain correct positions of custom components when removing">
<View style={{height: 128, backgroundColor: 'silver'}}>
<ContainerView style={{backgroundColor: 'white'}}>
<View collapsable={false}>
{Array.from({length: childCount}, (_v, k) => (
<ContainerView key={k}>
<Text>Element no. {k + 1}</Text>
</ContainerView>
))}
</View>
</ContainerView>
</View>
</TestCase.Example>
<TestCase.Example itShould="display a custom component with toggleable child">
<ContainerView>
<Blinker>
<View style={{width: 100, height: 100, backgroundColor: 'red'}} />
<Text>Content</Text>
</Blinker>
</ContainerView>
</TestCase.Example>
<TestCase.Example itShould="support arbitrary levels of custom component nesting">
<ArbitraryNestingTest />
</TestCase.Example>
<TestCase.Example itShould="display a custom component with ArkTS and CAPI children">
<ContainerView>
<View
collapsable={false}
style={{backgroundColor: 'blue', padding: 40}}>
<GeneratedSampleComponentArkTS
testProps={{
booleanTest: true,
intTest: 42,
floatTest: 42.5,
doubleTest: 42.5,
stringTest: 'foobar',
objectTest: {foo: {bar: 'baz'}},
colorTest: 'red',
arrayTest: ['foo', 'bar'],
readOnlyArrayTest: ['foo', 'bar'],
// intEnumTest: 1,
}}
/>
</View>
</ContainerView>
</TestCase.Example>
</TestSuite>
);
}
function ArbitraryNestingTest() {
const [depthString, setDepthString] = React.useState('3');
const depth = Math.min(parseInt(depthString) ?? 0, 15);
return (
<View>
<TextInput
value={depthString}
onChange={e => setDepthString(e.nativeEvent.text)}
style={{height: 30}}
/>
<IterativeContainer depth={depth} />
</View>
);
}
function IterativeContainer({depth}: {depth: number}) {
const COLORS = ['red', 'blue', 'white'];
if (depth <= 0 || isNaN(depth)) {
return null;
}
const backgroundColor = COLORS[depth % COLORS.length];
return (
<ContainerView>
<View collapsable={false} style={{backgroundColor, padding: 10}}>
<Text>Depth: {depth}</Text>
<IterativeContainer depth={depth - 1} />
</View>
</ContainerView>
);
}
function BindSheetViewTest() {
const [showSheet, setShowSheet] = useState(false);
return (
<TestSuite name="Custom ArkTS component with bindSheet">
<TestCase.Example itShould="display custom ArkTS component with bindSheet">
{showSheet ? (
<BindSheetView>
<Button label="Dismiss Sheet" onPress={() => setShowSheet(false)} />
</BindSheetView>
) : (
<Button label="Show Sheet" onPress={() => setShowSheet(true)} />
)}
</TestCase.Example>
</TestSuite>
);
}