|
1 | | -import { describe, expect, it } from 'vitest'; |
| 1 | +import { describe, expect, it, vi } from 'vitest'; |
2 | 2 |
|
3 | 3 | import { buildNsRtBridgeModule, discoverNsvBridgeExports } from './ns-rt-bridge.js'; |
4 | 4 |
|
@@ -110,6 +110,86 @@ describe('/ns/rt bridge builder', () => { |
110 | 110 | expect(code).not.toContain('with.dot'); |
111 | 111 | }); |
112 | 112 |
|
| 113 | + // Regression: root-mount navigation raced the strategy's dynamic import. |
| 114 | + describe('$navigateTo waits for the client strategy before declaring the navigator missing', () => { |
| 115 | + // Evaluates the served text, not a re-implementation. |
| 116 | + function loadNavigateTo(g: Record<string, any>) { |
| 117 | + const code = buildNsRtBridgeModule({ rtVer: '0', requireGuardSnippet: '', vendorExports: [] }); |
| 118 | + const pick = (re: RegExp) => { |
| 119 | + const m = re.exec(code); |
| 120 | + if (!m) throw new Error(`bridge text lost: ${re}`); |
| 121 | + return m[0]; |
| 122 | + }; |
| 123 | + const navigateTo = pick(/^export const \$navigateTo = .*$/m).replace(/^export const /, 'const '); |
| 124 | + const helpers = pick(/^function __navigateNow\(a\).*$/m) + '\n' + pick(/^function __navigatorMissing\(\).*$/m); |
| 125 | + const factory = new Function('g', `${helpers}\nconst __ns_core_bridge = null; const __cached_vm = {}; const __ensure = () => ({});\n${navigateTo}\nreturn $navigateTo;`); |
| 126 | + return factory(g) as (...a: any[]) => any; |
| 127 | + } |
| 128 | + const quiet = () => { |
| 129 | + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); |
| 130 | + return () => spy.mockRestore(); |
| 131 | + }; |
| 132 | + |
| 133 | + it('calls the navigator synchronously when it is already installed', () => { |
| 134 | + const calls: any[] = []; |
| 135 | + const g = { Frame: {}, __nsNavigateUsingApp: (...a: any[]) => (calls.push(a), 'page') }; |
| 136 | + expect(loadNavigateTo(g)({ name: 'Home' }, { props: { a: 1 } })).toBe('page'); |
| 137 | + expect(calls).toEqual([[{ name: 'Home' }, { props: { a: 1 } }]]); |
| 138 | + }); |
| 139 | + |
| 140 | + it('waits for __NS_CLIENT_STRATEGY_READY__ and then navigates when the strategy installs the navigator late', async () => { |
| 141 | + const calls: any[] = []; |
| 142 | + const g: Record<string, any> = { Frame: {} }; |
| 143 | + let installed!: () => void; |
| 144 | + g.__NS_CLIENT_STRATEGY_READY__ = new Promise<void>((resolve) => { |
| 145 | + installed = () => { |
| 146 | + g.__nsNavigateUsingApp = (...a: any[]) => (calls.push(a), 'page'); |
| 147 | + resolve(); |
| 148 | + }; |
| 149 | + }); |
| 150 | + const pending = loadNavigateTo(g)({ name: 'Home' }); |
| 151 | + expect(typeof pending.then).toBe('function'); |
| 152 | + expect(calls).toEqual([]); |
| 153 | + installed(); |
| 154 | + await expect(pending).resolves.toBe('page'); |
| 155 | + expect(calls).toEqual([[{ name: 'Home' }]]); |
| 156 | + }); |
| 157 | + |
| 158 | + it('rejects only after the strategy has settled without installing a navigator', async () => { |
| 159 | + const restore = quiet(); |
| 160 | + try { |
| 161 | + const g = { Frame: {}, __NS_CLIENT_STRATEGY_READY__: Promise.resolve() }; |
| 162 | + await expect(loadNavigateTo(g)({ name: 'Home' })).rejects.toThrow('app navigator missing'); |
| 163 | + } finally { |
| 164 | + restore(); |
| 165 | + } |
| 166 | + }); |
| 167 | + |
| 168 | + it('still throws synchronously when there is no readiness promise to wait for', () => { |
| 169 | + const restore = quiet(); |
| 170 | + try { |
| 171 | + expect(() => loadNavigateTo({ Frame: {} })({ name: 'Home' })).toThrow('app navigator missing'); |
| 172 | + } finally { |
| 173 | + restore(); |
| 174 | + } |
| 175 | + }); |
| 176 | + |
| 177 | + it('surfaces navigator errors unchanged', () => { |
| 178 | + const restore = quiet(); |
| 179 | + try { |
| 180 | + const g = { |
| 181 | + Frame: {}, |
| 182 | + __nsNavigateUsingApp: () => { |
| 183 | + throw new Error('boom'); |
| 184 | + }, |
| 185 | + }; |
| 186 | + expect(() => loadNavigateTo(g)({ name: 'Home' })).toThrow('boom'); |
| 187 | + } finally { |
| 188 | + restore(); |
| 189 | + } |
| 190 | + }); |
| 191 | + }); |
| 192 | + |
113 | 193 | it('discoverNsvBridgeExports returns an empty set when nativescript-vue is not resolvable from the project root', () => { |
114 | 194 | // No baseline fallback: discovery is the single source of truth. Pointing |
115 | 195 | // at an empty directory simulates a misconfigured project, which the |
|
0 commit comments