fix(rc5): harden frontend runtime and diagnostics
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createI18n } from "vue-i18n";
|
||||
|
||||
import {
|
||||
PEER_NAME_MAX_LENGTH,
|
||||
PEER_NAME_MIN_LENGTH,
|
||||
PEER_NAME_PUNCTUATION,
|
||||
} from "../src/constants/peer";
|
||||
import en from "../src/lang/package/en";
|
||||
import ru from "../src/lang/package/ru";
|
||||
|
||||
type Dictionary = Record<string, unknown>;
|
||||
|
||||
function leafMessages(
|
||||
value: unknown,
|
||||
prefix = ""
|
||||
): Array<[key: string, message: string]> {
|
||||
if (typeof value === "string") {
|
||||
return [[prefix, value]];
|
||||
}
|
||||
if (!value || typeof value !== "object") {
|
||||
return [];
|
||||
}
|
||||
return Object.entries(value as Dictionary).flatMap(([key, child]) =>
|
||||
leafMessages(child, prefix ? `${prefix}.${key}` : key)
|
||||
);
|
||||
}
|
||||
|
||||
function interpolationValues(message: string): Record<string, string | number> {
|
||||
const values: Record<string, string | number> = {
|
||||
count: 2,
|
||||
n: 2,
|
||||
min: PEER_NAME_MIN_LENGTH,
|
||||
max: PEER_NAME_MAX_LENGTH,
|
||||
punctuation: PEER_NAME_PUNCTUATION,
|
||||
};
|
||||
for (const match of message.matchAll(/\{([A-Za-z_][A-Za-z0-9_$-]*)\}/g)) {
|
||||
values[match[1]] ??= match[1];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
for (const [locale, dictionary] of Object.entries({ ru, en }) as Array<
|
||||
["ru" | "en", Dictionary]
|
||||
>) {
|
||||
describe(`runtime-компиляция локализации ${locale}`, () => {
|
||||
test("каждое строковое сообщение компилируется без diagnostics", () => {
|
||||
const compilerDiagnostics: string[] = [];
|
||||
const thrown: string[] = [];
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
console.error = (...args: unknown[]) => {
|
||||
compilerDiagnostics.push(args.map(String).join(" "));
|
||||
};
|
||||
|
||||
try {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale,
|
||||
fallbackLocale: false,
|
||||
missingWarn: false,
|
||||
fallbackWarn: false,
|
||||
messages: { [locale]: dictionary },
|
||||
});
|
||||
|
||||
for (const [key, message] of leafMessages(dictionary)) {
|
||||
try {
|
||||
const translated = i18n.global.t(key, interpolationValues(message));
|
||||
if (typeof translated !== "string") {
|
||||
thrown.push(`${key}: результат не является строкой`);
|
||||
}
|
||||
} catch (error) {
|
||||
thrown.push(
|
||||
`${key}: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
console.error = originalConsoleError;
|
||||
}
|
||||
|
||||
expect(thrown).toEqual([]);
|
||||
expect(compilerDiagnostics).toEqual([]);
|
||||
});
|
||||
|
||||
test("набор знаков имени пира проходит интерполяцию как данные", () => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale,
|
||||
fallbackLocale: false,
|
||||
messages: { [locale]: dictionary },
|
||||
});
|
||||
|
||||
const template = (dictionary as any).peer.form.nameHint as string;
|
||||
expect(
|
||||
i18n.global.t("peer.form.nameHint", {
|
||||
min: PEER_NAME_MIN_LENGTH,
|
||||
max: PEER_NAME_MAX_LENGTH,
|
||||
punctuation: PEER_NAME_PUNCTUATION,
|
||||
})
|
||||
).toBe(
|
||||
template
|
||||
.replace("{min}", String(PEER_NAME_MIN_LENGTH))
|
||||
.replace("{max}", String(PEER_NAME_MAX_LENGTH))
|
||||
.replace("{punctuation}", PEER_NAME_PUNCTUATION)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user