Coverage test
Summary: -- Reviewers: #reviewers Subscribers: jenkins-user Differential Revision: https://hub.sealcode.org/D1606
This commit is contained in:
parent
6b470fa709
commit
10ef8cd3d2
2
.gitignore
vendored
2
.gitignore
vendored
@ -49,3 +49,5 @@ lint-report.json
|
||||
/meili_data/
|
||||
!/meili_data/.keepme
|
||||
/src/colors.html
|
||||
coverage.json
|
||||
/test-results/
|
||||
|
42
convert-cobertura-to-arcanist.mjs
Normal file
42
convert-cobertura-to-arcanist.mjs
Normal file
@ -0,0 +1,42 @@
|
||||
import fs from "fs";
|
||||
import xml2js from "xml2js";
|
||||
|
||||
function convertCoberturaToArcanistJSON(coberturaXml) {
|
||||
const parser = new xml2js.Parser();
|
||||
return parser.parseStringPromise(coberturaXml).then((result) => {
|
||||
const coverage = {};
|
||||
|
||||
const classes = result.coverage.packages[0].package.flatMap(
|
||||
(pkg) => pkg.classes[0].class
|
||||
);
|
||||
|
||||
for (const cls of classes) {
|
||||
const filePath = cls.$.filename;
|
||||
const lines = cls.lines[0].line;
|
||||
|
||||
const lineCoverage = [];
|
||||
let maxLine = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
const num = parseInt(line.$.number, 10);
|
||||
const hits = parseInt(line.$.hits, 10);
|
||||
if (num > maxLine) maxLine = num;
|
||||
lineCoverage[num] = hits > 0 ? "C" : "U";
|
||||
}
|
||||
|
||||
// Fill gaps with 'N'
|
||||
for (let i = 1; i <= maxLine; i++) {
|
||||
if (!lineCoverage[i]) lineCoverage[i] = "N";
|
||||
}
|
||||
|
||||
const coverageStr = lineCoverage.slice(1).join("");
|
||||
coverage[filePath] = coverageStr;
|
||||
}
|
||||
return coverage;
|
||||
});
|
||||
}
|
||||
|
||||
const coberturaXml = fs.readFileSync("coverage/cobertura-coverage.xml", "utf8");
|
||||
convertCoberturaToArcanistJSON(coberturaXml).then((json) => {
|
||||
console.log(JSON.stringify(json));
|
||||
});
|
@ -4,6 +4,7 @@ services:
|
||||
image: mongo:4.4-bionic
|
||||
ports:
|
||||
- "127.0.0.1:${PORT:-2076}7:27017"
|
||||
|
||||
test:
|
||||
image: sealious-app:latest
|
||||
build:
|
||||
@ -12,14 +13,16 @@ services:
|
||||
volumes:
|
||||
- ./:/opt/sealious-app/
|
||||
- /tmp:/tmp
|
||||
- ./docker_node_modules:/opt/sealious-app/node_modules
|
||||
user: ${USER_ID:-1000}:${GROUP_ID:-1000}
|
||||
command: tail -f /dev/null # to keep it open
|
||||
|
||||
mailcatcher:
|
||||
image: schickling/mailcatcher:latest
|
||||
ports:
|
||||
- "127.0.0.1:${PORT:-108}2:1080"
|
||||
- "127.0.0.1:${PORT:-102}6:1025"
|
||||
user: ${USER_ID:-1000}:${GROUP_ID:-1000}
|
||||
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:v1.9
|
||||
ports:
|
||||
@ -30,3 +33,11 @@ services:
|
||||
MEILI_ENV: development
|
||||
MEILI_MASTER_KEY: ${MEILISEARCH_MASTER_KEY:-qwerty}
|
||||
user: ${USER_ID:-1000}:${GROUP_ID:-1000}
|
||||
|
||||
playwright:
|
||||
image: mcr.microsoft.com/playwright:v1.54.0-noble
|
||||
ports:
|
||||
- 127.0.0.1:3000:3000
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
command: npx -y playwright@1.54.0 run-server --port 3000 --host 0.0.0.0
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:21-bullseye-slim
|
||||
FROM node:22-bullseye-slim
|
||||
|
||||
ENV HOME=/opt/sealious-app
|
||||
|
||||
@ -10,11 +10,6 @@ RUN apt update
|
||||
RUN apt install -y git
|
||||
RUN apt install -y tmux
|
||||
|
||||
# playwright deps
|
||||
RUN apt-get update&& apt-get install -y --no-install-recommends libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libdrm2 libgbm1 libglib2.0-0 libnspr4 libnss3 libpango-1.0-0 libwayland-client0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 xvfb fonts-noto-color-emoji fonts-unifont libfontconfig1 libfreetype6 xfonts-cyrillic xfonts-scalable fonts-liberation fonts-ipafont-gothic fonts-wqy-zenhei fonts-tlwg-loma-otf fonts-freefont-ttf libcairo-gobject2 libdbus-glib-1-2 libgdk-pixbuf-2.0-0 libgtk-3-0 libharfbuzz0b libpangocairo-1.0-0 libx11-xcb1 libxcb-shm0 libxcursor1 libxi6 libxrender1 libxtst6 gstreamer1.0-libav gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good libegl1 libenchant-2-2 libepoxy0 libevdev2 libgles2 libglx0 libgstreamer-gl1.0-0 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 libgudev-1.0-0 libharfbuzz-icu0 libhyphen0 libicu67 libjpeg62-turbo liblcms2-2 libmanette-0.2-0 libnotify4 libopengl0 libopenjp2-7 libopus0 libpng16-16 libproxy1v5 libsecret-1-0 libsoup2.4-1 libwayland-egl1 libwayland-server0 libwebp6 libwebpdemux2 libwoff1 libxml2 libxslt1.1 libatomic1 libevent-2.1-7 python
|
||||
|
||||
RUN apt install wget
|
||||
|
||||
RUN chmod +x /tini
|
||||
ENTRYPOINT ["/tini", "--"]
|
||||
|
||||
|
@ -5,7 +5,6 @@ SEALIOUS_BASE_URL=$(cat .base_url)
|
||||
export SEALIOUS_BASE_URL
|
||||
|
||||
./npm.sh ci --no-progress --no-color
|
||||
./npm.sh run install-test-deps --no-progress --no-color
|
||||
./npm.sh run build --no-progress --no-color --ignore-scripts
|
||||
./npm.sh run typecheck --no-progress --no-color --ignore-scripts
|
||||
|
||||
@ -59,10 +58,16 @@ EOM
|
||||
|
||||
node -e "$parse_command" > lint-report.json
|
||||
|
||||
docker-compose up -d meilisearch mailcatcher
|
||||
docker-compose up -d meilisearch mailcatcher playwright
|
||||
|
||||
|
||||
CONTAINER_ID=$(docker compose run -d --user="$UID" \
|
||||
docker compose run --user="$UID" \
|
||||
-e "FORCE_COLOR=0" \
|
||||
test npx playwright install firefox
|
||||
|
||||
docker compose up -d test
|
||||
|
||||
docker compose exec -T --user="$UID" \
|
||||
-e "SEALIOUS_MONGO_PORT=27017" \
|
||||
-e "SEALIOUS_MONGO_HOST=db" \
|
||||
-e "SEALIOUS_PORT=8080" \
|
||||
@ -72,20 +77,7 @@ CONTAINER_ID=$(docker compose run -d --user="$UID" \
|
||||
-e "SEALIOUS_MAILCATCHER_API_PORT=1080" \
|
||||
-e "SEALIOUS_MAILCATCHER_SMTP_PORT=1025" \
|
||||
-e "FORCE_COLOR=0" \
|
||||
test npx playwright install firefox)
|
||||
test \
|
||||
npm run --ignore-scripts test-ci
|
||||
|
||||
CONTAINER_ID=$(docker compose run -d --user="$UID" \
|
||||
-e "SEALIOUS_MONGO_PORT=27017" \
|
||||
-e "SEALIOUS_MONGO_HOST=db" \
|
||||
-e "SEALIOUS_PORT=8080" \
|
||||
-e "SEALIOUS_BASE_URL=$SEALIOUS_BASE_URL" \
|
||||
-e "MEILISEARCH_HOST=http://meilisearch:7700" \
|
||||
-e "SEALIOUS_MAILCATCHER_HOST=mailcatcher" \
|
||||
-e "SEALIOUS_MAILCATCHER_API_PORT=1080" \
|
||||
-e "SEALIOUS_MAILCATCHER_SMTP_PORT=1025" \
|
||||
-e "FORCE_COLOR=0" \
|
||||
test npm run test-reports)
|
||||
|
||||
docker logs -f $CONTAINER_ID
|
||||
|
||||
exit $(docker inspect $CONTAINER_ID --format='{{.State.ExitCode}}')
|
||||
./npm.sh run --silent cover-arcanist > coverage.json
|
||||
|
8
npm.sh
8
npm.sh
@ -4,13 +4,9 @@
|
||||
|
||||
./docker-up.sh
|
||||
|
||||
CONTAINER_ID=$(docker-compose run \
|
||||
-d \
|
||||
--service-ports \
|
||||
docker-compose exec -T \
|
||||
-e "SEALIOUS_MONGO_PORT=27017" \
|
||||
-e "SEALIOUS_MONGO_HOST=db" \
|
||||
test \
|
||||
npm "$@")
|
||||
npm "$@"
|
||||
|
||||
docker logs -f $CONTAINER_ID
|
||||
docker rm $CONTAINER_ID
|
||||
|
19803
package-lock.json
generated
19803
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@ -15,8 +15,9 @@
|
||||
"postbuild": "npm run typecheck && npm run lint",
|
||||
"watch": "multiple-scripts-tmux -p watch",
|
||||
"reset-db": "docker compose down -v && docker compose up -d",
|
||||
"pretest": "npm run build && docker compose up -d",
|
||||
"test": "node test.cjs",
|
||||
"pretest": "rm -rf coverage && npm run build && docker compose up -d",
|
||||
"test": "PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:3000/ npx playwright test",
|
||||
"test-ci": "TESTS_RUN_IN_DOCKER=true PW_TEST_CONNECT_WS_ENDPOINT=ws://playwright:3000/ npx playwright test",
|
||||
"prepare": "husky",
|
||||
"lint-css": "stylelint \"**/*.css\" --config ./.stylelintrc.json",
|
||||
"lint-css-fix": "npm run lint-css -- --fix",
|
||||
@ -24,11 +25,9 @@
|
||||
"lint-js": "eslint src",
|
||||
"lint-js-report": "npm run --silent lint-js -- --format json > .eslint-result.json",
|
||||
"lint": "npm run lint-js && npm run lint-css",
|
||||
"lint-report": "npm run lint-js-report && npm run lint-css-report",
|
||||
"pretest-reports": "npm run build --ignore-scripts && rm -fr .xunit coverage",
|
||||
"test-reports": "npx c8 --exclude \"\" ./node_modules/.bin/mocha --recursive --timeout=10000 --require source-map-support/register 'dist/**/*.test.js' --exit && c8 report --reporter cobertura --exclude \"\"",
|
||||
"precover-html": "rm -rf coverage/lcov-report",
|
||||
"cover-html": "npm run test-reports && c8 report --reporter lcov --exclude \"\" && xdg-open coverage/lcov-report/index.html"
|
||||
"lint-report": "npm run lint-js-report; npm run lint-css-report",
|
||||
"cover-html": "npm run test; c8 report --reporter lcov --all --src src && xdg-open coverage/lcov-report/index.html",
|
||||
"cover-arcanist": "c8 report --reporter cobertura --all --src src && node ./convert-cobertura-to-arcanist.mjs"
|
||||
},
|
||||
"tmux-scripts": {
|
||||
"watch": [
|
||||
@ -83,7 +82,7 @@
|
||||
"@sealcode/file-manager": "^1.0.2",
|
||||
"@sealcode/jdd": "^0.8.0",
|
||||
"@sealcode/jdd-editor": "^0.2.0",
|
||||
"@sealcode/sealgen": "^0.18.0",
|
||||
"@sealcode/sealgen": "^0.18.5",
|
||||
"@sealcode/simplemde": "^1.12.1",
|
||||
"@sealcode/ts-predicates": "^0.6.2",
|
||||
"@types/kill-port": "^2.0.0",
|
||||
@ -95,6 +94,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"escape-goat": "^4.0.0",
|
||||
"get-port": "^7.0.0",
|
||||
"glob": "^11.0.3",
|
||||
"js-convert-case": "^4.2.0",
|
||||
"koa-mount": "^4.2.0",
|
||||
"koa-qs": "^3.0.0",
|
||||
@ -102,11 +102,13 @@
|
||||
"koa-static": "^5.0.0",
|
||||
"locreq": "^3.0.0",
|
||||
"meilisearch": "^0.41.0",
|
||||
"mongodb": "^6.17.0",
|
||||
"multiple-scripts-tmux": "^1.0.4",
|
||||
"nodemon": "^3.0.1",
|
||||
"nyc": "^17.1.0",
|
||||
"object-path": "^0.11.8",
|
||||
"qs": "^6.12.0",
|
||||
"sealious": "^0.21.4",
|
||||
"sealious": "^0.21.7",
|
||||
"slug": "^9.1.0",
|
||||
"stimulus": "^3.2.2",
|
||||
"tempstream": "^0.4.6",
|
||||
@ -140,12 +142,14 @@
|
||||
"kill-port": "^1.6.1",
|
||||
"mocha": "^10.4.0",
|
||||
"mri": "^1.2.0",
|
||||
"playwright": "^1.54.1",
|
||||
"prettier": "^3.5.3",
|
||||
"stylelint": "^16.10.0",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"ts-loader": "^8.0.14",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.6"
|
||||
"typescript": "^5.6",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=21.0.0"
|
||||
|
22
playwright.config.ts
Normal file
22
playwright.config.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "tests",
|
||||
globalTeardown: "./tests/global-teardown",
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: {
|
||||
browserName: "firefox",
|
||||
},
|
||||
},
|
||||
],
|
||||
expect: {
|
||||
toHaveScreenshot: {
|
||||
pathTemplate: "{testDir}/__screenshots/{testFilePath}/{arg}{ext}",
|
||||
},
|
||||
toMatchAriaSnapshot: {
|
||||
pathTemplate: "{testDir}/__snapshots/{testFilePath}/{arg}{ext}",
|
||||
},
|
||||
},
|
||||
});
|
@ -3,7 +3,7 @@ import type { Context } from "koa";
|
||||
import type { FlatTemplatable } from "tempstream";
|
||||
import type { Context as SealiousContext } from "sealious";
|
||||
import { App, LoggerMailer, SMTPMailer } from "sealious";
|
||||
import type { LoggerLevel } from "sealious/@types/src/app/logger.js";
|
||||
import type { LoggerLevel } from "sealious/@types/app/logger.js";
|
||||
import { collections } from "./collections/collections.js";
|
||||
import {
|
||||
BASE_URL,
|
||||
|
@ -4,7 +4,7 @@ import type { App, CollectionItem } from "sealious";
|
||||
import { CRUDRoles } from "../policy-types/roles.js";
|
||||
import assert from "assert";
|
||||
import TheApp from "../app.js";
|
||||
import type { FieldEntryMapping } from "sealious/@types/src/chip-types/collection.js";
|
||||
import type { FieldEntryMapping } from "sealious/@types/chip-types/collection.js";
|
||||
import { JDD } from "@sealcode/jdd";
|
||||
import { registry } from "../jdd-components/registry.js";
|
||||
import type { RawJDDocument } from "@sealcode/jdd";
|
||||
@ -77,7 +77,7 @@ export default class Pages extends Collection {
|
||||
...super.mapFieldsToFeed(),
|
||||
content: async (ctx, item) => {
|
||||
const jdd = await getJDD(ctx, item);
|
||||
const result = await tempstream/* HTML */ `${jdd.render()}`;
|
||||
const result = await tempstream /* HTML */ `${jdd.render()}`;
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
@ -1,91 +0,0 @@
|
||||
import axios from "axios";
|
||||
import assert from "assert";
|
||||
import type TheApp from "../app.js";
|
||||
import { withProdApp } from "../test_utils/with-prod-app.js";
|
||||
|
||||
describe("password-reset-intents", function () {
|
||||
//ts-ignore
|
||||
async function createAUser(app: TheApp) {
|
||||
await app.collections.users.suCreate({
|
||||
username: "user",
|
||||
email: "user@example.com",
|
||||
password: "password",
|
||||
roles: [],
|
||||
});
|
||||
}
|
||||
|
||||
it("tells you if the email address doesn't exist", async function () {
|
||||
return withProdApp(async ({ app, base_url }) => {
|
||||
const email = "fake@example.com";
|
||||
try {
|
||||
await axios.post(
|
||||
`${base_url}/api/v1/collections/password-reset-intents`,
|
||||
{
|
||||
email: email,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
assert.equal(
|
||||
e.response.data.data.field_messages.email.message,
|
||||
app.i18n("invalid_existing_value", ["users", "email", email])
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error("it didn't throw");
|
||||
});
|
||||
});
|
||||
|
||||
it("allows anyone to create an intent, if the email exists", async () =>
|
||||
withProdApp(async ({ app, base_url }) => {
|
||||
await createAUser(app);
|
||||
const { email, token } = (
|
||||
await axios.post(
|
||||
`${base_url}/api/v1/collections/password-reset-intents`,
|
||||
{
|
||||
email: "user@example.com",
|
||||
}
|
||||
)
|
||||
).data;
|
||||
assert.deepEqual(
|
||||
{ email, token },
|
||||
{
|
||||
email: "user@example.com",
|
||||
token: "it's a secret to everybody",
|
||||
}
|
||||
);
|
||||
}));
|
||||
|
||||
it("tells you if the email address is malformed", async () =>
|
||||
withProdApp(async ({ app, base_url }) => {
|
||||
const email = "incorrect-address";
|
||||
try {
|
||||
await axios.post(
|
||||
`${base_url}/api/v1/collections/password-reset-intents`,
|
||||
{
|
||||
email: email,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
assert.equal(
|
||||
e.response.data.data.field_messages.email.message,
|
||||
app.i18n("invalid_email", [email])
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error("it didn't throw");
|
||||
}));
|
||||
|
||||
it("sends an email with the reset password link", async () =>
|
||||
withProdApp(async ({ app, base_url, mail_api }) => {
|
||||
await createAUser(app);
|
||||
await axios.post(`${base_url}/api/v1/collections/password-reset-intents`, {
|
||||
email: "user@example.com",
|
||||
});
|
||||
const messages = (await mail_api.getMessages()).filter(
|
||||
(message) => message.recipients[0] == "<user@example.com>"
|
||||
);
|
||||
assert.equal(messages.length, 1);
|
||||
assert.equal(messages[0]?.recipients.length, 1);
|
||||
assert.equal(messages[0]?.recipients[0], "<user@example.com>");
|
||||
}));
|
||||
});
|
@ -6,6 +6,12 @@ import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
export const SEALIOUS_SANITY = Boolean(process.env.SEALIOUS_SANITY);
|
||||
|
||||
// for tests fixtures to be able to close nicely when the test is done - otherwise
|
||||
// coverage is not reported if we kill the process
|
||||
export const SEALIOUS_KILLSWITCH_PORT =
|
||||
parseInt(process.env.SEALIOUS_KILLSWITCH_PORT || "") || null;
|
||||
|
||||
export const PORT = process.env.SEALIOUS_PORT
|
||||
? parseInt(process.env.SEALIOUS_PORT)
|
||||
: 8080;
|
||||
|
@ -40,23 +40,11 @@ export function defaultHead({
|
||||
description: string;
|
||||
}): JSX.Element | Readable {
|
||||
const origin = ctx.URL.origin;
|
||||
return tempstream/* HTML */ `<title>${title}</title>
|
||||
return tempstream /* HTML */ `<title>${title}</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="${htmlEscape(description)}" />
|
||||
${ctx.$app.getFeedHTMLMetatags(ctx)}
|
||||
<link
|
||||
rel="preload"
|
||||
href="/dist/fonts/Poppins-400-1.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/dist/fonts/Poppins-400-2.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
/>
|
||||
<script defer src="/dist/bundle.js?v=${start_timestamp}"></script>
|
||||
${metaImage ? `<meta property="og:image" content="${metaImage}" />` : ""}
|
||||
${[
|
||||
|
@ -40,7 +40,7 @@ export default function html({
|
||||
controllers.push("refresh-on-ts-changes");
|
||||
}
|
||||
|
||||
return tempstreamAsync/* HTML */ ` <!DOCTYPE html>
|
||||
return tempstreamAsync /* HTML */ ` <!DOCTYPE html>
|
||||
<html
|
||||
lang="${htmlOptions.language || DEFAULT_HTML_LANG}"
|
||||
class="title--${typeof title == "string" ? toKebabCase(title) : ""}"
|
||||
|
@ -1,15 +1,13 @@
|
||||
import Router from "@koa/router";
|
||||
import dgram from "dgram";
|
||||
import kill from "kill-port";
|
||||
import { Middlewares } from "sealious";
|
||||
|
||||
import { default as Koa } from "koa";
|
||||
import installQS from "koa-qs";
|
||||
|
||||
import { Middlewares } from "sealious";
|
||||
import TheApp from "./app.js";
|
||||
import { PORT, SEALIOUS_SANITY } from "./config.js";
|
||||
import { PORT, SEALIOUS_KILLSWITCH_PORT, SEALIOUS_SANITY } from "./config.js";
|
||||
import { mainRouter } from "./routes/index.js";
|
||||
import { waitForMeilisearch } from "./services/meilisearch.js";
|
||||
|
||||
export const the_app = new TheApp();
|
||||
|
||||
void (async function () {
|
||||
@ -34,13 +32,21 @@ void (async function () {
|
||||
installQS(koa_app);
|
||||
koa_app.use(Middlewares.handleError());
|
||||
koa_app.context.$app = the_app;
|
||||
the_app.on("started", () => {
|
||||
the_app.on("started", async () => {
|
||||
mainRouter(the_app, koa_app, router);
|
||||
});
|
||||
|
||||
if (SEALIOUS_KILLSWITCH_PORT) {
|
||||
const server = dgram.createSocket("udp4");
|
||||
server.on("message", () => {
|
||||
process.exit(0);
|
||||
});
|
||||
server.bind(SEALIOUS_KILLSWITCH_PORT);
|
||||
}
|
||||
|
||||
await the_app.start();
|
||||
|
||||
koa_app.listen(PORT);
|
||||
koa_app.listen(PORT, "0.0.0.0");
|
||||
the_app.Logger.info(
|
||||
"STARTED",
|
||||
`App running. URL set in manifest: ${the_app.manifest.base_url}`
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { withProdApp } from "../test_utils/with-prod-app.js";
|
||||
|
||||
describe("roles", () => {
|
||||
it("allows access to users with designated role and denies access to users without it", async () =>
|
||||
withProdApp(async ({ app }) => {
|
||||
await app.collections.users.suCreate({
|
||||
username: "regular-user",
|
||||
password: "password",
|
||||
email: "regular@example.com",
|
||||
roles: [],
|
||||
});
|
||||
|
||||
await app.collections.users.suCreate({
|
||||
username: "someadmin",
|
||||
password: "admin-password",
|
||||
email: "admin@example.com",
|
||||
roles: [],
|
||||
});
|
||||
}));
|
||||
});
|
@ -1,40 +0,0 @@
|
||||
import { withProdApp } from "../test_utils/with-prod-app.js";
|
||||
import { VERY_LONG_TEST_TIMEOUT, webhintURL } from "../test_utils/webhint.js";
|
||||
import { HomeURL } from "./urls.js";
|
||||
import { getBrowser } from "../test_utils/browser-creator.js";
|
||||
import type { Browser, BrowserContext, Page } from "@playwright/test";
|
||||
|
||||
describe("Home webhint", () => {
|
||||
it("doesn't crash", async function () {
|
||||
return withProdApp(async ({ base_url, rest_api }) => {
|
||||
await rest_api.get(HomeURL);
|
||||
await webhintURL(base_url + HomeURL);
|
||||
// alternatively you can use webhintHTML for faster but less precise scans
|
||||
// or for scanning responses of requests that use some form of authorization:
|
||||
// const response = await rest_api.get(HomeURL);
|
||||
// await webhintHTML(response);
|
||||
});
|
||||
}).timeout(VERY_LONG_TEST_TIMEOUT);
|
||||
});
|
||||
|
||||
describe("Home", () => {
|
||||
let page: Page;
|
||||
let browser: Browser;
|
||||
let context: BrowserContext;
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await getBrowser();
|
||||
context = await browser.newContext();
|
||||
page = await context.newPage();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await context.close();
|
||||
});
|
||||
|
||||
it("works as expected", async function () {
|
||||
return withProdApp(async ({ base_url }) => {
|
||||
await page.goto(base_url + HomeURL);
|
||||
});
|
||||
}).timeout(VERY_LONG_TEST_TIMEOUT);
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
import { withProdApp } from "../test_utils/with-prod-app.js";
|
||||
import { AdminURL } from "./urls.js";
|
||||
import { getBrowser } from "../test_utils/browser-creator.js";
|
||||
import type { Browser, BrowserContext, Page } from "@playwright/test";
|
||||
|
||||
describe("Admin", () => {
|
||||
it("doesn't crash", async function () {
|
||||
return withProdApp(async ({ rest_api }) => {
|
||||
await rest_api.get(AdminURL);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Admin", () => {
|
||||
let page: Page;
|
||||
let browser: Browser;
|
||||
let context: BrowserContext;
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await getBrowser();
|
||||
context = await browser.newContext();
|
||||
page = await context.newPage();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await context.close();
|
||||
});
|
||||
|
||||
it("works as expected", async function () {
|
||||
return withProdApp(async ({ base_url }) => {
|
||||
await page.goto(base_url + AdminURL);
|
||||
});
|
||||
});
|
||||
});
|
@ -92,7 +92,7 @@ export default new (class NavbarLinksCRUDEditForm extends Form<typeof fields, vo
|
||||
return html({
|
||||
ctx,
|
||||
title: "Edit NavbarLinks",
|
||||
body: tempstream/* HTML */ ` <div class="sealgen-crud-form">
|
||||
body: tempstream /* HTML */ ` <div class="sealgen-crud-form">
|
||||
<a class="" href="${NavbarLinksCRUDListURL}"
|
||||
>← Back to navbar-links list</a
|
||||
>
|
||||
|
@ -50,7 +50,7 @@ export default new (class NavbarLinksCRUDCreateForm extends Form<typeof fields,
|
||||
return html({
|
||||
ctx,
|
||||
title: "Create navbar-links",
|
||||
body: tempstream/* HTML */ ` <div class="sealgen-crud-form">
|
||||
body: tempstream /* HTML */ ` <div class="sealgen-crud-form">
|
||||
<a class="" href="${NavbarLinksCRUDListURL}"
|
||||
>← Back to navbar-links list</a
|
||||
>
|
||||
|
@ -134,7 +134,7 @@ export default new (class NavbarLinksCRUDListPage extends SealiousItemListPage<
|
||||
ctx: Context,
|
||||
fields: { field: string; label?: string }[]
|
||||
): FlatTemplatable {
|
||||
return tempstream/* HTML */ `<thead>
|
||||
return tempstream /* HTML */ `<thead>
|
||||
<tr>
|
||||
${fields.map(({ label, field }) => this.renderHeading(ctx, field, label))}
|
||||
<th>Actions</th>
|
||||
|
@ -105,7 +105,7 @@ export default new (class PagesCRUDEditForm extends Form<typeof fields, void> {
|
||||
return html({
|
||||
ctx,
|
||||
title: "Edit Pages",
|
||||
body: tempstream/* HTML */ ` <div class="sealgen-crud-form">
|
||||
body: tempstream /* HTML */ ` <div class="sealgen-crud-form">
|
||||
<a class="" href="${PagesCRUDListURL}">← Back to pages list</a>
|
||||
${await super.render(ctx, data, show_field_errors)}
|
||||
</div>`,
|
||||
|
@ -56,7 +56,7 @@ export default new (class PagesCRUDCreateForm extends Form<typeof fields, void>
|
||||
return html({
|
||||
ctx,
|
||||
title: "Create pages",
|
||||
body: tempstream/* HTML */ ` <div class="sealgen-crud-form">
|
||||
body: tempstream /* HTML */ ` <div class="sealgen-crud-form">
|
||||
<a class="" href="${PagesCRUDListURL}">← Back to pages list</a>
|
||||
${await super.render(ctx, data, show_field_errors)}
|
||||
</div>`,
|
||||
|
@ -146,7 +146,7 @@ export default new (class PagesCRUDListPage extends SealiousItemListPage<
|
||||
ctx: Context,
|
||||
fields: { field: string; label?: string }[]
|
||||
): FlatTemplatable {
|
||||
return tempstream/* HTML */ `<thead>
|
||||
return tempstream /* HTML */ `<thead>
|
||||
<tr>
|
||||
${fields.map(({ label, field }) => this.renderHeading(ctx, field, label))}
|
||||
<th>Actions</th>
|
||||
|
@ -1,34 +0,0 @@
|
||||
import { withProdApp } from "../../test_utils/with-prod-app.js";
|
||||
import { ShowFirstRowDemoURL } from "../urls.js";
|
||||
import { getBrowser } from "../../test_utils/browser-creator.js";
|
||||
import type { Browser, BrowserContext, Page } from "@playwright/test";
|
||||
|
||||
describe("ShowFirstRowDemo", () => {
|
||||
it("doesn't crash", async function () {
|
||||
return withProdApp(async ({ rest_api }) => {
|
||||
await rest_api.get(ShowFirstRowDemoURL);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ShowFirstRowDemo", () => {
|
||||
let page: Page;
|
||||
let browser: Browser;
|
||||
let context: BrowserContext;
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await getBrowser();
|
||||
context = await browser.newContext();
|
||||
page = await context.newPage();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await context.close();
|
||||
});
|
||||
|
||||
it("works as expected", async function () {
|
||||
return withProdApp(async ({ base_url }) => {
|
||||
await page.goto(base_url + ShowFirstRowDemoURL);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,28 +0,0 @@
|
||||
import type { Browser, BrowserContext, Page } from "@playwright/test";
|
||||
import { firefox } from "@playwright/test";
|
||||
|
||||
let browser: Browser;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export async function getBrowser(): Promise<Browser> {
|
||||
if (!browser) browser = await firefox.launch();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return browser;
|
||||
}
|
||||
|
||||
export async function closeBrowser() {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPage(): Promise<{
|
||||
page: Page;
|
||||
browser: Browser;
|
||||
context: BrowserContext;
|
||||
}> {
|
||||
const browser = await getBrowser();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
return { browser, context, page };
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import type { CollectionItem, TestUtils } from "sealious";
|
||||
import type TheApp from "../app.js";
|
||||
import type Users from "../collections/users.js";
|
||||
|
||||
type Unpromisify<T> = T extends Promise<infer R> ? R : T;
|
||||
|
||||
export function createAUser(app: TheApp, username: string, roles: string[]) {
|
||||
return app.collections.users.suCreate({
|
||||
username,
|
||||
email: `${username}@example.com`,
|
||||
password: "password",
|
||||
roles: roles.map((role) => ({ role })),
|
||||
});
|
||||
}
|
||||
|
||||
export async function createAdmin(
|
||||
app: TheApp,
|
||||
rest_api: TestUtils.MockRestApi
|
||||
): Promise<[CollectionItem<Users>, Unpromisify<ReturnType<typeof rest_api.login>>]> {
|
||||
const user = await createAUser(app, "super_user", ["admin"]);
|
||||
const session = await rest_api.login({
|
||||
username: "super_user",
|
||||
password: "password",
|
||||
});
|
||||
return [user, session];
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import getPort from "get-port";
|
||||
import { default as Koa } from "koa";
|
||||
import { SMTPMailer, TestUtils, Middlewares } from "sealious";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import TheApp from "../app.js";
|
||||
import {
|
||||
MAILCATCHER_API_PORT,
|
||||
MAILCATCHER_HOST,
|
||||
MAILCATCHER_SMTP_PORT,
|
||||
} from "../config.js";
|
||||
import { mainRouter } from "../routes/index.js";
|
||||
import Router from "@koa/router";
|
||||
|
||||
const port_numbers = async function* () {
|
||||
yield await getPort();
|
||||
};
|
||||
|
||||
export async function withProdApp(
|
||||
callback: (args: {
|
||||
app: TheApp;
|
||||
base_url: string;
|
||||
rest_api: TestUtils.MockRestApi;
|
||||
mail_api: TestUtils.MailcatcherAPI;
|
||||
}) => Promise<void>
|
||||
) {
|
||||
const app = new TheApp();
|
||||
const port = (await port_numbers().next()).value;
|
||||
if (!port) {
|
||||
console.error("Constant port is empty.");
|
||||
return;
|
||||
}
|
||||
app.config["www-server"].port = port;
|
||||
app.config.datastore_mongo = {
|
||||
...app.config.datastore_mongo,
|
||||
db_name: "sealious-app-test" + uuid(),
|
||||
};
|
||||
app.config.logger.level = <const>"none";
|
||||
app.mailer = new SMTPMailer({
|
||||
host: MAILCATCHER_HOST,
|
||||
port: MAILCATCHER_SMTP_PORT,
|
||||
user: "any",
|
||||
password: "any",
|
||||
});
|
||||
|
||||
const koa_app = new Koa();
|
||||
const router = new Router();
|
||||
koa_app.use(Middlewares.handleError());
|
||||
koa_app.context.$app = app;
|
||||
const server = koa_app.listen(port);
|
||||
app.on("started", () => {
|
||||
mainRouter(app, koa_app, router);
|
||||
});
|
||||
|
||||
await app.start();
|
||||
const base_url = `http://127.0.0.1:${port}`;
|
||||
const mail_api = new TestUtils.MailcatcherAPI(
|
||||
`http://${MAILCATCHER_HOST}:${MAILCATCHER_API_PORT}`,
|
||||
app
|
||||
);
|
||||
await mail_api.deleteAllInstanceEmails();
|
||||
|
||||
async function stop() {
|
||||
await app.removeAllData();
|
||||
await app.stop();
|
||||
server.close();
|
||||
}
|
||||
|
||||
try {
|
||||
await callback({
|
||||
app,
|
||||
base_url,
|
||||
rest_api: new TestUtils.MockRestApi(base_url),
|
||||
mail_api,
|
||||
});
|
||||
|
||||
await stop();
|
||||
} catch (e) {
|
||||
if (app.status !== "stopped") {
|
||||
await stop();
|
||||
}
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
58
test.cjs
58
test.cjs
@ -1,58 +0,0 @@
|
||||
const mri = require("mri");
|
||||
const { spawn } = require("child_process");
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
const args = mri(argv);
|
||||
|
||||
const bin_dir = "./node_modules/.bin/";
|
||||
|
||||
const mocha = bin_dir + "mocha";
|
||||
|
||||
let mocha_options = [
|
||||
"--recursive",
|
||||
"--timeout=100000",
|
||||
"--require",
|
||||
"source-map-support/register",
|
||||
"--exit",
|
||||
];
|
||||
|
||||
if (args["serial"]) {
|
||||
mocha_options.push("--parallel");
|
||||
}
|
||||
|
||||
if (args["test-report"]) {
|
||||
mocha_options = [
|
||||
...mocha_options,
|
||||
// "--require",
|
||||
// "ts-node/register",
|
||||
// "--require",
|
||||
// "./src/http/type-overrides.ts",
|
||||
"--reporter",
|
||||
"xunit",
|
||||
"--reporter-option",
|
||||
"output=.xunit",
|
||||
];
|
||||
}
|
||||
|
||||
const mocha_files = ["dist/**/*.test.js"];
|
||||
|
||||
let command = [mocha, ...mocha_options, ...mocha_files];
|
||||
|
||||
if (args.debug) {
|
||||
command = ["node", "inspect", ...command];
|
||||
}
|
||||
|
||||
console.log("spawning mocha...", command.join(" "));
|
||||
|
||||
const proc = spawn(command[0], command.slice(1), {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
proc.on("exit", function (code) {
|
||||
if (args["test-report"]) {
|
||||
process.exit(0);
|
||||
} else {
|
||||
process.exit(code);
|
||||
}
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
tests/__screenshots/tekst.test.ts/Tekst-component-1.png
Normal file
BIN
tests/__screenshots/tekst.test.ts/Tekst-component-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
35
tests/admin.test.ts
Normal file
35
tests/admin.test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { test, expect } from "./backend-fixture.js";
|
||||
|
||||
test.beforeEach(async ({ page, backendUrl }) => {
|
||||
await page.goto(backendUrl);
|
||||
await page.getByRole("link", { name: "Go to Admin" }).click();
|
||||
await page.getByPlaceholder("text").click();
|
||||
await page.getByPlaceholder("text").fill("admin");
|
||||
await page.getByPlaceholder("text").press("Tab");
|
||||
await page.getByPlaceholder("password").fill("adminadmin");
|
||||
await page.getByRole("button", { name: "Wyślij" }).click();
|
||||
});
|
||||
|
||||
test("Basic flow of adding a navbar link", async ({ page }) => {
|
||||
await page.getByRole("link", { name: "Edit Navbar" }).click();
|
||||
await page.getByRole("link", { name: "Create" }).click();
|
||||
await page.getByLabel("label").click();
|
||||
await page.getByLabel("label").fill("Homepage");
|
||||
await page.getByLabel("href").fill("/");
|
||||
await page.getByRole("button", { name: "Wyślij" }).click();
|
||||
await page.getByRole("link", { name: "Homepage" }).click();
|
||||
await expect(page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("Basic flow of creating a page", async ({ page, backendUrl }) => {
|
||||
await page.getByRole("link", { name: "Edit Pages" }).click();
|
||||
await page.getByRole("link", { name: "Create" }).click();
|
||||
await page.getByLabel("url").click();
|
||||
await page.getByLabel("url").fill("/my-page/");
|
||||
await page.getByLabel("title").fill("My Page");
|
||||
await page.getByLabel("heading").fill("My Heading");
|
||||
await page.getByLabel("description").fill("My Description");
|
||||
await page.getByRole("button", { name: "Wyślij" }).click();
|
||||
await page.goto(`${backendUrl}/my-page/`);
|
||||
await expect(page).toHaveScreenshot();
|
||||
});
|
170
tests/backend-fixture.ts
Normal file
170
tests/backend-fixture.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import { test as base } from "@playwright/test";
|
||||
import { spawn, spawnSync } from "child_process";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import getPort from "get-port";
|
||||
import * as path from "path";
|
||||
import * as dgram from "dgram";
|
||||
import * as http from "http";
|
||||
import * as fs from "fs/promises";
|
||||
import { MongoClient } from "mongodb";
|
||||
import { MONGO_PORT, MONGO_HOST } from "../src/back/config.js";
|
||||
|
||||
type TestFixtures = {
|
||||
backendUrl: string;
|
||||
setMarkdownValue: (field_name: string, value: string) => Promise<void>;
|
||||
};
|
||||
|
||||
const c8 = "./node_modules/c8/bin/c8.js";
|
||||
const app_host = process.env.TESTS_RUN_IN_DOCKER
|
||||
? "test" // the docker name
|
||||
: "172.17.0.1"; // ip to access localhost from within docker (playwright always runs in docker)
|
||||
|
||||
export async function waitForHttpPort({
|
||||
port,
|
||||
host = app_host,
|
||||
timeout = 5000 * 20,
|
||||
interval = 200,
|
||||
}: {
|
||||
port: number;
|
||||
host?: string;
|
||||
timeout?: number;
|
||||
interval?: number;
|
||||
}): Promise<void> {
|
||||
const start = Date.now();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const check = () => {
|
||||
console.log("Making a request to", { host, port });
|
||||
const req = http.get({ hostname: host, port, timeout: 1000 }, (res) => {
|
||||
res.destroy();
|
||||
resolve(); // Port is ready
|
||||
});
|
||||
|
||||
req.on("error", () => {
|
||||
if (Date.now() - start > timeout) {
|
||||
reject(new Error(`Timeout waiting for HTTP port ${port}`));
|
||||
} else {
|
||||
setTimeout(check, interval);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
function triggerKillswitch(port: number) {
|
||||
const client = dgram.createSocket("udp4");
|
||||
|
||||
const message = Buffer.from("Hello, UDP!");
|
||||
const HOST = "127.0.0.1";
|
||||
|
||||
client.send(message, port, HOST, () => {
|
||||
client.close();
|
||||
});
|
||||
}
|
||||
|
||||
async function dropMongoDB(mongo_db_name: string) {
|
||||
const client = new MongoClient(`mongodb://${MONGO_HOST}:${MONGO_PORT}`);
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
const db = client.db(mongo_db_name);
|
||||
|
||||
const result = await db.dropDatabase();
|
||||
console.log(`Database "${mongo_db_name}" dropped:`, result);
|
||||
} catch (err) {
|
||||
console.error("Error dropping database:", err);
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
|
||||
export const test = base.extend<TestFixtures>({
|
||||
backendUrl: async ({}, use, { file, titlePath }) => {
|
||||
const port = await getPort();
|
||||
const mongo_db_name = `test-app-${uuid()}`;
|
||||
const killswitch_port = await getPort();
|
||||
const coverage_temp_dir = path.join(
|
||||
"coverage",
|
||||
"tmp_split",
|
||||
Date.now().toString()
|
||||
);
|
||||
await fs.mkdir(coverage_temp_dir, {
|
||||
recursive: true,
|
||||
});
|
||||
const backendProcess = spawn(
|
||||
c8,
|
||||
[
|
||||
"--src",
|
||||
"src",
|
||||
"--reporter",
|
||||
"none",
|
||||
"--temp-directory",
|
||||
coverage_temp_dir,
|
||||
"--all",
|
||||
"node",
|
||||
"dist/back/index.js",
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
SEALIOUS_PORT: `${port}`,
|
||||
SEALIOUS_MONGO_DB_NAME: mongo_db_name,
|
||||
...(process.env.TESTS_RUN_IN_DOCKER
|
||||
? { MEILISEARCH_HOST: `http://meilisearch:7700` }
|
||||
: {}),
|
||||
SEALIOUS_KILLSWITCH_PORT: `${killswitch_port}`,
|
||||
},
|
||||
stdio: "inherit",
|
||||
}
|
||||
);
|
||||
|
||||
const onEndPromise = new Promise<void>((resolve) => {
|
||||
backendProcess.on("exit", async () => {
|
||||
await dropMongoDB(mongo_db_name);
|
||||
spawnSync(
|
||||
c8,
|
||||
[
|
||||
"report",
|
||||
"--temp-directory",
|
||||
coverage_temp_dir,
|
||||
"--reporter",
|
||||
"lcovonly",
|
||||
"--report-dir",
|
||||
path.join("coverage", Date.now().toString()),
|
||||
],
|
||||
{ stdio: "inherit" }
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await waitForHttpPort({ port, timeout: 10000 });
|
||||
await use(`http://${app_host}:${port}`);
|
||||
triggerKillswitch(killswitch_port);
|
||||
await onEndPromise;
|
||||
},
|
||||
|
||||
setMarkdownValue: ({ page }, use) => {
|
||||
use(async (field_name: string, value: string) => {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
try {
|
||||
await page.getByRole("button", { name: "Preview" }).click();
|
||||
await page
|
||||
.locator(`textarea[name="${field_name}"] ~ .CodeMirror`)
|
||||
.first()
|
||||
.click({ timeout: 1000 });
|
||||
await page.getByRole("textbox").press("ControlOrMeta+a");
|
||||
await page.getByRole("textbox").first().type("Hamster, a dentist");
|
||||
break;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export { expect } from "@playwright/test";
|
23
tests/global-teardown.ts
Normal file
23
tests/global-teardown.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { glob } from "glob";
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
|
||||
export default async () => {
|
||||
console.log("All tests finished. merging coverage information...");
|
||||
try {
|
||||
await fs.rm("coverage/tmp", { recursive: true });
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Couldn't delete the './coverage/tmp' directory - maybe it wasn't there in the first place"
|
||||
);
|
||||
}
|
||||
await fs.mkdir("coverage/tmp", { recursive: true });
|
||||
|
||||
let files = await glob("coverage/tmp_split/**/*.json");
|
||||
|
||||
await Promise.all(
|
||||
files.map((file, index) => {
|
||||
fs.copyFile(file, path.join("coverage", "tmp", path.basename(file)));
|
||||
})
|
||||
);
|
||||
};
|
16
tests/tekst.test.ts
Normal file
16
tests/tekst.test.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { test, expect } from "./backend-fixture.js";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
|
||||
test("Tekst component", async ({ page, backendUrl, setMarkdownValue }) => {
|
||||
await page.goto(`${backendUrl}/components`);
|
||||
await page.waitForFunction(() => typeof (window as any).CodeMirror !== "undefined");
|
||||
await sleep(5000);
|
||||
await page.locator('select[name="component"]').selectOption("tekst");
|
||||
await page
|
||||
.locator(`select[name="$[components][0][args][color]"]`)
|
||||
.selectOption("accent");
|
||||
await setMarkdownValue(`$[components][0][args][content]`, "Hamster, a dentist");
|
||||
|
||||
await page.getByRole("button", { name: "Preview" }).click();
|
||||
await expect(page).toHaveScreenshot();
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user