vxrn 2.13.10 → 2.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exports/prebuildWithoutExpo.mjs +15 -1
- package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
- package/dist/exports/prebuildWithoutExpo.native.js +15 -1
- package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
- package/dist/utils/nativeSourceContract.mjs +742 -97
- package/dist/utils/nativeSourceContract.mjs.map +1 -1
- package/dist/utils/nativeSourceContract.native.js +758 -70
- package/dist/utils/nativeSourceContract.native.js.map +1 -1
- package/expo-plugin.cjs +15 -2
- package/native-project-patches.cjs +42 -0
- package/package.json +12 -12
- package/src/exports/expoPlugin.test.mjs +52 -0
- package/src/exports/prebuildWithoutExpo.test.ts +18 -0
- package/src/exports/prebuildWithoutExpo.ts +18 -1
- package/src/utils/nativeSourceContract.test.ts +74 -9
- package/src/utils/nativeSourceContract.ts +810 -105
- package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
- package/types/utils/nativeSourceContract.d.ts.map +1 -1
package/expo-plugin.cjs
CHANGED
|
@@ -898,11 +898,24 @@ extern void OneSwiftRegisterPackage(const char *name, int (*entry)(int, char **)
|
|
|
898
898
|
return nextConfig
|
|
899
899
|
},
|
|
900
900
|
],
|
|
901
|
+
[
|
|
902
|
+
withGradleProperties,
|
|
903
|
+
(nextConfig) => {
|
|
904
|
+
// MainActivity opts into androidx edge-to-edge; this tells react
|
|
905
|
+
// native the window is edge-to-edge on every api level.
|
|
906
|
+
const properties = nextConfig.modResults.filter(
|
|
907
|
+
(item) => !(item.type === 'property' && item.key === 'edgeToEdgeEnabled')
|
|
908
|
+
)
|
|
909
|
+
properties.push({ type: 'property', key: 'edgeToEdgeEnabled', value: 'true' })
|
|
910
|
+
nextConfig.modResults = properties
|
|
911
|
+
return nextConfig
|
|
912
|
+
},
|
|
913
|
+
],
|
|
901
914
|
[
|
|
902
915
|
withMainActivity,
|
|
903
916
|
(nextConfig) => {
|
|
904
|
-
const contents = nativeProjectPatches.
|
|
905
|
-
nextConfig.modResults.contents
|
|
917
|
+
const contents = nativeProjectPatches.enableEdgeToEdgeInMainActivity(
|
|
918
|
+
nativeProjectPatches.addReactNativeScreensFix(nextConfig.modResults.contents)
|
|
906
919
|
)
|
|
907
920
|
nextConfig.modResults.contents = options.holdLaunchScreen
|
|
908
921
|
? nativeProjectPatches.holdLaunchScreenInMainActivity(contents)
|
|
@@ -704,6 +704,47 @@ function addReactNativeScreensFix(input) {
|
|
|
704
704
|
return input
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
+
// edge-to-edge status bars follow the system appearance: AppCompat's DayNight
|
|
708
|
+
// theme leaves the status bar icon style at its white default, so every One
|
|
709
|
+
// android activity opts into androidx edge-to-edge like a native app. runs
|
|
710
|
+
// after addReactNativeScreensFix, which guarantees onCreate exists.
|
|
711
|
+
const EDGE_TO_EDGE_KT_IMPORT = 'import androidx.activity.enableEdgeToEdge'
|
|
712
|
+
const EDGE_TO_EDGE_JAVA_IMPORT = 'import androidx.activity.EdgeToEdge;'
|
|
713
|
+
|
|
714
|
+
function enableEdgeToEdgeInMainActivity(mainActivity) {
|
|
715
|
+
if (
|
|
716
|
+
mainActivity.includes(EDGE_TO_EDGE_KT_IMPORT) ||
|
|
717
|
+
mainActivity.includes(EDGE_TO_EDGE_JAVA_IMPORT)
|
|
718
|
+
) {
|
|
719
|
+
return mainActivity
|
|
720
|
+
}
|
|
721
|
+
const isKotlin = mainActivity.includes('class MainActivity : ReactActivity()')
|
|
722
|
+
if (isKotlin) {
|
|
723
|
+
if (!SUPER_ON_CREATE.test(mainActivity)) {
|
|
724
|
+
throw new Error(
|
|
725
|
+
'[vxrn] MainActivity.kt changed shape: cannot enable edge-to-edge before super.onCreate'
|
|
726
|
+
)
|
|
727
|
+
}
|
|
728
|
+
return mainActivity
|
|
729
|
+
.replace(/package\s+[\w.]+/, '$&\n' + EDGE_TO_EDGE_KT_IMPORT)
|
|
730
|
+
.replace(
|
|
731
|
+
SUPER_ON_CREATE,
|
|
732
|
+
'$1// [vxrn/one] status bar icons follow the system appearance\n$1enableEdgeToEdge()\n$1super.onCreate(null)'
|
|
733
|
+
)
|
|
734
|
+
}
|
|
735
|
+
if (!/^([ \t]*)super\.onCreate\(savedInstanceState\);$/m.test(mainActivity)) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
'[vxrn] MainActivity.java changed shape: cannot enable edge-to-edge before super.onCreate'
|
|
738
|
+
)
|
|
739
|
+
}
|
|
740
|
+
return mainActivity
|
|
741
|
+
.replace(/package\s+[\w.]+;/, '$&\n' + EDGE_TO_EDGE_JAVA_IMPORT)
|
|
742
|
+
.replace(
|
|
743
|
+
/^([ \t]*)super\.onCreate\(savedInstanceState\);$/m,
|
|
744
|
+
'$1// [vxrn/one] status bar icons follow the system appearance\n$1EdgeToEdge.enable(this);\n$1super.onCreate(savedInstanceState);'
|
|
745
|
+
)
|
|
746
|
+
}
|
|
747
|
+
|
|
707
748
|
/**
|
|
708
749
|
* swift packages in the app (a directory with Package.swift whose view a
|
|
709
750
|
* react native screen imports) are generated by prebuild as one local pod each
|
|
@@ -849,4 +890,5 @@ module.exports = {
|
|
|
849
890
|
replaceAppBuildGradleReactBlock,
|
|
850
891
|
addDepsPatchToAppBuildGradle,
|
|
851
892
|
addReactNativeScreensFix,
|
|
893
|
+
enableEdgeToEdgeInMainActivity,
|
|
852
894
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vxrn",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.1",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
"@react-native-community/cli": "^20.2.0",
|
|
81
81
|
"@react-native-community/cli-config": "^20.2.0",
|
|
82
82
|
"@react-native/dev-middleware": "^0.88.0",
|
|
83
|
-
"@vxrn/compiler": "2.
|
|
84
|
-
"@vxrn/debug": "2.
|
|
85
|
-
"@vxrn/query-string": "2.
|
|
86
|
-
"@vxrn/resolve": "2.
|
|
87
|
-
"@vxrn/url-parse": "2.
|
|
88
|
-
"@vxrn/utils": "2.
|
|
89
|
-
"@vxrn/vendor": "2.
|
|
90
|
-
"@vxrn/vite-flow": "2.
|
|
83
|
+
"@vxrn/compiler": "2.14.1",
|
|
84
|
+
"@vxrn/debug": "2.14.1",
|
|
85
|
+
"@vxrn/query-string": "2.14.1",
|
|
86
|
+
"@vxrn/resolve": "2.14.1",
|
|
87
|
+
"@vxrn/url-parse": "2.14.1",
|
|
88
|
+
"@vxrn/utils": "2.14.1",
|
|
89
|
+
"@vxrn/vendor": "2.14.1",
|
|
90
|
+
"@vxrn/vite-flow": "2.14.1",
|
|
91
91
|
"citty": "^0.1.6",
|
|
92
92
|
"dotenv": "^17.2.1",
|
|
93
93
|
"dotenv-expand": "^12.0.2",
|
|
@@ -121,20 +121,20 @@
|
|
|
121
121
|
"react-native": "0.88.0",
|
|
122
122
|
"typescript": "7.0.2",
|
|
123
123
|
"vitest": "^4.1.0",
|
|
124
|
-
"@vxrn/vite-plugin-metro": "2.
|
|
124
|
+
"@vxrn/vite-plugin-metro": "2.14.1"
|
|
125
125
|
},
|
|
126
126
|
"publishConfig": {
|
|
127
127
|
"access": "public"
|
|
128
128
|
},
|
|
129
129
|
"peerDependencies": {
|
|
130
|
-
"@vxrn/vite-plugin-metro": "2.
|
|
130
|
+
"@vxrn/vite-plugin-metro": "2.14.1"
|
|
131
131
|
},
|
|
132
132
|
"peerDependenciesMeta": {
|
|
133
133
|
"@vxrn/vite-plugin-metro": {
|
|
134
134
|
"optional": true
|
|
135
135
|
}
|
|
136
136
|
},
|
|
137
|
-
"releaseSourceCommit": "
|
|
137
|
+
"releaseSourceCommit": "e780d1ad0bdf938d0e21a5cdff99a0cc78184030",
|
|
138
138
|
"repository": {
|
|
139
139
|
"type": "git",
|
|
140
140
|
"url": "git+https://github.com/onejs/one.git",
|
|
@@ -131,6 +131,37 @@ class MainActivity : ReactActivity() {
|
|
|
131
131
|
expect(result).toContain('RNScreensFragmentFactory')
|
|
132
132
|
expect(result).toContain('super.onCreate(null)')
|
|
133
133
|
})
|
|
134
|
+
|
|
135
|
+
it('enables edge-to-edge in MainActivity', () => {
|
|
136
|
+
const kotlin = patches.enableEdgeToEdgeInMainActivity(
|
|
137
|
+
patches.addReactNativeScreensFix(`
|
|
138
|
+
package dev.one.test
|
|
139
|
+
import com.facebook.react.ReactActivity
|
|
140
|
+
class MainActivity : ReactActivity() {
|
|
141
|
+
}
|
|
142
|
+
`)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
expect(kotlin).toContain('import androidx.activity.enableEdgeToEdge')
|
|
146
|
+
expect(kotlin).toContain('enableEdgeToEdge()')
|
|
147
|
+
expect(kotlin.indexOf('enableEdgeToEdge()')).toBeLessThan(
|
|
148
|
+
kotlin.indexOf('super.onCreate(null)')
|
|
149
|
+
)
|
|
150
|
+
expect(patches.enableEdgeToEdgeInMainActivity(kotlin)).toBe(kotlin)
|
|
151
|
+
|
|
152
|
+
const java = patches.enableEdgeToEdgeInMainActivity(
|
|
153
|
+
patches.addReactNativeScreensFix(`
|
|
154
|
+
package dev.one.test;
|
|
155
|
+
import com.facebook.react.ReactActivity;
|
|
156
|
+
public class MainActivity extends ReactActivity {
|
|
157
|
+
}
|
|
158
|
+
`)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
expect(java).toContain('import androidx.activity.EdgeToEdge;')
|
|
162
|
+
expect(java).toContain('EdgeToEdge.enable(this);')
|
|
163
|
+
expect(patches.enableEdgeToEdgeInMainActivity(java)).toBe(java)
|
|
164
|
+
})
|
|
134
165
|
})
|
|
135
166
|
|
|
136
167
|
function expoProject() {
|
|
@@ -282,6 +313,27 @@ describe('vxrn/expo-plugin', () => {
|
|
|
282
313
|
})
|
|
283
314
|
})
|
|
284
315
|
|
|
316
|
+
it('writes edgeToEdgeEnabled to gradle.properties', async () => {
|
|
317
|
+
const projectRoot = expoProject()
|
|
318
|
+
const config = withVxrn({
|
|
319
|
+
name: 'TestApp',
|
|
320
|
+
slug: 'test-app',
|
|
321
|
+
_internal: { projectRoot },
|
|
322
|
+
})
|
|
323
|
+
const properties = (
|
|
324
|
+
await config.mods.android.gradleProperties({
|
|
325
|
+
...config,
|
|
326
|
+
modRequest: { projectRoot, introspect: false, projectName: 'TestApp' },
|
|
327
|
+
modResults: [{ type: 'property', key: 'hermesEnabled', value: 'true' }],
|
|
328
|
+
})
|
|
329
|
+
).modResults
|
|
330
|
+
expect(properties).toContainEqual({
|
|
331
|
+
type: 'property',
|
|
332
|
+
key: 'edgeToEdgeEnabled',
|
|
333
|
+
value: 'true',
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
285
337
|
it('resolves Expo config plugins from the app and binds the dangerous mod', async () => {
|
|
286
338
|
const projectRoot = expoProject()
|
|
287
339
|
const iosRoot = join(projectRoot, 'ios')
|
|
@@ -820,6 +820,24 @@ extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autoli
|
|
|
820
820
|
)
|
|
821
821
|
})
|
|
822
822
|
|
|
823
|
+
it('writes edgeToEdgeEnabled to gradle.properties', () => {
|
|
824
|
+
const props = renderPrebuildFile({
|
|
825
|
+
relativePath: 'gradle.properties',
|
|
826
|
+
content: 'hermesEnabled=true',
|
|
827
|
+
platform: 'android',
|
|
828
|
+
app,
|
|
829
|
+
})
|
|
830
|
+
expect(props.content).toContain('edgeToEdgeEnabled=true')
|
|
831
|
+
|
|
832
|
+
const twice = renderPrebuildFile({
|
|
833
|
+
relativePath: 'gradle.properties',
|
|
834
|
+
content: props.content,
|
|
835
|
+
platform: 'android',
|
|
836
|
+
app,
|
|
837
|
+
})
|
|
838
|
+
expect(twice.content).toBe(props.content)
|
|
839
|
+
})
|
|
840
|
+
|
|
823
841
|
it('omits notification entries when notifications is unset', () => {
|
|
824
842
|
const bare = { ...app, notifications: undefined }
|
|
825
843
|
const infoPlist = renderPrebuildFile({
|
|
@@ -69,6 +69,7 @@ type NativeProjectPatches = {
|
|
|
69
69
|
replaceAppBuildGradleReactBlock(input: string): string
|
|
70
70
|
addDepsPatchToAppBuildGradle(input: string): string
|
|
71
71
|
addReactNativeScreensFix(input: string): string
|
|
72
|
+
enableEdgeToEdgeInMainActivity(mainActivity: string): string
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
const requireVxrn = module.createRequire(import.meta.url)
|
|
@@ -2511,6 +2512,17 @@ ${schemes.map((scheme) => `\t\t\t\t<string>${scheme}</string>`).join('\n')}
|
|
|
2511
2512
|
rendered = `${trailed}\n# One.UI.Map: set by native.app.android.googleMapsApiKey.\n${line}\n`
|
|
2512
2513
|
}
|
|
2513
2514
|
}
|
|
2515
|
+
if (platform === 'android' && relativePath === 'gradle.properties') {
|
|
2516
|
+
// MainActivity opts into androidx edge-to-edge; this tells react
|
|
2517
|
+
// native the window is edge-to-edge on every api level, so its
|
|
2518
|
+
// status bar paths skip the legacy decorFitsSystemWindows route that
|
|
2519
|
+
// would undo it on android 14 and older.
|
|
2520
|
+
const line = 'edgeToEdgeEnabled=true'
|
|
2521
|
+
if (!rendered.includes(line)) {
|
|
2522
|
+
const trailed = rendered.endsWith('\n') ? rendered : `${rendered}\n`
|
|
2523
|
+
rendered = `${trailed}\n# Edge-to-edge: set by one prebuild to match enableEdgeToEdge in MainActivity.\n${line}\n`
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2514
2526
|
if (
|
|
2515
2527
|
platform === 'android' &&
|
|
2516
2528
|
relativePath === 'app/src/main/res/values/styles.xml' &&
|
|
@@ -3250,11 +3262,16 @@ export function applyAndroidDependencyPatches(args: {
|
|
|
3250
3262
|
'MainActivity.kt'
|
|
3251
3263
|
)
|
|
3252
3264
|
const rendered = nativeProjectPatches.holdLaunchScreenInMainActivity(
|
|
3253
|
-
nativeProjectPatches.
|
|
3265
|
+
nativeProjectPatches.enableEdgeToEdgeInMainActivity(
|
|
3266
|
+
nativeProjectPatches.addReactNativeScreensFix(readFileSync(activityPath, 'utf8'))
|
|
3267
|
+
)
|
|
3254
3268
|
)
|
|
3255
3269
|
if (!rendered.includes('RNScreensFragmentFactory')) {
|
|
3256
3270
|
throw new Error('[vxrn] failed to apply the react-native-screens activity patch')
|
|
3257
3271
|
}
|
|
3272
|
+
if (!rendered.includes('enableEdgeToEdge')) {
|
|
3273
|
+
throw new Error('[vxrn] failed to apply the edge-to-edge activity patch')
|
|
3274
|
+
}
|
|
3258
3275
|
writeFileSync(activityPath, rendered)
|
|
3259
3276
|
}
|
|
3260
3277
|
|
|
@@ -132,7 +132,9 @@ fun helper(x: Int) = x`
|
|
|
132
132
|
])
|
|
133
133
|
const glue = renderKotlinSourceGlue('K_counter', contract).source
|
|
134
134
|
expect(glue).toContain('class OneNativeSourceViews_K_counter: OneNativeSourceViewDispatch')
|
|
135
|
-
expect(glue).toContain('
|
|
135
|
+
expect(glue).toContain('val reader = OneJsonReader_K_counter(propsJson)')
|
|
136
|
+
expect(glue).not.toContain('JSONObject(propsJson)')
|
|
137
|
+
expect(glue).toContain('onPick = if (h.hasOnPick) ({ a0 -> emit("onPick"')
|
|
136
138
|
expect(() =>
|
|
137
139
|
nativeSourceContract('/app/native/Bad.kt', '@Composable\nfun Bad(count: Int = 1) {}')
|
|
138
140
|
).toThrow(/Bad\.kt:2:.*may only default to null/)
|
|
@@ -141,6 +143,51 @@ fun helper(x: Int) = x`
|
|
|
141
143
|
).toThrow(/must return Unit/)
|
|
142
144
|
})
|
|
143
145
|
|
|
146
|
+
it('decodes keyed change documents into held view state', () => {
|
|
147
|
+
const contract = nativeSourceContract(
|
|
148
|
+
'/app/native/Counter.kt',
|
|
149
|
+
`package app.counter
|
|
150
|
+
data class Row(val id: Int, val title: String)
|
|
151
|
+
@Composable
|
|
152
|
+
fun Counter(rows: List<Row>, tick: Int, note: String? = null, onTap: () -> Unit, onPick: ((Int) -> Unit)? = null, modifier: Modifier = Modifier) {
|
|
153
|
+
Text("x")
|
|
154
|
+
}`
|
|
155
|
+
)
|
|
156
|
+
const glue = renderKotlinSourceGlue('K_counter', contract).source
|
|
157
|
+
// decode runs at prop commit, render reads the held props: a change whose
|
|
158
|
+
// base the host no longer holds refuses (false) instead of rendering stale
|
|
159
|
+
expect(glue).toContain('fun apply(view: String, propsJson: String, state: OneNativeSourceKeyedState): Boolean')
|
|
160
|
+
expect(glue).toContain('fun Content(view: String, state: OneNativeSourceKeyedState, emit: (String, String) -> Unit)')
|
|
161
|
+
expect(glue).toContain('private class HeldCounter_K_counter {')
|
|
162
|
+
expect(glue).toContain('var rows: List<app.counter.Row>? = null')
|
|
163
|
+
expect(glue).toContain('var tick: Int = 0')
|
|
164
|
+
expect(glue).toContain('var hasOnPick: Boolean = false')
|
|
165
|
+
expect(glue).toContain('reader.selectName("\\$seq", "\\$base", "\\$lists", "rows", "tick", "note", "onTap", "onPick")')
|
|
166
|
+
expect(glue).toContain('if (hasBase && !hasSeq) error("seq: required")')
|
|
167
|
+
expect(glue).toContain('if (sawLists && !hasBase) error("lists: needs a change document")')
|
|
168
|
+
expect(glue).toContain('state.views["Counter"]')
|
|
169
|
+
expect(glue).toContain('state.seq = seq')
|
|
170
|
+
expect(glue).toContain('if (seq == state.seq) return true')
|
|
171
|
+
expect(glue).toContain('return false')
|
|
172
|
+
expect(glue).toContain('state.revision++')
|
|
173
|
+
expect(glue).toContain('val revision = state.revision')
|
|
174
|
+
expect(glue).toContain('private fun mergeListsCounterRows(reader: OneJsonReader_K_counter, old: List<app.counter.Row>?, prop: String): List<app.counter.Row>? {')
|
|
175
|
+
expect(glue).toContain('val current = old ?: return null')
|
|
176
|
+
expect(glue).toContain('var rowsMergeFailed = false')
|
|
177
|
+
expect(glue).toContain('if (mergedRows == null) rowsMergeFailed = true else rows = mergedRows')
|
|
178
|
+
expect(glue).toContain('if (rowsMergeFailed) error("lists.rows: cannot merge")')
|
|
179
|
+
expect(glue).toContain('lists.$prop: needs length')
|
|
180
|
+
expect(glue).toContain('lists.$prop: set out of order')
|
|
181
|
+
expect(glue).not.toContain('no held list')
|
|
182
|
+
expect(glue).not.toContain('missing index')
|
|
183
|
+
expect(glue).toContain('val si = readInt(itemReader, itemPath); if (!itemReader.hasNext()) error("$itemPath: expected element")')
|
|
184
|
+
expect(glue).toContain('rows = h.rows!!')
|
|
185
|
+
// apply owns the one reader and closes the document it decoded; a second
|
|
186
|
+
// close throws on device (expected '}' at end of document).
|
|
187
|
+
expect(glue.match(/val reader = OneJsonReader_K_counter\(propsJson\)/g)).toHaveLength(1)
|
|
188
|
+
expect(glue.match(/reader\.endDocument\(\)/g)).toHaveLength(1)
|
|
189
|
+
})
|
|
190
|
+
|
|
144
191
|
it('omits the events import and date/data helpers a contract never uses', () => {
|
|
145
192
|
// kotlin/wasm cannot resolve the jvm/android/event-host symbols, so a
|
|
146
193
|
// resources-shaped contract must not reference them.
|
|
@@ -167,6 +214,7 @@ fun Resources(title: String, onStep: ((Int) -> Unit)? = null) {
|
|
|
167
214
|
expect(glue).not.toContain(symbol)
|
|
168
215
|
}
|
|
169
216
|
expect(glue).toContain('import androidx.compose.runtime.Composable')
|
|
217
|
+
expect(glue).toContain('import dev.onejs.one.OneNativeSourceKeyedState')
|
|
170
218
|
expect(glue).toContain('import dev.onejs.one.OneNativeSourceDispatch')
|
|
171
219
|
expect(glue).toContain('title = readString(')
|
|
172
220
|
const dated = nativeSourceContract(
|
|
@@ -213,8 +261,12 @@ struct Helper: View {
|
|
|
213
261
|
])
|
|
214
262
|
const glue = renderSwiftSourceGlue('rn_host_app', [contract]).source
|
|
215
263
|
expect(glue).toContain('@objc(OneNativeSourceViews_rn_host_app) @MainActor public final class OneNativeSourceViews_rn_host_app: NSObject, OneNativeSourceViewDispatch')
|
|
216
|
-
expect(glue).toContain('
|
|
217
|
-
expect(glue).toContain('
|
|
264
|
+
expect(glue).toContain('var `value`: Double')
|
|
265
|
+
expect(glue).toContain('private struct Doc_Level: Decodable')
|
|
266
|
+
expect(glue).toContain('guard let props = held as? Held_Level else')
|
|
267
|
+
expect(glue).toContain('public func updateView(_ held: AnyObject?, _ name: String, _ document: String) throws -> AnyObject?')
|
|
268
|
+
expect(glue).toContain('value: props.`value`,')
|
|
269
|
+
expect(glue).toContain('onReset: props.`onReset` == true ? { () -> Void in emit("onReset", args([])) } : nil')
|
|
218
270
|
|
|
219
271
|
const temp = mkdtempSync(join(tmpdir(), 'one-native-source-views-'))
|
|
220
272
|
writeFileSync(join(temp, 'Level.d.swift.ts'), contract.declaration)
|
|
@@ -222,10 +274,10 @@ struct Helper: View {
|
|
|
222
274
|
compilerOptions: { strict: true, noEmit: true, allowArbitraryExtensions: true, module: 'esnext', moduleResolution: 'bundler', types: [] },
|
|
223
275
|
include: ['use.ts'],
|
|
224
276
|
}))
|
|
225
|
-
writeFileSync(join(temp, 'react.d.ts'), "declare module 'react' { export interface ReactElement {} }")
|
|
277
|
+
writeFileSync(join(temp, 'react.d.ts'), "declare module 'react' { export interface ReactElement {} }\ndeclare module 'react-native' { export type ViewStyle = { flex?: number }; export type StyleProp<T> = T | null | undefined }")
|
|
226
278
|
writeFileSync(join(temp, 'use.ts'), `/// <reference path="./react.d.ts" />
|
|
227
279
|
import { Level } from './Level.swift'
|
|
228
|
-
Level({ value: 1, onChange: (next: number) => void next, onReset: null })
|
|
280
|
+
Level({ value: 1, onChange: (next: number) => void next, onReset: null, style: { flex: 1 } })
|
|
229
281
|
Level({ value: 'wrong', onChange: () => {} })
|
|
230
282
|
`)
|
|
231
283
|
// one compilation must accept the valid element and reject only the wrong prop.
|
|
@@ -511,7 +563,18 @@ object Atlas : OneModule {
|
|
|
511
563
|
'object Atlas : OneModule {\n fun at(moment: java.util.Date): java.util.Date = moment\n}'
|
|
512
564
|
)
|
|
513
565
|
expect(kotlin.modules[0].methods[0].parameters[0].type).toBe('Date')
|
|
514
|
-
expect(renderKotlinSourceGlue('K_atlas', kotlin).source).toContain('readDate(
|
|
566
|
+
expect(renderKotlinSourceGlue('K_atlas', kotlin).source).toContain('readDate(reader, "args[0]")')
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
it('eats commas between positional module args', () => {
|
|
570
|
+
const kotlin = nativeSourceContract(
|
|
571
|
+
'/app/native/Atlas.kt',
|
|
572
|
+
'object Atlas : OneModule {\n fun combine(first: Int, second: String): String = second\n}'
|
|
573
|
+
)
|
|
574
|
+
const glue = renderKotlinSourceGlue('K_atlas', kotlin).source
|
|
575
|
+
expect(glue).toContain('val arg0 = readInt(reader, "args[0]")')
|
|
576
|
+
expect(glue).toContain('require(reader.hasNext()) { "K_atlas.Atlas.combine expects 2 arguments" }')
|
|
577
|
+
expect(glue).toContain('val arg1 = readString(reader, "args[1]")')
|
|
515
578
|
})
|
|
516
579
|
|
|
517
580
|
it('rejects unusable named types at their use site', () => {
|
|
@@ -573,10 +636,12 @@ public struct Level: View {
|
|
|
573
636
|
|
|
574
637
|
const kotlin = nativeSourceContract('/app/native/Atlas.kt', kotlinSource)
|
|
575
638
|
const kotlinGlue = renderKotlinSourceGlue('K_atlas', kotlin).source
|
|
576
|
-
expect(kotlinGlue).toContain('private
|
|
639
|
+
expect(kotlinGlue).toContain('private class OneJsonReader_K_atlas(private val text: String) {')
|
|
640
|
+
expect(kotlinGlue).toContain('private fun readPoint(reader: OneJsonReader_K_atlas, path: String): app.atlas.Point {')
|
|
577
641
|
expect(kotlinGlue).toContain('private fun encodePoint(value: app.atlas.Point): JSONObject {')
|
|
578
|
-
expect(kotlinGlue).toContain('private fun readDate(
|
|
579
|
-
expect(kotlinGlue).toContain('readPoint(
|
|
642
|
+
expect(kotlinGlue).toContain('private fun readDate(reader: OneJsonReader_K_atlas, path: String): java.util.Date {')
|
|
643
|
+
expect(kotlinGlue).toContain('readPoint(reader, "args[0]")')
|
|
644
|
+
expect(kotlinGlue).not.toContain('JSONArray(argsJson)')
|
|
580
645
|
expect(kotlinGlue).toContain('JSONArray().put(dateToString(result))')
|
|
581
646
|
})
|
|
582
647
|
|