| @@ -1,8 +1,8 @@ |
| 1 | | -import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs' |
| 1 | +import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs' |
| 2 | 2 | import { tmpdir } from 'node:os' |
| 3 | 3 | import { join } from 'node:path' |
| 4 | 4 | import { describe, expect, it } from 'bun:test' |
| 5 | | -import { build, init, installRuntime, renderAndroidDeepLinks, renderAndroidPermissions, resolveRuntimeDir, syncAndroidWebAssets } from './index' |
| 5 | +import { ANDROID_MIN_SDK, build, init, installRuntime, renderAndroidDeepLinks, renderAndroidPermissions, resolveRuntimeDir, syncAndroidWebAssets } from './index' |
| 6 | 6 | |
| 7 | 7 | function generatedFiles(path: string): string[] { |
| 8 | 8 | return readdirSync(path, { withFileTypes: true }).flatMap((entry) => { |
| @@ -12,6 +12,34 @@ function generatedFiles(path: string): string[] { |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | 14 | describe('Craft Android builder', () => { |
| 15 | + it('rejects Android API levels below the runtime floor before writing a project', async () => { |
| 16 | + const root = mkdtempSync(join(tmpdir(), 'craft-android-sdk-floor-')) |
| 17 | + try { |
| 18 | + for (const enableHealthConnect of [false, true]) { |
| 19 | + const output = join(root, String(enableHealthConnect)) |
| 20 | + await expect(init({ name: 'OldDevice', output, runtimeDir: null, config: { minSdk: 25, enableHealthConnect } })) |
| 21 | + .rejects.toThrow('Android minSdk must be at least 26') |
| 22 | + expect(existsSync(output)).toBe(false) |
| 23 | + } |
| 24 | + } |
| 25 | + finally { rmSync(root, { recursive: true, force: true }) } |
| 26 | + }) |
| 27 | + |
| 28 | + it('keeps the default and supported Android API levels aligned across generated metadata', async () => { |
| 29 | + const root = mkdtempSync(join(tmpdir(), 'craft-android-sdk-supported-')) |
| 30 | + try { |
| 31 | + for (const minSdk of [undefined, 26, 28]) { |
| 32 | + const output = join(root, String(minSdk)) |
| 33 | + await init({ name: 'SupportedDevice', output, runtimeDir: null, config: minSdk === undefined ? {} : { minSdk } }) |
| 34 | + const expected = minSdk ?? 26 |
| 35 | + expect(JSON.parse(readFileSync(join(output, 'craft.config.json'), 'utf8')).minSdk).toBe(expected) |
| 36 | + expect(JSON.parse(readFileSync(join(output, 'app/src/main/assets/craft.config.json'), 'utf8')).minSdk).toBe(expected) |
| 37 | + expect(readFileSync(join(output, 'app/build.gradle.kts'), 'utf8')).toContain(`minSdk = ${expected}`) |
| 38 | + } |
| 39 | + } |
| 40 | + finally { rmSync(root, { recursive: true, force: true }) } |
| 41 | + }) |
| 42 | + |
| 15 | 43 | it('routes every Kotlin template through the project generator', () => { |
| 16 | 44 | const templates = readdirSync(join(import.meta.dir, '../templates')) |
| 17 | 45 | .filter(name => name.endsWith('.kt.template')) |
| @@ -1298,7 +1326,7 @@ describe('Craft Android builder', () => { |
| 1298 | 1326 | name: 'WildLoop', |
| 1299 | 1327 | packageName: 'org.wildloop.app', |
| 1300 | 1328 | output, |
| 1301 | | - config: { compileSdk: 34, enableHealthConnect: true, minSdk: 24 }, |
| 1329 | + config: { compileSdk: 34, enableHealthConnect: true, minSdk: ANDROID_MIN_SDK }, |
| 1302 | 1330 | }) |
| 1303 | 1331 | |
| 1304 | 1332 | const bridge = readFileSync(join(output, 'app/src/main/java/org/wildloop/app/CraftBridge.kt'), 'utf8') |
| @@ -1489,12 +1517,6 @@ describe('the Zig library and the generated app agree on how old a device may be |
| 1489 | 1517 | |
| 1490 | 1518 | expect(declared).not.toBeNull() |
| 1491 | 1519 | |
| 1492 | | - |
| 1493 | | - |
| 1494 | | - const generator = readFileSync(join(import.meta.dir, 'index.ts'), 'utf8') |
| 1495 | | - const minSdk = generator.match(/minSdk:\s*(\d+)/) |
| 1496 | | - |
| 1497 | | - expect(minSdk).not.toBeNull() |
| 1498 | | - expect(Number(declared![1])).toBeLessThanOrEqual(Number(minSdk![1])) |
| 1520 | + expect(Number(declared![1])).toBeLessThanOrEqual(ANDROID_MIN_SDK) |
| 1499 | 1521 | }) |
| 1500 | 1522 | }) |