mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 08:06:00 +00:00
init e2e
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
playwright-report
|
||||
test-results
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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();
|
||||
});
|
||||
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
@@ -36,6 +36,7 @@ export type BaseProps = {
|
||||
size?: keyof typeof sizes;
|
||||
variant?: keyof typeof styles;
|
||||
labelClassName?: string;
|
||||
"data-testid"?: string;
|
||||
};
|
||||
|
||||
const Base = ({
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const config = {};
|
||||
|
||||
export const testIds = {
|
||||
data: {
|
||||
btnCreateEntity: "data-btns-create-entity",
|
||||
},
|
||||
media: {},
|
||||
};
|
||||
@@ -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
@@ -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";
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user