diff --git a/tests/static-smoke.test.mjs b/tests/static-smoke.test.mjs index a617677..75c6324 100644 --- a/tests/static-smoke.test.mjs +++ b/tests/static-smoke.test.mjs @@ -2,6 +2,85 @@ import { readFile } from "node:fs/promises"; import { test } from "node:test"; import assert from "node:assert/strict"; +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`); +} + +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`); + }); +} + +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`); + }); +} + test("vite entrypoint loads the react application", async () => { const html = await readFile("website/index.html", "utf8"); @@ -150,6 +229,11 @@ test("react application defines the expected local toolbox primitives", async () assert.match(screenshotsModule, /dataTransfer\.files/); }); +test("site content json is complete", async () => { + const site = JSON.parse(await readFile("website/public/data/site.json", "utf8")); + validateSiteContent(site); +}); + test("mhwilds data and assets are available", async () => { const site = JSON.parse(await readFile("website/public/data/site.json", "utf8")); const games = JSON.parse(await readFile("website/public/data/games.json", "utf8")); diff --git a/website/public/data/site.json b/website/public/data/site.json index 708b2e3..5bf00f6 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -75,6 +75,10 @@ "text": "Merci ! :)" } ] + }, + "legal": { + "copyright": "© 2026 Shinuwa / Sokko G. Tous droits réservés.", + "disclaimer": "Sokko G est un projet non officiel. Les noms, logos, images, illustrations et contenus liés aux jeux présentés appartiennent à leurs éditeurs et ayants droit respectifs. Les visuels officiels utilisés sur le site restent la propriété de leurs détenteurs." } }, "toolboxes": { diff --git a/website/src/main.jsx b/website/src/main.jsx index c6d94af..153bd2a 100644 --- a/website/src/main.jsx +++ b/website/src/main.jsx @@ -44,57 +44,57 @@ const DEFAULT_TOOLBOX_ICON = `${TOOLBOX_ICON_BASE}toolbox.png`; const DEFAULT_SITE_CONTENT = { brand: { name: "Sokko G", homeAriaLabel: "Accueil Sokko G" }, navigation: { home: "Accueil", toolboxes: "Toolboxes", games: "Jeux", mobileGames: "Infos" }, - sidebar: { badge: "Local only", note: "Données stockées dans ce navigateur." }, - topbar: { dashboard: "Dashboard", toolbox: "Toolbox active", games: "Informations jeu" }, + sidebar: { badge: "", note: "" }, + topbar: { dashboard: "", toolbox: "", games: "" }, gamesPage: { - eyebrow: "Pages informatives", - title: "Jeux disponibles", - description: "Choisissez un jeu pour consulter ses données maintenues et associer une toolbox locale.", + eyebrow: "", + title: "Jeux", + description: "", emptyTitle: "Aucun jeu disponible", - emptyText: "Ajoutez des entrées dans /data/games.json." + emptyText: "" }, home: { hero: { - eyebrow: "Session gaming efficace", + eyebrow: "", title: "Sokko G", - description: "Centralisez vos outils et repères de jeu dans une interface locale, rapide à consulter, pensée pour accompagner vos sessions sans interrompre l’action.", - primaryAction: "Voir les toolboxes", - secondaryAction: "Voir les jeux" + description: "", + primaryAction: "Toolboxes", + secondaryAction: "Jeux" }, stats: { - toolboxSingular: "toolbox créée", - toolboxPlural: "toolboxs créées", - toolSingular: "outil disponible", - toolPlural: "outils disponibles" + toolboxSingular: "toolbox", + toolboxPlural: "toolboxes", + toolSingular: "outil", + toolPlural: "outils" }, origin: { - eyebrow: "Origine du nom", - title: "Pourquoi Sokko G ?", - visualAlt: "Dragon Sokko G", - dialogueAriaLabel: "Dialogue d’origine du nom Sokko G", - caption: "\"G\" c'est pour Gaming", + eyebrow: "", + title: "", + visualAlt: "", + dialogueAriaLabel: "", + caption: "", lines: [] + }, + legal: { + copyright: "", + disclaimer: "" } }, toolboxes: { - eyebrow: "Données locales", + eyebrow: "", title: "Toolboxes", newButton: "Nouvelle toolbox", - importAll: "Importer tout", - exportAll: "Exporter tout", - importOne: "Importer une toolbox", - summary: "Créez des espaces modulaires pour regrouper vos outils de session : notes, suivis, références, captures et futurs modules restent accessibles rapidement, en local, par jeu ou par usage.", + importAll: "Importer", + exportAll: "Exporter", + importOne: "Importer", + summary: "", storageHelp: { - title: "Sauvegardes locales", - text: "Vos toolboxes restent sur cet appareil, dans ce navigateur. Rien n’est envoyé sur un serveur.", - items: [ - "Un nettoyage du navigateur ou un changement d’appareil peut supprimer les données.", - "Surveillez le quota, surtout si vous ajoutez des screenshots.", - "Faites un export global régulier pour garder une sauvegarde." - ] + title: "", + text: "", + items: [] }, emptyTitle: "Aucune toolbox", - emptyText: "Créez une première toolbox pour stocker vos outils dans ce navigateur." + emptyText: "" } }; @@ -901,8 +901,8 @@ function Shell({ route, content, games, toolboxes, links, actions, children }) { {content.sidebar.badge}
{content.sidebar.note}
+
>
);
}
@@ -1012,8 +1016,8 @@ function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions }) {