@stratawp/testing 0.1.0 → 0.1.1
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/README.md +18 -2
- package/dist/index.d.ts +1 -6
- package/dist/index.js +3 -6
- package/dist/index.js.map +1 -1
- package/dist/playwright.d.ts +7 -1
- package/dist/playwright.js +9 -0
- package/dist/playwright.js.map +1 -1
- package/dist/vitest.js +3 -6
- package/dist/vitest.js.map +1 -1
- package/package.json +11 -10
- package/vitest.config.ts +5 -6
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ describe('My Block', () => {
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
Run tests:
|
|
66
|
+
|
|
66
67
|
```bash
|
|
67
68
|
pnpm test
|
|
68
69
|
```
|
|
@@ -95,13 +96,13 @@ test.describe('My Block E2E', () => {
|
|
|
95
96
|
|
|
96
97
|
await publishPost(page)
|
|
97
98
|
|
|
98
|
-
await expect(page.locator('.components-snackbar'))
|
|
99
|
-
.toContainText('published')
|
|
99
|
+
await expect(page.locator('.components-snackbar')).toContainText('published')
|
|
100
100
|
})
|
|
101
101
|
})
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
Run E2E tests:
|
|
105
|
+
|
|
105
106
|
```bash
|
|
106
107
|
pnpm test:e2e
|
|
107
108
|
```
|
|
@@ -121,6 +122,7 @@ beforeAll(() => {
|
|
|
121
122
|
```
|
|
122
123
|
|
|
123
124
|
**Mocked APIs:**
|
|
125
|
+
|
|
124
126
|
- `@wordpress/blocks` - Block registration and manipulation
|
|
125
127
|
- `@wordpress/data` - Data stores and state management
|
|
126
128
|
- `@wordpress/i18n` - Internationalization functions
|
|
@@ -374,12 +376,14 @@ pnpm test:coverage
|
|
|
374
376
|
```
|
|
375
377
|
|
|
376
378
|
**Coverage Thresholds:**
|
|
379
|
+
|
|
377
380
|
- Lines: 80%
|
|
378
381
|
- Functions: 80%
|
|
379
382
|
- Branches: 75%
|
|
380
383
|
- Statements: 80%
|
|
381
384
|
|
|
382
385
|
View coverage report:
|
|
386
|
+
|
|
383
387
|
```bash
|
|
384
388
|
open coverage/index.html
|
|
385
389
|
```
|
|
@@ -389,6 +393,7 @@ open coverage/index.html
|
|
|
389
393
|
### Unit Tests
|
|
390
394
|
|
|
391
395
|
1. **Mock WordPress APIs**
|
|
396
|
+
|
|
392
397
|
```typescript
|
|
393
398
|
beforeAll(() => {
|
|
394
399
|
setupWordPressMocks()
|
|
@@ -397,6 +402,7 @@ open coverage/index.html
|
|
|
397
402
|
```
|
|
398
403
|
|
|
399
404
|
2. **Test Block Registration**
|
|
405
|
+
|
|
400
406
|
```typescript
|
|
401
407
|
it('should register with correct configuration', () => {
|
|
402
408
|
testBlockRegistration('my-theme/my-block', expectedConfig)
|
|
@@ -404,6 +410,7 @@ open coverage/index.html
|
|
|
404
410
|
```
|
|
405
411
|
|
|
406
412
|
3. **Test Component Rendering**
|
|
413
|
+
|
|
407
414
|
```typescript
|
|
408
415
|
it('should render with attributes', () => {
|
|
409
416
|
const { getByText } = renderBlockEdit(EditComponent, {
|
|
@@ -414,6 +421,7 @@ open coverage/index.html
|
|
|
414
421
|
```
|
|
415
422
|
|
|
416
423
|
4. **Test Attribute Updates**
|
|
424
|
+
|
|
417
425
|
```typescript
|
|
418
426
|
it('should update attributes', () => {
|
|
419
427
|
const setAttributes = vi.fn()
|
|
@@ -427,6 +435,7 @@ open coverage/index.html
|
|
|
427
435
|
### E2E Tests
|
|
428
436
|
|
|
429
437
|
1. **Login Before Each Test**
|
|
438
|
+
|
|
430
439
|
```typescript
|
|
431
440
|
test.beforeEach(async ({ page }) => {
|
|
432
441
|
await wpLogin(page)
|
|
@@ -434,6 +443,7 @@ open coverage/index.html
|
|
|
434
443
|
```
|
|
435
444
|
|
|
436
445
|
2. **Use Descriptive Test Names**
|
|
446
|
+
|
|
437
447
|
```typescript
|
|
438
448
|
test('should insert heading block and change level to H3', async ({ page }) => {
|
|
439
449
|
// ...
|
|
@@ -441,11 +451,13 @@ open coverage/index.html
|
|
|
441
451
|
```
|
|
442
452
|
|
|
443
453
|
3. **Wait for Network Idle**
|
|
454
|
+
|
|
444
455
|
```typescript
|
|
445
456
|
await page.waitForLoadState('networkidle')
|
|
446
457
|
```
|
|
447
458
|
|
|
448
459
|
4. **Take Screenshots on Failure**
|
|
460
|
+
|
|
449
461
|
```typescript
|
|
450
462
|
test('visual test', async ({ page }) => {
|
|
451
463
|
await page.screenshot({ path: 'screenshots/my-block.png' })
|
|
@@ -521,6 +533,7 @@ See the `examples/basic-theme/__tests__` and `examples/basic-theme/e2e` director
|
|
|
521
533
|
**Problem:** Tests fail with "wp is not defined"
|
|
522
534
|
|
|
523
535
|
**Solution:** Call `setupWordPressMocks()` before your tests:
|
|
536
|
+
|
|
524
537
|
```typescript
|
|
525
538
|
beforeAll(() => {
|
|
526
539
|
setupWordPressMocks()
|
|
@@ -532,6 +545,7 @@ beforeAll(() => {
|
|
|
532
545
|
**Problem:** Custom matchers like `toHaveBlockClass` are not recognized
|
|
533
546
|
|
|
534
547
|
**Solution:** Call `setupCustomMatchers()`:
|
|
548
|
+
|
|
535
549
|
```typescript
|
|
536
550
|
beforeAll(() => {
|
|
537
551
|
setupCustomMatchers()
|
|
@@ -543,6 +557,7 @@ beforeAll(() => {
|
|
|
543
557
|
**Problem:** E2E tests timeout
|
|
544
558
|
|
|
545
559
|
**Solution:** Increase timeout in `playwright.config.ts`:
|
|
560
|
+
|
|
546
561
|
```typescript
|
|
547
562
|
export default defineConfig({
|
|
548
563
|
timeout: 60 * 1000, // 60 seconds
|
|
@@ -554,6 +569,7 @@ export default defineConfig({
|
|
|
554
569
|
**Problem:** Block editor doesn't load in E2E tests
|
|
555
570
|
|
|
556
571
|
**Solution:** Ensure WordPress is running and the URL is correct:
|
|
572
|
+
|
|
557
573
|
```typescript
|
|
558
574
|
use: {
|
|
559
575
|
baseURL: process.env.WP_BASE_URL || 'http://localhost:8888',
|
package/dist/index.d.ts
CHANGED
|
@@ -3,11 +3,6 @@ export { RenderOptions } from '@testing-library/react';
|
|
|
3
3
|
import * as vitest from 'vitest';
|
|
4
4
|
import '@testing-library/user-event';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Block Testing Utilities
|
|
8
|
-
* Helper functions for testing Gutenberg blocks
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
6
|
/**
|
|
12
7
|
* Block Configuration Interface
|
|
13
8
|
*/
|
|
@@ -42,7 +37,7 @@ declare function createMockAttributes(blockType: string, overrides?: Record<stri
|
|
|
42
37
|
* Test block attribute validation
|
|
43
38
|
*/
|
|
44
39
|
declare function testBlockAttributes(EditComponent: React.ComponentType<any>, attributes: Record<string, any>): {
|
|
45
|
-
setAttributes: any
|
|
40
|
+
setAttributes: vitest.Mock<any, any>;
|
|
46
41
|
rerender: (newAttributes: Record<string, any>) => void;
|
|
47
42
|
};
|
|
48
43
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/utils/block-testing.ts
|
|
2
2
|
import { render } from "@testing-library/react";
|
|
3
3
|
import { createElement } from "react";
|
|
4
|
+
import { vi } from "vitest";
|
|
4
5
|
function renderBlockEdit(EditComponent, props = {}, options) {
|
|
5
6
|
const defaultProps = {
|
|
6
7
|
attributes: {},
|
|
@@ -136,9 +137,7 @@ var mockWordPressData = {
|
|
|
136
137
|
var mockWordPressi18n = {
|
|
137
138
|
__: vi2.fn((text) => text),
|
|
138
139
|
_x: vi2.fn((text) => text),
|
|
139
|
-
_n: vi2.fn(
|
|
140
|
-
(single, plural, number) => number === 1 ? single : plural
|
|
141
|
-
),
|
|
140
|
+
_n: vi2.fn((single, plural, number) => number === 1 ? single : plural),
|
|
142
141
|
sprintf: vi2.fn((format, ..._args) => format),
|
|
143
142
|
setLocaleData: vi2.fn()
|
|
144
143
|
};
|
|
@@ -244,9 +243,7 @@ function toHaveBlockAttributes(received, expectedAttributes) {
|
|
|
244
243
|
};
|
|
245
244
|
}
|
|
246
245
|
const actualAttributes = Object.keys(received.attributes);
|
|
247
|
-
const missingAttributes = expectedAttributes.filter(
|
|
248
|
-
(attr) => !actualAttributes.includes(attr)
|
|
249
|
-
);
|
|
246
|
+
const missingAttributes = expectedAttributes.filter((attr) => !actualAttributes.includes(attr));
|
|
250
247
|
const pass = missingAttributes.length === 0;
|
|
251
248
|
return {
|
|
252
249
|
pass,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/block-testing.ts","../src/mocks/wordpress.ts","../src/matchers/index.ts"],"sourcesContent":["/**\n * Block Testing Utilities\n * Helper functions for testing Gutenberg blocks\n */\n\nimport { render, RenderOptions } from '@testing-library/react'\nimport { createElement } from 'react'\n\n/**\n * Block Configuration Interface\n */\nexport interface BlockConfiguration {\n name?: string\n title?: string\n description?: string\n category?: string\n icon?: string | Record<string, any>\n keywords?: string[]\n attributes?: Record<string, any>\n supports?: Record<string, any>\n [key: string]: any\n}\n\n/**\n * Render a block's edit component for testing\n */\nexport function renderBlockEdit(\n EditComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n setAttributes: vi.fn(),\n isSelected: false,\n clientId: 'test-block-id',\n ...props,\n }\n\n return render(createElement(EditComponent, defaultProps), options)\n}\n\n/**\n * Render a block's save component for testing\n */\nexport function renderBlockSave(\n SaveComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n ...props,\n }\n\n return render(createElement(SaveComponent, defaultProps), options)\n}\n\n/**\n * Test if a block is registered correctly\n */\nexport function testBlockRegistration(\n blockName: string,\n expectedConfig: Partial<BlockConfiguration>\n) {\n const { getBlockType } = wp.blocks\n\n const blockType = getBlockType(blockName)\n\n if (!blockType) {\n throw new Error(`Block ${blockName} is not registered`)\n }\n\n // Check required fields\n expect(blockType.name).toBe(blockName)\n\n if (expectedConfig.title) {\n expect(blockType.title).toBe(expectedConfig.title)\n }\n\n if (expectedConfig.category) {\n expect(blockType.category).toBe(expectedConfig.category)\n }\n\n if (expectedConfig.attributes) {\n expect(blockType.attributes).toEqual(expectedConfig.attributes)\n }\n\n return blockType\n}\n\n/**\n * Create mock block attributes\n */\nexport function createMockAttributes(\n blockType: string,\n overrides: Record<string, any> = {}\n): Record<string, any> {\n const defaults: Record<string, Record<string, any>> = {\n 'core/paragraph': {\n content: 'Test paragraph content',\n align: 'left',\n },\n 'core/heading': {\n content: 'Test heading',\n level: 2,\n },\n 'core/image': {\n url: 'https://example.com/image.jpg',\n alt: 'Test image',\n id: 123,\n },\n }\n\n return {\n ...(defaults[blockType] || {}),\n ...overrides,\n }\n}\n\n/**\n * Test block attribute validation\n */\nexport function testBlockAttributes(\n EditComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const setAttributes = vi.fn()\n\n const { rerender } = renderBlockEdit(EditComponent, {\n attributes,\n setAttributes,\n })\n\n return {\n setAttributes,\n rerender: (newAttributes: Record<string, any>) => {\n return rerender(\n createElement(EditComponent, {\n attributes: newAttributes,\n setAttributes,\n })\n )\n },\n }\n}\n\n/**\n * Test block save output\n */\nexport function testBlockSaveOutput(\n SaveComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const { container } = renderBlockSave(SaveComponent, { attributes })\n return container.innerHTML\n}\n\n/**\n * Mock block context\n */\nexport function createMockBlockContext(overrides: any = {}) {\n return {\n clientId: 'mock-client-id',\n isSelected: false,\n isMultiSelected: false,\n hasSelectedInnerBlock: false,\n index: 0,\n ...overrides,\n }\n}\n\n/**\n * Wait for block to render\n */\nexport async function waitForBlockRender(\n callback: () => void,\n options: { timeout?: number } = {}\n) {\n const timeout = options.timeout || 1000\n const startTime = Date.now()\n\n while (Date.now() - startTime < timeout) {\n try {\n callback()\n return\n } catch (error) {\n await new Promise((resolve) => setTimeout(resolve, 50))\n }\n }\n\n throw new Error('Block failed to render within timeout')\n}\n","/**\n * WordPress API Mocks\n * Mock implementations of WordPress JavaScript APIs for testing\n */\n\nimport { vi } from 'vitest'\n\n/**\n * Mock @wordpress/blocks\n */\nexport const mockWordPressBlocks = {\n registerBlockType: vi.fn((name: string, settings: any) => {\n return { name, ...settings }\n }),\n unregisterBlockType: vi.fn(),\n getBlockType: vi.fn((name: string) => ({\n name,\n title: 'Mock Block',\n category: 'common',\n icon: 'block-default',\n attributes: {},\n })),\n getBlockTypes: vi.fn(() => []),\n hasBlockSupport: vi.fn(() => true),\n createBlock: vi.fn((name: string, attributes?: any) => ({\n name,\n attributes: attributes || {},\n clientId: 'mock-client-id',\n })),\n}\n\n/**\n * Mock @wordpress/data\n */\nexport const mockWordPressData = {\n useSelect: vi.fn((callback: any) => callback(() => ({}))),\n useDispatch: vi.fn((_storeName: string) => ({})),\n select: vi.fn((_storeName: string) => ({})),\n dispatch: vi.fn((_storeName: string) => ({})),\n subscribe: vi.fn(),\n registerStore: vi.fn(),\n}\n\n/**\n * Mock @wordpress/i18n\n */\nexport const mockWordPressi18n = {\n __: vi.fn((text: string) => text),\n _x: vi.fn((text: string) => text),\n _n: vi.fn((single: string, plural: string, number: number) =>\n number === 1 ? single : plural\n ),\n sprintf: vi.fn((format: string, ..._args: any[]) => format),\n setLocaleData: vi.fn(),\n}\n\n/**\n * Mock @wordpress/components\n */\nexport const mockWordPressComponents = {\n Button: vi.fn(({ children }: any) => children),\n TextControl: vi.fn(() => null),\n SelectControl: vi.fn(() => null),\n RangeControl: vi.fn(() => null),\n ToggleControl: vi.fn(() => null),\n Panel: vi.fn(({ children }: any) => children),\n PanelBody: vi.fn(({ children }: any) => children),\n PanelRow: vi.fn(({ children }: any) => children),\n}\n\n/**\n * Mock @wordpress/block-editor\n */\nexport const mockWordPressBlockEditor = {\n InspectorControls: vi.fn(({ children }: any) => children),\n BlockControls: vi.fn(({ children }: any) => children),\n useBlockProps: vi.fn((props?: any) => ({\n className: 'wp-block',\n ...props,\n })),\n RichText: vi.fn(() => null),\n MediaUpload: vi.fn(() => null),\n MediaUploadCheck: vi.fn(({ children }: any) => children),\n ColorPalette: vi.fn(() => null),\n URLInput: vi.fn(() => null),\n}\n\n/**\n * Mock @wordpress/element\n */\nexport const mockWordPressElement = {\n createElement: vi.fn((type: any, props?: any, ...children: any[]) => ({\n type,\n props: { ...props, children },\n })),\n Fragment: vi.fn(({ children }: any) => children),\n useState: vi.fn((initialState: any) => [initialState, vi.fn()]),\n useEffect: vi.fn((_effect: () => void, _deps?: any[]) => undefined),\n useRef: vi.fn((initialValue: any) => ({ current: initialValue })),\n useMemo: vi.fn((factory: () => any, _deps?: any[]) => factory()),\n useCallback: vi.fn((callback: (...args: any[]) => any, _deps?: any[]) => callback),\n}\n\n/**\n * Mock @wordpress/api-fetch\n */\nexport const mockWordPressApiFetch = vi.fn(\n (_options: { path: string; method?: string; data?: any }) =>\n Promise.resolve({ ok: true, json: () => Promise.resolve({}) })\n)\n\n/**\n * Setup all WordPress mocks\n */\nexport function setupWordPressMocks() {\n vi.mock('@wordpress/blocks', () => mockWordPressBlocks)\n vi.mock('@wordpress/data', () => mockWordPressData)\n vi.mock('@wordpress/i18n', () => mockWordPressi18n)\n vi.mock('@wordpress/components', () => mockWordPressComponents)\n vi.mock('@wordpress/block-editor', () => mockWordPressBlockEditor)\n vi.mock('@wordpress/element', () => mockWordPressElement)\n vi.mock('@wordpress/api-fetch', () => ({ default: mockWordPressApiFetch }))\n\n // Setup global WordPress objects\n ;(global as any).wp = {\n blocks: mockWordPressBlocks,\n data: mockWordPressData,\n i18n: mockWordPressi18n,\n components: mockWordPressComponents,\n blockEditor: mockWordPressBlockEditor,\n element: mockWordPressElement,\n apiFetch: mockWordPressApiFetch,\n }\n}\n\n/**\n * Clear all WordPress mocks\n */\nexport function clearWordPressMocks() {\n Object.values(mockWordPressBlocks).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n Object.values(mockWordPressData).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n // Clear other mocks...\n}\n","/**\n * Custom Vitest Matchers\n * WordPress-specific test matchers\n */\n\nimport { expect } from 'vitest'\n\n/**\n * Check if element has WordPress block class\n */\nexport function toHaveBlockClass(received: HTMLElement, blockName: string) {\n const className = `wp-block-${blockName.replace('/', '-')}`\n const pass = received.classList.contains(className)\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected element not to have block class ${className}`\n : `Expected element to have block class ${className}`,\n }\n}\n\n/**\n * Check if block is registered\n */\nexport function toBeRegisteredBlock(received: string) {\n const { getBlockType } = (global as any).wp?.blocks || {}\n\n if (!getBlockType) {\n return {\n pass: false,\n message: () => 'WordPress blocks API is not available',\n }\n }\n\n const blockType = getBlockType(received)\n const pass = blockType !== undefined\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block ${received} not to be registered`\n : `Expected block ${received} to be registered`,\n }\n}\n\n/**\n * Check if block has required attributes\n */\nexport function toHaveBlockAttributes(\n received: any,\n expectedAttributes: string[]\n) {\n if (!received || !received.attributes) {\n return {\n pass: false,\n message: () => 'Block does not have attributes property',\n }\n }\n\n const actualAttributes = Object.keys(received.attributes)\n const missingAttributes = expectedAttributes.filter(\n (attr) => !actualAttributes.includes(attr)\n )\n\n const pass = missingAttributes.length === 0\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block not to have attributes: ${expectedAttributes.join(', ')}`\n : `Expected block to have attributes: ${missingAttributes.join(', ')}`,\n }\n}\n\n/**\n * Check if element is a valid WordPress block\n */\nexport function toBeValidWordPressBlock(received: HTMLElement) {\n const hasDataType = received.hasAttribute('data-type')\n const hasWpBlock = received.classList.contains('wp-block') ||\n Array.from(received.classList).some(c => c.startsWith('wp-block-'))\n\n const pass = hasDataType && hasWpBlock\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected element not to be a valid WordPress block'\n : 'Expected element to be a valid WordPress block (must have data-type attribute and wp-block class)',\n }\n}\n\n/**\n * Check if string is valid block markup\n */\nexport function toBeValidBlockMarkup(received: string) {\n // Check for WordPress block comment syntax\n const hasStartComment = received.includes('<!-- wp:')\n const hasEndComment = received.includes('<!-- /wp:') || received.includes('/-->')\n\n const pass = hasStartComment && hasEndComment\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected string not to be valid block markup'\n : 'Expected string to be valid block markup (must include WordPress block comments)',\n }\n}\n\n/**\n * Extend Vitest matchers\n */\nexport function setupCustomMatchers() {\n expect.extend({\n toHaveBlockClass,\n toBeRegisteredBlock,\n toHaveBlockAttributes,\n toBeValidWordPressBlock,\n toBeValidBlockMarkup,\n })\n}\n\n// Type declarations for TypeScript\ndeclare global {\n namespace Vi {\n interface Matchers<R = any> {\n toHaveBlockClass(blockName: string): R\n toBeRegisteredBlock(): R\n toHaveBlockAttributes(attributes: string[]): R\n toBeValidWordPressBlock(): R\n toBeValidBlockMarkup(): R\n }\n }\n}\n"],"mappings":"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","names":["vi","expect"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/block-testing.ts","../src/mocks/wordpress.ts","../src/matchers/index.ts"],"sourcesContent":["/**\n * Block Testing Utilities\n * Helper functions for testing Gutenberg blocks\n */\n\nimport { render, RenderOptions } from '@testing-library/react'\nimport { createElement } from 'react'\nimport { vi } from 'vitest'\n\n/**\n * Block Configuration Interface\n */\nexport interface BlockConfiguration {\n name?: string\n title?: string\n description?: string\n category?: string\n icon?: string | Record<string, any>\n keywords?: string[]\n attributes?: Record<string, any>\n supports?: Record<string, any>\n [key: string]: any\n}\n\n/**\n * Render a block's edit component for testing\n */\nexport function renderBlockEdit(\n EditComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n setAttributes: vi.fn(),\n isSelected: false,\n clientId: 'test-block-id',\n ...props,\n }\n\n return render(createElement(EditComponent, defaultProps), options)\n}\n\n/**\n * Render a block's save component for testing\n */\nexport function renderBlockSave(\n SaveComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n ...props,\n }\n\n return render(createElement(SaveComponent, defaultProps), options)\n}\n\n/**\n * Test if a block is registered correctly\n */\nexport function testBlockRegistration(\n blockName: string,\n expectedConfig: Partial<BlockConfiguration>\n) {\n const { getBlockType } = wp.blocks\n\n const blockType = getBlockType(blockName)\n\n if (!blockType) {\n throw new Error(`Block ${blockName} is not registered`)\n }\n\n // Check required fields\n expect(blockType.name).toBe(blockName)\n\n if (expectedConfig.title) {\n expect(blockType.title).toBe(expectedConfig.title)\n }\n\n if (expectedConfig.category) {\n expect(blockType.category).toBe(expectedConfig.category)\n }\n\n if (expectedConfig.attributes) {\n expect(blockType.attributes).toEqual(expectedConfig.attributes)\n }\n\n return blockType\n}\n\n/**\n * Create mock block attributes\n */\nexport function createMockAttributes(\n blockType: string,\n overrides: Record<string, any> = {}\n): Record<string, any> {\n const defaults: Record<string, Record<string, any>> = {\n 'core/paragraph': {\n content: 'Test paragraph content',\n align: 'left',\n },\n 'core/heading': {\n content: 'Test heading',\n level: 2,\n },\n 'core/image': {\n url: 'https://example.com/image.jpg',\n alt: 'Test image',\n id: 123,\n },\n }\n\n return {\n ...(defaults[blockType] || {}),\n ...overrides,\n }\n}\n\n/**\n * Test block attribute validation\n */\nexport function testBlockAttributes(\n EditComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const setAttributes = vi.fn()\n\n const { rerender } = renderBlockEdit(EditComponent, {\n attributes,\n setAttributes,\n })\n\n return {\n setAttributes,\n rerender: (newAttributes: Record<string, any>) => {\n return rerender(\n createElement(EditComponent, {\n attributes: newAttributes,\n setAttributes,\n })\n )\n },\n }\n}\n\n/**\n * Test block save output\n */\nexport function testBlockSaveOutput(\n SaveComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const { container } = renderBlockSave(SaveComponent, { attributes })\n return container.innerHTML\n}\n\n/**\n * Mock block context\n */\nexport function createMockBlockContext(overrides: any = {}) {\n return {\n clientId: 'mock-client-id',\n isSelected: false,\n isMultiSelected: false,\n hasSelectedInnerBlock: false,\n index: 0,\n ...overrides,\n }\n}\n\n/**\n * Wait for block to render\n */\nexport async function waitForBlockRender(callback: () => void, options: { timeout?: number } = {}) {\n const timeout = options.timeout || 1000\n const startTime = Date.now()\n\n while (Date.now() - startTime < timeout) {\n try {\n callback()\n return\n } catch (error) {\n await new Promise((resolve) => setTimeout(resolve, 50))\n }\n }\n\n throw new Error('Block failed to render within timeout')\n}\n","/**\n * WordPress API Mocks\n * Mock implementations of WordPress JavaScript APIs for testing\n */\n\nimport { vi } from 'vitest'\n\n/**\n * Mock @wordpress/blocks\n */\nexport const mockWordPressBlocks = {\n registerBlockType: vi.fn((name: string, settings: any) => {\n return { name, ...settings }\n }),\n unregisterBlockType: vi.fn(),\n getBlockType: vi.fn((name: string) => ({\n name,\n title: 'Mock Block',\n category: 'common',\n icon: 'block-default',\n attributes: {},\n })),\n getBlockTypes: vi.fn(() => []),\n hasBlockSupport: vi.fn(() => true),\n createBlock: vi.fn((name: string, attributes?: any) => ({\n name,\n attributes: attributes || {},\n clientId: 'mock-client-id',\n })),\n}\n\n/**\n * Mock @wordpress/data\n */\nexport const mockWordPressData = {\n useSelect: vi.fn((callback: any) => callback(() => ({}))),\n useDispatch: vi.fn((_storeName: string) => ({})),\n select: vi.fn((_storeName: string) => ({})),\n dispatch: vi.fn((_storeName: string) => ({})),\n subscribe: vi.fn(),\n registerStore: vi.fn(),\n}\n\n/**\n * Mock @wordpress/i18n\n */\nexport const mockWordPressi18n = {\n __: vi.fn((text: string) => text),\n _x: vi.fn((text: string) => text),\n _n: vi.fn((single: string, plural: string, number: number) => (number === 1 ? single : plural)),\n sprintf: vi.fn((format: string, ..._args: any[]) => format),\n setLocaleData: vi.fn(),\n}\n\n/**\n * Mock @wordpress/components\n */\nexport const mockWordPressComponents = {\n Button: vi.fn(({ children }: any) => children),\n TextControl: vi.fn(() => null),\n SelectControl: vi.fn(() => null),\n RangeControl: vi.fn(() => null),\n ToggleControl: vi.fn(() => null),\n Panel: vi.fn(({ children }: any) => children),\n PanelBody: vi.fn(({ children }: any) => children),\n PanelRow: vi.fn(({ children }: any) => children),\n}\n\n/**\n * Mock @wordpress/block-editor\n */\nexport const mockWordPressBlockEditor = {\n InspectorControls: vi.fn(({ children }: any) => children),\n BlockControls: vi.fn(({ children }: any) => children),\n useBlockProps: vi.fn((props?: any) => ({\n className: 'wp-block',\n ...props,\n })),\n RichText: vi.fn(() => null),\n MediaUpload: vi.fn(() => null),\n MediaUploadCheck: vi.fn(({ children }: any) => children),\n ColorPalette: vi.fn(() => null),\n URLInput: vi.fn(() => null),\n}\n\n/**\n * Mock @wordpress/element\n */\nexport const mockWordPressElement = {\n createElement: vi.fn((type: any, props?: any, ...children: any[]) => ({\n type,\n props: { ...props, children },\n })),\n Fragment: vi.fn(({ children }: any) => children),\n useState: vi.fn((initialState: any) => [initialState, vi.fn()]),\n useEffect: vi.fn((_effect: () => void, _deps?: any[]) => undefined),\n useRef: vi.fn((initialValue: any) => ({ current: initialValue })),\n useMemo: vi.fn((factory: () => any, _deps?: any[]) => factory()),\n useCallback: vi.fn((callback: (...args: any[]) => any, _deps?: any[]) => callback),\n}\n\n/**\n * Mock @wordpress/api-fetch\n */\nexport const mockWordPressApiFetch = vi.fn(\n (_options: { path: string; method?: string; data?: any }) =>\n Promise.resolve({ ok: true, json: () => Promise.resolve({}) })\n)\n\n/**\n * Setup all WordPress mocks\n */\nexport function setupWordPressMocks() {\n vi.mock('@wordpress/blocks', () => mockWordPressBlocks)\n vi.mock('@wordpress/data', () => mockWordPressData)\n vi.mock('@wordpress/i18n', () => mockWordPressi18n)\n vi.mock('@wordpress/components', () => mockWordPressComponents)\n vi.mock('@wordpress/block-editor', () => mockWordPressBlockEditor)\n vi.mock('@wordpress/element', () => mockWordPressElement)\n vi.mock('@wordpress/api-fetch', () => ({ default: mockWordPressApiFetch }))\n\n // Setup global WordPress objects\n ;(global as any).wp = {\n blocks: mockWordPressBlocks,\n data: mockWordPressData,\n i18n: mockWordPressi18n,\n components: mockWordPressComponents,\n blockEditor: mockWordPressBlockEditor,\n element: mockWordPressElement,\n apiFetch: mockWordPressApiFetch,\n }\n}\n\n/**\n * Clear all WordPress mocks\n */\nexport function clearWordPressMocks() {\n Object.values(mockWordPressBlocks).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n Object.values(mockWordPressData).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n // Clear other mocks...\n}\n","/**\n * Custom Vitest Matchers\n * WordPress-specific test matchers\n */\n\nimport { expect } from 'vitest'\n\n/**\n * Check if element has WordPress block class\n */\nexport function toHaveBlockClass(received: HTMLElement, blockName: string) {\n const className = `wp-block-${blockName.replace('/', '-')}`\n const pass = received.classList.contains(className)\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected element not to have block class ${className}`\n : `Expected element to have block class ${className}`,\n }\n}\n\n/**\n * Check if block is registered\n */\nexport function toBeRegisteredBlock(received: string) {\n const { getBlockType } = (global as any).wp?.blocks || {}\n\n if (!getBlockType) {\n return {\n pass: false,\n message: () => 'WordPress blocks API is not available',\n }\n }\n\n const blockType = getBlockType(received)\n const pass = blockType !== undefined\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block ${received} not to be registered`\n : `Expected block ${received} to be registered`,\n }\n}\n\n/**\n * Check if block has required attributes\n */\nexport function toHaveBlockAttributes(received: any, expectedAttributes: string[]) {\n if (!received || !received.attributes) {\n return {\n pass: false,\n message: () => 'Block does not have attributes property',\n }\n }\n\n const actualAttributes = Object.keys(received.attributes)\n const missingAttributes = expectedAttributes.filter((attr) => !actualAttributes.includes(attr))\n\n const pass = missingAttributes.length === 0\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block not to have attributes: ${expectedAttributes.join(', ')}`\n : `Expected block to have attributes: ${missingAttributes.join(', ')}`,\n }\n}\n\n/**\n * Check if element is a valid WordPress block\n */\nexport function toBeValidWordPressBlock(received: HTMLElement) {\n const hasDataType = received.hasAttribute('data-type')\n const hasWpBlock =\n received.classList.contains('wp-block') ||\n Array.from(received.classList).some((c) => c.startsWith('wp-block-'))\n\n const pass = hasDataType && hasWpBlock\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected element not to be a valid WordPress block'\n : 'Expected element to be a valid WordPress block (must have data-type attribute and wp-block class)',\n }\n}\n\n/**\n * Check if string is valid block markup\n */\nexport function toBeValidBlockMarkup(received: string) {\n // Check for WordPress block comment syntax\n const hasStartComment = received.includes('<!-- wp:')\n const hasEndComment = received.includes('<!-- /wp:') || received.includes('/-->')\n\n const pass = hasStartComment && hasEndComment\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected string not to be valid block markup'\n : 'Expected string to be valid block markup (must include WordPress block comments)',\n }\n}\n\n/**\n * Extend Vitest matchers\n */\nexport function setupCustomMatchers() {\n expect.extend({\n toHaveBlockClass,\n toBeRegisteredBlock,\n toHaveBlockAttributes,\n toBeValidWordPressBlock,\n toBeValidBlockMarkup,\n })\n}\n\n// Type declarations for TypeScript — namespace Vi is required by Vitest's type augmentation API\n/* eslint-disable @typescript-eslint/no-namespace */\ndeclare global {\n namespace Vi {\n interface Matchers<R = any> {\n toHaveBlockClass(blockName: string): R\n toBeRegisteredBlock(): R\n toHaveBlockAttributes(attributes: string[]): R\n toBeValidWordPressBlock(): R\n toBeValidBlockMarkup(): R\n }\n }\n}\n/* eslint-enable @typescript-eslint/no-namespace */\n"],"mappings":";AAKA,SAAS,cAA6B;AACtC,SAAS,qBAAqB;AAC9B,SAAS,UAAU;AAoBZ,SAAS,gBACd,eACA,QAAa,CAAC,GACd,SAC2B;AAC3B,QAAM,eAAe;AAAA,IACnB,YAAY,CAAC;AAAA,IACb,eAAe,GAAG,GAAG;AAAA,IACrB,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,GAAG;AAAA,EACL;AAEA,SAAO,OAAO,cAAc,eAAe,YAAY,GAAG,OAAO;AACnE;AAKO,SAAS,gBACd,eACA,QAAa,CAAC,GACd,SAC2B;AAC3B,QAAM,eAAe;AAAA,IACnB,YAAY,CAAC;AAAA,IACb,GAAG;AAAA,EACL;AAEA,SAAO,OAAO,cAAc,eAAe,YAAY,GAAG,OAAO;AACnE;AAKO,SAAS,sBACd,WACA,gBACA;AACA,QAAM,EAAE,aAAa,IAAI,GAAG;AAE5B,QAAM,YAAY,aAAa,SAAS;AAExC,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,SAAS,SAAS,oBAAoB;AAAA,EACxD;AAGA,SAAO,UAAU,IAAI,EAAE,KAAK,SAAS;AAErC,MAAI,eAAe,OAAO;AACxB,WAAO,UAAU,KAAK,EAAE,KAAK,eAAe,KAAK;AAAA,EACnD;AAEA,MAAI,eAAe,UAAU;AAC3B,WAAO,UAAU,QAAQ,EAAE,KAAK,eAAe,QAAQ;AAAA,EACzD;AAEA,MAAI,eAAe,YAAY;AAC7B,WAAO,UAAU,UAAU,EAAE,QAAQ,eAAe,UAAU;AAAA,EAChE;AAEA,SAAO;AACT;AAKO,SAAS,qBACd,WACA,YAAiC,CAAC,GACb;AACrB,QAAM,WAAgD;AAAA,IACpD,kBAAkB;AAAA,MAChB,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,IACA,gBAAgB;AAAA,MACd,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,IACA,cAAc;AAAA,MACZ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAI,SAAS,SAAS,KAAK,CAAC;AAAA,IAC5B,GAAG;AAAA,EACL;AACF;AAKO,SAAS,oBACd,eACA,YACA;AACA,QAAM,gBAAgB,GAAG,GAAG;AAE5B,QAAM,EAAE,SAAS,IAAI,gBAAgB,eAAe;AAAA,IAClD;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,UAAU,CAAC,kBAAuC;AAChD,aAAO;AAAA,QACL,cAAc,eAAe;AAAA,UAC3B,YAAY;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,oBACd,eACA,YACA;AACA,QAAM,EAAE,UAAU,IAAI,gBAAgB,eAAe,EAAE,WAAW,CAAC;AACnE,SAAO,UAAU;AACnB;AAKO,SAAS,uBAAuB,YAAiB,CAAC,GAAG;AAC1D,SAAO;AAAA,IACL,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,uBAAuB;AAAA,IACvB,OAAO;AAAA,IACP,GAAG;AAAA,EACL;AACF;AAKA,eAAsB,mBAAmB,UAAsB,UAAgC,CAAC,GAAG;AACjG,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,YAAY,KAAK,IAAI;AAE3B,SAAO,KAAK,IAAI,IAAI,YAAY,SAAS;AACvC,QAAI;AACF,eAAS;AACT;AAAA,IACF,SAAS,OAAO;AACd,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,uCAAuC;AACzD;;;ACzLA,SAAS,MAAAA,WAAU;AAKZ,IAAM,sBAAsB;AAAA,EACjC,mBAAmBA,IAAG,GAAG,CAAC,MAAc,aAAkB;AACxD,WAAO,EAAE,MAAM,GAAG,SAAS;AAAA,EAC7B,CAAC;AAAA,EACD,qBAAqBA,IAAG,GAAG;AAAA,EAC3B,cAAcA,IAAG,GAAG,CAAC,UAAkB;AAAA,IACrC;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV,MAAM;AAAA,IACN,YAAY,CAAC;AAAA,EACf,EAAE;AAAA,EACF,eAAeA,IAAG,GAAG,MAAM,CAAC,CAAC;AAAA,EAC7B,iBAAiBA,IAAG,GAAG,MAAM,IAAI;AAAA,EACjC,aAAaA,IAAG,GAAG,CAAC,MAAc,gBAAsB;AAAA,IACtD;AAAA,IACA,YAAY,cAAc,CAAC;AAAA,IAC3B,UAAU;AAAA,EACZ,EAAE;AACJ;AAKO,IAAM,oBAAoB;AAAA,EAC/B,WAAWA,IAAG,GAAG,CAAC,aAAkB,SAAS,OAAO,CAAC,EAAE,CAAC;AAAA,EACxD,aAAaA,IAAG,GAAG,CAAC,gBAAwB,CAAC,EAAE;AAAA,EAC/C,QAAQA,IAAG,GAAG,CAAC,gBAAwB,CAAC,EAAE;AAAA,EAC1C,UAAUA,IAAG,GAAG,CAAC,gBAAwB,CAAC,EAAE;AAAA,EAC5C,WAAWA,IAAG,GAAG;AAAA,EACjB,eAAeA,IAAG,GAAG;AACvB;AAKO,IAAM,oBAAoB;AAAA,EAC/B,IAAIA,IAAG,GAAG,CAAC,SAAiB,IAAI;AAAA,EAChC,IAAIA,IAAG,GAAG,CAAC,SAAiB,IAAI;AAAA,EAChC,IAAIA,IAAG,GAAG,CAAC,QAAgB,QAAgB,WAAoB,WAAW,IAAI,SAAS,MAAO;AAAA,EAC9F,SAASA,IAAG,GAAG,CAAC,WAAmB,UAAiB,MAAM;AAAA,EAC1D,eAAeA,IAAG,GAAG;AACvB;AAKO,IAAM,0BAA0B;AAAA,EACrC,QAAQA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AAAA,EAC7C,aAAaA,IAAG,GAAG,MAAM,IAAI;AAAA,EAC7B,eAAeA,IAAG,GAAG,MAAM,IAAI;AAAA,EAC/B,cAAcA,IAAG,GAAG,MAAM,IAAI;AAAA,EAC9B,eAAeA,IAAG,GAAG,MAAM,IAAI;AAAA,EAC/B,OAAOA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AAAA,EAC5C,WAAWA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AAAA,EAChD,UAAUA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AACjD;AAKO,IAAM,2BAA2B;AAAA,EACtC,mBAAmBA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AAAA,EACxD,eAAeA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AAAA,EACpD,eAAeA,IAAG,GAAG,CAAC,WAAiB;AAAA,IACrC,WAAW;AAAA,IACX,GAAG;AAAA,EACL,EAAE;AAAA,EACF,UAAUA,IAAG,GAAG,MAAM,IAAI;AAAA,EAC1B,aAAaA,IAAG,GAAG,MAAM,IAAI;AAAA,EAC7B,kBAAkBA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AAAA,EACvD,cAAcA,IAAG,GAAG,MAAM,IAAI;AAAA,EAC9B,UAAUA,IAAG,GAAG,MAAM,IAAI;AAC5B;AAKO,IAAM,uBAAuB;AAAA,EAClC,eAAeA,IAAG,GAAG,CAAC,MAAW,UAAgB,cAAqB;AAAA,IACpE;AAAA,IACA,OAAO,EAAE,GAAG,OAAO,SAAS;AAAA,EAC9B,EAAE;AAAA,EACF,UAAUA,IAAG,GAAG,CAAC,EAAE,SAAS,MAAW,QAAQ;AAAA,EAC/C,UAAUA,IAAG,GAAG,CAAC,iBAAsB,CAAC,cAAcA,IAAG,GAAG,CAAC,CAAC;AAAA,EAC9D,WAAWA,IAAG,GAAG,CAAC,SAAqB,UAAkB,MAAS;AAAA,EAClE,QAAQA,IAAG,GAAG,CAAC,kBAAuB,EAAE,SAAS,aAAa,EAAE;AAAA,EAChE,SAASA,IAAG,GAAG,CAAC,SAAoB,UAAkB,QAAQ,CAAC;AAAA,EAC/D,aAAaA,IAAG,GAAG,CAAC,UAAmC,UAAkB,QAAQ;AACnF;AAKO,IAAM,wBAAwBA,IAAG;AAAA,EACtC,CAAC,aACC,QAAQ,QAAQ,EAAE,IAAI,MAAM,MAAM,MAAM,QAAQ,QAAQ,CAAC,CAAC,EAAE,CAAC;AACjE;AAKO,SAAS,sBAAsB;AACpC,EAAAA,IAAG,KAAK,qBAAqB,MAAM,mBAAmB;AACtD,EAAAA,IAAG,KAAK,mBAAmB,MAAM,iBAAiB;AAClD,EAAAA,IAAG,KAAK,mBAAmB,MAAM,iBAAiB;AAClD,EAAAA,IAAG,KAAK,yBAAyB,MAAM,uBAAuB;AAC9D,EAAAA,IAAG,KAAK,2BAA2B,MAAM,wBAAwB;AACjE,EAAAA,IAAG,KAAK,sBAAsB,MAAM,oBAAoB;AACxD,EAAAA,IAAG,KAAK,wBAAwB,OAAO,EAAE,SAAS,sBAAsB,EAAE;AAGzE,EAAC,OAAe,KAAK;AAAA,IACpB,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAKO,SAAS,sBAAsB;AACpC,SAAO,OAAO,mBAAmB,EAAE,QAAQ,CAAC,SAAc;AACxD,QAAI,OAAO,MAAM,cAAc,YAAY;AACzC,WAAK,UAAU;AAAA,IACjB;AAAA,EACF,CAAC;AAED,SAAO,OAAO,iBAAiB,EAAE,QAAQ,CAAC,SAAc;AACtD,QAAI,OAAO,MAAM,cAAc,YAAY;AACzC,WAAK,UAAU;AAAA,IACjB;AAAA,EACF,CAAC;AAGH;;;ACjJA,SAAS,UAAAC,eAAc;AAKhB,SAAS,iBAAiB,UAAuB,WAAmB;AACzE,QAAM,YAAY,YAAY,UAAU,QAAQ,KAAK,GAAG,CAAC;AACzD,QAAM,OAAO,SAAS,UAAU,SAAS,SAAS;AAElD,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MACP,OACI,4CAA4C,SAAS,KACrD,wCAAwC,SAAS;AAAA,EACzD;AACF;AAKO,SAAS,oBAAoB,UAAkB;AACpD,QAAM,EAAE,aAAa,IAAK,OAAe,IAAI,UAAU,CAAC;AAExD,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,MAAM;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,YAAY,aAAa,QAAQ;AACvC,QAAM,OAAO,cAAc;AAE3B,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MACP,OACI,kBAAkB,QAAQ,0BAC1B,kBAAkB,QAAQ;AAAA,EAClC;AACF;AAKO,SAAS,sBAAsB,UAAe,oBAA8B;AACjF,MAAI,CAAC,YAAY,CAAC,SAAS,YAAY;AACrC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS,MAAM;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,mBAAmB,OAAO,KAAK,SAAS,UAAU;AACxD,QAAM,oBAAoB,mBAAmB,OAAO,CAAC,SAAS,CAAC,iBAAiB,SAAS,IAAI,CAAC;AAE9F,QAAM,OAAO,kBAAkB,WAAW;AAE1C,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MACP,OACI,0CAA0C,mBAAmB,KAAK,IAAI,CAAC,KACvE,sCAAsC,kBAAkB,KAAK,IAAI,CAAC;AAAA,EAC1E;AACF;AAKO,SAAS,wBAAwB,UAAuB;AAC7D,QAAM,cAAc,SAAS,aAAa,WAAW;AACrD,QAAM,aACJ,SAAS,UAAU,SAAS,UAAU,KACtC,MAAM,KAAK,SAAS,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,WAAW,CAAC;AAEtE,QAAM,OAAO,eAAe;AAE5B,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MACP,OACI,uDACA;AAAA,EACR;AACF;AAKO,SAAS,qBAAqB,UAAkB;AAErD,QAAM,kBAAkB,SAAS,SAAS,UAAU;AACpD,QAAM,gBAAgB,SAAS,SAAS,WAAW,KAAK,SAAS,SAAS,MAAM;AAEhF,QAAM,OAAO,mBAAmB;AAEhC,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MACP,OACI,iDACA;AAAA,EACR;AACF;AAKO,SAAS,sBAAsB;AACpC,EAAAA,QAAO,OAAO;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;","names":["vi","expect"]}
|
package/dist/playwright.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Page, Locator } from '@playwright/test';
|
|
2
2
|
export { Browser, BrowserContext, Locator, Page, expect, test } from '@playwright/test';
|
|
3
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Playwright E2E Testing Helpers
|
|
@@ -95,4 +96,9 @@ declare function takeA11yScreenshot(page: Page, name: string): Promise<void>;
|
|
|
95
96
|
*/
|
|
96
97
|
declare function setupConsoleErrorTracking(page: Page): string[];
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
/** WCAG 2.1 A/AA tag set for the blocking gate. */
|
|
100
|
+
declare const WCAG_AA_TAGS: readonly ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"];
|
|
101
|
+
/** axe scanner scoped to WCAG 2.1 A/AA, excluding the wp-env admin bar. */
|
|
102
|
+
declare function makeAxeBuilder(page: any): AxeBuilder;
|
|
103
|
+
|
|
104
|
+
export { WCAG_AA_TAGS, blockExists, deleteBlock, getBlock, getBlockCount, getSelectedBlock, goToWPAdmin, insertBlock, isLoggedIn, makeAxeBuilder, moveBlockDown, moveBlockUp, openBlockEditor, previewPost, publishPost, saveDraft, selectBlock, setupConsoleErrorTracking, takeA11yScreenshot, typeInBlock, updateBlockAttribute, waitForWPAdmin, wpLogin, wpLogout };
|
package/dist/playwright.js
CHANGED
|
@@ -114,7 +114,15 @@ function setupConsoleErrorTracking(page) {
|
|
|
114
114
|
|
|
115
115
|
// src/playwright.ts
|
|
116
116
|
import { test, expect } from "@playwright/test";
|
|
117
|
+
|
|
118
|
+
// src/axe.ts
|
|
119
|
+
import AxeBuilder from "@axe-core/playwright";
|
|
120
|
+
var WCAG_AA_TAGS = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"];
|
|
121
|
+
function makeAxeBuilder(page) {
|
|
122
|
+
return new AxeBuilder({ page }).withTags([...WCAG_AA_TAGS]).exclude("#wpadminbar");
|
|
123
|
+
}
|
|
117
124
|
export {
|
|
125
|
+
WCAG_AA_TAGS,
|
|
118
126
|
blockExists,
|
|
119
127
|
deleteBlock,
|
|
120
128
|
expect,
|
|
@@ -124,6 +132,7 @@ export {
|
|
|
124
132
|
goToWPAdmin,
|
|
125
133
|
insertBlock,
|
|
126
134
|
isLoggedIn,
|
|
135
|
+
makeAxeBuilder,
|
|
127
136
|
moveBlockDown,
|
|
128
137
|
moveBlockUp,
|
|
129
138
|
openBlockEditor,
|
package/dist/playwright.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/playwright-helpers.ts","../src/playwright.ts"],"sourcesContent":["/**\n * Playwright E2E Testing Helpers\n * Helper functions for WordPress E2E testing with Playwright\n */\n\nimport type { Page, Locator } from '@playwright/test'\n\n/**\n * WordPress login helper\n */\nexport async function wpLogin(\n page: Page,\n username: string = 'admin',\n password: string = 'password'\n) {\n await page.goto('/wp-login.php')\n await page.fill('#user_login', username)\n await page.fill('#user_pass', password)\n await page.click('#wp-submit')\n await page.waitForLoadState('networkidle')\n}\n\n/**\n * WordPress logout helper\n */\nexport async function wpLogout(page: Page) {\n await page.goto('/wp-login.php?action=logout')\n await page.click('a') // Confirm logout\n await page.waitForLoadState('networkidle')\n}\n\n/**\n * Navigate to block editor\n */\nexport async function openBlockEditor(page: Page, postType: string = 'post') {\n await page.goto(`/wp-admin/post-new.php?post_type=${postType}`)\n await page.waitForSelector('.block-editor')\n}\n\n/**\n * Insert a block in the editor\n */\nexport async function insertBlock(page: Page, blockName: string) {\n // Click the block inserter\n await page.click('.block-editor-inserter__toggle')\n\n // Search for the block\n await page.fill('.block-editor-inserter__search input', blockName)\n\n // Click the block\n await page.click(`button[aria-label*=\"${blockName}\"]`)\n\n // Wait for block to be inserted\n await page.waitForSelector(`[data-type*=\"${blockName}\"]`)\n}\n\n/**\n * Get block by type\n */\nexport function getBlock(page: Page, blockType: string): Locator {\n return page.locator(`[data-type=\"${blockType}\"]`)\n}\n\n/**\n * Get selected block\n */\nexport function getSelectedBlock(page: Page): Locator {\n return page.locator('.is-selected')\n}\n\n/**\n * Type in block\n */\nexport async function typeInBlock(page: Page, blockType: string, text: string) {\n const block = getBlock(page, blockType)\n await block.click()\n await block.type(text)\n}\n\n/**\n * Update block attribute via inspector\n */\nexport async function updateBlockAttribute(\n page: Page,\n attributeName: string,\n value: string | number | boolean\n) {\n // Open inspector if not open\n const inspectorButton = page.locator('button[aria-label=\"Settings\"]')\n if (await inspectorButton.isVisible()) {\n await inspectorButton.click()\n }\n\n // Find and update the control\n const control = page.locator(`[aria-label*=\"${attributeName}\"]`)\n await control.fill(String(value))\n}\n\n/**\n * Publish post\n */\nexport async function publishPost(page: Page) {\n const publishButton = page.locator('.editor-post-publish-button__button')\n\n // Click publish panel button\n await publishButton.first().click()\n\n // Click final publish button\n await publishButton.last().click()\n\n // Wait for success notice\n await page.waitForSelector('.components-snackbar')\n}\n\n/**\n * Save draft\n */\nexport async function saveDraft(page: Page) {\n await page.click('.editor-post-save-draft')\n await page.waitForSelector('.is-saved')\n}\n\n/**\n * Preview post\n */\nexport async function previewPost(page: Page): Promise<Page> {\n const [previewPage] = await Promise.all([\n page.context().waitForEvent('page'),\n page.click('.editor-post-preview'),\n ])\n\n await previewPage.waitForLoadState('networkidle')\n return previewPage\n}\n\n/**\n * Check if block exists\n */\nexport async function blockExists(page: Page, blockType: string): Promise<boolean> {\n return (await getBlock(page, blockType).count()) > 0\n}\n\n/**\n * Get block count\n */\nexport async function getBlockCount(page: Page, blockType?: string): Promise<number> {\n if (blockType) {\n return await getBlock(page, blockType).count()\n }\n return await page.locator('[data-type]').count()\n}\n\n/**\n * Select block\n */\nexport async function selectBlock(page: Page, blockType: string) {\n await getBlock(page, blockType).click()\n}\n\n/**\n * Delete block\n */\nexport async function deleteBlock(page: Page, blockType: string) {\n await selectBlock(page, blockType)\n await page.keyboard.press('Backspace')\n}\n\n/**\n * Move block up\n */\nexport async function moveBlockUp(page: Page, blockType: string) {\n await selectBlock(page, blockType)\n await page.click('button[aria-label=\"Move up\"]')\n}\n\n/**\n * Move block down\n */\nexport async function moveBlockDown(page: Page, blockType: string) {\n await selectBlock(page, blockType)\n await page.click('button[aria-label=\"Move down\"]')\n}\n\n/**\n * Wait for WordPress admin\n */\nexport async function waitForWPAdmin(page: Page) {\n await page.waitForSelector('#wpadminbar')\n}\n\n/**\n * Navigate to WordPress admin page\n */\nexport async function goToWPAdmin(page: Page, path: string = '') {\n await page.goto(`/wp-admin/${path}`)\n await waitForWPAdmin(page)\n}\n\n/**\n * Check if user is logged in\n */\nexport async function isLoggedIn(page: Page): Promise<boolean> {\n await page.goto('/wp-admin/')\n return page.url().includes('/wp-admin/')\n}\n\n/**\n * Take accessible screenshot\n */\nexport async function takeA11yScreenshot(page: Page, name: string) {\n await page.screenshot({\n path: `screenshots/${name}.png`,\n fullPage: true,\n })\n}\n\n/**\n * Check for console errors\n */\nexport function setupConsoleErrorTracking(page: Page): string[] {\n const errors: string[] = []\n\n page.on('console', (msg) => {\n if (msg.type() === 'error') {\n errors.push(msg.text())\n }\n })\n\n page.on('pageerror', (error) => {\n errors.push(error.message)\n })\n\n return errors\n}\n","/**\n * Playwright E2E Testing Utilities\n * Re-export everything needed for Playwright tests\n */\n\nexport * from './utils/playwright-helpers'\n\n// Re-export Playwright test utilities\nexport { test, expect } from '@playwright/test'\nexport type { Page, Locator, Browser, BrowserContext } from '@playwright/test'\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/playwright-helpers.ts","../src/playwright.ts","../src/axe.ts"],"sourcesContent":["/**\n * Playwright E2E Testing Helpers\n * Helper functions for WordPress E2E testing with Playwright\n */\n\nimport type { Page, Locator } from '@playwright/test'\n\n/**\n * WordPress login helper\n */\nexport async function wpLogin(\n page: Page,\n username: string = 'admin',\n password: string = 'password'\n) {\n await page.goto('/wp-login.php')\n await page.fill('#user_login', username)\n await page.fill('#user_pass', password)\n await page.click('#wp-submit')\n await page.waitForLoadState('networkidle')\n}\n\n/**\n * WordPress logout helper\n */\nexport async function wpLogout(page: Page) {\n await page.goto('/wp-login.php?action=logout')\n await page.click('a') // Confirm logout\n await page.waitForLoadState('networkidle')\n}\n\n/**\n * Navigate to block editor\n */\nexport async function openBlockEditor(page: Page, postType: string = 'post') {\n await page.goto(`/wp-admin/post-new.php?post_type=${postType}`)\n await page.waitForSelector('.block-editor')\n}\n\n/**\n * Insert a block in the editor\n */\nexport async function insertBlock(page: Page, blockName: string) {\n // Click the block inserter\n await page.click('.block-editor-inserter__toggle')\n\n // Search for the block\n await page.fill('.block-editor-inserter__search input', blockName)\n\n // Click the block\n await page.click(`button[aria-label*=\"${blockName}\"]`)\n\n // Wait for block to be inserted\n await page.waitForSelector(`[data-type*=\"${blockName}\"]`)\n}\n\n/**\n * Get block by type\n */\nexport function getBlock(page: Page, blockType: string): Locator {\n return page.locator(`[data-type=\"${blockType}\"]`)\n}\n\n/**\n * Get selected block\n */\nexport function getSelectedBlock(page: Page): Locator {\n return page.locator('.is-selected')\n}\n\n/**\n * Type in block\n */\nexport async function typeInBlock(page: Page, blockType: string, text: string) {\n const block = getBlock(page, blockType)\n await block.click()\n await block.type(text)\n}\n\n/**\n * Update block attribute via inspector\n */\nexport async function updateBlockAttribute(\n page: Page,\n attributeName: string,\n value: string | number | boolean\n) {\n // Open inspector if not open\n const inspectorButton = page.locator('button[aria-label=\"Settings\"]')\n if (await inspectorButton.isVisible()) {\n await inspectorButton.click()\n }\n\n // Find and update the control\n const control = page.locator(`[aria-label*=\"${attributeName}\"]`)\n await control.fill(String(value))\n}\n\n/**\n * Publish post\n */\nexport async function publishPost(page: Page) {\n const publishButton = page.locator('.editor-post-publish-button__button')\n\n // Click publish panel button\n await publishButton.first().click()\n\n // Click final publish button\n await publishButton.last().click()\n\n // Wait for success notice\n await page.waitForSelector('.components-snackbar')\n}\n\n/**\n * Save draft\n */\nexport async function saveDraft(page: Page) {\n await page.click('.editor-post-save-draft')\n await page.waitForSelector('.is-saved')\n}\n\n/**\n * Preview post\n */\nexport async function previewPost(page: Page): Promise<Page> {\n const [previewPage] = await Promise.all([\n page.context().waitForEvent('page'),\n page.click('.editor-post-preview'),\n ])\n\n await previewPage.waitForLoadState('networkidle')\n return previewPage\n}\n\n/**\n * Check if block exists\n */\nexport async function blockExists(page: Page, blockType: string): Promise<boolean> {\n return (await getBlock(page, blockType).count()) > 0\n}\n\n/**\n * Get block count\n */\nexport async function getBlockCount(page: Page, blockType?: string): Promise<number> {\n if (blockType) {\n return await getBlock(page, blockType).count()\n }\n return await page.locator('[data-type]').count()\n}\n\n/**\n * Select block\n */\nexport async function selectBlock(page: Page, blockType: string) {\n await getBlock(page, blockType).click()\n}\n\n/**\n * Delete block\n */\nexport async function deleteBlock(page: Page, blockType: string) {\n await selectBlock(page, blockType)\n await page.keyboard.press('Backspace')\n}\n\n/**\n * Move block up\n */\nexport async function moveBlockUp(page: Page, blockType: string) {\n await selectBlock(page, blockType)\n await page.click('button[aria-label=\"Move up\"]')\n}\n\n/**\n * Move block down\n */\nexport async function moveBlockDown(page: Page, blockType: string) {\n await selectBlock(page, blockType)\n await page.click('button[aria-label=\"Move down\"]')\n}\n\n/**\n * Wait for WordPress admin\n */\nexport async function waitForWPAdmin(page: Page) {\n await page.waitForSelector('#wpadminbar')\n}\n\n/**\n * Navigate to WordPress admin page\n */\nexport async function goToWPAdmin(page: Page, path: string = '') {\n await page.goto(`/wp-admin/${path}`)\n await waitForWPAdmin(page)\n}\n\n/**\n * Check if user is logged in\n */\nexport async function isLoggedIn(page: Page): Promise<boolean> {\n await page.goto('/wp-admin/')\n return page.url().includes('/wp-admin/')\n}\n\n/**\n * Take accessible screenshot\n */\nexport async function takeA11yScreenshot(page: Page, name: string) {\n await page.screenshot({\n path: `screenshots/${name}.png`,\n fullPage: true,\n })\n}\n\n/**\n * Check for console errors\n */\nexport function setupConsoleErrorTracking(page: Page): string[] {\n const errors: string[] = []\n\n page.on('console', (msg) => {\n if (msg.type() === 'error') {\n errors.push(msg.text())\n }\n })\n\n page.on('pageerror', (error) => {\n errors.push(error.message)\n })\n\n return errors\n}\n","/**\n * Playwright E2E Testing Utilities\n * Re-export everything needed for Playwright tests\n */\n\nexport * from './utils/playwright-helpers'\n\n// Re-export Playwright test utilities\nexport { test, expect } from '@playwright/test'\nexport type { Page, Locator, Browser, BrowserContext } from '@playwright/test'\n\n// Re-export axe a11y utilities\nexport { makeAxeBuilder, WCAG_AA_TAGS } from './axe'\n","import AxeBuilder from '@axe-core/playwright'\n\n/** WCAG 2.1 A/AA tag set for the blocking gate. */\nexport const WCAG_AA_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] as const\n\n/** axe scanner scoped to WCAG 2.1 A/AA, excluding the wp-env admin bar. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function makeAxeBuilder(page: any): AxeBuilder {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n return new AxeBuilder({ page }).withTags([...WCAG_AA_TAGS]).exclude('#wpadminbar')\n}\n"],"mappings":"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","names":[]}
|
package/dist/vitest.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/utils/block-testing.ts
|
|
2
2
|
import { render } from "@testing-library/react";
|
|
3
3
|
import { createElement } from "react";
|
|
4
|
+
import { vi } from "vitest";
|
|
4
5
|
function renderBlockEdit(EditComponent, props = {}, options) {
|
|
5
6
|
const defaultProps = {
|
|
6
7
|
attributes: {},
|
|
@@ -136,9 +137,7 @@ var mockWordPressData = {
|
|
|
136
137
|
var mockWordPressi18n = {
|
|
137
138
|
__: vi2.fn((text) => text),
|
|
138
139
|
_x: vi2.fn((text) => text),
|
|
139
|
-
_n: vi2.fn(
|
|
140
|
-
(single, plural, number) => number === 1 ? single : plural
|
|
141
|
-
),
|
|
140
|
+
_n: vi2.fn((single, plural, number) => number === 1 ? single : plural),
|
|
142
141
|
sprintf: vi2.fn((format, ..._args) => format),
|
|
143
142
|
setLocaleData: vi2.fn()
|
|
144
143
|
};
|
|
@@ -244,9 +243,7 @@ function toHaveBlockAttributes(received, expectedAttributes) {
|
|
|
244
243
|
};
|
|
245
244
|
}
|
|
246
245
|
const actualAttributes = Object.keys(received.attributes);
|
|
247
|
-
const missingAttributes = expectedAttributes.filter(
|
|
248
|
-
(attr) => !actualAttributes.includes(attr)
|
|
249
|
-
);
|
|
246
|
+
const missingAttributes = expectedAttributes.filter((attr) => !actualAttributes.includes(attr));
|
|
250
247
|
const pass = missingAttributes.length === 0;
|
|
251
248
|
return {
|
|
252
249
|
pass,
|
package/dist/vitest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/block-testing.ts","../src/mocks/wordpress.ts","../src/matchers/index.ts","../src/vitest.ts"],"sourcesContent":["/**\n * Block Testing Utilities\n * Helper functions for testing Gutenberg blocks\n */\n\nimport { render, RenderOptions } from '@testing-library/react'\nimport { createElement } from 'react'\n\n/**\n * Block Configuration Interface\n */\nexport interface BlockConfiguration {\n name?: string\n title?: string\n description?: string\n category?: string\n icon?: string | Record<string, any>\n keywords?: string[]\n attributes?: Record<string, any>\n supports?: Record<string, any>\n [key: string]: any\n}\n\n/**\n * Render a block's edit component for testing\n */\nexport function renderBlockEdit(\n EditComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n setAttributes: vi.fn(),\n isSelected: false,\n clientId: 'test-block-id',\n ...props,\n }\n\n return render(createElement(EditComponent, defaultProps), options)\n}\n\n/**\n * Render a block's save component for testing\n */\nexport function renderBlockSave(\n SaveComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n ...props,\n }\n\n return render(createElement(SaveComponent, defaultProps), options)\n}\n\n/**\n * Test if a block is registered correctly\n */\nexport function testBlockRegistration(\n blockName: string,\n expectedConfig: Partial<BlockConfiguration>\n) {\n const { getBlockType } = wp.blocks\n\n const blockType = getBlockType(blockName)\n\n if (!blockType) {\n throw new Error(`Block ${blockName} is not registered`)\n }\n\n // Check required fields\n expect(blockType.name).toBe(blockName)\n\n if (expectedConfig.title) {\n expect(blockType.title).toBe(expectedConfig.title)\n }\n\n if (expectedConfig.category) {\n expect(blockType.category).toBe(expectedConfig.category)\n }\n\n if (expectedConfig.attributes) {\n expect(blockType.attributes).toEqual(expectedConfig.attributes)\n }\n\n return blockType\n}\n\n/**\n * Create mock block attributes\n */\nexport function createMockAttributes(\n blockType: string,\n overrides: Record<string, any> = {}\n): Record<string, any> {\n const defaults: Record<string, Record<string, any>> = {\n 'core/paragraph': {\n content: 'Test paragraph content',\n align: 'left',\n },\n 'core/heading': {\n content: 'Test heading',\n level: 2,\n },\n 'core/image': {\n url: 'https://example.com/image.jpg',\n alt: 'Test image',\n id: 123,\n },\n }\n\n return {\n ...(defaults[blockType] || {}),\n ...overrides,\n }\n}\n\n/**\n * Test block attribute validation\n */\nexport function testBlockAttributes(\n EditComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const setAttributes = vi.fn()\n\n const { rerender } = renderBlockEdit(EditComponent, {\n attributes,\n setAttributes,\n })\n\n return {\n setAttributes,\n rerender: (newAttributes: Record<string, any>) => {\n return rerender(\n createElement(EditComponent, {\n attributes: newAttributes,\n setAttributes,\n })\n )\n },\n }\n}\n\n/**\n * Test block save output\n */\nexport function testBlockSaveOutput(\n SaveComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const { container } = renderBlockSave(SaveComponent, { attributes })\n return container.innerHTML\n}\n\n/**\n * Mock block context\n */\nexport function createMockBlockContext(overrides: any = {}) {\n return {\n clientId: 'mock-client-id',\n isSelected: false,\n isMultiSelected: false,\n hasSelectedInnerBlock: false,\n index: 0,\n ...overrides,\n }\n}\n\n/**\n * Wait for block to render\n */\nexport async function waitForBlockRender(\n callback: () => void,\n options: { timeout?: number } = {}\n) {\n const timeout = options.timeout || 1000\n const startTime = Date.now()\n\n while (Date.now() - startTime < timeout) {\n try {\n callback()\n return\n } catch (error) {\n await new Promise((resolve) => setTimeout(resolve, 50))\n }\n }\n\n throw new Error('Block failed to render within timeout')\n}\n","/**\n * WordPress API Mocks\n * Mock implementations of WordPress JavaScript APIs for testing\n */\n\nimport { vi } from 'vitest'\n\n/**\n * Mock @wordpress/blocks\n */\nexport const mockWordPressBlocks = {\n registerBlockType: vi.fn((name: string, settings: any) => {\n return { name, ...settings }\n }),\n unregisterBlockType: vi.fn(),\n getBlockType: vi.fn((name: string) => ({\n name,\n title: 'Mock Block',\n category: 'common',\n icon: 'block-default',\n attributes: {},\n })),\n getBlockTypes: vi.fn(() => []),\n hasBlockSupport: vi.fn(() => true),\n createBlock: vi.fn((name: string, attributes?: any) => ({\n name,\n attributes: attributes || {},\n clientId: 'mock-client-id',\n })),\n}\n\n/**\n * Mock @wordpress/data\n */\nexport const mockWordPressData = {\n useSelect: vi.fn((callback: any) => callback(() => ({}))),\n useDispatch: vi.fn((_storeName: string) => ({})),\n select: vi.fn((_storeName: string) => ({})),\n dispatch: vi.fn((_storeName: string) => ({})),\n subscribe: vi.fn(),\n registerStore: vi.fn(),\n}\n\n/**\n * Mock @wordpress/i18n\n */\nexport const mockWordPressi18n = {\n __: vi.fn((text: string) => text),\n _x: vi.fn((text: string) => text),\n _n: vi.fn((single: string, plural: string, number: number) =>\n number === 1 ? single : plural\n ),\n sprintf: vi.fn((format: string, ..._args: any[]) => format),\n setLocaleData: vi.fn(),\n}\n\n/**\n * Mock @wordpress/components\n */\nexport const mockWordPressComponents = {\n Button: vi.fn(({ children }: any) => children),\n TextControl: vi.fn(() => null),\n SelectControl: vi.fn(() => null),\n RangeControl: vi.fn(() => null),\n ToggleControl: vi.fn(() => null),\n Panel: vi.fn(({ children }: any) => children),\n PanelBody: vi.fn(({ children }: any) => children),\n PanelRow: vi.fn(({ children }: any) => children),\n}\n\n/**\n * Mock @wordpress/block-editor\n */\nexport const mockWordPressBlockEditor = {\n InspectorControls: vi.fn(({ children }: any) => children),\n BlockControls: vi.fn(({ children }: any) => children),\n useBlockProps: vi.fn((props?: any) => ({\n className: 'wp-block',\n ...props,\n })),\n RichText: vi.fn(() => null),\n MediaUpload: vi.fn(() => null),\n MediaUploadCheck: vi.fn(({ children }: any) => children),\n ColorPalette: vi.fn(() => null),\n URLInput: vi.fn(() => null),\n}\n\n/**\n * Mock @wordpress/element\n */\nexport const mockWordPressElement = {\n createElement: vi.fn((type: any, props?: any, ...children: any[]) => ({\n type,\n props: { ...props, children },\n })),\n Fragment: vi.fn(({ children }: any) => children),\n useState: vi.fn((initialState: any) => [initialState, vi.fn()]),\n useEffect: vi.fn((_effect: () => void, _deps?: any[]) => undefined),\n useRef: vi.fn((initialValue: any) => ({ current: initialValue })),\n useMemo: vi.fn((factory: () => any, _deps?: any[]) => factory()),\n useCallback: vi.fn((callback: (...args: any[]) => any, _deps?: any[]) => callback),\n}\n\n/**\n * Mock @wordpress/api-fetch\n */\nexport const mockWordPressApiFetch = vi.fn(\n (_options: { path: string; method?: string; data?: any }) =>\n Promise.resolve({ ok: true, json: () => Promise.resolve({}) })\n)\n\n/**\n * Setup all WordPress mocks\n */\nexport function setupWordPressMocks() {\n vi.mock('@wordpress/blocks', () => mockWordPressBlocks)\n vi.mock('@wordpress/data', () => mockWordPressData)\n vi.mock('@wordpress/i18n', () => mockWordPressi18n)\n vi.mock('@wordpress/components', () => mockWordPressComponents)\n vi.mock('@wordpress/block-editor', () => mockWordPressBlockEditor)\n vi.mock('@wordpress/element', () => mockWordPressElement)\n vi.mock('@wordpress/api-fetch', () => ({ default: mockWordPressApiFetch }))\n\n // Setup global WordPress objects\n ;(global as any).wp = {\n blocks: mockWordPressBlocks,\n data: mockWordPressData,\n i18n: mockWordPressi18n,\n components: mockWordPressComponents,\n blockEditor: mockWordPressBlockEditor,\n element: mockWordPressElement,\n apiFetch: mockWordPressApiFetch,\n }\n}\n\n/**\n * Clear all WordPress mocks\n */\nexport function clearWordPressMocks() {\n Object.values(mockWordPressBlocks).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n Object.values(mockWordPressData).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n // Clear other mocks...\n}\n","/**\n * Custom Vitest Matchers\n * WordPress-specific test matchers\n */\n\nimport { expect } from 'vitest'\n\n/**\n * Check if element has WordPress block class\n */\nexport function toHaveBlockClass(received: HTMLElement, blockName: string) {\n const className = `wp-block-${blockName.replace('/', '-')}`\n const pass = received.classList.contains(className)\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected element not to have block class ${className}`\n : `Expected element to have block class ${className}`,\n }\n}\n\n/**\n * Check if block is registered\n */\nexport function toBeRegisteredBlock(received: string) {\n const { getBlockType } = (global as any).wp?.blocks || {}\n\n if (!getBlockType) {\n return {\n pass: false,\n message: () => 'WordPress blocks API is not available',\n }\n }\n\n const blockType = getBlockType(received)\n const pass = blockType !== undefined\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block ${received} not to be registered`\n : `Expected block ${received} to be registered`,\n }\n}\n\n/**\n * Check if block has required attributes\n */\nexport function toHaveBlockAttributes(\n received: any,\n expectedAttributes: string[]\n) {\n if (!received || !received.attributes) {\n return {\n pass: false,\n message: () => 'Block does not have attributes property',\n }\n }\n\n const actualAttributes = Object.keys(received.attributes)\n const missingAttributes = expectedAttributes.filter(\n (attr) => !actualAttributes.includes(attr)\n )\n\n const pass = missingAttributes.length === 0\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block not to have attributes: ${expectedAttributes.join(', ')}`\n : `Expected block to have attributes: ${missingAttributes.join(', ')}`,\n }\n}\n\n/**\n * Check if element is a valid WordPress block\n */\nexport function toBeValidWordPressBlock(received: HTMLElement) {\n const hasDataType = received.hasAttribute('data-type')\n const hasWpBlock = received.classList.contains('wp-block') ||\n Array.from(received.classList).some(c => c.startsWith('wp-block-'))\n\n const pass = hasDataType && hasWpBlock\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected element not to be a valid WordPress block'\n : 'Expected element to be a valid WordPress block (must have data-type attribute and wp-block class)',\n }\n}\n\n/**\n * Check if string is valid block markup\n */\nexport function toBeValidBlockMarkup(received: string) {\n // Check for WordPress block comment syntax\n const hasStartComment = received.includes('<!-- wp:')\n const hasEndComment = received.includes('<!-- /wp:') || received.includes('/-->')\n\n const pass = hasStartComment && hasEndComment\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected string not to be valid block markup'\n : 'Expected string to be valid block markup (must include WordPress block comments)',\n }\n}\n\n/**\n * Extend Vitest matchers\n */\nexport function setupCustomMatchers() {\n expect.extend({\n toHaveBlockClass,\n toBeRegisteredBlock,\n toHaveBlockAttributes,\n toBeValidWordPressBlock,\n toBeValidBlockMarkup,\n })\n}\n\n// Type declarations for TypeScript\ndeclare global {\n namespace Vi {\n interface Matchers<R = any> {\n toHaveBlockClass(blockName: string): R\n toBeRegisteredBlock(): R\n toHaveBlockAttributes(attributes: string[]): R\n toBeValidWordPressBlock(): R\n toBeValidBlockMarkup(): R\n }\n }\n}\n","/**\n * Vitest Testing Utilities\n * Re-export everything needed for Vitest tests\n */\n\nexport * from './utils/block-testing'\nexport * from './mocks/wordpress'\nexport * from './matchers/index'\n\n// Re-export testing library utilities\nexport * from '@testing-library/react'\nexport * from '@testing-library/user-event'\nexport { expect, describe, it, test, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest'\n"],"mappings":"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","names":["vi","expect","expect","vi"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/block-testing.ts","../src/mocks/wordpress.ts","../src/matchers/index.ts","../src/vitest.ts"],"sourcesContent":["/**\n * Block Testing Utilities\n * Helper functions for testing Gutenberg blocks\n */\n\nimport { render, RenderOptions } from '@testing-library/react'\nimport { createElement } from 'react'\nimport { vi } from 'vitest'\n\n/**\n * Block Configuration Interface\n */\nexport interface BlockConfiguration {\n name?: string\n title?: string\n description?: string\n category?: string\n icon?: string | Record<string, any>\n keywords?: string[]\n attributes?: Record<string, any>\n supports?: Record<string, any>\n [key: string]: any\n}\n\n/**\n * Render a block's edit component for testing\n */\nexport function renderBlockEdit(\n EditComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n setAttributes: vi.fn(),\n isSelected: false,\n clientId: 'test-block-id',\n ...props,\n }\n\n return render(createElement(EditComponent, defaultProps), options)\n}\n\n/**\n * Render a block's save component for testing\n */\nexport function renderBlockSave(\n SaveComponent: React.ComponentType<any>,\n props: any = {},\n options?: RenderOptions\n): ReturnType<typeof render> {\n const defaultProps = {\n attributes: {},\n ...props,\n }\n\n return render(createElement(SaveComponent, defaultProps), options)\n}\n\n/**\n * Test if a block is registered correctly\n */\nexport function testBlockRegistration(\n blockName: string,\n expectedConfig: Partial<BlockConfiguration>\n) {\n const { getBlockType } = wp.blocks\n\n const blockType = getBlockType(blockName)\n\n if (!blockType) {\n throw new Error(`Block ${blockName} is not registered`)\n }\n\n // Check required fields\n expect(blockType.name).toBe(blockName)\n\n if (expectedConfig.title) {\n expect(blockType.title).toBe(expectedConfig.title)\n }\n\n if (expectedConfig.category) {\n expect(blockType.category).toBe(expectedConfig.category)\n }\n\n if (expectedConfig.attributes) {\n expect(blockType.attributes).toEqual(expectedConfig.attributes)\n }\n\n return blockType\n}\n\n/**\n * Create mock block attributes\n */\nexport function createMockAttributes(\n blockType: string,\n overrides: Record<string, any> = {}\n): Record<string, any> {\n const defaults: Record<string, Record<string, any>> = {\n 'core/paragraph': {\n content: 'Test paragraph content',\n align: 'left',\n },\n 'core/heading': {\n content: 'Test heading',\n level: 2,\n },\n 'core/image': {\n url: 'https://example.com/image.jpg',\n alt: 'Test image',\n id: 123,\n },\n }\n\n return {\n ...(defaults[blockType] || {}),\n ...overrides,\n }\n}\n\n/**\n * Test block attribute validation\n */\nexport function testBlockAttributes(\n EditComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const setAttributes = vi.fn()\n\n const { rerender } = renderBlockEdit(EditComponent, {\n attributes,\n setAttributes,\n })\n\n return {\n setAttributes,\n rerender: (newAttributes: Record<string, any>) => {\n return rerender(\n createElement(EditComponent, {\n attributes: newAttributes,\n setAttributes,\n })\n )\n },\n }\n}\n\n/**\n * Test block save output\n */\nexport function testBlockSaveOutput(\n SaveComponent: React.ComponentType<any>,\n attributes: Record<string, any>\n) {\n const { container } = renderBlockSave(SaveComponent, { attributes })\n return container.innerHTML\n}\n\n/**\n * Mock block context\n */\nexport function createMockBlockContext(overrides: any = {}) {\n return {\n clientId: 'mock-client-id',\n isSelected: false,\n isMultiSelected: false,\n hasSelectedInnerBlock: false,\n index: 0,\n ...overrides,\n }\n}\n\n/**\n * Wait for block to render\n */\nexport async function waitForBlockRender(callback: () => void, options: { timeout?: number } = {}) {\n const timeout = options.timeout || 1000\n const startTime = Date.now()\n\n while (Date.now() - startTime < timeout) {\n try {\n callback()\n return\n } catch (error) {\n await new Promise((resolve) => setTimeout(resolve, 50))\n }\n }\n\n throw new Error('Block failed to render within timeout')\n}\n","/**\n * WordPress API Mocks\n * Mock implementations of WordPress JavaScript APIs for testing\n */\n\nimport { vi } from 'vitest'\n\n/**\n * Mock @wordpress/blocks\n */\nexport const mockWordPressBlocks = {\n registerBlockType: vi.fn((name: string, settings: any) => {\n return { name, ...settings }\n }),\n unregisterBlockType: vi.fn(),\n getBlockType: vi.fn((name: string) => ({\n name,\n title: 'Mock Block',\n category: 'common',\n icon: 'block-default',\n attributes: {},\n })),\n getBlockTypes: vi.fn(() => []),\n hasBlockSupport: vi.fn(() => true),\n createBlock: vi.fn((name: string, attributes?: any) => ({\n name,\n attributes: attributes || {},\n clientId: 'mock-client-id',\n })),\n}\n\n/**\n * Mock @wordpress/data\n */\nexport const mockWordPressData = {\n useSelect: vi.fn((callback: any) => callback(() => ({}))),\n useDispatch: vi.fn((_storeName: string) => ({})),\n select: vi.fn((_storeName: string) => ({})),\n dispatch: vi.fn((_storeName: string) => ({})),\n subscribe: vi.fn(),\n registerStore: vi.fn(),\n}\n\n/**\n * Mock @wordpress/i18n\n */\nexport const mockWordPressi18n = {\n __: vi.fn((text: string) => text),\n _x: vi.fn((text: string) => text),\n _n: vi.fn((single: string, plural: string, number: number) => (number === 1 ? single : plural)),\n sprintf: vi.fn((format: string, ..._args: any[]) => format),\n setLocaleData: vi.fn(),\n}\n\n/**\n * Mock @wordpress/components\n */\nexport const mockWordPressComponents = {\n Button: vi.fn(({ children }: any) => children),\n TextControl: vi.fn(() => null),\n SelectControl: vi.fn(() => null),\n RangeControl: vi.fn(() => null),\n ToggleControl: vi.fn(() => null),\n Panel: vi.fn(({ children }: any) => children),\n PanelBody: vi.fn(({ children }: any) => children),\n PanelRow: vi.fn(({ children }: any) => children),\n}\n\n/**\n * Mock @wordpress/block-editor\n */\nexport const mockWordPressBlockEditor = {\n InspectorControls: vi.fn(({ children }: any) => children),\n BlockControls: vi.fn(({ children }: any) => children),\n useBlockProps: vi.fn((props?: any) => ({\n className: 'wp-block',\n ...props,\n })),\n RichText: vi.fn(() => null),\n MediaUpload: vi.fn(() => null),\n MediaUploadCheck: vi.fn(({ children }: any) => children),\n ColorPalette: vi.fn(() => null),\n URLInput: vi.fn(() => null),\n}\n\n/**\n * Mock @wordpress/element\n */\nexport const mockWordPressElement = {\n createElement: vi.fn((type: any, props?: any, ...children: any[]) => ({\n type,\n props: { ...props, children },\n })),\n Fragment: vi.fn(({ children }: any) => children),\n useState: vi.fn((initialState: any) => [initialState, vi.fn()]),\n useEffect: vi.fn((_effect: () => void, _deps?: any[]) => undefined),\n useRef: vi.fn((initialValue: any) => ({ current: initialValue })),\n useMemo: vi.fn((factory: () => any, _deps?: any[]) => factory()),\n useCallback: vi.fn((callback: (...args: any[]) => any, _deps?: any[]) => callback),\n}\n\n/**\n * Mock @wordpress/api-fetch\n */\nexport const mockWordPressApiFetch = vi.fn(\n (_options: { path: string; method?: string; data?: any }) =>\n Promise.resolve({ ok: true, json: () => Promise.resolve({}) })\n)\n\n/**\n * Setup all WordPress mocks\n */\nexport function setupWordPressMocks() {\n vi.mock('@wordpress/blocks', () => mockWordPressBlocks)\n vi.mock('@wordpress/data', () => mockWordPressData)\n vi.mock('@wordpress/i18n', () => mockWordPressi18n)\n vi.mock('@wordpress/components', () => mockWordPressComponents)\n vi.mock('@wordpress/block-editor', () => mockWordPressBlockEditor)\n vi.mock('@wordpress/element', () => mockWordPressElement)\n vi.mock('@wordpress/api-fetch', () => ({ default: mockWordPressApiFetch }))\n\n // Setup global WordPress objects\n ;(global as any).wp = {\n blocks: mockWordPressBlocks,\n data: mockWordPressData,\n i18n: mockWordPressi18n,\n components: mockWordPressComponents,\n blockEditor: mockWordPressBlockEditor,\n element: mockWordPressElement,\n apiFetch: mockWordPressApiFetch,\n }\n}\n\n/**\n * Clear all WordPress mocks\n */\nexport function clearWordPressMocks() {\n Object.values(mockWordPressBlocks).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n Object.values(mockWordPressData).forEach((mock: any) => {\n if (typeof mock?.mockClear === 'function') {\n mock.mockClear()\n }\n })\n\n // Clear other mocks...\n}\n","/**\n * Custom Vitest Matchers\n * WordPress-specific test matchers\n */\n\nimport { expect } from 'vitest'\n\n/**\n * Check if element has WordPress block class\n */\nexport function toHaveBlockClass(received: HTMLElement, blockName: string) {\n const className = `wp-block-${blockName.replace('/', '-')}`\n const pass = received.classList.contains(className)\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected element not to have block class ${className}`\n : `Expected element to have block class ${className}`,\n }\n}\n\n/**\n * Check if block is registered\n */\nexport function toBeRegisteredBlock(received: string) {\n const { getBlockType } = (global as any).wp?.blocks || {}\n\n if (!getBlockType) {\n return {\n pass: false,\n message: () => 'WordPress blocks API is not available',\n }\n }\n\n const blockType = getBlockType(received)\n const pass = blockType !== undefined\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block ${received} not to be registered`\n : `Expected block ${received} to be registered`,\n }\n}\n\n/**\n * Check if block has required attributes\n */\nexport function toHaveBlockAttributes(received: any, expectedAttributes: string[]) {\n if (!received || !received.attributes) {\n return {\n pass: false,\n message: () => 'Block does not have attributes property',\n }\n }\n\n const actualAttributes = Object.keys(received.attributes)\n const missingAttributes = expectedAttributes.filter((attr) => !actualAttributes.includes(attr))\n\n const pass = missingAttributes.length === 0\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected block not to have attributes: ${expectedAttributes.join(', ')}`\n : `Expected block to have attributes: ${missingAttributes.join(', ')}`,\n }\n}\n\n/**\n * Check if element is a valid WordPress block\n */\nexport function toBeValidWordPressBlock(received: HTMLElement) {\n const hasDataType = received.hasAttribute('data-type')\n const hasWpBlock =\n received.classList.contains('wp-block') ||\n Array.from(received.classList).some((c) => c.startsWith('wp-block-'))\n\n const pass = hasDataType && hasWpBlock\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected element not to be a valid WordPress block'\n : 'Expected element to be a valid WordPress block (must have data-type attribute and wp-block class)',\n }\n}\n\n/**\n * Check if string is valid block markup\n */\nexport function toBeValidBlockMarkup(received: string) {\n // Check for WordPress block comment syntax\n const hasStartComment = received.includes('<!-- wp:')\n const hasEndComment = received.includes('<!-- /wp:') || received.includes('/-->')\n\n const pass = hasStartComment && hasEndComment\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected string not to be valid block markup'\n : 'Expected string to be valid block markup (must include WordPress block comments)',\n }\n}\n\n/**\n * Extend Vitest matchers\n */\nexport function setupCustomMatchers() {\n expect.extend({\n toHaveBlockClass,\n toBeRegisteredBlock,\n toHaveBlockAttributes,\n toBeValidWordPressBlock,\n toBeValidBlockMarkup,\n })\n}\n\n// Type declarations for TypeScript — namespace Vi is required by Vitest's type augmentation API\n/* eslint-disable @typescript-eslint/no-namespace */\ndeclare global {\n namespace Vi {\n interface Matchers<R = any> {\n toHaveBlockClass(blockName: string): R\n toBeRegisteredBlock(): R\n toHaveBlockAttributes(attributes: string[]): R\n toBeValidWordPressBlock(): R\n toBeValidBlockMarkup(): R\n }\n }\n}\n/* eslint-enable @typescript-eslint/no-namespace */\n","/**\n * Vitest Testing Utilities\n * Re-export everything needed for Vitest tests\n */\n\nexport * from './utils/block-testing'\nexport * from './mocks/wordpress'\nexport * from './matchers/index'\n\n// Re-export testing library utilities\nexport * from '@testing-library/react'\nexport * from '@testing-library/user-event'\nexport { expect, describe, it, test, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest'\n"],"mappings":"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","names":["vi","expect","expect","vi"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stratawp/testing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Comprehensive testing utilities for StrataWP themes",
|
|
5
5
|
"author": "Jon Imms",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -39,17 +39,18 @@
|
|
|
39
39
|
"playwright.config.ts"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@testing-library/react": "^14.1.2",
|
|
43
42
|
"@testing-library/jest-dom": "^6.1.5",
|
|
43
|
+
"@testing-library/react": "^14.1.2",
|
|
44
44
|
"@testing-library/user-event": "^14.5.1",
|
|
45
45
|
"@vitest/ui": "^1.0.4",
|
|
46
|
-
"
|
|
47
|
-
"jsdom": "^23.0.1",
|
|
46
|
+
"@wordpress/jest-preset-default": "^11.21.0",
|
|
48
47
|
"happy-dom": "^12.10.3",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
48
|
+
"jsdom": "^23.0.1",
|
|
49
|
+
"vitest": "^1.0.4"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
|
52
|
+
"@axe-core/playwright": "^4.11.3",
|
|
53
|
+
"@playwright/test": "^1.61.1",
|
|
53
54
|
"@types/react": "^18.2.45",
|
|
54
55
|
"@vitest/coverage-v8": "^1.0.4",
|
|
55
56
|
"tsup": "^8.0.1",
|
|
@@ -62,9 +63,9 @@
|
|
|
62
63
|
"scripts": {
|
|
63
64
|
"dev": "tsup --watch",
|
|
64
65
|
"build": "tsup",
|
|
65
|
-
"
|
|
66
|
-
"test
|
|
67
|
-
"test:
|
|
68
|
-
"
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"test:coverage": "vitest run --coverage",
|
|
69
|
+
"test:e2e": "playwright test"
|
|
69
70
|
}
|
|
70
71
|
}
|
package/vitest.config.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config'
|
|
2
|
-
import react from '@vitejs/plugin-react'
|
|
3
2
|
import path from 'path'
|
|
4
3
|
|
|
5
4
|
export default defineConfig({
|
|
6
|
-
plugins: [react()],
|
|
7
5
|
test: {
|
|
8
6
|
globals: true,
|
|
9
7
|
environment: 'jsdom',
|
|
@@ -11,6 +9,7 @@ export default defineConfig({
|
|
|
11
9
|
coverage: {
|
|
12
10
|
provider: 'v8',
|
|
13
11
|
reporter: ['text', 'json', 'html', 'lcov'],
|
|
12
|
+
include: ['src/matchers/**'],
|
|
14
13
|
exclude: [
|
|
15
14
|
'node_modules/',
|
|
16
15
|
'dist/',
|
|
@@ -20,10 +19,10 @@ export default defineConfig({
|
|
|
20
19
|
'**/*.config.{ts,js}',
|
|
21
20
|
],
|
|
22
21
|
thresholds: {
|
|
23
|
-
lines:
|
|
24
|
-
functions:
|
|
25
|
-
branches:
|
|
26
|
-
statements:
|
|
22
|
+
lines: 65,
|
|
23
|
+
functions: 34,
|
|
24
|
+
branches: 88,
|
|
25
|
+
statements: 65,
|
|
27
26
|
},
|
|
28
27
|
},
|
|
29
28
|
include: ['**/*.{test,spec}.{ts,tsx}'],
|