@sealcode/sealgen 0.19.18 → 0.19.21
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/.xunit +77 -0
- package/@types/add-crud.test.d.ts +1 -0
- package/@types/controllers/sealgen-table.stimulus.d.ts +5 -1
- package/@types/forms/controls/table.d.sync-conflict-20260502-145940-GP5D735.ts +39 -0
- package/@types/index.d.sync-conflict-20260502-145557-GP5D735.ts +47 -0
- package/@types/index.d.ts +1 -0
- package/@types/templates/form.test.d.ts +1 -0
- package/lib/add-crud.js +1 -1
- package/lib/add-crud.test.js +32 -0
- package/lib/core-js.d.js +1 -0
- package/lib/generate-routes.js +11 -2
- package/lib/page/default-list-filters.js +5 -1
- package/lib/templates/form.js +5 -6
- package/lib/templates/form.test.js +32 -0
- package/lib/templates/jdd-editor.js +22 -1
- package/lib/templates/long-running-process.js +2 -0
- package/lib/templates/multiform.js +2 -0
- package/lib/templates/page.js +2 -0
- package/lib/templates/post.js +2 -0
- package/lib/templates/redirect.js +2 -0
- package/lib/templates/shared/collection-list.js +2 -1
- package/lib/templates/shared/item-delete.js +2 -0
- package/lib/templates/stateful-page.js +2 -0
- package/package.json +2 -1
- package/src/add-crud.test.ts +44 -0
- package/src/add-crud.ts +1 -1
- package/src/core-js.d.ts +3 -0
- package/src/generate-routes.ts +13 -2
- package/src/index.ts +2 -0
- package/src/page/default-list-filters.ts +10 -7
- package/src/templates/form.test.ts +42 -0
- package/src/templates/form.ts +5 -6
- package/src/templates/jdd-editor.ts +22 -1
- package/src/templates/long-running-process.ts +2 -0
- package/src/templates/multiform.ts +2 -0
- package/src/templates/page.ts +2 -0
- package/src/templates/post.ts +2 -0
- package/src/templates/redirect.ts +2 -0
- package/src/templates/shared/collection-list.ts +2 -1
- package/src/templates/shared/item-delete.ts +2 -0
- package/src/templates/stateful-page.ts +2 -0
package/.xunit
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<testsuite name="Mocha Tests" tests="75" failures="0" errors="0" skipped="0" timestamp="Mon, 18 May 2026 09:00:42 GMT" time="365.918">
|
|
2
|
+
<testcase classname="add crud test" name="Generate simple sealious crud" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/add-crud.test.js" time="29.991"/>
|
|
3
|
+
<testcase classname="single-reference" name="creates an item list page with working partial string search for text fields" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/add-list-route.test.js" time="26.057"/>
|
|
4
|
+
<testcase classname="add route test" name="creates minimal sealious app and adds a simple route that works" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/add-route.test.js" time="24.718"/>
|
|
5
|
+
<testcase classname="empty test" name="empty test" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/example.test.js" time="0"/>
|
|
6
|
+
<testcase classname="checkboxed-list basic tests" name="sends the values" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/checkboxed-list-input.test.js" time="0.532"/>
|
|
7
|
+
<testcase classname="checkboxed-list basic tests" name="renders the label" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/checkboxed-list-input.test.js" time="0.481"/>
|
|
8
|
+
<testcase classname="checkboxed-list basic tests" name="groups checkboxes based on the value of the 'group' attribute" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/checkboxed-list-input.test.js" time="0.6"/>
|
|
9
|
+
<testcase classname="checkboxed-list basic tests behavior with (non)required" name="allows to send a form when no value is selected and fiels is not required" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/checkboxed-list-input.test.js" time="0.482"/>
|
|
10
|
+
<testcase classname="checkboxed-list basic tests behavior with (non)required" name="does not allow to send a form when no value is selected and field IS required" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/checkboxed-list-input.test.js" time="0.599"/>
|
|
11
|
+
<testcase classname="checkboxed-list basic tests behavior with (non)required" name="allows to send a form when no value is selected and field IS required" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/checkboxed-list-input.test.js" time="0.602"/>
|
|
12
|
+
<testcase classname="file-control basic tests" name="doesn't require image input if it already has old-value" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/file.test.js" time="0.506"/>
|
|
13
|
+
<testcase classname="photo-control basic tests" name="sends the old photo if it wasn't changed and it has an initial value" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/image.test.js" time="0.443"/>
|
|
14
|
+
<testcase classname="photo-control basic tests" name="doesn't require image input if it already has old-value" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/image.test.js" time="0.444"/>
|
|
15
|
+
<testcase classname="single-reference.test" name="creates minimal sealious app and adds form using single-reference field that works" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/controls/single-reference.test.js" time="24.635"/>
|
|
16
|
+
<testcase classname="boolean field" name="validation logic" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/boolean.test.js" time="0.001"/>
|
|
17
|
+
<testcase classname="boolean field" name="parse function" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/boolean.test.js" time="0"/>
|
|
18
|
+
<testcase classname="email field" name="validation logic" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/email.test.js" time="0.001"/>
|
|
19
|
+
<testcase classname="email field" name="parse function" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/email.test.js" time="0"/>
|
|
20
|
+
<testcase classname="basic field basic tests" name="sends the values" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/field.test.js" time="0.3"/>
|
|
21
|
+
<testcase classname="basic field basic tests" name="Doesn't call `isValueValid` and `parse` if the value is empty" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/field.test.js" time="0.401"/>
|
|
22
|
+
<testcase classname="basic field basic tests" name="Accepts a form where non-required values are empty" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/field.test.js" time="0.484"/>
|
|
23
|
+
<testcase classname="file field" name="should handle as imple upload scenario" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/file.test.js" time="0.47"/>
|
|
24
|
+
<testcase classname="number field" name="validation logic" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/number.test.js" time="0"/>
|
|
25
|
+
<testcase classname="number field" name="parse function" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/number.test.js" time="0"/>
|
|
26
|
+
<testcase classname="pick-from-list field" name="validation logic" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/pick-from-list.test.js" time="0"/>
|
|
27
|
+
<testcase classname="regexp field" name="validation logic" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/regexp.test.js" time="0"/>
|
|
28
|
+
<testcase classname="regexp field" name="parse function" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/fields/regexp.test.js" time="0"/>
|
|
29
|
+
<testcase classname="form test basic tests" name="does not allow to submit an empty form when there's a required field" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.623"/>
|
|
30
|
+
<testcase classname="form test basic tests" name="allows to submit a form when all required fields have a value" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.222"/>
|
|
31
|
+
<testcase classname="form test basic tests" name="does not allow submitting an empty form by circumventing HTML-based validation" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.021"/>
|
|
32
|
+
<testcase classname="form test canAccess tests" name="allows visit when configured when canAccess returns true" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.025"/>
|
|
33
|
+
<testcase classname="form test canAccess tests" name="passes the context to canAccess (false case)" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.029"/>
|
|
34
|
+
<testcase classname="form test canAccess tests" name="passes the context to canAccess (true case)" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.023"/>
|
|
35
|
+
<testcase classname="form test canAccess tests declines access when canAccess returns false" name="prevents the form from rendering" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.021"/>
|
|
36
|
+
<testcase classname="form test canAccess tests declines access when canAccess returns false" name="does not allow submitting of the form through axios" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.009"/>
|
|
37
|
+
<testcase classname="form test validation e2e validation message" name="shows up when incorrect input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.304"/>
|
|
38
|
+
<testcase classname="form test validation e2e validation message" name="doesn't show when correct input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.667"/>
|
|
39
|
+
<testcase classname="form test validation e2e field specific validation message" name="shows up when incorrect input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.275"/>
|
|
40
|
+
<testcase classname="form test validation e2e field specific validation message" name="doesn't show up when correct input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.668"/>
|
|
41
|
+
<testcase classname="form test getInitialValues tests" name="fills the form with values from getInitialValues" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/form.test.js" time="0.062"/>
|
|
42
|
+
<testcase classname="multiform test basic tests turbolinks + js enabled" name="checking whether only one submit handler is run" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.607"/>
|
|
43
|
+
<testcase classname="multiform test basic tests turbolinks + js enabled" name="checking whether the input in the not submitted form prevails" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.007"/>
|
|
44
|
+
<testcase classname="multiform test basic tests turbolinks + js disabled" name="checking whether only one submit handler is run" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.588"/>
|
|
45
|
+
<testcase classname="multiform test basic tests turbolinks + js disabled" name="checking whether the input in the not submitted form prevails" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.008"/>
|
|
46
|
+
<testcase classname="multiform test canAccess allow access to both forms when both canAccess return true" name="check wether it renders" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.498"/>
|
|
47
|
+
<testcase classname="multiform test canAccess allow access to both forms when both canAccess return true" name="check wether you can submit through http (axios)" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.007"/>
|
|
48
|
+
<testcase classname="multiform test canAccess deny access to both forms" name="check wether forms didnt render" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.778"/>
|
|
49
|
+
<testcase classname="multiform test canAccess deny access to both forms" name="check wether it's possible to submit through http" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.007"/>
|
|
50
|
+
<testcase classname="multiform test canAccess allow access to one form, deny to the other" name="check wether form1 renders" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.456"/>
|
|
51
|
+
<testcase classname="multiform test canAccess allow access to one form, deny to the other" name="check wether it's possible to submit to form1 through http" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.007"/>
|
|
52
|
+
<testcase classname="multiform test canAccess allow access to one form, deny to the other" name="check wether form2 does not render" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.347"/>
|
|
53
|
+
<testcase classname="multiform test canAccess allow access to one form, deny to the other" name="check wether it's possible to submit to form2 through http" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.005"/>
|
|
54
|
+
<testcase classname="multiform test validation e2e basic tests" name="js enabled" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="1.434"/>
|
|
55
|
+
<testcase classname="multiform test validation e2e basic tests" name="js disabled" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="1.086"/>
|
|
56
|
+
<testcase classname="multiform test validation e2e field specific validation js enabled" name="validation message shows when incorrect input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.314"/>
|
|
57
|
+
<testcase classname="multiform test validation e2e field specific validation js enabled" name="validation message doesn't show when correct input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.658"/>
|
|
58
|
+
<testcase classname="multiform test validation e2e field specific validation js disabled" name="validation message shows when incorrect input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.312"/>
|
|
59
|
+
<testcase classname="multiform test validation e2e field specific validation js disabled" name="validation message doesn't show when correct input is given" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/forms/multiform.test.js" time="0.671"/>
|
|
60
|
+
<testcase classname="generate routes" name="handles a url with params" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/generate-routes.test.js" time="0.013"/>
|
|
61
|
+
<testcase classname="generate routes" name="handles polish characters in the url" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/generate-routes.test.js" time="0"/>
|
|
62
|
+
<testcase classname="generate routes" name="handles a url with params and diacritics" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/generate-routes.test.js" time="0.007"/>
|
|
63
|
+
<testcase classname="generate routes" name="sorts routes from most specific first to least specific last, considering directory structure" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/generate-routes.test.js" time="0.001"/>
|
|
64
|
+
<testcase classname="mountable-with-fields" name="properly types the getParsedValues method and respects required status" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/page/mountable-with-fields.test.js" time="0"/>
|
|
65
|
+
<testcase classname="sealious-list test" name="creates minimal sealious app and adds a simple route that works" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/page/sealious-list.test.js" time="26.977"/>
|
|
66
|
+
<testcase classname="stateful page" name="has types that allow for extracting action argument types" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/page/stateful-page.test.js" time="0"/>
|
|
67
|
+
<testcase classname="stateful page" name="handles a basic case with action buttons" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/page/stateful-page.test.js" time="0"/>
|
|
68
|
+
<testcase classname="register-external CLI commands package.json configuration" name="registers external controllers and verifies package.json is updated" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="18.737"/>
|
|
69
|
+
<testcase classname="register-external CLI commands package.json configuration" name="registers external styles and verifies package.json is updated" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="19.216"/>
|
|
70
|
+
<testcase classname="register-external CLI commands package.json configuration" name="registers both controllers and styles in the same project" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="23.32"/>
|
|
71
|
+
<testcase classname="register-external CLI commands package.json configuration" name="handles duplicate registrations gracefully" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="23.56"/>
|
|
72
|
+
<testcase classname="register-external CLI commands package.json configuration" name="preserves existing sealgen configuration when adding new entries" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="22.742"/>
|
|
73
|
+
<testcase classname="register-external CLI commands build integration" name="registers external controllers and verifies they are included in build" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="24.028"/>
|
|
74
|
+
<testcase classname="register-external CLI commands build integration" name="registers external styles and verifies they are included in build" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="23.41"/>
|
|
75
|
+
<testcase classname="register-external CLI commands build integration" name="registers both external controllers and styles and verifies complete build integration" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/register-external.test.js" time="32.706"/>
|
|
76
|
+
<testcase classname="route form test" name="creates minimal sealious app, adds route with form and fills it" file="/home/kuba/projects/sealcode/superseal/repos/sealgen/lib-instrumented/templates/form.test.js" time="23.018"/>
|
|
77
|
+
</testsuite>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,6 +4,10 @@ export default class TableController extends Controller {
|
|
|
4
4
|
tableTarget: HTMLTableElement;
|
|
5
5
|
templateTarget: HTMLTemplateElement;
|
|
6
6
|
getTbody(): HTMLTableSectionElement;
|
|
7
|
+
getRowIndex(tr: HTMLTableRowElement): number;
|
|
7
8
|
addRow(): void;
|
|
8
|
-
swapRows(
|
|
9
|
+
swapRows(index_a: number, index_b: number): void;
|
|
10
|
+
moveUp(e: MouseEvent): void;
|
|
11
|
+
moveDown(e: MouseEvent): void;
|
|
12
|
+
reindex(): void;
|
|
9
13
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Router from "@koa/router";
|
|
2
|
+
import { Context } from "koa";
|
|
3
|
+
import { FlatTemplatable } from "tempstream";
|
|
4
|
+
import { FormField } from "../fields/field.js";
|
|
5
|
+
import { Table as TableField } from "../fields/table.js";
|
|
6
|
+
import { Form } from "../form.js";
|
|
7
|
+
import { FormControl, FormControlContext } from "./form-control.js";
|
|
8
|
+
export type TableControlOptions<F extends Record<string, FormField>> = {
|
|
9
|
+
subfield_controls: {
|
|
10
|
+
[field_name in keyof F]: FormControl;
|
|
11
|
+
};
|
|
12
|
+
allow_removing?: boolean;
|
|
13
|
+
label_add?: string;
|
|
14
|
+
label: string;
|
|
15
|
+
label_remove?: string;
|
|
16
|
+
} & ({
|
|
17
|
+
allow_adding: false;
|
|
18
|
+
} | {
|
|
19
|
+
allow_adding: true;
|
|
20
|
+
make_new_row: (ctx: Context) => Promise<{
|
|
21
|
+
[field_name in keyof F]: unknown;
|
|
22
|
+
}>;
|
|
23
|
+
});
|
|
24
|
+
export declare const TABLE_COLUMN_FIELD_INDEX_PLACEHOLDER = "TABLE_ROW_INDEX________________";
|
|
25
|
+
export declare class Table<F extends Record<string, FormField>> extends FormControl {
|
|
26
|
+
table_field: TableField<F>;
|
|
27
|
+
options: TableControlOptions<F>;
|
|
28
|
+
role: "input";
|
|
29
|
+
constructor(table_field: TableField<F>, options: TableControlOptions<F>);
|
|
30
|
+
render(fctx: FormControlContext): Promise<FlatTemplatable>;
|
|
31
|
+
getFrameID(): string;
|
|
32
|
+
getActionURL(field_name: string, action: Record<string, unknown>): string;
|
|
33
|
+
mount(router: Router, form: Form<any, any>): void;
|
|
34
|
+
setAllowRemoving(value: boolean): this;
|
|
35
|
+
setAllowAdding(value: boolean): this;
|
|
36
|
+
setLabel(value: string): this;
|
|
37
|
+
setRemoveLabel(value: string): this;
|
|
38
|
+
setAddLabel(value: string): this;
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export * as Controls from "./forms/controls/controls.js";
|
|
2
|
+
export * from "./forms/fields/field.js";
|
|
3
|
+
export * as Fields from "./forms/fields/fields.js";
|
|
4
|
+
export * from "./page/mountable.js";
|
|
5
|
+
export * from "./page/page.js";
|
|
6
|
+
export * from "./page/stateful-page.js";
|
|
7
|
+
export * from "./forms/form-types.js";
|
|
8
|
+
export * from "./forms/form.js";
|
|
9
|
+
export * from "./page/props-parser.js";
|
|
10
|
+
export * from "./utils/utils.js";
|
|
11
|
+
export * from "./page/list.js";
|
|
12
|
+
export * from "./page/sealious-list.js";
|
|
13
|
+
export * from "./utils/translate.js";
|
|
14
|
+
export * as sanitize from "./sanitize.js";
|
|
15
|
+
export * from "./forms/multiform.js";
|
|
16
|
+
export * as Components from "./components/components.js";
|
|
17
|
+
export * from "./mount.js";
|
|
18
|
+
export * from "./page/default-list-filters.js";
|
|
19
|
+
export * from "./forms/fields/get-field-for-sealious.js";
|
|
20
|
+
export * from "./forms/controls/form-control.js";
|
|
21
|
+
import { Context } from "koa";
|
|
22
|
+
import { Readable } from "node:stream";
|
|
23
|
+
export interface HTMLOptions {
|
|
24
|
+
morphing?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface HTMLHeadOptions {
|
|
27
|
+
ctx: Context;
|
|
28
|
+
title: string | Promise<string | Readable>;
|
|
29
|
+
htmlOptions: Partial<HTMLOptions>;
|
|
30
|
+
metaImage?: string;
|
|
31
|
+
canonicalPath?: string;
|
|
32
|
+
css_clumps: string[];
|
|
33
|
+
description: string;
|
|
34
|
+
}
|
|
35
|
+
export interface HTMLArgs {
|
|
36
|
+
ctx: Context;
|
|
37
|
+
title: string | Promise<string>;
|
|
38
|
+
body: string | Promise<string> | Readable;
|
|
39
|
+
htmlOptions?: Partial<HTMLOptions>;
|
|
40
|
+
makeHead?: (args: HTMLHeadOptions) => string | Promise<string> | Readable | Promise<Readable>;
|
|
41
|
+
metaImage?: string;
|
|
42
|
+
canonicalPath?: string;
|
|
43
|
+
css_clumps?: string[];
|
|
44
|
+
description: string;
|
|
45
|
+
hideNavigation?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export type HTMLFunc = (args: HTMLArgs) => Readable | Promise<Readable>;
|
package/@types/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/add-crud.js
CHANGED
|
@@ -60,7 +60,7 @@ async function addCRUD(params, app_directory = process.cwd()) {
|
|
|
60
60
|
{displayFields.map(({ field, format }) => {
|
|
61
61
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
62
62
|
const value = item.get(field as any);
|
|
63
|
-
return <td>{format ? format(value, item) : value}</td>;
|
|
63
|
+
return <td data-field-name={field}>{format ? format(value, item) : value}</td>;
|
|
64
64
|
})}
|
|
65
65
|
<td><div class="sealious-list__actions">
|
|
66
66
|
<a href={${edit_action_name}URL(item.id)}>Edit</a>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RealAppTest } from "./test_utils/test-on-real-app.js";
|
|
2
|
+
import { getBrowser } from "./utils/browser-creator.js";
|
|
3
|
+
describe("add crud test", () => {
|
|
4
|
+
let page;
|
|
5
|
+
before(async () => {
|
|
6
|
+
const browser = await getBrowser();
|
|
7
|
+
const context = await browser.newContext();
|
|
8
|
+
page = await context.newPage();
|
|
9
|
+
});
|
|
10
|
+
after(async () => {
|
|
11
|
+
await page.close();
|
|
12
|
+
});
|
|
13
|
+
it("Generate simple sealious crud", async function() {
|
|
14
|
+
const test_app = await RealAppTest.init();
|
|
15
|
+
const path = "/sample";
|
|
16
|
+
await test_app.runSealgenCommand(`add-collection --collection_name=sealcode-test`);
|
|
17
|
+
await test_app.runSealgenCommand(`add-crud --url=${path} --collection=sealcode-test`);
|
|
18
|
+
await test_app.start();
|
|
19
|
+
await page.goto(test_app.fullURL(`sample/create/`));
|
|
20
|
+
const contentInput = page.getByLabel("Content");
|
|
21
|
+
await contentInput.focus();
|
|
22
|
+
await contentInput.fill("Hello");
|
|
23
|
+
await page.locator("[type=submit]").click();
|
|
24
|
+
await page.locator('[href="/sample/"]').click();
|
|
25
|
+
const dataFieldNameAttr = await page.getByText("Hello").getAttribute("data-field-name");
|
|
26
|
+
if (dataFieldNameAttr !== "content") {
|
|
27
|
+
throw new Error("Generated td element does not have correct data-field-name attribute");
|
|
28
|
+
}
|
|
29
|
+
await test_app.close();
|
|
30
|
+
}).timeout(100 * 1e3);
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=add-crud.test.js.map
|
package/lib/core-js.d.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=core-js.d.js.map
|
package/lib/generate-routes.js
CHANGED
|
@@ -5,8 +5,8 @@ import { walkDir } from "./utils/walk.js";
|
|
|
5
5
|
import { importPath } from "./utils/import-path.js";
|
|
6
6
|
import { assertType, predicates } from "@sealcode/ts-predicates";
|
|
7
7
|
import { unescape_url_params } from "./utils/escape-url-params.js";
|
|
8
|
-
import { formatWithPrettier } from "./utils/prettier.js";
|
|
9
8
|
import { Templates } from "./templates/templates.js";
|
|
9
|
+
import { formatWithPrettier } from "./utils/prettier.js";
|
|
10
10
|
const target_locreq = _locreq(process.cwd());
|
|
11
11
|
async function extractActionName(full_file_path) {
|
|
12
12
|
const file_content = await fs.readFile(full_file_path, "utf-8");
|
|
@@ -49,6 +49,12 @@ function sortRoutes(urls) {
|
|
|
49
49
|
return encoded1 < encoded2 ? -1 : 1;
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
+
const getPathToBuiltFile = (filePath) => {
|
|
53
|
+
const rootPath = target_locreq.resolve("");
|
|
54
|
+
const relativePath = relative(rootPath, filePath);
|
|
55
|
+
const distPath = relativePath.replace(/^src[\\/]/, "dist/");
|
|
56
|
+
return distPath.replace(/\.(ts|tsx)$/, ".js");
|
|
57
|
+
};
|
|
52
58
|
async function generateRoutes() {
|
|
53
59
|
const files = await Promise.all(
|
|
54
60
|
(await walkDir(target_locreq.resolve("src/back/routes"))).filter(
|
|
@@ -58,6 +64,7 @@ async function generateRoutes() {
|
|
|
58
64
|
).map(async (fullpath) => ({
|
|
59
65
|
fullpath,
|
|
60
66
|
actionName: await extractActionName(fullpath),
|
|
67
|
+
module_path: getPathToBuiltFile(fullpath),
|
|
61
68
|
// trailing slash is important here, as it enables to use the entire path while building relative URLs. For example, while visiting /users/123, the path ./add-photo leads to /users/add-photo. While visiting /users/123/ (note the trailing slash), the path ./add-photo leads to /users/123/add-photo
|
|
62
69
|
url: unescape_url_params(
|
|
63
70
|
"/" + relative(
|
|
@@ -71,7 +78,7 @@ async function generateRoutes() {
|
|
|
71
78
|
}))
|
|
72
79
|
);
|
|
73
80
|
const url_tree = { children: {} };
|
|
74
|
-
sortRoutes(files).forEach(({ actionName, url }) => {
|
|
81
|
+
sortRoutes(files).forEach(({ actionName, url, module_path }) => {
|
|
75
82
|
const elements = url.split("/").filter((e) => e != "");
|
|
76
83
|
let pointer = url_tree;
|
|
77
84
|
for (const [index, element] of elements.entries()) {
|
|
@@ -81,6 +88,7 @@ async function generateRoutes() {
|
|
|
81
88
|
pointer = pointer.children[element];
|
|
82
89
|
if (index == elements.length - 1) {
|
|
83
90
|
pointer.actionName = actionName;
|
|
91
|
+
pointer.module_path = module_path;
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
});
|
|
@@ -88,6 +96,7 @@ async function generateRoutes() {
|
|
|
88
96
|
|
|
89
97
|
export type URLTree = {
|
|
90
98
|
actionName?: string;
|
|
99
|
+
module_path?: string;
|
|
91
100
|
children: { [key: string]: URLTree };
|
|
92
101
|
};
|
|
93
102
|
export const url_tree = ${JSON.stringify(url_tree)};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import regex_escape from "core-js-pure/features/regexp/escape.js";
|
|
2
|
+
console.warn(regex_escape);
|
|
3
|
+
const string = "jedna baba ;) drugiej babie ;)";
|
|
4
|
+
console.warn(regex_escape(string), new RegExp(regex_escape(string)));
|
|
1
5
|
const BooleanListFilter = {
|
|
2
6
|
render: (value, field) => {
|
|
3
7
|
return (
|
|
@@ -38,7 +42,7 @@ const SimpleTextFilter = {
|
|
|
38
42
|
);
|
|
39
43
|
},
|
|
40
44
|
prepareValue: (value) => {
|
|
41
|
-
return { regex: `.*${value}.*` };
|
|
45
|
+
return { regex: `.*${regex_escape(value)}.*` };
|
|
42
46
|
}
|
|
43
47
|
};
|
|
44
48
|
const DefaultListFilters = {
|
package/lib/templates/form.js
CHANGED
|
@@ -5,10 +5,9 @@ const defaults = {
|
|
|
5
5
|
import type {FormDataValue} from "@sealcode/sealgen";`,
|
|
6
6
|
get_fields: `()=>(<const>{ password: new Fields.Password(true) })`,
|
|
7
7
|
get_controls: `
|
|
8
|
-
(fields: ReturnType<typeof getFields>)=>[
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
})]`,
|
|
8
|
+
(fields: ReturnType<typeof getFields>)=>[
|
|
9
|
+
fields.password?.getControl()
|
|
10
|
+
]`,
|
|
12
11
|
validate_method: `async validateValues(
|
|
13
12
|
ctx: Context,
|
|
14
13
|
data: Record<string, FormDataValue>
|
|
@@ -41,13 +40,13 @@ const defaults = {
|
|
|
41
40
|
async function formTemplate(action_name, _params = defaults) {
|
|
42
41
|
const params = { ...defaults, ..._params };
|
|
43
42
|
const content = `import type { Context } from "koa";
|
|
44
|
-
import type { FormData } from "@sealcode/sealgen";
|
|
43
|
+
import type { FormData, BreadcrumbLabel } from "@sealcode/sealgen";
|
|
45
44
|
import { Form, Controls } from "@sealcode/sealgen";
|
|
46
45
|
import html from "src/back/html.js";
|
|
47
|
-
|
|
48
46
|
${params.postimport}
|
|
49
47
|
|
|
50
48
|
export const actionName = "${action_name}";
|
|
49
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
51
50
|
|
|
52
51
|
const getFields = ${params.get_fields};
|
|
53
52
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { RealAppTest } from "../test_utils/test-on-real-app.js";
|
|
3
|
+
import { getBrowser } from "../utils/browser-creator.js";
|
|
4
|
+
describe("route form test", () => {
|
|
5
|
+
let page;
|
|
6
|
+
before(async () => {
|
|
7
|
+
const browser = await getBrowser();
|
|
8
|
+
const context = await browser.newContext();
|
|
9
|
+
page = await context.newPage();
|
|
10
|
+
});
|
|
11
|
+
after(async () => {
|
|
12
|
+
await page.close();
|
|
13
|
+
});
|
|
14
|
+
it("creates minimal sealious app, adds route with form and fills it", async function() {
|
|
15
|
+
const test_app = await RealAppTest.init();
|
|
16
|
+
const path = "/form-test/";
|
|
17
|
+
await test_app.runSealgenCommand(`add-route --url=${path} --action=Sample --mode=form`);
|
|
18
|
+
await test_app.start();
|
|
19
|
+
const res = await test_app.httpGET(path);
|
|
20
|
+
if (res.status != 200) {
|
|
21
|
+
throw new Error("url generated by add-route exists but doesn't return 200");
|
|
22
|
+
}
|
|
23
|
+
await page.goto(test_app.fullURL(path));
|
|
24
|
+
const input = page.locator("#password");
|
|
25
|
+
await input.focus();
|
|
26
|
+
await input.fill("abc123");
|
|
27
|
+
await page.locator("[type=submit]").click();
|
|
28
|
+
await expect(page.locator("text=Formularz wype\u0142niony poprawnie")).toBeVisible();
|
|
29
|
+
await test_app.close();
|
|
30
|
+
}).timeout(100 * 1e3);
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=form.test.js.map
|
|
@@ -48,12 +48,15 @@ import type { FieldNames } from "sealious";
|
|
|
48
48
|
import { TempstreamJSX } from "tempstream";
|
|
49
49
|
import type ${collectionClassName} from "src/back/collections/${collection_name}.js";
|
|
50
50
|
import { EditJDDField } from "@sealcode/jdd-editor";
|
|
51
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
51
52
|
import html from "src/back/html.js";
|
|
52
53
|
import { registry } from "src/back/jdd-components/registry.js";
|
|
53
54
|
import { makeJDDContext } from "src/back/jdd-context.js";
|
|
54
55
|
import { defaultHead } from "src/back/defaultHead.js";
|
|
56
|
+
import { get_breadcrumbs_from_path } from "src/back/routes/url-tree.js";
|
|
55
57
|
|
|
56
58
|
export const actionName = "${actionName}";
|
|
59
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${actionName}";
|
|
57
60
|
|
|
58
61
|
export default new (class JDDCreatePreviewPage extends EditJDDField<${collectionClassName}> {
|
|
59
62
|
getCollection(ctx: Context) {
|
|
@@ -66,9 +69,27 @@ export default new (class JDDCreatePreviewPage extends EditJDDField<${collection
|
|
|
66
69
|
|
|
67
70
|
async renderPreParameterButtons(ctx: Context) {
|
|
68
71
|
const item = await this.getItem(ctx);
|
|
72
|
+
const breadcrumbs = get_breadcrumbs_from_path(ctx.path);
|
|
73
|
+
const lastIdx = breadcrumbs.length - 2;
|
|
74
|
+
let prev_page = null;
|
|
75
|
+
|
|
76
|
+
for (let i = lastIdx; i > -1; i--) {
|
|
77
|
+
if (breadcrumbs[i]?.actionName) {
|
|
78
|
+
prev_page = breadcrumbs[i];
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
return (
|
|
70
84
|
<div>
|
|
71
|
-
|
|
85
|
+
{!! prev_page && !!prev_page.url && prev_page.actionName !== undefined ? (
|
|
86
|
+
<a href={prev_page?.url || ""}>
|
|
87
|
+
\u2190 Back to {prev_page.breadcrumbLabel?.(ctx) || prev_page.actionName || ""}
|
|
88
|
+
</a>
|
|
89
|
+
) : (
|
|
90
|
+
""
|
|
91
|
+
)}
|
|
92
|
+
<h1>Edit ${collection_name}: {item.id}</h1>
|
|
72
93
|
</div>
|
|
73
94
|
);
|
|
74
95
|
}
|
|
@@ -5,10 +5,12 @@ async function LongRunningProcessTemplate(action_name, newfilefullpath) {
|
|
|
5
5
|
return `import { Context } from "koa";
|
|
6
6
|
import { TempstreamJSX } from "tempstream";
|
|
7
7
|
import { Page } from "@sealcode/sealgen";
|
|
8
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
8
9
|
import html from "${rel("src/back/html.js")}";
|
|
9
10
|
import { LongRunningProcess } from "sealious";
|
|
10
11
|
|
|
11
12
|
export const actionName = "${action_name}";
|
|
13
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
12
14
|
|
|
13
15
|
export default new (class PatroniteAutoImportStatusPage extends Page {
|
|
14
16
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -4,9 +4,11 @@ async function multiformTemplate(action_name, newfilefullpath) {
|
|
|
4
4
|
return `import { Context } from "koa";
|
|
5
5
|
import html from "${rel("src/back/html.js")}";
|
|
6
6
|
import { Controls, Fields, Form, FormMessage, Multiform } from "@sealcode/sealgen";
|
|
7
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
7
8
|
import { FlatTemplatable } from "tempstream";
|
|
8
9
|
|
|
9
10
|
export const actionName = "${action_name}";
|
|
11
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
10
12
|
|
|
11
13
|
const fields1 = {
|
|
12
14
|
email: new Fields.TextBasedSimpleField(true),
|
package/lib/templates/page.js
CHANGED
|
@@ -2,9 +2,11 @@ async function pageTemplate(action_name) {
|
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { TempstreamJSX } from "tempstream";
|
|
4
4
|
import { Page } from "@sealcode/sealgen";
|
|
5
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
5
6
|
import html from "src/back/html.js";
|
|
6
7
|
|
|
7
8
|
export const actionName = "${action_name}";
|
|
9
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
8
10
|
|
|
9
11
|
export default new (class ${action_name}Page extends Page {
|
|
10
12
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
package/lib/templates/post.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
async function postTemplate(action_name) {
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { Mountable } from "@sealcode/sealgen";
|
|
4
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
4
5
|
import type Router from "@koa/router";
|
|
5
6
|
|
|
6
7
|
export const actionName = "${action_name}";
|
|
8
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
7
9
|
|
|
8
10
|
export default new (class ${action_name}Redirect extends Mountable {
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
async function redirectTemplate(action_name) {
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { Mountable } from "@sealcode/sealgen";
|
|
4
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
4
5
|
import type Router from "@koa/router";
|
|
5
6
|
|
|
6
7
|
export const actionName = "${action_name}";
|
|
8
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
7
9
|
|
|
8
10
|
export default new (class ${action_name}Redirect extends Mountable {
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -91,7 +91,7 @@ import type { FlatTemplatable, Templatable } from "tempstream";
|
|
|
91
91
|
import { TempstreamJSX, tempstream } from "tempstream";
|
|
92
92
|
import { ${uppercase_collection} } from "src/back/collections/collections.js";
|
|
93
93
|
import html from "src/back/html.js";
|
|
94
|
-
import type { ListFilterRender, FormDisplayInfo } from "@sealcode/sealgen";
|
|
94
|
+
import type { ListFilterRender, FormDisplayInfo, BreadcrumbLabel } from "@sealcode/sealgen";
|
|
95
95
|
import {
|
|
96
96
|
SealiousItemListPage,
|
|
97
97
|
BaseListPageFields,
|
|
@@ -103,6 +103,7 @@ ${hooks.post_import_js}
|
|
|
103
103
|
${field_import_string}
|
|
104
104
|
|
|
105
105
|
export const actionName = "${action_name}";
|
|
106
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
106
107
|
|
|
107
108
|
const filterFields:{
|
|
108
109
|
field: keyof (typeof ${uppercase_collection})["fields"];
|
|
@@ -4,6 +4,7 @@ function itemDeleteTemplate(delete_action_name, collection_name, list_action_nam
|
|
|
4
4
|
const result = `import type { Context } from "koa";
|
|
5
5
|
import { Mountable } from "@sealcode/sealgen";
|
|
6
6
|
import type Router from "@koa/router";
|
|
7
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
7
8
|
|
|
8
9
|
import { ${toPascalCase(collection_name)} } from "${importPath(
|
|
9
10
|
"src/back/collections/collections.js"
|
|
@@ -14,6 +15,7 @@ import { ${list_action_name}URL } from "${importPath(
|
|
|
14
15
|
)}";
|
|
15
16
|
|
|
16
17
|
export const actionName = "${delete_action_name}";
|
|
18
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${delete_action_name}";
|
|
17
19
|
|
|
18
20
|
export default new (class ${delete_action_name}Redirect extends Mountable {
|
|
19
21
|
canAccess = async (ctx: Context) => {
|
|
@@ -5,9 +5,11 @@ async function statefulPageTemplate(action_name, newfilefullpath) {
|
|
|
5
5
|
return formatWithPrettier(`import { TempstreamJSX, Templatable } from "tempstream";
|
|
6
6
|
import { Context } from "koa";
|
|
7
7
|
import { StatefulPage, StatefulPageActionArgument } from "@sealcode/sealgen";
|
|
8
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
8
9
|
import html from "${rel("src/back/html.js")}";
|
|
9
10
|
|
|
10
11
|
export const actionName = "${action_name}";
|
|
12
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
11
13
|
|
|
12
14
|
const actions = {
|
|
13
15
|
add: ({ state, inputs }: StatefulPageActionArgument<State>) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealcode/sealgen",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.21",
|
|
4
4
|
"description": "Module to automate adding routes and collections to a sealious application",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"@sealcode/ts-predicates": "^0.4.3",
|
|
67
67
|
"chokidar": "^3.6.0",
|
|
68
68
|
"colord": "^2.9.3",
|
|
69
|
+
"core-js-pure": "^3.49.0",
|
|
69
70
|
"country-codes-list": "^2.0.0",
|
|
70
71
|
"deepmerge": "^4.3.1",
|
|
71
72
|
"esbuild": "^0.20.0",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Locator, Page } from "playwright";
|
|
2
|
+
import { RealAppTest } from "./test_utils/test-on-real-app.js";
|
|
3
|
+
import { getBrowser } from "./utils/browser-creator.js";
|
|
4
|
+
|
|
5
|
+
describe("add crud test", () => {
|
|
6
|
+
let page: Page;
|
|
7
|
+
before(async () => {
|
|
8
|
+
const browser = await getBrowser();
|
|
9
|
+
const context = await browser.newContext();
|
|
10
|
+
page = await context.newPage();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
after(async () => {
|
|
14
|
+
await page.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("Generate simple sealious crud", async function () {
|
|
18
|
+
const test_app = await RealAppTest.init();
|
|
19
|
+
|
|
20
|
+
const path = "/sample";
|
|
21
|
+
await test_app.runSealgenCommand(`add-collection --collection_name=sealcode-test`);
|
|
22
|
+
await test_app.runSealgenCommand(`add-crud --url=${path} --collection=sealcode-test`);
|
|
23
|
+
await test_app.start();
|
|
24
|
+
|
|
25
|
+
await page.goto(test_app.fullURL(`sample/create/`));
|
|
26
|
+
|
|
27
|
+
const contentInput: Locator = page.getByLabel("Content");
|
|
28
|
+
|
|
29
|
+
await contentInput.focus();
|
|
30
|
+
await contentInput.fill("Hello");
|
|
31
|
+
|
|
32
|
+
await page.locator("[type=submit]").click();
|
|
33
|
+
await page.locator('[href="/sample/"]').click();
|
|
34
|
+
|
|
35
|
+
const dataFieldNameAttr: string | null = await page
|
|
36
|
+
.getByText("Hello")
|
|
37
|
+
.getAttribute("data-field-name");
|
|
38
|
+
|
|
39
|
+
if (dataFieldNameAttr !== "content") {
|
|
40
|
+
throw new Error("Generated td element does not have correct data-field-name attribute");
|
|
41
|
+
}
|
|
42
|
+
await test_app.close();
|
|
43
|
+
}).timeout(100 * 1000);
|
|
44
|
+
});
|
package/src/add-crud.ts
CHANGED
|
@@ -71,7 +71,7 @@ export async function addCRUD(
|
|
|
71
71
|
{displayFields.map(({ field, format }) => {
|
|
72
72
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
73
73
|
const value = item.get(field as any);
|
|
74
|
-
return <td>{format ? format(value, item) : value}</td>;
|
|
74
|
+
return <td data-field-name={field}>{format ? format(value, item) : value}</td>;
|
|
75
75
|
})}
|
|
76
76
|
<td><div class="sealious-list__actions">
|
|
77
77
|
<a href={${edit_action_name}URL(item.id)}>Edit</a>
|
package/src/core-js.d.ts
ADDED
package/src/generate-routes.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { walkDir } from "./utils/walk.js";
|
|
|
5
5
|
import { importPath } from "./utils/import-path.js";
|
|
6
6
|
import { assertType, predicates } from "@sealcode/ts-predicates";
|
|
7
7
|
import { unescape_url_params } from "./utils/escape-url-params.js";
|
|
8
|
-
import { formatWithPrettier } from "./utils/prettier.js";
|
|
9
8
|
import { Templates } from "./templates/templates.js";
|
|
9
|
+
import { formatWithPrettier } from "./utils/prettier.js";
|
|
10
10
|
|
|
11
11
|
const target_locreq = _locreq(process.cwd());
|
|
12
12
|
|
|
@@ -67,6 +67,13 @@ export function sortRoutes<T extends { url: string }>(urls: T[]): T[] {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
const getPathToBuiltFile = (filePath: string) => {
|
|
71
|
+
const rootPath = target_locreq.resolve("");
|
|
72
|
+
const relativePath = relative(rootPath, filePath);
|
|
73
|
+
const distPath = relativePath.replace(/^src[\\/]/, "dist/");
|
|
74
|
+
return distPath.replace(/\.(ts|tsx)$/, ".js");
|
|
75
|
+
};
|
|
76
|
+
|
|
70
77
|
export async function generateRoutes(): Promise<void> {
|
|
71
78
|
const files = await Promise.all(
|
|
72
79
|
(await walkDir(target_locreq.resolve("src/back/routes")))
|
|
@@ -79,6 +86,7 @@ export async function generateRoutes(): Promise<void> {
|
|
|
79
86
|
.map(async (fullpath) => ({
|
|
80
87
|
fullpath,
|
|
81
88
|
actionName: await extractActionName(fullpath),
|
|
89
|
+
module_path: getPathToBuiltFile(fullpath),
|
|
82
90
|
// trailing slash is important here, as it enables to use the entire path while building relative URLs. For example, while visiting /users/123, the path ./add-photo leads to /users/add-photo. While visiting /users/123/ (note the trailing slash), the path ./add-photo leads to /users/123/add-photo
|
|
83
91
|
url: unescape_url_params(
|
|
84
92
|
"/" +
|
|
@@ -98,11 +106,12 @@ export async function generateRoutes(): Promise<void> {
|
|
|
98
106
|
|
|
99
107
|
type URLTree = {
|
|
100
108
|
actionName?: string;
|
|
109
|
+
module_path?: string;
|
|
101
110
|
children: { [key: string]: URLTree };
|
|
102
111
|
};
|
|
103
112
|
|
|
104
113
|
const url_tree: URLTree = { children: {} };
|
|
105
|
-
sortRoutes(files).forEach(({ actionName, url }) => {
|
|
114
|
+
sortRoutes(files).forEach(({ actionName, url, module_path }) => {
|
|
106
115
|
const elements = url.split("/").filter((e) => e != "");
|
|
107
116
|
let pointer: URLTree = url_tree;
|
|
108
117
|
for (const [index, element] of elements.entries()) {
|
|
@@ -113,6 +122,7 @@ export async function generateRoutes(): Promise<void> {
|
|
|
113
122
|
if (index == elements.length - 1) {
|
|
114
123
|
(pointer as unknown as { actionName: string }).actionName =
|
|
115
124
|
actionName;
|
|
125
|
+
pointer.module_path = module_path;
|
|
116
126
|
}
|
|
117
127
|
}
|
|
118
128
|
});
|
|
@@ -121,6 +131,7 @@ export async function generateRoutes(): Promise<void> {
|
|
|
121
131
|
|
|
122
132
|
export type URLTree = {
|
|
123
133
|
actionName?: string;
|
|
134
|
+
module_path?: string;
|
|
124
135
|
children: { [key: string]: URLTree };
|
|
125
136
|
};
|
|
126
137
|
export const url_tree = ${JSON.stringify(url_tree)};
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Context } from "koa";
|
|
3
3
|
import { Field } from "sealious";
|
|
4
4
|
import { FlatTemplatable } from "tempstream";
|
|
5
|
+
import regex_escape from "core-js-pure/features/regexp/escape.js";
|
|
5
6
|
|
|
6
7
|
export type ListFilterRender = (
|
|
7
8
|
value: string,
|
|
@@ -27,12 +28,14 @@ export const BooleanListFilter = {
|
|
|
27
28
|
</option>
|
|
28
29
|
<option
|
|
29
30
|
value=""
|
|
30
|
-
${
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
${(
|
|
32
|
+
value == "null" ||
|
|
33
|
+
value == "" ||
|
|
34
|
+
value == "undefined" ||
|
|
35
|
+
value == undefined
|
|
36
|
+
) ?
|
|
37
|
+
"selected"
|
|
38
|
+
: ""}
|
|
36
39
|
>
|
|
37
40
|
-
|
|
38
41
|
</option>
|
|
@@ -54,7 +57,7 @@ export const SimpleTextFilter = {
|
|
|
54
57
|
</label>`;
|
|
55
58
|
},
|
|
56
59
|
prepareValue: (value: string) => {
|
|
57
|
-
return { regex: `.*${value}.*` };
|
|
60
|
+
return { regex: `.*${regex_escape(value)}.*` };
|
|
58
61
|
},
|
|
59
62
|
};
|
|
60
63
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Locator, Page } from "playwright";
|
|
2
|
+
import { expect } from "@playwright/test";
|
|
3
|
+
import { RealAppTest } from "../test_utils/test-on-real-app.js";
|
|
4
|
+
import { getBrowser } from "../utils/browser-creator.js";
|
|
5
|
+
|
|
6
|
+
describe("route form test", () => {
|
|
7
|
+
let page: Page;
|
|
8
|
+
before(async () => {
|
|
9
|
+
const browser = await getBrowser();
|
|
10
|
+
const context = await browser.newContext();
|
|
11
|
+
page = await context.newPage();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
after(async () => {
|
|
15
|
+
await page.close();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("creates minimal sealious app, adds route with form and fills it", async function () {
|
|
19
|
+
const test_app = await RealAppTest.init();
|
|
20
|
+
|
|
21
|
+
const path = "/form-test/";
|
|
22
|
+
await test_app.runSealgenCommand(`add-route --url=${path} --action=Sample --mode=form`);
|
|
23
|
+
await test_app.start();
|
|
24
|
+
const res = await test_app.httpGET(path);
|
|
25
|
+
|
|
26
|
+
if (res.status != 200) {
|
|
27
|
+
throw new Error("url generated by add-route exists but doesn't return 200");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await page.goto(test_app.fullURL(path));
|
|
31
|
+
|
|
32
|
+
const input: Locator = page.locator("#password");
|
|
33
|
+
|
|
34
|
+
await input.focus();
|
|
35
|
+
await input.fill("abc123");
|
|
36
|
+
await page.locator("[type=submit]").click();
|
|
37
|
+
|
|
38
|
+
await expect(page.locator("text=Formularz wypełniony poprawnie")).toBeVisible();
|
|
39
|
+
|
|
40
|
+
await test_app.close();
|
|
41
|
+
}).timeout(100 * 1000);
|
|
42
|
+
});
|
package/src/templates/form.ts
CHANGED
|
@@ -6,10 +6,9 @@ const defaults = {
|
|
|
6
6
|
import type {FormDataValue} from "@sealcode/sealgen";`,
|
|
7
7
|
get_fields: `()=>(<const>{ password: new Fields.Password(true) })`,
|
|
8
8
|
get_controls: `
|
|
9
|
-
(fields: ReturnType<typeof getFields>)=>[
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
})]`,
|
|
9
|
+
(fields: ReturnType<typeof getFields>)=>[
|
|
10
|
+
fields.password?.getControl()
|
|
11
|
+
]`,
|
|
13
12
|
validate_method: `async validateValues(
|
|
14
13
|
ctx: Context,
|
|
15
14
|
data: Record<string, FormDataValue>
|
|
@@ -46,13 +45,13 @@ export async function formTemplate(
|
|
|
46
45
|
): Promise<string> {
|
|
47
46
|
const params = { ...defaults, ..._params };
|
|
48
47
|
const content = `import type { Context } from "koa";
|
|
49
|
-
import type { FormData } from "@sealcode/sealgen";
|
|
48
|
+
import type { FormData, BreadcrumbLabel } from "@sealcode/sealgen";
|
|
50
49
|
import { Form, Controls } from "@sealcode/sealgen";
|
|
51
50
|
import html from "src/back/html.js";
|
|
52
|
-
|
|
53
51
|
${params.postimport}
|
|
54
52
|
|
|
55
53
|
export const actionName = "${action_name}";
|
|
54
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
56
55
|
|
|
57
56
|
const getFields = ${params.get_fields};
|
|
58
57
|
|
|
@@ -58,12 +58,15 @@ import type { FieldNames } from "sealious";
|
|
|
58
58
|
import { TempstreamJSX } from "tempstream";
|
|
59
59
|
import type ${collectionClassName} from "src/back/collections/${collection_name}.js";
|
|
60
60
|
import { EditJDDField } from "@sealcode/jdd-editor";
|
|
61
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
61
62
|
import html from "src/back/html.js";
|
|
62
63
|
import { registry } from "src/back/jdd-components/registry.js";
|
|
63
64
|
import { makeJDDContext } from "src/back/jdd-context.js";
|
|
64
65
|
import { defaultHead } from "src/back/defaultHead.js";
|
|
66
|
+
import { get_breadcrumbs_from_path } from "src/back/routes/url-tree.js";
|
|
65
67
|
|
|
66
68
|
export const actionName = "${actionName}";
|
|
69
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${actionName}";
|
|
67
70
|
|
|
68
71
|
export default new (class JDDCreatePreviewPage extends EditJDDField<${collectionClassName}> {
|
|
69
72
|
getCollection(ctx: Context) {
|
|
@@ -76,9 +79,27 @@ export default new (class JDDCreatePreviewPage extends EditJDDField<${collection
|
|
|
76
79
|
|
|
77
80
|
async renderPreParameterButtons(ctx: Context) {
|
|
78
81
|
const item = await this.getItem(ctx);
|
|
82
|
+
const breadcrumbs = get_breadcrumbs_from_path(ctx.path);
|
|
83
|
+
const lastIdx = breadcrumbs.length - 2;
|
|
84
|
+
let prev_page = null;
|
|
85
|
+
|
|
86
|
+
for (let i = lastIdx; i > -1; i--) {
|
|
87
|
+
if (breadcrumbs[i]?.actionName) {
|
|
88
|
+
prev_page = breadcrumbs[i];
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
return (
|
|
80
94
|
<div>
|
|
81
|
-
|
|
95
|
+
{!! prev_page && !!prev_page.url && prev_page.actionName !== undefined ? (
|
|
96
|
+
<a href={prev_page?.url || ""}>
|
|
97
|
+
← Back to {prev_page.breadcrumbLabel?.(ctx) || prev_page.actionName || ""}
|
|
98
|
+
</a>
|
|
99
|
+
) : (
|
|
100
|
+
""
|
|
101
|
+
)}
|
|
102
|
+
<h1>Edit ${collection_name}: {item.id}</h1>
|
|
82
103
|
</div>
|
|
83
104
|
);
|
|
84
105
|
}
|
|
@@ -9,10 +9,12 @@ export async function LongRunningProcessTemplate(
|
|
|
9
9
|
return `import { Context } from "koa";
|
|
10
10
|
import { TempstreamJSX } from "tempstream";
|
|
11
11
|
import { Page } from "@sealcode/sealgen";
|
|
12
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
12
13
|
import html from "${rel("src/back/html.js")}";
|
|
13
14
|
import { LongRunningProcess } from "sealious";
|
|
14
15
|
|
|
15
16
|
export const actionName = "${action_name}";
|
|
17
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
16
18
|
|
|
17
19
|
export default new (class PatroniteAutoImportStatusPage extends Page {
|
|
18
20
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -8,9 +8,11 @@ export async function multiformTemplate(
|
|
|
8
8
|
return `import { Context } from "koa";
|
|
9
9
|
import html from "${rel("src/back/html.js")}";
|
|
10
10
|
import { Controls, Fields, Form, FormMessage, Multiform } from "@sealcode/sealgen";
|
|
11
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
11
12
|
import { FlatTemplatable } from "tempstream";
|
|
12
13
|
|
|
13
14
|
export const actionName = "${action_name}";
|
|
15
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
14
16
|
|
|
15
17
|
const fields1 = {
|
|
16
18
|
email: new Fields.TextBasedSimpleField(true),
|
package/src/templates/page.ts
CHANGED
|
@@ -2,9 +2,11 @@ export async function pageTemplate(action_name: string): Promise<string> {
|
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { TempstreamJSX } from "tempstream";
|
|
4
4
|
import { Page } from "@sealcode/sealgen";
|
|
5
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
5
6
|
import html from "src/back/html.js";
|
|
6
7
|
|
|
7
8
|
export const actionName = "${action_name}";
|
|
9
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
8
10
|
|
|
9
11
|
export default new (class ${action_name}Page extends Page {
|
|
10
12
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
package/src/templates/post.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export async function postTemplate(action_name: string): Promise<string> {
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { Mountable } from "@sealcode/sealgen";
|
|
4
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
4
5
|
import type Router from "@koa/router";
|
|
5
6
|
|
|
6
7
|
export const actionName = "${action_name}";
|
|
8
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
7
9
|
|
|
8
10
|
export default new (class ${action_name}Redirect extends Mountable {
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export async function redirectTemplate(action_name: string): Promise<string> {
|
|
2
2
|
return `import type { Context } from "koa";
|
|
3
3
|
import { Mountable } from "@sealcode/sealgen";
|
|
4
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
4
5
|
import type Router from "@koa/router";
|
|
5
6
|
|
|
6
7
|
export const actionName = "${action_name}";
|
|
8
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
7
9
|
|
|
8
10
|
export default new (class ${action_name}Redirect extends Mountable {
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -118,7 +118,7 @@ import type { FlatTemplatable, Templatable } from "tempstream";
|
|
|
118
118
|
import { TempstreamJSX, tempstream } from "tempstream";
|
|
119
119
|
import { ${uppercase_collection} } from "src/back/collections/collections.js";
|
|
120
120
|
import html from "src/back/html.js";
|
|
121
|
-
import type { ListFilterRender, FormDisplayInfo } from "@sealcode/sealgen";
|
|
121
|
+
import type { ListFilterRender, FormDisplayInfo, BreadcrumbLabel } from "@sealcode/sealgen";
|
|
122
122
|
import {
|
|
123
123
|
SealiousItemListPage,
|
|
124
124
|
BaseListPageFields,
|
|
@@ -130,6 +130,7 @@ ${hooks.post_import_js}
|
|
|
130
130
|
${field_import_string}
|
|
131
131
|
|
|
132
132
|
export const actionName = "${action_name}";
|
|
133
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
133
134
|
|
|
134
135
|
const filterFields:{
|
|
135
136
|
field: keyof (typeof ${uppercase_collection})["fields"];
|
|
@@ -10,6 +10,7 @@ export function itemDeleteTemplate(
|
|
|
10
10
|
const result = `import type { Context } from "koa";
|
|
11
11
|
import { Mountable } from "@sealcode/sealgen";
|
|
12
12
|
import type Router from "@koa/router";
|
|
13
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
13
14
|
|
|
14
15
|
import { ${toPascalCase(collection_name)} } from "${importPath(
|
|
15
16
|
"src/back/collections/collections.js"
|
|
@@ -20,6 +21,7 @@ import { ${list_action_name}URL } from "${importPath(
|
|
|
20
21
|
)}";
|
|
21
22
|
|
|
22
23
|
export const actionName = "${delete_action_name}";
|
|
24
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${delete_action_name}";
|
|
23
25
|
|
|
24
26
|
export default new (class ${delete_action_name}Redirect extends Mountable {
|
|
25
27
|
canAccess = async (ctx: Context) => {
|
|
@@ -9,9 +9,11 @@ export async function statefulPageTemplate(
|
|
|
9
9
|
return formatWithPrettier(`import { TempstreamJSX, Templatable } from "tempstream";
|
|
10
10
|
import { Context } from "koa";
|
|
11
11
|
import { StatefulPage, StatefulPageActionArgument } from "@sealcode/sealgen";
|
|
12
|
+
import type { BreadcrumbLabel } from "@sealcode/sealgen";
|
|
12
13
|
import html from "${rel("src/back/html.js")}";
|
|
13
14
|
|
|
14
15
|
export const actionName = "${action_name}";
|
|
16
|
+
export const breadcrumbLabel: BreadcrumbLabel = "${action_name}";
|
|
15
17
|
|
|
16
18
|
const actions = {
|
|
17
19
|
add: ({ state, inputs }: StatefulPageActionArgument<State>) => {
|