Skip to content

Commit 954bacc

Browse files
ashleyshawclaude
andcommitted
refactor: Reorganize project structure for better maintainability
This commit reorganizes the codebase into a more logical structure with dedicated directories for different types of scripts and tests. **Major Changes:** - Moved utility scripts to scripts/utils/ - Moved validation scripts to scripts/validation/ - Moved dry-run tests to scripts/dry-run/ - Consolidated test setup files to .github/__tests__/ and .github/tests/ - Moved fixtures to appropriate locations - Updated schema file locations - Added comprehensive README files for new directories **Files Reorganized:** - scripts/utils/: audit-frontmatter.js, check-markdown-references.js, count-tokens.js, fix-instruction-references.js, scan-mustache-variables.js, scan.js, update-version.js - scripts/validation/: validate-mustache-registry.js, validate-plugin-config.js - scripts/dry-run/: dry-run-config.js, related tests - Test helpers consolidated and duplicated removed **Benefits:** - Clearer separation of concerns - Easier to find and maintain scripts - Better test organization - Reduced duplication of test utilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent f770bcd commit 954bacc

File tree

87 files changed

+3207
-5241
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+3207
-5241
lines changed

.github/__tests__/setup-tests.js

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/**
2+
* Jest Setup File
3+
*
4+
* Runs after the test framework is installed.
5+
* Use this for global test configuration.
6+
*
7+
* @package multi-block-plugin-scaffold
8+
* @see https://jestjs.io/docs/configuration#setupfilesafterenv-array
9+
*/
10+
11+
// Set test timeout
12+
jest.setTimeout(10000);
13+
14+
require('@testing-library/jest-dom');
15+
16+
const React = require('react');
17+
18+
// Mock console methods to reduce noise in tests
19+
global.console = {
20+
...console,
21+
// Suppress console.log in tests unless explicitly needed
22+
log: jest.fn(),
23+
// Keep error and warn visible for debugging
24+
error: console.error,
25+
warn: console.warn,
26+
info: jest.fn(),
27+
debug: jest.fn(),
28+
};
29+
30+
const createSimpleComponent = (tag = 'div', defaultProps = {}) => ({
31+
children,
32+
...props
33+
}) => React.createElement(tag, { ...defaultProps, ...props }, children);
34+
35+
const defaultUseSelectImplementation = (selector) => {
36+
if (typeof selector !== 'function') {
37+
return undefined;
38+
}
39+
40+
const coreSelect = {
41+
getEntityRecord: jest.fn(),
42+
getMedia: jest.fn(),
43+
};
44+
45+
return selector(() => coreSelect);
46+
};
47+
48+
const mockRegisterBlockType = jest.fn();
49+
const mockUseSelect = jest.fn(defaultUseSelectImplementation);
50+
51+
const mockButton = jest.fn(({ children, ...props }) =>
52+
React.createElement('button', props, children)
53+
);
54+
mockButton.mockName('MockWPButton');
55+
56+
const ToggleControl = ({ label, checked = false, onChange, ...props }) => {
57+
const handleChange = () => {
58+
if (typeof onChange === 'function') {
59+
onChange(!checked);
60+
}
61+
};
62+
63+
return React.createElement(
64+
'input',
65+
{
66+
type: 'checkbox',
67+
'aria-label': label,
68+
checked,
69+
onChange: handleChange,
70+
...props,
71+
},
72+
null
73+
);
74+
};
75+
76+
const RangeControl = ({ label, ...props }) =>
77+
React.createElement(
78+
'input',
79+
{
80+
type: 'range',
81+
'aria-label': label,
82+
...props,
83+
},
84+
null
85+
);
86+
87+
const SelectControl = ({ label, children, ...props }) =>
88+
React.createElement(
89+
'select',
90+
{
91+
'aria-label': label,
92+
...props,
93+
},
94+
children
95+
);
96+
97+
const TextControl = ({ label, ...props }) =>
98+
React.createElement(
99+
'input',
100+
{
101+
type: 'text',
102+
'aria-label': label,
103+
...props,
104+
},
105+
null
106+
);
107+
108+
const TextareaControl = ({ label, ...props }) =>
109+
React.createElement(
110+
'textarea',
111+
{
112+
'aria-label': label,
113+
...props,
114+
},
115+
null
116+
);
117+
118+
const mockComponents = {
119+
Button: mockButton,
120+
PanelBody: createSimpleComponent('div'),
121+
ToggleControl,
122+
RangeControl,
123+
SelectControl,
124+
TextControl,
125+
TextareaControl,
126+
ComboboxControl: createSimpleComponent('div'),
127+
FormTokenField: createSimpleComponent('div'),
128+
Spinner: createSimpleComponent('div'),
129+
};
130+
131+
const InspectorControls = ({ children }) =>
132+
React.createElement(React.Fragment, null, children);
133+
const MediaUpload = ({ render }) =>
134+
React.createElement(React.Fragment, null, render({ open: jest.fn() }));
135+
const MediaUploadCheck = ({ children }) =>
136+
React.createElement(React.Fragment, null, children);
137+
138+
const mockBlockEditor = {
139+
useBlockProps: jest.fn((props = {}) => props),
140+
InspectorControls,
141+
MediaUpload,
142+
MediaUploadCheck,
143+
};
144+
145+
const mockI18n = {
146+
__: (value) => value,
147+
};
148+
149+
const mockIcons = {
150+
chevronLeft: { name: 'chevronLeft' },
151+
chevronRight: { name: 'chevronRight' },
152+
chevronDown: { name: 'chevronDown' },
153+
arrowUp: { name: 'arrowUp' },
154+
};
155+
156+
const wp = {
157+
blocks: {
158+
registerBlockType: mockRegisterBlockType,
159+
},
160+
components: mockComponents,
161+
data: {
162+
useSelect: mockUseSelect,
163+
},
164+
i18n: mockI18n,
165+
icons: mockIcons,
166+
blockEditor: mockBlockEditor,
167+
};
168+
169+
global.wp = wp;
170+
171+
jest.mock('@wordpress/blocks', () => ({
172+
registerBlockType: mockRegisterBlockType,
173+
}));
174+
jest.mock('@wordpress/data', () => ({
175+
useSelect: mockUseSelect,
176+
}));
177+
jest.mock('@wordpress/components', () => mockComponents);
178+
jest.mock('@wordpress/icons', () => mockIcons);
179+
jest.mock('@wordpress/i18n', () => mockI18n);
180+
jest.mock('@wordpress/element', () => require('react'));
181+
jest.mock('@wordpress/block-editor', () => ({
182+
useBlockProps: mockBlockEditor.useBlockProps,
183+
InspectorControls: mockBlockEditor.InspectorControls,
184+
MediaUpload: mockBlockEditor.MediaUpload,
185+
MediaUploadCheck: mockBlockEditor.MediaUploadCheck,
186+
}));
187+
188+
afterEach(() => {
189+
mockUseSelect.mockImplementation(defaultUseSelectImplementation);
190+
mockBlockEditor.useBlockProps.mockImplementation((props = {}) => props);
191+
});
192+
193+
// Global test helpers
194+
global.testHelpers = {
195+
/**
196+
* Wait for async operations to complete
197+
* @return {Promise<void>}
198+
*/
199+
async flushPromises() {
200+
return new Promise(resolve => setImmediate(resolve));
201+
},
202+
};

