@sealcode/sealgen 0.15.21 → 0.15.23

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.
@@ -8,34 +8,41 @@ class CheckboxedListInput extends FormFieldControl {
8
8
  this.options = options;
9
9
  }
10
10
  async render(fctx) {
11
- const { parsed: currentValue } = await this.field.getValue(
12
- fctx.ctx,
13
- fctx.data
14
- );
11
+ const {
12
+ valid,
13
+ message,
14
+ parsed: currentValue
15
+ } = await this.field.getValue(fctx.ctx, fctx.data, true);
15
16
  const pickedValues = Object.entries(currentValue || {}).filter(([, is_on]) => is_on).map(([key]) => key);
16
17
  const [options, isVisible] = await Promise.all([
17
18
  this.field.generateOptions(fctx.ctx),
18
19
  this.field.isVisible(fctx.ctx)
19
20
  ]);
20
21
  const { name, required } = this.field;
21
- return tempstream`${isVisible ? options.map(
22
- ({ value, label }) => inputWrapper(
23
- [name, getRequiredClass(required)],
24
- /* HTML */
25
- `
26
- <input
27
- type="checkbox"
28
- id="${name}.${value}"
29
- name="${name}.${value}"
30
- ${pickedValues.includes(value) ? "checked" : ""}
31
- form=${fctx.form_id}
32
- />
33
- <label for="${name}.${value}"
34
- >${label || value}</label
35
- >
36
- `
37
- )
38
- ) : ""}`;
22
+ return inputWrapper(
23
+ [name, getRequiredClass(required)],
24
+ tempstream`<label
25
+ >${this.options.label || this.field.name}</label
26
+ >${isVisible ? options.map(
27
+ ({ value, label }) => inputWrapper(
28
+ [name],
29
+ /* HTML */
30
+ `
31
+ <input
32
+ type="checkbox"
33
+ id="${name}.${value}"
34
+ name="${name}.${value}"
35
+ ${pickedValues.includes(value) ? "checked" : ""}
36
+ form=${fctx.form_id}
37
+ />
38
+ <label for="${name}.${value}"
39
+ >${label || value}</label
40
+ >
41
+ `
42
+ )
43
+ ) : ""}
44
+ ${~valid && fctx.validate ? `<div class="input__error">${message}</div>` : ""}`
45
+ );
39
46
  }
40
47
  }
