| @@ -3,6 +3,7 @@ import { tmpdir } from 'node:os' |
| 3 | 3 | import { gunzipSync } from 'node:zlib' |
| 4 | 4 | import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs' |
| 5 | 5 | import { join, resolve } from 'node:path' |
| 6 | +import { createContext, runInContext } from 'node:vm' |
| 6 | 7 | import { afterAll, describe, expect, it } from 'bun:test' |
| 7 | 8 | import { addSchemeBuildTargets, addSchemeTestTargets, insertProjectTargetsBeforeSchemes } from '../scripts/insert-project-targets' |
| 8 | 9 | import { |
| @@ -646,6 +647,57 @@ describe('Craft iOS builder', () => { |
| 646 | 647 | expect(source).toContain('guard width > 0, abs(width - reportedViewportWidth) >= 0.5 else { return }') |
| 647 | 648 | }) |
| 648 | 649 | |
| 650 | + it('preserves native shortcut requests through the full prelude and bundle namespace replacement', async () => { |
| 651 | + const source = readFileSync(join(import.meta.dir, '..', 'templates', 'CraftNativeScreen.swift'), 'utf8') |
| 652 | + |
| 653 | + |
| 654 | + function prelude(anchor: string): string { |
| 655 | + const marker = source.indexOf(anchor) |
| 656 | + expect(marker).toBeGreaterThan(-1) |
| 657 | + const opening = 'jsContext.evaluateScript("""' |
| 658 | + const start = source.lastIndexOf(opening, marker) + opening.length |
| 659 | + const end = source.indexOf('""")', marker) |
| 660 | + expect(end).toBeGreaterThan(start) |
| 661 | + return source.slice(start, end) |
| 662 | + } |
| 663 | + const requests: Array<{ module: string, method: string, args: unknown[] }> = [] |
| 664 | + const sandbox: { |
| 665 | + __stxNativeBridge: { capabilities: string[], postMessage: (raw: string) => void } |
| 666 | + __stxNativeCallback?: (message: unknown) => void |
| 667 | + } = { |
| 668 | + __stxNativeBridge: { |
| 669 | + capabilities: ['shortcuts'], |
| 670 | + postMessage(raw) { |
| 671 | + const message = JSON.parse(raw) |
| 672 | + requests.push({ module: message.payload.module, method: message.payload.method, args: message.payload.args }) |
| 673 | + sandbox.__stxNativeCallback?.({ type: 'API_RESPONSE', payload: { |
| 674 | + requestId: message.id, data: message.payload.method === 'set' ? { count: 1 } : true, |
| 675 | + } }) |
| 676 | + }, |
| 677 | + }, |
| 678 | + } |
| 679 | + const context = createContext(sandbox) |
| 680 | + runInContext(prelude('var bridge = globalThis.__stxNativeBridge;'), context) |
| 681 | + const nativeSetter = runInContext('Object.getOwnPropertyDescriptor(craft, "shortcuts").set', context) |
| 682 | + runInContext(prelude('function keep(name, extras)'), context) |
| 683 | + |
| 684 | + |
| 685 | + for (const reinstall of [false, true]) { |
| 686 | + if (reinstall) runInContext('__craftInstallNativeDialog()', context) |
| 687 | + for (const replacement of ['{extra:42}', '{extra:42,set:function(){return false},clear:function(){return false}}', 'null', 'undefined']) { |
| 688 | + runInContext(`craft.shortcuts = ${replacement}`, context) |
| 689 | + expect(await runInContext('craft.shortcuts.set([{type:"saved",title:"Saved"}])', context)).toEqual({ count: 1 }) |
| 690 | + expect(await runInContext('craft.shortcuts.clear()', context)).toBe(true) |
| 691 | + expect(runInContext('craft.shortcuts.extra', context)).toBe(42) |
| 692 | + } |
| 693 | + if (!reinstall) expect(runInContext('Object.getOwnPropertyDescriptor(craft, "shortcuts").set', context)).toBe(nativeSetter) |
| 694 | + } |
| 695 | + expect(requests).toEqual(Array.from({ length: 8 }, () => [ |
| 696 | + { module: 'Shortcuts', method: 'set', args: [[{ type: 'saved', title: 'Saved' }]] }, |
| 697 | + { module: 'Shortcuts', method: 'clear', args: [] }, |
| 698 | + ]).flat()) |
| 699 | + }) |
| 700 | + |
| 649 | 701 | it('advertises mounted-view focus and measurement in native screens', () => { |
| 650 | 702 | const source = readFileSync(join(import.meta.dir, '..', 'templates', 'CraftNativeScreen.swift'), 'utf8') |
| 651 | 703 | const actions = readFileSync(join(import.meta.dir, '..', 'templates', 'CraftNativeActions.swift'), 'utf8') |