mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
fix: GL maps read the stored copy again while offline, and a switch that is on keeps a visible knob in the dark scheme
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '../../../tests/helpers/render';
|
||||
import MToggle from './MToggle';
|
||||
|
||||
vi.mock('../../native/haptics', () => ({ selectionHaptic: vi.fn() }));
|
||||
|
||||
describe('MToggle', () => {
|
||||
it('FE-MOB-TOGGLE-001: on, the knob takes the track ink so it stays visible on the near-white dark track', () => {
|
||||
const onChange = vi.fn();
|
||||
const { rerender } = render(<MToggle checked onChange={onChange} ariaLabel="Offline" />);
|
||||
const knob = screen.getByRole('switch').firstElementChild as HTMLElement;
|
||||
expect(knob.className).toContain('bg-[color:var(--m-actfg)]');
|
||||
expect(knob.className).not.toContain('--m-knob');
|
||||
|
||||
rerender(<MToggle checked={false} onChange={onChange} ariaLabel="Offline" />);
|
||||
expect((screen.getByRole('switch').firstElementChild as HTMLElement).className).toContain(
|
||||
'bg-[color:var(--m-knob)]'
|
||||
);
|
||||
fireEvent.click(screen.getByRole('switch'));
|
||||
expect(onChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,11 @@ interface MToggleProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
/** iOS-style switch: --m-act track when on, --m-trackoff when off, 18px knob. */
|
||||
/**
|
||||
* iOS-style switch: --m-act track when on, --m-trackoff when off, 18px knob. The knob on
|
||||
* takes the track's ink (--m-actfg): in the dark scheme the track is near white, and a
|
||||
* white knob on it left the whole switch one white pill.
|
||||
*/
|
||||
export default function MToggle({ checked, onChange, ariaLabel, disabled = false, className = '' }: MToggleProps) {
|
||||
return (
|
||||
<button
|
||||
@@ -27,8 +31,10 @@ export default function MToggle({ checked, onChange, ariaLabel, disabled = false
|
||||
} ${className}`}
|
||||
>
|
||||
<span
|
||||
className={`block h-[18px] w-[18px] rounded-full bg-[color:var(--m-knob)] transition-transform duration-[180ms] ${
|
||||
checked ? 'translate-x-[18px] rtl:-translate-x-[18px]' : 'translate-x-0'
|
||||
className={`block h-[18px] w-[18px] rounded-full transition-transform duration-[180ms] ${
|
||||
checked
|
||||
? 'translate-x-[18px] bg-[color:var(--m-actfg)] rtl:-translate-x-[18px]'
|
||||
: 'translate-x-0 bg-[color:var(--m-knob)]'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { hasNativeFeature } from './capabilities';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { isEffectivelyOffline } from '../sync/networkMode';
|
||||
import { GL_MAPS_THROUGH_APP_SCHEMES } from './glSchemes';
|
||||
import { installOfflineProtocol, offlineStyle } from './offlineTiles';
|
||||
import { isNativeApp, isNativeIos } from './platform';
|
||||
import { glTileStoreActive, mapboxTileCacheRequest, tileCacheActive } from './tileCache';
|
||||
|
||||
vi.mock('./platform', () => ({ isNativeIos: vi.fn(() => true), isNativeApp: vi.fn(() => true) }));
|
||||
vi.mock('./capabilities', () => ({ hasNativeFeature: vi.fn(() => true) }));
|
||||
vi.mock('../sync/networkMode', () => ({ isEffectivelyOffline: vi.fn(() => false) }));
|
||||
|
||||
const STYLE = 'https://tiles.openfreemap.org/styles/positron';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('caches', { open: vi.fn() });
|
||||
vi.mocked(isEffectivelyOffline).mockReturnValue(false);
|
||||
});
|
||||
|
||||
describe('GL maps and the app schemes', () => {
|
||||
it('FE-NATIVE-GLSCHEME-001: in the iOS app the GL maps load straight from their servers, the raster store stays', () => {
|
||||
expect(vi.mocked(isNativeIos)()).toBe(true);
|
||||
expect(vi.mocked(isNativeApp)()).toBe(true);
|
||||
expect(vi.mocked(hasNativeFeature)('tileCache')).toBe(true);
|
||||
it('FE-NATIVE-GLSCHEME-001: online in the iOS app the GL maps load straight from their servers, the raster store stays', () => {
|
||||
expect(GL_MAPS_THROUGH_APP_SCHEMES).toBe(false);
|
||||
|
||||
const style = 'https://tiles.openfreemap.org/styles/positron';
|
||||
expect(offlineStyle(style)).toBe(style);
|
||||
const addProtocol = vi.fn();
|
||||
installOfflineProtocol({ addProtocol });
|
||||
expect(addProtocol).not.toHaveBeenCalled();
|
||||
|
||||
expect(offlineStyle(STYLE)).toBe(STYLE);
|
||||
const tile = 'https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/1/1/1.vector.pbf';
|
||||
expect(mapboxTileCacheRequest(tile, 'Tile')).toEqual({ url: tile });
|
||||
expect(glTileStoreActive()).toBe(false);
|
||||
expect(tileCacheActive()).toBe(true);
|
||||
});
|
||||
|
||||
it('FE-NATIVE-GLSCHEME-002: offline, or set to be, the MapLibre basemap reads the stored copy through the protocol', () => {
|
||||
const addProtocol = vi.fn();
|
||||
installOfflineProtocol({ addProtocol });
|
||||
expect(addProtocol).toHaveBeenCalledWith('trek-offline', expect.any(Function));
|
||||
|
||||
vi.mocked(isEffectivelyOffline).mockReturnValue(true);
|
||||
expect(offlineStyle(STYLE)).toBe('trek-offline://tiles.openfreemap.org/styles/positron');
|
||||
// Mapbox never goes through the store: offline it gives way to the stored MapLibre basemap.
|
||||
expect(glTileStoreActive()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isEffectivelyOffline } from '../sync/networkMode';
|
||||
|
||||
/**
|
||||
* Whether the GL maps (the Leaflet map's vector basemap and Mapbox GL) read
|
||||
* their style and tiles through the app's own URL schemes, trek-offline:// and
|
||||
@@ -7,8 +9,20 @@
|
||||
* Mapbox black) while the 3D map, the Mapbox preview in the settings and the
|
||||
* raster maps, which do not go through those schemes for a GL map, drew
|
||||
* normally. The same maps draw normally in a browser. Until that is traced on a
|
||||
* device, the GL maps load straight from their servers in the app, as they do
|
||||
* in a browser, and keep no offline copy; the raster maps (Topo, satellite, a
|
||||
* raster template of the user's) keep going through the tile store.
|
||||
* device, the GL maps load straight from their servers in the app while it is
|
||||
* online, as they do in a browser; offline they read the stored copy as before
|
||||
* (glMapsThroughAppSchemes). The raster maps (Topo, satellite, a raster
|
||||
* template of the user's) keep going through the tile store throughout.
|
||||
*/
|
||||
export const GL_MAPS_THROUGH_APP_SCHEMES = false;
|
||||
|
||||
/**
|
||||
* Whether a MapLibre basemap goes through trek-offline:// right now: always
|
||||
* while the switch above is on, and otherwise while the app is offline (or set
|
||||
* to be). Offline the stored copy is all there is to draw, so nothing is lost
|
||||
* by trying it; online the map loads straight from its server. Mapbox does not
|
||||
* follow: offline it gives way to the stored MapLibre basemap anyway.
|
||||
*/
|
||||
export function glMapsThroughAppSchemes(): boolean {
|
||||
return GL_MAPS_THROUGH_APP_SCHEMES || isEffectivelyOffline();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { isNativeIos } from './platform';
|
||||
|
||||
vi.mock('./platform', () => ({ isNativeIos: vi.fn() }));
|
||||
// The mechanism is what is tested here; whether the GL maps use it is glSchemes.ts's switch.
|
||||
vi.mock('./glSchemes', () => ({ GL_MAPS_THROUGH_APP_SCHEMES: true }));
|
||||
vi.mock('./glSchemes', () => ({ GL_MAPS_THROUGH_APP_SCHEMES: true, glMapsThroughAppSchemes: () => true }));
|
||||
|
||||
const cached = new Map<string, Response>();
|
||||
const cache = { match: vi.fn(async (url: string) => cached.get(url)?.clone()) };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AddProtocolAction, GetResourceResponse, RequestParameters } from 'maplibre-gl';
|
||||
import { VECTOR_CACHE } from '../sync/glPrefetcher';
|
||||
import { GL_MAPS_THROUGH_APP_SCHEMES } from './glSchemes';
|
||||
import { glMapsThroughAppSchemes } from './glSchemes';
|
||||
import { isNativeIos } from './platform';
|
||||
|
||||
/**
|
||||
@@ -20,8 +20,14 @@ import { isNativeIos } from './platform';
|
||||
const OFFLINE_SCHEME = 'trek-offline';
|
||||
const PREFIX = `${OFFLINE_SCHEME}://`;
|
||||
|
||||
/** Whether the iOS app can serve the protocol at all. */
|
||||
function offlineProtocolAvailable(): boolean {
|
||||
return isNativeIos() && typeof caches !== 'undefined';
|
||||
}
|
||||
|
||||
/** Whether a style goes through the protocol right now (see glSchemes.ts). */
|
||||
function offlineTilesActive(): boolean {
|
||||
return GL_MAPS_THROUGH_APP_SCHEMES && isNativeIos() && typeof caches !== 'undefined';
|
||||
return offlineProtocolAvailable() && glMapsThroughAppSchemes();
|
||||
}
|
||||
|
||||
export function toOfflineUrl(url: string): string {
|
||||
@@ -106,8 +112,8 @@ async function load(url: string, networkFirst: boolean, signal: AbortSignal): Pr
|
||||
async function openCache(): Promise<Cache | null> {
|
||||
try {
|
||||
return await caches.open(VECTOR_CACHE);
|
||||
} catch (err) {
|
||||
console.warn('[offline tiles] the vector cache is not available, reading from the network', err);
|
||||
} catch {
|
||||
// Refused by the web view: read from the network instead, which is all a cache miss does anyway.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -127,5 +133,5 @@ export const offlineProtocol: AddProtocolAction = async (
|
||||
export function installOfflineProtocol(maplibre: {
|
||||
addProtocol: (name: string, action: AddProtocolAction) => void;
|
||||
}): void {
|
||||
if (offlineTilesActive()) maplibre.addProtocol(OFFLINE_SCHEME, offlineProtocol);
|
||||
if (offlineProtocolAvailable()) maplibre.addProtocol(OFFLINE_SCHEME, offlineProtocol);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user