also looking at this
feat(maps): add key-free native Android maps and shared map controls
#363
28 files
+1857
-481
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.
| @@ -27,6 +27,14 @@ const keepProjects = process.argv.includes('--keep') | ||
| 27 | 27 | const workspace = createTempDir('craft-android-templates-') |
| 28 | 28 | |
| 29 | 29 | const fixtures: CompileFixture[] = [ |
| 30 | { | |
| 31 | // Compile the key-free engine in both Debug and minified Release, with | |
| 32 | // real MapLibre and location APIs resolved from the published SDK. | |
| 33 | directoryName: 'native-maplibre', | |
| 34 | name: 'CraftNativeMapLibre', | |
| 35 | packageName: 'dev.craft.fixture.maplibre', | |
| 36 | config: { renderer: 'native', mapProvider: 'maplibre', enableGeolocation: true }, | |
| 37 | }, | |
| 30 | 38 | { |
| 31 | 39 | directoryName: 'native', |
| 32 | 40 | name: 'CraftNativeTemplate', |
| @@ -0,0 +1,60 @@ | ||
| 1 | import { afterAll, describe, expect, it } from 'bun:test' | |
| 2 | import { readFileSync } from 'node:fs' | |
| 3 | import { join } from 'node:path' | |
| 4 | import { init } from './index' | |
| 5 | import { createTempDir, removeAllTempDirs } from '../../typescript/src/temp-dir' | |
| 6 | ||
| 7 | afterAll(removeAllTempDirs) | |
| 8 | ||
| 9 | describe('native Android map provider generation', () => { | |
| 10 | it('builds a MapLibre host without a Google map SDK dependency or key', async () => { | |
| 11 | const output = createTempDir('craft-native-maplibre-') | |
| 12 | await init({ name: 'Native maps', output, runtimeDir: null, config: { renderer: 'native', mapProvider: 'maplibre', enableGeolocation: true } }) | |
| 13 | const source = readFileSync(join(output, 'app/src/main/java/com/craft/native/MainActivity.kt'), 'utf8') | |
| 14 | const gradle = readFileSync(join(output, 'app/build.gradle.kts'), 'utf8') | |
| 15 | const manifest = readFileSync(join(output, 'app/src/main/AndroidManifest.xml'), 'utf8') | |
| 16 | expect(source).toContain('MapLibre.getInstance(context)') | |
| 17 | expect(source).toContain('MapView(context)') | |
| 18 | expect(source).toContain('GeoJsonSource("craft-map-overlays")') | |
| 19 | expect(source).toContain('iconRotationAlignment("map")') | |
| 20 | expect(source).toContain('LocationComponentActivationOptions.builder(context, style)') | |
| 21 | expect(source).not.toContain('com.google.android.gms.maps') | |
| 22 | expect(source).not.toContain('GoogleMap') | |
| 23 | expect(source).not.toContain('{{NATIVE_MAP_') | |
| 24 | expect(gradle).toContain('org.maplibre.gl:android-sdk:13.7.1') | |
| 25 | expect(gradle).not.toContain('play-services-maps') | |
| 26 | expect(manifest).not.toContain('com.google.android.geo.API_KEY') | |
| 27 | expect(JSON.parse(readFileSync(join(output, 'craft.config.json'), 'utf8')).mapProvider).toBe('maplibre') | |
| 28 | // Native STX uses the Android JavaScript engine and real views, not the | |
| 29 | // ts-maps DOM adapter mounted as a substitute for the native map. | |
| 30 | expect(source).toContain('JavaScriptSandbox.isSupported()') | |
| 31 | expect(source).not.toContain('import android.webkit.WebView') | |
| 32 | }) | |
| 33 | ||
| 34 | it('keeps the existing Google host when explicitly selected', async () => { | |
| 35 | const output = createTempDir('craft-native-google-map-') | |
| 36 | await init({ name: 'Google maps', output, runtimeDir: null, config: { renderer: 'native', mapProvider: 'google', mapsApiKey: 'restricted-fixture-key' } }) | |
| 37 | const source = readFileSync(join(output, 'app/src/main/java/com/craft/native/MainActivity.kt'), 'utf8') | |
| 38 | const gradle = readFileSync(join(output, 'app/build.gradle.kts'), 'utf8') | |
| 39 | const manifest = readFileSync(join(output, 'app/src/main/AndroidManifest.xml'), 'utf8') | |
| 40 | expect(source).toContain('com.google.android.gms.maps.GoogleMap') | |
| 41 | expect(source).not.toContain('org.maplibre.android') | |
| 42 | expect(gradle).toContain('com.google.android.gms:play-services-maps:18.2.0') | |
| 43 | expect(gradle).not.toContain('org.maplibre.gl:android-sdk') | |
| 44 | expect(manifest).toContain('android:name="com.google.android.geo.API_KEY" android:value="restricted-fixture-key"') | |
| 45 | }) | |
| 46 | ||
| 47 | it('does not emit an unused Google map key for a MapLibre app', async () => { | |
| 48 | const output = createTempDir('craft-maplibre-manifest-') | |
| 49 | await init({ name: 'MapLibre', output, runtimeDir: null, config: { renderer: 'native', mapProvider: 'maplibre', mapsApiKey: 'unused-fixture-key' } }) | |
| 50 | const manifest = readFileSync(join(output, 'app/src/main/AndroidManifest.xml'), 'utf8') | |
| 51 | expect(manifest).not.toContain('com.google.android.geo.API_KEY') | |
| 52 | expect(manifest).not.toContain('unused-fixture-key') | |
| 53 | }) | |
| 54 | ||
| 55 | it('rejects an unsupported native map provider before generating a project', async () => { | |
| 56 | const output = createTempDir('craft-unknown-map-provider-') | |
| 57 | // Exercise untyped JSON input, which still needs runtime validation. | |
| 58 | await expect(init({ name: 'Unknown maps', output, runtimeDir: null, config: { renderer: 'native', mapProvider: 'unknown' as never } })).rejects.toThrow('Unknown Android native map provider') | |
| 59 | }) | |
| 60 | }) | |