@sealcode/sealgen 0.15.6 → 0.15.8

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 (34) hide show
  1. package/@types/src/builder.d.ts +0 -1
  2. package/@types/src/esbuild-plugins/load-assets.d.ts +2 -1
  3. package/@types/src/esbuild-plugins/rewrite-asset-imports.d.ts +2 -1
  4. package/@types/src/forms/controls/autocomplete.d.ts +2 -1
  5. package/@types/src/forms/controls/checkbox-group.d.ts +1 -2
  6. package/@types/src/forms/controls/collapsible-field-group.d.ts +2 -1
  7. package/@types/src/forms/controls/form-control.d.ts +1 -1
  8. package/@types/src/forms/controls/multiple-files.d.ts +3 -3
  9. package/@types/src/forms/controls/photo.d.ts +1 -1
  10. package/@types/src/forms/controls/simple-input.d.ts +1 -1
  11. package/@types/src/forms/controls/textarea.d.ts +1 -1
  12. package/@types/src/forms/controls/tickable.d.ts +2 -2
  13. package/@types/src/forms/fields/field.d.ts +2 -2
  14. package/@types/src/forms/fields/file.d.ts +1 -1
  15. package/@types/src/forms/fields/structured-array.d.ts +1 -1
  16. package/@types/src/forms/form.d.ts +1 -1
  17. package/@types/src/mount.d.ts +2 -1
  18. package/@types/src/page/sealious-list.d.ts +1 -1
  19. package/@types/src/page/stateful-page.d.ts +6 -6
  20. package/@types/src/page/stateful-page.test.d.ts +1 -0
  21. package/@types/src/utils/translate.d.ts +1 -1
  22. package/lib/forms/controls/multiple-files.js +5 -3
  23. package/lib/page/stateful-page.js +4 -2
  24. package/lib/page/stateful-page.test.js +26 -0
  25. package/lib/templates/jdd-component.js +0 -1
  26. package/lib/templates/page.js +1 -1
  27. package/lib/templates/route-test.js +5 -7
  28. package/package.json +3 -1
  29. package/src/forms/controls/multiple-files.ts +5 -3
  30. package/src/page/stateful-page.test.ts +32 -0
  31. package/src/page/stateful-page.ts +15 -5
  32. package/src/templates/jdd-component.ts +0 -1
  33. package/src/templates/page.ts +1 -1
  34. package/src/templates/route-test.ts +5 -7
@@ -4,7 +4,6 @@ export declare abstract class Builder {
4
4
  abstract getName(): string;
5
5
  abstract _build(): Promise<void>;
6
6
  abstract dispose(): Promise<void>;
7
- constructor();
8
7
  ongoing_build: Promise<void> | null;
9
8
  build(notifier?: (message: string) => void): Promise<void>;
10
9
  }
@@ -1,4 +1,5 @@
1
+ import { PluginBuild } from "esbuild";
1
2
  export declare const load_assets_plugin: {
2
3
  name: string;
3
- setup(build: any): void;
4
+ setup(build: PluginBuild): void;
4
5
  };
@@ -1,4 +1,5 @@
1
+ import { PluginBuild } from "esbuild";
1
2
  export declare const rewrite_asset_imports_plugin: {
2
3
  name: string;
3
- setup(build: any): void;
4
+ setup(build: PluginBuild): void;
4
5
  };
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" resolution-mode="require"/>
1
2
  import { Context } from "koa";
2
3
  import { PickFromListField } from "../fields/pick-from-list.js";
3
4
  import { FormControlContext } from "./form-control.js";
@@ -18,5 +19,5 @@ export declare class Autocomplete extends SimpleInput<string | null> {
18
19
  getInputAttributes(fctx: FormControlContext): Promise<{
19
20
  list: string;
20
21
  }>;
21
- postInput(ctx: Context): Promise<string>;
22
+ postInput(ctx: Context): Promise<import("stream").Readable>;
22
23
  }
@@ -1,6 +1,5 @@
1
1
  import { FlatTemplatable } from "tempstream";
2
2
  import { EnumMultipleField } from "../fields/enum-multiple.js";
3
- import { FormField } from "../fields/field.js";
4
3
  import { CheckboxWithValue } from "./checkbox.js";
5
4
  import { FormControlContext } from "./form-control.js";
6
5
  import { FormFieldControl } from "./form-field-control.js";
