@stratawp/testing 0.1.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.
- package/LICENSE +21 -0
- package/README.md +579 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +308 -0
- package/dist/index.js.map +1 -0
- package/dist/playwright.d.ts +98 -0
- package/dist/playwright.js +143 -0
- package/dist/playwright.js.map +1 -0
- package/dist/vitest.d.ts +4 -0
- package/dist/vitest.js +322 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +70 -0
- package/playwright.config.ts +84 -0
- package/vitest.config.ts +43 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/utils/playwright-helpers.ts
|
|
2
|
+
async function wpLogin(page, username = "admin", password = "password") {
|
|
3
|
+
await page.goto("/wp-login.php");
|
|
4
|
+
await page.fill("#user_login", username);
|
|
5
|
+
await page.fill("#user_pass", password);
|
|
6
|
+
await page.click("#wp-submit");
|
|
7
|
+
await page.waitForLoadState("networkidle");
|
|
8
|
+
}
|
|
9
|
+
async function wpLogout(page) {
|
|
10
|
+
await page.goto("/wp-login.php?action=logout");
|
|
11
|
+
await page.click("a");
|
|
12
|
+
await page.waitForLoadState("networkidle");
|
|
13
|
+
}
|
|
14
|
+
async function openBlockEditor(page, postType = "post") {
|
|
15
|
+
await page.goto(`/wp-admin/post-new.php?post_type=${postType}`);
|
|
16
|
+
await page.waitForSelector(".block-editor");
|
|
17
|
+
}
|
|
18
|
+
async function insertBlock(page, blockName) {
|
|
19
|
+
await page.click(".block-editor-inserter__toggle");
|
|
20
|
+
await page.fill(".block-editor-inserter__search input", blockName);
|
|
21
|
+
await page.click(`button[aria-label*="${blockName}"]`);
|
|
22
|
+
await page.waitForSelector(`[data-type*="${blockName}"]`);
|
|
23
|
+
}
|
|
24
|
+
function getBlock(page, blockType) {
|
|
25
|
+
return page.locator(`[data-type="${blockType}"]`);
|
|
26
|
+
}
|
|
27
|
+
function getSelectedBlock(page) {
|
|
28
|
+
return page.locator(".is-selected");
|
|
29
|
+
}
|
|
30
|
+
async function typeInBlock(page, blockType, text) {
|
|
31
|
+
const block = getBlock(page, blockType);
|
|
32
|
+
await block.click();
|
|
33
|
+
await block.type(text);
|
|
34
|
+
}
|
|
35
|
+
async function updateBlockAttribute(page, attributeName, value) {
|
|
36
|
+
const inspectorButton = page.locator('button[aria-label="Settings"]');
|
|
37
|
+
if (await inspectorButton.isVisible()) {
|
|
38
|
+
await inspectorButton.click();
|
|
39
|
+
}
|
|
40
|
+
const control = page.locator(`[aria-label*="${attributeName}"]`);
|
|
41
|
+
await control.fill(String(value));
|
|
42
|
+
}
|
|
43
|
+
async function publishPost(page) {
|
|
44
|
+
const publishButton = page.locator(".editor-post-publish-button__button");
|
|
45
|
+
await publishButton.first().click();
|
|
46
|
+
await publishButton.last().click();
|
|
47
|
+
await page.waitForSelector(".components-snackbar");
|
|
48
|
+
}
|
|
49
|
+
async function saveDraft(page) {
|
|
50
|
+
await page.click(".editor-post-save-draft");
|
|
51
|
+
await page.waitForSelector(".is-saved");
|
|
52
|
+
}
|
|
53
|
+
async function previewPost(page) {
|
|
54
|
+
const [previewPage] = await Promise.all([
|
|
55
|
+
page.context().waitForEvent("page"),
|
|
56
|
+
page.click(".editor-post-preview")
|
|
57
|
+
]);
|
|
58
|
+
await previewPage.waitForLoadState("networkidle");
|
|
59
|
+
return previewPage;
|
|
60
|
+
}
|
|
61
|
+
async function blockExists(page, blockType) {
|
|
62
|
+
return await getBlock(page, blockType).count() > 0;
|
|
63
|
+
}
|
|
64
|
+
async function getBlockCount(page, blockType) {
|
|
65
|
+
if (blockType) {
|
|
66
|
+
return await getBlock(page, blockType).count();
|
|
67
|
+
}
|
|
68
|
+
return await page.locator("[data-type]").count();
|
|
69
|
+
}
|
|
70
|
+
async function selectBlock(page, blockType) {
|
|
71
|
+
await getBlock(page, blockType).click();
|
|
72
|
+
}
|
|
73
|
+
async function deleteBlock(page, blockType) {
|
|
74
|
+
await selectBlock(page, blockType);
|
|
75
|
+
await page.keyboard.press("Backspace");
|
|
76
|
+
}
|
|
77
|
+
async function moveBlockUp(page, blockType) {
|
|
78
|
+
await selectBlock(page, blockType);
|
|
79
|
+
await page.click('button[aria-label="Move up"]');
|
|
80
|
+
}
|
|
81
|
+
async function moveBlockDown(page, blockType) {
|
|
82
|
+
await selectBlock(page, blockType);
|
|
83
|
+
await page.click('button[aria-label="Move down"]');
|
|
84
|
+
}
|
|
85
|
+
async function waitForWPAdmin(page) {
|
|
86
|
+
await page.waitForSelector("#wpadminbar");
|
|
87
|
+
}
|
|
88
|
+
async function goToWPAdmin(page, path = "") {
|
|
89
|
+
await page.goto(`/wp-admin/${path}`);
|
|
90
|
+
await waitForWPAdmin(page);
|
|
91
|
+
}
|
|
92
|
+
async function isLoggedIn(page) {
|
|
93
|
+
await page.goto("/wp-admin/");
|
|
94
|
+
return page.url().includes("/wp-admin/");
|
|
95
|
+
}
|
|
96
|
+
async function takeA11yScreenshot(page, name) {
|
|
97
|
+
await page.screenshot({
|
|
98
|
+
path: `screenshots/${name}.png`,
|
|
99
|
+
fullPage: true
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function setupConsoleErrorTracking(page) {
|
|
103
|
+
const errors = [];
|
|
104
|
+
page.on("console", (msg) => {
|
|
105
|
+
if (msg.type() === "error") {
|
|
106
|
+
errors.push(msg.text());
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
page.on("pageerror", (error) => {
|
|
110
|
+
errors.push(error.message);
|
|
111
|
+
});
|
|
112
|
+
return errors;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/playwright.ts
|
|
116
|
+
import { test, expect } from "@playwright/test";
|
|
117
|
+
export {
|
|
118
|
+
blockExists,
|
|
119
|
+
deleteBlock,
|
|
120
|
+
expect,
|
|
121
|
+
getBlock,
|
|
122
|
+
getBlockCount,
|
|
123
|
+
getSelectedBlock,
|
|
124
|
+
goToWPAdmin,
|
|
125
|
+
insertBlock,
|
|
126
|
+
isLoggedIn,
|
|
127
|
+
moveBlockDown,
|
|
128
|
+
moveBlockUp,
|
|
129
|
+
openBlockEditor,
|
|
130
|
+
previewPost,
|
|
131
|
+
publishPost,
|
|
132
|
+
saveDraft,
|
|
133
|
+
selectBlock,
|
|
134
|
+
setupConsoleErrorTracking,
|
|
135
|
+
takeA11yScreenshot,
|
|
136
|
+
test,
|
|
137
|
+
typeInBlock,
|
|
138
|
+
updateBlockAttribute,
|
|
139
|
+
waitForWPAdmin,
|
|
140
|
+
wpLogin,
|
|
141
|
+
wpLogout
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=playwright.js.map
|
|
@@ -0,0 +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":";AAUA,eAAsB,QACpB,MACA,WAAmB,SACnB,WAAmB,YACnB;AACA,QAAM,KAAK,KAAK,eAAe;AAC/B,QAAM,KAAK,KAAK,eAAe,QAAQ;AACvC,QAAM,KAAK,KAAK,cAAc,QAAQ;AACtC,QAAM,KAAK,MAAM,YAAY;AAC7B,QAAM,KAAK,iBAAiB,aAAa;AAC3C;AAKA,eAAsB,SAAS,MAAY;AACzC,QAAM,KAAK,KAAK,6BAA6B;AAC7C,QAAM,KAAK,MAAM,GAAG;AACpB,QAAM,KAAK,iBAAiB,aAAa;AAC3C;AAKA,eAAsB,gBAAgB,MAAY,WAAmB,QAAQ;AAC3E,QAAM,KAAK,KAAK,oCAAoC,QAAQ,EAAE;AAC9D,QAAM,KAAK,gBAAgB,eAAe;AAC5C;AAKA,eAAsB,YAAY,MAAY,WAAmB;AAE/D,QAAM,KAAK,MAAM,gCAAgC;AAGjD,QAAM,KAAK,KAAK,wCAAwC,SAAS;AAGjE,QAAM,KAAK,MAAM,uBAAuB,SAAS,IAAI;AAGrD,QAAM,KAAK,gBAAgB,gBAAgB,SAAS,IAAI;AAC1D;AAKO,SAAS,SAAS,MAAY,WAA4B;AAC/D,SAAO,KAAK,QAAQ,eAAe,SAAS,IAAI;AAClD;AAKO,SAAS,iBAAiB,MAAqB;AACpD,SAAO,KAAK,QAAQ,cAAc;AACpC;AAKA,eAAsB,YAAY,MAAY,WAAmB,MAAc;AAC7E,QAAM,QAAQ,SAAS,MAAM,SAAS;AACtC,QAAM,MAAM,MAAM;AAClB,QAAM,MAAM,KAAK,IAAI;AACvB;AAKA,eAAsB,qBACpB,MACA,eACA,OACA;AAEA,QAAM,kBAAkB,KAAK,QAAQ,+BAA+B;AACpE,MAAI,MAAM,gBAAgB,UAAU,GAAG;AACrC,UAAM,gBAAgB,MAAM;AAAA,EAC9B;AAGA,QAAM,UAAU,KAAK,QAAQ,iBAAiB,aAAa,IAAI;AAC/D,QAAM,QAAQ,KAAK,OAAO,KAAK,CAAC;AAClC;AAKA,eAAsB,YAAY,MAAY;AAC5C,QAAM,gBAAgB,KAAK,QAAQ,qCAAqC;AAGxE,QAAM,cAAc,MAAM,EAAE,MAAM;AAGlC,QAAM,cAAc,KAAK,EAAE,MAAM;AAGjC,QAAM,KAAK,gBAAgB,sBAAsB;AACnD;AAKA,eAAsB,UAAU,MAAY;AAC1C,QAAM,KAAK,MAAM,yBAAyB;AAC1C,QAAM,KAAK,gBAAgB,WAAW;AACxC;AAKA,eAAsB,YAAY,MAA2B;AAC3D,QAAM,CAAC,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IACtC,KAAK,QAAQ,EAAE,aAAa,MAAM;AAAA,IAClC,KAAK,MAAM,sBAAsB;AAAA,EACnC,CAAC;AAED,QAAM,YAAY,iBAAiB,aAAa;AAChD,SAAO;AACT;AAKA,eAAsB,YAAY,MAAY,WAAqC;AACjF,SAAQ,MAAM,SAAS,MAAM,SAAS,EAAE,MAAM,IAAK;AACrD;AAKA,eAAsB,cAAc,MAAY,WAAqC;AACnF,MAAI,WAAW;AACb,WAAO,MAAM,SAAS,MAAM,SAAS,EAAE,MAAM;AAAA,EAC/C;AACA,SAAO,MAAM,KAAK,QAAQ,aAAa,EAAE,MAAM;AACjD;AAKA,eAAsB,YAAY,MAAY,WAAmB;AAC/D,QAAM,SAAS,MAAM,SAAS,EAAE,MAAM;AACxC;AAKA,eAAsB,YAAY,MAAY,WAAmB;AAC/D,QAAM,YAAY,MAAM,SAAS;AACjC,QAAM,KAAK,SAAS,MAAM,WAAW;AACvC;AAKA,eAAsB,YAAY,MAAY,WAAmB;AAC/D,QAAM,YAAY,MAAM,SAAS;AACjC,QAAM,KAAK,MAAM,8BAA8B;AACjD;AAKA,eAAsB,cAAc,MAAY,WAAmB;AACjE,QAAM,YAAY,MAAM,SAAS;AACjC,QAAM,KAAK,MAAM,gCAAgC;AACnD;AAKA,eAAsB,eAAe,MAAY;AAC/C,QAAM,KAAK,gBAAgB,aAAa;AAC1C;AAKA,eAAsB,YAAY,MAAY,OAAe,IAAI;AAC/D,QAAM,KAAK,KAAK,aAAa,IAAI,EAAE;AACnC,QAAM,eAAe,IAAI;AAC3B;AAKA,eAAsB,WAAW,MAA8B;AAC7D,QAAM,KAAK,KAAK,YAAY;AAC5B,SAAO,KAAK,IAAI,EAAE,SAAS,YAAY;AACzC;AAKA,eAAsB,mBAAmB,MAAY,MAAc;AACjE,QAAM,KAAK,WAAW;AAAA,IACpB,MAAM,eAAe,IAAI;AAAA,IACzB,UAAU;AAAA,EACZ,CAAC;AACH;AAKO,SAAS,0BAA0B,MAAsB;AAC9D,QAAM,SAAmB,CAAC;AAE1B,OAAK,GAAG,WAAW,CAAC,QAAQ;AAC1B,QAAI,IAAI,KAAK,MAAM,SAAS;AAC1B,aAAO,KAAK,IAAI,KAAK,CAAC;AAAA,IACxB;AAAA,EACF,CAAC;AAED,OAAK,GAAG,aAAa,CAAC,UAAU;AAC9B,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,CAAC;AAED,SAAO;AACT;;;ACjOA,SAAS,MAAM,cAAc;","names":[]}
|
package/dist/vitest.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { BlockConfiguration, clearWordPressMocks, createMockAttributes, createMockBlockContext, mockWordPressApiFetch, mockWordPressBlockEditor, mockWordPressBlocks, mockWordPressComponents, mockWordPressData, mockWordPressElement, mockWordPressi18n, renderBlockEdit, renderBlockSave, setupCustomMatchers, setupWordPressMocks, testBlockAttributes, testBlockRegistration, testBlockSaveOutput, toBeRegisteredBlock, toBeValidBlockMarkup, toBeValidWordPressBlock, toHaveBlockAttributes, toHaveBlockClass, waitForBlockRender } from './index.js';
|
|
2
|
+
export * from '@testing-library/react';
|
|
3
|
+
export * from '@testing-library/user-event';
|
|
4
|
+
export { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test, vi } from 'vitest';
|
package/dist/vitest.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// src/utils/block-testing.ts
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { createElement } from "react";
|
|
4
|
+
function renderBlockEdit(EditComponent, props = {}, options) {
|
|
5
|
+
const defaultProps = {
|
|
6
|
+
attributes: {},
|
|
7
|
+
setAttributes: vi.fn(),
|
|
8
|
+
isSelected: false,
|
|
9
|
+
clientId: "test-block-id",
|
|
10
|
+
...props
|
|
11
|
+
};
|
|
12
|
+
return render(createElement(EditComponent, defaultProps), options);
|
|
13
|
+
}
|
|
14
|
+
function renderBlockSave(SaveComponent, props = {}, options) {
|
|
15
|
+
const defaultProps = {
|
|
16
|
+
attributes: {},
|
|
17
|
+
...props
|
|
18
|
+
};
|
|
19
|
+
return render(createElement(SaveComponent, defaultProps), options);
|
|
20
|
+
}
|
|
21
|
+
function testBlockRegistration(blockName, expectedConfig) {
|
|
22
|
+
const { getBlockType } = wp.blocks;
|
|
23
|
+
const blockType = getBlockType(blockName);
|
|
24
|
+
if (!blockType) {
|
|
25
|
+
throw new Error(`Block ${blockName} is not registered`);
|
|
26
|
+
}
|
|
27
|
+
expect(blockType.name).toBe(blockName);
|
|
28
|
+
if (expectedConfig.title) {
|
|
29
|
+
expect(blockType.title).toBe(expectedConfig.title);
|
|
30
|
+
}
|
|
31
|
+
if (expectedConfig.category) {
|
|
32
|
+
expect(blockType.category).toBe(expectedConfig.category);
|
|
33
|
+
}
|
|
34
|
+
if (expectedConfig.attributes) {
|
|
35
|
+
expect(blockType.attributes).toEqual(expectedConfig.attributes);
|
|
36
|
+
}
|
|
37
|
+
return blockType;
|
|
38
|
+
}
|
|
39
|
+
function createMockAttributes(blockType, overrides = {}) {
|
|
40
|
+
const defaults = {
|
|
41
|
+
"core/paragraph": {
|
|
42
|
+
content: "Test paragraph content",
|
|
43
|
+
align: "left"
|
|
44
|
+
},
|
|
45
|
+
"core/heading": {
|
|
46
|
+
content: "Test heading",
|
|
47
|
+
level: 2
|
|
48
|
+
},
|
|
49
|
+
"core/image": {
|
|
50
|
+
url: "https://example.com/image.jpg",
|
|
51
|
+
alt: "Test image",
|
|
52
|
+
id: 123
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
...defaults[blockType] || {},
|
|
57
|
+
...overrides
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function testBlockAttributes(EditComponent, attributes) {
|
|
61
|
+
const setAttributes = vi.fn();
|
|
62
|
+
const { rerender } = renderBlockEdit(EditComponent, {
|
|
63
|
+
attributes,
|
|
64
|
+
setAttributes
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
setAttributes,
|
|
68
|
+
rerender: (newAttributes) => {
|
|
69
|
+
return rerender(
|
|
70
|
+
createElement(EditComponent, {
|
|
71
|
+
attributes: newAttributes,
|
|
72
|
+
setAttributes
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function testBlockSaveOutput(SaveComponent, attributes) {
|
|
79
|
+
const { container } = renderBlockSave(SaveComponent, { attributes });
|
|
80
|
+
return container.innerHTML;
|
|
81
|
+
}
|
|
82
|
+
function createMockBlockContext(overrides = {}) {
|
|
83
|
+
return {
|
|
84
|
+
clientId: "mock-client-id",
|
|
85
|
+
isSelected: false,
|
|
86
|
+
isMultiSelected: false,
|
|
87
|
+
hasSelectedInnerBlock: false,
|
|
88
|
+
index: 0,
|
|
89
|
+
...overrides
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function waitForBlockRender(callback, options = {}) {
|
|
93
|
+
const timeout = options.timeout || 1e3;
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
while (Date.now() - startTime < timeout) {
|
|
96
|
+
try {
|
|
97
|
+
callback();
|
|
98
|
+
return;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new Error("Block failed to render within timeout");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/mocks/wordpress.ts
|
|
107
|
+
import { vi as vi2 } from "vitest";
|
|
108
|
+
var mockWordPressBlocks = {
|
|
109
|
+
registerBlockType: vi2.fn((name, settings) => {
|
|
110
|
+
return { name, ...settings };
|
|
111
|
+
}),
|
|
112
|
+
unregisterBlockType: vi2.fn(),
|
|
113
|
+
getBlockType: vi2.fn((name) => ({
|
|
114
|
+
name,
|
|
115
|
+
title: "Mock Block",
|
|
116
|
+
category: "common",
|
|
117
|
+
icon: "block-default",
|
|
118
|
+
attributes: {}
|
|
119
|
+
})),
|
|
120
|
+
getBlockTypes: vi2.fn(() => []),
|
|
121
|
+
hasBlockSupport: vi2.fn(() => true),
|
|
122
|
+
createBlock: vi2.fn((name, attributes) => ({
|
|
123
|
+
name,
|
|
124
|
+
attributes: attributes || {},
|
|
125
|
+
clientId: "mock-client-id"
|
|
126
|
+
}))
|
|
127
|
+
};
|
|
128
|
+
var mockWordPressData = {
|
|
129
|
+
useSelect: vi2.fn((callback) => callback(() => ({}))),
|
|
130
|
+
useDispatch: vi2.fn((_storeName) => ({})),
|
|
131
|
+
select: vi2.fn((_storeName) => ({})),
|
|
132
|
+
dispatch: vi2.fn((_storeName) => ({})),
|
|
133
|
+
subscribe: vi2.fn(),
|
|
134
|
+
registerStore: vi2.fn()
|
|
135
|
+
};
|
|
136
|
+
var mockWordPressi18n = {
|
|
137
|
+
__: vi2.fn((text) => text),
|
|
138
|
+
_x: vi2.fn((text) => text),
|
|
139
|
+
_n: vi2.fn(
|
|
140
|
+
(single, plural, number) => number === 1 ? single : plural
|
|
141
|
+
),
|
|
142
|
+
sprintf: vi2.fn((format, ..._args) => format),
|
|
143
|
+
setLocaleData: vi2.fn()
|
|
144
|
+
};
|
|
145
|
+
var mockWordPressComponents = {
|
|
146
|
+
Button: vi2.fn(({ children }) => children),
|
|
147
|
+
TextControl: vi2.fn(() => null),
|
|
148
|
+
SelectControl: vi2.fn(() => null),
|
|
149
|
+
RangeControl: vi2.fn(() => null),
|
|
150
|
+
ToggleControl: vi2.fn(() => null),
|
|
151
|
+
Panel: vi2.fn(({ children }) => children),
|
|
152
|
+
PanelBody: vi2.fn(({ children }) => children),
|
|
153
|
+
PanelRow: vi2.fn(({ children }) => children)
|
|
154
|
+
};
|
|
155
|
+
var mockWordPressBlockEditor = {
|
|
156
|
+
InspectorControls: vi2.fn(({ children }) => children),
|
|
157
|
+
BlockControls: vi2.fn(({ children }) => children),
|
|
158
|
+
useBlockProps: vi2.fn((props) => ({
|
|
159
|
+
className: "wp-block",
|
|
160
|
+
...props
|
|
161
|
+
})),
|
|
162
|
+
RichText: vi2.fn(() => null),
|
|
163
|
+
MediaUpload: vi2.fn(() => null),
|
|
164
|
+
MediaUploadCheck: vi2.fn(({ children }) => children),
|
|
165
|
+
ColorPalette: vi2.fn(() => null),
|
|
166
|
+
URLInput: vi2.fn(() => null)
|
|
167
|
+
};
|
|
168
|
+
var mockWordPressElement = {
|
|
169
|
+
createElement: vi2.fn((type, props, ...children) => ({
|
|
170
|
+
type,
|
|
171
|
+
props: { ...props, children }
|
|
172
|
+
})),
|
|
173
|
+
Fragment: vi2.fn(({ children }) => children),
|
|
174
|
+
useState: vi2.fn((initialState) => [initialState, vi2.fn()]),
|
|
175
|
+
useEffect: vi2.fn((_effect, _deps) => void 0),
|
|
176
|
+
useRef: vi2.fn((initialValue) => ({ current: initialValue })),
|
|
177
|
+
useMemo: vi2.fn((factory, _deps) => factory()),
|
|
178
|
+
useCallback: vi2.fn((callback, _deps) => callback)
|
|
179
|
+
};
|
|
180
|
+
var mockWordPressApiFetch = vi2.fn(
|
|
181
|
+
(_options) => Promise.resolve({ ok: true, json: () => Promise.resolve({}) })
|
|
182
|
+
);
|
|
183
|
+
function setupWordPressMocks() {
|
|
184
|
+
vi2.mock("@wordpress/blocks", () => mockWordPressBlocks);
|
|
185
|
+
vi2.mock("@wordpress/data", () => mockWordPressData);
|
|
186
|
+
vi2.mock("@wordpress/i18n", () => mockWordPressi18n);
|
|
187
|
+
vi2.mock("@wordpress/components", () => mockWordPressComponents);
|
|
188
|
+
vi2.mock("@wordpress/block-editor", () => mockWordPressBlockEditor);
|
|
189
|
+
vi2.mock("@wordpress/element", () => mockWordPressElement);
|
|
190
|
+
vi2.mock("@wordpress/api-fetch", () => ({ default: mockWordPressApiFetch }));
|
|
191
|
+
global.wp = {
|
|
192
|
+
blocks: mockWordPressBlocks,
|
|
193
|
+
data: mockWordPressData,
|
|
194
|
+
i18n: mockWordPressi18n,
|
|
195
|
+
components: mockWordPressComponents,
|
|
196
|
+
blockEditor: mockWordPressBlockEditor,
|
|
197
|
+
element: mockWordPressElement,
|
|
198
|
+
apiFetch: mockWordPressApiFetch
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function clearWordPressMocks() {
|
|
202
|
+
Object.values(mockWordPressBlocks).forEach((mock) => {
|
|
203
|
+
if (typeof mock?.mockClear === "function") {
|
|
204
|
+
mock.mockClear();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
Object.values(mockWordPressData).forEach((mock) => {
|
|
208
|
+
if (typeof mock?.mockClear === "function") {
|
|
209
|
+
mock.mockClear();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/matchers/index.ts
|
|
215
|
+
import { expect as expect2 } from "vitest";
|
|
216
|
+
function toHaveBlockClass(received, blockName) {
|
|
217
|
+
const className = `wp-block-${blockName.replace("/", "-")}`;
|
|
218
|
+
const pass = received.classList.contains(className);
|
|
219
|
+
return {
|
|
220
|
+
pass,
|
|
221
|
+
message: () => pass ? `Expected element not to have block class ${className}` : `Expected element to have block class ${className}`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function toBeRegisteredBlock(received) {
|
|
225
|
+
const { getBlockType } = global.wp?.blocks || {};
|
|
226
|
+
if (!getBlockType) {
|
|
227
|
+
return {
|
|
228
|
+
pass: false,
|
|
229
|
+
message: () => "WordPress blocks API is not available"
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const blockType = getBlockType(received);
|
|
233
|
+
const pass = blockType !== void 0;
|
|
234
|
+
return {
|
|
235
|
+
pass,
|
|
236
|
+
message: () => pass ? `Expected block ${received} not to be registered` : `Expected block ${received} to be registered`
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function toHaveBlockAttributes(received, expectedAttributes) {
|
|
240
|
+
if (!received || !received.attributes) {
|
|
241
|
+
return {
|
|
242
|
+
pass: false,
|
|
243
|
+
message: () => "Block does not have attributes property"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const actualAttributes = Object.keys(received.attributes);
|
|
247
|
+
const missingAttributes = expectedAttributes.filter(
|
|
248
|
+
(attr) => !actualAttributes.includes(attr)
|
|
249
|
+
);
|
|
250
|
+
const pass = missingAttributes.length === 0;
|
|
251
|
+
return {
|
|
252
|
+
pass,
|
|
253
|
+
message: () => pass ? `Expected block not to have attributes: ${expectedAttributes.join(", ")}` : `Expected block to have attributes: ${missingAttributes.join(", ")}`
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function toBeValidWordPressBlock(received) {
|
|
257
|
+
const hasDataType = received.hasAttribute("data-type");
|
|
258
|
+
const hasWpBlock = received.classList.contains("wp-block") || Array.from(received.classList).some((c) => c.startsWith("wp-block-"));
|
|
259
|
+
const pass = hasDataType && hasWpBlock;
|
|
260
|
+
return {
|
|
261
|
+
pass,
|
|
262
|
+
message: () => pass ? "Expected element not to be a valid WordPress block" : "Expected element to be a valid WordPress block (must have data-type attribute and wp-block class)"
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function toBeValidBlockMarkup(received) {
|
|
266
|
+
const hasStartComment = received.includes("<!-- wp:");
|
|
267
|
+
const hasEndComment = received.includes("<!-- /wp:") || received.includes("/-->");
|
|
268
|
+
const pass = hasStartComment && hasEndComment;
|
|
269
|
+
return {
|
|
270
|
+
pass,
|
|
271
|
+
message: () => pass ? "Expected string not to be valid block markup" : "Expected string to be valid block markup (must include WordPress block comments)"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function setupCustomMatchers() {
|
|
275
|
+
expect2.extend({
|
|
276
|
+
toHaveBlockClass,
|
|
277
|
+
toBeRegisteredBlock,
|
|
278
|
+
toHaveBlockAttributes,
|
|
279
|
+
toBeValidWordPressBlock,
|
|
280
|
+
toBeValidBlockMarkup
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/vitest.ts
|
|
285
|
+
export * from "@testing-library/react";
|
|
286
|
+
export * from "@testing-library/user-event";
|
|
287
|
+
import { expect as expect3, describe, it, test, beforeAll, afterAll, beforeEach, afterEach, vi as vi3 } from "vitest";
|
|
288
|
+
export {
|
|
289
|
+
afterAll,
|
|
290
|
+
afterEach,
|
|
291
|
+
beforeAll,
|
|
292
|
+
beforeEach,
|
|
293
|
+
clearWordPressMocks,
|
|
294
|
+
createMockAttributes,
|
|
295
|
+
createMockBlockContext,
|
|
296
|
+
describe,
|
|
297
|
+
expect3 as expect,
|
|
298
|
+
it,
|
|
299
|
+
mockWordPressApiFetch,
|
|
300
|
+
mockWordPressBlockEditor,
|
|
301
|
+
mockWordPressBlocks,
|
|
302
|
+
mockWordPressComponents,
|
|
303
|
+
mockWordPressData,
|
|
304
|
+
mockWordPressElement,
|
|
305
|
+
mockWordPressi18n,
|
|
306
|
+
renderBlockEdit,
|
|
307
|
+
renderBlockSave,
|
|
308
|
+
setupCustomMatchers,
|
|
309
|
+
setupWordPressMocks,
|
|
310
|
+
test,
|
|
311
|
+
testBlockAttributes,
|
|
312
|
+
testBlockRegistration,
|
|
313
|
+
testBlockSaveOutput,
|
|
314
|
+
toBeRegisteredBlock,
|
|
315
|
+
toBeValidBlockMarkup,
|
|
316
|
+
toBeValidWordPressBlock,
|
|
317
|
+
toHaveBlockAttributes,
|
|
318
|
+
toHaveBlockClass,
|
|
319
|
+
vi3 as vi,
|
|
320
|
+
waitForBlockRender
|
|
321
|
+
};
|
|
322
|
+
//# sourceMappingURL=vitest.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stratawp/testing",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Comprehensive testing utilities for StrataWP themes",
|
|
5
|
+
"author": "Jon Imms",
|
|
6
|
+
"license": "GPL-3.0-or-later",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"wordpress",
|
|
10
|
+
"testing",
|
|
11
|
+
"vitest",
|
|
12
|
+
"playwright",
|
|
13
|
+
"e2e",
|
|
14
|
+
"unit-testing",
|
|
15
|
+
"stratawp"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/JonImmsWordpressDev/StrataWP.git",
|
|
20
|
+
"directory": "packages/testing"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./vitest": {
|
|
28
|
+
"types": "./dist/vitest.d.ts",
|
|
29
|
+
"import": "./dist/vitest.js"
|
|
30
|
+
},
|
|
31
|
+
"./playwright": {
|
|
32
|
+
"types": "./dist/playwright.d.ts",
|
|
33
|
+
"import": "./dist/playwright.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"vitest.config.ts",
|
|
39
|
+
"playwright.config.ts"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@testing-library/react": "^14.1.2",
|
|
43
|
+
"@testing-library/jest-dom": "^6.1.5",
|
|
44
|
+
"@testing-library/user-event": "^14.5.1",
|
|
45
|
+
"@vitest/ui": "^1.0.4",
|
|
46
|
+
"vitest": "^1.0.4",
|
|
47
|
+
"jsdom": "^23.0.1",
|
|
48
|
+
"happy-dom": "^12.10.3",
|
|
49
|
+
"@playwright/test": "^1.40.1",
|
|
50
|
+
"@wordpress/jest-preset-default": "^11.21.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/react": "^18.2.45",
|
|
54
|
+
"@vitest/coverage-v8": "^1.0.4",
|
|
55
|
+
"tsup": "^8.0.1",
|
|
56
|
+
"typescript": "^5.3.3"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": "^18.0.0",
|
|
60
|
+
"react-dom": "^18.0.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"dev": "tsup --watch",
|
|
64
|
+
"build": "tsup",
|
|
65
|
+
"test": "vitest",
|
|
66
|
+
"test:coverage": "vitest --coverage",
|
|
67
|
+
"test:e2e": "playwright test",
|
|
68
|
+
"lint": "eslint src"
|
|
69
|
+
}
|
|
70
|
+
}
|