also looking at this
fix(android): skip the whole launch intent on a relaunch from Recents
#240
2 files
+51
-9
| @@ -339,7 +339,7 @@ describe('Craft Android builder', () => { | ||
| 339 | 339 | const holder = readFileSync(join(output, 'app/src/main/java/com/craft/runtime/CraftNative.kt'), 'utf8') |
| 340 | 340 | |
| 341 | 341 | expect(activity).not.toContain('dispatch = false') |
| 342 | expect(activity).toContain('craftBridge.receiveDeepLink(launchLink)') | |
| 342 | expect(activity).toContain('intent.data?.toString()?.let(craftBridge::receiveDeepLink)') | |
| 343 | 343 | expect(activity).toContain('Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY') |
| 344 | 344 | expect(activity).toContain('intent.data?.toString()?.let(craftBridge::receiveDeepLink)') |
| 345 | 345 | // A recreated activity restores what the bridge saved, not getIntent(), |
| @@ -438,6 +438,41 @@ describe('Craft Android builder', () => { | ||
| 438 | 438 | expect(bridge).toContain('if (!window.__craftBridgeInstalled)') |
| 439 | 439 | }) |
| 440 | 440 | |
| 441 | it('ignores the launch intent on a relaunch from Recents, and the saved one on a recreate', async () => { | |
| 442 | // #229, two halves. The shortcut path had no history check, so the | |
| 443 | // shortcut the app was first opened with was dispatched again every time | |
| 444 | // the task was reopened from Recents. And a recreated activity must read | |
| 445 | // what the bridge saved, not getIntent(), which onNewIntent replaces with | |
| 446 | // the latest warm link — that half landed with #232 and is pinned here so | |
| 447 | // it cannot quietly go back. | |
| 448 | const output = mkdtempSync(join(tmpdir(), 'craft-android-recents-')) | |
| 449 | await init({ name: 'WildLoop', packageName: 'org.wildloop.app', output, config: { enableDeepLinks: true, urlSchemes: ['wildloop'] } }) | |
| 450 | ||
| 451 | const activity = readFileSync(join(output, 'app/src/main/java/org/wildloop/app/MainActivity.kt'), 'utf8') | |
| 452 | const onCreate = activity.slice(activity.indexOf('override fun onCreate'), activity.indexOf('private fun setupWebView')) | |
| 453 | ||
| 454 | // One flag, guarding both, rather than the link alone. | |
| 455 | expect(onCreate).toContain('Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY') | |
| 456 | expect(onCreate.match(/launchedFromHistory/g)?.length).toBe(2) | |
| 457 | // Bounded by the block's own closing brace: a slice that ran to the end | |
| 458 | // of onCreate would still find a shortcut call that had moved back out. | |
| 459 | const guardStart = onCreate.indexOf('if (!launchedFromHistory) {') | |
| 460 | const guard = onCreate.slice(guardStart, onCreate.indexOf('\n }', guardStart)) | |
| 461 | expect(guard).toContain('craftBridge::receiveDeepLink') | |
| 462 | expect(guard).toContain('handleIncomingShortcut(intent)') | |
| 463 | // And exactly once, so it cannot also be dispatched outside the guard. | |
| 464 | expect(onCreate.match(/handleIncomingShortcut\(intent\)/g)?.length).toBe(1) | |
| 465 | ||
| 466 | // A genuinely new intent still carries both through. | |
| 467 | const onNewIntent = activity.slice(activity.indexOf('override fun onNewIntent'), activity.indexOf('override fun onBackPressed')) | |
| 468 | expect(onNewIntent).toContain('craftBridge::receiveDeepLink') | |
| 469 | expect(onNewIntent).toContain('handleIncomingShortcut(intent)') | |
| 470 | ||
| 471 | // And a recreate reads the bundle, never the intent. | |
| 472 | expect(onCreate).toContain('craftBridge.restoreDeepLinks(savedInstanceState)') | |
| 473 | expect(activity).toContain('craftBridge.saveDeepLinks(outState)') | |
| 474 | }) | |
| 475 | ||
| 441 | 476 | it('routes external Activity results back to every pending media promise', async () => { |
| 442 | 477 | const output = mkdtempSync(join(tmpdir(), 'craft-android-activity-results-')) |
| 443 | 478 | await init({ name: 'WildLoop', packageName: 'org.wildloop.app', output }) |