@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.
Files changed (39) hide show
  1. package/.npmignore +4 -0
  2. package/.turbo/daemon/b38c7656669412eb-turbo.log.2026-01-28 +0 -0
  3. package/.turbo/turbo-build.log +14 -0
  4. package/CHANGELOG.md +11 -0
  5. package/create-vite-config.ts +91 -0
  6. package/dist/create-vite-config.js +13965 -0
  7. package/index.html +12 -0
  8. package/package.json +63 -0
  9. package/postcss.config.cjs +8 -0
  10. package/public-zip/readme.txt +1 -0
  11. package/src/assets/bolt-uxp-quickstart.gif +0 -0
  12. package/src/assets/bolt-uxp.png +0 -0
  13. package/src/assets/bolt-uxp.svg +55 -0
  14. package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_Black_V01.png +0 -0
  15. package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_Black_V01.svg +78 -0
  16. package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_White_V01.png +0 -0
  17. package/src/assets/built-with-bolt-uxp/Built_With_BOLT_UXP_Logo_White_V01.svg +81 -0
  18. package/src/assets/react.png +0 -0
  19. package/src/assets/react.svg +36 -0
  20. package/src/assets/sass.png +0 -0
  21. package/src/assets/sass.svg +45 -0
  22. package/src/assets/svelte.png +0 -0
  23. package/src/assets/svelte.svg +1 -0
  24. package/src/assets/typescript.png +0 -0
  25. package/src/assets/typescript.svg +6 -0
  26. package/src/assets/vite.png +0 -0
  27. package/src/assets/vite.svg +1 -0
  28. package/src/assets/vue.png +0 -0
  29. package/src/assets/vue.svg +2 -0
  30. package/src/bolt-uxp-ws-listener.js +26 -0
  31. package/src/components/ErrorView.tsx +69 -0
  32. package/src/index-react.tsx +14 -0
  33. package/src/index.css +11 -0
  34. package/src/lib/resolvePath.ts +19 -0
  35. package/src/main.tsx +238 -0
  36. package/src/tests.d.ts +5 -0
  37. package/tailwind.config.js +15 -0
  38. package/tsup.config.ts +11 -0
  39. 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,5 @@
1
+ declare module "TESTS" {
2
+ type Test = import("@bubblydoo/uxp-test-framework-base").Test;
3
+
4
+ export const tests: Test[];
5
+ }
@@ -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
+ };