@@ -18,5 +17,5 @@ export declare class CheckboxGroup<Options extends DefaultCheckboxOptions = Defa
18
17
  options: Options;
19
18
  constructor(field: EnumMultipleField<readonly string[], boolean>, options?: Options);
20
19
  wrapGroup(fctx: FormControlContext, checkboxes: CheckboxWithValue[]): Promise<FlatTemplatable>;
21
- render<Fields extends Record<string, FormField>>(fctx: FormControlContext): Promise<FlatTemplatable>;
20
+ render(fctx: FormControlContext): Promise<FlatTemplatable>;
22
21
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" resolution-mode="require"/>
1
2
  import { FlatTemplatable } from "tempstream";
2
3
  import { FieldGroup } from "./field-group.js";
3
4
  import { FormControl } from "./form-control.js";
@@ -14,7 +15,7 @@ export declare class CollapsibleFieldGroup extends FieldGroup {
14
15
  options: CollapsibleFieldGroupOptions;
15
16
  collapseID: string;
16
17
  constructor(controls: FormControl[], options?: CollapsibleFieldGroupOptions);
17
- getGroupLabelProps(): string;
18
+ getGroupLabelProps(): import("stream").Readable;
18
19
  preLabelContainer(): FlatTemplatable;
19
20
  postLabel(): FlatTemplatable;
20
21
  }
