159 lines
5.0 KiB
JavaScript
159 lines
5.0 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 { readJsonFile } from "./helpers/readJsonFile";
|
|
|
|
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 readJsonFile(join(root, `src/i18n/locales/${locale}.json`));
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
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",
|
|
];
|
|
|
|
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;
|
|
};
|
|
|
|
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(/[?�]/);
|
|
}
|
|
}
|
|
});
|
|
|
|
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")
|
|
);
|
|
}
|
|
});
|
|
});
|