@sealcode/sealgen 0.8.48 → 0.8.49

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.
Files changed (136) hide show
  1. package/.log/ti-107027.log +576 -0
  2. package/.log/ti-129414.log +1355 -0
  3. package/.log/ti-135707.log +1049 -0
  4. package/.log/ti-136226.log +1049 -0
  5. package/.log/ti-147652.log +533 -0
  6. package/.log/ti-157088.log +2938 -0
  7. package/.log/ti-250667.log +548 -0
  8. package/.log/ti-29438.log +165 -0
  9. package/.log/ti-29894.log +149 -0
  10. package/.log/ti-33785.log +149 -0
  11. package/.log/ti-34262.log +149 -0
  12. package/.log/ti-35719.log +149 -0
  13. package/.log/ti-41629.log +518 -0
  14. package/.log/ti-441248.log +517 -0
  15. package/.log/ti-5352.log +517 -0
  16. package/.log/ti-76335.log +639 -0
  17. package/.log/ti-811639.log +517 -0
  18. package/.log/ti-81805.log +564 -0
  19. package/.log/ti-86459.log +517 -0
  20. package/.log/tsserver.log +6881 -0
  21. package/.log/tsserver.sync-conflict-20221010-222251-JUX7UR2.log +9412 -0
  22. package/package.json +1 -1
  23. package/src/add-collection.ts +31 -0
  24. package/src/add-route.test.ts +58 -0
  25. package/src/add-route.ts +89 -0
  26. package/src/build.ts +94 -0
  27. package/src/cli.ts +36 -0
  28. package/src/components/components.ts +2 -0
  29. package/src/components/tabs.ts +95 -0
  30. package/src/components/toggle-modal.ts +52 -0
  31. package/src/example.test.ts +3 -0
  32. package/src/find-scss-includes.ts +32 -0
  33. package/src/forms/controls/array.ts +226 -0
  34. package/src/forms/controls/autocomplete.ts +33 -0
  35. package/src/forms/controls/big-radio-group.ts +48 -0
  36. package/src/forms/controls/checkbox-group.ts +71 -0
  37. package/src/forms/controls/checkbox.ts +34 -0
  38. package/src/forms/controls/checkboxed-list-input.ts +50 -0
  39. package/src/forms/controls/collapsible-checkbox-group.ts +28 -0
  40. package/src/forms/controls/collapsible-field-group.ts +94 -0
  41. package/src/forms/controls/controls.ts +30 -0
  42. package/src/forms/controls/derived-frame.ts +91 -0
  43. package/src/forms/controls/derived.ts +108 -0
  44. package/src/forms/controls/dropdown.ts +87 -0
  45. package/src/forms/controls/editable-collection-subset.ts +97 -0
  46. package/src/forms/controls/field-group.ts +69 -0
  47. package/src/forms/controls/file.ts +47 -0
  48. package/src/forms/controls/form-control.ts +24 -0
  49. package/src/forms/controls/form-field-control.ts +22 -0
  50. package/src/forms/controls/form-header.ts +19 -0
  51. package/src/forms/controls/form-paragraph.ts +11 -0
  52. package/src/forms/controls/frame.ts +26 -0
  53. package/src/forms/controls/hidden-input.ts +22 -0
  54. package/src/forms/controls/html.ts +38 -0
  55. package/src/forms/controls/multiple-files.ts +256 -0
  56. package/src/forms/controls/multiple-photos.ts +44 -0
  57. package/src/forms/controls/number.ts +38 -0
  58. package/src/forms/controls/photo.ts +74 -0
  59. package/src/forms/controls/radio.ts +43 -0
  60. package/src/forms/controls/reverse-single-reference.ts +50 -0
  61. package/src/forms/controls/simple-input.ts +148 -0
  62. package/src/forms/controls/textarea.ts +60 -0
  63. package/src/forms/controls/tickable.ts +127 -0
  64. package/src/forms/fields/boolean.test.ts +36 -0
  65. package/src/forms/fields/boolean.ts +63 -0
  66. package/src/forms/fields/checkboxed-list.ts +66 -0
  67. package/src/forms/fields/collection-field.ts +62 -0
  68. package/src/forms/fields/email.test.ts +24 -0
  69. package/src/forms/fields/email.ts +20 -0
  70. package/src/forms/fields/enum-multiple.test.ts +31 -0
  71. package/src/forms/fields/enum-multiple.ts +62 -0
  72. package/src/forms/fields/enum.test.ts +25 -0
  73. package/src/forms/fields/enum.ts +56 -0
  74. package/src/forms/fields/existing-sealious-value.ts +22 -0
  75. package/src/forms/fields/field.ts +172 -0
  76. package/src/forms/fields/file.ts +89 -0
  77. package/src/forms/fields/multiple-files.ts +20 -0
  78. package/src/forms/fields/number.test.ts +34 -0
  79. package/src/forms/fields/number.ts +70 -0
  80. package/src/forms/fields/pick-from-list.test.ts +19 -0
  81. package/src/forms/fields/pick-from-list.ts +52 -0
  82. package/src/forms/fields/proxy-field.ts +24 -0
  83. package/src/forms/fields/regexp.test.ts +25 -0
  84. package/src/forms/fields/regexp.ts +53 -0
  85. package/src/forms/fields/reverse-single-reference.ts +20 -0
  86. package/src/forms/fields/simple-form-field.ts +30 -0
  87. package/src/forms/fields/structured-array.ts +27 -0
  88. package/src/forms/form.test.ts +351 -0
  89. package/src/forms/form.ts +294 -0
  90. package/src/forms/forms.scss +85 -0
  91. package/src/forms/multiform.test.ts +540 -0
  92. package/src/forms/multiform.ts +236 -0
  93. package/src/generate-collections.ts +77 -0
  94. package/src/generate-routes.ts +121 -0
  95. package/src/index.ts +25 -0
  96. package/src/make-env.ts +15 -0
  97. package/src/make-hidden-inputs.ts +24 -0
  98. package/src/mount.ts +85 -0
  99. package/src/page/access-control.ts +18 -0
  100. package/src/page/list.ts +240 -0
  101. package/src/page/mountable-with-fields.ts +158 -0
  102. package/src/page/mountable.ts +13 -0
  103. package/src/page/page.ts +46 -0
  104. package/src/page/props-parser.ts +38 -0
  105. package/src/page/stateful-page.ts +202 -0
  106. package/src/run.ts +0 -0
  107. package/src/sanitize.ts +9 -0
  108. package/src/templates/collection.ts +11 -0
  109. package/src/templates/form.ts +60 -0
  110. package/src/templates/list.ts +75 -0
  111. package/src/templates/multiform.ts +58 -0
  112. package/src/templates/page.ts +25 -0
  113. package/src/templates/post.ts +21 -0
  114. package/src/templates/redirect.ts +22 -0
  115. package/src/templates/route-test.ts +27 -0
  116. package/src/templates/stateful-page.ts +65 -0
  117. package/src/test_utils/locator_is_visible.ts +17 -0
  118. package/src/utils/assert-throws.ts +18 -0
  119. package/src/utils/base64.ts +7 -0
  120. package/src/utils/browser-creator.js +10 -0
  121. package/src/utils/escape-url-params.ts +14 -0
  122. package/src/utils/exec.ts +30 -0
  123. package/src/utils/format-external-url.ts +13 -0
  124. package/src/utils/import-path.ts +25 -0
  125. package/src/utils/input-wrapper.ts +21 -0
  126. package/src/utils/list-collections.ts +16 -0
  127. package/src/utils/make-slug.ts +5 -0
  128. package/src/utils/natural-numbers.ts +5 -0
  129. package/src/utils/prettier.ts +13 -0
  130. package/src/utils/question.ts +23 -0
  131. package/src/utils/render-attributes.ts +17 -0
  132. package/src/utils/sleep.ts +3 -0
  133. package/src/utils/translate.ts +13 -0
  134. package/src/utils/url-with-new-params.ts +9 -0
  135. package/src/utils/utils.ts +23 -0
  136. package/src/utils/walk.ts +20 -0
