|
1 | | -import { generatePmtilesUrls } from './GtfsVisualizationMap.functions'; |
| 1 | +import { |
| 2 | + generatePmtilesUrls, |
| 3 | + generateStopColorExpression, |
| 4 | + generateRouteOutlineColorExpression, |
| 5 | +} from './GtfsVisualizationMap.functions'; |
| 6 | + |
| 7 | +const LIGHT_BG = '#f6f6f6'; |
| 8 | +const DARK_BG = '#0d0d0d'; |
| 9 | +const LIGHT_ALT = '#444444'; |
| 10 | +const DARK_ALT = '#ffffff'; |
| 11 | + |
| 12 | +describe('generateStopColorExpression', () => { |
| 13 | + it('returns fallback directly when routeIdToColor is empty', () => { |
| 14 | + expect(generateStopColorExpression({}, LIGHT_BG, LIGHT_ALT, '#888')).toBe( |
| 15 | + '#888', |
| 16 | + ); |
| 17 | + }); |
| 18 | + |
| 19 | + it('skips entries with null or invalid hex colors and returns fallback', () => { |
| 20 | + const result = generateStopColorExpression( |
| 21 | + { r1: null as unknown as string, r2: 'gggggg', r3: '' }, |
| 22 | + LIGHT_BG, |
| 23 | + LIGHT_ALT, |
| 24 | + ); |
| 25 | + expect(result).toBe('#888'); |
| 26 | + }); |
| 27 | + |
| 28 | + it('expands 3-digit hex to 6-digit before comparison', () => { |
| 29 | + // #000 → #000000 (black) — high contrast against light bg → route color wins |
| 30 | + const result = generateStopColorExpression( |
| 31 | + { r1: '000' }, |
| 32 | + LIGHT_BG, |
| 33 | + LIGHT_ALT, |
| 34 | + ) as unknown[]; |
| 35 | + expect(result[0]).toBe('case'); |
| 36 | + expect(result[2]).toBe('#000000'); |
| 37 | + }); |
| 38 | + |
| 39 | + it('uses route color when crBg >= crAlt (black route on light bg)', () => { |
| 40 | + // crBg(black vs #f6f6f6) ≈ 20.3 >> crAlt(black vs #444444) ≈ 13.4 → route color wins |
| 41 | + const result = generateStopColorExpression( |
| 42 | + { route1: '000000' }, |
| 43 | + LIGHT_BG, |
| 44 | + LIGHT_ALT, |
| 45 | + ) as unknown[]; |
| 46 | + expect(result[0]).toBe('case'); |
| 47 | + expect(result[2]).toBe('#000000'); |
| 48 | + }); |
| 49 | + |
| 50 | + it('uses alt color when crAlt > crBg (white route on light bg)', () => { |
| 51 | + // crBg(white vs #f6f6f6) ≈ 1.03 << crAlt(white vs #444444) ≈ 3.31 → alt wins |
| 52 | + const result = generateStopColorExpression( |
| 53 | + { route1: 'ffffff' }, |
| 54 | + LIGHT_BG, |
| 55 | + LIGHT_ALT, |
| 56 | + ) as unknown[]; |
| 57 | + expect(result[0]).toBe('case'); |
| 58 | + expect(result[2]).toBe(LIGHT_ALT); |
| 59 | + }); |
| 60 | + |
| 61 | + it('uses alt color for dark route on dark map background', () => { |
| 62 | + // crBg(black vs #0d0d0d) ≈ 2.02 << crAlt(black vs #ffffff) ≈ 21 → alt wins |
| 63 | + const result = generateStopColorExpression( |
| 64 | + { route1: '000000' }, |
| 65 | + DARK_BG, |
| 66 | + DARK_ALT, |
| 67 | + ) as unknown[]; |
| 68 | + expect(result[2]).toBe(DARK_ALT); |
| 69 | + }); |
| 70 | + |
| 71 | + it('picks correct color independently per route', () => { |
| 72 | + // r1=black → route color, r2=white → alt |
| 73 | + const result = generateStopColorExpression( |
| 74 | + { r1: '000000', r2: 'ffffff' }, |
| 75 | + LIGHT_BG, |
| 76 | + LIGHT_ALT, |
| 77 | + ) as unknown[]; |
| 78 | + expect(result[0]).toBe('case'); |
| 79 | + expect(result[2]).toBe('#000000'); // r1: route color |
| 80 | + expect(result[4]).toBe(LIGHT_ALT); // r2: alt color |
| 81 | + expect(result[5]).toBe('#888'); // fallback |
| 82 | + }); |
| 83 | + |
| 84 | + it('includes the fallback as the last element of the case expression', () => { |
| 85 | + const result = generateStopColorExpression( |
| 86 | + { r1: '000000' }, |
| 87 | + LIGHT_BG, |
| 88 | + LIGHT_ALT, |
| 89 | + '#cccccc', |
| 90 | + ) as unknown[]; |
| 91 | + expect(result[result.length - 1]).toBe('#cccccc'); |
| 92 | + }); |
| 93 | +}); |
| 94 | + |
| 95 | +describe('generateRouteOutlineColorExpression', () => { |
| 96 | + // Helper to safely index into nested unknown arrays |
| 97 | + type NestedArr = Array<unknown | NestedArr>; |
| 98 | + const at = (arr: NestedArr, ...indices: number[]): NestedArr => |
| 99 | + indices.reduce<NestedArr>((a, i) => (a as NestedArr[])[i], arr); |
| 100 | + it('returns an array expression starting with "let" and "rgba"', () => { |
| 101 | + const result = generateRouteOutlineColorExpression(LIGHT_BG, LIGHT_ALT); |
| 102 | + expect(Array.isArray(result)).toBe(true); |
| 103 | + expect(result[0]).toBe('let'); |
| 104 | + expect(result[1]).toBe('rgba'); |
| 105 | + }); |
| 106 | + |
| 107 | + it('embeds mapBgColor and altOutlineColor as the case outputs', () => { |
| 108 | + const result = generateRouteOutlineColorExpression(LIGHT_BG, LIGHT_ALT); |
| 109 | + // Navigate to the innermost case: result[3][3][3][3] |
| 110 | + const caseExpr = at(result as NestedArr, 3, 3, 3, 3); |
| 111 | + expect(caseExpr[0]).toBe('case'); |
| 112 | + expect(caseExpr[2]).toBe(LIGHT_BG); // chosen when crBg >= crAlt |
| 113 | + expect(caseExpr[3]).toBe(LIGHT_ALT); // chosen when crAlt > crBg |
| 114 | + }); |
| 115 | + |
| 116 | + it('embeds precomputed bgLum for the given mapBgColor in the crBg expression', () => { |
| 117 | + const result = generateRouteOutlineColorExpression('#f6f6f6', LIGHT_ALT); |
| 118 | + // bgLum for #f6f6f6: (246/255) ≈ 0.9647 (Rec. 709 coefficients sum to 1 for grey) |
| 119 | + const expectedBgLum = (0.2126 * 246 + 0.7152 * 246 + 0.0722 * 246) / 255; |
| 120 | + // structure: result[3]=['let','lum',...,innerCrBg], result[3][3]=['let','crBg',crBgExpr,...] |
| 121 | + // crBgExpr = ['/', ['+', ['max', ['var','lum'], bgLum], 0.05], ...] |
| 122 | + // bgLum appears at crBgExpr[1][1][2] |
| 123 | + const crBgExpr = at(result as NestedArr, 3, 3, 2); |
| 124 | + const bgLumInExpr = at(crBgExpr, 1, 1)[2] as number; |
| 125 | + expect(bgLumInExpr).toBeCloseTo(expectedBgLum, 10); |
| 126 | + }); |
| 127 | + |
| 128 | + it('works for dark mode colors without error', () => { |
| 129 | + expect(() => |
| 130 | + generateRouteOutlineColorExpression(DARK_BG, DARK_ALT), |
| 131 | + ).not.toThrow(); |
| 132 | + }); |
| 133 | + |
| 134 | + it('uses different precomputed luminance values for light vs dark bg', () => { |
| 135 | + const lightResult = generateRouteOutlineColorExpression( |
| 136 | + LIGHT_BG, |
| 137 | + LIGHT_ALT, |
| 138 | + ); |
| 139 | + const darkResult = generateRouteOutlineColorExpression(DARK_BG, DARK_ALT); |
| 140 | + const crBgLight = at(lightResult as NestedArr, 3, 3, 2); |
| 141 | + const crBgDark = at(darkResult as NestedArr, 3, 3, 2); |
| 142 | + const bgLumLight = at(crBgLight, 1, 1)[2] as number; |
| 143 | + const bgLumDark = at(crBgDark, 1, 1)[2] as number; |
| 144 | + expect(bgLumLight).toBeGreaterThan(bgLumDark); |
| 145 | + }); |
| 146 | +}); |
2 | 147 |
|
3 | 148 | describe('generatePmtilesUrls', () => { |
4 | 149 | const latestDataset = { |
|
0 commit comments