Skip to content

Commit c00d706

Browse files
committed
tests and motd
1 parent 04fc4e4 commit c00d706

File tree

9 files changed

+331
-142
lines changed

9 files changed

+331
-142
lines changed

e2e/demo.test.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.

playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ export default defineConfig({
55
command: "npm run build && npm run preview",
66
port: 4173,
77
},
8-
testDir: "e2e",
8+
testDir: "src/e2e",
99
});

src/e2e/basic.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { defaultColorReverseLUT } from "$lib/text/general";
2+
import { expect, test } from "@playwright/test";
3+
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto("/");
6+
});
7+
8+
test("home page loads", async ({ page }) => {
9+
const resp = await page.goto("/");
10+
expect(resp).toBeDefined();
11+
expect(resp?.ok()).toBe(true);
12+
});
13+
14+
test("blank text updates with formatted output", async ({ page }) => {
15+
await page.getByLabel("Keybinds").waitFor();
16+
const textbox = page.getByRole("textbox").nth(1);
17+
await textbox.click();
18+
await textbox.fill("lorem ipsum");
19+
const output = page.locator("#outputbox").first();
20+
await expect(output).toHaveText(`"lorem ipsum"`);
21+
});
22+
23+
test("the bold button should work", async ({ page }) => {
24+
const textbox = page.getByRole("textbox").nth(1);
25+
await textbox.click();
26+
await textbox.fill("lorem ipsum");
27+
await textbox.selectText();
28+
let button = page.getByRole("button", { name: "Bold " });
29+
await button.click();
30+
expect(await textbox.locator("p>strong").count()).toBeGreaterThan(0);
31+
await expect(textbox.locator("p>strong").first()).toHaveCSS(
32+
"font-family",
33+
"MinecraftBold",
34+
);
35+
});
36+
37+
test("the italic button should work", async ({ page }) => {
38+
const textbox = page.getByRole("textbox").nth(1);
39+
await textbox.click();
40+
await textbox.fill("lorem ipsum");
41+
await textbox.selectText();
42+
let button = page.getByRole("button", { name: "Italic " });
43+
await button.click();
44+
expect(await textbox.locator("p>em").count()).toBeGreaterThan(0);
45+
await expect(textbox.locator("p>em").first()).toHaveCSS(
46+
"font-style",
47+
"italic",
48+
);
49+
});
50+
51+
test("the strikethrough button should work", async ({ page }) => {
52+
const textbox = page.getByRole("textbox").nth(1);
53+
await textbox.click();
54+
await textbox.fill("lorem ipsum");
55+
await textbox.selectText();
56+
let button = page.getByRole("button", { name: "Strikethrough " });
57+
await button.click();
58+
expect(await textbox.locator("p>s").count()).toBeGreaterThan(0);
59+
await expect(textbox.locator("p>s").first()).toHaveCSS(
60+
"text-decoration-line",
61+
"line-through",
62+
);
63+
});
64+
65+
test("the underline button should work", async ({ page }) => {
66+
const textbox = page.getByRole("textbox").nth(1);
67+
await textbox.click();
68+
await textbox.fill("lorem ipsum");
69+
await textbox.selectText();
70+
let button = page.getByRole("button", { name: "Underline " });
71+
await button.click();
72+
expect(await textbox.locator("p>u").count()).toBeGreaterThan(0);
73+
await expect(textbox.locator("p>u").first()).toHaveCSS(
74+
"text-decoration-line",
75+
"underline",
76+
);
77+
});
78+
79+
test("the obfuscation button should work", async ({ page }) => {
80+
const textbox = page.getByRole("textbox").nth(1);
81+
await textbox.click();
82+
await textbox.fill("lorem ipsum");
83+
await textbox.selectText();
84+
let button = page.getByRole("button", { name: "Obfuscated " });
85+
await button.click();
86+
expect(await textbox.locator("p>span.obfuscated").count()).toBeGreaterThan(0);
87+
});

