@bubblydoo/uxp-test-framework-plugin 0.0.8 → 0.0.9
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/dist-vite/{cli-CHBMTgYc.js → cli-44bGj_kl.js} +88609 -88609
- package/dist-vite/cli-44bGj_kl.js.map +1 -0
- package/dist-vite/cli.d.ts +1 -1
- package/dist-vite/cli.js +15 -15
- package/dist-vite/{index-B82GTETV.js → index-CooNDnAn.js} +16 -16
- package/dist-vite/{index-B82GTETV.js.map → index-CooNDnAn.js.map} +1 -1
- package/package.json +13 -12
- package/src-plugin/bolt-uxp-ws-listener.ts +13 -11
- package/src-plugin/components/ErrorView.tsx +22 -9
- package/src-plugin/index-react.tsx +7 -7
- package/src-plugin/lib/resolvePath.ts +6 -4
- package/src-plugin/main.tsx +52 -44
- package/src-plugin/tests.d.ts +2 -2
- package/dist-vite/cli-CHBMTgYc.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubblydoo/uxp-test-framework-plugin",
|
|
3
|
-
"version": "0.0.8",
|
|
4
3
|
"type": "module",
|
|
5
|
-
"
|
|
4
|
+
"version": "0.0.9",
|
|
6
5
|
"author": "Hans Otto Wirtz <hansottowirtz@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/bubblydoo/uxp-toolkit/tree/main/packages/uxp-test-framework-plugin",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "https://github.com/bubblydoo/uxp-toolkit.git"
|
|
10
11
|
},
|
|
11
|
-
"homepage": "https://github.com/bubblydoo/uxp-toolkit/tree/main/packages/uxp-test-framework-plugin",
|
|
12
12
|
"exports": {
|
|
13
13
|
"./cli": {
|
|
14
14
|
"types": "./dist-vite/cli.d.ts",
|
|
@@ -21,20 +21,19 @@
|
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"dist-vite",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"index.html",
|
|
25
|
+
"src-plugin"
|
|
26
26
|
],
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@bubblydoo/uxp-toolkit": "0.0.
|
|
28
|
+
"@bubblydoo/uxp-toolkit": "0.0.9"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"chokidar": "^5.0.0",
|
|
32
|
+
"tailwindcss": "^3.4.17",
|
|
32
33
|
"typescript": "^5.9.3",
|
|
33
|
-
"vite": "^6.4.1"
|
|
34
|
-
"tailwindcss": "^3.4.17"
|
|
34
|
+
"vite": "^6.4.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@adobe/cc-ext-uxp-types": "https://github.com/justintaylor-dev/cc-ext-uxp-types.git",
|
|
38
37
|
"@babel/preset-typescript": "^7.28.5",
|
|
39
38
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
40
39
|
"@rollup/plugin-json": "^6.1.0",
|
|
@@ -42,7 +41,6 @@
|
|
|
42
41
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
43
42
|
"@tanstack/react-query": "^5.90.20",
|
|
44
43
|
"@types/node": "^20.8.7",
|
|
45
|
-
"@types/photoshop": "^25.0.2",
|
|
46
44
|
"@types/react": "^19.1.6",
|
|
47
45
|
"@types/react-dom": "^19.1.6",
|
|
48
46
|
"@types/ws": "^8.5.8",
|
|
@@ -64,11 +62,14 @@
|
|
|
64
62
|
"vite-uxp-plugin": "^1.2.5",
|
|
65
63
|
"ws": "^8.14.2",
|
|
66
64
|
"zod": "^4.3.6",
|
|
65
|
+
"@adobe-uxp-types/photoshop": "0.1.0",
|
|
66
|
+
"@adobe-uxp-types/uxp": "0.1.0",
|
|
67
67
|
"@bubblydoo/tsconfig": "0.0.3",
|
|
68
68
|
"@bubblydoo/uxp-polyfills": "0.0.4",
|
|
69
|
-
"@bubblydoo/uxp-test-framework-base": "0.0.
|
|
69
|
+
"@bubblydoo/uxp-test-framework-base": "0.0.5"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
|
-
"build": "rm -rf dist-vite && pnpm exec rollup -c && cp cli.d.ts dist-vite/cli.d.ts"
|
|
72
|
+
"build": "rm -rf dist-vite && pnpm exec rollup -c && cp cli.d.ts dist-vite/cli.d.ts",
|
|
73
|
+
"typecheck": "tsc --noEmit"
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { uxpEntrypointsSchema } from '@bubblydoo/uxp-toolkit';
|
|
3
|
+
import { entrypoints } from 'uxp';
|
|
3
4
|
|
|
4
5
|
const manifestId = uxpEntrypointsSchema.parse(entrypoints)._pluginInfo.id;
|
|
5
6
|
|
|
6
7
|
declare global {
|
|
8
|
+
// eslint-disable-next-line vars-on-top
|
|
7
9
|
var BOLT_UXP_HOT_RELOAD_PORT: number;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
const prefix =
|
|
12
|
+
const prefix = '[⚡ Bolt Hot Reload]';
|
|
11
13
|
|
|
12
14
|
const log = console.log.bind(console, prefix);
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
if (typeof BOLT_UXP_HOT_RELOAD_PORT ===
|
|
16
|
-
log(
|
|
16
|
+
function listenForHotReload() {
|
|
17
|
+
if (typeof BOLT_UXP_HOT_RELOAD_PORT === 'undefined') {
|
|
18
|
+
log('BOLT_UXP_HOT_RELOAD_PORT is not defined');
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
21
|
const reconnect = (reason: string) => {
|
|
@@ -23,17 +25,17 @@ const listenForHotReload = () => {
|
|
|
23
25
|
setTimeout(listenForHotReload, 3000);
|
|
24
26
|
};
|
|
25
27
|
const ws = new WebSocket(`ws://localhost:${BOLT_UXP_HOT_RELOAD_PORT}`);
|
|
26
|
-
ws.onclose = () => reconnect(
|
|
28
|
+
ws.onclose = () => reconnect('closed');
|
|
27
29
|
ws.onmessage = (event) => {
|
|
28
30
|
const data = JSON.parse(event.data);
|
|
29
|
-
if (data.id === manifestId && data.status ===
|
|
30
|
-
log(
|
|
31
|
+
if (data.id === manifestId && data.status === 'updated') {
|
|
32
|
+
log('Hot reloading...');
|
|
31
33
|
location.reload();
|
|
32
34
|
}
|
|
33
35
|
};
|
|
34
36
|
ws.onopen = () => {
|
|
35
|
-
log(
|
|
37
|
+
log('Connected to server');
|
|
36
38
|
};
|
|
37
|
-
}
|
|
39
|
+
}
|
|
38
40
|
|
|
39
41
|
listenForHotReload();
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { BasicStackFrame } from '@bubblydoo/uxp-toolkit';
|
|
2
|
+
import { copyToClipboard, getBasicStackFrameAbsoluteFilePath, parseUxpErrorSourcemaps } from '@bubblydoo/uxp-toolkit';
|
|
3
|
+
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
3
4
|
|
|
4
5
|
declare const __UNSOURCEMAPPED_HEADER_LINES__: number;
|
|
5
6
|
|
|
6
|
-
const UNSOURCEMAPPED_HEADER_LINES
|
|
7
|
-
typeof __UNSOURCEMAPPED_HEADER_LINES__ ===
|
|
7
|
+
const UNSOURCEMAPPED_HEADER_LINES
|
|
8
|
+
= typeof __UNSOURCEMAPPED_HEADER_LINES__ === 'number'
|
|
8
9
|
? __UNSOURCEMAPPED_HEADER_LINES__
|
|
9
10
|
: 0;
|
|
10
11
|
|
|
11
12
|
export function ErrorView({ error }: { error: Error }) {
|
|
12
13
|
const sourcemappedError = useQuery({
|
|
13
|
-
queryKey: [
|
|
14
|
+
queryKey: ['sourcemappedError', error],
|
|
14
15
|
queryFn: async () => {
|
|
15
16
|
return await parseUxpErrorSourcemaps(error, {
|
|
16
17
|
unsourcemappedHeaderLines: UNSOURCEMAPPED_HEADER_LINES,
|
|
@@ -35,7 +36,10 @@ export function ErrorView({ error }: { error: Error }) {
|
|
|
35
36
|
<div className="p-2">
|
|
36
37
|
<div>Sourcemapped error:</div>
|
|
37
38
|
{copyMutation.error && (
|
|
38
|
-
<>
|
|
39
|
+
<>
|
|
40
|
+
Copy error:
|
|
41
|
+
{errorToString(copyMutation.error)}
|
|
42
|
+
</>
|
|
39
43
|
)}
|
|
40
44
|
{sourcemappedError.isPending && (
|
|
41
45
|
<div>⏳ Loading sourcemapped error...</div>
|
|
@@ -45,15 +49,24 @@ export function ErrorView({ error }: { error: Error }) {
|
|
|
45
49
|
)}
|
|
46
50
|
{sourcemappedError.data && (
|
|
47
51
|
<div className="text-red-500 whitespace-pre-wrap">
|
|
48
|
-
{error.name}
|
|
52
|
+
{error.name}
|
|
53
|
+
:
|
|
54
|
+
{error.message}
|
|
49
55
|
{sourcemappedError.data.map((frame, i) => (
|
|
50
56
|
<div key={i}>
|
|
51
|
-
{frame.functionName}
|
|
57
|
+
{frame.functionName}
|
|
58
|
+
{' '}
|
|
59
|
+
@
|
|
60
|
+
{' '}
|
|
52
61
|
<span
|
|
53
62
|
className="underline"
|
|
54
63
|
onClick={() => copyMutation.mutate(frame)}
|
|
55
64
|
>
|
|
56
|
-
{frame.fileName}
|
|
65
|
+
{frame.fileName}
|
|
66
|
+
:
|
|
67
|
+
{frame.lineNumber}
|
|
68
|
+
:
|
|
69
|
+
{frame.columnNumber}
|
|
57
70
|
</span>
|
|
58
71
|
</div>
|
|
59
72
|
))}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import { App } from './main';
|
|
5
|
+
import '@bubblydoo/uxp-polyfills';
|
|
6
|
+
import './bolt-uxp-ws-listener';
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import './index.css';
|
|
9
9
|
|
|
10
|
-
ReactDOM.createRoot(document.getElementById(
|
|
10
|
+
ReactDOM.createRoot(document.getElementById('app') as HTMLElement).render(
|
|
11
11
|
<React.StrictMode>
|
|
12
12
|
<App />
|
|
13
13
|
</React.StrictMode>,
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
// eslint-disable-next-line unicorn/prefer-node-protocol
|
|
2
|
+
import { resolve as nativeResolve } from 'path';
|
|
2
3
|
|
|
3
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* for some reason native path.resolve in UXP returns a URL object
|
|
4
6
|
* this function converts it to a string
|
|
5
7
|
*/
|
|
6
8
|
export function pathResolve(...pathSegments: string[]): string {
|
|
7
9
|
const urlOrString = nativeResolve(...pathSegments) as URL | string;
|
|
8
|
-
if (typeof urlOrString ===
|
|
10
|
+
if (typeof urlOrString === 'string') {
|
|
9
11
|
return urlOrString;
|
|
10
12
|
}
|
|
11
13
|
if (isUrl(urlOrString)) {
|
|
12
14
|
return urlOrString.toString();
|
|
13
15
|
}
|
|
14
|
-
throw new Error(
|
|
16
|
+
throw new Error('Unexpected URL object');
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
function isUrl(urlOrString: any): urlOrString is URL {
|
package/src-plugin/main.tsx
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { Test } from '@bubblydoo/uxp-test-framework-base';
|
|
2
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import {
|
|
3
5
|
createContext,
|
|
6
|
+
use,
|
|
4
7
|
useCallback,
|
|
5
|
-
useContext,
|
|
6
8
|
useEffect,
|
|
7
9
|
useMemo,
|
|
8
10
|
useState,
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
11
|
-
import { ErrorView } from
|
|
12
|
-
import { Test } from "@bubblydoo/uxp-test-framework-base";
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { tests } from 'TESTS';
|
|
13
|
+
import { ErrorView } from './components/ErrorView';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
status:
|
|
15
|
+
interface TestResult {
|
|
16
|
+
status: 'idle' | 'success' | 'error' | 'pending';
|
|
16
17
|
isIdle: boolean;
|
|
17
18
|
isSuccess: boolean;
|
|
18
19
|
isPending: boolean;
|
|
19
20
|
error: any;
|
|
20
|
-
}
|
|
21
|
+
}
|
|
21
22
|
|
|
22
23
|
const TestResultsContext = createContext<{
|
|
23
|
-
set(test: Test, result: TestResult)
|
|
24
|
-
get(test: Test)
|
|
25
|
-
delete(test: Test)
|
|
24
|
+
set: (test: Test, result: TestResult) => void;
|
|
25
|
+
get: (test: Test) => TestResult | undefined;
|
|
26
|
+
delete: (test: Test) => void;
|
|
26
27
|
}>(null!);
|
|
27
28
|
|
|
28
|
-
export
|
|
29
|
+
export function App() {
|
|
29
30
|
const [queryClient] = useState(() => new QueryClient());
|
|
30
31
|
const [testResultsCache, setTestResults] = useState(
|
|
31
|
-
() => new Map<Test, any>()
|
|
32
|
+
() => new Map<Test, any>(),
|
|
32
33
|
);
|
|
33
34
|
const testResults = useMemo(
|
|
34
35
|
() => ({
|
|
35
36
|
set: (test: Test, result: TestResult) =>
|
|
36
|
-
setTestResults(
|
|
37
|
+
setTestResults(prev => new Map(prev).set(test, result)),
|
|
37
38
|
get: (test: Test) => testResultsCache.get(test),
|
|
38
39
|
delete: (test: Test) => {
|
|
39
40
|
setTestResults((prev) => {
|
|
@@ -43,26 +44,27 @@ export const App = () => {
|
|
|
43
44
|
});
|
|
44
45
|
},
|
|
45
46
|
}),
|
|
46
|
-
[testResultsCache]
|
|
47
|
+
[testResultsCache],
|
|
47
48
|
);
|
|
48
49
|
|
|
49
50
|
return (
|
|
50
51
|
<>
|
|
51
52
|
<main className="text-white overflow-y-auto">
|
|
52
53
|
<QueryClientProvider client={queryClient}>
|
|
53
|
-
<TestResultsContext
|
|
54
|
+
<TestResultsContext value={testResults}>
|
|
54
55
|
<TestView tests={tests} />
|
|
55
|
-
</TestResultsContext
|
|
56
|
+
</TestResultsContext>
|
|
56
57
|
</QueryClientProvider>
|
|
57
58
|
</main>
|
|
58
59
|
</>
|
|
59
60
|
);
|
|
60
|
-
}
|
|
61
|
+
}
|
|
61
62
|
|
|
62
63
|
async function runTest(test: Test): Promise<void> {
|
|
63
64
|
try {
|
|
64
65
|
await test.run.call(undefined, { name: test.name });
|
|
65
|
-
}
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
66
68
|
if (!(e instanceof Error)) {
|
|
67
69
|
console.error(`Test ${test.name} threw a non-Error (${typeof e}): ${e}`);
|
|
68
70
|
}
|
|
@@ -95,25 +97,25 @@ function usePromiseStatus<T>(promise: Promise<T> | null): TestResult {
|
|
|
95
97
|
const result: TestResult = useMemo(
|
|
96
98
|
() => ({
|
|
97
99
|
status: success
|
|
98
|
-
?
|
|
100
|
+
? 'success'
|
|
99
101
|
: error
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
? 'error'
|
|
103
|
+
: isPending
|
|
104
|
+
? 'pending'
|
|
105
|
+
: 'idle',
|
|
104
106
|
isIdle: !promise,
|
|
105
107
|
isSuccess: success,
|
|
106
108
|
error,
|
|
107
109
|
isPending,
|
|
108
110
|
}),
|
|
109
|
-
[success, error, isPending, promise]
|
|
111
|
+
[success, error, isPending, promise],
|
|
110
112
|
);
|
|
111
113
|
|
|
112
114
|
return result;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
const stableIdleResult: TestResult = {
|
|
116
|
-
status:
|
|
118
|
+
status: 'idle',
|
|
117
119
|
isIdle: true,
|
|
118
120
|
isSuccess: false,
|
|
119
121
|
isPending: false,
|
|
@@ -122,19 +124,19 @@ const stableIdleResult: TestResult = {
|
|
|
122
124
|
|
|
123
125
|
function useTestResult(test: Test): { mutate: () => void; result: TestResult } {
|
|
124
126
|
const [promise, setPromise] = useState<Promise<void> | null>(null);
|
|
125
|
-
const testResults =
|
|
127
|
+
const testResults = use(TestResultsContext);
|
|
126
128
|
const mutate = useCallback(() => {
|
|
127
129
|
testResults.delete(test);
|
|
128
130
|
setPromise(runTest(test));
|
|
129
131
|
}, [test, testResults]);
|
|
130
132
|
const promiseResult = usePromiseStatus(promise);
|
|
131
133
|
useEffect(() => {
|
|
132
|
-
if (promiseResult.status !==
|
|
134
|
+
if (promiseResult.status !== 'idle') {
|
|
133
135
|
testResults.set(test, promiseResult);
|
|
134
136
|
}
|
|
135
137
|
}, [promiseResult]);
|
|
136
|
-
const globalResult
|
|
137
|
-
promiseResult.status !==
|
|
138
|
+
const globalResult
|
|
139
|
+
= promiseResult.status !== 'idle'
|
|
138
140
|
? promiseResult
|
|
139
141
|
: testResults.get(test) ?? stableIdleResult;
|
|
140
142
|
return {
|
|
@@ -144,13 +146,13 @@ function useTestResult(test: Test): { mutate: () => void; result: TestResult } {
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
function TestView({ tests }: { tests: Test[] }) {
|
|
147
|
-
const resultContext =
|
|
149
|
+
const resultContext = use(TestResultsContext);
|
|
148
150
|
const runAllTests = useCallback(async () => {
|
|
149
151
|
for (const test of tests) {
|
|
150
152
|
console.log(`Running test ${test.name}`);
|
|
151
153
|
// await runTest(test);
|
|
152
154
|
resultContext.set(test, {
|
|
153
|
-
status:
|
|
155
|
+
status: 'pending',
|
|
154
156
|
isIdle: false,
|
|
155
157
|
isSuccess: false,
|
|
156
158
|
isPending: true,
|
|
@@ -159,15 +161,16 @@ function TestView({ tests }: { tests: Test[] }) {
|
|
|
159
161
|
try {
|
|
160
162
|
await runTest(test);
|
|
161
163
|
resultContext.set(test, {
|
|
162
|
-
status:
|
|
164
|
+
status: 'success',
|
|
163
165
|
isIdle: false,
|
|
164
166
|
isSuccess: true,
|
|
165
167
|
isPending: false,
|
|
166
168
|
error: null,
|
|
167
169
|
});
|
|
168
|
-
}
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
169
172
|
resultContext.set(test, {
|
|
170
|
-
status:
|
|
173
|
+
status: 'error',
|
|
171
174
|
isIdle: false,
|
|
172
175
|
isSuccess: false,
|
|
173
176
|
isPending: false,
|
|
@@ -187,7 +190,8 @@ function TestView({ tests }: { tests: Test[] }) {
|
|
|
187
190
|
Run All Tests
|
|
188
191
|
</button>
|
|
189
192
|
{tests.map((test, i) => {
|
|
190
|
-
if (!test)
|
|
193
|
+
if (!test)
|
|
194
|
+
return null;
|
|
191
195
|
return <OneTest test={test} key={i} />;
|
|
192
196
|
})}
|
|
193
197
|
</div>
|
|
@@ -216,7 +220,11 @@ function OneTest(props: { test: Test }) {
|
|
|
216
220
|
</button>
|
|
217
221
|
</div>
|
|
218
222
|
<div>
|
|
219
|
-
Result:
|
|
223
|
+
Result:
|
|
224
|
+
{' '}
|
|
225
|
+
{result.status}
|
|
226
|
+
{' '}
|
|
227
|
+
<TestResultEmoji result={result} />
|
|
220
228
|
{result.error && (
|
|
221
229
|
<div className="pt-2">
|
|
222
230
|
<ErrorView error={result.error} />
|
|
@@ -229,10 +237,10 @@ function OneTest(props: { test: Test }) {
|
|
|
229
237
|
|
|
230
238
|
function TestResultEmoji({ result }: { result: TestResult }) {
|
|
231
239
|
return result.error
|
|
232
|
-
?
|
|
240
|
+
? '❌'
|
|
233
241
|
: result.isSuccess
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
? '✅'
|
|
243
|
+
: result.isPending
|
|
244
|
+
? '⏳'
|
|
245
|
+
: '❔';
|
|
238
246
|
}
|
package/src-plugin/tests.d.ts
CHANGED