doxa-maps/internal/check-approx-pin-note.mjs
Doxa Maps 1ac32fe3d0 People-group pins say what they are: one shared note, and the groups that share a point are named
A people-group pin is a representative point, not an address, and some pins ARE the same point: on the
Doxa roster 2,107 groups sit on 2,048 coordinates, 108 of them on a point shared with another (gospel-
plugin's measurement). Every map with people-group pins must say so, in one wording.

- library/components/legends/ApproxPinNote.vue: "The dot marks where this people is found, not a town or a
  boundary." always; when the selected pin's point is shared, "It sits on the same point as <names> — <n>
  groups share this coordinate…". Never prints the raw coordinate. Wording: gospel-plugin's P5 proposal,
  in a new pins catalogue (11 locales).
- library/utils/colocation.js: the co-location index, built from the roster THE MAP draws (rows or
  GeoJSON features), never a hardcoded count.
- LegendDesktop and LegendMobile (research map, simple map, country posters, cities, UPG-100) show the
  note under the legend, and under the detail card with the shared names.
- check:approx-pin-note (headless, gates:post): the index, the two sentences, no coordinate, Arabic.
2026-09-26 13:04:15 +00:00

58 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
/**
* check-approx-pin-note — people-group pins say what they are, in one shared wording (APPROXPIN). Headless.
*
* ASSERTS (1) library/utils/colocation.js: groups on the same coordinate find each other (pairs, triples),
* a lone point shares with nobody, rows without a position are ignored, and both roster rows and GeoJSON
* features are read; (2) ApproxPinNote renders the first sentence always, the named second sentence only
* for a shared point, never a raw coordinate, and in Arabic from the Arabic catalogue.
* EXIT 0 pass · 1 fail
*/
import { readFileSync } from 'node:fs'
import { join, resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { buildColocationIndex, colocatedWith } from '../library/utils/colocation.js'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
let bad = 0
const check = (name, ok, got) => { if (!ok) bad++; console.log(` ${ok ? 'ok ' : 'FAIL'} ${name}${ok ? '' : ' — ' + JSON.stringify(got)}`) }
const rows = [
{ name: 'Godda', latitude: 12.843781, longitude: 75.247906 },
{ name: 'Koraga', latitude: '12.843781', longitude: '75.247906' },
{ type: 'Feature', properties: { name: 'Maila' }, geometry: { type: 'Point', coordinates: [75.247906, 12.843781] } },
{ name: 'Alone', latitude: 10, longitude: 10 },
{ name: 'NoPos', latitude: '', longitude: '' },
]
const idx = buildColocationIndex(rows)
check('three groups on one point find each other', colocatedWith(idx, rows[0]).sort().join() === 'Koraga,Maila', colocatedWith(idx, rows[0]))
check('a GeoJSON feature is matched to rows on the same point', colocatedWith(idx, rows[2]).sort().join() === 'Godda,Koraga', colocatedWith(idx, rows[2]))
check('a lone point shares with nobody', colocatedWith(idx, rows[3]).length === 0, colocatedWith(idx, rows[3]))
check('a row without a position is ignored', colocatedWith(idx, rows[4]).length === 0, colocatedWith(idx, rows[4]))
const { createServer } = await import('vite')
const { default: vue } = await import('@vitejs/plugin-vue')
const server = await createServer({ root: ROOT, configFile: false, logLevel: 'error', appType: 'custom',
server: { middlewareMode: true, hmr: false, ws: false, watch: null }, plugins: [vue()], resolve: { alias: { '@map': join(ROOT, 'library') } } })
try {
globalThis.__INTLIFY_JIT_COMPILATION__ = true
const Note = (await server.ssrLoadModule('/library/components/legends/ApproxPinNote.vue')).default
const { createSSRApp, h } = await import('vue')
const { createI18n } = await import('vue-i18n')
const { renderToString } = await import('vue/server-renderer')
const render = async (lc, props) => {
const pins = JSON.parse(readFileSync(join(ROOT, 'library/i18n/locales', lc, 'pins.json'), 'utf8'))
const app = createSSRApp({ render: () => h(Note, props) })
app.use(createI18n({ legacy: false, locale: lc, messages: { [lc]: { pins } } }))
return (await renderToString(app)).replace(/<[^>]+>/g, '').replace(/<!--.*?-->/g, '').trim()
}
const one = await render('en', {})
check('first sentence always', /not a town or a boundary/.test(one) && !/same point/.test(one), one)
const two = await render('en', { shared: ['Koraga', 'Maila'] })
check('shared point names the others and counts all', /Koraga, Maila/.test(two) && /3 groups share/.test(two), two)
check('never prints a raw coordinate', !/\d+\.\d{3,}/.test(one + two), one + two)
const ar = await render('ar', { shared: ['Koraga'] })
check('Arabic renders from the Arabic catalogue', /[؀-ۿ]/.test(ar) && /Koraga/.test(ar), ar)
} finally { await server.close() }
if (bad) { console.log(`APPROX-PIN-NOTE: FAIL — ${bad} case(s).`); process.exit(1) }
console.log('APPROX-PIN-NOTE: PASS — pins say they are localities, and name the groups that share a point.')