90 lines
4.6 KiB
TypeScript
90 lines
4.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { createElement } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { Composer, isPublishShortcut } from "@/components/Composer";
|
|
import { attachmentMeta, BULLET, COMPOSER_EMOJI, formatBytes, insertBullets } from "@/components/composer-text";
|
|
|
|
describe("insertBullets", () => {
|
|
it("adds a bullet at the start of the caret's line", () => {
|
|
expect(insertBullets("hello", 3, 3)).toEqual({ text: "• hello", selectionStart: 5, selectionEnd: 5 });
|
|
expect(insertBullets("one\ntwo\nthree", 5, 5).text).toBe("one\n• two\nthree");
|
|
});
|
|
it("adds a bullet to an empty text and to an empty last line", () => {
|
|
expect(insertBullets("", 0, 0)).toEqual({ text: BULLET, selectionStart: 2, selectionEnd: 2 });
|
|
expect(insertBullets("a\n", 2, 2).text).toBe("a\n• ");
|
|
});
|
|
it("adds a bullet to every selected line and keeps the selection on the same characters", () => {
|
|
const r = insertBullets("one\ntwo\nthree", 1, 9);
|
|
expect(r.text).toBe("• one\n• two\n• three");
|
|
expect(r.text.slice(r.selectionStart, r.selectionEnd)).toBe("ne\n• two\n• t");
|
|
});
|
|
it("does not bullet the next line when the selection ends right after a line break", () => {
|
|
expect(insertBullets("one\ntwo", 0, 4).text).toBe("• one\ntwo");
|
|
});
|
|
it("leaves lines that already start with a bullet unchanged", () => {
|
|
expect(insertBullets("• one\ntwo", 0, 9).text).toBe("• one\n• two");
|
|
expect(insertBullets("• one", 3, 3).text).toBe("• one");
|
|
});
|
|
it("changes nothing but the inserted characters", () => {
|
|
const text = "**bold** _it_\n indented";
|
|
const r = insertBullets(text, 0, text.length);
|
|
expect(r.text.replaceAll(BULLET, "")).toBe(text);
|
|
});
|
|
});
|
|
|
|
describe("composer helpers", () => {
|
|
it("offers 32 distinct emoji", () => {
|
|
expect(COMPOSER_EMOJI).toHaveLength(32);
|
|
expect(new Set(COMPOSER_EMOJI).size).toBe(32);
|
|
});
|
|
it("describes an attachment by type and size", () => {
|
|
expect(formatBytes(512)).toBe("512 B");
|
|
expect(formatBytes(2048)).toBe("2 KB");
|
|
expect(attachmentMeta({ type: "image/png", size: 2.4 * 1024 * 1024 })).toBe("Image, 2.4 MB");
|
|
expect(attachmentMeta({ type: "video/mp4", size: 18.2 * 1024 * 1024 })).toBe("Video, 18.2 MB");
|
|
});
|
|
it("publishes on Ctrl + Enter and Cmd + Enter only", () => {
|
|
expect(isPublishShortcut({ key: "Enter", ctrlKey: true, metaKey: false })).toBe(true);
|
|
expect(isPublishShortcut({ key: "Enter", ctrlKey: false, metaKey: true })).toBe(true);
|
|
expect(isPublishShortcut({ key: "Enter", ctrlKey: false, metaKey: false })).toBe(false);
|
|
expect(isPublishShortcut({ key: "a", ctrlKey: true, metaKey: false })).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("Composer markup", () => {
|
|
const base = { value: "", busy: false, error: false, onChange: () => {}, onSubmit: () => {} };
|
|
const html = (p: Partial<Parameters<typeof Composer>[0]>) => renderToStaticMarkup(createElement(Composer, { ...base, ...p }));
|
|
|
|
it("is collapsed when empty and open when holding text or a file", () => {
|
|
expect(html({})).toMatch(/class="composer"/);
|
|
expect(html({ value: "hi" })).toContain("composer composer-open");
|
|
const f = new File(["x"], "a.png", { type: "image/png" });
|
|
expect(html({ file: f, onFile: () => {} })).toContain("composer composer-open");
|
|
});
|
|
it("orders the toolbar: photo, video, emoji, divider, bulleted list, hint, Post", () => {
|
|
const out = html({ onFile: () => {} });
|
|
const order = ["Add image", "Add video", "Add emoji", "composer-sep", "Bulleted list", "composer-kbd", "btn-flip"];
|
|
const at = order.map((s) => out.indexOf(s));
|
|
expect(at.every((i) => i >= 0)).toBe(true);
|
|
expect([...at].sort((a, b) => a - b)).toEqual(at);
|
|
expect(out).not.toMatch(/aria-label="(Bold|Italic)"/);
|
|
});
|
|
it("shows the three Post states", () => {
|
|
expect(html({})).toMatch(/btn-flip-front">Post<.*btn-flip-back[^>]*>Write first/);
|
|
expect(html({ value: "hi" })).toMatch(/btn-flip-back[^>]*>Publish<svg/);
|
|
const busy = html({ value: "hi", busy: true });
|
|
expect(busy).toContain("btn-flip-busy");
|
|
expect(busy).toContain(">Posting<");
|
|
});
|
|
it("shows the attachment row with name, type, size, progress and remove", () => {
|
|
const f = new File([new Uint8Array(3 * 1024)], "clip.mp4", { type: "video/mp4" });
|
|
const out = html({ file: f, onFile: () => {}, person: { name: "Jordan Rivera" } });
|
|
expect(out).toContain("clip.mp4");
|
|
expect(out).toContain("Video, 3 KB");
|
|
expect(out).toContain('role="progressbar"');
|
|
expect(out).toContain('aria-label="Remove attachment"');
|
|
expect(out).toContain("composer-att-indent");
|
|
expect(out).toContain("hex-avatar");
|
|
});
|
|
});
|