Prepare IDE beta 6 with Turkish native dialogs and map errors
This commit is contained in:
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 !!!')
|
||||
})
|
||||
})
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user