1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
import { runInNewContext } from "node:vm";
const forumDirectory = join(dirname(fileURLToPath(import.meta.url)), "../../forum");
const forumScript = readFileSync(join(forumDirectory, "site.js"), "utf8");
const forumPage = readFileSync(join(forumDirectory, "index.html"), "utf8");
function createElement() {
const listeners = new Map();
const attributes = new Map();
return {
textContent: "",
listeners,
attributes,
addEventListener(name, callback) {
listeners.set(name, callback);
},
setAttribute(name, value) {
attributes.set(name, value);
},
click() {
listeners.get("click")();
},
};
}
function loadForum(navigator = { languages: ["en-US"] }, initialStorage = {}) {
const elements = new Map(
["eyebrow", "title", "message", "home", "source", "theme"].map((id) => [id, createElement()]),
);
const values = new Map(Object.entries(initialStorage));
const localStorage = {
getItem(key) {
return values.get(key) ?? null;
},
setItem(key, value) {
values.set(key, value);
},
removeItem(key) {
values.delete(key);
},
};
const document = {
title: "",
documentElement: { lang: "en", dataset: {} },
getElementById(id) {
const element = elements.get(id);
if (!element) throw new Error(`unexpected element: ${id}`);
return element;
},
};
runInNewContext(forumScript, { document, localStorage, navigator });
return { document, elements, values };
}
test("forum shows English and dark mode to an English browser without offering sign-up", () => {
const { document, elements } = loadForum();
assert.equal(document.documentElement.lang, "en");
assert.equal(document.documentElement.dataset.textDirection, "ltr");
assert.equal(document.documentElement.dataset.theme, "dark");
assert.match(elements.get("title").textContent, /isn't open yet/);
assert.match(elements.get("message").textContent, /no accounts, posts/);
assert.doesNotMatch(elements.get("message").textContent, /sign up/i);
assert.match(document.title, /Tala/);
});
test("forum shows its one notice in the language of the browser", () => {
const german = loadForum({ languages: ["de-AT", "en"] });
assert.equal(german.document.documentElement.lang, "de");
assert.match(german.elements.get("title").textContent, /nicht geöffnet/);
assert.equal(german.elements.get("theme").attributes.get("aria-label"), "Farbschema wechseln");
const russian = loadForum({ languages: ["ru-RU"] });
assert.equal(russian.document.documentElement.lang, "ru");
assert.match(russian.elements.get("title").textContent, /Форум пока не открыт/);
assert.match(russian.elements.get("message").textContent, /нет ни аккаунтов, ни сообщений/);
for (const languages of [["he-IL"], ["iw"], ["HE", "en"]]) {
const hebrew = loadForum({ languages });
assert.equal(hebrew.document.documentElement.lang, "he");
assert.equal(hebrew.document.documentElement.dataset.textDirection, "rtl");
assert.match(hebrew.elements.get("title").textContent, /הפורום עדיין לא נפתח/);
assert.match(hebrew.elements.get("home").textContent, /←$/);
}
});
test("forum falls back to English when the browser language has no translation", () => {
for (const navigator of [
{ languages: ["tr-TR"] },
{ languages: ["tr-TR", "fr"] },
{ languages: [] },
{ languages: ["constructor"] },
{ language: "ja" },
{},
]) {
const { document, elements } = loadForum(navigator);
assert.equal(document.documentElement.lang, "en", JSON.stringify(navigator));
assert.match(elements.get("title").textContent, /isn't open yet/);
}
// A later preference with a translation is used before English.
assert.equal(loadForum({ languages: ["tr-TR", "de"] }).document.documentElement.lang, "de");
// A browser that reports only one language is honored.
assert.equal(loadForum({ language: "ru" }).document.documentElement.lang, "ru");
});
test("forum has no language menu and ignores a language stored by an older page", () => {
assert.doesNotMatch(forumPage, /<select/);
assert.doesNotMatch(forumPage, /id="language"/);
const { document, values } = loadForum({ languages: ["de"] }, { "vxs-forum-language": "ru" });
assert.equal(document.documentElement.lang, "de");
assert.equal(values.get("vxs-forum-language"), "ru");
});
test("forum restores the saved light preference", () => {
const { document } = loadForum({ languages: ["en"] }, { "tala-forum-theme": "light" });
assert.equal(document.documentElement.dataset.theme, "light");
});
test("forum moves a theme saved under the former key to the current one", () => {
const { document, values } = loadForum({ languages: ["en"] }, { "vxs-forum-theme": "light" });
assert.equal(document.documentElement.dataset.theme, "light");
assert.equal(values.get("tala-forum-theme"), "light");
assert.equal(values.has("vxs-forum-theme"), false);
});
test("forum theme button toggles and persists in both directions", () => {
const { document, elements, values } = loadForum();
elements.get("theme").click();
assert.equal(document.documentElement.dataset.theme, "light");
assert.equal(values.get("tala-forum-theme"), "light");
elements.get("theme").click();
assert.equal(document.documentElement.dataset.theme, "dark");
assert.equal(values.get("tala-forum-theme"), "dark");
});
|