Prepare IDE beta 6 with Turkish native dialogs and map errors

This commit is contained in:
yilsem committed 2026-09-06 05:34:34 +03:00
1 parent 7724d65e92
commit 75afd01b4b
15 files changed
+265 -57

No files matched your search

@@ -1,8 +1,10 @@
import { describe, expect, it } from 'vitest'
import { EN_SURFACES } from '@/i18n/en-surfaces'
import { TR_SURFACES } from '@/i18n/tr-surfaces'
import type { StarmapGraph } from '@/types/hermes'
import { decodeShareCode, encodeShareCode, ShareCodeError } from './share-code'
import { decodeShareCode, encodeShareCode, ShareCodeError, shareCodeErrorText } from './share-code'
function sampleGraph(): StarmapGraph {
return {
@@ -154,6 +156,36 @@ describe('share-code', () => {
expect(() => decodeShareCode('')).toThrow(ShareCodeError)
})
it('formats real decoding failures in the selected language without changing valid codes', () => {
const graph = sampleGraph()
const valid = encodeShareCode(graph)
const capture = (code: string): unknown => {
try { decodeShareCode(code) } catch (error) { return error }
throw new Error('Expected a decoding failure')
}
const invalid = capture('not a real code !!!')
expect(shareCodeErrorText(invalid, TR_SURFACES.mapErrors)).toBe('Bu metin bir harita koduna benzemiyor.')
expect(shareCodeErrorText(invalid, EN_SURFACES.mapErrors)).toBe("That doesn't look like a map code.")
const raw = atob(valid.slice(3).replace(/-/g, '+').replace(/_/g, '/'))
const expected = raw.charCodeAt(0)
const actual = (expected + 1) % 256
const otherVersion = 'HML' + btoa(String.fromCharCode(actual) + raw.slice(1))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
expect(shareCodeErrorText(capture(otherVersion), TR_SURFACES.mapErrors))
.toBe(TR_SURFACES.mapErrors.version(actual, expected))
const middle = Math.floor(valid.length / 2)
const corrupted = valid.slice(0, middle) + (valid[middle] === 'A' ? 'B' : 'A') + valid.slice(middle + 1)
expect(shareCodeErrorText(capture(corrupted), TR_SURFACES.mapErrors)).toBe(TR_SURFACES.mapErrors.corrupted)
expect(shareCodeErrorText(new Error('internal details'), TR_SURFACES.mapErrors)).toBe('Harita kodu okunamadı.')
expect(encodeShareCode(graph)).toBe(valid)
expect(decodeShareCode(valid).nodes).toHaveLength(graph.nodes.length)
})
it('rejects a corrupted (bit-flipped) code', () => {
const code = encodeShareCode(sampleGraph())
// Flip a mid-payload char (trailing base64 bits can be dropped on decode).
@@ -1,3 +1,4 @@
import type { EN_SURFACES } from '@/i18n/en-surfaces'
import { type BitReader, type BitWriter, createLoadout, Dict, idxOf, indexBits, LoadoutError } from '@/lib/loadout'
import type { StarmapEdge, StarmapGraph, StarmapNode } from '@/types/hermes'
@@ -168,6 +169,13 @@ function readGraph(r: BitReader): StarmapGraph {
export class ShareCodeError extends LoadoutError {}
export function shareCodeErrorText(error: unknown, messages: typeof EN_SURFACES.mapErrors): string {
if (!(error instanceof ShareCodeError) || !error.info) {return messages.unknown}
const info = error.info
return info.kind === 'version' ? messages.version(info.actual, info.expected) : messages[info.kind]
}
const codec = createLoadout<StarmapGraph>({
error: ShareCodeError,
noun: 'map code',
@@ -1,6 +1,10 @@
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react'
import { afterEach, describe, expect, it } from 'vitest'
import { I18nProvider } from '@/i18n/context'
import { TR_SURFACES } from '@/i18n/tr-surfaces'
import { decodeShareCode, shareCodeErrorText } from './share-code'
import { ShareControls } from './share-controls'
describe('ShareControls', () => {
@@ -8,17 +12,35 @@ describe('ShareControls', () => {
cleanup()
})
it('opens its dialog when the trigger has a tooltip', async () => {
render(<ShareControls shareCode="map-code" />)
it.each([
['en', 'Import / export map'],
['tr', 'Haritayı içe / dışa aktar']
])('opens its dialog with a tooltip in %s', async (locale, title) => {
render(<I18nProvider configClient={null} initialLocale={locale}><ShareControls shareCode="map-code" /></I18nProvider>)
const trigger = screen.getByRole('button', { name: 'Import / export map' })
const trigger = screen.getByRole('button', { name: title })
fireEvent.pointerMove(trigger, { pointerType: 'mouse' })
expect((await screen.findByRole('tooltip')).textContent).toContain('Import / export map')
expect((await screen.findByRole('tooltip')).textContent).toContain(title)
fireEvent.click(trigger)
const dialog = await screen.findByRole('dialog')
expect(within(dialog).getByRole('heading', { name: 'Import / export map' })).toBeTruthy()
expect(within(dialog).getByRole('heading', { name: title })).toBeTruthy()
})
it('keeps an invalid pasted code editable and displays the Turkish decoder error', async () => {
render(<I18nProvider configClient={null} initialLocale="tr"><ShareControls onImport={code => {
try { decodeShareCode(code);
return null } catch (error) { return shareCodeErrorText(error, TR_SURFACES.mapErrors) }
}} /></I18nProvider>)
fireEvent.click(screen.getByRole('button', { name: 'Haritayı içe / dışa aktar' }))
const dialog = await screen.findByRole('dialog')
const input = within(dialog).getByRole('textbox')
fireEvent.change(input, { target: { value: 'not a valid code !!!' } })
fireEvent.click(within(dialog).getByRole('button', { name: 'Yükle', exact: true }))
expect(within(dialog).getByText('Bu metin bir harita koduna benzemiyor.')).toBeTruthy()
expect((input as HTMLTextAreaElement).value).toBe('not a valid code !!!')
})
})
+3 -3
View File
@@ -12,7 +12,7 @@ import { RING_OUTER, TILT, ZOOM_MAX, ZOOM_MIN } from './constants'
import { clamp, distToSegmentSq, fitScale, fitViewport, nodeRadius } from './geometry'
import { NodeContextMenu, type NodeMenuTarget } from './node-context-menu'
import { drawScene, drawScramble } from './render'
import { decodeShareCode, encodeShareCode, ShareCodeError } from './share-code'
import { decodeShareCode, encodeShareCode, shareCodeErrorText } from './share-code'
import { ShareControls } from './share-controls'
import { buildSimulation } from './simulation'
import { formatDate } from './text'
@@ -198,10 +198,10 @@ export function StarMap({
return null
} catch (err) {
return err instanceof ShareCodeError ? err.message : 'Could not read that map code.'
return shareCodeErrorText(err, t.surfaces.mapErrors)
}
},
[onImport]
[onImport, t.surfaces.mapErrors]
)
// Mark the canvas dirty and wake the (otherwise-idle) render loop.