import Router from "@koa/router"; import Koa from "koa"; import axios from "axios"; import { Browser, Page } from "playwright"; import { expect } from "@playwright/test"; import { FlatTemplatable, tempstream } from "tempstream"; import { mount } from "../mount.js"; import { Multiform } from "./multiform.js"; import { assertThrowsAsync } from "../utils/utils.js"; import { form_factory } from "./form.test.js"; import { getBrowser } from "../utils/browser-creator.js"; import { TextBasedSimpleField } from "./fields/simple-form-field.js"; import { Form } from "./form.js"; import { FormHeader } from "./controls/form-header.js"; import { SimpleInput } from "./controls/simple-input.js"; import { Mountable } from "../page/mountable.js"; import { FormDataValue, FormMessage } from "./form-types.js"; import { EmailField } from "./fields/email.js"; import getPort from "get-port"; const port = await getPort(); console.info(`Using port ${port} form multiform.test.ts`); const fields1 = { email: new TextBasedSimpleField(true), }; const form1 = new (class extends Form { defaultSuccessMessage = "Form1 done!"; getControls = () => [new FormHeader("form1"), new SimpleInput(fields1.email)]; getFields = () => fields1; onSubmit() { return; } })(); const fields2 = { name: new TextBasedSimpleField(true), }; const form2 = new (class extends Form, void> { defaultSuccessMessage = "Form2 done!"; getControls = () => [new FormHeader("form2"), new SimpleInput(fields2.name)]; getFields = () => fields2; onSubmit() { return; } })(); function multiform_factory( canAccessFun1?: Mountable["canAccess"], canAccessFun2?: Mountable["canAccess"] ): Multiform { return new (class extends Multiform { name = "multiform_test"; forms = { form1: form_factory(canAccessFun1), form2: form_factory(canAccessFun2), }; async render(...args: Parameters): Promise { return tempstream /* HTML */ ` Multiform Test - Sealgen ${super.render(...args)} `; } })(); } async function tests(js_enabled: boolean) { let page: Page; before(async () => { const browser = await getBrowser(); const context = await browser.newContext({ javaScriptEnabled: js_enabled, }); page = await context.newPage(); await page.goto(`http://localhost:${port}`); await page.getByLabel("email").click(); await page.getByLabel("email").fill("sample1"); await page.getByLabel("name").click(); await page.getByLabel("name").fill("sample2"); await page.locator("#multiform_test_form1_submit").click(); }); it("checking whether only one submit handler is run", async () => { await page.getByText("Form1 done!").click(); await assertThrowsAsync(async () => { return page.getByText("Form2 done!").click({ timeout: 500 }); }); }); it("checking whether the input in the not submitted form prevails", async () => { await expect(page.getByLabel("name")).toHaveValue("sample2"); }); } async function general_form_validation_e2e_tests(js_enabled: boolean) { let browser: Browser; let page: Page; browser = await getBrowser(); const context = await browser.newContext({ javaScriptEnabled: js_enabled, }); page = await context.newPage(); await page.goto(`http://localhost:${port}`); await page.getByPlaceholder("text").click(); await page.getByPlaceholder("text").fill("incorrect"); await page.getByPlaceholder("password").click(); await page.getByPlaceholder("password").fill("test"); await page.locator("#multiform_test_form2_submit").click(); await assertThrowsAsync(async () => { await page.getByText("Incorrect input").click({ timeout: 500 }); }); } async function individual_field_validation_e2e_tests(javaScriptEnabled: boolean) { let browser: Browser; let page: Page; before(async () => { browser = await getBrowser(); const context = await browser.newContext({ javaScriptEnabled, }); page = await context.newPage(); await page.goto(`http://localhost:${port}`); }); it("validation message shows when incorrect input is given", async () => { await page.getByPlaceholder("text").click(); await page.getByPlaceholder("text").fill("notanemail"); await page.locator("#multiform_test_form1_submit").click(); await page.getByText("Please enter a proper email address").click(); }); it("validation message doesn't show when correct input is given", async () => { await page.getByPlaceholder("text").click(); await page.getByPlaceholder("text").fill("yes@an.email"); await page.locator("#multiform_test_form1_submit").click(); await assertThrowsAsync(async () => { return page.getByText("Please enter a proper email address").click({ timeout: 500 }); }); }); } describe("multiform test", () => { describe("basic tests", async () => { let server: ReturnType; before(async () => { const app = new Koa(); const router = new Router(); mount( router, "/", new (class extends Multiform { name = "multiform_test"; forms = { form1, form2 }; async render( ctx: Koa.Context, messages: FormMessage[], prerenderedForms?: Record, show_field_errors = true ): Promise { return tempstream /* HTML */ ` Multiform Test - Sealgen ${super.render( ctx, messages, prerenderedForms, show_field_errors )} `; } })(), true ); app.use(router.routes()).use(router.allowedMethods()); server = app.listen(port); }); after(() => { server.close(); }); describe("turbolinks + js enabled", async () => tests(true)); describe("turbolinks + js disabled", async () => tests(false)); }); describe("canAccess", async () => { let page: Page; let server: ReturnType; describe("allow access to both forms when both canAccess return true", async () => { before(async () => { const app = new Koa(); const router = new Router(); mount( router, "/", multiform_factory( async ( ctx: Koa.Context ): Promise<{ canAccess: boolean; message: string }> => { return { canAccess: true, message: "" }; }, async ( ctx: Koa.Context ): Promise<{ canAccess: boolean; message: string }> => { return { canAccess: true, message: "" }; } ), true ); app.use(router.routes()).use(router.allowedMethods()); server = app.listen(port); const browser = await getBrowser(); const context = await browser.newContext(); page = await context.newPage(); }); after(async () => { server.close(); }); it("check wether it renders", async () => { const response = await page.goto(`http://localhost:${port}`); if (response?.status() != 200) { throw new Error( `Should return 200 status and it returns ${response?.status()}` ); } await page.locator("#form1").getByText("This is a test:").click(); await page.locator("#form2").getByText("This is a test:").click(); }); it("check wether you can submit through http (axios)", async () => { await axios.post( `http://localhost:${port}/form2`, { form2__text: "pass", }, { validateStatus: (status: number) => { if (status == 422) return true; return false; }, } ); }); }); describe("deny access to both forms", async () => { before(async () => { const app = new Koa(); const router = new Router(); mount( router, "/", multiform_factory( async ( ctx: Koa.Context ): Promise<{ canAccess: boolean; message: string }> => { return { canAccess: false, message: "" }; }, async ( ctx: Koa.Context ): Promise<{ canAccess: boolean; message: string }> => { return { canAccess: false, message: "" }; } ), true ); app.use(router.routes()).use(router.allowedMethods()); server = app.listen(port); const browser = await getBrowser(); const context = await browser.newContext(); page = await context.newPage(); }); after(async () => { server.close(); }); it("check wether forms didnt render", async () => { await page.goto(`http://localhost:${port}`); await assertThrowsAsync(async () => { return page .locator("#form1") .getByText("This is a test:") .click({ timeout: 300 }); }); }); it("check wether it's possible to submit through http", async () => { await axios.post( `http://localhost:${port}/form2`, { form2__text: "pass", }, { validateStatus: (status: number) => { if (status == 403) return true; return false; }, } ); }); }); describe("allow access to one form, deny to the other", async () => { before(async () => { const app = new Koa(); const router = new Router(); mount( router, "/", multiform_factory( async ( ctx: Koa.Context ): Promise<{ canAccess: boolean; message: string }> => { return { canAccess: true, message: "" }; }, async ( ctx: Koa.Context ): Promise<{ canAccess: boolean; message: string }> => { return { canAccess: false, message: "" }; } ), true ); app.use(router.routes()).use(router.allowedMethods()); server = app.listen(port); const browser = await getBrowser(); const context = await browser.newContext({}); page = await context.newPage(); }); after(async () => { server.close(); }); it("check wether form1 renders", async () => { await page.goto(`http://localhost:${port}`); await page.locator("#form1").getByText("This is a test:").click({ timeout: 300 }); }); it("check wether it's possible to submit to form1 through http", async () => { await axios.post( `http://localhost:${port}/form1`, { form1__text: "pass", }, { validateStatus: (status: number) => { if (status == 422) return true; return false; }, } ); }); it("check wether form2 does not render", async () => { await page.goto(`http://localhost:${port}`); await assertThrowsAsync(async () => { return page .locator("#form2") .getByText("This is a test:") .click({ timeout: 300 }); }); }); it("check wether it's possible to submit to form2 through http", async () => { await axios.post( `http://localhost:${port}/form2`, { form2__text: "pass", }, { validateStatus: (status: number) => { if (status == 403) return true; return false; }, } ); }); }); }); describe("validation e2e", async () => { describe("basic tests", async () => { let server: ReturnType; before(async () => { const app = new Koa(); const router = new Router(); mount( router, "/", new (class extends Multiform { name = "multiform_test"; forms = { form1: new (class extends Form { fields = fields1; submitButtonText = "Submit"; getControls = () => [ new SimpleInput(fields1.email, { label: "This is a test:", type: "text", }), ]; async onSubmit() { return; } async validateValues( ctx: Koa.Context, data: Record ): Promise<{ valid: boolean; error: string }> { if (data.email === "incorrect") return { valid: false, error: "Incorrect input", }; return { valid: true, error: "" }; } })(), form2: form_factory(), }; async render( ...args: Parameters ): Promise { return tempstream /* HTML */ ` Multiform Test - Sealgen ${super.render(...args)} `; } })(), true ); app.use(router.routes()).use(router.allowedMethods()); server = app.listen(port); }); after(async () => { server.close(); }); it("js enabled", async () => general_form_validation_e2e_tests(true)); it("js disabled", async () => general_form_validation_e2e_tests(false)); }); describe("field specific validation", async () => { let server: ReturnType; before(async () => { const app = new Koa(); const router = new Router(); const fields = { text: new EmailField(true), }; mount( router, "/", new (class extends Multiform { name = "multiform_test"; forms = { form1: new (class extends Form { getFields = () => fields; submitButtonText = "Submit"; getControls = () => [ new SimpleInput(fields.text, { label: "This is a test:", type: "text", }), ]; async onSubmit() { return; } })(), form2: form_factory(), }; async render( ...args: Parameters ): Promise { return tempstream /* HTML */ ` Multiform Test - Sealgen ${super.render(...args)} `; } })(), true ); app.use(router.routes()).use(router.allowedMethods()); server = app.listen(port); }); after(async () => { server.close(); }); describe("js enabled", async () => individual_field_validation_e2e_tests(true)); describe("js disabled", async () => individual_field_validation_e2e_tests(false)); }); }); });