@@ -47,6 +47,8 @@ type Examples =
4747 | 'getRootTag'
4848 | 'getSet'
4949 | 'getString'
50+ | 'getStringRoundTrip'
51+ | 'getStringControlChars'
5052 | 'getUnion'
5153 | 'getUnsafeObject'
5254 | 'getValue'
@@ -69,6 +71,49 @@ type ErrorExamples =
6971 | 'promiseAssert'
7072 | 'installJSIBindings' ;
7173
74+ const STRING_ROUND_TRIP_ROUNDS = 200 ;
75+ const STRING_ROUND_TRIP_INPUTS = [
76+ '' ,
77+ 'a' ,
78+ 'hello' ,
79+ 'h\u00e9llo \u00e7\u00e3\u00f5' ,
80+ '\u65e5\u672c\u8a9e\u30c6\u30ad\u30b9\u30c8' ,
81+ '\u041f\u0440\u0438\u0432\u0435\u0442' ,
82+ '\u0645\u0631\u062d\u0628\u0627' ,
83+ '\ud83d\ude00' ,
84+ '\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66' ,
85+ '\ud83c\uddee\ud83c\uddf3' ,
86+ 'e\u0301' ,
87+ 'x' . repeat ( 255 ) ,
88+ 'x' . repeat ( 256 ) ,
89+ 'x' . repeat ( 257 ) ,
90+ '\ud83d\ude00' . repeat ( 200 ) ,
91+ 'x' . repeat ( 5000 ) ,
92+ '\u65e5' . repeat ( 100000 ) ,
93+ ] ;
94+
95+ // Kept out of the Maestro-driven test: the demo Toast shows the input and
96+ // uiautomator cannot serialize NUL or lone surrogates.
97+ const CONTROL_CHAR_INPUTS = [ '\ud800' , '\udc00x' , 'a\u0000b' ] ;
98+
99+ function randomUnicodeString ( ) : string {
100+ let out = '' ;
101+ const length = Math . floor ( Math . random ( ) * 3000 ) ;
102+ for ( let i = 0 ; i < length ; i ++ ) {
103+ const kind = Math . random ( ) ;
104+ const codePoint =
105+ kind < 0.4
106+ ? 32 + Math . floor ( Math . random ( ) * 95 )
107+ : kind < 0.6
108+ ? 0x80 + Math . floor ( Math . random ( ) * 0x780 )
109+ : kind < 0.8
110+ ? 0x1000 + Math . floor ( Math . random ( ) * 0xc000 )
111+ : 0x1f300 + Math . floor ( Math . random ( ) * 0x300 ) ;
112+ out += String . fromCodePoint ( codePoint ) ;
113+ }
114+ return out ;
115+ }
116+
72117class SampleTurboModuleExample extends React . Component < { } , State > {
73118 static contextType : React . Context < RootTag > = RootTagContext ;
74119 eventSubscriptions : EventSubscription [ ] = [ ] ;
@@ -120,6 +165,36 @@ class SampleTurboModuleExample extends React.Component<{}, State> {
120165 NativeSampleTurboModule . getObject ( { a : 1 , b : 'foo' , c : null } ) ,
121166 getRootTag : ( ) => NativeSampleTurboModule . getRootTag ( this . context ) ,
122167 getString : ( ) => NativeSampleTurboModule . getString ( 'Hello' ) ,
168+ getStringRoundTrip : ( ) => {
169+ const expected =
170+ STRING_ROUND_TRIP_ROUNDS * ( STRING_ROUND_TRIP_INPUTS . length + 2 ) ;
171+ let pass = 0 ;
172+ let failure = '' ;
173+ for ( let round = 0 ; round < STRING_ROUND_TRIP_ROUNDS ; round ++ ) {
174+ const inputs = [
175+ ...STRING_ROUND_TRIP_INPUTS ,
176+ randomUnicodeString ( ) ,
177+ randomUnicodeString ( ) ,
178+ ] ;
179+ for ( const input of inputs ) {
180+ const output = NativeSampleTurboModule . getString ( input ) ;
181+ if ( output === input ) {
182+ pass ++ ;
183+ } else if ( failure === '' ) {
184+ failure = ` FAIL len ${ input . length } -> ${
185+ output == null ? 'null' : output . length
186+ } `;
187+ }
188+ }
189+ }
190+ // $FlowFixMe[incompatible-call] null must round-trip as null
191+ const nullOk = NativeSampleTurboModule . getString ( null ) === null ;
192+ return `${ pass } /${ expected } ok${ nullOk ? '' : ' null FAIL' } ${ failure } ` ;
193+ } ,
194+ getStringControlChars : ( ) =>
195+ CONTROL_CHAR_INPUTS . map (
196+ input => NativeSampleTurboModule . getString ( input ) === input ,
197+ ) . join ( ',' ) ,
123198 getUnsafeObject : ( ) =>
124199 NativeSampleTurboModule . getUnsafeObject ( { a : 1 , b : 'foo' , c : null } ) ,
125200 getValue : ( ) =>
0 commit comments