Compare commits

..

No commits in common. "main" and "0.4.0" have entirely different histories.
main ... 0.4.0

25 changed files with 425 additions and 2188 deletions

41
.github/workflows/deno.yml vendored Normal file
View file

@ -0,0 +1,41 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will install Deno then run `deno lint` and `deno test`.
# For more information see: https://github.com/denoland/setup-deno
name: Deno
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Setup repo
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
# Uncomment this step to verify the use of 'deno fmt' on each commit.
- name: Verify formatting
run: deno fmt --check
- name: Run linter
run: deno lint
- name: Run tests
run: deno test -A

View file

@ -1,7 +1,7 @@
name: Publish on JSR name: Publish on JSR
on: on:
workflow_run: workflow_run:
workflows: ["Test"] workflows: ["Deno"]
types: [completed] types: [completed]
branches: branches:
- "main" - "main"
@ -9,7 +9,6 @@ on:
jobs: jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions: permissions:
contents: read contents: read
@ -18,36 +17,5 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup repo - name: Publish package
uses: actions/checkout@v4 run: npx jsr publish
- uses: pnpm/action-setup@v4
with:
run_install: false
- uses: actions/setup-node@v4
with:
node-version: "24.x"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
# This step removes JSR dependencies from package.json before publishing,
# Because apparently now while major npm compatible package managers
# support JSR dependencies in package.json, JSR itself does not.
- name: Remove JSR deps from package.json
run: |
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
for (const dep in pkg.dependencies ?? {}) {
if (pkg.dependencies[dep].startsWith('jsr:')) {
delete pkg.dependencies[dep];
}
}
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
"
- name: Publish to JSR
# Have to use --allow-dirty because we modified package.json in the previous step
run: pnpm dlx jsr publish --allow-dirty

View file

@ -9,21 +9,18 @@ jobs:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- name: Setup repo - uses: actions/checkout@v4
uses: actions/checkout@v4 - name: Setup Deno
- uses: pnpm/action-setup@v4 uses: denoland/setup-deno@v2
with: with:
run_install: false deno-version: v2.x
# Setup .npmrc file to publish to npm # Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: "24.x" node-version: "22.x"
cache: "pnpm"
registry-url: "https://registry.npmjs.org" registry-url: "https://registry.npmjs.org"
- run: pnpm install - run: deno run -A scripts/build_npm.ts
- run: pnpm build - run: cd npm && npm ci
- run: pnpm test - run: cd npm && npm publish --provenance --access public
# --no-git-checks because of https://github.com/pnpm/pnpm/issues/5894.
- run: pnpm publish --provenance --access public --no-git-checks
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -1,32 +0,0 @@
name: Test
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Setup repo
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
run_install: false
- uses: actions/setup-node@v4
with:
node-version: "24.x"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Run tests
run: pnpm test

3
.gitignore vendored
View file

@ -1,3 +1,2 @@
npm
.DS_Store .DS_Store
node_modules
dist

View file

@ -3,6 +3,25 @@
// For a full list of overridable settings, and general information on folder-specific settings, // For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings // see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings
{ {
"formatter": "prettier", "languages": {
"TypeScript": {
"language_servers": ["deno", "!typescript-language-server", "!eslint"],
"formatter": {
"external": {
"command": "deno",
"arguments": ["fmt", "-"]
}
}
},
"TSX": {
"language_servers": ["deno", "!typescript-language-server", "!eslint"],
"formatter": {
"external": {
"command": "deno",
"arguments": ["fmt", "-"]
}
}
}
},
"format_on_save": "on" "format_on_save": "on"
} }

View file

@ -1,6 +1,10 @@
[ [
{ {
"label": "Test", "label": "Test",
"command": "pnpm test" "command": "deno test"
},
{
"label": "Test - Watch",
"command": "deno test --watch"
} }
] ]

View file

@ -1,5 +1,5 @@
The MIT License (MIT) The MIT License (MIT)
Copyright © 2025 Shibo Lyu <hi@lao.sb> Copyright © 2024 Shibo Lyu <hi@lao.sb>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View file

