create-octane-xplat 0.11.1 → 0.12.0
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/package.json +1 -1
- package/scaffold.mjs +10 -2
- package/template/AGENTS.md +29 -0
- package/template/App_Resources/iOS/Info.plist +8 -0
- package/template/package.json +14 -9
- package/template/pnpm-workspace.yaml +19 -30
- package/template/references.d.ts +1 -0
- package/template/src/App.tsrx +4 -4
- package/template/src/app.spec.tsrx +132 -0
- package/template/src/test.ts +22 -0
- package/template/vitest.config.mts +30 -0
package/package.json
CHANGED
package/scaffold.mjs
CHANGED
|
@@ -34,7 +34,7 @@ export const targets = {
|
|
|
34
34
|
label: 'iOS (NativeScript)',
|
|
35
35
|
requires: ['native-shared'],
|
|
36
36
|
files: ['App_Resources/iOS'],
|
|
37
|
-
scripts: ['dev:ios', 'build:ios'],
|
|
37
|
+
scripts: ['dev:ios', 'test:ios', 'build:ios'],
|
|
38
38
|
devDeps: ['@nativescript/ios'],
|
|
39
39
|
note: 'needs macOS + Xcode — `pnpm xplat doctor` checks the toolchain',
|
|
40
40
|
},
|
|
@@ -42,7 +42,7 @@ export const targets = {
|
|
|
42
42
|
label: 'Android (NativeScript)',
|
|
43
43
|
requires: ['native-shared'],
|
|
44
44
|
files: ['App_Resources/Android'],
|
|
45
|
-
scripts: ['dev:android', 'build:android'],
|
|
45
|
+
scripts: ['dev:android', 'test:android', 'build:android'],
|
|
46
46
|
devDeps: ['@nativescript/android'],
|
|
47
47
|
note: 'needs Android SDK + a compatible JDK — `pnpm xplat doctor` checks the toolchain',
|
|
48
48
|
},
|
|
@@ -67,6 +67,12 @@ export const targets = {
|
|
|
67
67
|
'tsconfig.native.json',
|
|
68
68
|
'references.d.ts',
|
|
69
69
|
'src/main.ts',
|
|
70
|
+
// On-device Vitest lane — `ns test` builds src/test.ts under
|
|
71
|
+
// --env.unitTesting and runs every src/**/*.spec file on a
|
|
72
|
+
// simulator/emulator through the upstream runner.
|
|
73
|
+
'src/test.ts',
|
|
74
|
+
'src/app.spec.tsrx',
|
|
75
|
+
'vitest.config.mts',
|
|
70
76
|
],
|
|
71
77
|
deps: [
|
|
72
78
|
'@nativescript-community/gesturehandler',
|
|
@@ -80,8 +86,10 @@ export const targets = {
|
|
|
80
86
|
devDeps: [
|
|
81
87
|
'@nativescript-community/vite-octane',
|
|
82
88
|
'@nativescript/types',
|
|
89
|
+
'@nativescript/unit-test-runner',
|
|
83
90
|
'@nativescript/vite',
|
|
84
91
|
'nativescript',
|
|
92
|
+
'vitest',
|
|
85
93
|
],
|
|
86
94
|
typecheck: 'tsrx-tsc --noEmit -p tsconfig.native.json',
|
|
87
95
|
},
|
package/template/AGENTS.md
CHANGED
|
@@ -7,3 +7,32 @@ Octane Xplat app — one TypeScript codebase for web + iOS + Android (Octane
|
|
|
7
7
|
Before writing code, read `.agents/skills/xplat/SKILL.md` (or invoke the
|
|
8
8
|
`xplat` skill). Its invariants are enforced by `pnpm lint` — violations
|
|
9
9
|
usually still compile but silently break on one platform.
|
|
10
|
+
|
|
11
|
+
## Stable automation targets
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button } from '@octane-xplat/ui'
|
|
15
|
+
|
|
16
|
+
export function AddItem() {
|
|
17
|
+
return <Button testID="packing.add-item" label="Add item" accessibilityLabel="Add item" />
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Assign optional semantic `testID` names to meaningful controls and asserted
|
|
22
|
+
state as you build: `packing.add-item`, `profile.name`,
|
|
23
|
+
`packing.item.${item.key}.open`. Keep names unique among mounted targets,
|
|
24
|
+
stable across localization, and based on durable repeated-item keys rather
|
|
25
|
+
than array positions. Preserve `id` values, useful accessibility labels and
|
|
26
|
+
roles. Do not put IDs on every decorative child or expose personal data.
|
|
27
|
+
Inputs target their editable host; overlays target mounted content. Verify
|
|
28
|
+
forwarding when composing another component; inherited types alone do not
|
|
29
|
+
promise platform support. See [qualified hosts and mappings](https://octane-xplat.goddardai.org/verify/test-identifiers).
|
|
30
|
+
|
|
31
|
+
Argent is optional AI-agent device/browser control through MCP, with no app
|
|
32
|
+
runtime dependency. Use [official setup](https://docs.swmansion.com/argent/docs/fundamentals/installation/)
|
|
33
|
+
and the [qualified local recipe](https://octane-xplat.goddardai.org/verify/argent)
|
|
34
|
+
(version 0.27.0). Prefer stable IDs for reliable targeting while keeping
|
|
35
|
+
spoken accessibility semantics. Discovery and replay have different native
|
|
36
|
+
prerequisites. Chromium prefers DOM `id` over `data-testid` when both exist.
|
|
37
|
+
The recipe states the actual iOS/Android/Chromium scope and limits; do not
|
|
38
|
+
infer AppKit, physical-device, or arbitrary typing support.
|
|
@@ -48,5 +48,13 @@
|
|
|
48
48
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
49
49
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
50
50
|
</array>
|
|
51
|
+
<key>NSAppTransportSecurity</key>
|
|
52
|
+
<dict>
|
|
53
|
+
<!-- Loopback WebSocket for `ns test` (@nativescript/unit-test-runner):
|
|
54
|
+
the on-device coordinator reaches the host Vitest session at
|
|
55
|
+
ws://127.0.0.1:<port>. Scoped to local networking only. -->
|
|
56
|
+
<key>NSAllowsLocalNetworking</key>
|
|
57
|
+
<true/>
|
|
58
|
+
</dict>
|
|
51
59
|
</dict>
|
|
52
60
|
</plist>
|
package/template/package.json
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"dev": "vite",
|
|
9
9
|
"dev:ios": "ns run ios",
|
|
10
10
|
"dev:android": "ns run android",
|
|
11
|
+
"test:ios": "ns test ios",
|
|
12
|
+
"test:android": "ns test android",
|
|
11
13
|
"build": "vite build",
|
|
12
14
|
"build:ios": "ns build ios",
|
|
13
15
|
"build:android": "ns build android",
|
|
@@ -19,28 +21,31 @@
|
|
|
19
21
|
"@nativescript-community/gesturehandler": "2.0.45",
|
|
20
22
|
"@nativescript-community/octane": "0.2.4",
|
|
21
23
|
"@nativescript-community/ui-drawer": "0.1.32",
|
|
22
|
-
"@nativescript/core": "9.1.
|
|
23
|
-
"@octane-xplat/effects": "^0.
|
|
24
|
-
"@octane-xplat/image": "^0.
|
|
25
|
-
"@octane-xplat/platform": "^0.
|
|
26
|
-
"@octane-xplat/ui": "^0.
|
|
24
|
+
"@nativescript/core": "9.1.3",
|
|
25
|
+
"@octane-xplat/effects": "^0.12.0",
|
|
26
|
+
"@octane-xplat/image": "^0.12.0",
|
|
27
|
+
"@octane-xplat/platform": "^0.12.0",
|
|
28
|
+
"@octane-xplat/ui": "^0.12.0",
|
|
27
29
|
"@valor/nativescript-websockets": "2.0.3",
|
|
28
|
-
"octane": "0.
|
|
30
|
+
"octane": "0.10.2"
|
|
29
31
|
},
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"@nativescript-community/vite-octane": "0.2.1",
|
|
32
34
|
"@nativescript/android": "9.1.1",
|
|
33
35
|
"@nativescript/ios": "9.1.0",
|
|
34
36
|
"@nativescript/types": "9.1.4",
|
|
37
|
+
"@nativescript/unit-test-runner": "5.0.0",
|
|
35
38
|
"@nativescript/vite": "8.0.17",
|
|
36
|
-
"@octane-xplat/cli": "^0.
|
|
37
|
-
"@octane-xplat/lint": "^0.
|
|
39
|
+
"@octane-xplat/cli": "^0.12.0",
|
|
40
|
+
"@octane-xplat/lint": "^0.12.0",
|
|
38
41
|
"@octanejs/vite-plugin": "0.1.61",
|
|
42
|
+
"@tsrx/oxc": "0.16.0",
|
|
39
43
|
"@tsrx/typescript-plugin": "0.4.11",
|
|
40
44
|
"nativescript": "9.1.1",
|
|
41
45
|
"oxlint": "^1.74.0",
|
|
42
46
|
"typescript": "5.9.3",
|
|
43
|
-
"vite": "8.3.0"
|
|
47
|
+
"vite": "8.3.0",
|
|
48
|
+
"vitest": "4.1.11"
|
|
44
49
|
},
|
|
45
50
|
"packageManager": "pnpm@11.24.0"
|
|
46
51
|
}
|
|
@@ -21,35 +21,24 @@ allowUnusedPatches: true
|
|
|
21
21
|
# applied via pnpm patchedDependencies. Managed upstream by @octane-xplat/cli (`xplat
|
|
22
22
|
# patches apply` refreshes, `xplat patches check` verifies); do not edit by hand.
|
|
23
23
|
patchedDependencies:
|
|
24
|
-
# Published
|
|
25
|
-
# ownership,
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
# #11444): TabView onItemsChanged keeps retained items attached instead of
|
|
43
|
-
# unloading/re-adding them (the churn that left TabViewItem-hosted Frames isLoaded=false
|
|
44
|
-
# on iOS and detached from their tab fragment on Android), iOS items writes load the
|
|
45
|
-
# selected item's view once controllers exist, and Frame._pushInFrameStack only promotes
|
|
46
|
-
# frames whose ancestors present them (Frame.topmost stops returning hidden preloaded tab
|
|
47
|
-
# frames). Also maps CSS font-weight to the OpenType wght axis for custom Android fonts on
|
|
48
|
-
# API 26+, preserves explicit variation settings, and falls back to the prior file load
|
|
49
|
-
# when the axis is unsupported. Carries NativeScript PR #11481: preserves percentage
|
|
50
|
-
# translate values through parsing and resolves each axis against the view's own
|
|
51
|
-
# dimensions, including native views and animations.
|
|
52
|
-
"@nativescript/core@9.1.2": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__core@9.1.2.patch
|
|
24
|
+
# Published 0.10.2 ships async/generator non-component helper compilation and Descriptor
|
|
25
|
+
# lexical/ancestor ownership, so the helper hunks are removed and the old four-hunk
|
|
26
|
+
# instance ownership fix becomes one declaring-owner reader fallback per distribution.
|
|
27
|
+
# Retains renderer-owned Children.toArray/map with explicit universal-native re-exports
|
|
28
|
+
# for older runtimes, universal signal-read tracking and local hooks, root event-error
|
|
29
|
+
# forwarding, and the complete universal imperative-ref lifecycle. Connects universal
|
|
30
|
+
# signal-owner identities to the upstream enclosingOwner chain so child reads reuse
|
|
31
|
+
# instance declarations. Verified against the registry 0.10.2 tarball and the patched
|
|
32
|
+
# 0.6.3 output.
|
|
33
|
+
octane@0.10.2: node_modules/.pnpm-config/@octane-xplat/patches/patches/octane@0.10.2.patch
|
|
34
|
+
# Published 9.1.3 ships the #11446 nested-tab-frame lifecycle fix and iOS explicit-size
|
|
35
|
+
# flex alignment, so those hunks are removed. Retains the unshipped parent-padding
|
|
36
|
+
# measure-spec fix and Android explicit-size flex alignment, iOS continuous corner
|
|
37
|
+
# clipping, CSS @layer cascade support, custom Android variable-font weight mapping, and
|
|
38
|
+
# percentage translate parsing, native view, and animation support. Verified against the
|
|
39
|
+
# registry 9.1.3 tarball; it has no Windows implementation, so the separate Windows
|
|
40
|
+
# preview patch remains.
|
|
41
|
+
"@nativescript/core@9.1.3": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript__core@9.1.3.patch
|
|
53
42
|
# @nativescript/vite serializes bundled CSS and dev app.css through reworkcss `css`@3.0.0,
|
|
54
43
|
# which mis-parses @layer into garbage selectors — rules die before reaching
|
|
55
44
|
# _populateRules. Adds atlayer() covering block and statement forms. Applies to a
|
|
@@ -118,4 +107,4 @@ patchedDependencies:
|
|
|
118
107
|
# bytes to disk.
|
|
119
108
|
"@nativescript-community/ui-image@5.0.17": node_modules/.pnpm-config/@octane-xplat/patches/patches/@nativescript-community__ui-image@5.0.17.patch
|
|
120
109
|
configDependencies:
|
|
121
|
-
"@octane-xplat/patches": 0.
|
|
110
|
+
"@octane-xplat/patches": 0.12.0
|
package/template/references.d.ts
CHANGED
package/template/src/App.tsrx
CHANGED
|
@@ -28,16 +28,16 @@ export function App() @{
|
|
|
28
28
|
<Text className="muted">one codebase — web · iOS · android</Text>
|
|
29
29
|
|
|
30
30
|
<HStack className="counter">
|
|
31
|
-
<Pressable className="btn" onPress={() => setCount(count - 1)}><Text>−</Text></Pressable>
|
|
32
|
-
<Text className="count">{count}</Text>
|
|
33
|
-
<Pressable className="btn" onPress={() => setCount(count + 1)}><Text>+</Text></Pressable>
|
|
31
|
+
<Pressable testID="counter.decrement" accessibilityLabel="Decrease count" className="btn" onPress={() => setCount(count - 1)}><Text>−</Text></Pressable>
|
|
32
|
+
<Text testID="counter.value" className="count">{count}</Text>
|
|
33
|
+
<Pressable testID="counter.increment" accessibilityLabel="Increase count" className="btn" onPress={() => setCount(count + 1)}><Text>+</Text></Pressable>
|
|
34
34
|
</HStack>
|
|
35
35
|
|
|
36
36
|
<ScrollableArea>
|
|
37
37
|
{FEATURES.map((f) => <Text className="chip">{f}</Text>)}
|
|
38
38
|
</ScrollableArea>
|
|
39
39
|
|
|
40
|
-
<Pressable className="link" onPress={() => {
|
|
40
|
+
<Pressable testID="settings.toggle-theme" className="link" onPress={() => {
|
|
41
41
|
const next = !dark;
|
|
42
42
|
storage.setString('theme-preference', next ? 'dark' : 'light');
|
|
43
43
|
setOverride(next);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Starter spec — `pnpm test:ios` runs this inside the real iOS runtime
|
|
2
|
+
// (same renderer, native views, and services your app uses).
|
|
3
|
+
//
|
|
4
|
+
// mountXplat() renders a component onto the test host page and unmounts it
|
|
5
|
+
// when the test finishes; tap()/enterText() dispatch through the same
|
|
6
|
+
// gesture observers as real touches — that proves your wiring, not OS
|
|
7
|
+
// hit-testing (device journeys like that belong to Maestro).
|
|
8
|
+
import { describe, expect, it } from 'vitest'
|
|
9
|
+
import { useEffect, useState } from 'octane'
|
|
10
|
+
import { Pressable, Text, TextInput, View } from '@octane-xplat/ui'
|
|
11
|
+
import {
|
|
12
|
+
type TextInputView,
|
|
13
|
+
enterText,
|
|
14
|
+
findByTestId,
|
|
15
|
+
mountXplat,
|
|
16
|
+
nextRenderPass,
|
|
17
|
+
tap,
|
|
18
|
+
viewText,
|
|
19
|
+
waitUntil,
|
|
20
|
+
} from '@octane-xplat/platform/testing'
|
|
21
|
+
|
|
22
|
+
import { App } from './App.tsrx'
|
|
23
|
+
|
|
24
|
+
// Controlled input — typing flows up through onChange and back down as the
|
|
25
|
+
// rendered value, and parent writes reach the native field.
|
|
26
|
+
function NameForm() @{
|
|
27
|
+
const [name, setName] = useState('')
|
|
28
|
+
|
|
29
|
+
<View>
|
|
30
|
+
<TextInput
|
|
31
|
+
testID="greeting.name"
|
|
32
|
+
value={name}
|
|
33
|
+
onChange={setName}
|
|
34
|
+
placeholder="Your name"
|
|
35
|
+
/>
|
|
36
|
+
<Pressable testID="greeting.reset" onPress={() => setName('Bob')}>
|
|
37
|
+
<Text>set bob</Text>
|
|
38
|
+
</Pressable>
|
|
39
|
+
<Text>{'greeting: ' + (name || 'stranger')}</Text>
|
|
40
|
+
</View>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Conditional mount — a subtree exists only while its state says so.
|
|
44
|
+
function Details() @{
|
|
45
|
+
const [open, setOpen] = useState(false)
|
|
46
|
+
|
|
47
|
+
<View>
|
|
48
|
+
<Pressable testID="details.toggle" onPress={() => setOpen(!open)}>
|
|
49
|
+
<Text>toggle</Text>
|
|
50
|
+
</Pressable>
|
|
51
|
+
@if (open) {
|
|
52
|
+
<Text>detail: open</Text>
|
|
53
|
+
}
|
|
54
|
+
</View>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Async states — loading first, then a settled value or an error. The
|
|
58
|
+
// cleanup flag keeps a late timer from writing into an unmounted tree.
|
|
59
|
+
function FetchLabel(props: { fail?: boolean }) @{
|
|
60
|
+
const [state, setState] = useState('loading')
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
let live = true
|
|
63
|
+
const timer = setTimeout(() => {
|
|
64
|
+
if (live) setState(props.fail ? 'error' : 'ready')
|
|
65
|
+
}, 30)
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
live = false
|
|
69
|
+
clearTimeout(timer)
|
|
70
|
+
}
|
|
71
|
+
}, [])
|
|
72
|
+
|
|
73
|
+
<Text>{'fetch: ' + state}</Text>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('starter app on device', () => {
|
|
77
|
+
it('mounts, reacts to a tap, and unmounts cleanly', async () => {
|
|
78
|
+
const { view, host, unmount } = await mountXplat(App)
|
|
79
|
+
|
|
80
|
+
const increment = findByTestId(view, 'counter.increment')
|
|
81
|
+
const count = findByTestId<TextInputView>(view, 'counter.value')
|
|
82
|
+
expect(increment?.isLoaded, 'counter.increment resolves to a loaded native view').toBe(true)
|
|
83
|
+
|
|
84
|
+
await tap(increment!)
|
|
85
|
+
await waitUntil(() => count?.text === '1', {
|
|
86
|
+
message: 'tap did not reach the increment Pressable',
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await unmount()
|
|
90
|
+
await nextRenderPass()
|
|
91
|
+
expect(host.content).toBe(null)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('round-trips a controlled TextInput', async () => {
|
|
95
|
+
const { view } = await mountXplat(NameForm)
|
|
96
|
+
const field = findByTestId<TextInputView>(view, 'greeting.name')
|
|
97
|
+
expect(field?.isLoaded).toBe(true)
|
|
98
|
+
|
|
99
|
+
await enterText(field!, 'Ada')
|
|
100
|
+
await waitUntil(() => viewText(view).includes('greeting: Ada'), {
|
|
101
|
+
message: 'onChange did not deliver the typed text',
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
await tap(findByTestId(view, 'greeting.reset')!)
|
|
105
|
+
await waitUntil(() => field?.text === 'Bob', {
|
|
106
|
+
message: 'parent write did not reach the native field',
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('mounts and unmounts a conditional subtree', async () => {
|
|
111
|
+
const { view } = await mountXplat(Details)
|
|
112
|
+
expect(viewText(view).includes('detail: open')).toBe(false)
|
|
113
|
+
|
|
114
|
+
await tap(findByTestId(view, 'details.toggle')!)
|
|
115
|
+
await waitUntil(() => viewText(view).includes('detail: open'))
|
|
116
|
+
|
|
117
|
+
await tap(findByTestId(view, 'details.toggle')!)
|
|
118
|
+
await waitUntil(() => !viewText(view).includes('detail: open'))
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('settles async loading and error states', async () => {
|
|
122
|
+
const ready = await mountXplat(FetchLabel)
|
|
123
|
+
await waitUntil(() => viewText(ready.view).includes('fetch: ready'), {
|
|
124
|
+
message: 'loading state never settled',
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const failed = await mountXplat(FetchLabel, { fail: true })
|
|
128
|
+
await waitUntil(() => viewText(failed.view).includes('fetch: error'), {
|
|
129
|
+
message: 'error state never surfaced',
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Test bundle entry — `ns test` builds with --env.unitTesting, and the
|
|
2
|
+
// xplatNative preset swaps the app entry (src/main.ts) for this file, so the
|
|
3
|
+
// device boots the Vitest coordinator instead of the app.
|
|
4
|
+
import '@valor/nativescript-websockets'
|
|
5
|
+
import { Application } from '@nativescript/core'
|
|
6
|
+
import {
|
|
7
|
+
NativeScriptVitestCoordinator,
|
|
8
|
+
createNativeScriptTestRegistry,
|
|
9
|
+
} from '@nativescript/unit-test-runner/runtime'
|
|
10
|
+
|
|
11
|
+
import { createVitestHostPage } from '@nativescript/unit-test-runner/testing'
|
|
12
|
+
|
|
13
|
+
const coordinator = new NativeScriptVitestCoordinator({
|
|
14
|
+
// Every spec matched by vitest.config.mts `include` must also match this
|
|
15
|
+
// glob — vite statically bundles each as a lazily imported chunk that the
|
|
16
|
+
// registry loads on demand.
|
|
17
|
+
registry: createNativeScriptTestRegistry(import.meta.glob('./**/*.spec.{ts,tsx,tsrx}')),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
void coordinator.start()
|
|
21
|
+
// The host page keeps the screen free as a mountXplat() surface for UI specs.
|
|
22
|
+
Application.run({ create: () => createVitestHostPage(coordinator) })
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import { nativeScript } from '@nativescript/unit-test-runner'
|
|
3
|
+
|
|
4
|
+
// `pnpm test:ios` / `pnpm test:android` (ns test) drive this config: the
|
|
5
|
+
// plugin launches `ns run <platform> --no-hmr --env.unitTesting` and
|
|
6
|
+
// orchestrates spec execution on-device over a loopback WebSocket — Vitest
|
|
7
|
+
// stays the orchestrator on the host.
|
|
8
|
+
//
|
|
9
|
+
// On hosts without Simulator.app (Xcode 27+), `ns run` can only target an
|
|
10
|
+
// already-booted simulator: `xcrun simctl boot <udid>` first, then pass
|
|
11
|
+
// `--device <udid>` (or NS_DEVICE).
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
plugins: [
|
|
14
|
+
nativeScript({
|
|
15
|
+
platform: process.env.NS_PLATFORM || 'ios', // 'android' | 'ios' | 'visionos'
|
|
16
|
+
device: process.env.NS_DEVICE || undefined,
|
|
17
|
+
// Keep this in sync with the import.meta.glob in src/test.ts —
|
|
18
|
+
// the device can only run files that made it into the bundle.
|
|
19
|
+
include: ['src/**/*.spec.{ts,tsx,tsrx}'],
|
|
20
|
+
// The connect clock starts when the host session opens and covers
|
|
21
|
+
// `ns run`'s native build + install + boot — a cold build easily
|
|
22
|
+
// exceeds the 120s default.
|
|
23
|
+
connectTimeout: 600_000,
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
test: {
|
|
27
|
+
// Device runs include app startup and real layout passes.
|
|
28
|
+
testTimeout: 30_000,
|
|
29
|
+
},
|
|
30
|
+
})
|