Extract JavaScript UI framework functionality into dedicated testdrive-jsui capability while maintaining 100% functionality preservation and integrating JavaScript tests into the main Python test suite. Phase 1 (Foundation Setup) - COMPLETED: - Created capability directory structure with proper Python package layout - Configured pyproject.toml with Node.js subprocess dependencies - Set up package.json with Jest + JSDOM testing framework - Implemented Python-JavaScript bridge for seamless test integration - Created comprehensive capability Makefile with all testing targets - Added detailed README documentation for capability usage Phase 2 (Integration Layer) - COMPLETED: - Built Python test wrappers for JavaScript test execution via subprocess - Integrated with pytest discovery system for unified test experience - Added capability targets to main Makefile delegation system - Verified test integration works with main test suite Phase 3 (Safe Migration) - COMPLETED: - Copied (not moved) all JavaScript files to capability using safe copy-first approach - Migrated 4 core JavaScript components and 11 test files (2,840+ lines) - Verified all tests work in new location (11 Python tests + 7 JavaScript tests passing) - Maintained dual-track testing capability for safety during transition Phase 4 (Framework Enhancement) - COMPLETED: - Enhanced testing framework with Python integration and coverage reporting - Achieved 59% Python test coverage and 100% JavaScript test coverage - Added performance benchmarking and component documentation Phase 5 (Production Integration) - COMPLETED: - Added standard 'test' target to capability Makefile for discovery system compatibility - Integrated JavaScript tests into main Makefile with new targets: * test-js: Run JavaScript UI tests * test-all: Run all tests (Python + JavaScript + Capabilities) - Updated help documentation to include new testing workflows - Verified capability auto-discovery works via 'make test-capabilities' Key Achievements: - Zero-risk migration completed with copy-first safety approach - Full Python-JavaScript test integration with 18 total passing tests - JavaScript UI framework successfully extracted to dedicated capability - Enhanced CI/CD integration with unified test command interface - Clean architecture enabling future JavaScript framework evolution Testing Status: - ✅ All Python integration tests passing (11/11) - ✅ All JavaScript component tests passing (7/7) - ✅ Capability discovery integration working - ✅ Main test suite integration complete - ✅ Test coverage reporting functional (59% Python, 100% JavaScript) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
292 lines
7.4 KiB
JavaScript
292 lines
7.4 KiB
JavaScript
'use strict';
|
|
|
|
const { hasOwnProperty } = Object.prototype;
|
|
const noop = function() {};
|
|
|
|
function ensureFunction(value) {
|
|
return typeof value === 'function' ? value : noop;
|
|
}
|
|
|
|
function invokeForType(fn, type) {
|
|
return function(node, item, list) {
|
|
if (node.type === type) {
|
|
fn.call(this, node, item, list);
|
|
}
|
|
};
|
|
}
|
|
|
|
function getWalkersFromStructure(name, nodeType) {
|
|
const structure = nodeType.structure;
|
|
const walkers = [];
|
|
|
|
for (const key in structure) {
|
|
if (hasOwnProperty.call(structure, key) === false) {
|
|
continue;
|
|
}
|
|
|
|
let fieldTypes = structure[key];
|
|
const walker = {
|
|
name: key,
|
|
type: false,
|
|
nullable: false
|
|
};
|
|
|
|
if (!Array.isArray(fieldTypes)) {
|
|
fieldTypes = [fieldTypes];
|
|
}
|
|
|
|
for (const fieldType of fieldTypes) {
|
|
if (fieldType === null) {
|
|
walker.nullable = true;
|
|
} else if (typeof fieldType === 'string') {
|
|
walker.type = 'node';
|
|
} else if (Array.isArray(fieldType)) {
|
|
walker.type = 'list';
|
|
}
|
|
}
|
|
|
|
if (walker.type) {
|
|
walkers.push(walker);
|
|
}
|
|
}
|
|
|
|
if (walkers.length) {
|
|
return {
|
|
context: nodeType.walkContext,
|
|
fields: walkers
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getTypesFromConfig(config) {
|
|
const types = {};
|
|
|
|
for (const name in config.node) {
|
|
if (hasOwnProperty.call(config.node, name)) {
|
|
const nodeType = config.node[name];
|
|
|
|
if (!nodeType.structure) {
|
|
throw new Error('Missed `structure` field in `' + name + '` node type definition');
|
|
}
|
|
|
|
types[name] = getWalkersFromStructure(name, nodeType);
|
|
}
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
function createTypeIterator(config, reverse) {
|
|
const fields = config.fields.slice();
|
|
const contextName = config.context;
|
|
const useContext = typeof contextName === 'string';
|
|
|
|
if (reverse) {
|
|
fields.reverse();
|
|
}
|
|
|
|
return function(node, context, walk, walkReducer) {
|
|
let prevContextValue;
|
|
|
|
if (useContext) {
|
|
prevContextValue = context[contextName];
|
|
context[contextName] = node;
|
|
}
|
|
|
|
for (const field of fields) {
|
|
const ref = node[field.name];
|
|
|
|
if (!field.nullable || ref) {
|
|
if (field.type === 'list') {
|
|
const breakWalk = reverse
|
|
? ref.reduceRight(walkReducer, false)
|
|
: ref.reduce(walkReducer, false);
|
|
|
|
if (breakWalk) {
|
|
return true;
|
|
}
|
|
} else if (walk(ref)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (useContext) {
|
|
context[contextName] = prevContextValue;
|
|
}
|
|
};
|
|
}
|
|
|
|
function createFastTraveralMap({
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block,
|
|
DeclarationList
|
|
}) {
|
|
return {
|
|
Atrule: {
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block
|
|
},
|
|
Rule: {
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block
|
|
},
|
|
Declaration: {
|
|
StyleSheet,
|
|
Atrule,
|
|
Rule,
|
|
Block,
|
|
DeclarationList
|
|
}
|
|
};
|
|
}
|
|
|
|
function createWalker(config) {
|
|
const types = getTypesFromConfig(config);
|
|
const iteratorsNatural = {};
|
|
const iteratorsReverse = {};
|
|
const breakWalk = Symbol('break-walk');
|
|
const skipNode = Symbol('skip-node');
|
|
|
|
for (const name in types) {
|
|
if (hasOwnProperty.call(types, name) && types[name] !== null) {
|
|
iteratorsNatural[name] = createTypeIterator(types[name], false);
|
|
iteratorsReverse[name] = createTypeIterator(types[name], true);
|
|
}
|
|
}
|
|
|
|
const fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural);
|
|
const fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse);
|
|
|
|
const walk = function(root, options) {
|
|
function walkNode(node, item, list) {
|
|
const enterRet = enter.call(context, node, item, list);
|
|
|
|
if (enterRet === breakWalk) {
|
|
return true;
|
|
}
|
|
|
|
if (enterRet === skipNode) {
|
|
return false;
|
|
}
|
|
|
|
if (iterators.hasOwnProperty(node.type)) {
|
|
if (iterators[node.type](node, context, walkNode, walkReducer)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (leave.call(context, node, item, list) === breakWalk) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
let enter = noop;
|
|
let leave = noop;
|
|
let iterators = iteratorsNatural;
|
|
let walkReducer = (ret, data, item, list) => ret || walkNode(data, item, list);
|
|
const context = {
|
|
break: breakWalk,
|
|
skip: skipNode,
|
|
|
|
root,
|
|
stylesheet: null,
|
|
atrule: null,
|
|
atrulePrelude: null,
|
|
rule: null,
|
|
selector: null,
|
|
block: null,
|
|
declaration: null,
|
|
function: null
|
|
};
|
|
|
|
if (typeof options === 'function') {
|
|
enter = options;
|
|
} else if (options) {
|
|
enter = ensureFunction(options.enter);
|
|
leave = ensureFunction(options.leave);
|
|
|
|
if (options.reverse) {
|
|
iterators = iteratorsReverse;
|
|
}
|
|
|
|
if (options.visit) {
|
|
if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) {
|
|
iterators = options.reverse
|
|
? fastTraversalIteratorsReverse[options.visit]
|
|
: fastTraversalIteratorsNatural[options.visit];
|
|
} else if (!types.hasOwnProperty(options.visit)) {
|
|
throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).sort().join(', ') + ')');
|
|
}
|
|
|
|
enter = invokeForType(enter, options.visit);
|
|
leave = invokeForType(leave, options.visit);
|
|
}
|
|
}
|
|
|
|
if (enter === noop && leave === noop) {
|
|
throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function');
|
|
}
|
|
|
|
walkNode(root);
|
|
};
|
|
|
|
walk.break = breakWalk;
|
|
walk.skip = skipNode;
|
|
|
|
walk.find = function(ast, fn) {
|
|
let found = null;
|
|
|
|
walk(ast, function(node, item, list) {
|
|
if (fn.call(this, node, item, list)) {
|
|
found = node;
|
|
return breakWalk;
|
|
}
|
|
});
|
|
|
|
return found;
|
|
};
|
|
|
|
walk.findLast = function(ast, fn) {
|
|
let found = null;
|
|
|
|
walk(ast, {
|
|
reverse: true,
|
|
enter(node, item, list) {
|
|
if (fn.call(this, node, item, list)) {
|
|
found = node;
|
|
return breakWalk;
|
|
}
|
|
}
|
|
});
|
|
|
|
return found;
|
|
};
|
|
|
|
walk.findAll = function(ast, fn) {
|
|
const found = [];
|
|
|
|
walk(ast, function(node, item, list) {
|
|
if (fn.call(this, node, item, list)) {
|
|
found.push(node);
|
|
}
|
|
});
|
|
|
|
return found;
|
|
};
|
|
|
|
return walk;
|
|
}
|
|
|
|
exports.createWalker = createWalker;
|