@ -1,5 +0,0 @@
import { defineBuildConfig } from "obuild/config";
export default defineBuildConfig({
entries: ["src/mod.ts"],
});

View file

@ -1,11 +1,9 @@
{ {
"name": "@textplace/core", "name": "@textplace/core",
"version": "0.5.0", "version": "0.4.0",
"exports": "./src/mod.ts", "exports": "./mod.ts",
"imports": { "imports": {
"@std/cli": "jsr:@std/cli@1" "@deno/dnt": "jsr:@deno/dnt@^0.41.3",
}, "@std/cli": "jsr:@std/cli@^1.0.11"
"publish": {
"include": ["src/**/*.ts", "LICENSE", "README.md"]
} }
} }

162
deno.lock generated Normal file
View file

@ -0,0 +1,162 @@
{
"version": "4",
"specifiers": {
"jsr:@david/code-block-writer@^13.0.2": "13.0.3",
"jsr:@deno/cache-dir@~0.10.3": "0.10.3",
"jsr:@deno/dnt@~0.41.3": "0.41.3",
"jsr:@std/assert@0.223": "0.223.0",
"jsr:@std/assert@0.226": "0.226.0",
"jsr:@std/bytes@0.223": "0.223.0",
"jsr:@std/cli@^1.0.11": "1.0.11",
"jsr:@std/fmt@0.223": "0.223.0",
"jsr:@std/fmt@1": "1.0.3",
"jsr:@std/fs@0.223": "0.223.0",
"jsr:@std/fs@1": "1.0.6",
"jsr:@std/fs@~0.229.3": "0.229.3",
"jsr:@std/io@0.223": "0.223.0",
"jsr:@std/path@0.223": "0.223.0",
"jsr:@std/path@1": "1.0.8",
"jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1",
"jsr:@std/path@^1.0.8": "1.0.8",
"jsr:@std/path@~0.225.2": "0.225.2",
"jsr:@ts-morph/bootstrap@0.24": "0.24.0",
"jsr:@ts-morph/common@0.24": "0.24.0"
},
"jsr": {
"@david/code-block-writer@13.0.3": {
"integrity": "f98c77d320f5957899a61bfb7a9bead7c6d83ad1515daee92dbacc861e13bb7f"
},
"@deno/cache-dir@0.10.3": {
"integrity": "eb022f84ecc49c91d9d98131c6e6b118ff63a29e343624d058646b9d50404776",
"dependencies": [
"jsr:@std/fmt@0.223",
"jsr:@std/fs@0.223",
"jsr:@std/io",
"jsr:@std/path@0.223"
]
},
"@deno/dnt@0.41.3": {
"integrity": "b2ef2c8a5111eef86cb5bfcae103d6a2938e8e649e2461634a7befb7fc59d6d2",
"dependencies": [
"jsr:@david/code-block-writer",
"jsr:@deno/cache-dir",
"jsr:@std/fmt@1",
"jsr:@std/fs@1",
"jsr:@std/path@1",
"jsr:@ts-morph/bootstrap"
]
},
"@std/assert@0.223.0": {
"integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24"
},
"@std/assert@0.226.0": {
"integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3"
},
"@std/bytes@0.223.0": {
"integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8"
},
"@std/cli@1.0.11": {
"integrity": "ec219619fdcd31bcf0d8e53bee1e2706ec9a02f70255365a094f69755dadd340"
},
"@std/fmt@0.223.0": {
"integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208"
},
"@std/fmt@1.0.3": {
"integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f"
},
"@std/fs@0.223.0": {
"integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c"
},
"@std/fs@0.229.3": {
"integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb",
"dependencies": [
"jsr:@std/path@1.0.0-rc.1"
]
},
"@std/fs@1.0.6": {
"integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2",
"dependencies": [
"jsr:@std/path@^1.0.8"
]
},
"@std/io@0.223.0": {
"integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1",
"dependencies": [
"jsr:@std/assert@0.223",
"jsr:@std/bytes"
]
},
"@std/path@0.223.0": {
"integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989",
"dependencies": [
"jsr:@std/assert@0.223"
]
},
"@std/path@0.225.2": {
"integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506",
"dependencies": [
"jsr:@std/assert@0.226"
]
},
"@std/path@1.0.0-rc.1": {
"integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6"
},
"@std/path@1.0.8": {
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
},
"@ts-morph/bootstrap@0.24.0": {
"integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060",
"dependencies": [
"jsr:@ts-morph/common"
]
},
"@ts-morph/common@0.24.0": {
"integrity": "12b625b8e562446ba658cdbe9ad77774b4bd96b992ae8bd34c60dbf24d06c1f3",
"dependencies": [
"jsr:@std/fs@~0.229.3",
"jsr:@std/path@~0.225.2"
]
}
},
"remote": {
"https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975",
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
"https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293",
"https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7",
"https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74",
"https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd",
"https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff",
"https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46",
"https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b",
"https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c",
"https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491",
"https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68",
"https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3",
"https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7",
"https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29",
"https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a",
"https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a",
"https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8",
"https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693",
"https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31",
"https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5",
"https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8",
"https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb",
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
"https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47",
"https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68",
"https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3",
"https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73",
"https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19",
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
"https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6",
"https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2",
"https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e"
},
"workspace": {
"dependencies": [
"jsr:@deno/dnt@~0.41.3",
"jsr:@std/cli@^1.0.11"
]
}
}