.github/__tests__/test-helper.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Test Helper
3+
*
4+
* Centralized test helper that re-exports utilities from other test files.
5+
* This provides a single import point for test utilities.
6+
*
7+
* @package multi-block-plugin-scaffold
8+
*/
9+
10+
const testLogger = require('./test-logger');
11+
const testUtils = require('./test-utils');
12+
13+
module.exports = {
14+
// Re-export test logger
15+
...testLogger,
16+
17+
// Re-export test utils
18+
...testUtils,
19+
};

.github/__tests__/test-logger.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Test Logger
3+
*
4+
* Provides in-memory logging for Jest tests and file-based logging for E2E/integration tests.
5+
*
6+
* Special: If you pass { inMemory: false, category: 'dryrun-debug' }, logs will be written to dryrun-debug.log in the project root.
7+
*
8+
* @package multi-block-plugin-scaffold
9+
*/
10+
11+
/**
12+
* In-memory test logger for Jest unit tests
13+
*/
14+
class InMemoryTestLogger {
15+
constructor() {
16+
this.logs = [];
17+
}
18+
19+
/**
20+
* Log info message
21+
* @param {string} message
22+
* @param {Object} data
23+
*/
24+
info(message, data = {}) {
25+
this.logs.push({ level: 'INFO', message, data, timestamp: new Date().toISOString() });
26+
}
27+
28+
/**
29+
* Log debug message
30+
* @param {string} message
31+
* @param {Object} data
32+
*/
33+
debug(message, data = {}) {
34+
this.logs.push({ level: 'DEBUG', message, data, timestamp: new Date().toISOString() });
35+
}
36+
37+
/**
38+
* Log error message
39+
* @param {string} message
40+
* @param {Object} data
41+
*/
42+
error(message, data = {}) {
43+
this.logs.push({ level: 'ERROR', message, data, timestamp: new Date().toISOString() });
44+
}
45+
46+
/**
47+
* Log warning message
48+
* @param {string} message
49+
* @param {Object} data
50+
*/
51+
warn(message, data = {}) {
52+
this.logs.push({ level: 'WARN', message, data, timestamp: new Date().toISOString() });
53+
}
54+
55+
/**
56+
* Get all logs
57+
* @return {Array} All logged messages
58+
*/
59+
getLogs() {
60+
return this.logs;
61+
}
62+
63+
/**
64+
* Get logs by level
65+
* @param {string} level
66+
* @return {Array} Logs matching the level
67+
*/
68+
getLogsByLevel(level) {
69+
return this.logs.filter(log => log.level === level.toUpperCase());
70+
}
71+
72+
/**
73+
* Clear all logs
74+
*/
75+
clear() {
76+
this.logs = [];
77+
}
78+
79+
/**
80+
* Mock save method (no-op for in-memory logger)
81+
* @return {Promise<void>}
82+
*/
83+
async save() {
84+
// No-op
85+
}
86+
}
87+
88+
/**
89+
* Factory for test logger
90+
* @param {Object} opts
91+
* @return {InMemoryTestLogger}
92+
*/
93+
function createTestLogger(opts = {}) {
94+
return new InMemoryTestLogger();
95+
}
96+
97+
module.exports = {
98+
InMemoryTestLogger,
99+
createTestLogger,
100+
};

0 commit comments

Comments
 (0)