@sealcode/sealgen 0.8.43 → 0.8.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -1
- package/.mocharc.js +10 -0
- package/.nycrc +6 -0
- package/.prettierrc +20 -13
- package/@types/add-route.d.ts +3 -1
- package/@types/build.d.ts +3 -1
- package/@types/forms/fields/boolean.test.d.ts +1 -0
- package/@types/forms/fields/email.test.d.ts +1 -0
- package/@types/forms/fields/enum-multiple.test.d.ts +1 -0
- package/@types/forms/fields/enum.test.d.ts +1 -0
- package/@types/forms/fields/number.test.d.ts +1 -0
- package/@types/forms/fields/pick-from-list.test.d.ts +1 -0
- package/@types/forms/fields/regexp.test.d.ts +1 -0
- package/@types/forms/form.test.d.ts +6 -0
- package/@types/forms/multiform.test.d.ts +1 -0
- package/@types/mount.d.ts +1 -1
- package/@types/page/mountable-with-fields.d.ts +1 -1
- package/@types/test_utils/locator_is_visible.d.ts +2 -0
- package/@types/utils/assert-throws.d.ts +1 -0
- package/@types/utils/browser-creator.d.ts +1 -0
- package/@types/utils/utils.d.ts +1 -0
- package/lib/add-route.js +3 -3
- package/lib/add-route.js.map +1 -1
- package/lib/add-route.test.js +55 -0
- package/lib/add-route.test.js.map +1 -0
- package/lib/build.js +3 -2
- package/lib/build.js.map +1 -1
- package/lib/cli.js +1 -1
- package/lib/cli.js.map +1 -1
- package/lib/example.test.js +4 -0
- package/lib/example.test.js.map +1 -0
- package/lib/forms/fields/boolean.test.js +37 -0
- package/lib/forms/fields/boolean.test.js.map +1 -0
- package/lib/forms/fields/email.test.js +25 -0
- package/lib/forms/fields/email.test.js.map +1 -0
- package/lib/forms/fields/enum-multiple.test.js +32 -0
- package/lib/forms/fields/enum-multiple.test.js.map +1 -0
- package/lib/forms/fields/enum.test.js +26 -0
- package/lib/forms/fields/enum.test.js.map +1 -0
- package/lib/forms/fields/number.js +0 -1
- package/lib/forms/fields/number.js.map +1 -1
- package/lib/forms/fields/number.test.js +35 -0
- package/lib/forms/fields/number.test.js.map +1 -0
- package/lib/forms/fields/pick-from-list.test.js +22 -0
- package/lib/forms/fields/pick-from-list.test.js.map +1 -0
- package/lib/forms/fields/regexp.test.js +26 -0
- package/lib/forms/fields/regexp.test.js.map +1 -0
- package/lib/forms/form.test.js +275 -0
- package/lib/forms/form.test.js.map +1 -0
- package/lib/forms/multiform.js +11 -2
- package/lib/forms/multiform.js.map +1 -1
- package/lib/forms/multiform.test.js +431 -0
- package/lib/forms/multiform.test.js.map +1 -0
- package/lib/make-env.js +1 -1
- package/lib/mount.js +38 -3
- package/lib/mount.js.map +1 -1
- package/lib/test_utils/locator_is_visible.js +15 -0
- package/lib/test_utils/locator_is_visible.js.map +1 -0
- package/lib/utils/assert-throws.js +24 -0
- package/lib/utils/assert-throws.js.map +1 -0
- package/lib/utils/browser-creator.js +14 -0
- package/lib/utils/browser-creator.js.map +1 -0
- package/lib/utils/exec.js.map +1 -1
- package/lib/utils/question.js.map +1 -1
- package/lib/utils/utils.js +3 -1
- package/lib/utils/utils.js.map +1 -1
- package/mocha_root_hooks.js +6 -0
- package/package.json +16 -8
- package/src/add-route.test.ts +58 -0
- package/src/add-route.ts +6 -5
- package/src/build.ts +6 -3
- package/src/cli.ts +2 -2
- package/src/forms/fields/boolean.test.ts +36 -0
- package/src/forms/fields/email.test.ts +24 -0
- package/src/forms/fields/enum-multiple.test.ts +31 -0
- package/src/forms/fields/enum.test.ts +25 -0
- package/src/forms/fields/number.test.ts +34 -0
- package/src/forms/fields/number.ts +0 -1
- package/src/forms/fields/pick-from-list.test.ts +19 -0
- package/src/forms/fields/regexp.test.ts +25 -0
- package/src/forms/form.test.ts +351 -0
- package/src/forms/multiform.test.ts +540 -0
- package/src/forms/multiform.ts +12 -2
- package/src/make-env.ts +1 -1
- package/src/mount.ts +43 -9
- package/src/page/mountable-with-fields.ts +1 -1
- package/src/test_utils/locator_is_visible.ts +17 -0
- package/src/utils/assert-throws.ts +18 -0
- package/src/utils/browser-creator.js +10 -0
- package/src/utils/exec.ts +8 -2
- package/src/utils/question.ts +4 -1
- package/src/utils/utils.ts +1 -0
- package/tsconfig.json +2 -3
- package/.nyc_output/1c5dff80-7861-427a-b1bb-d0686211f098.json +0 -1
- package/.nyc_output/a38a0d08-1f1f-4973-b5e2-efc434c1593d.json +0 -1
- package/.nyc_output/processinfo/1c5dff80-7861-427a-b1bb-d0686211f098.json +0 -1
- package/.nyc_output/processinfo/a38a0d08-1f1f-4973-b5e2-efc434c1593d.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/.xunit +0 -3
- package/@types/forms/fields/files-multiple.d.ts +0 -12
- package/coverage/clover.xml +0 -79
- package/lib/forms/fields/files-multiple.js +0 -18
- package/lib/forms/fields/files-multiple.js.map +0 -1
- package/lib/sealgen-url.js +0 -1
- package/lib/sealgen-url.js.map +0 -1
- package/lib/tests/manual/multiform.js +0 -95
- package/lib/tests/manual/multiform.js.map +0 -1
- package/src/tests/manual/multiform.ts +0 -112
- package/test.js +0 -68
- /package/@types/{tests/manual/multiform.d.ts → add-route.test.d.ts} +0 -0
- /package/@types/{sealgen-url.d.ts → example.test.d.ts} +0 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import Router from "@koa/router";
|
|
2
|
+
import Koa from "koa";
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import { Browser, firefox, Page } from "playwright";
|
|
5
|
+
import { expect } from "@playwright/test";
|
|
6
|
+
import { FlatTemplatable, tempstream } from "tempstream";
|
|
7
|
+
import { Controls, Fields, Form, Mountable } from "..";
|
|
8
|
+
import { mount } from "../mount";
|
|
9
|
+
import { FormDataValue, FormMessage } from "./form";
|
|
10
|
+
import { Multiform } from "./multiform";
|
|
11
|
+
import { assertThrowsAsync } from "../utils/utils";
|
|
12
|
+
import { form_factory } from "./form.test";
|
|
13
|
+
import { getBrowser } from "../utils/browser-creator";
|
|
14
|
+
|
|
15
|
+
const fields1 = {
|
|
16
|
+
email: new Fields.SimpleFormField(true),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const form1 = new (class extends Form<typeof fields1, void> {
|
|
20
|
+
defaultSuccessMessage = "Form1 done!";
|
|
21
|
+
controls = [new Controls.FormHeader("form1"), new Controls.SimpleInput(fields1.email)];
|
|
22
|
+
fields = fields1;
|
|
23
|
+
onSubmit() {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
|
|
28
|
+
const fields2 = {
|
|
29
|
+
name: new Fields.SimpleFormField(true),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const form2 = new (class extends Form<Record<never, unknown>, void> {
|
|
33
|
+
defaultSuccessMessage = "Form2 done!";
|
|
34
|
+
controls = [new Controls.FormHeader("form2"), new Controls.SimpleInput(fields2.name)];
|
|
35
|
+
fields = fields2;
|
|
36
|
+
onSubmit() {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
|
|
41
|
+
function multiform_factory(
|
|
42
|
+
canAccessFun1?: Mountable["canAccess"],
|
|
43
|
+
canAccessFun2?: Mountable["canAccess"]
|
|
44
|
+
): Multiform {
|
|
45
|
+
return new (class extends Multiform {
|
|
46
|
+
name = "multiform_test";
|
|
47
|
+
forms = {
|
|
48
|
+
form1: form_factory(canAccessFun1),
|
|
49
|
+
form2: form_factory(canAccessFun2),
|
|
50
|
+
};
|
|
51
|
+
async render(...args: Parameters<Multiform["render"]>): Promise<FlatTemplatable> {
|
|
52
|
+
return tempstream/* HTML */ `<!DOCTYPE html>
|
|
53
|
+
<html>
|
|
54
|
+
<head>
|
|
55
|
+
<title>Multiform Test - Sealgen</title>
|
|
56
|
+
<script type="module">
|
|
57
|
+
import hotwiredTurbo from "https://cdn.skypack.dev/pin/@hotwired/turbo@v7.0.0-9XNv1By6uiqRlBzQLO2d/mode=imports,min/optimized/@hotwired/turbo.js";
|
|
58
|
+
</script>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
${super.render(...args)}
|
|
62
|
+
</body>
|
|
63
|
+
</html>`;
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function tests(js_enabled: boolean) {
|
|
69
|
+
let page: Page;
|
|
70
|
+
|
|
71
|
+
before(async () => {
|
|
72
|
+
const browser = await getBrowser();
|
|
73
|
+
const context = await browser.newContext({
|
|
74
|
+
javaScriptEnabled: js_enabled,
|
|
75
|
+
});
|
|
76
|
+
page = await context.newPage();
|
|
77
|
+
await page.goto("http://localhost:8080");
|
|
78
|
+
await page.getByLabel("email").click();
|
|
79
|
+
await page.getByLabel("email").fill("sample1");
|
|
80
|
+
await page.getByLabel("name").click();
|
|
81
|
+
await page.getByLabel("name").fill("sample2");
|
|
82
|
+
await page.locator("#multiform_test_form1_submit").click();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("checking whether only one submit handler is run", async () => {
|
|
86
|
+
await page.getByText("Form1 done!").click();
|
|
87
|
+
await assertThrowsAsync(async () => {
|
|
88
|
+
return page.getByText("Form2 done!").click({ timeout: 500 });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("checking whether the input in the not submitted form prevails", async () => {
|
|
93
|
+
await expect(page.getByLabel("name")).toHaveValue("sample2");
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function general_form_validation_e2e_tests(js_enabled: boolean) {
|
|
98
|
+
let browser: Browser;
|
|
99
|
+
let page: Page;
|
|
100
|
+
|
|
101
|
+
browser = await getBrowser();
|
|
102
|
+
const context = await browser.newContext({
|
|
103
|
+
javaScriptEnabled: js_enabled,
|
|
104
|
+
});
|
|
105
|
+
page = await context.newPage();
|
|
106
|
+
await page.goto("http://localhost:8080");
|
|
107
|
+
await page.getByPlaceholder("text").click();
|
|
108
|
+
await page.getByPlaceholder("text").fill("incorrect");
|
|
109
|
+
await page.getByPlaceholder("password").click();
|
|
110
|
+
await page.getByPlaceholder("password").fill("test");
|
|
111
|
+
await page.locator("#multiform_test_form2_submit").click();
|
|
112
|
+
assertThrowsAsync(async () => {
|
|
113
|
+
page.getByText("Incorrect input").click({ timeout: 500 });
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function individual_field_validation_e2e_tests(javaScriptEnabled: boolean) {
|
|
118
|
+
let browser: Browser;
|
|
119
|
+
let page: Page;
|
|
120
|
+
|
|
121
|
+
before(async () => {
|
|
122
|
+
browser = await getBrowser();
|
|
123
|
+
const context = await browser.newContext({
|
|
124
|
+
javaScriptEnabled,
|
|
125
|
+
});
|
|
126
|
+
page = await context.newPage();
|
|
127
|
+
await page.goto("http://localhost:8080");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("validation message shows when incorrect input is given", async () => {
|
|
131
|
+
await page.getByPlaceholder("text").click();
|
|
132
|
+
await page.getByPlaceholder("text").fill("notanemail");
|
|
133
|
+
await page.locator("#multiform_test_form1_submit").click();
|
|
134
|
+
await page.getByText("Please enter a proper email address").click();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("validation message doesn't show when correct input is given", async () => {
|
|
138
|
+
await page.getByPlaceholder("text").click();
|
|
139
|
+
await page.getByPlaceholder("text").fill("yes@an.email");
|
|
140
|
+
await page.locator("#multiform_test_form1_submit").click();
|
|
141
|
+
assertThrowsAsync(async () => {
|
|
142
|
+
return page.getByText("Please enter a proper email address").click({ timeout: 500 });
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
describe("multiform test", () => {
|
|
148
|
+
describe("basic tests", async () => {
|
|
149
|
+
let server: ReturnType<Koa["listen"]>;
|
|
150
|
+
|
|
151
|
+
before(async () => {
|
|
152
|
+
const app = new Koa();
|
|
153
|
+
const router = new Router();
|
|
154
|
+
|
|
155
|
+
mount(
|
|
156
|
+
router,
|
|
157
|
+
"/",
|
|
158
|
+
new (class extends Multiform {
|
|
159
|
+
name = "multiform_test";
|
|
160
|
+
forms = { form1, form2 };
|
|
161
|
+
async render(
|
|
162
|
+
ctx: Koa.Context,
|
|
163
|
+
messages: FormMessage[],
|
|
164
|
+
prerenderedForms?: Record<string, FlatTemplatable | undefined>,
|
|
165
|
+
show_field_errors = true
|
|
166
|
+
): Promise<FlatTemplatable> {
|
|
167
|
+
return tempstream/* HTML */ `<!DOCTYPE html>
|
|
168
|
+
<html>
|
|
169
|
+
<head>
|
|
170
|
+
<title>Multiform Test - Sealgen</title>
|
|
171
|
+
<script type="module">
|
|
172
|
+
import hotwiredTurbo from "https://cdn.skypack.dev/pin/@hotwired/turbo@v7.0.0-9XNv1By6uiqRlBzQLO2d/mode=imports,min/optimized/@hotwired/turbo.js";
|
|
173
|
+
</script>
|
|
174
|
+
</head>
|
|
175
|
+
<body>
|
|
176
|
+
${super.render(
|
|
177
|
+
ctx,
|
|
178
|
+
messages,
|
|
179
|
+
prerenderedForms,
|
|
180
|
+
show_field_errors
|
|
181
|
+
)}
|
|
182
|
+
</body>
|
|
183
|
+
</html>`;
|
|
184
|
+
}
|
|
185
|
+
})(),
|
|
186
|
+
true
|
|
187
|
+
);
|
|
188
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
189
|
+
server = app.listen(8080);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
after(() => {
|
|
193
|
+
server.close();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("turbolinks + js enabled", async () => tests(true));
|
|
197
|
+
describe("turbolinks + js disabled", async () => tests(false));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("canAccess", async () => {
|
|
201
|
+
let page: Page;
|
|
202
|
+
let server: ReturnType<Koa["listen"]>;
|
|
203
|
+
|
|
204
|
+
describe("allow access to both forms when both canAccess return true", async () => {
|
|
205
|
+
before(async () => {
|
|
206
|
+
const app = new Koa();
|
|
207
|
+
const router = new Router();
|
|
208
|
+
|
|
209
|
+
mount(
|
|
210
|
+
router,
|
|
211
|
+
"/",
|
|
212
|
+
multiform_factory(
|
|
213
|
+
async (
|
|
214
|
+
ctx: Koa.Context
|
|
215
|
+
): Promise<{ canAccess: boolean; message: string }> => {
|
|
216
|
+
return { canAccess: true, message: "" };
|
|
217
|
+
},
|
|
218
|
+
async (
|
|
219
|
+
ctx: Koa.Context
|
|
220
|
+
): Promise<{ canAccess: boolean; message: string }> => {
|
|
221
|
+
return { canAccess: true, message: "" };
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
true
|
|
225
|
+
);
|
|
226
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
227
|
+
server = app.listen(8080);
|
|
228
|
+
const browser = await getBrowser();
|
|
229
|
+
const context = await browser.newContext();
|
|
230
|
+
page = await context.newPage();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
after(async () => {
|
|
234
|
+
server.close();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("check wether it renders", async () => {
|
|
238
|
+
const response = await page.goto("http://localhost:8080");
|
|
239
|
+
|
|
240
|
+
if (response?.status() != 200) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Should return 200 status and it returns ${response?.status()}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
await page.locator("#form1").getByText("This is a test:").click();
|
|
246
|
+
await page.locator("#form2").getByText("This is a test:").click();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("check wether you can submit through http (axios)", async () => {
|
|
250
|
+
await axios.post(
|
|
251
|
+
"http://localhost:8080/form2",
|
|
252
|
+
{
|
|
253
|
+
form2__text: "pass",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
validateStatus: (status: number) => {
|
|
257
|
+
if (status == 422) return true;
|
|
258
|
+
return false;
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe("deny access to both forms", async () => {
|
|
266
|
+
before(async () => {
|
|
267
|
+
const app = new Koa();
|
|
268
|
+
const router = new Router();
|
|
269
|
+
|
|
270
|
+
mount(
|
|
271
|
+
router,
|
|
272
|
+
"/",
|
|
273
|
+
multiform_factory(
|
|
274
|
+
async (
|
|
275
|
+
ctx: Koa.Context
|
|
276
|
+
): Promise<{ canAccess: boolean; message: string }> => {
|
|
277
|
+
return { canAccess: false, message: "" };
|
|
278
|
+
},
|
|
279
|
+
async (
|
|
280
|
+
ctx: Koa.Context
|
|
281
|
+
): Promise<{ canAccess: boolean; message: string }> => {
|
|
282
|
+
return { canAccess: false, message: "" };
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
true
|
|
286
|
+
);
|
|
287
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
288
|
+
server = app.listen(8080);
|
|
289
|
+
const browser = await getBrowser();
|
|
290
|
+
const context = await browser.newContext();
|
|
291
|
+
page = await context.newPage();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
after(async () => {
|
|
295
|
+
server.close();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("check wether forms didnt render", async () => {
|
|
299
|
+
await page.goto("http://localhost:8080");
|
|
300
|
+
await assertThrowsAsync(async () => {
|
|
301
|
+
return page
|
|
302
|
+
.locator("#form1")
|
|
303
|
+
.getByText("This is a test:")
|
|
304
|
+
.click({ timeout: 300 });
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("check wether it's possible to submit through http", async () => {
|
|
309
|
+
await axios.post(
|
|
310
|
+
"http://localhost:8080/form2",
|
|
311
|
+
{
|
|
312
|
+
form2__text: "pass",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
validateStatus: (status: number) => {
|
|
316
|
+
if (status == 403) return true;
|
|
317
|
+
return false;
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe("allow access to one form, deny to the other", async () => {
|
|
325
|
+
before(async () => {
|
|
326
|
+
const app = new Koa();
|
|
327
|
+
const router = new Router();
|
|
328
|
+
|
|
329
|
+
mount(
|
|
330
|
+
router,
|
|
331
|
+
"/",
|
|
332
|
+
multiform_factory(
|
|
333
|
+
async (
|
|
334
|
+
ctx: Koa.Context
|
|
335
|
+
): Promise<{ canAccess: boolean; message: string }> => {
|
|
336
|
+
return { canAccess: true, message: "" };
|
|
337
|
+
},
|
|
338
|
+
async (
|
|
339
|
+
ctx: Koa.Context
|
|
340
|
+
): Promise<{ canAccess: boolean; message: string }> => {
|
|
341
|
+
return { canAccess: false, message: "" };
|
|
342
|
+
}
|
|
343
|
+
),
|
|
344
|
+
true
|
|
345
|
+
);
|
|
346
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
347
|
+
server = app.listen(8080);
|
|
348
|
+
const browser = await getBrowser();
|
|
349
|
+
const context = await browser.newContext({});
|
|
350
|
+
page = await context.newPage();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
after(async () => {
|
|
354
|
+
server.close();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("check wether form1 renders", async () => {
|
|
358
|
+
await page.goto("http://localhost:8080");
|
|
359
|
+
await page.locator("#form1").getByText("This is a test:").click({ timeout: 300 });
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("check wether it's possible to submit to form1 through http", async () => {
|
|
363
|
+
await axios.post(
|
|
364
|
+
"http://localhost:8080/form1",
|
|
365
|
+
{
|
|
366
|
+
form1__text: "pass",
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
validateStatus: (status: number) => {
|
|
370
|
+
if (status == 422) return true;
|
|
371
|
+
return false;
|
|
372
|
+
},
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("check wether form2 does not render", async () => {
|
|
378
|
+
await page.goto("http://localhost:8080");
|
|
379
|
+
await assertThrowsAsync(async () => {
|
|
380
|
+
return page
|
|
381
|
+
.locator("#form2")
|
|
382
|
+
.getByText("This is a test:")
|
|
383
|
+
.click({ timeout: 300 });
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("check wether it's possible to submit to form2 through http", async () => {
|
|
388
|
+
await axios.post(
|
|
389
|
+
"http://localhost:8080/form2",
|
|
390
|
+
{
|
|
391
|
+
form2__text: "pass",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
validateStatus: (status: number) => {
|
|
395
|
+
if (status == 403) return true;
|
|
396
|
+
return false;
|
|
397
|
+
},
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("validation e2e", async () => {
|
|
405
|
+
describe("basic tests", async () => {
|
|
406
|
+
let server: ReturnType<Koa["listen"]>;
|
|
407
|
+
|
|
408
|
+
before(async () => {
|
|
409
|
+
const app = new Koa();
|
|
410
|
+
const router = new Router();
|
|
411
|
+
|
|
412
|
+
mount(
|
|
413
|
+
router,
|
|
414
|
+
"/",
|
|
415
|
+
new (class extends Multiform {
|
|
416
|
+
name = "multiform_test";
|
|
417
|
+
forms = {
|
|
418
|
+
form1: new (class extends Form<typeof fields1, void> {
|
|
419
|
+
fields = fields1;
|
|
420
|
+
submitButtonText = "Submit";
|
|
421
|
+
controls = [
|
|
422
|
+
new Controls.SimpleInput(fields1.email, {
|
|
423
|
+
label: "This is a test:",
|
|
424
|
+
type: "text",
|
|
425
|
+
}),
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
async onSubmit() {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async validateValues(
|
|
433
|
+
ctx: Koa.Context,
|
|
434
|
+
data: Record<string, FormDataValue>
|
|
435
|
+
): Promise<{ valid: boolean; error: string }> {
|
|
436
|
+
if (data.email === "incorrect")
|
|
437
|
+
return {
|
|
438
|
+
valid: false,
|
|
439
|
+
error: "Incorrect input",
|
|
440
|
+
};
|
|
441
|
+
return { valid: true, error: "" };
|
|
442
|
+
}
|
|
443
|
+
})(),
|
|
444
|
+
form2: form_factory(),
|
|
445
|
+
};
|
|
446
|
+
async render(
|
|
447
|
+
...args: Parameters<Multiform["render"]>
|
|
448
|
+
): Promise<FlatTemplatable> {
|
|
449
|
+
return tempstream/* HTML */ `<!DOCTYPE html>
|
|
450
|
+
<html>
|
|
451
|
+
<head>
|
|
452
|
+
<title>Multiform Test - Sealgen</title>
|
|
453
|
+
<script type="module">
|
|
454
|
+
import hotwiredTurbo from "https://cdn.skypack.dev/pin/@hotwired/turbo@v7.0.0-9XNv1By6uiqRlBzQLO2d/mode=imports,min/optimized/@hotwired/turbo.js";
|
|
455
|
+
</script>
|
|
456
|
+
</head>
|
|
457
|
+
<body>
|
|
458
|
+
${super.render(...args)}
|
|
459
|
+
</body>
|
|
460
|
+
</html>`;
|
|
461
|
+
}
|
|
462
|
+
})(),
|
|
463
|
+
true
|
|
464
|
+
);
|
|
465
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
466
|
+
server = app.listen(8080);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
after(async () => {
|
|
470
|
+
server.close();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("js enabled", async () => general_form_validation_e2e_tests(true));
|
|
474
|
+
it("js disabled", async () => general_form_validation_e2e_tests(false));
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe("field specific validation", async () => {
|
|
478
|
+
let server: ReturnType<Koa["listen"]>;
|
|
479
|
+
|
|
480
|
+
before(async () => {
|
|
481
|
+
const app = new Koa();
|
|
482
|
+
const router = new Router();
|
|
483
|
+
const fields = {
|
|
484
|
+
text: new Fields.EmailField(true),
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
mount(
|
|
488
|
+
router,
|
|
489
|
+
"/",
|
|
490
|
+
new (class extends Multiform {
|
|
491
|
+
name = "multiform_test";
|
|
492
|
+
forms = {
|
|
493
|
+
form1: new (class extends Form<typeof fields, void> {
|
|
494
|
+
fields = fields;
|
|
495
|
+
submitButtonText = "Submit";
|
|
496
|
+
controls = [
|
|
497
|
+
new Controls.SimpleInput(fields.text, {
|
|
498
|
+
label: "This is a test:",
|
|
499
|
+
type: "text",
|
|
500
|
+
}),
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
async onSubmit() {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
})(),
|
|
507
|
+
form2: form_factory(),
|
|
508
|
+
};
|
|
509
|
+
async render(
|
|
510
|
+
...args: Parameters<Multiform["render"]>
|
|
511
|
+
): Promise<FlatTemplatable> {
|
|
512
|
+
return tempstream/* HTML */ `<!DOCTYPE html>
|
|
513
|
+
<html>
|
|
514
|
+
<head>
|
|
515
|
+
<title>Multiform Test - Sealgen</title>
|
|
516
|
+
<script type="module">
|
|
517
|
+
import hotwiredTurbo from "https://cdn.skypack.dev/pin/@hotwired/turbo@v7.0.0-9XNv1By6uiqRlBzQLO2d/mode=imports,min/optimized/@hotwired/turbo.js";
|
|
518
|
+
</script>
|
|
519
|
+
</head>
|
|
520
|
+
<body>
|
|
521
|
+
${super.render(...args)}
|
|
522
|
+
</body>
|
|
523
|
+
</html>`;
|
|
524
|
+
}
|
|
525
|
+
})(),
|
|
526
|
+
true
|
|
527
|
+
);
|
|
528
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
529
|
+
server = app.listen(8080);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
after(async () => {
|
|
533
|
+
server.close();
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe("js enabled", async () => individual_field_validation_e2e_tests(true));
|
|
537
|
+
describe("js disabled", async () => individual_field_validation_e2e_tests(false));
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
});
|
package/src/forms/multiform.ts
CHANGED
|
@@ -111,7 +111,7 @@ export class Multiform extends MountableWithFields {
|
|
|
111
111
|
sub_form_name: string
|
|
112
112
|
): Promise<Record<string, FormDataValue>> {
|
|
113
113
|
const result = Object.fromEntries(
|
|
114
|
-
Object.entries(this.extractRawValues(ctx))
|
|
114
|
+
Object.entries(await this.extractRawValues(ctx))
|
|
115
115
|
.filter(([key]) =>
|
|
116
116
|
key.startsWith(sub_form_name + FIELD_PREFIX_SEPARATOR)
|
|
117
117
|
)
|
|
@@ -138,7 +138,7 @@ export class Multiform extends MountableWithFields {
|
|
|
138
138
|
id="${this.name}"
|
|
139
139
|
novalidate
|
|
140
140
|
${
|
|
141
|
-
/* novalidate is here because all the subforms are
|
|
141
|
+
/* novalidate is here because all the subforms are actually one form and errors in one will prevent submitting all of the forms */ ""
|
|
142
142
|
}
|
|
143
143
|
method="POST"
|
|
144
144
|
></form>
|
|
@@ -203,6 +203,16 @@ export class Multiform extends MountableWithFields {
|
|
|
203
203
|
router.post(
|
|
204
204
|
path + (path.endsWith("/") ? "" : "/") + key,
|
|
205
205
|
async (ctx) => {
|
|
206
|
+
const result = await form.canAccess(ctx);
|
|
207
|
+
if (!result.canAccess) {
|
|
208
|
+
ctx.body = this.renderError(ctx, {
|
|
209
|
+
type: "access",
|
|
210
|
+
message: result.message,
|
|
211
|
+
});
|
|
212
|
+
ctx.status = 403;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
206
216
|
const reaction = await form.handlePost(ctx);
|
|
207
217
|
if (reaction.action == "stay") {
|
|
208
218
|
ctx.status = 422;
|
package/src/make-env.ts
CHANGED
|
@@ -7,7 +7,7 @@ const target_locreq = _locreq(process.cwd());
|
|
|
7
7
|
export async function makeEnv(): Promise<void> {
|
|
8
8
|
const { uid, gid } = os.userInfo();
|
|
9
9
|
const content = `USER_ID=${uid}
|
|
10
|
-
|
|
10
|
+
GROUP_ID=${gid}
|
|
11
11
|
`;
|
|
12
12
|
const path = target_locreq.resolve(".env");
|
|
13
13
|
await fs.writeFile(path, content);
|
package/src/mount.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Router from "@koa/router";
|
|
2
|
-
import Koa, { Context } from "koa";
|
|
2
|
+
import Koa, { BaseContext, Context } from "koa";
|
|
3
3
|
import { Middlewares } from "sealious";
|
|
4
4
|
import { Mountable } from "./page/mountable";
|
|
5
5
|
|
|
@@ -15,15 +15,49 @@ async function handleHtmlPromise(ctx: Context, next: Koa.Next) {
|
|
|
15
15
|
export function mount(
|
|
16
16
|
router: Router,
|
|
17
17
|
url: string | { rawURL: string },
|
|
18
|
-
mountable: Mountable
|
|
18
|
+
mountable: Mountable,
|
|
19
|
+
use_dummy_app = false
|
|
19
20
|
): void {
|
|
20
21
|
const raw_url = typeof url === "string" ? url : url.rawURL;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const args = use_dummy_app
|
|
23
|
+
? [
|
|
24
|
+
async (ctx: BaseContext, next: any) => {
|
|
25
|
+
// this dummy is a temporary solution it's ilustrating the ways that
|
|
26
|
+
// sealgen is coupled with sealious. Definitely room for improvement in this
|
|
27
|
+
// regard
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
29
|
+
ctx.$app = {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
31
|
+
Logger: new Proxy(
|
|
32
|
+
{},
|
|
33
|
+
{
|
|
34
|
+
get: (_target: any, prop: any) => {
|
|
35
|
+
if (prop === "info") {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
37
|
+
return () => {};
|
|
38
|
+
} else {
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
40
|
+
return () => {};
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
getString: (x: string) => x,
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
} as any;
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
49
|
+
await next();
|
|
50
|
+
},
|
|
51
|
+
Middlewares.parseBody(),
|
|
52
|
+
handleHtmlPromise,
|
|
53
|
+
]
|
|
54
|
+
: [
|
|
55
|
+
Middlewares.extractContext(),
|
|
56
|
+
Middlewares.parseBody(),
|
|
57
|
+
handleHtmlPromise,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
router.use(raw_url, ...args);
|
|
27
61
|
mountable.init();
|
|
28
62
|
|
|
29
63
|
// to automatically add trailing slashes:
|
|
@@ -31,7 +65,7 @@ export function mount(
|
|
|
31
65
|
const url2 = ctx.URL;
|
|
32
66
|
if (!url2.pathname.endsWith("/")) {
|
|
33
67
|
url2.pathname += "/";
|
|
34
|
-
ctx.redirect(url2.
|
|
68
|
+
ctx.redirect(url2.href);
|
|
35
69
|
}
|
|
36
70
|
await next();
|
|
37
71
|
});
|
|
@@ -11,7 +11,7 @@ import { Mountable } from "./mountable";
|
|
|
11
11
|
|
|
12
12
|
export type PageErrorMessage = { type: "access" | "internal"; message: string };
|
|
13
13
|
|
|
14
|
-
export type Fields = Record<string, FormField<boolean,
|
|
14
|
+
export type Fields = Record<string, FormField<boolean, any>>;
|
|
15
15
|
|
|
16
16
|
export abstract class MountableWithFields<
|
|
17
17
|
F extends Fields = Fields
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
import { Locator } from "playwright";
|
|
3
|
+
import { sleep } from "../utils/sleep";
|
|
4
|
+
|
|
5
|
+
export async function locator_is_visible(
|
|
6
|
+
locator: Locator,
|
|
7
|
+
timeout = 500
|
|
8
|
+
): Promise<boolean> {
|
|
9
|
+
let exists = false;
|
|
10
|
+
while (timeout > 0 && !exists) {
|
|
11
|
+
await sleep(50);
|
|
12
|
+
timeout -= 50;
|
|
13
|
+
exists = await locator.isVisible();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return exists;
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
|
|
3
|
+
export async function assertThrowsAsync(
|
|
4
|
+
fn: () => Promise<unknown>,
|
|
5
|
+
error_handler?: (e: unknown) => Promise<void> | void
|
|
6
|
+
): Promise<void> {
|
|
7
|
+
let error: unknown = null;
|
|
8
|
+
try {
|
|
9
|
+
await fn();
|
|
10
|
+
} catch (e: unknown) {
|
|
11
|
+
error = e;
|
|
12
|
+
} finally {
|
|
13
|
+
assert.notStrictEqual(error, null, "It didn't throw");
|
|
14
|
+
if (error_handler) {
|
|
15
|
+
await error_handler(error);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|