View file

@ -10,6 +10,5 @@ export function getCharacterWidth(ch: string): number {
); );
} }
// TODO: Properly fix this. return unicodeWidth(ch);
return Math.min(unicodeWidth(ch), 2);
} }

View file

View file

@ -1,36 +0,0 @@
{
"name": "@textplace/core",
"version": "0.5.0",
"description": "The core logic of TextPlace.",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/TextPlace/CoreTextPlace"
},
"bugs": {
"url": "https://github.com/TextPlace/CoreTextPlace/issues"
},
"scripts": {
"test": "vitest",
"build": "obuild"
},
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/mod.d.ts",
"import": "./dist/mod.mjs"
}
},
"dependencies": {
"@std/cli": "jsr:^1.0.24"
},
"devDependencies": {
"obuild": "^0.4.3",
"prettier": "^3.7.2",
"typescript": "^5.9.3",
"vitest": "^4.0.14"
},
"packageManager": "pnpm@10.23.0"
}

1917
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

36
scripts/build_npm.ts Normal file
View file

@ -0,0 +1,36 @@
import { build, emptyDir } from "@deno/dnt";
import denoJson from "../deno.json" with { type: "json" };
await emptyDir("./npm");
await build({
entryPoints: ["./mod.ts"],
outDir: "./npm",
shims: {
// see JS docs for overview and more options
deno: true,
},
compilerOptions: {
lib: ["ES2022"],
},
package: {
// package.json properties
name: "@textplace/core",
version: denoJson.version,
description: "The core logic of TextPlace.",
license: "MIT",
repository: {
type: "git",
url: "https://github.com/TextPlace/CoreTextPlace",
},
bugs: {
url: "https://github.com/TextPlace/CoreTextPlace/issues",
},
},
postBuild() {
// steps to run after building and before running the tests
Deno.copyFileSync("LICENSE", "npm/LICENSE");
Deno.copyFileSync("README.md", "npm/README.md");
},
});

View file

