@bubblydoo/uxp-test-framework-plugin 0.0.2
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/.npmignore +4 -0
- package/.turbo/daemon/b38c7656669412eb-turbo.log.2026-01-28 +0 -0
- package/.turbo/turbo-build.log +14 -0
- package/CHANGELOG.md +11 -0
- package/create-vite-config.ts +91 -0
- package/dist/create-vite-config.js +13965 -0
- package/index.html +12 -0
- package/package.json +63 -0
- package/postcss.config.cjs +8 -0
- package/public-zip/readme.txt +1 -0
- package/src/assets/bolt-uxp-quickstart.gif +0 -0
- package/src/assets/bolt-uxp.png +0 -0
- package/src/assets/bolt-uxp.svg +55 -0
- package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_Black_V01.png +0 -0
- package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_Black_V01.svg +78 -0
- package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_White_V01.png +0 -0
- package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_White_V01.svg +81 -0
- package/src/assets/react.png +0 -0
- package/src/assets/react.svg +36 -0
- package/src/assets/sass.png +0 -0
- package/src/assets/sass.svg +45 -0
- package/src/assets/svelte.png +0 -0
- package/src/assets/svelte.svg +1 -0
- package/src/assets/typescript.png +0 -0
- package/src/assets/typescript.svg +6 -0
- package/src/assets/vite.png +0 -0
- package/src/assets/vite.svg +1 -0
- package/src/assets/vue.png +0 -0
- package/src/assets/vue.svg +2 -0
- package/src/bolt-uxp-ws-listener.js +26 -0
- package/src/components/ErrorView.tsx +69 -0
- package/src/index-react.tsx +14 -0
- package/src/index.css +11 -0
- package/src/lib/resolvePath.ts +19 -0
- package/src/main.tsx +238 -0
- package/src/tests.d.ts +5 -0
- package/tailwind.config.js +15 -0
- package/tsup.config.ts +11 -0
- package/uxp.config.ts +109 -0
package/src/main.tsx
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { tests } from "TESTS";
|
|
2
|
+
import React, {
|
|
3
|
+
createContext,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
11
|
+
import { ErrorView } from "./components/ErrorView";
|
|
12
|
+
import { Test } from "@bubblydoo/uxp-test-framework-base";
|
|
13
|
+
|
|
14
|
+
type TestResult = {
|
|
15
|
+
status: "idle" | "success" | "error" | "pending";
|
|
16
|
+
isIdle: boolean;
|
|
17
|
+
isSuccess: boolean;
|
|
18
|
+
isPending: boolean;
|
|
19
|
+
error: any;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const TestResultsContext = createContext<{
|
|
23
|
+
set(test: Test, result: TestResult): void;
|
|
24
|
+
get(test: Test): TestResult | undefined;
|
|
25
|
+
delete(test: Test): void;
|
|
26
|
+
}>(null!);
|
|
27
|
+
|
|
28
|
+
export const App = () => {
|
|
29
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
30
|
+
const [testResultsCache, setTestResults] = useState(
|
|
31
|
+
() => new Map<Test, any>()
|
|
32
|
+
);
|
|
33
|
+
const testResults = useMemo(
|
|
34
|
+
() => ({
|
|
35
|
+
set: (test: Test, result: TestResult) =>
|
|
36
|
+
setTestResults((prev) => new Map(prev).set(test, result)),
|
|
37
|
+
get: (test: Test) => testResultsCache.get(test),
|
|
38
|
+
delete: (test: Test) => {
|
|
39
|
+
setTestResults((prev) => {
|
|
40
|
+
const copy = new Map(prev);
|
|
41
|
+
copy.delete(test);
|
|
42
|
+
return copy;
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
[testResultsCache]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<main className="text-white overflow-y-auto">
|
|
52
|
+
<QueryClientProvider client={queryClient}>
|
|
53
|
+
<TestResultsContext.Provider value={testResults}>
|
|
54
|
+
<TestView tests={tests} />
|
|
55
|
+
</TestResultsContext.Provider>
|
|
56
|
+
</QueryClientProvider>
|
|
57
|
+
</main>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
async function runTest(test: Test): Promise<void> {
|
|
63
|
+
try {
|
|
64
|
+
await test.run.call(undefined, { name: test.name });
|
|
65
|
+
} catch (e) {
|
|
66
|
+
if (!(e instanceof Error)) {
|
|
67
|
+
console.error(`Test ${test.name} threw a non-Error (${typeof e}): ${e}`);
|
|
68
|
+
}
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function usePromiseStatus<T>(promise: Promise<T> | null): TestResult {
|
|
74
|
+
const [success, setSuccess] = useState<boolean>(false);
|
|
75
|
+
const [error, setError] = useState<Error | null>(null);
|
|
76
|
+
const [isPending, setIsPending] = useState<boolean>(false);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!promise) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
setIsPending(true);
|
|
83
|
+
promise
|
|
84
|
+
.then((result) => {
|
|
85
|
+
setSuccess(true);
|
|
86
|
+
})
|
|
87
|
+
.catch((e) => {
|
|
88
|
+
setError(e);
|
|
89
|
+
})
|
|
90
|
+
.finally(() => {
|
|
91
|
+
setIsPending(false);
|
|
92
|
+
});
|
|
93
|
+
}, [promise]);
|
|
94
|
+
|
|
95
|
+
const result: TestResult = useMemo(
|
|
96
|
+
() => ({
|
|
97
|
+
status: success
|
|
98
|
+
? "success"
|
|
99
|
+
: error
|
|
100
|
+
? "error"
|
|
101
|
+
: isPending
|
|
102
|
+
? "pending"
|
|
103
|
+
: "idle",
|
|
104
|
+
isIdle: !promise,
|
|
105
|
+
isSuccess: success,
|
|
106
|
+
error,
|
|
107
|
+
isPending,
|
|
108
|
+
}),
|
|
109
|
+
[success, error, isPending, promise]
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const stableIdleResult: TestResult = {
|
|
116
|
+
status: "idle",
|
|
117
|
+
isIdle: true,
|
|
118
|
+
isSuccess: false,
|
|
119
|
+
isPending: false,
|
|
120
|
+
error: null,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
function useTestResult(test: Test): { mutate: () => void; result: TestResult } {
|
|
124
|
+
const [promise, setPromise] = useState<Promise<void> | null>(null);
|
|
125
|
+
const testResults = useContext(TestResultsContext);
|
|
126
|
+
const mutate = useCallback(() => {
|
|
127
|
+
testResults.delete(test);
|
|
128
|
+
setPromise(runTest(test));
|
|
129
|
+
}, [test, testResults]);
|
|
130
|
+
const promiseResult = usePromiseStatus(promise);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (promiseResult.status !== "idle") {
|
|
133
|
+
testResults.set(test, promiseResult);
|
|
134
|
+
}
|
|
135
|
+
}, [promiseResult]);
|
|
136
|
+
const globalResult =
|
|
137
|
+
promiseResult.status !== "idle"
|
|
138
|
+
? promiseResult
|
|
139
|
+
: testResults.get(test) ?? stableIdleResult;
|
|
140
|
+
return {
|
|
141
|
+
mutate,
|
|
142
|
+
result: globalResult,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function TestView({ tests }: { tests: Test[] }) {
|
|
147
|
+
const resultContext = useContext(TestResultsContext);
|
|
148
|
+
const runAllTests = useCallback(async () => {
|
|
149
|
+
for (const test of tests) {
|
|
150
|
+
console.log(`Running test ${test.name}`);
|
|
151
|
+
// await runTest(test);
|
|
152
|
+
resultContext.set(test, {
|
|
153
|
+
status: "pending",
|
|
154
|
+
isIdle: false,
|
|
155
|
+
isSuccess: false,
|
|
156
|
+
isPending: true,
|
|
157
|
+
error: null,
|
|
158
|
+
});
|
|
159
|
+
try {
|
|
160
|
+
await runTest(test);
|
|
161
|
+
resultContext.set(test, {
|
|
162
|
+
status: "success",
|
|
163
|
+
isIdle: false,
|
|
164
|
+
isSuccess: true,
|
|
165
|
+
isPending: false,
|
|
166
|
+
error: null,
|
|
167
|
+
});
|
|
168
|
+
} catch (e) {
|
|
169
|
+
resultContext.set(test, {
|
|
170
|
+
status: "error",
|
|
171
|
+
isIdle: false,
|
|
172
|
+
isSuccess: false,
|
|
173
|
+
isPending: false,
|
|
174
|
+
error: e,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}, []);
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div className="px-2">
|
|
182
|
+
<h1 className="text-white text-xl bold mb-1">Tests</h1>
|
|
183
|
+
<button
|
|
184
|
+
onClick={runAllTests}
|
|
185
|
+
className="bg-white text-black rounded-md p-1"
|
|
186
|
+
>
|
|
187
|
+
Run All Tests
|
|
188
|
+
</button>
|
|
189
|
+
{tests.map((test, i) => {
|
|
190
|
+
if (!test) return;
|
|
191
|
+
return <OneTest test={test} key={i} />;
|
|
192
|
+
})}
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function OneTest(props: { test: Test }) {
|
|
198
|
+
const { test } = props;
|
|
199
|
+
|
|
200
|
+
const { mutate, result } = useTestResult(test);
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div
|
|
204
|
+
key={test.name}
|
|
205
|
+
className="p-2 border border-white rounded-md mt-2 w-full"
|
|
206
|
+
>
|
|
207
|
+
<div className="flex flex-row">
|
|
208
|
+
<div className="text-base mr-2 flex-1">{test.name}</div>
|
|
209
|
+
<button
|
|
210
|
+
disabled={result.isPending}
|
|
211
|
+
key={test.name}
|
|
212
|
+
onClick={() => mutate()}
|
|
213
|
+
className="bg-white text-black rounded-md p-1"
|
|
214
|
+
>
|
|
215
|
+
Run Test
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
<div>
|
|
219
|
+
Result: {result.status} <TestResultEmoji result={result} />
|
|
220
|
+
{result.error && (
|
|
221
|
+
<div className="pt-2">
|
|
222
|
+
<ErrorView error={result.error} />
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function TestResultEmoji({ result }: { result: TestResult }) {
|
|
231
|
+
return result.error
|
|
232
|
+
? "❌"
|
|
233
|
+
: result.isSuccess
|
|
234
|
+
? "✅"
|
|
235
|
+
: result.isPending
|
|
236
|
+
? "⏳"
|
|
237
|
+
: "❔";
|
|
238
|
+
}
|
package/src/tests.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
4
|
+
|
|
5
|
+
/** @type {import('tailwindcss').Config} */
|
|
6
|
+
export default {
|
|
7
|
+
content: [
|
|
8
|
+
__dirname + "/index.html",
|
|
9
|
+
__dirname + "/src/**/*.{js,ts,jsx,tsx}",
|
|
10
|
+
],
|
|
11
|
+
theme: {
|
|
12
|
+
extend: {},
|
|
13
|
+
},
|
|
14
|
+
plugins: [],
|
|
15
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['create-vite-config.ts'],
|
|
5
|
+
format: "esm",
|
|
6
|
+
target: "node20",
|
|
7
|
+
// dts: true,
|
|
8
|
+
external: ["vite", "vite-uxp-plugin", "@vitejs/plugin-react", "autoprefixer", "vite-tsconfig-paths"],
|
|
9
|
+
clean: true,
|
|
10
|
+
outDir: "dist",
|
|
11
|
+
})
|
package/uxp.config.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { UXP_Manifest, UXP_Config } from "vite-uxp-plugin";
|
|
2
|
+
|
|
3
|
+
const extraPrefs = {
|
|
4
|
+
hotReloadPort: 8080,
|
|
5
|
+
copyZipAssets: ["public-zip/*"],
|
|
6
|
+
uniqueIds: true,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type CreateManifestOpts = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const createUxpManifest: (opts: CreateManifestOpts) => UXP_Manifest = ({
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
version,
|
|
19
|
+
}) => {
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
name,
|
|
23
|
+
version,
|
|
24
|
+
main: "index.html",
|
|
25
|
+
manifestVersion: 6,
|
|
26
|
+
host: [
|
|
27
|
+
{
|
|
28
|
+
app: "PS",
|
|
29
|
+
minVersion: "24.2.0",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
entrypoints: [
|
|
33
|
+
{
|
|
34
|
+
type: "panel",
|
|
35
|
+
id: `${id}.main`,
|
|
36
|
+
label: {
|
|
37
|
+
default: name,
|
|
38
|
+
},
|
|
39
|
+
minimumSize: { width: 230, height: 200 },
|
|
40
|
+
maximumSize: { width: 2000, height: 2000 },
|
|
41
|
+
preferredDockedSize: { width: 230, height: 300 },
|
|
42
|
+
preferredFloatingSize: { width: 450, height: 400 },
|
|
43
|
+
icons: [
|
|
44
|
+
{
|
|
45
|
+
width: 23,
|
|
46
|
+
height: 23,
|
|
47
|
+
path: "icons/dark.png",
|
|
48
|
+
scale: [1, 2],
|
|
49
|
+
theme: ["darkest", "dark", "medium"],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
width: 23,
|
|
53
|
+
height: 23,
|
|
54
|
+
path: "icons/light.png",
|
|
55
|
+
scale: [1, 2],
|
|
56
|
+
theme: ["lightest", "light"],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
featureFlags: {
|
|
62
|
+
enableAlerts: true,
|
|
63
|
+
},
|
|
64
|
+
requiredPermissions: {
|
|
65
|
+
localFileSystem: "fullAccess",
|
|
66
|
+
launchProcess: {
|
|
67
|
+
schemes: ["https", "slack", "file", "ws"],
|
|
68
|
+
extensions: [".xd", ".psd", ".bat", ".cmd", ""],
|
|
69
|
+
},
|
|
70
|
+
network: {
|
|
71
|
+
domains: [
|
|
72
|
+
`ws://localhost:${extraPrefs.hotReloadPort}`, // Required for hot reload
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
clipboard: "readAndWrite",
|
|
76
|
+
webview: {
|
|
77
|
+
allow: "yes",
|
|
78
|
+
allowLocalRendering: "yes",
|
|
79
|
+
domains: "all",
|
|
80
|
+
enableMessageBridge: "localAndRemote",
|
|
81
|
+
},
|
|
82
|
+
ipc: {
|
|
83
|
+
enablePluginCommunication: true,
|
|
84
|
+
},
|
|
85
|
+
allowCodeGenerationFromStrings: true,
|
|
86
|
+
},
|
|
87
|
+
icons: [
|
|
88
|
+
{
|
|
89
|
+
width: 48,
|
|
90
|
+
height: 48,
|
|
91
|
+
path: "icons/plugin-icon.png",
|
|
92
|
+
scale: [1, 2],
|
|
93
|
+
theme: ["darkest", "dark", "medium", "lightest", "light", "all"],
|
|
94
|
+
species: ["pluginList"],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const createUxpConfig: (opts: CreateManifestOpts) => UXP_Config = ({
|
|
101
|
+
id,
|
|
102
|
+
name,
|
|
103
|
+
version,
|
|
104
|
+
}) => {
|
|
105
|
+
return {
|
|
106
|
+
manifest: createUxpManifest({ id, name, version }),
|
|
107
|
+
...extraPrefs,
|
|
108
|
+
} as UXP_Config;
|
|
109
|
+
};
|