loopwind 0.25.6 → 0.25.7
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/app/.astro/types.d.ts +1 -0
- package/app/dist/_astro/callback.Ci5gaEfJ.css +1 -0
- package/app/dist/auth/callback/index.html +81 -0
- package/app/dist/device/index.html +70 -0
- package/app/dist/index.html +327 -0
- package/app/package-lock.json +9239 -0
- package/app/package.json +23 -0
- package/app/wrangler.toml +8 -0
- package/dist/cli.js +54 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +60 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +15 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/publish.d.ts +10 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +155 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/templates.d.ts +5 -0
- package/dist/commands/templates.d.ts.map +1 -0
- package/dist/commands/templates.js +60 -0
- package/dist/commands/templates.js.map +1 -0
- package/dist/commands/unpublish.d.ts +5 -0
- package/dist/commands/unpublish.d.ts.map +1 -0
- package/dist/commands/unpublish.js +54 -0
- package/dist/commands/unpublish.js.map +1 -0
- package/dist/commands/whoami.d.ts +5 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +30 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/lib/api.d.ts +92 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +149 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/auth.d.ts +41 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +89 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/bundler.d.ts +18 -0
- package/dist/lib/bundler.d.ts.map +1 -0
- package/dist/lib/bundler.js +105 -0
- package/dist/lib/bundler.js.map +1 -0
- package/dist/lib/helpers.d.ts +35 -2
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/helpers.js +91 -13
- package/dist/lib/helpers.js.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/sdk/edge.d.ts +65 -0
- package/dist/sdk/edge.d.ts.map +1 -0
- package/dist/sdk/edge.js +329 -0
- package/dist/sdk/edge.js.map +1 -0
- package/dist/sdk/errors.d.ts +64 -0
- package/dist/sdk/errors.d.ts.map +1 -0
- package/dist/sdk/errors.js +94 -0
- package/dist/sdk/errors.js.map +1 -0
- package/dist/sdk/index.d.ts +29 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +30 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/render.d.ts +52 -0
- package/dist/sdk/render.d.ts.map +1 -0
- package/dist/sdk/render.js +432 -0
- package/dist/sdk/render.js.map +1 -0
- package/dist/sdk/types.d.ts +185 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/types.js +5 -0
- package/dist/sdk/types.js.map +1 -0
- package/dist/types/template.d.ts +18 -0
- package/dist/types/template.d.ts.map +1 -1
- package/package.json +26 -4
- package/plans/PLATFORM.md +1637 -237
- package/plans/PLATFORM_IMPLEMENTATION.md +1347 -530
- package/plans/SDK.md +797 -0
- package/platform/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/ebad93a0a7be9c5768c512f3e30740b64d2b6e575277a40d77044af5ae8fd3f2.sqlite +0 -0
- package/platform/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/ebad93a0a7be9c5768c512f3e30740b64d2b6e575277a40d77044af5ae8fd3f2.sqlite-shm +0 -0
- package/platform/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/ebad93a0a7be9c5768c512f3e30740b64d2b6e575277a40d77044af5ae8fd3f2.sqlite-wal +0 -0
- package/platform/migrations/0001_initial.sql +90 -0
- package/platform/package-lock.json +3104 -0
- package/platform/package.json +30 -0
- package/platform/wrangler.toml +43 -0
- package/tests-sdk/createRenderer.test.ts +251 -0
- package/tests-sdk/errors.test.ts +230 -0
- package/tests-sdk/render.test.ts +241 -0
- package/tests-sdk/tw.test.ts +277 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Render Function Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { test, describe } from 'node:test';
|
|
6
|
+
import assert from 'node:assert';
|
|
7
|
+
import { render, ValidationError } from '../dist/sdk/index.js';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
// Simple test template
|
|
11
|
+
const SimpleTemplate = ({ title, tw }: { title: string; tw: (c: string) => any }) => {
|
|
12
|
+
return React.createElement(
|
|
13
|
+
'div',
|
|
14
|
+
{ style: tw('w-full h-full bg-blue-500 flex items-center justify-center') },
|
|
15
|
+
React.createElement('h1', { style: tw('text-white text-4xl') }, title)
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Template with multiple props
|
|
20
|
+
const ComplexTemplate = ({
|
|
21
|
+
title,
|
|
22
|
+
subtitle,
|
|
23
|
+
showBadge,
|
|
24
|
+
tw,
|
|
25
|
+
}: {
|
|
26
|
+
title: string;
|
|
27
|
+
subtitle?: string;
|
|
28
|
+
showBadge?: boolean;
|
|
29
|
+
tw: (c: string) => any;
|
|
30
|
+
}) => {
|
|
31
|
+
return React.createElement(
|
|
32
|
+
'div',
|
|
33
|
+
{ style: tw('w-full h-full bg-gradient-to-r from-purple-500 to-pink-500 p-12 flex flex-col') },
|
|
34
|
+
[
|
|
35
|
+
React.createElement('h1', { key: 'title', style: tw('text-white text-5xl font-bold') }, title),
|
|
36
|
+
subtitle && React.createElement('p', { key: 'sub', style: tw('text-white/80 text-xl mt-4') }, subtitle),
|
|
37
|
+
showBadge && React.createElement('span', { key: 'badge', style: tw('bg-white text-purple-500 px-4 py-2 rounded-full mt-4') }, 'NEW'),
|
|
38
|
+
]
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe('render()', () => {
|
|
43
|
+
test('renders PNG with basic template', async () => {
|
|
44
|
+
const result = await render(SimpleTemplate, {
|
|
45
|
+
props: { title: 'Hello World' },
|
|
46
|
+
width: 800,
|
|
47
|
+
height: 400,
|
|
48
|
+
format: 'png',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array, 'Result should be a buffer');
|
|
52
|
+
assert.ok(result.length > 0, 'Result should not be empty');
|
|
53
|
+
|
|
54
|
+
// Check PNG magic bytes
|
|
55
|
+
const pngMagic = [0x89, 0x50, 0x4e, 0x47];
|
|
56
|
+
const header = Array.from(result.slice(0, 4));
|
|
57
|
+
assert.deepStrictEqual(header, pngMagic, 'Result should be valid PNG');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('renders SVG with basic template', async () => {
|
|
61
|
+
const result = await render(SimpleTemplate, {
|
|
62
|
+
props: { title: 'Hello SVG' },
|
|
63
|
+
width: 800,
|
|
64
|
+
height: 400,
|
|
65
|
+
format: 'svg',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array, 'Result should be a buffer');
|
|
69
|
+
|
|
70
|
+
const svgString = result.toString('utf-8');
|
|
71
|
+
assert.ok(svgString.includes('<svg'), 'Result should contain SVG tag');
|
|
72
|
+
assert.ok(svgString.includes('</svg>'), 'Result should be valid SVG');
|
|
73
|
+
assert.ok(svgString.length > 500, 'SVG should have substantial content');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('renders with complex props', async () => {
|
|
77
|
+
const result = await render(ComplexTemplate, {
|
|
78
|
+
props: {
|
|
79
|
+
title: 'Complex Test',
|
|
80
|
+
subtitle: 'With subtitle',
|
|
81
|
+
showBadge: true,
|
|
82
|
+
},
|
|
83
|
+
width: 1200,
|
|
84
|
+
height: 630,
|
|
85
|
+
format: 'png',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array);
|
|
89
|
+
assert.ok(result.length > 0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('uses default dimensions', async () => {
|
|
93
|
+
const result = await render(SimpleTemplate, {
|
|
94
|
+
props: { title: 'Default Size' },
|
|
95
|
+
format: 'svg',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const svgString = result.toString('utf-8');
|
|
99
|
+
// Default is 1200x630
|
|
100
|
+
assert.ok(svgString.includes('width="1200"'), 'Should use default width');
|
|
101
|
+
assert.ok(svgString.includes('height="630"'), 'Should use default height');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('respects custom dimensions', async () => {
|
|
105
|
+
const result = await render(SimpleTemplate, {
|
|
106
|
+
props: { title: 'Custom Size' },
|
|
107
|
+
width: 500,
|
|
108
|
+
height: 250,
|
|
109
|
+
format: 'svg',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const svgString = result.toString('utf-8');
|
|
113
|
+
assert.ok(svgString.includes('width="500"'), 'Should use custom width');
|
|
114
|
+
assert.ok(svgString.includes('height="250"'), 'Should use custom height');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('throws ValidationError for invalid width', async () => {
|
|
118
|
+
await assert.rejects(
|
|
119
|
+
async () => {
|
|
120
|
+
await render(SimpleTemplate, {
|
|
121
|
+
props: { title: 'Test' },
|
|
122
|
+
width: -100,
|
|
123
|
+
format: 'png',
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
(error: any) => {
|
|
127
|
+
assert.ok(error instanceof ValidationError);
|
|
128
|
+
assert.strictEqual(error.field, 'width');
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('throws ValidationError for invalid height', async () => {
|
|
135
|
+
await assert.rejects(
|
|
136
|
+
async () => {
|
|
137
|
+
await render(SimpleTemplate, {
|
|
138
|
+
props: { title: 'Test' },
|
|
139
|
+
height: 999999,
|
|
140
|
+
format: 'png',
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
(error: any) => {
|
|
144
|
+
assert.ok(error instanceof ValidationError);
|
|
145
|
+
assert.strictEqual(error.field, 'height');
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('throws ValidationError for invalid format', async () => {
|
|
152
|
+
await assert.rejects(
|
|
153
|
+
async () => {
|
|
154
|
+
await render(SimpleTemplate, {
|
|
155
|
+
props: { title: 'Test' },
|
|
156
|
+
format: 'invalid' as any,
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
(error: any) => {
|
|
160
|
+
assert.ok(error instanceof ValidationError);
|
|
161
|
+
assert.strictEqual(error.field, 'format');
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('renders JPG format', async () => {
|
|
168
|
+
const result = await render(SimpleTemplate, {
|
|
169
|
+
props: { title: 'JPEG Test' },
|
|
170
|
+
width: 400,
|
|
171
|
+
height: 200,
|
|
172
|
+
format: 'jpg',
|
|
173
|
+
quality: 80,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array);
|
|
177
|
+
assert.ok(result.length > 0);
|
|
178
|
+
|
|
179
|
+
// Check JPEG magic bytes
|
|
180
|
+
const jpegMagic = [0xff, 0xd8, 0xff];
|
|
181
|
+
const header = Array.from(result.slice(0, 3));
|
|
182
|
+
assert.deepStrictEqual(header, jpegMagic, 'Result should be valid JPEG');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('renders WebP format', async () => {
|
|
186
|
+
const result = await render(SimpleTemplate, {
|
|
187
|
+
props: { title: 'WebP Test' },
|
|
188
|
+
width: 400,
|
|
189
|
+
height: 200,
|
|
190
|
+
format: 'webp',
|
|
191
|
+
quality: 85,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array);
|
|
195
|
+
assert.ok(result.length > 0);
|
|
196
|
+
|
|
197
|
+
// Check WebP magic bytes (RIFF....WEBP)
|
|
198
|
+
const riffMagic = result.slice(0, 4).toString('utf-8');
|
|
199
|
+
const webpMagic = result.slice(8, 12).toString('utf-8');
|
|
200
|
+
assert.strictEqual(riffMagic, 'RIFF', 'Should have RIFF header');
|
|
201
|
+
assert.strictEqual(webpMagic, 'WEBP', 'Should have WEBP identifier');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('respects scale option', async () => {
|
|
205
|
+
const scale1 = await render(SimpleTemplate, {
|
|
206
|
+
props: { title: 'Scale 1' },
|
|
207
|
+
width: 100,
|
|
208
|
+
height: 100,
|
|
209
|
+
format: 'png',
|
|
210
|
+
scale: 1,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const scale2 = await render(SimpleTemplate, {
|
|
214
|
+
props: { title: 'Scale 2' },
|
|
215
|
+
width: 100,
|
|
216
|
+
height: 100,
|
|
217
|
+
format: 'png',
|
|
218
|
+
scale: 2,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Scale 2 should produce larger file (more pixels)
|
|
222
|
+
assert.ok(scale2.length > scale1.length, 'Higher scale should produce larger file');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('render() with timeout', () => {
|
|
227
|
+
test('completes within timeout', async () => {
|
|
228
|
+
const result = await render(SimpleTemplate, {
|
|
229
|
+
props: { title: 'Fast Render' },
|
|
230
|
+
width: 100,
|
|
231
|
+
height: 100,
|
|
232
|
+
format: 'png',
|
|
233
|
+
timeout: 30000, // 30 seconds should be plenty
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
assert.ok(result.length > 0);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Note: Testing actual timeout is tricky without a slow template
|
|
240
|
+
// In production, you'd want to test with a deliberately slow render
|
|
241
|
+
});
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK tw() Function Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests that Tailwind classes are properly converted to inline styles
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { test, describe } from 'node:test';
|
|
8
|
+
import assert from 'node:assert';
|
|
9
|
+
import { render } from '../dist/sdk/index.js';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
|
|
12
|
+
// Helper to extract styles from SVG output
|
|
13
|
+
async function renderAndGetSvg(twClasses: string): Promise<string> {
|
|
14
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
15
|
+
return React.createElement('div', { style: tw(twClasses) }, 'Test');
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const result = await render(Template, {
|
|
19
|
+
props: {},
|
|
20
|
+
width: 100,
|
|
21
|
+
height: 100,
|
|
22
|
+
format: 'svg',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return result.toString('utf-8');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('tw() function', () => {
|
|
29
|
+
test('converts flex classes', async () => {
|
|
30
|
+
const svg = await renderAndGetSvg('flex items-center justify-center');
|
|
31
|
+
// Satori renders successfully with flex - just verify output exists
|
|
32
|
+
assert.ok(svg.length > 0, 'Should render successfully with flex');
|
|
33
|
+
assert.ok(svg.includes('<svg'), 'Should contain SVG output');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('converts padding classes', async () => {
|
|
37
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
38
|
+
const style = tw('p-4');
|
|
39
|
+
// p-4 = 16px padding (4 * 4)
|
|
40
|
+
return React.createElement('div', { style }, 'Padded');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const result = await render(Template, {
|
|
44
|
+
props: {},
|
|
45
|
+
format: 'svg',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
assert.ok(result.length > 0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('converts margin classes', async () => {
|
|
52
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
53
|
+
const style = tw('m-8 mt-4');
|
|
54
|
+
return React.createElement('div', { style }, 'Margin');
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const result = await render(Template, {
|
|
58
|
+
props: {},
|
|
59
|
+
format: 'svg',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
assert.ok(result.length > 0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('converts width and height classes', async () => {
|
|
66
|
+
const svg = await renderAndGetSvg('w-full h-full');
|
|
67
|
+
assert.ok(svg.includes('100%') || svg.includes('width'), 'Should contain width');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('converts background color classes', async () => {
|
|
71
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
72
|
+
return React.createElement(
|
|
73
|
+
'div',
|
|
74
|
+
{ style: tw('bg-blue-500') },
|
|
75
|
+
'Blue Background'
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const result = await render(Template, {
|
|
80
|
+
props: {},
|
|
81
|
+
format: 'svg',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// bg-blue-500 = #3b82f6
|
|
85
|
+
const svg = result.toString('utf-8');
|
|
86
|
+
assert.ok(
|
|
87
|
+
svg.includes('#3b82f6') || svg.includes('3b82f6') || svg.includes('rgb'),
|
|
88
|
+
'Should contain blue color'
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('converts text color classes', async () => {
|
|
93
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
94
|
+
return React.createElement(
|
|
95
|
+
'span',
|
|
96
|
+
{ style: tw('text-white') },
|
|
97
|
+
'White Text'
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const result = await render(Template, {
|
|
102
|
+
props: {},
|
|
103
|
+
format: 'svg',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const svg = result.toString('utf-8');
|
|
107
|
+
// text-white = #ffffff or white
|
|
108
|
+
assert.ok(
|
|
109
|
+
svg.includes('#fff') || svg.includes('white') || svg.includes('#ffffff'),
|
|
110
|
+
'Should contain white color'
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('converts font size classes', async () => {
|
|
115
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
116
|
+
return React.createElement('span', { style: tw('text-4xl') }, 'Large');
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const result = await render(Template, {
|
|
120
|
+
props: {},
|
|
121
|
+
format: 'svg',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
assert.ok(result.length > 0);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('converts font weight classes', async () => {
|
|
128
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
129
|
+
return React.createElement('span', { style: tw('font-bold') }, 'Bold');
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const result = await render(Template, {
|
|
133
|
+
props: {},
|
|
134
|
+
format: 'svg',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// font-bold = font-weight: 700, just verify rendering succeeds
|
|
138
|
+
assert.ok(result.length > 0, 'Should render successfully with font-bold');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('converts border radius classes', async () => {
|
|
142
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
143
|
+
return React.createElement(
|
|
144
|
+
'div',
|
|
145
|
+
{ style: tw('rounded-lg') },
|
|
146
|
+
'Rounded'
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const result = await render(Template, {
|
|
151
|
+
props: {},
|
|
152
|
+
format: 'svg',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
assert.ok(result.length > 0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('converts shadow classes', async () => {
|
|
159
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
160
|
+
return React.createElement('div', { style: tw('shadow-lg') }, 'Shadow');
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const result = await render(Template, {
|
|
164
|
+
props: {},
|
|
165
|
+
format: 'svg',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
assert.ok(result.length > 0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('handles multiple classes', async () => {
|
|
172
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
173
|
+
return React.createElement(
|
|
174
|
+
'div',
|
|
175
|
+
{
|
|
176
|
+
style: tw(
|
|
177
|
+
'w-full h-full flex items-center justify-center bg-gradient-to-r from-blue-500 to-purple-600 p-8 rounded-xl'
|
|
178
|
+
),
|
|
179
|
+
},
|
|
180
|
+
'Multi-class'
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const result = await render(Template, {
|
|
185
|
+
props: {},
|
|
186
|
+
format: 'svg',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
assert.ok(result.length > 0);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('handles gap classes', async () => {
|
|
193
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
194
|
+
return React.createElement(
|
|
195
|
+
'div',
|
|
196
|
+
{ style: tw('flex gap-4') },
|
|
197
|
+
[
|
|
198
|
+
React.createElement('span', { key: '1' }, 'A'),
|
|
199
|
+
React.createElement('span', { key: '2' }, 'B'),
|
|
200
|
+
]
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = await render(Template, {
|
|
205
|
+
props: {},
|
|
206
|
+
format: 'svg',
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
assert.ok(result.length > 0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('handles opacity classes', async () => {
|
|
213
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
214
|
+
return React.createElement(
|
|
215
|
+
'div',
|
|
216
|
+
{ style: tw('opacity-50') },
|
|
217
|
+
'Semi-transparent'
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const result = await render(Template, {
|
|
222
|
+
props: {},
|
|
223
|
+
format: 'svg',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
assert.ok(result.length > 0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('returns style object that can be spread', async () => {
|
|
230
|
+
const Template = ({ tw }: { tw: (c: string) => any }) => {
|
|
231
|
+
const baseStyles = tw('bg-white p-4');
|
|
232
|
+
const customStyles = { opacity: 0.8 };
|
|
233
|
+
|
|
234
|
+
return React.createElement(
|
|
235
|
+
'div',
|
|
236
|
+
{ style: { ...baseStyles, ...customStyles } },
|
|
237
|
+
'Merged styles'
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const result = await render(Template, {
|
|
242
|
+
props: {},
|
|
243
|
+
format: 'svg',
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
assert.ok(result.length > 0);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('handles dynamic class construction', async () => {
|
|
250
|
+
const Template = ({
|
|
251
|
+
variant,
|
|
252
|
+
tw,
|
|
253
|
+
}: {
|
|
254
|
+
variant: 'primary' | 'secondary';
|
|
255
|
+
tw: (c: string) => any;
|
|
256
|
+
}) => {
|
|
257
|
+
const bgClass = variant === 'primary' ? 'bg-blue-500' : 'bg-gray-500';
|
|
258
|
+
return React.createElement('div', { style: tw(`p-4 ${bgClass}`) }, variant);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const primary = await render(Template, {
|
|
262
|
+
props: { variant: 'primary' },
|
|
263
|
+
format: 'svg',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const secondary = await render(Template, {
|
|
267
|
+
props: { variant: 'secondary' },
|
|
268
|
+
format: 'svg',
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Both should render successfully
|
|
272
|
+
assert.ok(primary.length > 0);
|
|
273
|
+
assert.ok(secondary.length > 0);
|
|
274
|
+
// And they should be different (different colors)
|
|
275
|
+
assert.notStrictEqual(primary.toString(), secondary.toString());
|
|
276
|
+
});
|
|
277
|
+
});
|