@dolphy-app/extension-sdk 0.3.0 → 0.4.0
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/README.md +15 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.js +7 -1
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +7 -1
- package/dist/testing.d.ts +190 -6
- package/dist/testing.js +462 -34
- package/docs/debugging.md +210 -0
- package/docs/no-build.md +99 -0
- package/docs/quick-start.md +172 -0
- package/docs/recipe-command-panel.md +211 -0
- package/docs/recipe-event-storage.md +266 -0
- package/docs/recipe-exercise-type.md +379 -0
- package/docs/recipe-import-export.md +241 -0
- package/docs/recipe-settings.md +161 -0
- package/docs/recipe-theme.md +116 -0
- package/docs/recipe-ui-kit.md +172 -0
- package/docs/recipe-when-dependencies.md +158 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
SDK for extension authors: defineExtension, defineAnswerView, test helpers
|
|
4
4
|
|
|
5
|
-
The package version equals the version of the Dolphy app release it was published from (0.
|
|
5
|
+
The package version equals the version of the Dolphy app release it was published from (0.4.0).
|
|
6
6
|
|
|
7
7
|
`@dolphy-app/extension-sdk` — extension code (`defineExtension`,
|
|
8
8
|
`defineExerciseType`), answer views (`defineAnswerView`), panels,
|
|
@@ -16,6 +16,20 @@ import { defineExtension } from '@dolphy-app/extension-sdk';
|
|
|
16
16
|
import { loadExerciseType } from '@dolphy-app/extension-sdk/testing';
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
The package ships a guide in `docs/` (`node_modules/@dolphy-app/extension-sdk/docs/`
|
|
20
|
+
after the install): a
|
|
21
|
+
[quick start](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/quick-start.md), recipes for
|
|
22
|
+
[an exercise type](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-exercise-type.md),
|
|
23
|
+
[a theme](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-theme.md),
|
|
24
|
+
[a command and a panel](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-command-panel.md),
|
|
25
|
+
[events and storage](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-event-storage.md),
|
|
26
|
+
[settings](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-settings.md),
|
|
27
|
+
[an importer and an exporter](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-import-export.md),
|
|
28
|
+
[visibility conditions and dependencies](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-when-dependencies.md) and
|
|
29
|
+
[a panel on the UI kit](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/recipe-ui-kit.md), a path
|
|
30
|
+
[without a build](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/no-build.md) and notes on
|
|
31
|
+
[debugging](https://github.com/dolphy-app/dolphy/blob/main/packages/extension-sdk/docs/debugging.md).
|
|
32
|
+
|
|
19
33
|
## Installation
|
|
20
34
|
|
|
21
35
|
```sh
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as defineAnswerView, i as MountAnswerView, n as AnswerViewApi, r as AnswerViewInstance, t as AnswerView } from "./answer-view-D6wnyThb.js";
|
|
2
|
-
import { CommandHandler, ExerciseTypeHandler, ExtensionContext as ExtensionContext$1, ExtensionIdSet, ExtensionModule, GradePolicyHandler, JsonValue, LearningEventHandler, MarkdownRenderContext, MarkdownRendererModule, NotifyEffect, OpenPanelEffect, PanelContext as PanelContext$1, PanelModule } from "@dolphy-app/extension-api";
|
|
2
|
+
import { CommandHandler, ExerciseTypeHandler, ExporterHandler, ExtensionContext as ExtensionContext$1, ExtensionIdSet, ExtensionModule, GradePolicyHandler, ImporterHandler, JsonValue, LearningEventHandler, MarkdownRenderContext, MarkdownRendererModule, NotifyEffect, OpenPanelEffect, PanelContext as PanelContext$1, PanelModule, ScheduleHandler, WidgetContext as WidgetContext$1, WidgetModule } from "@dolphy-app/extension-api";
|
|
3
3
|
export * from "@dolphy-app/extension-api";
|
|
4
4
|
//#region packages/extension-sdk/src/ids.d.ts
|
|
5
5
|
/**
|
|
@@ -34,6 +34,8 @@ type HasGeneratedIds = [keyof ExtensionIds] extends [never] ? false : true;
|
|
|
34
34
|
type ExtensionContext = ExtensionContext$1<ResolvedIds>;
|
|
35
35
|
/** Context of a panel module; `call` accepts the declared command ids only. */
|
|
36
36
|
type PanelContext = PanelContext$1<ResolvedIds['commands']>;
|
|
37
|
+
/** Context of a widget module; `call` accepts the declared command ids only. */
|
|
38
|
+
type WidgetContext = WidgetContext$1<ResolvedIds['commands']>;
|
|
37
39
|
/**
|
|
38
40
|
* A record that holds exactly the declared ids: a missing and an extra key are
|
|
39
41
|
* both compile errors. With no generated declarations any keys are accepted;
|
|
@@ -48,6 +50,8 @@ type Exact<Id extends string, Value> = [HasGeneratedIds] extends [false] ? {
|
|
|
48
50
|
type ExtensionViews = Exact<ResolvedIds['exerciseTypes'], AnswerView>;
|
|
49
51
|
/** `export const panels = { … } satisfies ExtensionPanels`: one `defineExtensionPanel` per declared panel. */
|
|
50
52
|
type ExtensionPanels = Exact<ResolvedIds['panels'], PanelModule<HTMLElement, ResolvedIds['commands']>>;
|
|
53
|
+
/** `export const widgets = { … } satisfies ExtensionWidgets`: one `defineExtensionWidget` per declared widget. */
|
|
54
|
+
type ExtensionWidgets = Exact<ResolvedIds['widgets'], WidgetModule<HTMLElement, ResolvedIds['commands']>>;
|
|
51
55
|
/** `export const markdown = { … } satisfies ExtensionMarkdown`: one `defineMarkdownRenderer` per declared language. */
|
|
52
56
|
type ExtensionMarkdown = Exact<ResolvedIds['markdownLanguages'], MarkdownRendererModule<HTMLElement>>;
|
|
53
57
|
//#endregion
|
|
@@ -66,6 +70,7 @@ interface InActivate {
|
|
|
66
70
|
/**
|
|
67
71
|
* A record value in `defineExtension` that says "this id is registered in
|
|
68
72
|
* `activate`" (`ctx.commands.register`, `ctx.events.on`,
|
|
73
|
+
* `ctx.importers.register`, `ctx.exporters.register`, `ctx.schedule.on`,
|
|
69
74
|
* `ctx.registerExerciseType`, `ctx.registerGradePolicy`) rather than by a
|
|
70
75
|
* handler in the record. Needed because the records must name every declared
|
|
71
76
|
* id: a handler that needs `ctx` is written in `activate`, and its id gets this
|
|
@@ -85,11 +90,12 @@ type Ids = ResolvedIds;
|
|
|
85
90
|
/** Learning-event handlers by event name; the events must be declared in `contributes.events`, the `learning.events` permission is needed. */
|
|
86
91
|
type EventHandlers = { readonly [N in Ids['events']]: LearningEventHandler<N> | InActivate; };
|
|
87
92
|
/**
|
|
88
|
-
* What `defineExtension` takes. `exerciseTypes`, `gradePolicies`, `events
|
|
89
|
-
* `commands`
|
|
90
|
-
*
|
|
93
|
+
* What `defineExtension` takes. `exerciseTypes`, `gradePolicies`, `events`,
|
|
94
|
+
* `commands`, `schedules`, `importers` and `exporters` name every id
|
|
95
|
+
* `extension.json` declares for them, exactly: a handler, or `inActivate` for
|
|
96
|
+
* an id that `activate` registers.
|
|
91
97
|
*/
|
|
92
|
-
type ExtensionDefinition = Section<'exerciseTypes', Ids['exerciseTypes'], { readonly [K in Ids['exerciseTypes']]: ExerciseTypeHandler | InActivate; }> & Section<'gradePolicies', Ids['gradePolicies'], { readonly [K in Ids['gradePolicies']]: GradePolicyHandler | InActivate; }> & Section<'events', Ids['events'], EventHandlers> & Section<'commands', Ids['commands'], { readonly [K in Ids['commands']]: CommandHandler | InActivate; }> & {
|
|
98
|
+
type ExtensionDefinition = Section<'exerciseTypes', Ids['exerciseTypes'], { readonly [K in Ids['exerciseTypes']]: ExerciseTypeHandler | InActivate; }> & Section<'gradePolicies', Ids['gradePolicies'], { readonly [K in Ids['gradePolicies']]: GradePolicyHandler | InActivate; }> & Section<'events', Ids['events'], EventHandlers> & Section<'commands', Ids['commands'], { readonly [K in Ids['commands']]: CommandHandler | InActivate; }> & Section<'schedules', Ids['schedules'], { readonly [K in Ids['schedules']]: ScheduleHandler | InActivate; }> & Section<'importers', Ids['importers'], { readonly [K in Ids['importers']]: ImporterHandler | InActivate; }> & Section<'exporters', Ids['exporters'], { readonly [K in Ids['exporters']]: ExporterHandler | InActivate; }> & {
|
|
93
99
|
/** Runs after the records are registered. */
|
|
94
100
|
activate?(context: ExtensionContext): void | Promise<void>;
|
|
95
101
|
deactivate?(): void | Promise<void>;
|
|
@@ -104,5 +110,7 @@ export declare const defineMarkdownRenderer: (render: (source: string, container
|
|
|
104
110
|
//#region packages/extension-sdk/src/panel.d.ts
|
|
105
111
|
/** An entry of `panels[<panel id>]` in `src/index.ts` (`contributes.panels`); `ctx.call` accepts the declared command ids. */
|
|
106
112
|
export declare const defineExtensionPanel: (module: PanelModule<HTMLElement, ResolvedIds["commands"]>) => PanelModule<HTMLElement, ResolvedIds["commands"]>;
|
|
113
|
+
/** An entry of `widgets[<widget id>]` in `src/index.ts` (`contributes.widgets`); `ctx.call` accepts the declared command ids. */
|
|
114
|
+
export declare const defineExtensionWidget: (module: WidgetModule<HTMLElement, ResolvedIds["commands"]>) => WidgetModule<HTMLElement, ResolvedIds["commands"]>;
|
|
107
115
|
//#endregion
|
|
108
|
-
export { type AnswerView, type AnswerViewApi, type AnswerViewInstance, type EventHandlers, type ExtensionContext, type ExtensionDefinition, type ExtensionIds, type ExtensionMarkdown, type ExtensionPanels, type ExtensionViews, type InActivate, type MountAnswerView, type PanelContext, defineAnswerView };
|
|
116
|
+
export { type AnswerView, type AnswerViewApi, type AnswerViewInstance, type EventHandlers, type ExtensionContext, type ExtensionDefinition, type ExtensionIds, type ExtensionMarkdown, type ExtensionPanels, type ExtensionViews, type ExtensionWidgets, type InActivate, type MountAnswerView, type PanelContext, type WidgetContext, defineAnswerView };
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const openPanel = (panelId, props) => props === void 0 ? { openPanel: panelId }
|
|
|
14
14
|
/**
|
|
15
15
|
* A record value in `defineExtension` that says "this id is registered in
|
|
16
16
|
* `activate`" (`ctx.commands.register`, `ctx.events.on`,
|
|
17
|
+
* `ctx.importers.register`, `ctx.exporters.register`, `ctx.schedule.on`,
|
|
17
18
|
* `ctx.registerExerciseType`, `ctx.registerGradePolicy`) rather than by a
|
|
18
19
|
* handler in the record. Needed because the records must name every declared
|
|
19
20
|
* id: a handler that needs `ctx` is written in `activate`, and its id gets this
|
|
@@ -40,6 +41,9 @@ const defineExtension = /* @__NO_SIDE_EFFECTS__ */ (declared) => {
|
|
|
40
41
|
for (const [id, handler] of handlersOf(definition.gradePolicies)) registrations.push(context.registerGradePolicy(id, handler));
|
|
41
42
|
for (const [name, handler] of handlersOf(definition.events)) registrations.push(context.events.on(name, handler));
|
|
42
43
|
for (const [id, handler] of handlersOf(definition.commands)) registrations.push(context.commands.register(id, handler));
|
|
44
|
+
for (const [id, handler] of handlersOf(definition.schedules)) registrations.push(context.schedule.on(id, handler));
|
|
45
|
+
for (const [id, handler] of handlersOf(definition.importers)) registrations.push(context.importers.register(id, handler));
|
|
46
|
+
for (const [id, handler] of handlersOf(definition.exporters)) registrations.push(context.exporters.register(id, handler));
|
|
43
47
|
};
|
|
44
48
|
const activate = async (context) => {
|
|
45
49
|
try {
|
|
@@ -86,6 +90,8 @@ const defineMarkdownRenderer = /* @__NO_SIDE_EFFECTS__ */ (render) => ({ render
|
|
|
86
90
|
//#region packages/extension-sdk/src/panel.ts
|
|
87
91
|
/** An entry of `panels[<panel id>]` in `src/index.ts` (`contributes.panels`); `ctx.call` accepts the declared command ids. */
|
|
88
92
|
const defineExtensionPanel = /* @__NO_SIDE_EFFECTS__ */ (module) => module;
|
|
93
|
+
/** An entry of `widgets[<widget id>]` in `src/index.ts` (`contributes.widgets`); `ctx.call` accepts the declared command ids. */
|
|
94
|
+
const defineExtensionWidget = /* @__NO_SIDE_EFFECTS__ */ (module) => module;
|
|
89
95
|
|
|
90
96
|
//#endregion
|
|
91
|
-
export { defineAnswerView, defineExerciseType, defineExtension, defineExtensionPanel, defineMarkdownRenderer, inActivate, notify, openPanel };
|
|
97
|
+
export { defineAnswerView, defineExerciseType, defineExtension, defineExtensionPanel, defineExtensionWidget, defineMarkdownRenderer, inActivate, notify, openPanel };
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { t as AnswerView } from "./answer-view-D6wnyThb.js";
|
|
2
|
-
import { MarkdownRendererModule, PanelModule } from "@dolphy-app/extension-api";
|
|
2
|
+
import { MarkdownRendererModule, PanelModule, WidgetModule } from "@dolphy-app/extension-api";
|
|
3
3
|
//#region packages/extension-sdk/src/answer-element.d.ts
|
|
4
4
|
/** Defines the kind's custom element; calling again with the same tag changes nothing. */
|
|
5
5
|
export declare const registerAnswerView: (tag: string, view: AnswerView) => void;
|
|
6
6
|
//#endregion
|
|
7
7
|
//#region packages/extension-sdk/src/runtime.d.ts
|
|
8
8
|
type PanelEntry = PanelModule<HTMLElement>;
|
|
9
|
+
type WidgetEntry = WidgetModule<HTMLElement>;
|
|
9
10
|
type MarkdownEntry = MarkdownRendererModule<HTMLElement>;
|
|
10
11
|
/** Panel module that selects the `panels` entry by `ctx.panelId` (for a file shared by several panels). */
|
|
11
12
|
export declare const dispatchPanels: (panels: Readonly<Record<string, PanelEntry>>) => PanelEntry;
|
|
13
|
+
/** Widget module that selects the `widgets` entry by `ctx.widgetId` (for a file shared by several widgets). */
|
|
14
|
+
export declare const dispatchWidgets: (widgets: Readonly<Record<string, WidgetEntry>>) => WidgetEntry;
|
|
12
15
|
/** Renderer module that selects the `markdown` entry by block language. */
|
|
13
16
|
export declare const dispatchMarkdown: (renderers: Readonly<Record<string, MarkdownEntry>>) => MarkdownEntry;
|
|
14
17
|
//#endregion
|
package/dist/runtime.js
CHANGED
|
@@ -7,6 +7,12 @@ const dispatchPanels = (panels) => ({ mount(container, context) {
|
|
|
7
7
|
if (panel === void 0) throw new Error(`panel '${context.panelId}' is not exported`);
|
|
8
8
|
return panel.mount(container, context);
|
|
9
9
|
} });
|
|
10
|
+
/** Widget module that selects the `widgets` entry by `ctx.widgetId` (for a file shared by several widgets). */
|
|
11
|
+
const dispatchWidgets = (widgets) => ({ mount(container, context) {
|
|
12
|
+
const widget = widgets[context.widgetId];
|
|
13
|
+
if (widget === void 0) throw new Error(`widget '${context.widgetId}' is not exported`);
|
|
14
|
+
return widget.mount(container, context);
|
|
15
|
+
} });
|
|
10
16
|
/** Renderer module that selects the `markdown` entry by block language. */
|
|
11
17
|
const dispatchMarkdown = (renderers) => ({ render(source, container, context) {
|
|
12
18
|
const renderer = renderers[context.language];
|
|
@@ -15,4 +21,4 @@ const dispatchMarkdown = (renderers) => ({ render(source, container, context) {
|
|
|
15
21
|
} });
|
|
16
22
|
|
|
17
23
|
//#endregion
|
|
18
|
-
export { dispatchMarkdown, dispatchPanels, registerAnswerView };
|
|
24
|
+
export { dispatchMarkdown, dispatchPanels, dispatchWidgets, registerAnswerView };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as AnswerView } from "./answer-view-D6wnyThb.js";
|
|
2
|
-
import { AnswerChangeDetail, AnswerElementProps, CommandOutcome, ExtensionCommands, ExtensionEvents, ExtensionLogger, ExtensionModule, ExtensionSettings, ExtensionStorage, GradePolicyInput, GradeResult, GradeValue, JsonSchema, JsonValue, LearningEventName, LearningEventPayloads, LibraryReader, PanelModule, SettingContribution, SettingValue } from "@dolphy-app/extension-api";
|
|
2
|
+
import { AnswerChangeDetail, AnswerElementProps, CommandOutcome, ExportInput, ExportResult, ExtensionCommands, ExtensionEvents, ExtensionExporters, ExtensionImporters, ExtensionLogger, ExtensionModule, ExtensionNotification, ExtensionNotifications, ExtensionSchedule, ExtensionSecrets, ExtensionSettings, ExtensionStats, ExtensionStorage, GradePolicyInput, GradeResult, GradeValue, ImportInput, ImportResult, ImporterInputKind, JsonSchema, JsonValue, LearningEventName, LearningEventPayloads, LibraryReader, PanelContextInfo, PanelModule, SettingContribution, SettingValue, WidgetModule } from "@dolphy-app/extension-api";
|
|
3
3
|
//#region packages/extension-sdk/src/testing.d.ts
|
|
4
4
|
export declare const createMemoryLibrary: (files: Readonly<Record<string, string>>) => LibraryReader;
|
|
5
5
|
/**
|
|
@@ -8,6 +8,22 @@ export declare const createMemoryLibrary: (files: Readonly<Record<string, string
|
|
|
8
8
|
* JSON text and returned as copies; on rejection nothing changes.
|
|
9
9
|
*/
|
|
10
10
|
export declare const createMemoryStorage: () => ExtensionStorage;
|
|
11
|
+
/** In-memory secrets for tests: `ExtensionSecrets` plus a switch that imitates a missing system key store. */
|
|
12
|
+
export interface MemorySecrets extends ExtensionSecrets {
|
|
13
|
+
/** Imitates the system key store becoming (un)available; stored values are kept. */
|
|
14
|
+
setAvailable(available: boolean): void;
|
|
15
|
+
}
|
|
16
|
+
export interface MemorySecretsOptions {
|
|
17
|
+
/** Default `true`; `false` imitates Linux `basic_text`, no key store, or an app that is not ready. */
|
|
18
|
+
available?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* In-memory secrets with the engine's limits and errors
|
|
22
|
+
* (`EXTENSION_SECRET_LIMITS`, `StorageQuotaError`, `SecretsUnavailableError`):
|
|
23
|
+
* without a key store `set` and `get` of an existing key throw, `get` of a
|
|
24
|
+
* missing key gives `undefined` and `delete` works.
|
|
25
|
+
*/
|
|
26
|
+
export declare const createMemorySecrets: (options?: MemorySecretsOptions) => MemorySecrets;
|
|
11
27
|
export interface MemorySettings extends ExtensionSettings {
|
|
12
28
|
/**
|
|
13
29
|
* Changes the value as the user does in the dialog: the value is validated
|
|
@@ -52,14 +68,136 @@ export interface MemoryCommandsOptions {
|
|
|
52
68
|
}
|
|
53
69
|
/** In-memory commands: the same registration rules and result parsing as the host. */
|
|
54
70
|
export declare const createMemoryCommands: (options?: MemoryCommandsOptions) => MemoryCommands;
|
|
71
|
+
export interface MemorySchedule extends ExtensionSchedule {
|
|
72
|
+
/**
|
|
73
|
+
* Fires a schedule the way the host does and awaits the handler: resolves
|
|
74
|
+
* `true` once the handler returned. With no subscription, or while the
|
|
75
|
+
* handler of the previous firing is still running, the firing is skipped and
|
|
76
|
+
* resolves `false`. Unlike the host, a handler failure is not swallowed but
|
|
77
|
+
* rejects the promise, and the 10 s handler timeout is not applied.
|
|
78
|
+
*/
|
|
79
|
+
fire(id: string): Promise<boolean>;
|
|
80
|
+
/** Subscribed schedules in subscription order. */
|
|
81
|
+
ids(): string[];
|
|
82
|
+
}
|
|
83
|
+
export interface MemoryScheduleOptions {
|
|
84
|
+
/** Schedules from `contributes.schedules`: subscribing to another throws, as in the host. Unset — any are allowed. */
|
|
85
|
+
declared?: readonly string[];
|
|
86
|
+
}
|
|
87
|
+
/** In-memory schedule subscriptions: one handler per schedule and no overlapping firings, as in the host. */
|
|
88
|
+
export declare const createMemorySchedule: (options?: MemoryScheduleOptions) => MemorySchedule;
|
|
89
|
+
export interface MemoryImporters extends ExtensionImporters {
|
|
90
|
+
/**
|
|
91
|
+
* Runs a registered importer the way the host does: the input must have the
|
|
92
|
+
* form the importer declares (`text` unless `input: 'bytes'`) and at most
|
|
93
|
+
* `EXTENSION_TRANSFER_LIMITS.inputBytes`; the result goes through the host's
|
|
94
|
+
* rules (`normalizeImportResult`: paths, sizes, number of files). An
|
|
95
|
+
* unregistered importer and an invalid result reject the promise. The 30 s
|
|
96
|
+
* handler timeout is not applied.
|
|
97
|
+
*/
|
|
98
|
+
run(id: string, input: ImportInput): Promise<ImportResult>;
|
|
99
|
+
/** Registered importers in registration order. */
|
|
100
|
+
ids(): string[];
|
|
101
|
+
}
|
|
102
|
+
export interface MemoryImportersOptions {
|
|
103
|
+
/** Importers from `contributes.importers`: registering another throws and `input` is checked, as in the host. Unset — any are allowed. */
|
|
104
|
+
declaredImporters?: readonly {
|
|
105
|
+
id: string;
|
|
106
|
+
input?: ImporterInputKind;
|
|
107
|
+
}[];
|
|
108
|
+
}
|
|
109
|
+
/** In-memory importers: the same registration rules and result checks as the host. */
|
|
110
|
+
export declare const createMemoryImporters: (options?: MemoryImportersOptions) => MemoryImporters;
|
|
111
|
+
export interface MemoryExporters extends ExtensionExporters {
|
|
112
|
+
/**
|
|
113
|
+
* Runs a registered exporter the way the host does: the input must match the
|
|
114
|
+
* exporter's declared `scope` and a course snapshot is at most
|
|
115
|
+
* `EXTENSION_TRANSFER_LIMITS.totalBytes`; the result goes through the host's
|
|
116
|
+
* rules (`normalizeExportResult`: file name, size, `text` xor `bytes`). An
|
|
117
|
+
* unregistered exporter and an invalid result reject the promise. The 30 s
|
|
118
|
+
* handler timeout is not applied.
|
|
119
|
+
*/
|
|
120
|
+
run(id: string, input: ExportInput): Promise<ExportResult>;
|
|
121
|
+
/** Registered exporters in registration order. */
|
|
122
|
+
ids(): string[];
|
|
123
|
+
}
|
|
124
|
+
export interface MemoryExportersOptions {
|
|
125
|
+
/** Exporters from `contributes.exporters`: registering another throws and `scope` is checked, as in the host. Unset — any are allowed. */
|
|
126
|
+
declaredExporters?: readonly {
|
|
127
|
+
id: string;
|
|
128
|
+
scope: ExportInput['scope'];
|
|
129
|
+
}[];
|
|
130
|
+
}
|
|
131
|
+
/** In-memory exporters: the same registration rules and result checks as the host. */
|
|
132
|
+
export declare const createMemoryExporters: (options?: MemoryExportersOptions) => MemoryExporters;
|
|
133
|
+
export interface MemoryStatsAttempt {
|
|
134
|
+
/** When the attempt happened: epoch milliseconds, a `Date`, or an ISO-8601 string. */
|
|
135
|
+
at: number | Date | string;
|
|
136
|
+
/** Grade 1-5; 3 and higher counts as correct, as in the app. */
|
|
137
|
+
grade: number;
|
|
138
|
+
/** Course of the attempt, for the `courseId` filter. Without it the attempt belongs to no course. */
|
|
139
|
+
courseId?: string;
|
|
140
|
+
}
|
|
141
|
+
export interface MemoryStatsOptions {
|
|
142
|
+
/** Attempts the statistics start with; more arrive through `record`. */
|
|
143
|
+
attempts?: readonly MemoryStatsAttempt[];
|
|
144
|
+
/** IANA time zone whose local days are counted. Defaults to the time zone of this process. */
|
|
145
|
+
timeZone?: string;
|
|
146
|
+
/** The current time, for the `current` streak. Defaults to `Date.now`. */
|
|
147
|
+
now?: () => number;
|
|
148
|
+
/** false — every call rejects with `PermissionError('learning.stats')`, as for an extension without the permission. Defaults to true. */
|
|
149
|
+
permitted?: boolean;
|
|
150
|
+
}
|
|
151
|
+
export interface MemoryStats extends ExtensionStats {
|
|
152
|
+
/** Adds an attempt to the history. */
|
|
153
|
+
record(attempt: MemoryStatsAttempt): void;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* In-memory statistics with the semantics of the app: local days in a time zone,
|
|
157
|
+
* correct at grade 3 or higher, `current` streak not broken while today has no
|
|
158
|
+
* attempts yet, one `daily` entry per date (at most `EXTENSION_STATS_LIMITS.dailyDays`).
|
|
159
|
+
*/
|
|
160
|
+
export declare const createMemoryStats: (options?: MemoryStatsOptions) => MemoryStats;
|
|
161
|
+
export interface MemoryNotificationsOptions {
|
|
162
|
+
/** false — every call rejects with `PermissionError('notifications')`, as for an extension without the permission. Defaults to true. */
|
|
163
|
+
permitted?: boolean;
|
|
164
|
+
/** false — the operating system does not support notifications: `show` resolves `false`. Defaults to true. */
|
|
165
|
+
supported?: boolean;
|
|
166
|
+
/** false — the user switched notifications off for the extension: `show` resolves `false`. Defaults to true. */
|
|
167
|
+
enabled?: boolean;
|
|
168
|
+
/** The current time for the rate windows. Defaults to `Date.now`. */
|
|
169
|
+
now?: () => number;
|
|
170
|
+
}
|
|
171
|
+
export interface MemoryNotifications extends ExtensionNotifications {
|
|
172
|
+
/** Notifications handed to the (imitated) operating system, oldest first, as the app shows them: sanitized text. */
|
|
173
|
+
readonly shown: readonly ExtensionNotification[];
|
|
174
|
+
/** The user's switch "Notifications" of the extension. */
|
|
175
|
+
setEnabled(enabled: boolean): void;
|
|
176
|
+
/** Whether the imitated operating system supports notifications. */
|
|
177
|
+
setSupported(supported: boolean): void;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* In-memory notifications with the engine's rules: the text is sanitized and
|
|
181
|
+
* limited, `perMinute`/`perHour` windows throw `NotificationRateLimitError`,
|
|
182
|
+
* a switched-off extension or an unsupporting system resolves `false` (and
|
|
183
|
+
* does not use the rate limit). `shown` is the log the app's fake notifier
|
|
184
|
+
* would keep.
|
|
185
|
+
*/
|
|
186
|
+
export declare const createMemoryNotifications: (options?: MemoryNotificationsOptions) => MemoryNotifications;
|
|
55
187
|
/** What a test replaces in the extension context; by default everything is in memory and silent. */
|
|
56
188
|
export interface LoadOptions {
|
|
57
189
|
library?: LibraryReader;
|
|
58
190
|
logger?: ExtensionLogger;
|
|
59
191
|
storage?: ExtensionStorage;
|
|
192
|
+
secrets?: ExtensionSecrets;
|
|
60
193
|
settings?: ExtensionSettings;
|
|
61
194
|
events?: ExtensionEvents;
|
|
62
195
|
commands?: ExtensionCommands;
|
|
196
|
+
importers?: ExtensionImporters;
|
|
197
|
+
exporters?: ExtensionExporters;
|
|
198
|
+
stats?: ExtensionStats;
|
|
199
|
+
notifications?: ExtensionNotifications;
|
|
200
|
+
schedule?: ExtensionSchedule;
|
|
63
201
|
}
|
|
64
202
|
export declare const createSchemaValidator: (schema: JsonSchema) => (value: unknown) => string[];
|
|
65
203
|
export interface LoadedExerciseType {
|
|
@@ -95,12 +233,14 @@ export interface LoadedEvents {
|
|
|
95
233
|
/** Events are delivered as in the host: to the subscribed handler, one at a time. See `MemoryEvents.emit`. */
|
|
96
234
|
emit: MemoryEvents['emit'];
|
|
97
235
|
storage: ExtensionStorage;
|
|
236
|
+
secrets: ExtensionSecrets;
|
|
98
237
|
settings: MemorySettings;
|
|
99
238
|
/** Deactivates the extension module. */
|
|
100
239
|
dispose(): Promise<void>;
|
|
101
240
|
}
|
|
102
|
-
export interface LoadEventsOptions extends Omit<LoadOptions, 'storage' | 'settings' | 'events'>, MemoryEventsOptions {
|
|
241
|
+
export interface LoadEventsOptions extends Omit<LoadOptions, 'storage' | 'secrets' | 'settings' | 'events'>, MemoryEventsOptions {
|
|
103
242
|
storage?: ExtensionStorage;
|
|
243
|
+
secrets?: ExtensionSecrets;
|
|
104
244
|
/** Definitions from the manifest's `contributes.settings`; values are read and changed through `settings`. */
|
|
105
245
|
settings?: readonly SettingContribution[];
|
|
106
246
|
/** User values in place of `default`. */
|
|
@@ -120,6 +260,33 @@ export interface LoadedCommands {
|
|
|
120
260
|
export interface LoadCommandsOptions extends Omit<LoadOptions, 'commands'>, MemoryCommandsOptions {}
|
|
121
261
|
/** Activates the module with in-memory commands and lets the test invoke them like the host. */
|
|
122
262
|
export declare const loadCommands: (module: ExtensionModule, options?: LoadCommandsOptions) => Promise<LoadedCommands>;
|
|
263
|
+
export interface LoadedImporters {
|
|
264
|
+
run: MemoryImporters['run'];
|
|
265
|
+
ids: MemoryImporters['ids'];
|
|
266
|
+
/** Deactivates the extension module. */
|
|
267
|
+
dispose(): Promise<void>;
|
|
268
|
+
}
|
|
269
|
+
export interface LoadImportersOptions extends Omit<LoadOptions, 'importers'>, MemoryImportersOptions {}
|
|
270
|
+
/** Activates the module with in-memory importers and lets the test run them like the host. */
|
|
271
|
+
export declare const loadImporters: (module: ExtensionModule, options?: LoadImportersOptions) => Promise<LoadedImporters>;
|
|
272
|
+
export interface LoadedExporters {
|
|
273
|
+
run: MemoryExporters['run'];
|
|
274
|
+
ids: MemoryExporters['ids'];
|
|
275
|
+
/** Deactivates the extension module. */
|
|
276
|
+
dispose(): Promise<void>;
|
|
277
|
+
}
|
|
278
|
+
export interface LoadExportersOptions extends Omit<LoadOptions, 'exporters'>, MemoryExportersOptions {}
|
|
279
|
+
/** Activates the module with in-memory exporters and lets the test run them like the host; `stats` feeds `ctx.stats` of a progress exporter. */
|
|
280
|
+
export declare const loadExporters: (module: ExtensionModule, options?: LoadExportersOptions) => Promise<LoadedExporters>;
|
|
281
|
+
export interface LoadedSchedules {
|
|
282
|
+
fire: MemorySchedule['fire'];
|
|
283
|
+
ids: MemorySchedule['ids'];
|
|
284
|
+
/** Deactivates the extension module. */
|
|
285
|
+
dispose(): Promise<void>;
|
|
286
|
+
}
|
|
287
|
+
export interface LoadSchedulesOptions extends Omit<LoadOptions, 'schedule'>, MemoryScheduleOptions {}
|
|
288
|
+
/** Activates the module with in-memory schedules and lets the test fire them like the host. */
|
|
289
|
+
export declare const loadSchedules: (module: ExtensionModule, options?: LoadSchedulesOptions) => Promise<LoadedSchedules>;
|
|
123
290
|
export interface LoadViewOptions extends Partial<AnswerElementProps> {
|
|
124
291
|
/** `aria-label` of the host element, as the app sets it. */
|
|
125
292
|
label?: string;
|
|
@@ -152,11 +319,21 @@ export interface LoadPanelOptions {
|
|
|
152
319
|
props?: JsonValue;
|
|
153
320
|
/** Reply to `ctx.call`; by default the call is rejected. */
|
|
154
321
|
call?: (commandId: string, args: JsonValue | undefined) => JsonValue | undefined | Promise<JsonValue | undefined>;
|
|
322
|
+
/** The surroundings the frame starts with (`ctx.context`); defaults to all courses (`courseId: null`). */
|
|
323
|
+
context?: PanelContextInfo;
|
|
324
|
+
/** Where to mount; defaults to a new `div` in `document.body`. */
|
|
325
|
+
container?: HTMLElement;
|
|
326
|
+
}
|
|
327
|
+
export interface LoadWidgetOptions {
|
|
328
|
+
/** Reply to `ctx.call`; by default the call is rejected. */
|
|
329
|
+
call?: LoadPanelOptions['call'];
|
|
330
|
+
/** The surroundings the frame starts with (`ctx.context`); defaults to all courses (`courseId: null`). */
|
|
331
|
+
context?: PanelContextInfo;
|
|
155
332
|
/** Where to mount; defaults to a new `div` in `document.body`. */
|
|
156
333
|
container?: HTMLElement;
|
|
157
334
|
}
|
|
158
|
-
export interface
|
|
159
|
-
/** Container the
|
|
335
|
+
export interface LoadedFrame {
|
|
336
|
+
/** Container the module received in `mount`. */
|
|
160
337
|
readonly container: HTMLElement;
|
|
161
338
|
/** `ctx.call` invocations in order. */
|
|
162
339
|
readonly calls: readonly {
|
|
@@ -165,11 +342,18 @@ export interface LoadedPanel {
|
|
|
165
342
|
}[];
|
|
166
343
|
/** Whether `ctx.signal` was aborted (after `dispose()`). */
|
|
167
344
|
readonly aborted: boolean;
|
|
168
|
-
/**
|
|
169
|
-
|
|
345
|
+
/** The app focused another course: updates `ctx.context` and notifies `ctx.onContextChange` subscribers. */
|
|
346
|
+
setContext(context: PanelContextInfo): void;
|
|
170
347
|
/** Closes the frame: aborts `ctx.signal` and removes the container. */
|
|
171
348
|
dispose(): void;
|
|
172
349
|
}
|
|
350
|
+
export interface LoadedPanel extends LoadedFrame {
|
|
351
|
+
/** Sends new properties to the panel (`ctx.onProps`). */
|
|
352
|
+
setProps(props: JsonValue | undefined): void;
|
|
353
|
+
}
|
|
354
|
+
export type LoadedWidget = LoadedFrame;
|
|
173
355
|
/** Mounts a panel from `panels[id]` in the test DOM environment with the same context the frame provides. */
|
|
174
356
|
export declare const loadPanel: (panels: Readonly<Record<string, PanelModule<HTMLElement>>>, id: string, options?: LoadPanelOptions) => Promise<LoadedPanel>;
|
|
357
|
+
/** Mounts a widget from `widgets[id]` in the test DOM environment with the same context the frame provides. */
|
|
358
|
+
export declare const loadWidget: (widgets: Readonly<Record<string, WidgetModule<HTMLElement>>>, id: string, options?: LoadWidgetOptions) => Promise<LoadedWidget>;
|
|
175
359
|
//#endregion
|