41
48
  export {
@@ -7,6 +7,7 @@ import { Form } from "../form.js";
7
7
  import { CheckboxedListInput } from "./checkboxed-list-input.js";
8
8
  import { getBrowser } from "../../utils/browser-creator.js";
9
9
  import assert from "assert";
10
+ import { sleep } from "../../utils/sleep.js";
10
11
  describe("checkboxed-list", () => {
11
12
  describe("basic tests", async () => {
12
13
  let server;
@@ -58,6 +59,178 @@ describe("checkboxed-list", () => {
58
59
  await page.getByRole("button", { name: "Wy\u015Blij" }).click();
59
60
  assert.deepStrictEqual(received_values, { syllables: { co: "on", de: "on" } });
60
61
  });
62
+ describe("behavior with (non)required", () => {
63
+ it("allows to send a form when no value is selected and fiels is not required", async () => {
64
+ const port = await getPort();
65
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
66
+ const app = new Koa();
67
+ const router = new Router();
68
+ const fields = {
69
+ syllables: new CheckboxedListField(false, async () => {
70
+ await sleep(10);
71
+ return ["sa", "mple", "co", "de"].map((s) => ({
72
+ value: s,
73
+ label: s.toLocaleUpperCase()
74
+ }));
75
+ })
76
+ };
77
+ let submit_called = false;
78
+ mount(
79
+ router,
80
+ "/",
81
+ new class extends Form {
82
+ constructor() {
83
+ super(...arguments);
84
+ this.fields = fields;
85
+ this.controls = [new CheckboxedListInput(fields.syllables)];
86
+ }
87
+ async onSubmit(ctx) {
88
+ submit_called = true;
89
+ }
90
+ async canAccess(_) {
91
+ return { canAccess: true, message: "" };
92
+ }
93
+ }(),
94
+ true
95
+ );
96
+ app.use(router.routes()).use(router.allowedMethods());
97
+ server = app.listen(port);
98
+ browser = await getBrowser();
99
+ const context = await browser.newContext();
100
+ page = await context.newPage();
101
+ await page.goto(`http://localhost:${port}`);
102
+ await page.getByRole("button", { name: "Wy\u015Blij" }).click();
103
+ await sleep(100);
104
+ assert(submit_called);
105
+ });
106
+ it("does not allow to send a form when no value is selected and field IS required", async () => {
107
+ const port = await getPort();
108
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
109
+ const app = new Koa();
110
+ const router = new Router();
111
+ const fields = {
112
+ syllables: new CheckboxedListField(true, async () => {
113
+ await sleep(10);
114
+ return ["sa", "mple", "co", "de"].map((s) => ({
115
+ value: s,
116
+ label: s.toLocaleUpperCase()
117
+ }));
118
+ })
119
+ };
120
+ let submit_called = false;
121
+ mount(
122
+ router,
123
+ "/",
124
+ new class extends Form {
125
+ constructor() {
126
+ super(...arguments);
127
+ this.fields = fields;
128
+ this.controls = [new CheckboxedListInput(fields.syllables)];
129
+ }
130
+ async onSubmit(ctx) {
131
+ submit_called = true;
132
+ }
133
+ async canAccess(_) {
134
+ return { canAccess: true, message: "" };
135
+ }
136
+ }(),
137
+ true
138
+ );
139
+ app.use(router.routes()).use(router.allowedMethods());
140
+ server = app.listen(port);
141
+ browser = await getBrowser();
142
+ const context = await browser.newContext();
143
+ page = await context.newPage();
144
+ await page.goto(`http://localhost:${port}`);
145
+ await page.getByRole("button", { name: "Wy\u015Blij" }).click();
146
+ await sleep(100);
147
+ assert(submit_called == false);
148
+ await page.getByText(/empty value/).click({ timeout: 500 });
149
+ });
150
+ it("allows to send a form when no value is selected and field IS required", async () => {
151
+ const port = await getPort();
152
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
153
+ const app = new Koa();
154
+ const router = new Router();
155
+ const fields = {
156
+ syllables: new CheckboxedListField(true, async () => {
157
+ await sleep(10);
158
+ return ["sa", "mple", "co", "de"].map((s) => ({
159
+ value: s,
160
+ label: s.toLocaleUpperCase()
161
+ }));
162
+ })
163
+ };
164
+ let submit_called = false;
165
+ mount(
166
+ router,
167
+ "/",
168
+ new class extends Form {
169
+ constructor() {
170
+ super(...arguments);
171
+ this.fields = fields;
172
+ this.controls = [new CheckboxedListInput(fields.syllables)];
173
+ }
174
+ async onSubmit(ctx) {
175
+ submit_called = true;
176
+ }
177
+ async canAccess(_) {
178
+ return { canAccess: true, message: "" };
179
+ }
180
+ }(),
181
+ true
182
+ );
183
+ app.use(router.routes()).use(router.allowedMethods());
184
+ server = app.listen(port);
185
+ browser = await getBrowser();
186
+ const context = await browser.newContext();
187
+ page = await context.newPage();
188
+ await page.goto(`http://localhost:${port}`);
189
+ await page.getByLabel("DE").check();
190
+ await page.getByRole("button", { name: "Wy\u015Blij" }).click();
191
+ await sleep(100);
192
+ assert(submit_called);
193
+ });
194
+ });
195
+ it("renders the label", async () => {
196
+ const port = await getPort();
197
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
198
+ const app = new Koa();
199
+ const router = new Router();
200
+ const fields = {
201
+ syllables: new CheckboxedListField(true, async () => {
202
+ return ["sa", "mple", "co", "de"].map((s) => ({
203
+ value: s,
204
+ label: s.toLocaleUpperCase()
205
+ }));
206
+ })
207
+ };
208
+ mount(
209
+ router,
210
+ "/",
211
+ new class extends Form {
212
+ constructor() {
213
+ super(...arguments);
214
+ this.fields = fields;
215
+ this.controls = [new CheckboxedListInput(fields.syllables, { label: "ABC" })];
216
+ }
217
+ async onSubmit(ctx) {
218
+ }
219
+ async canAccess(_) {
220
+ return { canAccess: true, message: "" };
221
+ }
222
+ }(),
223
+ true
224
+ );
225
+ app.use(router.routes()).use(router.allowedMethods());
226
+ server = app.listen(port);
227
+ browser = await getBrowser();
228
+ const context = await browser.newContext();
229
+ page = await context.newPage();
230
+ await page.goto(`http://localhost:${port}`);
231
+ await page.getByRole("button", { name: "Wy\u015Blij" }).click();
232
+ await page.getByText(/ABC/).click({ timeout: 500 });
233
+ });
61
234
  });
62
235
  });
63
236
  //# sourceMappingURL=checkboxed-list-input.test.js.map
@@ -40,7 +40,7 @@ class FormField {
40
40
  if (raw == void 0 || raw == null || raw == "") {
41
41
  return {
42
42
  parsed: null,
43
- message: this.required ? "empty value" : "",
43
+ message: this.required ? "empty value, but field is required" : "",
44
44
  raw,
45
45
  ...validate ? { valid: !this.required } : {}
46
46
  };
package/lib/forms/form.js CHANGED
@@ -119,7 +119,10 @@ class Form extends MountableWithFields {
119
119
  raw_values
120
120
  );
121
121
  if (!valid) {
122
- console.debug("Form values invalid: ", form_messages);
122
+ console.debug("Form values invalid: ", {
123
+ form_messages,
124
+ field_errors
125
+ });
123
126
  return this.onValuesInvalid(ctx, form_messages, field_errors);
124
127
  }
125
128
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.15.21",
3
+ "version": "0.15.23",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -9,6 +9,7 @@ import { Form } from "../form.js";
9
9
  import { CheckboxedListInput } from "./checkboxed-list-input.js";
10
10
  import { getBrowser } from "../../utils/browser-creator.js";
11
11
  import assert from "assert";
12
+ import { sleep } from "../../utils/sleep.js";
12
13
 
13
14
  describe("checkboxed-list", () => {
14
15
  describe("basic tests", async () => {
@@ -66,5 +67,193 @@ describe("checkboxed-list", () => {
66
67
  await page.getByRole("button", { name: "Wyślij" }).click();
67
68
  assert.deepStrictEqual(received_values, { syllables: { co: "on", de: "on" } });
68
69
  });
70
+
71
+ describe("behavior with (non)required", () => {
72
+ it("allows to send a form when no value is selected and fiels is not required", async () => {
73
+ const port = await getPort();
74
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
75
+ const app = new Koa();
76
+ const router = new Router();
77
+
78
+ const fields = {
79
+ syllables: new CheckboxedListField(false, async () => {
80
+ await sleep(10);
81
+ return ["sa", "mple", "co", "de"].map((s) => ({
82
+ value: s,
83
+ label: s.toLocaleUpperCase(),
84
+ }));
85
+ }),
86
+ };
87
+
88
+ let submit_called = false;
89
+
90
+ mount(
91
+ router,
92
+ "/",
93
+ new (class extends Form<typeof fields, unknown> {
94
+ fields = fields;
95
+ controls = [new CheckboxedListInput(fields.syllables)];
96
+
97
+ async onSubmit(ctx: Context) {
98
+ submit_called = true;
99
+ }
100
+
101
+ async canAccess(_: Context) {
102
+ return { canAccess: true, message: "" };
103
+ }
104
+ })(),
105
+ true
106
+ );
107
+ app.use(router.routes()).use(router.allowedMethods());
108
+ server = app.listen(port);
109
+
110
+ browser = await getBrowser();
111
+ const context = await browser.newContext();
112
+ page = await context.newPage();
113
+ await page.goto(`http://localhost:${port}`);
114
+ await page.getByRole("button", { name: "Wyślij" }).click();
115
+ await sleep(100);
116
+ assert(submit_called);
117
+ });
118
+
119
+ it("does not allow to send a form when no value is selected and field IS required", async () => {
120
+ const port = await getPort();
121
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
122
+ const app = new Koa();
123
+ const router = new Router();
124
+
125
+ const fields = {
126
+ syllables: new CheckboxedListField(true, async () => {
127
+ await sleep(10);
128
+ return ["sa", "mple", "co", "de"].map((s) => ({
129
+ value: s,
130
+ label: s.toLocaleUpperCase(),
131
+ }));
132
+ }),
133
+ };
134
+
135
+ let submit_called = false;
136
+
137
+ mount(
138
+ router,
139
+ "/",
140
+ new (class extends Form<typeof fields, unknown> {
141
+ fields = fields;
142
+ controls = [new CheckboxedListInput(fields.syllables)];
143
+
144
+ async onSubmit(ctx: Context) {
145
+ submit_called = true;
146
+ }
147
+
148
+ async canAccess(_: Context) {
149
+ return { canAccess: true, message: "" };
150
+ }
151
+ })(),
152
+ true
153
+ );
154
+ app.use(router.routes()).use(router.allowedMethods());
155
+ server = app.listen(port);
156
+
157
+ browser = await getBrowser();
158
+ const context = await browser.newContext();
159
+ page = await context.newPage();
160
+ await page.goto(`http://localhost:${port}`);
161
+ await page.getByRole("button", { name: "Wyślij" }).click();
162
+ await sleep(100);
163
+ assert(submit_called == false);
164
+ await page.getByText(/empty value/).click({ timeout: 500 }); // should display an error message for that field
165
+ });
166
+
167
+ it("allows to send a form when no value is selected and field IS required", async () => {
168
+ const port = await getPort();
169
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
170
+ const app = new Koa();
171
+ const router = new Router();
172
+
173
+ const fields = {
174
+ syllables: new CheckboxedListField(true, async () => {
175
+ await sleep(10);
176
+ return ["sa", "mple", "co", "de"].map((s) => ({
177
+ value: s,
178
+ label: s.toLocaleUpperCase(),
179
+ }));
180
+ }),
181
+ };
182
+
183
+ let submit_called = false;
184
+
185
+ mount(
186
+ router,
187
+ "/",
188
+ new (class extends Form<typeof fields, unknown> {
189
+ fields = fields;
190
+ controls = [new CheckboxedListInput(fields.syllables)];
191
+
192
+ async onSubmit(ctx: Context) {
193
+ submit_called = true;
194
+ }
195
+
196
+ async canAccess(_: Context) {
197
+ return { canAccess: true, message: "" };
198
+ }
199
+ })(),
200
+ true
201
+ );
202
+ app.use(router.routes()).use(router.allowedMethods());
203
+ server = app.listen(port);
204
+
205
+ browser = await getBrowser();
206
+ const context = await browser.newContext();
207
+ page = await context.newPage();
208
+ await page.goto(`http://localhost:${port}`);
209
+ await page.getByLabel("DE").check();
210
+ await page.getByRole("button", { name: "Wyślij" }).click();
211
+ await sleep(100);
212
+ assert(submit_called);
213
+ });
214
+ });
215
+
216
+ it("renders the label", async () => {
217
+ const port = await getPort();
218
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
219
+ const app = new Koa();
220
+ const router = new Router();
221
+
222
+ const fields = {
223
+ syllables: new CheckboxedListField(true, async () => {
224
+ return ["sa", "mple", "co", "de"].map((s) => ({
225
+ value: s,
226
+ label: s.toLocaleUpperCase(),
227
+ }));
228
+ }),
229
+ };
230
+
231
+ mount(
232
+ router,
233
+ "/",
234
+ new (class extends Form<typeof fields, unknown> {
235
+ fields = fields;
236
+ controls = [new CheckboxedListInput(fields.syllables, { label: "ABC" })];
237
+
238
+ async onSubmit(ctx: Context) {
239
+ // noop
240
+ }
241
+
242
+ async canAccess(_: Context) {
243
+ return { canAccess: true, message: "" };
244
+ }
245
+ })(),
246
+ true
247
+ );
248
+ app.use(router.routes()).use(router.allowedMethods());
249
+ server = app.listen(port);
250
+
251
+ browser = await getBrowser();
252
+ const context = await browser.newContext();
253
+ page = await context.newPage();
254
+ await page.goto(`http://localhost:${port}`);
255
+ await page.getByRole("button", { name: "Wyślij" }).click();
256
+ await page.getByText(/ABC/).click({ timeout: 500 }); // should display an error message for that field
257
+ });
69
258
  });
70
259
  });
@@ -13,10 +13,11 @@ export class CheckboxedListInput extends FormFieldControl {
13
13
  }
14
14
 
15
15
  async render(fctx: FormControlContext) {
16
- const { parsed: currentValue } = await this.field.getValue(
17
- fctx.ctx,
18
- fctx.data
19
- );
16
+ const {
17
+ valid,
18
+ message,
19
+ parsed: currentValue,
20
+ } = await this.field.getValue(fctx.ctx, fctx.data, true);
20
21
  const pickedValues = Object.entries(currentValue || {})
21
22
  .filter(([, is_on]) => is_on)
22
23
  .map(([key]) => key);
@@ -26,24 +27,34 @@ export class CheckboxedListInput extends FormFieldControl {
26
27
  ]);
27
28
 
28
29
  const { name, required } = this.field;
29
- return tempstream/* HTML */ `${isVisible
30
- ? options.map(({ value, label }) =>
31
- inputWrapper(
32
- [name, getRequiredClass(required)],
33
- /* HTML */ `
34
- <input
35
- type="checkbox"
36
- id="${name}.${value}"
37
- name="${name}.${value}"
38
- ${pickedValues.includes(value) ? "checked" : ""}
39
- form=${fctx.form_id}
40
- />
41
- <label for="${name}.${value}"
42
- >${label || value}</label
43
- >
44
- `
45
- )
46
- )
47
- : ""}`;
30
+ return inputWrapper(
31
+ [name, getRequiredClass(required)],
32
+ tempstream/* HTML */ `<label
33
+ >${this.options.label || this.field.name}</label
34
+ >${isVisible
35
+ ? options.map(({ value, label }) =>
36
+ inputWrapper(
37
+ [name],
38
+ /* HTML */ `
39
+ <input
40
+ type="checkbox"
41
+ id="${name}.${value}"
42
+ name="${name}.${value}"
43
+ ${pickedValues.includes(value)
44
+ ? "checked"
45
+ : ""}
46
+ form=${fctx.form_id}
47
+ />
48
+ <label for="${name}.${value}"
49
+ >${label || value}</label
50
+ >
51
+ `
52
+ )
53
+ )
54
+ : ""}
55
+ ${~valid && fctx.validate
56
+ ? `<div class="input__error">${message}</div>`
57
+ : ""}`
58
+ );
48
59
  }
49
60
  }
@@ -130,7 +130,9 @@ export abstract class FormField<
130
130
  if (raw == undefined || raw == null || raw == "") {
131
131
  return {
132
132
  parsed: null,
133
- message: this.required ? "empty value" : "",
133
+ message: this.required
134
+ ? "empty value, but field is required"
135
+ : "",
134
136
  raw,
135
137
  ...(validate ? { valid: !this.required } : {}),
136
138
  };
package/src/forms/form.ts CHANGED
@@ -181,7 +181,10 @@ export abstract class Form<
181
181
  raw_values
182
182
  );
183
183
  if (!valid) {
184
- console.debug("Form values invalid: ", form_messages);
184
+ console.debug("Form values invalid: ", {
185
+ form_messages,
186
+ field_errors,
187
+ });
185
188
  return this.onValuesInvalid(ctx, form_messages, field_errors);
186
189
  }
187
190
  try {