mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 00:03:15 +00:00
test(mobile): scope i18n coverage to what this PR lands
Supersedes the UNVERIFIED note on the first commit: the suites have now run. Three embedded-webview-copy cases asserted that the rich-editor and terminal WebView documents emit localized, escaped copy. Those documents are not localized here, so the cases belonged to string extraction, not to this PR. Replaced with direct coverage of escapeEmbeddedHtmlCopy, including the ampersand-first ordering that keeps entities from double-decoding. Also relabels the catalog-registration test. It cannot distinguish lazy from eager registration while only en.json exists — registering every supported locale yields an identical result — so it now says what it actually pins: a supported locale without a catalog is never registered.
This commit is contained in:
@@ -1,79 +1,34 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
buildMobileRichMarkdownEditorHtml,
|
||||
escapeInjectedJavaScriptString
|
||||
} from '../components/mobile-rich-markdown-editor-html'
|
||||
import { buildTerminalWebViewHtml } from '../terminal/terminal-webview-html'
|
||||
import { escapeEmbeddedHtmlCopy } from './embedded-webview-copy'
|
||||
import { mobileI18n } from './mobile-i18n'
|
||||
|
||||
const INITIAL_LOCALE = mobileI18n.language
|
||||
|
||||
afterEach(async () => {
|
||||
await mobileI18n.changeLanguage(INITIAL_LOCALE)
|
||||
})
|
||||
|
||||
describe('embedded WebView copy', () => {
|
||||
it('escapes translated HTML text and attributes', () => {
|
||||
// The WebView-injection call sites are not localized yet, so this covers the
|
||||
// primitive on its own. Extraction adds the cases that assert translated copy
|
||||
// reaches the rich-editor and terminal documents already escaped.
|
||||
describe('escapeEmbeddedHtmlCopy', () => {
|
||||
it('escapes text and attribute delimiters', () => {
|
||||
expect(escapeEmbeddedHtmlCopy('<Copy "all" & more>')).toBe(
|
||||
'<Copy "all" & more>'
|
||||
)
|
||||
})
|
||||
|
||||
it('injects localized rich-editor and terminal controls', async () => {
|
||||
await mobileI18n.changeLanguage('es')
|
||||
|
||||
const editorHtml = buildMobileRichMarkdownEditorHtml()
|
||||
expect(editorHtml).toContain('<html lang="es">')
|
||||
expect(editorHtml).toContain('data-placeholder="Empieza a escribir..."')
|
||||
expect(editorHtml).toContain('window.prompt("URL del enlace")')
|
||||
expect(editorHtml).toContain('window.prompt("URL de la imagen")')
|
||||
expect(editorHtml).toContain(`+ "Tarea" +`)
|
||||
|
||||
const terminalHtml = buildTerminalWebViewHtml()
|
||||
expect(terminalHtml).toContain('<html lang="es">')
|
||||
expect(terminalHtml).toContain('id="sel-menu-copy">Copiar</button>')
|
||||
expect(terminalHtml).toContain('id="sel-menu-all">Seleccionar todo</button>')
|
||||
it('neutralizes markup that would otherwise parse as an element', () => {
|
||||
expect(escapeEmbeddedHtmlCopy('<img src=x onerror=alert(1)>')).toBe(
|
||||
'<img src=x onerror=alert(1)>'
|
||||
)
|
||||
expect(escapeEmbeddedHtmlCopy('<b>Task & more</b>')).toBe('<b>Task & more</b>')
|
||||
})
|
||||
|
||||
it('escapes script-state delimiters in translated JavaScript strings', () => {
|
||||
expect(escapeInjectedJavaScriptString('<!--<script>')).toBe('"\\u003c!--\\u003cscript>"')
|
||||
it('escapes ampersands first so entities are not double-decoded', () => {
|
||||
expect(escapeEmbeddedHtmlCopy('<')).toBe('&lt;')
|
||||
})
|
||||
|
||||
it('HTML-escapes rich-editor placeholders before insertHTML parses them', () => {
|
||||
const codePlaceholder = mobileI18n.getResource(
|
||||
'en',
|
||||
'translation',
|
||||
'richMarkdown.codePlaceholder'
|
||||
)
|
||||
const taskPlaceholder = mobileI18n.getResource(
|
||||
'en',
|
||||
'translation',
|
||||
'richMarkdown.taskPlaceholder'
|
||||
)
|
||||
mobileI18n.addResource(
|
||||
'en',
|
||||
'translation',
|
||||
'richMarkdown.codePlaceholder',
|
||||
'<img src=x onerror=alert(1)>'
|
||||
)
|
||||
mobileI18n.addResource(
|
||||
'en',
|
||||
'translation',
|
||||
'richMarkdown.taskPlaceholder',
|
||||
'<b>Task & more</b>'
|
||||
)
|
||||
it('escapes the single quote that would close an attribute', () => {
|
||||
expect(escapeEmbeddedHtmlCopy("it's")).toBe('it's')
|
||||
})
|
||||
|
||||
try {
|
||||
const editorHtml = buildMobileRichMarkdownEditorHtml()
|
||||
expect(editorHtml).toContain('<img src=x onerror=alert(1)>')
|
||||
expect(editorHtml).toContain('<b>Task & more</b>')
|
||||
expect(editorHtml).not.toContain('<img src=x onerror=alert(1)>')
|
||||
expect(editorHtml).not.toContain('<b>Task & more</b>')
|
||||
} finally {
|
||||
mobileI18n.addResource('en', 'translation', 'richMarkdown.codePlaceholder', codePlaceholder)
|
||||
mobileI18n.addResource('en', 'translation', 'richMarkdown.taskPlaceholder', taskPlaceholder)
|
||||
}
|
||||
it('leaves copy without markup untouched', () => {
|
||||
expect(escapeEmbeddedHtmlCopy('Seleccionar todo')).toBe('Seleccionar todo')
|
||||
expect(escapeEmbeddedHtmlCopy('')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -83,9 +83,12 @@ describe('mobile i18n startup', () => {
|
||||
expect(mobileI18n.isInitialized).toBe(true)
|
||||
})
|
||||
|
||||
// A locale change restarts the app, so only the fallback and the active locale are
|
||||
// ever needed. Registering all five would parse unused catalogs on every boot.
|
||||
it('registers only the catalogs it has, not every supported locale', () => {
|
||||
// Coverage, not a regression guard: with only en.json present, registering every
|
||||
// supported locale produces the same result as registering the active one, so this
|
||||
// cannot distinguish the two. It pins that a supported locale without a catalog is
|
||||
// never registered. Extraction adds a second catalog, which is what finally makes
|
||||
// the lazy path observable.
|
||||
it('never registers a supported locale that has no catalog', () => {
|
||||
expect(mobileI18n.hasResourceBundle('en', 'translation')).toBe(true)
|
||||
for (const locale of ['es', 'ja', 'ko', 'zh'] satisfies MobileUiLocale[]) {
|
||||
expect(loadMobileLocaleCatalog(locale)).toBeUndefined()
|
||||
|
||||
Reference in New Issue
Block a user