From 018a60387b83745c622ee3f4e4f772fecf0a02ce Mon Sep 17 00:00:00 2001 From: Christian Hugo Date: Fri, 22 Aug 2025 19:54:40 +0200 Subject: [PATCH] basic test for running and building react views --- .github/workflows/nodejs.yml | 65 +++++++++++++++++++++++++++++++++++ package.json | 6 +++- tests/backup.zip | Bin 0 -> 16735 bytes tests/build.test.js | 37 ++++++++++++++++++++ tests/view.test.js | 45 ++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/nodejs.yml create mode 100644 tests/backup.zip create mode 100644 tests/build.test.js create mode 100644 tests/view.test.js diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..238ed1f --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,65 @@ +# This workflow installs saltcorn from npm and runs the plugin tests. + +name: Node.js CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 25 + services: + # Label used to access the service container + postgres: + # Docker Hub image + image: postgres + # Provide the password for postgres + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: saltcorn_test + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: "22.x" + - run: npm install -g @saltcorn/cli@1.3.1-beta.11 + env: + CI: true + PGHOST: localhost + PGUSER: postgres + PGDATABASE: saltcorn_test + PGPASSWORD: postgres + - run: saltcorn info + env: + CI: true + PGHOST: localhost + PGUSER: postgres + PGDATABASE: saltcorn_test + PGPASSWORD: postgres + - run: saltcorn add-schema -f + env: + CI: true + PGHOST: localhost + PGUSER: postgres + PGDATABASE: saltcorn_test + PGPASSWORD: postgres + - run: saltcorn dev:plugin-test -d $PWD -f backup.zip + env: + CI: true + PGHOST: localhost + PGUSER: postgres + PGDATABASE: saltcorn_test + PGPASSWORD: postgres diff --git a/package.json b/package.json index fc56be6..d8da964 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build_view_dev": "webpack --mode development --config user-code/webpack.config.js", "build_main": "webpack --mode production --config main-code/webpack.config.js", "build_main_dev": "webpack --mode development --config main-code/webpack.config.js", - "eslint": "eslint ./app-code" + "eslint": "eslint ./app-code", + "test": "jest tests --runInBand" }, "dependencies": { "@saltcorn/markup": "^0.2.0", @@ -29,6 +30,9 @@ "style-loader": "4.0.0", "css-loader": "7.1.2" }, + "devDependencies": { + "jest": "^29.7.0" + }, "author": "Christian Hugo", "license": "MIT", "repository": "github:saltcorn/react", diff --git a/tests/backup.zip b/tests/backup.zip new file mode 100644 index 0000000000000000000000000000000000000000..b9df466a6abdbfc34feda16a85ca2269896a09ef GIT binary patch literal 16735 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 McBI+kKKkqb08KgVLI3~& literal 0 HcmV?d00001 diff --git a/tests/build.test.js b/tests/build.test.js new file mode 100644 index 0000000..38adc59 --- /dev/null +++ b/tests/build.test.js @@ -0,0 +1,37 @@ +const { getState } = require("@saltcorn/data/db/state"); +const View = require("@saltcorn/data/models/view"); +const { mockReqRes } = require("@saltcorn/data/tests/mocks"); +const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals"); + +getState().registerPlugin("base", require("@saltcorn/data/base-plugin")); +getState().registerPlugin("@saltcorn/react", require("..")); + +afterAll(require("@saltcorn/data/db").close); +beforeAll(async () => { + await getState().refresh(true); +}); + +describe("react view build tests", () => { + it("run tableless view", async () => { + const view = View.findOne({ name: "empty_react_view" }); + const body = { + user_code: ` +import React from "react"; +export default function App({ viewName, query }) { + return

Please write your React code here

; +};`, + build_mode: "development", + timestamp: undefined, + }; + await view.runRoute( + "build_user_code", + body, + mockReqRes.res, + { req: { ...mockReqRes.req, body }, res: mockReqRes.res }, + false, + ); + const stored = mockReqRes.getStored(); + expect(stored).toBeDefined(); + expect(stored).toEqual({ json: { notify_success: "Build successful" } }); + }); +}); diff --git a/tests/view.test.js b/tests/view.test.js new file mode 100644 index 0000000..31cc2ca --- /dev/null +++ b/tests/view.test.js @@ -0,0 +1,45 @@ +const { getState } = require("@saltcorn/data/db/state"); +const View = require("@saltcorn/data/models/view"); +const { mockReqRes } = require("@saltcorn/data/tests/mocks"); +const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals"); + +getState().registerPlugin("base", require("@saltcorn/data/base-plugin")); +getState().registerPlugin("@saltcorn/react", require("..")); + +afterAll(require("@saltcorn/data/db").close); +beforeAll(async () => { + await getState().refresh(true); +}); + +describe("react view run tests", () => { + it("run tableless view", async () => { + const view = View.findOne({ name: "default_react_view" }); + const result = await view.run({}, mockReqRes); + expect(result).toBeDefined(); + expect(result).toContain('
{ + const view = View.findOne({ name: "react_view_with_data" }); + const result = await view.run({}, mockReqRes); + expect(result).toBeDefined(); + expect(result).toContain('