@times-design-system/components-wordpress 1.4.0 → 1.5.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.
Files changed (72) hide show
  1. package/BLOCK_CREATION_CHECKLIST.md +124 -12
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +46 -29
  4. package/TESTING.md +152 -0
  5. package/dist/blocks/dialog-box/block.json +24 -0
  6. package/dist/blocks/dialog-box/edit.js +41 -0
  7. package/dist/blocks/dialog-box/index.js +17 -0
  8. package/dist/blocks/dialog-box/render.php +24 -0
  9. package/dist/blocks/dialog-box/save.js +23 -0
  10. package/dist/blocks/dialog-box/style.css +23 -0
  11. package/dist/blocks/icon/block.json +24 -0
  12. package/dist/blocks/icon/edit.js +41 -0
  13. package/dist/blocks/icon/index.js +17 -0
  14. package/dist/blocks/icon/render.php +24 -0
  15. package/dist/blocks/icon/save.js +23 -0
  16. package/dist/blocks/icon/style.css +23 -0
  17. package/dist/blocks/input-helper-message/block.json +24 -0
  18. package/dist/blocks/input-helper-message/edit.js +42 -0
  19. package/dist/blocks/input-helper-message/index.js +17 -0
  20. package/dist/blocks/input-helper-message/render.php +24 -0
  21. package/dist/blocks/input-helper-message/save.js +23 -0
  22. package/dist/blocks/input-helper-message/style.css +23 -0
  23. package/dist/blocks/tab/block.json +24 -0
  24. package/dist/blocks/tab/edit.js +41 -0
  25. package/dist/blocks/tab/index.js +17 -0
  26. package/dist/blocks/tab/render.php +24 -0
  27. package/dist/blocks/tab/save.js +23 -0
  28. package/dist/blocks/tab/style.css +23 -0
  29. package/dist/blocks/tab-group/block.json +24 -0
  30. package/dist/blocks/tab-group/edit.js +41 -0
  31. package/dist/blocks/tab-group/index.js +17 -0
  32. package/dist/blocks/tab-group/render.php +24 -0
  33. package/dist/blocks/tab-group/save.js +23 -0
  34. package/dist/blocks/tab-group/style.css +23 -0
  35. package/dist/vitest.config.d.ts +2 -0
  36. package/dist/vitest.setup.d.ts +1 -0
  37. package/package.json +21 -5
  38. package/scripts/create-wordpress-block-tests.cjs +438 -0
  39. package/scripts/create-wordpress-block.cjs +681 -0
  40. package/src/blocks/dialog-box/block.json +24 -0
  41. package/src/blocks/dialog-box/edit.js +41 -0
  42. package/src/blocks/dialog-box/index.js +17 -0
  43. package/src/blocks/dialog-box/render.php +24 -0
  44. package/src/blocks/dialog-box/save.js +23 -0
  45. package/src/blocks/dialog-box/style.css +23 -0
  46. package/src/blocks/icon/block.json +24 -0
  47. package/src/blocks/icon/edit.js +41 -0
  48. package/src/blocks/icon/index.js +17 -0
  49. package/src/blocks/icon/render.php +24 -0
  50. package/src/blocks/icon/save.js +23 -0
  51. package/src/blocks/icon/style.css +23 -0
  52. package/src/blocks/input-helper-message/block.json +24 -0
  53. package/src/blocks/input-helper-message/edit.js +42 -0
  54. package/src/blocks/input-helper-message/index.js +17 -0
  55. package/src/blocks/input-helper-message/render.php +24 -0
  56. package/src/blocks/input-helper-message/save.js +23 -0
  57. package/src/blocks/input-helper-message/style.css +23 -0
  58. package/src/blocks/tab/block.json +24 -0
  59. package/src/blocks/tab/edit.js +41 -0
  60. package/src/blocks/tab/index.js +17 -0
  61. package/src/blocks/tab/render.php +24 -0
  62. package/src/blocks/tab/save.js +23 -0
  63. package/src/blocks/tab/style.css +23 -0
  64. package/src/blocks/tab-group/block.json +24 -0
  65. package/src/blocks/tab-group/edit.js +41 -0
  66. package/src/blocks/tab-group/index.js +17 -0
  67. package/src/blocks/tab-group/render.php +24 -0
  68. package/src/blocks/tab-group/save.js +23 -0
  69. package/src/blocks/tab-group/style.css +23 -0
  70. package/vitest.config.js +28 -0
  71. package/vitest.config.ts +28 -0
  72. package/vitest.setup.ts +129 -0
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * WordPress Block Test Generator
5
+ *
6
+ * Generates test files for WordPress blocks based on component patterns
7
+ *
8
+ * Usage:
9
+ * node scripts/create-wordpress-block-tests.cjs ComponentName
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // Colors for console output
16
+ const colors = {
17
+ reset: '\x1b[0m',
18
+ bright: '\x1b[1m',
19
+ dim: '\x1b[2m',
20
+ green: '\x1b[32m',
21
+ yellow: '\x1b[33m',
22
+ red: '\x1b[31m',
23
+ cyan: '\x1b[36m',
24
+ };
25
+
26
+ function log(message, color = 'reset') {
27
+ console.log(`${colors[color]}${message}${colors.reset}`);
28
+ }
29
+
30
+ function componentToBlockName(componentName) {
31
+ return componentName
32
+ .replace(/([A-Z])/g, '-$1')
33
+ .toLowerCase()
34
+ .replace(/^-/, '');
35
+ }
36
+
37
+ /**
38
+ * Template for basic component test
39
+ */
40
+ function generateBasicTest(componentName, blockName) {
41
+ return `import { describe, it, expect } from 'vitest';
42
+ import { render } from '@testing-library/react';
43
+ import '@testing-library/jest-dom';
44
+ import React from 'react';
45
+
46
+ // Simplified Save component for testing
47
+ const ${componentName}Save = ({ attributes }) => {
48
+ const {} = attributes || {};
49
+
50
+ return React.createElement(
51
+ 'div',
52
+ { className: 'wp-block tds-${blockName}-wrapper' },
53
+ React.createElement('div', { className: 'tds-${blockName}' },
54
+ 'TODO: Implement component rendering'
55
+ )
56
+ );
57
+ };
58
+
59
+ describe('${componentName} Block', () => {
60
+ describe('Basic Rendering', () => {
61
+ it('should render wrapper element', () => {
62
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
63
+ expect(container.querySelector('.tds-${blockName}-wrapper')).toBeInTheDocument();
64
+ });
65
+
66
+ it('should render component element', () => {
67
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
68
+ expect(container.querySelector('.tds-${blockName}')).toBeInTheDocument();
69
+ });
70
+
71
+ it('should render with no attributes', () => {
72
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
73
+ expect(container.firstChild).toBeInTheDocument();
74
+ });
75
+ });
76
+
77
+ describe('Attribute Handling', () => {
78
+ it('should handle empty attributes gracefully', () => {
79
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
80
+ expect(container.querySelector('.tds-${blockName}')).toBeInTheDocument();
81
+ });
82
+
83
+ it('should handle missing attributes object', () => {
84
+ const { container } = render(React.createElement(${componentName}Save, {}));
85
+ expect(container.querySelector('.tds-${blockName}')).toBeInTheDocument();
86
+ });
87
+ });
88
+
89
+ describe('Accessibility', () => {
90
+ it('should render semantic HTML', () => {
91
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
92
+ expect(container.querySelector('.tds-${blockName}')).toBeInTheDocument();
93
+ });
94
+ });
95
+
96
+ // TODO: Add variant tests based on component props
97
+ // Reference: packages/components-react/src/${componentName}/${componentName}.tsx
98
+ // Patterns to implement:
99
+ // - Enum variants (intent, size, etc.)
100
+ // - Boolean flags (disabled, channel, etc.)
101
+ // - String content (label, content, etc.)
102
+ });
103
+ `;
104
+ }
105
+
106
+ /**
107
+ * Template for button-like components (with intent, size, behaviour, state)
108
+ */
109
+ function generateButtonLikeTest(componentName, blockName) {
110
+ return `import { describe, it, expect, vi } from 'vitest';
111
+ import { render } from '@testing-library/react';
112
+ import '@testing-library/jest-dom';
113
+ import React from 'react';
114
+
115
+ // Mock the class builder
116
+ vi.mock('../../src/utils/classBuilder.js', () => ({
117
+ build${componentName}Class: ({ intent = 'primary', size = 'medium', disabled = false }) => {
118
+ const classes = [
119
+ 'tds-${blockName}',
120
+ \`tds-${blockName}--intent-\${intent}\`,
121
+ \`tds-${blockName}--size-\${size}\`,
122
+ ];
123
+ if (disabled) classes.push('tds-${blockName}--disabled');
124
+ return classes.join(' ');
125
+ }
126
+ }));
127
+
128
+ // Simplified Save component for testing
129
+ const ${componentName}Save = ({ attributes }) => {
130
+ const {
131
+ label = '${componentName}',
132
+ intent = 'primary',
133
+ size = 'medium',
134
+ disabled = false,
135
+ ariaLabel,
136
+ } = attributes || {};
137
+
138
+ const classes = [
139
+ 'tds-${blockName}',
140
+ \`tds-${blockName}--intent-\${intent}\`,
141
+ \`tds-${blockName}--size-\${size}\`,
142
+ disabled ? 'tds-${blockName}--disabled' : '',
143
+ ].filter(Boolean).join(' ');
144
+
145
+ return React.createElement(
146
+ 'div',
147
+ { className: 'wp-block tds-${blockName}-wrapper' },
148
+ React.createElement(
149
+ 'div',
150
+ {
151
+ className: classes,
152
+ 'aria-label': ariaLabel,
153
+ },
154
+ label
155
+ )
156
+ );
157
+ };
158
+
159
+ describe('${componentName} Block', () => {
160
+ describe('Basic Rendering', () => {
161
+ it('should render wrapper element', () => {
162
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
163
+ expect(container.querySelector('.tds-${blockName}-wrapper')).toBeInTheDocument();
164
+ });
165
+
166
+ it('should render component with default label', () => {
167
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
168
+ expect(container.textContent).toContain('${componentName}');
169
+ });
170
+
171
+ it('should render component with custom label', () => {
172
+ const { container } = render(
173
+ React.createElement(${componentName}Save, { attributes: { label: 'Custom' } })
174
+ );
175
+ expect(container.textContent).toContain('Custom');
176
+ });
177
+ });
178
+
179
+ describe('Intent Variants', () => {
180
+ ['primary', 'secondary', 'negative'].forEach(intent => {
181
+ it(\`should render \${intent} intent\`, () => {
182
+ const { container } = render(
183
+ React.createElement(${componentName}Save, { attributes: { intent } })
184
+ );
185
+ expect(container.querySelector(\`.tds-${blockName}--intent-\${intent}\`)).toBeInTheDocument();
186
+ });
187
+ });
188
+ });
189
+
190
+ describe('Size Variants', () => {
191
+ ['small', 'medium', 'large'].forEach(size => {
192
+ it(\`should render \${size} size\`, () => {
193
+ const { container } = render(
194
+ React.createElement(${componentName}Save, { attributes: { size } })
195
+ );
196
+ expect(container.querySelector(\`.tds-${blockName}--size-\${size}\`)).toBeInTheDocument();
197
+ });
198
+ });
199
+ });
200
+
201
+ describe('Disabled State', () => {
202
+ it('should add disabled class when disabled is true', () => {
203
+ const { container } = render(
204
+ React.createElement(${componentName}Save, { attributes: { disabled: true } })
205
+ );
206
+ expect(container.querySelector('.tds-${blockName}--disabled')).toBeInTheDocument();
207
+ });
208
+
209
+ it('should not add disabled class when disabled is false', () => {
210
+ const { container } = render(
211
+ React.createElement(${componentName}Save, { attributes: { disabled: false } })
212
+ );
213
+ expect(container.querySelector('.tds-${blockName}--disabled')).not.toBeInTheDocument();
214
+ });
215
+ });
216
+
217
+ describe('Accessibility', () => {
218
+ it('should include aria-label when provided', () => {
219
+ const { container } = render(
220
+ React.createElement(${componentName}Save, { attributes: { ariaLabel: 'Custom label' } })
221
+ );
222
+ expect(container.querySelector('.tds-${blockName}')).toHaveAttribute('aria-label', 'Custom label');
223
+ });
224
+ });
225
+
226
+ describe('Multiple Attributes', () => {
227
+ it('should combine intent, size, and disabled', () => {
228
+ const { container } = render(
229
+ React.createElement(${componentName}Save, {
230
+ attributes: {
231
+ intent: 'negative',
232
+ size: 'large',
233
+ disabled: true
234
+ }
235
+ })
236
+ );
237
+ const element = container.querySelector('.tds-${blockName}');
238
+ expect(element).toHaveClass('tds-${blockName}--intent-negative');
239
+ expect(element).toHaveClass('tds-${blockName}--size-large');
240
+ expect(element).toHaveClass('tds-${blockName}--disabled');
241
+ });
242
+ });
243
+ });
244
+ `;
245
+ }
246
+
247
+ /**
248
+ * Template for text-like components (typography, semantic elements)
249
+ */
250
+ function generateTextLikeTest(componentName, blockName) {
251
+ return `import { describe, it, expect } from 'vitest';
252
+ import { render } from '@testing-library/react';
253
+ import '@testing-library/jest-dom';
254
+ import React from 'react';
255
+
256
+ // Simplified Save component for testing
257
+ const ${componentName}Save = ({ attributes }) => {
258
+ const {
259
+ content = 'Default text',
260
+ typography = 'body',
261
+ semanticElement = 'p',
262
+ } = attributes || {};
263
+
264
+ const classes = [
265
+ 'tds-${blockName}',
266
+ \`tds-${blockName}--typography-\${typography}\`,
267
+ ].filter(Boolean).join(' ');
268
+
269
+ return React.createElement(
270
+ 'div',
271
+ { className: 'wp-block tds-${blockName}-wrapper' },
272
+ React.createElement(
273
+ semanticElement,
274
+ { className: classes },
275
+ content
276
+ )
277
+ );
278
+ };
279
+
280
+ describe('${componentName} Block', () => {
281
+ describe('Basic Rendering', () => {
282
+ it('should render wrapper element', () => {
283
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
284
+ expect(container.querySelector('.tds-${blockName}-wrapper')).toBeInTheDocument();
285
+ });
286
+
287
+ it('should render with default content', () => {
288
+ const { container } = render(React.createElement(${componentName}Save, { attributes: {} }));
289
+ expect(container.textContent).toContain('Default text');
290
+ });
291
+
292
+ it('should render with custom content', () => {
293
+ const { container } = render(
294
+ React.createElement(${componentName}Save, { attributes: { content: 'Custom content' } })
295
+ );
296
+ expect(container.textContent).toContain('Custom content');
297
+ });
298
+ });
299
+
300
+ describe('Typography Variants', () => {
301
+ ['heading1', 'heading2', 'heading3', 'body', 'caption'].forEach(typography => {
302
+ it(\`should render \${typography} typography\`, () => {
303
+ const { container } = render(
304
+ React.createElement(${componentName}Save, { attributes: { typography } })
305
+ );
306
+ expect(container.querySelector(\`.tds-${blockName}--typography-\${typography}\`)).toBeInTheDocument();
307
+ });
308
+ });
309
+ });
310
+
311
+ describe('Semantic Elements', () => {
312
+ ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(element => {
313
+ it(\`should render as \${element} element\`, () => {
314
+ const { container } = render(
315
+ React.createElement(${componentName}Save, { attributes: { semanticElement: element } })
316
+ );
317
+ expect(container.querySelector(element)).toBeInTheDocument();
318
+ });
319
+ });
320
+ });
321
+
322
+ describe('Accessibility', () => {
323
+ it('should render heading as semantic element', () => {
324
+ const { container } = render(
325
+ React.createElement(${componentName}Save, { attributes: { semanticElement: 'h1' } })
326
+ );
327
+ expect(container.querySelector('h1')).toBeInTheDocument();
328
+ });
329
+
330
+ it('should render paragraph as semantic element', () => {
331
+ const { container } = render(
332
+ React.createElement(${componentName}Save, { attributes: { semanticElement: 'p' } })
333
+ );
334
+ expect(container.querySelector('p')).toBeInTheDocument();
335
+ });
336
+ });
337
+
338
+ describe('Multiple Attributes', () => {
339
+ it('should combine typography and semantic element', () => {
340
+ const { container } = render(
341
+ React.createElement(${componentName}Save, {
342
+ attributes: {
343
+ typography: 'heading1',
344
+ semanticElement: 'h1',
345
+ content: 'Heading'
346
+ }
347
+ })
348
+ );
349
+ expect(container.querySelector('h1')).toHaveClass('tds-${blockName}--typography-heading1');
350
+ });
351
+ });
352
+ });
353
+ `;
354
+ }
355
+
356
+ /**
357
+ * Determine component type and generate appropriate template
358
+ */
359
+ function generateTestFile(componentName, blockName) {
360
+ // Simple heuristic: check component type by name or attributes
361
+ // This can be improved by reading the React component file
362
+
363
+ const buttonLikeComponents = ['Button', 'IconButton', 'Flag', 'Toast'];
364
+ const textLikeComponents = ['Text', 'InputHelperMessage'];
365
+ const dividerLikeComponents = ['Divider', 'AdContainer'];
366
+
367
+ if (buttonLikeComponents.includes(componentName)) {
368
+ return generateButtonLikeTest(componentName, blockName);
369
+ } else if (textLikeComponents.includes(componentName)) {
370
+ return generateTextLikeTest(componentName, blockName);
371
+ } else if (dividerLikeComponents.includes(componentName)) {
372
+ return generateBasicTest(componentName, blockName);
373
+ } else {
374
+ // Default: basic test
375
+ return generateBasicTest(componentName, blockName);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Create test file for a component
381
+ */
382
+ function createBlockTest(componentName) {
383
+ const blockName = componentToBlockName(componentName);
384
+ const testDir = path.join(__dirname, '../__tests__/blocks');
385
+ const testFile = path.join(testDir, `${blockName}.test.js`);
386
+
387
+ // Check if test already exists
388
+ if (fs.existsSync(testFile)) {
389
+ log(`\n⚠️ Test file already exists: ${blockName}.test.js`, 'yellow');
390
+ return false;
391
+ }
392
+
393
+ // Create directory if it doesn't exist
394
+ if (!fs.existsSync(testDir)) {
395
+ fs.mkdirSync(testDir, { recursive: true });
396
+ }
397
+
398
+ // Generate and write test file
399
+ const testContent = generateTestFile(componentName, blockName);
400
+ fs.writeFileSync(testFile, testContent);
401
+
402
+ return true;
403
+ }
404
+
405
+ function main() {
406
+ const args = process.argv.slice(2);
407
+
408
+ if (args.length === 0) {
409
+ log(`\n❌ Component name required`, 'red');
410
+ log(`\nUsage: node scripts/create-wordpress-block-tests.cjs ComponentName`, 'dim');
411
+ log(`\nExample: node scripts/create-wordpress-block-tests.cjs Button\n`, 'dim');
412
+ process.exit(1);
413
+ }
414
+
415
+ const componentName = args[0];
416
+ const blockName = componentToBlockName(componentName);
417
+
418
+ if (createBlockTest(componentName)) {
419
+ log(`\n✅ Test file created successfully!`, 'green');
420
+ log(`\n📝 Test Details:`, 'bright');
421
+ log(` Component: ${componentName}`);
422
+ log(` Block: tds/${blockName}`);
423
+ log(` File: __tests__/blocks/${blockName}.test.js`);
424
+ log(`\n💡 Next Steps:`, 'cyan');
425
+ log(`\n 1. Review the generated test file`);
426
+ log(` 2. Implement the Save component rendering`);
427
+ log(` 3. Add variant and attribute tests based on component props`);
428
+ log(` 4. Run tests: npm test`);
429
+ log(`\n📖 Reference: __tests__/blocks/button.test.js for detailed example\n`);
430
+ process.exit(0);
431
+ } else {
432
+ process.exit(1);
433
+ }
434
+ }
435
+
436
+ module.exports = { createBlockTest, componentToBlockName };
437
+
438
+ main();