@stainless-api/playgrounds 0.0.1-beta.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/CHANGELOG.md +15 -0
- package/README.md +23 -0
- package/eslint.config.js +2 -0
- package/package.json +69 -0
- package/src/Logs.tsx +216 -0
- package/src/Panel.tsx +21 -0
- package/src/PlaygroundPanelWrapper.tsx +5 -0
- package/src/build-py-types.ts +152 -0
- package/src/build-ts-types.ts +70 -0
- package/src/build.ts +97 -0
- package/src/codemirror/comlink.ts +698 -0
- package/src/codemirror/curl/curlconverter.vendor.js +7959 -0
- package/src/codemirror/curl.ts +108 -0
- package/src/codemirror/deps.ts +12 -0
- package/src/codemirror/fix-lsp-markdown.ts +50 -0
- package/src/codemirror/lsp.ts +87 -0
- package/src/codemirror/python/anser.ts +398 -0
- package/src/codemirror/python/pyodide.ts +180 -0
- package/src/codemirror/python.ts +160 -0
- package/src/codemirror/react.tsx +615 -0
- package/src/codemirror/sanitize-html.ts +12 -0
- package/src/codemirror/shiki.ts +65 -0
- package/src/codemirror/typescript/cdn-typescript.d.ts +1 -0
- package/src/codemirror/typescript/cdn-typescript.js +1 -0
- package/src/codemirror/typescript/console.ts +590 -0
- package/src/codemirror/typescript/get-signature.ts +94 -0
- package/src/codemirror/typescript/prettier-plugin-external-typescript.vendor.js +4968 -0
- package/src/codemirror/typescript/runner.ts +396 -0
- package/src/codemirror/typescript/special-info.ts +171 -0
- package/src/codemirror/typescript/worker.ts +292 -0
- package/src/codemirror/typescript.tsx +198 -0
- package/src/create.tsx +44 -0
- package/src/icon.tsx +21 -0
- package/src/index.ts +6 -0
- package/src/logs-context.ts +5 -0
- package/src/playground.css +359 -0
- package/src/sandbox-worker/in-frame.js +179 -0
- package/src/sandbox-worker/index.ts +202 -0
- package/src/use-storage.ts +54 -0
- package/src/util.ts +29 -0
- package/src/virtual-module.d.ts +45 -0
- package/src/vite-env.d.ts +1 -0
- package/test/get-signature.test.ts +73 -0
- package/test/use-storage.test.ts +60 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import inFrame from './in-frame.js?raw';
|
|
2
|
+
export class SandboxWorker extends EventTarget implements Worker {
|
|
3
|
+
#frame: HTMLIFrameElement | undefined;
|
|
4
|
+
#port: MessagePort;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The **`SandboxWorker`** class represents a sandboxed background task that can be created via script, which can send messages back to its creator.
|
|
8
|
+
*
|
|
9
|
+
* Based on the [Worker API](https://developer.mozilla.org/docs/Web/API/Worker).
|
|
10
|
+
*/
|
|
11
|
+
constructor(url: string | URL, options?: WorkerOptions) {
|
|
12
|
+
super();
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const channel = new MessageChannel();
|
|
15
|
+
const port = channel.port1;
|
|
16
|
+
this.#port = port;
|
|
17
|
+
new Promise<void>((resolve, reject) => {
|
|
18
|
+
(async () => {
|
|
19
|
+
const blob = /\?worker_file&type=module$/.test(url + '')
|
|
20
|
+
? new Blob(
|
|
21
|
+
[
|
|
22
|
+
(
|
|
23
|
+
await (
|
|
24
|
+
await fetch((url + '').replace(/\?worker_file&type=module$/, '?worker_file&type=classic'))
|
|
25
|
+
).text()
|
|
26
|
+
).replace('importScripts("/@vite/env")', ''),
|
|
27
|
+
],
|
|
28
|
+
{
|
|
29
|
+
type: 'text/javascript',
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
: await (await fetch(url)).blob();
|
|
33
|
+
const frame = document.createElement('iframe');
|
|
34
|
+
frame.style.display = 'none';
|
|
35
|
+
frame.sandbox = 'allow-scripts';
|
|
36
|
+
const html = String.raw;
|
|
37
|
+
frame.srcdoc = html`
|
|
38
|
+
<!doctype html>
|
|
39
|
+
<meta charset="utf-8" />
|
|
40
|
+
<script>
|
|
41
|
+
addEventListener('message', (e) => {
|
|
42
|
+
typeof e.data === 'object' &&
|
|
43
|
+
e.data &&
|
|
44
|
+
e.data.workerInit &&
|
|
45
|
+
new Function(e.data.code + ';return init')()(e.data.params);
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
48
|
+
`;
|
|
49
|
+
frame.addEventListener(
|
|
50
|
+
'load',
|
|
51
|
+
() => {
|
|
52
|
+
frame.contentWindow!.postMessage(
|
|
53
|
+
{
|
|
54
|
+
workerInit: true,
|
|
55
|
+
code: inFrame,
|
|
56
|
+
params: { port: channel.port2, blob, options },
|
|
57
|
+
},
|
|
58
|
+
'*',
|
|
59
|
+
[channel.port2],
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
port.addEventListener('message', (e) => {
|
|
67
|
+
if (e.data.type === 'ready') {
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
if (e.data.type === 'error') {
|
|
71
|
+
this.#dispatchError(e.data.error);
|
|
72
|
+
}
|
|
73
|
+
if (e.data.type === 'message' || e.data.type === 'messageerror') {
|
|
74
|
+
this.dispatchEvent(new MessageEvent(e.data.type, { data: e.data.data }));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
port.start();
|
|
78
|
+
const timeout = setTimeout(() => {
|
|
79
|
+
reject(new Error('timed out waiting for sandbox frame'));
|
|
80
|
+
}, 1000);
|
|
81
|
+
controller.signal.addEventListener('abort', () => {
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
});
|
|
84
|
+
this.#frame = frame;
|
|
85
|
+
document.head.appendChild(frame);
|
|
86
|
+
})().catch(reject);
|
|
87
|
+
})
|
|
88
|
+
.catch((e) => {
|
|
89
|
+
this.#dispatchError(e);
|
|
90
|
+
this.terminate();
|
|
91
|
+
})
|
|
92
|
+
.finally(() => {
|
|
93
|
+
controller.abort();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#dispatchError(e: unknown) {
|
|
98
|
+
const defaultBehavior = this.dispatchEvent(
|
|
99
|
+
new ErrorEvent('error', {
|
|
100
|
+
error: e,
|
|
101
|
+
message: e instanceof Error ? e.message : '' + e,
|
|
102
|
+
cancelable: true,
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
if (defaultBehavior) {
|
|
106
|
+
reportError(e);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The **`postMessage()`** method of the Worker interface sends a message to the worker.
|
|
112
|
+
*
|
|
113
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Worker/postMessage)
|
|
114
|
+
*/
|
|
115
|
+
postMessage(message: unknown, transfer: Transferable[]): void;
|
|
116
|
+
postMessage(message: unknown, options?: StructuredSerializeOptions): void;
|
|
117
|
+
postMessage(message: unknown, options?: StructuredSerializeOptions | Transferable[]): void {
|
|
118
|
+
this.#port.postMessage(
|
|
119
|
+
{
|
|
120
|
+
type: 'postMessage',
|
|
121
|
+
data: message,
|
|
122
|
+
options,
|
|
123
|
+
},
|
|
124
|
+
Array.isArray(options) ? { transfer: options } : options,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
terminate(): void {
|
|
129
|
+
this.#port.postMessage({ type: 'terminate' });
|
|
130
|
+
this.#port.close();
|
|
131
|
+
if (this.#frame) {
|
|
132
|
+
this.#frame.remove();
|
|
133
|
+
this.#frame = undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// #region - event types & shorthand handlers
|
|
138
|
+
declare addEventListener: {
|
|
139
|
+
<K extends keyof WorkerEventMap>(
|
|
140
|
+
type: K,
|
|
141
|
+
listener: (this: Worker, ev: WorkerEventMap[K]) => unknown,
|
|
142
|
+
options?: boolean | AddEventListenerOptions,
|
|
143
|
+
): void;
|
|
144
|
+
(
|
|
145
|
+
type: string,
|
|
146
|
+
listener: EventListenerOrEventListenerObject,
|
|
147
|
+
options?: boolean | AddEventListenerOptions,
|
|
148
|
+
): void;
|
|
149
|
+
};
|
|
150
|
+
declare removeEventListener: {
|
|
151
|
+
<K extends keyof WorkerEventMap>(
|
|
152
|
+
type: K,
|
|
153
|
+
listener: (this: Worker, ev: WorkerEventMap[K]) => unknown,
|
|
154
|
+
options?: boolean | EventListenerOptions,
|
|
155
|
+
): void;
|
|
156
|
+
(
|
|
157
|
+
type: string,
|
|
158
|
+
listener: EventListenerOrEventListenerObject,
|
|
159
|
+
options?: boolean | EventListenerOptions,
|
|
160
|
+
): void;
|
|
161
|
+
};
|
|
162
|
+
#onerror: ((this: AbstractWorker, ev: ErrorEvent) => unknown) | null | undefined;
|
|
163
|
+
get onerror(): ((this: AbstractWorker, ev: ErrorEvent) => unknown) | null {
|
|
164
|
+
return this.#onerror ?? null;
|
|
165
|
+
}
|
|
166
|
+
set onerror(value: ((this: AbstractWorker, ev: ErrorEvent) => unknown) | null) {
|
|
167
|
+
if (this.#onerror) {
|
|
168
|
+
this.removeEventListener('error', this.#onerror);
|
|
169
|
+
}
|
|
170
|
+
this.#onerror = value ?? null;
|
|
171
|
+
if (this.#onerror) {
|
|
172
|
+
this.addEventListener('error', this.#onerror);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
#onmessage: ((this: Worker, ev: MessageEvent) => unknown) | null | undefined;
|
|
176
|
+
get onmessage(): ((this: Worker, ev: MessageEvent) => unknown) | null {
|
|
177
|
+
return this.#onmessage ?? null;
|
|
178
|
+
}
|
|
179
|
+
set onmessage(value: ((this: Worker, ev: MessageEvent) => unknown) | null) {
|
|
180
|
+
if (this.#onmessage) {
|
|
181
|
+
this.removeEventListener('message', this.#onmessage);
|
|
182
|
+
}
|
|
183
|
+
this.#onmessage = value ?? null;
|
|
184
|
+
if (this.#onmessage) {
|
|
185
|
+
this.addEventListener('message', this.#onmessage);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
#onmessageerror: ((this: Worker, ev: MessageEvent) => unknown) | null | undefined;
|
|
189
|
+
get onmessageerror(): ((this: Worker, ev: MessageEvent) => unknown) | null {
|
|
190
|
+
return this.#onmessageerror ?? null;
|
|
191
|
+
}
|
|
192
|
+
set onmessageerror(value: ((this: Worker, ev: MessageEvent) => unknown) | null) {
|
|
193
|
+
if (this.#onmessageerror) {
|
|
194
|
+
this.removeEventListener('messageerror', this.#onmessageerror);
|
|
195
|
+
}
|
|
196
|
+
this.#onmessageerror = value ?? null;
|
|
197
|
+
if (this.#onmessageerror) {
|
|
198
|
+
this.addEventListener('messageerror', this.#onmessageerror);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// #endregion - event types & shorthand handlers
|
|
202
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback, useMemo, useSyncExternalStore } from 'react';
|
|
2
|
+
import type { Jsonifiable, Jsonify, JsonValue } from 'type-fest';
|
|
3
|
+
|
|
4
|
+
type UseStorageResult<T> = [
|
|
5
|
+
T extends JsonValue ? T : Jsonify<T>,
|
|
6
|
+
(value: T | ((prev: T extends JsonValue ? T : Jsonify<T>) => T)) => void,
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export function useStorage<T extends Jsonifiable = null>(
|
|
10
|
+
provider: Storage,
|
|
11
|
+
key: string,
|
|
12
|
+
defaultValue: T | (() => T),
|
|
13
|
+
): UseStorageResult<T> {
|
|
14
|
+
const subscribe = useCallback(
|
|
15
|
+
(callback: () => void) => {
|
|
16
|
+
globalThis.addEventListener('storage', callback);
|
|
17
|
+
globalThis.addEventListener(`storage-${key}`, callback);
|
|
18
|
+
|
|
19
|
+
return () => {
|
|
20
|
+
globalThis.removeEventListener('storage', callback);
|
|
21
|
+
globalThis.removeEventListener(`storage-${key}`, callback);
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
[key],
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const getSnapshot = useCallback((): string | null => {
|
|
28
|
+
return provider.getItem(key);
|
|
29
|
+
}, [provider, key]);
|
|
30
|
+
|
|
31
|
+
const serializedDefault = useMemo(
|
|
32
|
+
() => JSON.stringify(typeof defaultValue === 'function' ? defaultValue() : defaultValue),
|
|
33
|
+
[defaultValue],
|
|
34
|
+
);
|
|
35
|
+
const serializedValue = useSyncExternalStore(subscribe, getSnapshot, getSnapshot) ?? serializedDefault;
|
|
36
|
+
|
|
37
|
+
const value = useMemo(() => JSON.parse(serializedValue), [serializedValue]) as JsonValue;
|
|
38
|
+
|
|
39
|
+
const setValue = useCallback(
|
|
40
|
+
(newValue: JsonValue | ((prev: JsonValue) => JsonValue)) => {
|
|
41
|
+
const valueToStore = JSON.stringify(typeof newValue === 'function' ? newValue(value) : newValue);
|
|
42
|
+
if (valueToStore !== serializedValue) {
|
|
43
|
+
provider.setItem(key, valueToStore);
|
|
44
|
+
globalThis.dispatchEvent?.(new CustomEvent(`storage-${key}`));
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
[provider, key, serializedValue, defaultValue],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// If we use Jsonify internally we get recursion errors.
|
|
51
|
+
return [value, setValue] satisfies UseStorageResult<JsonValue> as unknown as UseStorageResult<T>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default useStorage;
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export async function execCommand(
|
|
4
|
+
command: string,
|
|
5
|
+
args: string[],
|
|
6
|
+
options: { cwd: string; env?: Record<string, string> },
|
|
7
|
+
) {
|
|
8
|
+
return new Promise<void>((resolve, reject) => {
|
|
9
|
+
const proc = spawn(command, args, {
|
|
10
|
+
cwd: options.cwd,
|
|
11
|
+
env: { ...process.env, ...options.env },
|
|
12
|
+
stdio: 'inherit',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
proc.on('close', (code) => {
|
|
16
|
+
if (code === 0) {
|
|
17
|
+
resolve();
|
|
18
|
+
} else {
|
|
19
|
+
reject(
|
|
20
|
+
new Error(
|
|
21
|
+
`Command \`${[command, ...args].map((e) => (/^[\w/.-]+$/.test(e) ? e : "'" + e.replaceAll("'", "'\\''") + "'")).join(' ')}\` failed with code ${code} while building playground`,
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
proc.on('error', reject);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type CreatePlayground = typeof import('./create').createPlayground;
|
|
2
|
+
type ComlinkRemote<T> = import('./codemirror/comlink').Remote<T>;
|
|
3
|
+
declare module 'virtual:stl-playground/typescript.json' {
|
|
4
|
+
const data: {
|
|
5
|
+
links: [string, string][];
|
|
6
|
+
files: [string, string][];
|
|
7
|
+
} | null;
|
|
8
|
+
export { data as default };
|
|
9
|
+
}
|
|
10
|
+
declare module 'virtual:stl-playground/python.json' {
|
|
11
|
+
const data: { files: Record<string, string>; wheel: string } | null;
|
|
12
|
+
export { data as default };
|
|
13
|
+
}
|
|
14
|
+
declare module 'virtual:stl-playground/auth.json' {
|
|
15
|
+
const data:
|
|
16
|
+
| ({
|
|
17
|
+
type: 'http_bearer' | 'query' | 'header' | 'oauth2' | 'http_basic' | 'http_digest';
|
|
18
|
+
description?: string;
|
|
19
|
+
name: string;
|
|
20
|
+
title: string;
|
|
21
|
+
header: string | undefined;
|
|
22
|
+
example: string | undefined;
|
|
23
|
+
} & {
|
|
24
|
+
opts: {
|
|
25
|
+
name: string;
|
|
26
|
+
type: 'string' | 'number' | 'boolean' | 'null' | 'integer';
|
|
27
|
+
nullable: boolean;
|
|
28
|
+
description?: string | undefined;
|
|
29
|
+
example?: unknown;
|
|
30
|
+
default?: unknown;
|
|
31
|
+
read_env?: string | undefined;
|
|
32
|
+
auth?:
|
|
33
|
+
| {
|
|
34
|
+
security_scheme: string;
|
|
35
|
+
role?: 'value' | 'password' | 'username' | 'client_id' | 'client_secret' | undefined;
|
|
36
|
+
}
|
|
37
|
+
| undefined;
|
|
38
|
+
}[];
|
|
39
|
+
})[]
|
|
40
|
+
| null;
|
|
41
|
+
export { data as default };
|
|
42
|
+
}
|
|
43
|
+
declare module 'comlink' {
|
|
44
|
+
type Remote<T> = ComlinkRemote<T>;
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { getSignature } from '../src/codemirror/typescript/get-signature.ts';
|
|
4
|
+
import isCallable from 'is-callable';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
|
|
7
|
+
const testCases = [
|
|
8
|
+
() => {},
|
|
9
|
+
() => 0,
|
|
10
|
+
(_: unknown) => {},
|
|
11
|
+
(_: unknown) => 0,
|
|
12
|
+
{ _() {} }._,
|
|
13
|
+
{ get() {} }.get,
|
|
14
|
+
{ set() {} }.set,
|
|
15
|
+
Object.getOwnPropertyDescriptor(
|
|
16
|
+
{
|
|
17
|
+
get _() {
|
|
18
|
+
return;
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
'_',
|
|
22
|
+
)!.get!,
|
|
23
|
+
Object.getOwnPropertyDescriptor({ set _(_: unknown) {} }, '_')!.set!,
|
|
24
|
+
function () {},
|
|
25
|
+
function a() {},
|
|
26
|
+
class {
|
|
27
|
+
constructor() {}
|
|
28
|
+
},
|
|
29
|
+
class a {
|
|
30
|
+
a() {}
|
|
31
|
+
constructor() {}
|
|
32
|
+
},
|
|
33
|
+
async () => {},
|
|
34
|
+
async () => 0,
|
|
35
|
+
async (_: unknown) => {},
|
|
36
|
+
async (_: unknown) => 0,
|
|
37
|
+
{ async _() {} }._,
|
|
38
|
+
{ async get() {} }.get,
|
|
39
|
+
{ async set() {} }.set,
|
|
40
|
+
async function () {},
|
|
41
|
+
async function a() {},
|
|
42
|
+
function* () {},
|
|
43
|
+
function* a() {},
|
|
44
|
+
async function* () {},
|
|
45
|
+
async function* a() {},
|
|
46
|
+
];
|
|
47
|
+
describe('getSignature', () => {
|
|
48
|
+
for (const testCase of testCases) {
|
|
49
|
+
it('works with ' + testCase.toString(), () => {
|
|
50
|
+
const signature = getSignature(testCase.toString());
|
|
51
|
+
if (isCallable(testCase)) {
|
|
52
|
+
assert(/^(async )?function/.test(signature), 'function signatures should be function');
|
|
53
|
+
} else {
|
|
54
|
+
assert(/^class /.test(signature), 'class signatures should be classes');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let fake: (..._: never[]) => unknown;
|
|
58
|
+
assert.doesNotThrow(
|
|
59
|
+
() =>
|
|
60
|
+
(fake = new Function(
|
|
61
|
+
'return (' + (signature.startsWith('class ') ? signature : `${signature} {}`) + ')',
|
|
62
|
+
)()),
|
|
63
|
+
'signature should be valid syntax',
|
|
64
|
+
);
|
|
65
|
+
assert.equal(
|
|
66
|
+
fake!.name,
|
|
67
|
+
testCase.name.replace(/^(get|set) /, ''),
|
|
68
|
+
'signature should have same name as function',
|
|
69
|
+
);
|
|
70
|
+
assert.equal(fake!.length, testCase.length, 'signature should have same argument count as function');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import { useStorage } from '../src/use-storage.ts';
|
|
3
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
4
|
+
import { createElement } from 'react';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
|
|
7
|
+
let keyRead: string | null = null;
|
|
8
|
+
let mockValue: string | null = null;
|
|
9
|
+
const storageMock: Storage = {
|
|
10
|
+
length: 0,
|
|
11
|
+
clear: function (): void {
|
|
12
|
+
throw new Error('Function not implemented.');
|
|
13
|
+
},
|
|
14
|
+
getItem: function (key): string | null {
|
|
15
|
+
keyRead = key;
|
|
16
|
+
return mockValue;
|
|
17
|
+
},
|
|
18
|
+
key: function (): string | null {
|
|
19
|
+
throw new Error('Function not implemented.');
|
|
20
|
+
},
|
|
21
|
+
removeItem: function (): void {
|
|
22
|
+
throw new Error('Function not implemented.');
|
|
23
|
+
},
|
|
24
|
+
setItem: function (key, value): void {
|
|
25
|
+
assert.equal(key, keyRead);
|
|
26
|
+
mockValue = value;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('useStorage', () => {
|
|
31
|
+
it('reads from storage', () => {
|
|
32
|
+
keyRead = null;
|
|
33
|
+
mockValue = '"value"';
|
|
34
|
+
renderToStaticMarkup(
|
|
35
|
+
createElement(function () {
|
|
36
|
+
const [value] = useStorage<string | null>(storageMock, 'key', null);
|
|
37
|
+
assert.equal(value, 'value');
|
|
38
|
+
return null;
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
assert.equal(keyRead, 'key');
|
|
42
|
+
});
|
|
43
|
+
it('accepts jsonifiable objects', () => {
|
|
44
|
+
keyRead = null;
|
|
45
|
+
mockValue = null;
|
|
46
|
+
renderToStaticMarkup(
|
|
47
|
+
createElement(function () {
|
|
48
|
+
const [value, setValue] = useStorage<Date>(storageMock, 'key', new Date(0));
|
|
49
|
+
assert.equal(value, '1970-01-01T00:00:00.000Z');
|
|
50
|
+
setValue((prev) => {
|
|
51
|
+
assert.equal(prev, '1970-01-01T00:00:00.000Z');
|
|
52
|
+
return new Date();
|
|
53
|
+
});
|
|
54
|
+
setValue(new Date());
|
|
55
|
+
return null;
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
assert.equal(keyRead, 'key');
|
|
59
|
+
});
|
|
60
|
+
});
|