@@ -0,0 +1,60 @@
1
+ import { Context } from "koa";
2
+ import { FormField } from "../fields/field";
3
+ import { FormDataValue } from "../form";
4
+ import { FormControlContext } from "./form-control";
5
+ import { SimpleInput, SimpleInputOptions } from "./simple-input";
6
+
7
+ export class Textarea<Required extends boolean> extends SimpleInput<
8
+ string | null
9
+ > {
10
+ constructor(
11
+ public field: FormField<boolean, string | null>,
12
+ public options: SimpleInputOptions & {
13
+ label?: string;
14
+ rows?: number;
15
+ cols?: number;
16
+ description?: string;
17
+ autogrow?: boolean;
18
+ } = {}
19
+ ) {
20
+ super(field, options);
21
+ }
22
+
23
+ renderDescription() {
24
+ return this.options.description
25
+ ? /* HTML */ `<div class="form-input__description">
26
+ ${this.options.description}
27
+ </div>`
28
+ : "";
29
+ }
30
+
31
+ async getInputAttributes(
32
+ fctx: FormControlContext
33
+ ): Promise<Record<string, string | boolean>> {
34
+ const original = (await super.getInputAttributes(fctx)) as Record<
35
+ string,
36
+ string | boolean
37
+ >;
38
+ let onkeydown = original.onkeydown || "";
39
+ if (this.options.autogrow) {
40
+ onkeydown += `;this.style.height = '5px';this.style.height = Math.max((this.scrollHeight)+32, ${
41
+ ((this.options.rows || 2) + 1) * 16
42
+ } )+'px';`;
43
+ }
44
+ return {
45
+ ...original,
46
+ rows: this.options.rows ? this.options.rows.toString() : false,
47
+ cols: this.options.cols ? this.options.cols.toString() : false,
48
+ onkeydown,
49
+ };
50
+ }
51
+
52
+ async renderInput(
53
+ ctx: Context,
54
+ attributes: string,
55
+ data: Record<string, FormDataValue>
56
+ ) {
57
+ const { parsed: value } = await this.field.getValue(ctx, data);
58
+ return /* HTML */ `<textarea ${attributes}>${value}</textarea>`;
59
+ }
60
+ }
@@ -0,0 +1,127 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable } from "tempstream";
3
+ import { attribute } from "../../sanitize";
4
+ import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
5
+ import { FormField } from "../fields/field";
6
+ import { FormDataValue } from "../form";
7
+ import { FormFieldControl } from "./controls";
8
+ import { FormControlContext } from "./form-control";
9
+
10
+ export type TickableOptions = {
11
+ id?: string;
12
+ name?: string;
13
+ label?: string;
14
+ hide_errors?: boolean;
15
+ readonly?: boolean;
16
+ };
17
+
18
+ export abstract class Tickable<T extends unknown> extends FormFieldControl {
19
+ constructor(
20
+ public field: FormField<boolean, T>,
21
+ public options: TickableOptions = {}
22
+ ) {
23
+ super([field]);
24
+ }
25
+
26
+ public type: "checkbox" | "radio";
27
+
28
+ abstract getValueAttribute(
29
+ ctx: Context,
30
+ data: Record<string, FormDataValue>,
31
+ value: T
32
+ ): string;
33
+
34
+ abstract isChecked(
35
+ ctx: Context,
36
+ data: Record<string, FormDataValue>,
37
+ value: T
38
+ ): boolean;
39
+
40
+ makeInputID(
41
+ ctx: Context,
42
+ data: Record<string, FormDataValue>,
43
+ value: T
44
+ ): string {
45
+ const value_attr = this.getValueAttribute(ctx, data, value);
46
+ return (
47
+ this.options.id ||
48
+ this.field.name + (value_attr ? "--" + value_attr : "")
49
+ );
50
+ }
51
+
52
+ getWrapperClasses(
53
+ ctx: Context,
54
+ data: Record<string, FormDataValue>,
55
+ value: T
56
+ ): string[] {
57
+ return [
58
+ this.makeInputID(ctx, data, value),
59
+ this.type,
60
+ this.field.name.replaceAll(/\W/g, "-"),
61
+ getRequiredClass(this.field.required),
62
+ ...(this.isChecked(ctx, data, value) ? ["checked"] : []),
63
+ ];
64
+ }
65
+
66
+ async render<Fields extends Record<string, FormField>>(
67
+ fctx: FormControlContext
68
+ ): Promise<FlatTemplatable> {
69
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
70
+ const { parsed, valid, message } = await this.field.getValue(
71
+ fctx.ctx,
72
+ fctx.data
73
+ );
74
+ const value = this.getValueAttribute(fctx.ctx, fctx.data, parsed);
75
+ const id = this.makeInputID(fctx.ctx, fctx.data, parsed);
76
+ const label =
77
+ this.options.label != undefined
78
+ ? this.options.label
79
+ : this.field.name;
80
+
81
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
82
+ const readonly = this.options.readonly || false;
83
+ const required = this.field.required;
84
+
85
+ return inputWrapper(
86
+ this.getWrapperClasses(fctx.ctx, fctx.data, parsed),
87
+
88
+ /* HTML */ `
89
+ <input
90
+ id="${id}"
91
+ type="${this.type}"
92
+ name="${fctx.field_name_prefix}${this.options.name ||
93
+ this.field.name}"
94
+ ${value ? `value="${attribute(value)}"` : ""}
95
+ ${fctx.form_id ? `form="${fctx.form_id}"` : ""}
96
+ ${this.isChecked(fctx.ctx, fctx.data, parsed)
97
+ ? "checked"
98
+ : ""}
99
+ ${readonly ? "readonly" : ""}
100
+ ${required ? "required" : ""}
101
+ autocomplete="off"
102
+ onchange="this.closest('.form-container').querySelectorAll('.form-input__wrapper--${this
103
+ .field
104
+ .name}').forEach(e=>e.setAttribute('data-checked','false'));
105
+ this.closest('.form-input__wrapper').setAttribute('data-checked',this.checked);"
106
+ />
107
+
108
+ <label for="${id}">${label}</label>
109
+ <script>
110
+ (function () {
111
+ let wrapper = document.currentScript.closest(
112
+ ".form-input__wrapper"
113
+ );
114
+ wrapper.setAttribute(
115
+ "data-checked",
116
+ wrapper.querySelector("input").checked.toString()
117
+ );
118
+ })();
119
+ </script>
120
+
121
+ ${~valid && !this.options.hide_errors && fctx.validate
122
+ ? `<div class="input__error">${message}</div>`
123
+ : ""}
124
+ `
125
+ );
126
+ }
127
+ }
@@ -0,0 +1,36 @@
1
+ import assert from "assert";
2
+ import { Fields } from "../../index";
3
+
4
+ describe("boolean field", () => {
5
+ const ctx = {} as any;
6
+
7
+ it("validation logic", async () => {
8
+ const required = new Fields.Boolean(true);
9
+ const not_required = new Fields.Boolean(false);
10
+
11
+ assert.strictEqual((await required.isValueValid(ctx, true)).valid, true);
12
+ assert.strictEqual((await required.isValueValid(ctx, false)).valid, false);
13
+ assert.strictEqual((await not_required.isValueValid(ctx, true)).valid, true);
14
+ assert.strictEqual((await not_required.isValueValid(ctx, false)).valid, true);
15
+ });
16
+
17
+ it("parse function", async () => {
18
+ const field = new Fields.Boolean(true);
19
+ const true_response = await field.parse(ctx, true as any);
20
+ const null_response = await field.parse(ctx, null as any);
21
+ const true_as_string_response = await field.parse(ctx, "true");
22
+ const undefined_response = await field.parse(ctx, undefined);
23
+ const number_response = await field.parse(ctx, 42);
24
+
25
+ assert.strictEqual(true_response.parsable, true);
26
+ assert.strictEqual(true_response.parsed_value, true);
27
+ assert.strictEqual(null_response.parsable, true);
28
+ assert.strictEqual(null_response.parsed_value, false);
29
+ assert.strictEqual(true_as_string_response.parsable, true);
30
+ assert.strictEqual(true_as_string_response.parsed_value, true);
31
+ assert.strictEqual(undefined_response.parsable, true);
32
+ assert.strictEqual(undefined_response.parsed_value, false);
33
+ assert.strictEqual(number_response.parsable, false);
34
+ assert.strictEqual(number_response.error, "Please enter a boolean");
35
+ });
36
+ });
@@ -0,0 +1,63 @@
1
+ import { is, predicates } from "@sealcode/ts-predicates";
2
+ import { Context } from "koa";
3
+ import { FormDataValue } from "../form";
4
+ import { FieldParseResult, FormField } from "./field";
5
+
6
+ const truthy = ["true", "on", "yes", "1"];
7
+ const falsy = ["false", "off", "no", "0"];
8
+
9
+ export class Boolean<Required extends boolean> extends FormField<
10
+ Required,
11
+ boolean
12
+ > {
13
+ predicate = predicates.boolean;
14
+ constructor(required: Required, public error_message: string = "") {
15
+ super(required);
16
+ }
17
+
18
+ public async isValueValid(
19
+ _: Context,
20
+ value: boolean
21
+ ): Promise<{ valid: boolean; message: string }> {
22
+ if (this.required && !value) {
23
+ return { valid: false, message: this.error_message };
24
+ }
25
+ return { valid: true, message: "" };
26
+ }
27
+
28
+ async parse(
29
+ _: Context,
30
+ value: FormDataValue
31
+ ): Promise<FieldParseResult<boolean>> {
32
+ if (is(value, predicates.boolean)) {
33
+ return { parsable: true, parsed_value: value, error: null };
34
+ } else if (
35
+ is(value, predicates.string) &&
36
+ [...truthy, ...falsy].includes(value)
37
+ ) {
38
+ return {
39
+ parsable: true,
40
+ parsed_value: truthy.includes(value),
41
+ error: null,
42
+ };
43
+ } else if (is(value, predicates.undefined)) {
44
+ return {
45
+ parsable: true,
46
+ parsed_value: false,
47
+ error: null,
48
+ };
49
+ } else if (value === null) {
50
+ return { parsable: true, parsed_value: false, error: null };
51
+ } else {
52
+ return {
53
+ parsable: false,
54
+ parsed_value: null,
55
+ error: "Please enter a boolean",
56
+ };
57
+ }
58
+ }
59
+
60
+ public getEmptyValue(): boolean {
61
+ return false;
62
+ }
63
+ }
@@ -0,0 +1,66 @@
1
+ import { is, predicates } from "@sealcode/ts-predicates";
2
+ import { Context } from "koa";
3
+ import { FormDataValue } from "../form";
4
+ import {
5
+ FieldParseResult,
6
+ FormField,
7
+ FormFieldValidationResponse,
8
+ } from "./field";
9
+
10
+ export class ChekboxedListField<Required extends boolean> extends FormField<
11
+ Required,
12
+ Record<string, "on">
13
+ > {
14
+ constructor(
15
+ public required: Required,
16
+ public generateOptions: (
17
+ ctx: Context
18
+ ) => Promise<Record<string, string> | { [i: string]: string }>,
19
+ public isVisible: (ctx: Context) => Promise<boolean> = () =>
20
+ Promise.resolve(true)
21
+ ) {
22
+ super(required);
23
+ }
24
+
25
+ predicate = predicates.array(predicates.string); // TODO: this is probably wrong
26
+
27
+ public async isValueValid(
28
+ _: Context,
29
+ value: unknown
30
+ ): Promise<FormFieldValidationResponse> {
31
+ if (is(value, predicates.string)) {
32
+ return { valid: false, message: "you need an array" };
33
+ }
34
+ if (is(value, predicates.null)) {
35
+ return { valid: false, message: "you need an array" };
36
+ }
37
+ return { valid: true, message: "" };
38
+ }
39
+
40
+ async parse(
41
+ _: Context,
42
+ raw_value: FormDataValue
43
+ ): Promise<FieldParseResult<Record<string, "on">>> {
44
+ if (is(raw_value, predicates.object)) {
45
+ return {
46
+ parsable: true,
47
+ parsed_value: Object.fromEntries(
48
+ Object.entries(raw_value).filter(
49
+ ([, value]) => value === "on"
50
+ )
51
+ ) as Record<string, "on">,
52
+ error: null,
53
+ };
54
+ } else {
55
+ return {
56
+ parsed_value: null,
57
+ parsable: false,
58
+ error: "Expected an object",
59
+ };
60
+ }
61
+ }
62
+
63
+ public getEmptyValue(): Record<string, "on"> {
64
+ return {};
65
+ }
66
+ }
@@ -0,0 +1,62 @@
1
+ import { predicates } from "@sealcode/ts-predicates";
2
+ import { Context } from "koa";
3
+ import { Depromisify, Field, FieldOutput } from "sealious";
4
+ import { FormDataValue } from "../form";
5
+
6
+ import {
7
+ FieldParseResult,
8
+ FormField,
9
+ FormFieldValidationResponse,
10
+ } from "./field";
11
+
12
+ export class CollectionField<
13
+ F extends Field,
14
+ Required extends boolean
15
+ > extends FormField<Required, FieldOutput<F> | null> {
16
+ constructor(required: Required, public sealiousField: F) {
17
+ super(required);
18
+ }
19
+
20
+ predicate = predicates.unknown;
21
+
22
+ async parse(
23
+ ctx: Context,
24
+ raw_value: FormDataValue
25
+ ): Promise<FieldParseResult<FieldOutput<F> | null>> {
26
+ if (raw_value === undefined) {
27
+ return { parsable: true, parsed_value: null, error: null };
28
+ }
29
+ const encoded_value = (await this.sealiousField.encode(
30
+ ctx.$context,
31
+ raw_value
32
+ )) as Depromisify<ReturnType<F["encode"]>>;
33
+ const parsed_value = (await this.sealiousField.decode(
34
+ ctx.$context,
35
+ encoded_value,
36
+ null,
37
+ {}
38
+ )) as Depromisify<ReturnType<F["encode"]>>;
39
+ return {
40
+ parsable: true,
41
+ error: null,
42
+ parsed_value,
43
+ };
44
+ }
45
+
46
+ public async isValueValid(
47
+ ctx: Context,
48
+ value: FieldOutput<F>
49
+ ): Promise<FormFieldValidationResponse> {
50
+ const { valid, reason } = await this.sealiousField.checkValue(
51
+ ctx.$context,
52
+ value,
53
+ undefined,
54
+ null
55
+ );
56
+ return { valid, message: reason || (!valid ? "Wrong value" : "") };
57
+ }
58
+
59
+ public getEmptyValue(): Depromisify<ReturnType<F["decode"]>> {
60
+ return "" as unknown as Depromisify<ReturnType<F["decode"]>>;
61
+ }
62
+ }
@@ -0,0 +1,24 @@
1
+ import assert from "assert";
2
+ import { Fields } from "../../index";
3
+
4
+ describe("email field", () => {
5
+ const ctx = {} as any;
6
+
7
+ it("validation logic", async () => {
8
+ const field = new Fields.EmailField(true);
9
+
10
+ assert.strictEqual((await field.isValueValid(ctx, "yes@an.email")).valid, true);
11
+ assert.strictEqual((await field.isValueValid(ctx, "notanemail")).valid, false);
12
+ });
13
+
14
+ it("parse function", async () => {
15
+ const field = new Fields.EmailField(true);
16
+ const string_response = await field.parse(ctx, "42");
17
+ const number_response = await field.parse(ctx, 42);
18
+
19
+ assert.strictEqual(string_response.parsable, true);
20
+ assert.strictEqual(string_response.parsed_value, "42");
21
+ assert.strictEqual(number_response.parsable, false);
22
+ assert.strictEqual(number_response.error, "Please enter a string");
23
+ });
24
+ });
@@ -0,0 +1,20 @@
1
+ import { predicates } from "@sealcode/ts-predicates";
2
+ import { RegExpField } from "./regexp";
3
+
4
+ export class EmailField<
5
+ Required extends boolean
6
+ > extends RegExpField<Required> {
7
+ predicate = predicates.string;
8
+ constructor(
9
+ required: Required,
10
+ public error_message = "Please enter a proper email address",
11
+ public default_value = ""
12
+ ) {
13
+ super(
14
+ required,
15
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
16
+ error_message,
17
+ default_value
18
+ );
19
+ }
20
+ }
@@ -0,0 +1,31 @@
1
+ import assert from "assert";
2
+ import { Fields } from "../../index";
3
+
4
+ describe("enum-multiple field", () => {
5
+ const ctx = {} as any;
6
+
7
+ it("validation logic", async () => {
8
+ const field = new Fields.EnumMultipleField(true, ["sa", "mp", "le"]);
9
+
10
+ assert.strictEqual((await field.isValueValid(ctx, ["sa", "le"])).valid, true);
11
+ assert.strictEqual((await field.isValueValid(ctx, ["notvalid"])).valid, false);
12
+ assert.strictEqual((await field.isValueValid(ctx, [42] as any)).valid, false);
13
+ });
14
+
15
+ it("parse function", async () => {
16
+ const field = new Fields.EnumMultipleField(true, []);
17
+ const string_array_response = await field.parse(ctx, ["42"]);
18
+ const number_response = await field.parse(ctx, 42);
19
+ const string_response = await field.parse(ctx, "sample");
20
+ const null_response = await field.parse(ctx, null as any);
21
+
22
+ assert.strictEqual(string_array_response.parsable, true);
23
+ assert.deepEqual(string_array_response.parsed_value, ["42"]);
24
+ assert.strictEqual(number_response.parsable, false);
25
+ assert.strictEqual(number_response.error, "Please enter a list of strings");
26
+ assert.strictEqual(string_response.parsable, true);
27
+ assert.deepEqual(string_response.parsed_value, ["sample"]);
28
+ assert.strictEqual(null_response.parsable, true);
29
+ assert.deepEqual(null_response.parsed_value, []);
30
+ });
31
+ });
@@ -0,0 +1,62 @@
1
+ import { is, predicates } from "@sealcode/ts-predicates";
2
+ import { Context } from "koa";
3
+ import { FormDataValue } from "../form";
4
+ import { FieldParseResult, FormField } from "./field";
5
+
6
+ export class EnumMultipleField<
7
+ Options extends readonly string[],
8
+ Required extends boolean
9
+ > extends FormField<Required, string[]> {
10
+ constructor(
11
+ required: Required,
12
+ public options: Options,
13
+ public error_message: (options: Options) => string = (options) =>
14
+ options.join(", ")
15
+ ) {
16
+ super(required);
17
+ }
18
+
19
+ predicate = predicates.enum<Options>(this.options);
20
+
21
+ public async isValueValid(
22
+ _: Context,
23
+ values: string[]
24
+ ): Promise<{ valid: boolean; message: string }> {
25
+ for (const value of values) {
26
+ if (!is(value, predicates.string)) {
27
+ return { valid: false, message: "expected a string" };
28
+ }
29
+ if (!this.options.includes(value)) {
30
+ return { valid: false, message: "invalid value" };
31
+ }
32
+ }
33
+ return { valid: true, message: "" };
34
+ }
35
+
36
+ async parse(
37
+ _: Context,
38
+ value: FormDataValue
39
+ ): Promise<FieldParseResult<string[]>> {
40
+ if (is(value, predicates.string)) {
41
+ return { parsable: true, parsed_value: [value], error: null };
42
+ } else if (is(value, predicates.array(predicates.string))) {
43
+ return {
44
+ parsable: true,
45
+ parsed_value: value,
46
+ error: null,
47
+ };
48
+ } else if (value == null) {
49
+ return { parsable: true, parsed_value: [], error: null };
50
+ } else {
51
+ return {
52
+ parsable: false,
53
+ parsed_value: null,
54
+ error: "Please enter a list of strings",
55
+ };
56
+ }
57
+ }
58
+
59
+ public getEmptyValue(): string[] {
60
+ return [];
61
+ }
62
+ }
@@ -0,0 +1,25 @@
1
+ import assert from "assert";
2
+ import { Fields } from "../../index";
3
+
4
+ describe("enum field", () => {
5
+ const ctx = {} as any;
6
+
7
+ it("validation logic", async () => {
8
+ const field = new Fields.EnumField(true, ["sa", "mp", "le"]);
9
+
10
+ assert.strictEqual((await field.isValueValid(ctx, 42)).valid, false);
11
+ assert.strictEqual((await field.isValueValid(ctx, "mp")).valid, true);
12
+ assert.strictEqual((await field.isValueValid(ctx, "notvalid")).valid, false);
13
+ });
14
+
15
+ it("parse function", async () => {
16
+ const field = new Fields.EnumField(true, []);
17
+ const string_response = await field.parse(ctx, "42");
18
+ const number_response = await field.parse(ctx, 42);
19
+
20
+ assert.strictEqual(string_response.parsable, true);
21
+ assert.strictEqual(string_response.parsed_value, "42");
22
+ assert.strictEqual(number_response.parsable, false);
23
+ assert.strictEqual(number_response.error, "Please enter a string");
24
+ });
25
+ });
@@ -0,0 +1,56 @@
1
+ import { is, predicates } from "@sealcode/ts-predicates";
2
+ import { Context } from "koa";
3
+ import { FormDataValue } from "../form";
4
+ import { FieldParseResult, FormField } from "./field";
5
+
6
+ export class EnumField<
7
+ Options extends readonly string[],
8
+ Required extends boolean
9
+ > extends FormField<Required, string> {
10
+ constructor(
11
+ required: Required,
12
+ public options: Options,
13
+ public error_message: (options: Options) => string = (options) =>
14
+ options.join(", ")
15
+ ) {
16
+ super(required);
17
+ }
18
+
19
+ predicate = predicates.enum<Options>(this.options);
20
+
21
+ public async isValueValid(
22
+ _: Context,
23
+ value: unknown
24
+ ): Promise<{ valid: boolean; message: string }> {
25
+ if (!is(value, predicates.string)) {
26
+ return { valid: false, message: "expected a string" };
27
+ }
28
+ if (this.options.includes(value)) {
29
+ return { valid: true, message: "" };
30
+ }
31
+ return { valid: false, message: this.error_message(this.options) };
32
+ }
33
+
34
+ async parse(
35
+ _: Context,
36
+ value: FormDataValue
37
+ ): Promise<FieldParseResult<string>> {
38
+ if (is(value, predicates.string)) {
39
+ return {
40
+ parsable: true,
41
+ parsed_value: value,
42
+ error: null,
43
+ };
44
+ } else {
45
+ return {
46
+ parsable: false,
47
+ parsed_value: null,
48
+ error: "Please enter a string",
49
+ };
50
+ }
51
+ }
52
+
53
+ public getEmptyValue(): string {
54
+ return "";
55
+ }
56
+ }
@@ -0,0 +1,22 @@
1
+ import { Field } from "sealious";
2
+ import { PickFromListField } from "./pick-from-list";
3
+
4
+ export class ExistingSealiousValue<
5
+ Required extends boolean
6
+ > extends PickFromListField<Required> {
7
+ constructor(required: Required, field: Field) {
8
+ super(required, async (ctx) => {
9
+ const { items } = await field.collection.list(ctx.$context).fetch();
10
+ return Object.fromEntries(
11
+ items.map((item) => {
12
+ const value = item.get(field.name);
13
+ return [value, value];
14
+ })
15
+ );
16
+ });
17
+ }
18
+
19
+ getEmptyValue() {
20
+ return "";
21
+ }
22
+ }