also looking at this
fix(mobile): preserve native editing, navigation, and durable media requests
#369
Merged
chris
wants to merge
codex/native-vertical-scroll-tab-swipes
into
main
36 files
+864
-185
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -11,3 +11,13 @@ export function nativeNavigationGroup(source: string, group: string): string[] { | ||
| 11 | 11 | if (!selected.length) throw new Error(`Empty native navigation UI group: ${group}`) |
| 12 | 12 | return selected.map(name => `NativeNavigationUITests/NativeNavigationUITests/${name}`) |
| 13 | 13 | } |
| 14 | + | |
| 15 | +/** An ignored XCTest selector must not silently reduce required coverage. */ | |
| 16 | +export function verifyNativeNavigationResults(counts: Record<string, unknown>, expectedTestCount?: number): number { | |
| 17 | + const count = counts.totalTestCount | |
| 18 | + if (counts.result !== 'Passed' || typeof count !== 'number' || !Number.isInteger(count) || count < 1 | |
| 19 | + || counts.passedTests !== count || counts.failedTests !== 0 || counts.skippedTests !== 0 | |
| 20 | + || (expectedTestCount !== undefined && count !== expectedTestCount)) | |
| 21 | + throw new Error(`Expected complete native navigation coverage (${expectedTestCount ?? 'nonempty'} tests): ${JSON.stringify(counts)}`) | |
| 22 | + return count | |
| 23 | +} | |
| @@ -2,7 +2,7 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from | ||
| 2 | 2 | import { dirname, join, resolve } from 'node:path' |
| 3 | 3 | import { bootSimulator, build, init, pickSimulator } from '../src/index' |
| 4 | 4 | import { addSchemeTestTargets, insertProjectTargetsBeforeSchemes } from './insert-project-targets' |
| 5 | -import { nativeNavigationGroup } from './native-navigation-groups' | |
| 5 | +import { nativeNavigationGroup, verifyNativeNavigationResults } from './native-navigation-groups' | |
| 6 | 6 | import { createTempDir, keepTempDir, removeTempDir } from '../../typescript/src/temp-dir' |
| 7 | 7 | |
| 8 | 8 | const fixture = join(import.meta.dir, '..', 'fixtures', 'native-navigation') |
| @@ -20,7 +20,7 @@ function run(args: string[], cwd: string): void { | ||
| 20 | 20 | throw new Error(`${args[0]} exited with ${result.exitCode}`) |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | -function runXcodeTests(args: string[], cwd: string, resultBundle: string): void { | |
| 23 | +function runXcodeTests(args: string[], cwd: string, resultBundle: string, expectedTestCount?: number): void { | |
| 24 | 24 | console.log(args.join(' ')) |
| 25 | 25 | const result = Bun.spawnSync(args, { cwd, stdout: 'inherit', stderr: 'inherit' }) |
| 26 | 26 | const evidence = process.env.CRAFT_NATIVE_NAVIGATION_RESULT |
| @@ -29,8 +29,7 @@ function runXcodeTests(args: string[], cwd: string, resultBundle: string): void | ||
| 29 | 29 | if (summary.exitCode !== 0) throw new Error(`Could not read XCTest results: ${summary.stderr.toString()}`) |
| 30 | 30 | const counts = JSON.parse(summary.stdout.toString()) |
| 31 | 31 | if (evidence) writeFileSync(`${resolve(evidence)}.summary.json`, summary.stdout) |
| 32 | - if (counts.result !== 'Passed' || !Number.isInteger(counts.totalTestCount) || counts.totalTestCount < 1 || counts.passedTests !== counts.totalTestCount || counts.failedTests !== 0 || counts.skippedTests !== 0) | |
| 33 | - throw new Error(`Expected passed native navigation tests with no empty selection or skips: ${JSON.stringify(counts)}`) | |
| 32 | + verifyNativeNavigationResults(counts, expectedTestCount) | |
| 34 | 33 | console.log(`XCTest verified ${counts.passedTests} passed tests, zero failures and skips`) |
| 35 | 34 | return |
| 36 | 35 | } |
| @@ -144,7 +143,8 @@ try { | ||
| 144 | 143 | const selectedTarget = process.env.CRAFT_ONLY_TESTING |
| 145 | 144 | const group = process.env.CRAFT_NATIVE_NAVIGATION_GROUP |
| 146 | 145 | if (group && (selectedTarget || selectedTest)) throw new Error('A required native UI group cannot be combined with focused selectors') |
| 147 | - const selection = group ? nativeNavigationGroup(readFileSync(join(fixture, 'NativeNavigationUITests.swift'), 'utf8'), group).map(target => `-only-testing:${target}`) : selectedTarget ? selectedTarget.split(',').map(target => target.trim()).filter(Boolean).map(target => `-only-testing:${target}`) : selectedTest | |
| 146 | + const groupCases = group ? nativeNavigationGroup(readFileSync(join(fixture, 'NativeNavigationUITests.swift'), 'utf8'), group) : undefined | |
| 147 | + const selection = groupCases ? groupCases.map(target => `-only-testing:${target}`) : selectedTarget ? selectedTarget.split(',').map(target => target.trim()).filter(Boolean).map(target => `-only-testing:${target}`) : selectedTest | |
| 148 | 148 | ? [`-only-testing:NativeNavigationUITests/NativeNavigationUITests/${selectedTest}`] |
| 149 | 149 | : [] |
| 150 | 150 | const xcodeArgs = [ |
| @@ -162,7 +162,7 @@ try { | ||
| 162 | 162 | '-resultBundlePath', resultBundle, |
| 163 | 163 | ...selection, |
| 164 | 164 | 'test-without-building', |
| 165 | - ], output, resultBundle) | |
| 165 | + ], output, resultBundle, groupCases?.length) | |
| 166 | 166 | console.log('Native navigation simulator tests passed') |
| 167 | 167 | } |
| 168 | 168 | } |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import { expect, test } from 'bun:test' |
| 2 | 2 | import { readFileSync } from 'node:fs' |
| 3 | 3 | import { join } from 'node:path' |
| 4 | -import { nativeNavigationGroup } from '../scripts/native-navigation-groups' | |
| 4 | +import { nativeNavigationGroup, verifyNativeNavigationResults } from '../scripts/native-navigation-groups' | |
| 5 | 5 | |
| 6 | 6 | const source = readFileSync(join(import.meta.dir, '../fixtures/native-navigation/NativeNavigationUITests.swift'), 'utf8') |
| 7 | 7 | |
| @@ -27,3 +27,17 @@ test('invalid, empty or ambiguous native UI coverage fails closed', () => { | ||
| 27 | 27 | expect(() => nativeNavigationGroup(source.replace('func testPushNativeBackSwipeReplaceAndRetainedHomeState', 'func noControlsJourney'), 'controls')).toThrow('coverage') |
| 28 | 28 | expect(() => nativeNavigationGroup(`${source}\nfunc testTabSwipeSwitchesNativeRootsAndReturns() {}`, 'core')).toThrow('coverage') |
| 29 | 29 | }) |
| 30 | + | |
| 31 | +test('XCTest must execute every selected case without failures or skips', () => { | |
| 32 | + const expected = nativeNavigationGroup(source, 'core').length | |
| 33 | + const passed = { result: 'Passed', totalTestCount: expected, passedTests: expected, failedTests: 0, skippedTests: 0 } | |
| 34 | + expect(verifyNativeNavigationResults(passed, expected)).toBe(expected) | |
| 35 | + expect(() => verifyNativeNavigationResults({ ...passed, totalTestCount: expected - 1, passedTests: expected - 1 }, expected)).toThrow('complete') | |
| 36 | + for (const incomplete of [ | |
| 37 | + { ...passed, result: 'Failed' }, | |
| 38 | + { ...passed, totalTestCount: 0, passedTests: 0 }, | |
| 39 | + { ...passed, passedTests: expected - 1 }, | |
| 40 | + { ...passed, failedTests: 1 }, | |
| 41 | + { ...passed, skippedTests: 1 }, | |
| 42 | + ]) expect(() => verifyNativeNavigationResults(incomplete, expected)).toThrow('complete') | |
| 43 | +}) | |