Files
pleno-vue/tests/unit/i18n-action-settings-wheel.spec.js
T

173 lines
5.7 KiB
JavaScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { CUSTOMER_RULE_DEFINITIONS } from "@/features/customer/customerRuleRegistry.js";
import { readI18nRuntimeMessages } from "./helpers/readI18nRuntimeMessages";
const root = process.cwd();
const componentPath = join(root, "src/components/displays/buttons/ActionSettingsWheelButton.vue");
const activeLocales = ["da", "en", "sv", "de", "no"];
const translationCallPattern = /(?:\$t|\bt)\(\s*(["'])([^"'\\]*(?:\\.[^"'\\]*)*)\1/g;
const readLocale = (locale) => {
return readI18nRuntimeMessages(locale);
};
const extractLiteralTranslationKeys = (source) => {
const keys = new Set();
for (const match of source.matchAll(translationCallPattern)) {
const key = match[2];
if (!key || !key.includes(".")) {
continue;
}
keys.add(key);
}
return [...keys].sort();
};
const hasKeyPath = (messages, keyPath) => {
let current = messages;
for (const segment of keyPath.split(".")) {
if (!current || typeof current !== "object" || Array.isArray(current)) {
return false;
}
if (!(segment in current)) {
return false;
}
current = current[segment];
}
return true;
};
const getKeyPathValue = (messages, keyPath) => {
let current = messages;
for (const segment of keyPath.split(".")) {
if (!current || typeof current !== "object" || Array.isArray(current)) {
return undefined;
}
current = current[segment];
}
return current;
};
describe("ActionSettingsWheelButton i18n coverage", () => {
it("contains all literal translation keys in all active locales", () => {
const componentSource = readFileSync(componentPath, "utf8");
const usedKeys = extractLiteralTranslationKeys(componentSource);
expect(usedKeys.length).toBeGreaterThan(0);
const missingByLocale = {};
for (const locale of activeLocales) {
const messages = readLocale(locale);
const missingKeys = usedKeys.filter((key) => !hasKeyPath(messages, key));
if (missingKeys.length > 0) {
missingByLocale[locale] = missingKeys;
}
}
if (Object.keys(missingByLocale).length > 0) {
const details = Object.entries(missingByLocale)
.map(([locale, keys]) => `${locale} (${keys.length}):\n- ${keys.join("\n- ")}`)
.join("\n\n");
throw new Error(
[
"Missing translation keys detected for ActionSettingsWheelButton literal usages.",
"Each key below is used in src/components/displays/buttons/ActionSettingsWheelButton.vue",
"but absent from the locale JSON.",
"",
details,
].join("\n")
);
}
});
it("keeps the department hardware menu labels free of encoding placeholders", () => {
const departmentHardwareKeys = [
"admin.pos.settings_wheel.self_serve_studio_section",
"admin.pos.settings_wheel.gates_section",
"admin.pos.settings_wheel.relays_section",
"admin.pos.settings_wheel.gateways_section",
"admin.pos.settings_wheel.open_studio",
"admin.pos.settings_wheel.open_legacy_self_serve",
"admin.pos.settings_wheel.open_hardware_workspace_lanes",
"admin.pos.settings_wheel.open_gates_tab",
"admin.pos.settings_wheel.open_legacy_gates",
"admin.pos.settings_wheel.add_gate",
"admin.pos.settings_wheel.open_relays_tab",
"admin.pos.settings_wheel.open_legacy_relays",
"admin.pos.settings_wheel.add_relay",
"admin.pos.settings_wheel.open_gateways_tab",
"admin.pos.settings_wheel.open_fleet_landing",
"admin.pos.settings_wheel.open_primary_gateway",
];
for (const locale of activeLocales) {
const messages = readLocale(locale);
for (const key of departmentHardwareKeys) {
const value = getKeyPathValue(messages, key);
expect(typeof value).toBe("string");
expect(value).not.toMatch(/[?\uFFFD]/u);
expect(value).not.toContain("\u00ef\u00bf\u00bd");
}
}
});
it("renders the Danish vehicle action labels with the expected copy", () => {
const messages = readLocale("da");
const manageValue = getKeyPathValue(messages, "admin.pos.settings_wheel.manage_vehicle");
const newTabValue = getKeyPathValue(messages, "admin.pos.settings_wheel.view_vehicle_new_tab");
const deleteValue = getKeyPathValue(messages, "admin.pos.settings_wheel.delete_vehicle");
expect(manageValue).toBe("Administrer køretøj");
expect(newTabValue).toBe("Åbn køretøj i ny fane");
expect(newTabValue).not.toContain("{reg}");
expect(deleteValue).toBe("Slet køretøj");
expect([manageValue, newTabValue, deleteValue].join(" ")).not.toMatch(/[?\uFFFD]/u);
});
it("contains all shared customer rule translation keys in all active locales", () => {
const sharedCustomerRuleKeys = CUSTOMER_RULE_DEFINITIONS.flatMap((rule) => [rule.labelKey, rule.descriptionKey]);
const missingByLocale = {};
for (const locale of activeLocales) {
const messages = readLocale(locale);
const missingKeys = sharedCustomerRuleKeys.filter((key) => !hasKeyPath(messages, key));
if (missingKeys.length > 0) {
missingByLocale[locale] = missingKeys;
}
}
if (Object.keys(missingByLocale).length > 0) {
const details = Object.entries(missingByLocale)
.map(([locale, keys]) => `${locale} (${keys.length}):\n- ${keys.join("\n- ")}`)
.join("\n\n");
throw new Error(
[
"Missing translation keys detected for the shared customer rule registry.",
"Each key below is used by ActionSettingsWheelButton and PosSelectedCustomer",
"but absent from the locale JSON.",
"",
details,
].join("\n")
);
}
});
});