src/lib/components/modals/ExportModal.svelte

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import Modal from "$lib/components/Modal.svelte";
33
import IconCopy from "~icons/tabler/copy";
44
import IconTick from "~icons/tabler/check";
5-
import { translate, convert } from "$lib/text/nbt_or_json";
5+
import { translateJSON, convert } from "$lib/text/nbt_or_json";
6+
import { translateMOTD } from "$lib/text/motd";
67
let {
78
outputDialog = $bindable(),
89
outputVersion = $bindable(),
@@ -74,7 +75,7 @@
7475
{:else}
7576
<code class="inline-block max-h-56 w-full overflow-auto"
7677
>[lore={editor
77-
? `'${translate(editor.getJSON(), {
78+
? `'${translateJSON(editor.getJSON(), {
7879
exportType: "item_lore",
7980
exportVersion: outputVersion,
8081
optimise: shouldOptimise,
@@ -84,13 +85,30 @@
8485
{/if}
8586
</div>
8687

88+
<p class="mt-2">As a MOTD:</p>
89+
<div class="flex items-start space-x-3 rounded-lg bg-zinc-950 p-3">
90+
<button
91+
class="rounded-md p-1 text-lg font-medium hover:bg-zinc-900 active:bg-white/10"
92+
onclick={() => {
93+
navigator.clipboard.writeText(editor ? translateMOTD(editor.getJSON()) : "Loading...");
94+
recentlyCopied = true;
95+
setTimeout(() => (recentlyCopied = false), 2000);
96+
}}>
97+
<IconCopy />
98+
</button>
99+
<code class="inline-block max-h-56 w-full overflow-auto"
100+
>{editor
101+
? translateMOTD(editor.getJSON()) : "Loading..."}
102+
</code>
103+
</div>
104+
87105
<p class="mt-2">As JSON:</p>
88106
<div class="flex items-start space-x-3 rounded-lg bg-zinc-950 p-3">
89107
<button
90108
class="rounded-md p-1 text-lg font-medium hover:bg-zinc-900 active:bg-white/10"
91109
onclick={() => {
92110
navigator.clipboard.writeText(
93-
translate(editor.getJSON(), {
111+
translateJSON(editor.getJSON(), {
94112
exportType: "standard",
95113
exportVersion: outputVersion,
96114
indent,
@@ -105,7 +123,7 @@
105123
</button>
106124
<code class="inline-block max-h-56 w-full overflow-auto"
107125
><pre>{editor
108-
? translate(editor.getJSON(), {
126+
? translateJSON(editor.getJSON(), {
109127
exportType: "standard",
110128
exportVersion: outputVersion,
111129
indent,

src/lib/text/general.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,26 @@ export function trueMarkOrUndefined(
1313
): true | undefined {
1414
const value = content.marks?.some((e) => e.type === mark);
1515
return value === true ? value : undefined;
16-
} /**
17-
* A color value LUT
16+
}
17+
/**
18+
* A LUT to find the name of a color
1819
*
1920
* @param color the hex code
2021
* @returns the color name
2122
*/
22-
2323
export function defaultColorLUT(color: string): string | undefined {
2424
if (!color || color === "null") {
2525
return;
2626
}
2727
return colorMap.find((e) => e.value.toLowerCase() === color)?.name || color;
28-
} /**
29-
* A color name LUT
28+
}
29+
30+
/**
31+
* A LUT to find the value of a named color
3032
*
3133
* @param color the color name you want to find
3234
* @returns the hex code for the color
3335
*/
34-
3536
export function defaultColorReverseLUT(color: string): string | undefined {
3637
if (!color || color === "null") {
3738
return;
@@ -104,21 +105,22 @@ export function applyGradient(editor: Editor, gradientColors: string[]) {
104105

105106
chain.run();
106107
}
108+
107109
export const colorMap = [
108-
{ name: "dark_red", value: "#AA0000" },
109-
{ name: "red", value: "#FF5555" },
110-
{ name: "gold", value: "#FFAA00" },
111-
{ name: "yellow", value: "#FFFF55" },
112-
{ name: "green", value: "#55FF55" },
113-
{ name: "dark_green", value: "#00AA00" },
114-
{ name: "aqua", value: "#55FFFF" },
115-
{ name: "dark_aqua", value: "#00AAAA" },
116-
{ name: "blue", value: "#5555FF" },
117-
{ name: "dark_blue", value: "#0000AA" },
118-
{ name: "dark_purple", value: "#AA00AA" },
119-
{ name: "light_purple", value: "#FF55FF" },
120-
{ name: "white", value: "#FFFFFF" },
121-
{ name: "gray", value: "#AAAAAA" },
122-
{ name: "dark_gray", value: "#555555" },
123-
{ name: "black", value: "#000000" },
110+
{ name: "dark_red", value: "#AA0000", code: "4" },
111+
{ name: "red", value: "#FF5555", code: "c" },
112+
{ name: "gold", value: "#FFAA00", code: "6" },
113+
{ name: "yellow", value: "#FFFF55", code: "e" },
114+
{ name: "green", value: "#55FF55", code: "2" },
115+
{ name: "dark_green", value: "#00AA00", code: "a" },
116+
{ name: "aqua", value: "#55FFFF", code: "b"},
117+
{ name: "dark_aqua", value: "#00AAAA", code: "3"},
118+
{ name: "blue", value: "#5555FF", code: "1"},
119+
{ name: "dark_blue", value: "#0000AA", code: "9"},
120+
{ name: "dark_purple", value: "#AA00AA", code: "d"},
121+
{ name: "light_purple", value: "#FF55FF", code: "5"},
122+
{ name: "white", value: "#FFFFFF", code: "f"},
123+
{ name: "gray", value: "#AAAAAA", code: "7"},
124+
{ name: "dark_gray", value: "#555555", code: "8"},
125+
{ name: "black", value: "#000000", code: "0"},
124126
];

src/lib/text/motd.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { JSONContent } from "@tiptap/core";
2+
import { colorMap, defaultColorLUT, trueMarkOrUndefined } from "./general";
3+
4+
function hexToRgb(hex: string): [number, number, number] {
5+
hex = hex.replace(/^#/, "");
6+
if (hex.length === 3) {
7+
hex = hex
8+
.split("")
9+
.map((x) => x + x)
10+
.join("");
11+
}
12+
const num = parseInt(hex, 16);
13+
return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
14+
}
15+
16+
function rgbToLab([r, g, b]: [number, number, number]): [
17+
number,
18+
number,
19+
number,
20+
] {
21+
// Convert RGB to XYZ
22+
let [rr, gg, bb] = [r, g, b].map((v) => {
23+
v /= 255;
24+
return v > 0.04045 ? Math.pow((v + 0.055) / 1.055, 2.4) : v / 12.92;
25+
});
26+
27+
const x = (rr * 0.4124 + gg * 0.3576 + bb * 0.1805) / 0.95047;
28+
const y = (rr * 0.2126 + gg * 0.7152 + bb * 0.0722) / 1.0;
29+
const z = (rr * 0.0193 + gg * 0.1192 + bb * 0.9505) / 1.08883;
30+
31+
// Convert XYZ to LAB
32+
const xyz = [x, y, z].map((v) =>
33+
v > 0.008856 ? Math.cbrt(v) : 7.787 * v + 16 / 116,
34+
);
35+
36+
const l = 116 * xyz[1] - 16;
37+
const a = 500 * (xyz[0] - xyz[1]);
38+
const b_ = 200 * (xyz[1] - xyz[2]);
39+
return [l, a, b_];
40+
}
41+
42+
export function deltaE(hex1: string, hex2: string): number {
43+
const lab1 = rgbToLab(hexToRgb(hex1));
44+
const lab2 = rgbToLab(hexToRgb(hex2));
45+
const [l1, a1, b1] = lab1;
46+
const [l2, a2, b2] = lab2;
47+
return Math.sqrt(
48+
Math.pow(l1 - l2, 2) + Math.pow(a1 - a2, 2) + Math.pow(b1 - b2, 2),
49+
);
50+
}
51+
52+
const formattingCodes = [
53+
{key: "obfuscated", value: "k"},
54+
{key: "bold", value: "l"},
55+
{key: "strike", value: "m"},
56+
{key: "underline", value: "n"},
57+
{key: "italic", value: "o"},
58+
]
59+
60+
export function translateMOTD(c: JSONContent) {
61+
const char = "\\u00a7";
62+
const paragraphs = c.content!;
63+
64+
let data = "";
65+
66+
for (const [i, p] of paragraphs.entries()) {
67+
const content = p.content ?? [];
68+
69+
70+
for (const c of content) {
71+
if (!c.text) {
72+
continue;
73+
}
74+
75+
let lowestDE = 999;
76+
let lowestDEVal = "";
77+
let formatting = ""
78+
79+
const color = defaultColorLUT(c.marks?.at(0)?.attrs?.color);
80+
for (const c of colorMap) {
81+
if (!color) {
82+
continue;
83+
}
84+
85+
const dE = deltaE(color, c.value);
86+
if (dE === 0) {
87+
lowestDEVal = `${char}${c.code}`
88+
break;
89+
}
90+
if (dE < lowestDE) {
91+
lowestDE = dE
92+
lowestDEVal = `${char}${c.code}`
93+
}
94+
}
95+
96+
for (const code of formattingCodes) {
97+
if (trueMarkOrUndefined(c, code.key)) {
98+
formatting += `${char}${code.value}`
99+
}
100+
}
101+
102+
data += `${char}r${lowestDEVal}${formatting}${c.text}`
103+
}
104+
if (i < paragraphs.length - 1) data += "\\n";
105+
}
106+
return data
107+
}

0 commit comments

Comments
 (0)