@@ -15,5 +15,5 @@ export declare class FormControlContext {
15
15
  export declare abstract class FormControl {
16
16
  abstract render(fctx: FormControlContext): FlatTemplatable | Promise<FlatTemplatable>;
17
17
  abstract role: "input" | "decoration" | "messages" | "submit";
18
- mount(router: Router): void;
18
+ mount(_router: Router): void;
19
19
  }
@@ -1,7 +1,7 @@
1
1
  /// <reference types="koa__router" />
2
2
  /// <reference types="node" resolution-mode="require"/>
3
3
  import Router from "@koa/router";
4
- import { CollectionItem, Field as SealiousField } from "sealious";
4
+ import { Collection, CollectionItem, Field as SealiousField } from "sealious";
5
5
  import { Context } from "koa";
6
6
  import { FlatTemplatable } from "tempstream";
7
7
  import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files.js";
@@ -25,9 +25,9 @@ export declare class MultipleFiles extends FormFieldControl {
25
25
  getFrameID(): string;
26
26
  renderFrame(_ctx: Context, content?: FlatTemplatable): import("stream").Readable;
27
27
  render(fctx: FormControlContext): FlatTemplatable | Promise<FlatTemplatable>;
28
- getReferencingCollection(): import("sealious").Collection;
28
+ getReferencingCollection(): Collection;
29
29
  getFileField(): SealiousField | null;
30
- extractFileFromItem(item: CollectionItem): Promise<FilePointer | null>;
30
+ extractFileFromItem(item: CollectionItem<Collection>): Promise<FilePointer | null>;
31
31
  renderFileItemPreview(_fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
32
32
  renderFileItem(fileItem: CollectionItem, file: FilePointer): Promise<FlatTemplatable>;
33
33
  renderFileRemoveButton(fileItem: CollectionItem): FlatTemplatable;
@@ -12,7 +12,7 @@ export declare class Photo extends File {
12
12
  imageRouter: KoaResponsiveImageRouter;
13
13
  constructor(field: FormField<boolean, FileContainer>, imageRouter: KoaResponsiveImageRouter, options?: SimpleInputOptions);
14
14
  getImgStyle(): Promise<string>;
15
- renderFilePreview(ctx: Context, file: FilePointer | null): Promise<FlatTemplatable>;
15
+ renderFilePreview(_ctx: Context, file: FilePointer | null): Promise<FlatTemplatable>;
16
16
  getWrapperModifiers(): string[];
17
17
  getInputAttributes(fctx: FormControlContext): Promise<{
18
18
  accept: string;
@@ -29,5 +29,5 @@ export declare class SimpleInput<ParsedValue, Options extends SimpleInputOptions
29
29
  postInput(_: Context): Promise<FlatTemplatable>;
30
30
  renderInput(_ctx: Context, attributes_str: string, _data: Record<string, FormDataValue>): Promise<FlatTemplatable>;
31
31
  getWrapperModifiers(): string[];
32
- render<Fields extends Record<string, FormField>>(fctx: FormControlContext): Promise<FlatTemplatable>;
32
+ render(fctx: FormControlContext): Promise<FlatTemplatable>;
33
33
  }
@@ -3,7 +3,7 @@ import { FormField } from "../fields/field.js";
3
3
  import { FormDataValue } from "../form-types.js";
4
4
  import { FormControlContext } from "./form-control.js";
5
5
  import { SimpleInput, SimpleInputOptions } from "./simple-input.js";
6
- export declare class Textarea<Required extends boolean> extends SimpleInput<string | null> {
6
+ export declare class Textarea extends SimpleInput<string | null> {
7
7
  field: FormField<boolean, string | null>;
8
8
  options: SimpleInputOptions & {
9
9
  label?: string;
@@ -11,7 +11,7 @@ export type TickableOptions = {
11
11
  hide_errors?: boolean;
12
12
  readonly?: boolean;
13
13
  };
14
- export declare abstract class Tickable<T extends unknown> extends FormFieldControl {
14
+ export declare abstract class Tickable<T> extends FormFieldControl {
15
15
  field: FormField<boolean, T>;
16
16
  options: TickableOptions;
17
17
  constructor(field: FormField<boolean, T>, options?: TickableOptions);
@@ -20,5 +20,5 @@ export declare abstract class Tickable<T extends unknown> extends FormFieldContr
20
20
  abstract isChecked(ctx: Context, data: Record<string, FormDataValue>, value: T): boolean;
21
21
  makeInputID(ctx: Context, data: Record<string, FormDataValue>, value: T): string;
22
22
  getWrapperClasses(ctx: Context, data: Record<string, FormDataValue>, value: T): string[];
23
- render<Fields extends Record<string, FormField>>(fctx: FormControlContext): Promise<FlatTemplatable>;
23
+ render(fctx: FormControlContext): Promise<FlatTemplatable>;
24
24
  }
@@ -21,7 +21,7 @@ export type FieldParseResult<T> = {
21
21
  error: string;
22
22
  parsed_value: null;
23
23
  };
24
- export declare abstract class FormField<Required extends boolean = boolean, ParsedValue extends unknown = unknown> {
24
+ export declare abstract class FormField<Required extends boolean = boolean, ParsedValue = unknown> {
25
25
  readonly required: Required;
26
26
  name: string;
27
27
  constructor(required: Required);
@@ -30,7 +30,7 @@ export declare abstract class FormField<Required extends boolean = boolean, Pars
30
30
  init(fieldname: string): Promise<void>;
31
31
  _validate(ctx: Context, value: ParsedValue): Promise<FormFieldValidationResponse>;
32
32
  abstract getEmptyValue(): ParsedValue;
33
- isValueValid(ctx: Context, value: ParsedValue): Promise<FormFieldValidationResponse>;
33
+ isValueValid(_ctx: Context, _value: ParsedValue): Promise<FormFieldValidationResponse>;
34
34
  abstract parse(ctx: Context, raw_value: FormDataValue): Promise<FieldParseResult<ParsedValue>>;
35
35
  getValue(ctx: Context, all_form_values: Record<string, FormDataValue>): Promise<{
36
36
  valid: boolean;
@@ -10,7 +10,7 @@ export declare class File<Required extends boolean> extends FormField<Required,
10
10
  readonly required: Required;
11
11
  file_manager: FileManager;
12
12
  constructor(required: Required, file_manager: FileManager);
13
- parse(ctx: Context, raw_value: FormDataValue): Promise<FieldParseResult<FileContainer>>;
13
+ parse(_ctx: Context, raw_value: FormDataValue): Promise<FieldParseResult<FileContainer>>;
14
14
  getDatabaseValue(ctx: Context, data: Record<string, FormDataValue>): Promise<FilePointer | undefined>;
15
15
  getEmptyValue(): {
16
16
  old: null;
@@ -5,7 +5,7 @@ import { FormField } from "./field.js";
5
5
  export declare class StructuredArrayField<Fields extends Record<string, Field>> extends FormField<true, FieldTypes.ArrayStorageInput<FieldsetOutput<Fields>>> {
6
6
  sealious_field: FieldTypes.StructuredArray<Fields>;
7
7
  constructor(sealious_field: FieldTypes.StructuredArray<Fields>);
8
- parse(context: Context, value: FormDataValue): Promise<{
8
+ parse(_context: Context, value: FormDataValue): Promise<{
9
9
  parsable: true;
10
10
  error: null;
11
11
  parsed_value: FieldsetOutput<Fields>;
@@ -4,7 +4,7 @@ import Router from "@koa/router";
4
4
  import { FlatTemplatable } from "tempstream";
5
5
  import { FormData, FormDataValue, FormMessage, FormReaction } from "./form-types.js";
6
6
  import { Fields, MountableWithFields, PageErrorMessage } from "../page/mountable-with-fields.js";
7
- export declare abstract class Form<F extends Fields, SubmitResult extends any> extends MountableWithFields<F> {
7
+ export declare abstract class Form<F extends Fields, SubmitResult> extends MountableWithFields<F> {
8
8
  defaultSuccessMessage: string;
9
9
  submitButtonText: string;
10
10
  action: string;
@@ -1,6 +1,7 @@
1
1
  /// <reference types="koa__router" />
2
2
  import Router from "@koa/router";
3
+ import { FileManager } from "@sealcode/file-manager";
3
4
  import { Mountable } from "./page/mountable.js";
4
5
  export declare function mount(router: Router, url: string | {
5
6
  rawURL: string;
6
- }, mountable: Mountable, use_dummy_app?: boolean): void;
7
+ }, mountable: Mountable, use_dummy_app?: boolean, file_manager?: FileManager | undefined): void;
@@ -11,7 +11,7 @@ export declare abstract class SealiousItemListPage<C extends Collection, F exten
11
11
  makeQuery(ctx: Context): Promise<ItemList<C>>;
12
12
  getItems(ctx: Context, page: number, itemsPerPage: number): Promise<CollectionItem<C>[]>;
13
13
  getFilterValues(ctx: Context): Promise<Record<string, string | undefined>>;
14
- renderItem(_: Context, item: CollectionItem<C>, index: number): Promise<FlatTemplatable>;
14
+ renderItem(_: Context, item: CollectionItem<C>, _index: number): Promise<FlatTemplatable>;
15
15
  canAccess: (ctx: Context) => Promise<{
16
16
  canAccess: boolean;
17
17
  message: string;
@@ -21,9 +21,9 @@ export type StateAndMetadata<State, Actions> = {
21
21
  action_args: null;
22
22
  $: Record<string, unknown>;
23
23
  };
24
- export declare abstract class StatefulPage<State extends Record<string, unknown>, Actions extends Record<string, (state: State, inputs: Record<string, string>, ...args: unknown[]) => State | Promise<State>>> extends Mountable {
24
+ export declare abstract class StatefulPage<State extends Record<string, unknown>, Actions extends Record<string, (ctx: BaseContext, state: State, inputs: Record<string, string>, ...args: unknown[]) => State | Promise<State>>> extends Mountable {
25
25
  abstract actions: Actions;
26
- abstract getInitialState(): State | Promise<State>;
26
+ abstract getInitialState(ctx: BaseContext): State | Promise<State>;
27
27
  abstract render(ctx: BaseContext, state: State, inputs: Record<string, string>): Templatable | Promise<Templatable>;
28
28
  canAccess(): Promise<{
29
29
  readonly canAccess: true;
@@ -37,15 +37,15 @@ export declare abstract class StatefulPage<State extends Record<string, unknown>
37
37
  label: string;
38
38
  content: string;
39
39
  };
40
- makeActionURL<ActionName extends keyof Actions>(action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
41
- makeActionButton<ActionName extends keyof Actions>(_state: State, action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
40
+ makeActionURL<ActionName extends keyof Actions>(action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>>): string;
41
+ makeActionButton<ActionName extends keyof Actions>(_state: State, action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>>): string;
42
42
  makeActionCallback<ActionName extends keyof Actions>(action_description: {
43
43
  action: ActionName;
44
44
  label?: string;
45
- } | ActionName, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
45
+ } | ActionName, ...args: ExtractTail<ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>>): string;
46
46
  rerender(): string;
47
47
  preprocessState(values: State): Promise<State>;
48
- preprocessOverrides(state: State, values: Record<string, unknown>): Promise<Record<string, unknown>>;
48
+ preprocessOverrides(_context: BaseContext, _state: State, values: Record<string, unknown>): Promise<Record<string, unknown>>;
49
49
  serializeState(_context: BaseContext, state: State): Promise<string>;
50
50
  deserializeState(_context: BaseContext, s: string): Promise<State>;
51
51
  extractState(ctx: BaseContext): Promise<StateAndMetadata<State, Actions>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,3 @@
1
1
  import { Context } from "koa";
2
2
  import { Translation } from "sealious";
3
- export declare function t(ctx: Context, key: string, props: any[], default_translation: Translation): string;
3
+ export declare function t(ctx: Context, key: string, props: unknown[], default_translation: Translation): string;
@@ -102,9 +102,11 @@ class MultipleFiles extends FormFieldControl {
102
102
  if (!sealious_field) {
103
103
  return null;
104
104
  }
105
- return await item.collection.app.FileManager.fromToken(
106
- item.get(sealious_field.name).token
107
- );
105
+ const token = item.get(sealious_field.name).token;
106
+ if (!token) {
107
+ return null;
108
+ }
109
+ return await item.collection.app.FileManager.fromToken(token);
108
110
  }
109
111
  async renderFileItemPreview(_fileItem, file) {
110
112
  return file.getOriginalFilename();
@@ -84,7 +84,7 @@ class StatefulPage extends Mountable {
84
84
  async preprocessState(values) {
85
85
  return values;
86
86
  }
87
- async preprocessOverrides(_state, values) {
87
+ async preprocessOverrides(_context, _state, values) {
88
88
  return values;
89
89
  }
90
90
  async serializeState(_context, state) {
@@ -123,6 +123,7 @@ class StatefulPage extends Mountable {
123
123
  const $body = ctx.$body;
124
124
  let state_overrides = $body.$ || {};
125
125
  state_overrides = await this.preprocessOverrides(
126
+ ctx,
126
127
  original_state,
127
128
  state_overrides
128
129
  );
@@ -169,12 +170,13 @@ class StatefulPage extends Mountable {
169
170
  }
170
171
  mount(router, path) {
171
172
  router.get(path, async (ctx) => {
172
- ctx.body = this.render(ctx, await this.getInitialState(), {});
173
+ ctx.body = this.render(ctx, await this.getInitialState(ctx), {});
173
174
  });
174
175
  router.post(path, async (ctx) => {
175
176
  const { action, state, inputs, action_args } = await this.extractState(ctx);
176
177
  if (action) {
177
178
  const new_state = await this.actions[action](
179
+ ctx,
178
180
  state,
179
181
  inputs,
180
182
  ...JSON.parse(
@@ -0,0 +1,26 @@
1
+ import { tempstream } from "tempstream";
2
+ import { StatefulPage } from "./stateful-page.js";
3
+ describe("stateful page", () => {
4
+ it("handles a basic case with action buttons", () => {
5
+ const actions = {
6
+ some_action: async (_ctx, _state, _inputs, _arg1) => {
7
+ }
8
+ };
9
+ new class extends StatefulPage {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.actions = actions;
13
+ }
14
+ getInitialState(_ctx) {
15
+ return {};
16
+ }
17
+ wrapInLayout(_ctx, content, _state) {
18
+ return content;
19
+ }
20
+ render(_ctx, state) {
21
+ return tempstream`<div>${this.makeActionButton(state, "some_action", 2)}</div>`;
22
+ }
23
+ }();
24
+ });
25
+ });
26
+ //# sourceMappingURL=stateful-page.test.js.map
@@ -35,7 +35,6 @@ export class ${toPascalCase(
35
35
  );
36
36
  }
37
37
  }
38
-
39
38
  `;
40
39
  }
41
40
  export {
@@ -1,7 +1,7 @@
1
1
  import { curryImportPath } from "../utils/import-path.js";
2
2
  async function pageTemplate(action_name, newfilefullpath) {
3
3
  const rel = curryImportPath(newfilefullpath);
4
- return `import { Context } from "koa";
4
+ return `import type { Context } from "koa";
5
5
  import { TempstreamJSX } from "tempstream";
6
6
  import { Page } from "@sealcode/sealgen";
7
7
  import html from "${rel("src/back/html.js")}";
@@ -9,7 +9,7 @@ import { VERY_LONG_TEST_TIMEOUT, webhintURL } from "${rel(
9
9
  )}";
10
10
  import { ${action_name}URL } from "${rel("src/back/routes/urls.js")}";
11
11
  import { getBrowser } from "${rel("src/back/test_utils/browser-creator.js")}";
12
- import { Browser, BrowserContext, Page } from "@playwright/test";
12
+ import type { Browser, BrowserContext, Page } from "@playwright/test";
13
13
 
14
14
  describe("${action_name} webhint", () => {
15
15
  it(
@@ -23,9 +23,8 @@ describe("${action_name} webhint", () => {
23
23
  // const response = await rest_api.get(${action_name}URL);
24
24
  // await webhintHTML(response);
25
25
  });
26
- },
27
- VERY_LONG_TEST_TIMEOUT
28
- );
26
+ }
27
+ ).timeout(VERY_LONG_TEST_TIMEOUT);
29
28
  });
30
29
 
31
30
  describe("${action_name}", () => {
@@ -49,9 +48,8 @@ describe("${action_name}", () => {
49
48
  return withProdApp(async ({ base_url }) => {
50
49
  await page.goto(base_url + ${action_name}URL);
51
50
  });
52
- },
53
- VERY_LONG_TEST_TIMEOUT
54
- );
51
+ }
52
+ ).timeout(VERY_LONG_TEST_TIMEOUT);
55
53
  });
56
54
  `;
57
55
  }
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.15.6",
3
+ "version": "0.15.8",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "prebuild": "npm run lint && rm -rf lib",
9
9
  "build": "node ./build.js",
10
+ "postbuild": "npm run build-types",
10
11
  "pretest": "npm run build",
11
12
  "test": "node test.cjs",
12
13
  "typecheck": "tsc --noemit",
14
+ "build-types": "tsc --emitDeclarationOnly",
13
15
  "prepare": "npm run build",
14
16
  "lint": "eslint src",
15
17
  "preinstrument": "npm run build && rm -fr .xunit coverage lib-instrumented",
@@ -146,9 +146,11 @@ export class MultipleFiles extends FormFieldControl {
146
146
  if (!sealious_field) {
147
147
  return null;
148
148
  }
149
- return await item.collection.app.FileManager.fromToken(
150
- (item.get(sealious_field.name) as FilePointer).token
151
- );
149
+ const token = (item.get(sealious_field.name) as FilePointer).token;
150
+ if (!token) {
151
+ return null;
152
+ }
153
+ return await item.collection.app.FileManager.fromToken(token);
152
154
  }
153
155
 
154
156
  async renderFileItemPreview(
@@ -0,0 +1,32 @@
1
+ import { BaseContext } from "koa";
2
+ import { Templatable, tempstream } from "tempstream";
3
+ import { StatefulPage } from "./stateful-page.js";
4
+
5
+ describe("stateful page", () => {
6
+ it("handles a basic case with action buttons", () => {
7
+ type TestState = {};
8
+ const actions = <const>{
9
+ some_action: async (
10
+ _ctx: BaseContext,
11
+ _state: TestState,
12
+ _inputs: Record<string, unknown>,
13
+ _arg1: number
14
+ ) => {},
15
+ };
16
+ new (class extends StatefulPage<TestState, typeof actions> {
17
+ actions = actions;
18
+
19
+ getInitialState(_ctx: BaseContext) {
20
+ return {};
21
+ }
22
+
23
+ wrapInLayout(_ctx: BaseContext, content: Templatable, _state: TestState): Templatable {
24
+ return content;
25
+ }
26
+
27
+ render(_ctx: BaseContext, state: TestState) {
28
+ return tempstream`<div>${this.makeActionButton(state, "some_action", 2)}</div>`;
29
+ }
30
+ })();
31
+ });
32
+ });
@@ -37,6 +37,7 @@ export abstract class StatefulPage<
37
37
  Actions extends Record<
38
38
  string,
39
39
  (
40
+ ctx: BaseContext,
40
41
  state: State,
41
42
  inputs: Record<string, string>,
42
43
  ...args: unknown[]
@@ -44,7 +45,7 @@ export abstract class StatefulPage<
44
45
  >
45
46
  > extends Mountable {
46
47
  abstract actions: Actions;
47
- abstract getInitialState(): State | Promise<State>;
48
+ abstract getInitialState(ctx: BaseContext): State | Promise<State>;
48
49
  abstract render(
49
50
  ctx: BaseContext,
50
51
  state: State,
@@ -118,7 +119,9 @@ export abstract class StatefulPage<
118
119
 
119
120
  makeActionURL<ActionName extends keyof Actions>(
120
121
  action_description: StatefulPageActionDescription<ActionName>,
121
- ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
122
+ ...args: ExtractTail<
123
+ ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
124
+ >
122
125
  ) {
123
126
  const { action } = this.extractActionAndLabel(action_description);
124
127
  return `./?action=${action}&action_args=${encodeURIComponent(
@@ -130,7 +133,9 @@ export abstract class StatefulPage<
130
133
  makeActionButton<ActionName extends keyof Actions>(
131
134
  _state: State,
132
135
  action_description: StatefulPageActionDescription<ActionName>,
133
- ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
136
+ ...args: ExtractTail<
137
+ ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
138
+ >
134
139
  ) {
135
140
  const { label, content } =
136
141
  this.extractActionAndLabel(action_description);
@@ -152,7 +157,9 @@ export abstract class StatefulPage<
152
157
  label?: string;
153
158
  }
154
159
  | ActionName,
155
- ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
160
+ ...args: ExtractTail<
161
+ ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
162
+ >
156
163
  ) {
157
164
  return `(()=>{const form = this.closest('form'); form.action='${this.makeActionURL(
158
165
  action_description,
@@ -169,6 +176,7 @@ export abstract class StatefulPage<
169
176
  }
170
177
 
171
178
  async preprocessOverrides(
179
+ _context: BaseContext,
172
180
  _state: State,
173
181
  values: Record<string, unknown>
174
182
  ): Promise<Record<string, unknown>> {
@@ -223,6 +231,7 @@ export abstract class StatefulPage<
223
231
  const $body = ctx.$body;
224
232
  let state_overrides = $body.$ || {};
225
233
  state_overrides = await this.preprocessOverrides(
234
+ ctx,
226
235
  original_state,
227
236
  state_overrides
228
237
  );
@@ -283,7 +292,7 @@ export abstract class StatefulPage<
283
292
 
284
293
  mount(router: Router, path: string) {
285
294
  router.get(path, async (ctx) => {
286
- ctx.body = this.render(ctx, await this.getInitialState(), {});
295
+ ctx.body = this.render(ctx, await this.getInitialState(ctx), {});
287
296
  });
288
297
 
289
298
  router.post(path, async (ctx) => {
@@ -291,6 +300,7 @@ export abstract class StatefulPage<
291
300
  await this.extractState(ctx);
292
301
  if (action) {
293
302
  const new_state = await this.actions[action](
303
+ ctx,
294
304
  state,
295
305
  inputs,
296
306
  ...(JSON.parse(
@@ -36,6 +36,5 @@ export class ${toPascalCase(
36
36
  );
37
37
  }
38
38
  }
39
-
40
39
  `;
41
40
  }
@@ -4,7 +4,7 @@ export async function pageTemplate(
4
4
  newfilefullpath: string
5
5
  ): Promise<string> {
6
6
  const rel = curryImportPath(newfilefullpath);
7
- return `import { Context } from "koa";
7
+ return `import type { Context } from "koa";
8
8
  import { TempstreamJSX } from "tempstream";
9
9
  import { Page } from "@sealcode/sealgen";
10
10
  import html from "${rel("src/back/html.js")}";
@@ -10,7 +10,7 @@ import { VERY_LONG_TEST_TIMEOUT, webhintURL } from "${rel(
10
10
  )}";
11
11
  import { ${action_name}URL } from "${rel("src/back/routes/urls.js")}";
12
12
  import { getBrowser } from "${rel("src/back/test_utils/browser-creator.js")}";
13
- import { Browser, BrowserContext, Page } from "@playwright/test";
13
+ import type { Browser, BrowserContext, Page } from "@playwright/test";
14
14
 
15
15
  describe("${action_name} webhint", () => {
16
16
  it(
@@ -24,9 +24,8 @@ describe("${action_name} webhint", () => {
24
24
  // const response = await rest_api.get(${action_name}URL);
25
25
  // await webhintHTML(response);
26
26
  });
27
- },
28
- VERY_LONG_TEST_TIMEOUT
29
- );
27
+ }
28
+ ).timeout(VERY_LONG_TEST_TIMEOUT);
30
29
  });
31
30
 
32
31
  describe("${action_name}", () => {
@@ -50,9 +49,8 @@ describe("${action_name}", () => {
50
49
  return withProdApp(async ({ base_url }) => {
51
50
  await page.goto(base_url + ${action_name}URL);
52
51
  });
53
- },
54
- VERY_LONG_TEST_TIMEOUT
55
- );
52
+ }
53
+ ).timeout(VERY_LONG_TEST_TIMEOUT);
56
54
  });
57
55
  `;
58
56
  }