Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
~*
.DS_Store
.coverage
3 changes: 0 additions & 3 deletions CONTRIBUTING.md

This file was deleted.

8 changes: 7 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@
"tasks": {
"example:basic": "deno -Aq --watch ./examples/demo.ts",
"example:layout": "deno -Aq --watch ./examples/layout.ts",
"example:calculator": "deno -Aq --watch ./examples/calculator.ts"
"example:calculator": "deno -Aq --watch ./examples/calculator.ts",
"test": "deno test -A --no-check=remote --parallel --coverage=.coverage",
"test:no-check": "deno test -A --no-check --parallel --coverage=.coverage",
"lint": "deno lint",
"lint:fix": "deno lint --fix",
"fmt": "deno fmt",
"fmt:check": "deno fmt --check"
},
"lint": {
"exclude": ["./examples"]
Expand Down
33 changes: 32 additions & 1 deletion deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions tests/canvas/box.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2023 Im-Beast. MIT license.

import { BoxObject } from "../../src/canvas/box.ts";
import { assertEquals } from "../deps.ts";
import { createCanvas } from "../fixtures.ts";

Deno.test("canvas/box.ts", () => {
const { canvas } = createCanvas({ columns: 5, rows: 2 });
const box = new BoxObject({
canvas,
style: (text) => `<${text}>`,
zIndex: 0,
filler: "x",
rectangle: { column: 0, row: 0, width: 3, height: 1 },
});

box.queueRerender(0, 0);
box.queueRerender(0, 1);
box.queueRerender(0, 2);
box.omitCells[0] = new Set([1]);

box.rerender();

assertEquals(canvas.frameBuffer[0][0], "<x>");
assertEquals(canvas.frameBuffer[0][1], undefined);
assertEquals(canvas.frameBuffer[0][2], "<x>");
assertEquals(Array.from(canvas.rerenderQueue[0] ?? []), [0, 2]);
assertEquals(Array.from(box.rerenderCells[0] ?? []), []);
});
60 changes: 60 additions & 0 deletions tests/canvas/core.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2023 Im-Beast. MIT license.

import { BoxObject } from "../../src/canvas/box.ts";
import { assertEquals } from "../deps.ts";
import { createCanvas, flush, identityStyle } from "../fixtures.ts";

Deno.test("canvas/canvas.ts", async (t) => {
await t.step("updateIntersections() tracks overlapping objects", () => {
const { canvas } = createCanvas({ columns: 5, rows: 3 });
const lower = new BoxObject({
canvas,
style: identityStyle,
zIndex: 0,
rectangle: { column: 0, row: 0, width: 2, height: 1 },
});
const higher = new BoxObject({
canvas,
style: identityStyle,
zIndex: 1,
rectangle: { column: 1, row: 0, width: 2, height: 1 },
});

lower.draw();
higher.draw();

canvas.updateIntersections(higher);
canvas.updateIntersections(lower);

assertEquals(higher.objectsUnder.has(lower), true);
assertEquals(lower.omitCells[0]?.has(1), true);
});

await t.step(
"render() writes queued cells and clears update state",
async () => {
const { canvas, writes } = createCanvas({ columns: 4, rows: 2 });
const box = new BoxObject({
canvas,
style: identityStyle,
zIndex: 0,
filler: "#",
rectangle: { column: 0, row: 0, width: 2, height: 1 },
});

let renders = 0;
canvas.on("render", () => {
++renders;
});

box.draw();
await flush();
canvas.render();

assertEquals(writes.length > 0, true);
assertEquals(canvas.updateObjects.length, 0);
assertEquals(Array.from(canvas.rerenderQueue[0] ?? []), []);
assertEquals(renders, 1);
},
);
});
11 changes: 11 additions & 0 deletions tests/canvas/mod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2023 Im-Beast. MIT license.

import * as canvas from "../../src/canvas/mod.ts";
import { assertEquals } from "../deps.ts";

Deno.test("canvas/mod.ts exports", () => {
assertEquals(typeof canvas.BoxObject, "function");
assertEquals(typeof canvas.TextObject, "function");
assertEquals(typeof canvas.Canvas, "function");
assertEquals(typeof canvas.Renderable, "function");
});
72 changes: 72 additions & 0 deletions tests/canvas/renderable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2023 Im-Beast. MIT license.

import { Renderable } from "../../src/canvas/renderable.ts";
import { View } from "../../src/view.ts";
import { signalify } from "../../src/signals/signalify.ts";
import { assertEquals } from "../deps.ts";
import { createCanvas, identityStyle } from "../fixtures.ts";

class TestRenderable extends Renderable<"test"> {
constructor(
options: ConstructorParameters<typeof Renderable<"test">>[1] & {
rectangle: { column: number; row: number; width: number; height: number };
},
) {
super("test", options);
this.rectangle = signalify(options.rectangle, { deepObserve: true });
}
}

Deno.test("canvas/renderable.ts", async (t) => {
await t.step("queueRerender() respects canvas and view bounds", () => {
const { canvas } = createCanvas({ columns: 4, rows: 3 });
const view = new View({
rectangle: { column: 1, row: 1, width: 2, height: 1 },
});
const renderable = new TestRenderable({
canvas,
style: identityStyle,
zIndex: 0,
view,
rectangle: { column: 0, row: 0, width: 2, height: 2 },
});

renderable.queueRerender(-1, 0);
renderable.queueRerender(0, 10);
renderable.queueRerender(1, 1);
renderable.queueRerender(1, 2);
renderable.queueRerender(0, 0);

assertEquals(Array.from(renderable.rerenderCells[1] ?? []), [1, 2]);
assertEquals(renderable.rerenderCells[0], undefined);
});

await t.step(
"updateOutOfBounds() handles zero-size and off-view rectangles",
() => {
const { canvas } = createCanvas({ columns: 4, rows: 3 });
const view = new View({
rectangle: { column: 0, row: 0, width: 2, height: 2 },
});
const renderable = new TestRenderable({
canvas,
style: identityStyle,
zIndex: 0,
view,
rectangle: { column: 0, row: 0, width: 1, height: 1 },
});

renderable.updateOutOfBounds();
assertEquals(renderable.outOfBounds, false);

renderable.rectangle.value.width = 0;
renderable.updateOutOfBounds();
assertEquals(renderable.outOfBounds, true);

renderable.rectangle.value.width = 1;
renderable.rectangle.value.column = 5;
renderable.updateOutOfBounds();
assertEquals(renderable.outOfBounds, true);
},
);
});
43 changes: 43 additions & 0 deletions tests/canvas/text.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 Im-Beast. MIT license.

import { TextObject } from "../../src/canvas/text.ts";
import { assertEquals } from "../deps.ts";
import { createCanvas, flush, identityStyle } from "../fixtures.ts";

Deno.test("canvas/text.ts", async (t) => {
await t.step(
"auto-sizes its rectangle when overwriteRectangle is false",
async () => {
const { canvas } = createCanvas();
const text = new TextObject({
canvas,
style: identityStyle,
zIndex: 0,
value: "Hello",
rectangle: { column: 0, row: 0 },
});

await flush();

assertEquals(text.rectangle.peek().width, 5);
assertEquals(text.rectangle.peek().height, 1);
},
);

await t.step("supports multi-code-point character splitting", async () => {
const { canvas } = createCanvas();
const text = new TextObject({
canvas,
style: identityStyle,
zIndex: 0,
value: "A👀B",
rectangle: { column: 0, row: 0 },
multiCodePointSupport: true,
});

await flush();

assertEquals(text.valueChars, ["A", "👀", "B"]);
assertEquals(text.rectangle.peek().width, 4);
});
});
62 changes: 62 additions & 0 deletions tests/component.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2023 Im-Beast. MIT license.

import { Component } from "../src/component.ts";
import { assertEquals } from "./deps.ts";
import { createFakeRoot, flush } from "./fixtures.ts";

class TestComponent extends Component {
}

Deno.test("component.ts", async (t) => {
await t.step("interact() records the last interaction", async () => {
const { root } = createFakeRoot();
const component = new TestComponent({
parent: root as never,
theme: {},
rectangle: { column: 0, row: 0, width: 1, height: 1 },
zIndex: 0,
});

await flush();
component.interact("mouse");

assertEquals(component.lastInteraction.method, "mouse");
assertEquals(component.lastInteraction.time > 0, true);
});

await t.step(
"changeDrawnObjectVisibility() toggles singletons and arrays",
async () => {
const { root } = createFakeRoot();
const component = new TestComponent({
parent: root as never,
theme: {},
rectangle: { column: 0, row: 0, width: 1, height: 1 },
zIndex: 0,
});
const calls: string[] = [];

component.drawnObjects = {
single: {
draw: () => calls.push("single:draw"),
erase: () => calls.push("single:erase"),
},
many: [
{
draw: () => calls.push("many1:draw"),
erase: () => calls.push("many1:erase"),
},
{
draw: () => calls.push("many2:draw"),
erase: () => calls.push("many2:erase"),
},
],
} as never;

component.changeDrawnObjectVisibility(false, true);

assertEquals(calls, ["single:erase", "many1:erase", "many2:erase"]);
assertEquals(component.drawnObjects, {});
},
);
});
23 changes: 23 additions & 0 deletions tests/components/box.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2023 Im-Beast. MIT license.

import { Box } from "../../src/components/box.ts";
import { BoxObject } from "../../src/canvas/box.ts";
import { assertEquals, assertStrictEquals } from "../deps.ts";
import { createFakeRoot, flush } from "../fixtures.ts";

Deno.test("components/box.ts", async () => {
const { root } = createFakeRoot();
const component = new Box({
parent: root as never,
theme: {},
rectangle: { column: 1, row: 1, width: 2, height: 3 },
zIndex: 0,
});

await flush();
component.draw();

assertEquals(component.drawnObjects.box instanceof BoxObject, true);
assertStrictEquals(component.drawnObjects.box.rectangle, component.rectangle);
assertStrictEquals(component.drawnObjects.box.style, component.style);
});
11 changes: 11 additions & 0 deletions tests/components/mod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2023 Im-Beast. MIT license.

import * as components from "../../src/components/mod.ts";
import { assertEquals } from "../deps.ts";

Deno.test("components/mod.ts exports", () => {
assertEquals(typeof components.Box, "function");
assertEquals(typeof components.Text, "function");
assertEquals(typeof components.Button, "function");
assertEquals(typeof components.Table, "function");
});
Loading
Loading