import assert from "node:assert/strict"; const SNAKE_CASE_RE = /^[a-z0-9]+(?:_[a-z0-9]+)*$/; const PHYSICAL_HITZONE_KEYS = ["cut", "blunt", "ammo"]; const ELEMENTAL_HITZONE_KEYS = ["fire", "water", "thunder", "ice", "dragon"]; function valueAt(object, path) { return path.split(".").reduce((value, key) => value?.[key], object); } function assertNonEmptyString(object, path) { const value = valueAt(object, path); assert.equal(typeof value, "string", `${path} must be a string`); assert.ok(value.trim(), `${path} must not be empty`); } export function assertStringArray(object, path, minLength = 1) { const value = valueAt(object, path); assert.ok(Array.isArray(value), `${path} must be an array`); assert.ok(value.length >= minLength, `${path} must contain at least ${minLength} item(s)`); value.forEach((item, index) => { assert.equal(typeof item, "string", `${path}[${index}] must be a string`); assert.ok(item.trim(), `${path}[${index}] must not be empty`); }); } export function validateSiteContent(site) { [ "brand.name", "brand.homeAriaLabel", "navigation.home", "navigation.toolboxes", "navigation.games", "navigation.mobileGames", "sidebar.badge", "sidebar.note", "topbar.dashboard", "topbar.toolbox", "topbar.games", "gamesPage.eyebrow", "gamesPage.title", "gamesPage.description", "gamesPage.emptyTitle", "gamesPage.emptyText", "home.hero.eyebrow", "home.hero.title", "home.hero.description", "home.hero.primaryAction", "home.hero.secondaryAction", "home.stats.toolboxSingular", "home.stats.toolboxPlural", "home.stats.toolSingular", "home.stats.toolPlural", "home.origin.eyebrow", "home.origin.title", "home.origin.visualAlt", "home.origin.dialogueAriaLabel", "home.origin.caption", "home.legal.copyright", "home.legal.disclaimer", "toolboxes.eyebrow", "toolboxes.title", "toolboxes.newButton", "toolboxes.importAll", "toolboxes.exportAll", "toolboxes.importOne", "toolboxes.summary", "toolboxes.storageHelp.title", "toolboxes.storageHelp.text", "toolboxes.emptyTitle", "toolboxes.emptyText" ].forEach((path) => assertNonEmptyString(site, path)); assertStringArray(site, "toolboxes.storageHelp.items", 3); const lines = valueAt(site, "home.origin.lines"); assert.ok(Array.isArray(lines), "home.origin.lines must be an array"); assert.ok(lines.length >= 2, "home.origin.lines must contain at least 2 items"); lines.forEach((line, index) => { assert.ok(["user", "app"].includes(line?.speaker), `home.origin.lines[${index}].speaker must be "user" or "app"`); assert.equal(typeof line?.text, "string", `home.origin.lines[${index}].text must be a string`); assert.ok(line.text.trim(), `home.origin.lines[${index}].text must not be empty`); }); } function assertSnakeCase(value, path) { assert.equal(typeof value, "string", `${path} must be a string`); assert.match(value, SNAKE_CASE_RE, `${path} must be snake_case`); } function validateConditionValues(items, path) { assert.ok(Array.isArray(items), `${path} must be an array`); items.forEach((item, index) => { assertSnakeCase(item?.condition, `${path}[${index}].condition`); assert.ok(Array.isArray(item?.values), `${path}[${index}].values must be an array`); item.values.forEach((value, valueIndex) => assertSnakeCase(value, `${path}[${index}].values[${valueIndex}]`)); }); } export function validateMonsterData(monsters) { assert.ok(Array.isArray(monsters), "monsters must be an array"); const ids = new Set(); const names = new Set(); monsters.forEach((monster, monsterIndex) => { const path = `monsters[${monsterIndex}]`; assert.equal(typeof monster.id, "number", `${path}.id must be a number`); assert.ok(Number.isInteger(monster.id), `${path}.id must be an integer`); assert.ok(!ids.has(monster.id), `${path}.id must be unique`); ids.add(monster.id); assertSnakeCase(monster.name, `${path}.name`); assert.ok(!names.has(monster.name), `${path}.name must be unique`); names.add(monster.name); assertSnakeCase(monster.type, `${path}.type`); validateConditionValues(monster.ailments, `${path}.ailments`); validateConditionValues(monster.weaknesses, `${path}.weaknesses`); validateConditionValues(monster.weakpoints, `${path}.weakpoints`); assert.equal(monster.damage, undefined, `${path}.damage must not be used`); assert.ok(Array.isArray(monster.hitzones), `${path}.hitzones must be an array`); assert.ok(monster.hitzones.length > 0, `${path}.hitzones must not be empty`); monster.hitzones.forEach((hitzone, hitzoneIndex) => { const hitzonePath = `${path}.hitzones[${hitzoneIndex}]`; assertSnakeCase(hitzone.name, `${hitzonePath}.name`); assert.equal(typeof hitzone.physical, "object", `${hitzonePath}.physical must be an object`); assert.equal(typeof hitzone.elemental, "object", `${hitzonePath}.elemental must be an object`); PHYSICAL_HITZONE_KEYS.forEach((key) => assert.equal(typeof hitzone.physical[key], "number", `${hitzonePath}.physical.${key} must be a number`)); ELEMENTAL_HITZONE_KEYS.forEach((key) => assert.equal(typeof hitzone.elemental[key], "number", `${hitzonePath}.elemental.${key} must be a number`)); }); }); } export function validateEndemicData(endemicPayload) { ["endemicLife", "aquaticLife"].forEach((collection) => { assert.ok(Array.isArray(endemicPayload[collection]), `${collection} must be an array`); endemicPayload[collection].forEach((item, itemIndex) => { const path = `${collection}[${itemIndex}]`; assertSnakeCase(item.name, `${path}.name`); if (item.description) assertSnakeCase(item.description, `${path}.description`); validateConditionValues(item.locations, `${path}.locations`); }); }); } export function validateTranslationKeys(translations, path) { Object.keys(translations).forEach((key) => { assert.ok(!/\s/.test(key), `${path}.${key} must not contain spaces`); }); } export function validateDiablo4Affixes(payload) { assert.ok(Array.isArray(payload.categories), "diablo4.categories must be an array"); assert.ok(payload.categories.length > 0, "diablo4.categories must not be empty"); assert.ok(Array.isArray(payload.affixes), "diablo4.affixes must be an array"); assert.ok(payload.affixes.length > 0, "diablo4.affixes must not be empty"); const ids = new Set(); const categoryIds = payload.categories.map((category, index) => { assertSnakeCase(category.id, `diablo4.categories[${index}].id`); assertNonEmptyString({ category }, "category.label"); assertSnakeCase(category.icon, `diablo4.categories[${index}].icon`); assertSnakeCase(category.tone, `diablo4.categories[${index}].tone`); return category.id; }); payload.affixes.forEach((affix, index) => { assertSnakeCase(affix.id, `diablo4.affixes[${index}].id`); assert.ok(!ids.has(affix.id), `diablo4.affixes[${index}].id must be unique`); ids.add(affix.id); assertNonEmptyString({ affix }, "affix.label"); assert.ok(Array.isArray(affix.categories), `diablo4.affixes[${index}].categories must be an array`); affix.categories.forEach((category, categoryIndex) => { assertSnakeCase(category, `diablo4.affixes[${index}].categories[${categoryIndex}]`); assert.ok(categoryIds.includes(category), `diablo4.affixes[${index}].categories[${categoryIndex}] must exist in diablo4.categories`); }); }); }