This commit is contained in:
dswbx
2025-03-28 15:17:04 +01:00
parent 9e3c081e50
commit 7ed5db5eaa
16 changed files with 377 additions and 73 deletions
+2
View File
@@ -0,0 +1,2 @@
playwright-report
test-results
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

+22
View File
@@ -0,0 +1,22 @@
// @ts-check
import { test, expect } from "@playwright/test";
import { testIds } from "../src/ui/lib/config";
test("start page has expected title", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/BKND/);
});
test("start page has expected heading", async ({ page }) => {
await page.goto("/");
// Example of checking if a heading with "No entity selected" exists and is visible
const heading = page.getByRole("heading", { name: /No entity selected/i });
await expect(heading).toBeVisible();
});
test("modal opens on button click", async ({ page }) => {
await page.goto("/");
await page.getByTestId(testIds.data.btnCreateEntity).click();
await expect(page.getByRole("dialog")).toBeVisible();
});
+51
View File
@@ -0,0 +1,51 @@
// @ts-check
import { test, expect } from "@playwright/test";
import { testIds } from "../src/ui/lib/config";
import type { SchemaResponse } from "../src/modules/server/SystemController";
// Annotate entire file as serial.
test.describe.configure({ mode: "serial" });
test("can enable media", async ({ page }) => {
await page.goto("/media/settings");
// enable
const enableToggle = page.locator("css=button#enabled");
if ((await enableToggle.getAttribute("aria-checked")) !== "true") {
await expect(enableToggle).toBeVisible();
await enableToggle.click();
await expect(enableToggle).toHaveAttribute("aria-checked", "true");
// select local
const localAdapter = page.locator("css=button#adapter-local");
await expect(localAdapter).toBeVisible();
await localAdapter.click();
// save
const saveBtn = page.getByRole("button", { name: /Update/i });
await expect(saveBtn).toBeVisible();
// intercept network request, wait for it to finish and get the response
const [request] = await Promise.all([
page.waitForRequest((request) => request.url().includes("api/system/schema")),
saveBtn.click(),
]);
const response = await request.response();
expect(response?.status(), "fresh config 200").toBe(200);
const body = (await response?.json()) as SchemaResponse;
expect(body.config.media.enabled, "media is enabled").toBe(true);
expect(body.config.media.adapter.type, "adapter is local").toBe("local");
}
});
test("can upload a file", async ({ page }) => {
await page.goto("/media");
// check any text to contain "Upload files"
await expect(page.getByText(/Upload files/i)).toBeVisible();
// upload a file from disk
// Start waiting for file chooser before clicking. Note no await.
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByText("Upload file").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles("./e2e/assets/image1.png");
});
+14 -2
View File
@@ -15,7 +15,7 @@
},
"scripts": {
"dev": "vite",
"test": "ALL_TESTS=1 bun test --bail",
"test": "bun run test:unit && bun run test:e2e",
"test:all": "bun run test && bun run test:node",
"test:bun": "ALL_TESTS=1 bun test --bail",
"test:node": "tsx --test $(find . -type f -name '*.native-spec.ts')",
@@ -32,7 +32,13 @@
"updater": "bun x npm-check-updates -ui",
"cli": "LOCAL=1 bun src/cli/index.ts",
"prepublishOnly": "bun run types && bun run test && bun run build:all && cp ../README.md ./",
"postpublish": "rm -f README.md"
"postpublish": "rm -f README.md",
"test:unit": "vitest",
"test:unit:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug",
"test:e2e:report": "playwright show-report"
},
"license": "FSL-1.1-MIT",
"dependencies": {
@@ -75,18 +81,23 @@
"@libsql/kysely-libsql": "^0.4.1",
"@mantine/modals": "^7.17.1",
"@mantine/notifications": "^7.17.1",
"@playwright/test": "^1.51.1",
"@rjsf/core": "5.22.2",
"@tabler/icons-react": "3.18.0",
"@tailwindcss/postcss": "^4.0.12",
"@tailwindcss/vite": "^4.0.12",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/node": "^22.13.10",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.9",
"autoprefixer": "^10.4.21",
"clsx": "^2.1.1",
"dotenv": "^16.4.7",
"jotai": "^2.12.2",
"jsdom": "^26.0.0",
"kysely-d1": "^0.3.0",
"open": "^10.1.0",
"openapi-types": "^12.1.3",
@@ -108,6 +119,7 @@
"tsx": "^4.19.3",
"vite": "^6.2.1",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.9",
"wouter": "^3.6.0"
},
"optionalDependencies": {
+37
View File
@@ -0,0 +1,37 @@
import { defineConfig, devices } from "@playwright/test";
const baseUrl = process.env.TEST_URL || "http://localhost:28623";
const startCommand = process.env.START_COMMAND || "bun run dev";
export default defineConfig({
testDir: "./e2e",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: "html",
use: {
baseURL: baseUrl,
trace: "on-first-retry",
video: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
/* {
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
}, */
],
webServer: {
command: startCommand,
url: baseUrl,
reuseExistingServer: !process.env.CI,
},
});
@@ -18,6 +18,7 @@ import { Controller } from "modules/Controller";
import {
MODULE_NAMES,
type ModuleConfigs,
type ModuleSchemas,
type ModuleKey,
getDefaultConfig,
} from "modules/ModuleManager";
@@ -36,6 +37,12 @@ export type ConfigUpdate<Key extends ModuleKey = ModuleKey> = {
export type ConfigUpdateResponse<Key extends ModuleKey = ModuleKey> =
| ConfigUpdate<Key>
| { success: false; type: "type-invalid" | "error" | "unknown"; error?: any; errors?: any };
export type SchemaResponse = {
version: string;
schema: ModuleSchemas;
config: ModuleConfigs;
permissions: string[];
};
export class SystemController extends Controller {
constructor(private readonly app: App) {
+12
View File
@@ -0,0 +1,12 @@
import { describe, it, expect } from "vitest";
describe("Example Test Suite", () => {
it("should pass basic arithmetic", () => {
expect(1 + 1).toBe(2);
});
it("should handle async operations", async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
});
+8
View File
@@ -0,0 +1,8 @@
import "@testing-library/jest-dom";
import { afterEach } from "vitest";
import { cleanup } from "@testing-library/react";
// Automatically cleanup after each test
afterEach(() => {
cleanup();
});
+1
View File
@@ -36,6 +36,7 @@ export type BaseProps = {
size?: keyof typeof sizes;
variant?: keyof typeof styles;
labelClassName?: string;
"data-testid"?: string;
};
const Base = ({
+8
View File
@@ -0,0 +1,8 @@
export const config = {};
export const testIds = {
data: {
btnCreateEntity: "data-btns-create-entity",
},
media: {},
};
+2
View File
@@ -21,6 +21,7 @@ import { Link, isLinkActive } from "ui/components/wouter/Link";
import { useBrowserTitle } from "ui/hooks/use-browser-title";
import * as AppShell from "ui/layouts/AppShell/AppShell";
import { routes, useNavigate, useRouteNavigate } from "ui/lib/routes";
import { testIds } from "ui/lib/config";
export function DataRoot({ children }) {
// @todo: settings routes should be centralized
@@ -269,6 +270,7 @@ export function DataEmpty() {
}}
primary={{
children: "Create entity",
"data-testid": testIds.data.btnCreateEntity,
onClick: $data.modals.createEntity,
}}
/>
@@ -139,6 +139,7 @@ function Adapters() {
<Button
key={i}
onClick={() => ctx.select(i)}
id={`adapter-${schema.properties.type.const}`}
variant={ctx.selected === i ? "primary" : "outline"}
className={twMerge(
"flex flex-row items-center justify-center gap-3 border",
+1 -1
View File
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
import { serveStatic } from "@hono/node-server/serve-static";
import { showRoutes } from "hono/dev";
import { App, registries } from "./src";
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
import { StorageLocalAdapter } from "./src/adapter/node/storage/StorageLocalAdapter";
import { EntityManager, LibsqlConnection } from "data";
import { __bknd } from "modules/ModuleManager";
+23
View File
@@ -0,0 +1,23 @@
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./src/test/setup.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: [
"node_modules/",
"src/test/setup.ts",
"**/*.d.ts",
"**/*.test.ts",
"**/*.config.ts",
],
},
},
});