Skip to content
Open
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
166 changes: 153 additions & 13 deletions desktop/frontend/bun.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion desktop/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.9.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tauri-apps/cli": "^2",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.2.10",
"@types/mathjs": "^9.4.1",
"@vitest/ui": "^4.0.16",
"jsdom": "^27.3.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "~5.6.2",
Expand Down
304 changes: 304 additions & 0 deletions desktop/frontend/tests/cell.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
import Cell from "$lib/components/Cell.svelte";
import { beforeEach, describe, test, type Mock, vi, expect } from "vitest";
import type { Cell as CellType } from "$lib/types";
import { flushSync, mount, unmount } from "svelte";
import userEvent from "@testing-library/user-event";
import { fireEvent } from "@testing-library/svelte"

type MouseEventCallback = (event: MouseEvent) => void;
type KeyboardEventCallback = (event: KeyboardEvent) => void;

describe("Cell", () => {
let grid: Record<string, CellType>;
let mockCell: CellType;
let handleEnterPress: Mock<KeyboardEventCallback>;
let handleMouseDown: Mock<MouseEventCallback>;
let handleMouseUp: Mock<MouseEventCallback>;

beforeEach(() => {
document.body.innerHTML = "";
grid = {};
mockCell = {
x: 1,
y: 1,
rawValue: "2",
displayValue: "2",
isSelected: false,
isEditing: false,
};
handleEnterPress = vi.fn();
handleMouseDown = vi.fn();
handleMouseUp = vi.fn();
});

test("通常セルはボタンとして表示される", () => {
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const button = document.querySelector("button");
expect(button).toBeTruthy();
expect(button?.textContent).toBe("2");
expect(button?.className).toContain("bg-white");

unmount(component);
});

test("選択されたセルは背景色が変わる", () => {
mockCell.isSelected = true;
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const button = document.querySelector("button");
expect(button?.className).toContain("bg-gray-200");

unmount(component);
});

test("列ヘッダーセルは列名を表示する", () => {
mockCell.x = 1;
mockCell.y = 0;
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const div = document.querySelector("div");
expect(div?.textContent?.trim()).toBe("A");

unmount(component);
});

test("行ヘッダーセルは行番号を表示する", () => {
mockCell.x = 0;
mockCell.y = 3;
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const div = document.querySelector("div");
expect(div?.textContent?.trim()).toBe("3");

unmount(component);
});

test("ダブルクリックで編集モードに入る", async () => {
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const button = document.querySelector("button");
expect(button).toBeTruthy();

if (button) {
fireEvent.dblClick(button)
}
flushSync()

const input = document.querySelector("input");
expect(input).toBeTruthy()
expect(input?.value).toBe("2");

unmount(component);
});

test("編集モードでEnterキーを押すと処理が実行される", () => {
mockCell.isEditing = true;
mockCell.rawValue = "5";
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const input = document.querySelector("input");
input?.dispatchEvent(
new KeyboardEvent("keydown", { key: "Enter", bubbles: true })
);
flushSync();

expect(handleEnterPress).toHaveBeenCalled();
expect(mockCell.displayValue).toBe("5");

unmount(component);
});

test("編集モードでEscapeキーを押すと編集モードが終了する", () => {
mockCell.isEditing = true;
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const input = document.querySelector("input");
input?.dispatchEvent(
new KeyboardEvent("keydown", { key: "Escape", bubbles: true })
);
flushSync();

expect(mockCell.isEditing).toBe(false);
expect(mockCell.isSelected).toBe(false);

unmount(component);
});

test("通常モードで文字キーを押すと編集モードに入り、その文字が入力される", () => {
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const button = document.querySelector("button");
button?.dispatchEvent(
new KeyboardEvent("keydown", { key: "a", bubbles: true })
);
flushSync();

expect(mockCell.isEditing).toBe(true);
expect(mockCell.rawValue).toBe("a");

unmount(component);
});

test("数式(=で始まる)が評価される", () => {
mockCell.isEditing = true;
mockCell.rawValue = "=2+3";
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const input = document.querySelector("input");
input?.dispatchEvent(
new KeyboardEvent("keydown", { key: "Enter", bubbles: true })
);
flushSync();

expect(mockCell.displayValue).toBe("5");

unmount(component);
});

test("数式のエラーは#ERRORと表示される", () => {
mockCell.isEditing = true;
mockCell.rawValue = "=invalid formula";
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const input = document.querySelector("input");
input?.dispatchEvent(
new KeyboardEvent("keydown", { key: "Enter", bubbles: true })
);
flushSync();

expect(mockCell.displayValue).toBe("#ERROR");

unmount(component);
});

test("マウスダウンイベントが呼ばれる", () => {
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const button = document.querySelector("button");
button?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));

expect(handleMouseDown).toHaveBeenCalled();

unmount(component);
});

test("マウスアップイベントが呼ばれる", () => {
const component = mount(Cell, {
target: document.body,
props: {
grid,
cell: mockCell,
onEnterPress: handleEnterPress,
onMouseDown: handleMouseDown,
onMouseUp: handleMouseUp,
},
});

const button = document.querySelector("button");
button?.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));

expect(handleMouseUp).toHaveBeenCalled();

unmount(component);
});
});
1 change: 1 addition & 0 deletions desktop/frontend/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"types": ["@testing-library/jest-dom"],
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
Expand Down
13 changes: 10 additions & 3 deletions desktop/frontend/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { defineConfig } from "vitest/config";
import { sveltekit } from "@sveltejs/kit/vite";
import { svelteTesting } from "@testing-library/svelte/vite"
import tailwindcss from "@tailwindcss/vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";

// @ts-expect-error process is a nodejs global
const host: string = process.env.TAURI_DEV_HOST;

// https://vitejs.dev/config/
export default defineConfig(async () => ({
resolve: process.env.VITEST ? {
resolve: process.env.VITEST
? {
conditions: ["browser"]
}
}
: undefined,
plugins: [sveltekit(), tailwindcss()],
test: {
environment: "jsdom",
setupFiles: ["./vitest-setup.ts"]
},
plugins: [sveltekit(), tailwindcss(), svelteTesting()],

// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
Expand Down
1 change: 1 addition & 0 deletions desktop/frontend/vitest-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom/vitest'