@ -1,19 +1,22 @@
import { describe, it, expect } from "vitest"; import {
assert,
assertEquals,
} from "https://deno.land/std@0.224.0/assert/mod.ts";
import { import {
createBoard, createBoard,
getSectionOnBoard, getSectionOnBoard,
renderFullBoard, renderFullBoard,
} from "../src/logic/board.ts"; } from "../logic/board.ts";
import type { BoardData } from "../src/types/board.ts"; import type { BoardData } from "../types/board.ts";
import { checkFullBoard } from "./checkFullBoard.ts"; import { checkFullBoard } from "./checkFullBoard.ts";
import { locateSection } from "../src/logic/board.ts"; import { locateSection } from "../logic/board.ts";
import { applyChangeOnBoard } from "../src/logic/board.ts"; import { applyChangeOnBoard } from "../logic/board.ts";
describe("board", () => { Deno.test("board", async (t) => {
let board: BoardData | undefined; let board: BoardData | undefined;
it("createBoard", () => { await t.step("createBoard", () => {
board = createBoard({ board = createBoard({
xSections: 3, xSections: 3,
ySections: 3, ySections: 3,
@ -26,96 +29,96 @@ describe("board", () => {
}); });
// Sections are created on demand. // Sections are created on demand.
expect(board.sections.length).toBe(0); assertEquals(board.sections.length, 0);
}); });
it("locateSection", () => { await t.step("locateSection", () => {
expect(board).toBeDefined(); assert(board);
const { sx, sy } = locateSection({ x: 0, y: 0 }, board!.config); const { sx, sy } = locateSection({ x: 0, y: 0 }, board.config);
expect(sx).toBe(0); assertEquals(sx, 0);
expect(sy).toBe(0); assertEquals(sy, 0);
const { sx: sx2, sy: sy2 } = locateSection({ x: 4, y: 0 }, board!.config); const { sx: sx2, sy: sy2 } = locateSection({ x: 4, y: 0 }, board.config);
expect(sx2).toBe(1); assertEquals(sx2, 1);
expect(sy2).toBe(0); assertEquals(sy2, 0);
}); });
it("applyChangeOnBoard", () => { await t.step("applyChangeOnBoard", () => {
expect(board).toBeDefined(); assert(board);
applyChangeOnBoard({ x: 0, y: 0, ch: "A" }, board!); applyChangeOnBoard({ x: 0, y: 0, ch: "A" }, board);
applyChangeOnBoard({ x: 4, y: 0, ch: "B" }, board!); applyChangeOnBoard({ x: 4, y: 0, ch: "B" }, board);
applyChangeOnBoard({ x: 0, y: 3, ch: "C" }, board!); applyChangeOnBoard({ x: 0, y: 3, ch: "C" }, board);
applyChangeOnBoard({ x: 4, y: 3, ch: "D" }, board!); applyChangeOnBoard({ x: 4, y: 3, ch: "D" }, board);
applyChangeOnBoard({ x: 5, y: 3, ch: "E" }, board!); applyChangeOnBoard({ x: 5, y: 3, ch: "E" }, board);
expect(board!.sections[0][0].ch[0][0]).toBe("A"); assertEquals(board.sections[0][0].ch[0][0], "A");
expect(board!.sections[0][1].ch[0][0]).toBe("B"); assertEquals(board.sections[0][1].ch[0][0], "B");
expect(board!.sections[1][0].ch[0][0]).toBe("C"); assertEquals(board.sections[1][0].ch[0][0], "C");
expect(board!.sections[1][1].ch[0]).toEqual(["D", "E", " ", " "]); assertEquals(board.sections[1][1].ch[0], ["D", "E", " ", " "]);
applyChangeOnBoard({ x: 0, y: 1, ch: "你" }, board!); applyChangeOnBoard({ x: 0, y: 1, ch: "你" }, board);
applyChangeOnBoard({ x: 4, y: 2, ch: "好" }, board!); applyChangeOnBoard({ x: 4, y: 2, ch: "好" }, board);
applyChangeOnBoard({ x: 0, y: 4, ch: "嗎" }, board!); applyChangeOnBoard({ x: 0, y: 4, ch: "嗎" }, board);
applyChangeOnBoard({ x: 4, y: 4, ch: "嘛" }, board!); applyChangeOnBoard({ x: 4, y: 4, ch: "嘛" }, board);
expect(board!.sections[0][0].ch[1][0]).toBe("你"); assertEquals(board.sections[0][0].ch[1][0], "你");
expect(board!.sections[0][1].ch[2][0]).toBe("好"); assertEquals(board.sections[0][1].ch[2][0], "好");
expect(board!.sections[1][0].ch[1][0]).toBe("嗎"); assertEquals(board.sections[1][0].ch[1][0], "嗎");
expect(board!.sections[1][1].ch[1]).toEqual(["嘛", " ", " ", " "]); assertEquals(board.sections[1][1].ch[1], ["嘛", " ", " ", " "]);
applyChangeOnBoard({ x: 5, y: 4, ch: "啊" }, board!); applyChangeOnBoard({ x: 5, y: 4, ch: "啊" }, board);
expect(board!.sections[1][1].ch[1]).toEqual(["啊", " ", " ", " "]); assertEquals(board.sections[1][1].ch[1], ["啊", " ", " ", " "]);
}); });
it("getSectionOnBoard: existing section", () => { await t.step("getSectionOnBoard: existing section", () => {
expect(board).toBeDefined(); assert(board);
const section = getSectionOnBoard({ sx: 1, sy: 1 }, board!, { const section = getSectionOnBoard({ sx: 1, sy: 1 }, board, {
readOnly: true, readOnly: true,
}); });
expect(section.ch[0]).toEqual(["D", "E", " ", " "]); assertEquals(section.ch[0], ["D", "E", " ", " "]);
expect(section.color[0][0]).toBe("F"); assertEquals(section.color[0][0], "F");
expect(section.bgColor[0][0]).toBe("0"); assertEquals(section.bgColor[0][0], "0");
expect(section.width[0]).toEqual([1, 1, 1, 1]); assertEquals(section.width[0], [1, 1, 1, 1]);
}); });
it("getSectionOnBoard: non-existing row", () => { await t.step("getSectionOnBoard: non-existing row", () => {
expect(board).toBeDefined(); assert(board);
const section = getSectionOnBoard({ sx: 1, sy: 2 }, board!, { const section = getSectionOnBoard({ sx: 1, sy: 2 }, board, {
readOnly: true, readOnly: true,
}); });
expect(section.ch[0][0]).toBe(" "); assertEquals(section.ch[0][0], " ");
expect(section.color[0][0]).toBe("F"); assertEquals(section.color[0][0], "F");
expect(section.bgColor[0][0]).toBe("0"); assertEquals(section.bgColor[0][0], "0");
expect(section.width[0][0]).toBe(1); assertEquals(section.width[0][0], 1);
}); });
it("getSectionOnBoard: non-existing section", () => { await t.step("getSectionOnBoard: non-existing section", () => {
expect(board).toBeDefined(); assert(board);
const section = getSectionOnBoard({ sx: 2, sy: 1 }, board!, { const section = getSectionOnBoard({ sx: 2, sy: 1 }, board, {
readOnly: true, readOnly: true,
}); });
expect(section.ch[0][0]).toBe(" "); assertEquals(section.ch[0][0], " ");
expect(section.color[0][0]).toBe("F"); assertEquals(section.color[0][0], "F");
expect(section.bgColor[0][0]).toBe("0"); assertEquals(section.bgColor[0][0], "0");
expect(section.width[0][0]).toBe(1); assertEquals(section.width[0][0], 1);
}); });
it("renderFullBoard", () => { await t.step("renderFullBoard", () => {
expect(board).toBeDefined(); assert(board);
const rendered = renderFullBoard(board!); const rendered = renderFullBoard(board);
checkFullBoard(rendered); checkFullBoard(rendered);
}); });
it("on-demand creation: only changed sections are saved", () => { await t.step("on-demand creation: only changed sections are saved", () => {
expect(board).toBeDefined(); assert(board);
expect(board!.sections[2]).toBeUndefined(); assertEquals(board.sections[2], undefined);
expect(board!.sections[0][2]).toBeUndefined(); assertEquals(board.sections[0][2], undefined);
}); });
}); });

View file

@ -1,40 +1,37 @@
import { it, expect } from "vitest"; import {
assertEquals,
assertThrows,
} from "https://deno.land/std@0.224.0/assert/mod.ts";
import { getCharacterWidth } from "../src/mod.ts"; import { getCharacterWidth } from "../mod.ts";
it("getCharacterWidth ASCII", () => { Deno.test("getCharacterWidth ASCII", () => {
expect(getCharacterWidth("a")).toBe(1); assertEquals(getCharacterWidth("a"), 1);
expect(getCharacterWidth("A")).toBe(1); assertEquals(getCharacterWidth("A"), 1);
expect(getCharacterWidth("1")).toBe(1); assertEquals(getCharacterWidth("1"), 1);
expect(getCharacterWidth("@")).toBe(1); assertEquals(getCharacterWidth("@"), 1);
expect(getCharacterWidth(" ")).toBe(1); assertEquals(getCharacterWidth(" "), 1);
expect(() => getCharacterWidth("")).toThrow(); assertThrows(() => getCharacterWidth(""));
expect(() => getCharacterWidth("ab")).toThrow(); assertThrows(() => getCharacterWidth("ab"));
}); });
it("getCharacterWidth CJK", () => { Deno.test("getCharacterWidth CJK", () => {
expect(getCharacterWidth("你")).toBe(2); assertEquals(getCharacterWidth("你"), 2);
expect(getCharacterWidth("好")).toBe(2); assertEquals(getCharacterWidth("好"), 2);
expect(getCharacterWidth("吗")).toBe(2); assertEquals(getCharacterWidth("吗"), 2);
expect(getCharacterWidth("ガ")).toBe(2); assertEquals(getCharacterWidth("ガ"), 2);
expect(getCharacterWidth("ギ")).toBe(2); assertEquals(getCharacterWidth("ギ"), 2);
expect(getCharacterWidth("グ")).toBe(2); assertEquals(getCharacterWidth("グ"), 2);
expect(getCharacterWidth("ソ")).toBe(2); assertEquals(getCharacterWidth("ソ"), 2);
expect(getCharacterWidth("")).toBe(2); assertEquals(getCharacterWidth(""), 2);
expect(getCharacterWidth("")).toBe(2); assertEquals(getCharacterWidth(""), 2);
expect(() => getCharacterWidth("你好")).toThrow(); assertThrows(() => getCharacterWidth("你好"));
expect(() => getCharacterWidth("ヨスガノ")).toThrow(); assertThrows(() => getCharacterWidth("ヨスガノ"));
}); });
it("getCharacterWidth Emoji", () => { Deno.test("getCharacterWidth previously faulty cases", () => {
expect(getCharacterWidth("👋")).toBe(2); assertEquals(getCharacterWidth("𤲶"), 2);
expect(getCharacterWidth("🌲️")).toBe(2);
expect(getCharacterWidth("👨‍👩‍👧‍👦")).toBe(2);
});
it("getCharacterWidth previously faulty cases", () => {
expect(getCharacterWidth("𤲶")).toBe(2);
}); });

View file

@ -1,5 +1,5 @@
import { getCharacterWidth } from "../src/logic/character.ts"; import { getCharacterWidth } from "../logic/character.ts";
import type { FullBoard } from "../src/types/board.ts"; import type { FullBoard } from "../types/board.ts";
function isCorrectWidth(cWd: number, cCh: string): boolean { function isCorrectWidth(cWd: number, cCh: string): boolean {
return getCharacterWidth(cCh) === cWd; return getCharacterWidth(cCh) === cWd;

View file

@ -1,13 +1,17 @@
import { describe, it, expect } from "vitest"; import {
assert,
assertEquals,
assertThrows,
} from "https://deno.land/std@0.224.0/assert/mod.ts";
import { applyChange, createSection } from "../src/logic/section.ts"; import { applyChange, createSection } from "../logic/section.ts";
import type { SectionData } from "../src/types/section.ts"; import type { SectionData } from "../types/section.ts";
describe("section", () => { Deno.test("section", async (t) => {
let section: SectionData | undefined; let section: SectionData | undefined;
it("createSection non-lcm", () => { await t.step("createSection non-lcm", () => {
expect(() => { assertThrows(() => {
createSection( createSection(
{ sx: 0, sy: 0 }, { sx: 0, sy: 0 },
{ {
@ -21,10 +25,10 @@ describe("section", () => {
defaultWidth: 1, defaultWidth: 1,
}, },
); );
}).toThrow(); });
}); });
it("createSection non-origin section", () => { await t.step("createSection non-origin section", () => {
section = createSection( section = createSection(
{ sx: 1, sy: 1 }, { sx: 1, sy: 1 },
{ {
@ -39,11 +43,11 @@ describe("section", () => {
}, },
); );
expect(section.offsetX).toBe(4); assertEquals(section.offsetX, 4);
expect(section.offsetY).toBe(3); assertEquals(section.offsetY, 3);
}); });
it("createSection", () => { await t.step("createSection", () => {
section = createSection( section = createSection(
{ sx: 0, sy: 0 }, { sx: 0, sy: 0 },
{ {
@ -58,8 +62,8 @@ describe("section", () => {
}, },
); );
expect(section.offsetX).toBe(0); assertEquals(section.offsetX, 0);
expect(section.offsetY).toBe(0); assertEquals(section.offsetY, 0);
function assertSectionContent<T>( function assertSectionContent<T>(
content: T[][], content: T[][],
@ -67,11 +71,11 @@ describe("section", () => {
columnCount: number, columnCount: number,
value: T, value: T,
) { ) {
expect(content.length).toBe(rowCount); assertEquals(content.length, rowCount);
for (const row of content) { for (const row of content) {
expect(row.length).toBe(columnCount); assertEquals(row.length, columnCount);
for (const item of row) { for (const item of row) {
expect(item).toBe(value); assertEquals(item, value);
} }
} }
} }
@ -82,44 +86,44 @@ describe("section", () => {
assertSectionContent(section.width, 3, 4, 1); assertSectionContent(section.width, 3, 4, 1);
}); });
it("applyChange 1-width", () => { await t.step("applyChange 1-width", () => {
expect(section).toBeDefined(); assert(section);
applyChange({ x: 0, y: 0, ch: "t" }, section!); applyChange({ x: 0, y: 0, ch: "t" }, section);
expect(section!.ch[0]).toEqual(["t", " ", " ", " "]); assertEquals(section.ch[0], ["t", " ", " ", " "]);
expect(section!.ch[1]).toEqual([" ", " ", " ", " "]); assertEquals(section.ch[1], [" ", " ", " ", " "]);
expect(section!.width[0]).toEqual([1, 1, 1, 1]); assertEquals(section.width[0], [1, 1, 1, 1]);
}); });
it("applyChange 1-width at odd position", () => { await t.step("applyChange 1-width at odd position", () => {
expect(section).toBeDefined(); assert(section);
applyChange({ x: 1, y: 0, ch: "t" }, section!); applyChange({ x: 1, y: 0, ch: "t" }, section);
expect(section!.ch[0]).toEqual(["t", "t", " ", " "]); assertEquals(section.ch[0], ["t", "t", " ", " "]);
expect(section!.width[0]).toEqual([1, 1, 1, 1]); assertEquals(section.width[0], [1, 1, 1, 1]);
}); });
it("applyChange 2-width at a correct position", () => { await t.step("applyChange 2-width at a correct position", () => {
expect(section).toBeDefined(); assert(section);
applyChange({ x: 0, y: 0, ch: "あ" }, section!); applyChange({ x: 0, y: 0, ch: "あ" }, section);
expect(section!.ch[0]).toEqual(["あ", "t", " ", " "]); assertEquals(section.ch[0], ["あ", "t", " ", " "]);
expect(section!.width[0]).toEqual([2, 1, 1, 1]); assertEquals(section.width[0], [2, 1, 1, 1]);
}); });
it("applyChange 2-width at an alternate position", () => { await t.step("applyChange 2-width at an alternate position", () => {
expect(section).toBeDefined(); assert(section);
applyChange({ x: 1, y: 0, ch: "あ" }, section!); applyChange({ x: 1, y: 0, ch: "あ" }, section);
expect(section!.ch[0]).toEqual(["あ", "t", " ", " "]); assertEquals(section.ch[0], ["あ", "t", " ", " "]);
expect(section!.width[0]).toEqual([2, 1, 1, 1]); assertEquals(section.width[0], [2, 1, 1, 1]);
}); });
it("applyChange incorrect section", () => { await t.step("applyChange incorrect section", () => {
expect(section).toBeDefined(); assertThrows(() => {
assert(section);
expect(() => { applyChange({ x: 6, y: 3, ch: "あ" }, section);
applyChange({ x: 6, y: 3, ch: "あ" }, section!); });
}).toThrow();
}); });
}); });