computed-styles-regression-test
Version:
DOM & CSSOM based regression testing utility
164 lines • 6.79 kB
JavaScript
function compareElements(expected, actual, path, options) {
const differences = [];
// Compare tag names
if (expected.nodeName !== actual.nodeName) {
differences.push({
type: 'structure',
path: `${path}.nodeName`,
expected: expected.nodeName,
actual: actual.nodeName,
description: `Node name mismatch at ${path}`,
});
}
// Compare attributes (if not ignoring class names)
if (!options.ignoreClassNames) {
const expectedAttrs = { ...expected.attributes };
const actualAttrs = { ...actual.attributes };
for (const [key, value] of Object.entries(expectedAttrs)) {
if (actualAttrs[key] !== value) {
differences.push({
type: 'structure',
path: `${path}.attributes.${key}`,
expected: value,
actual: actualAttrs[key],
description: `Attribute ${key} mismatch at ${path}`,
});
}
}
for (const key of Object.keys(actualAttrs)) {
if (!(key in expectedAttrs)) {
differences.push({
type: 'structure',
path: `${path}.attributes.${key}`,
expected: undefined,
actual: actualAttrs[key],
description: `Unexpected attribute ${key} at ${path}`,
});
}
}
}
// Compare computed styles
const expectedStyles = new Map(Object.entries(expected.computedStyles));
const actualStyles = new Map(Object.entries(actual.computedStyles));
const stylesToCheck = options.styleProperties || Array.from(expectedStyles.keys());
for (const property of stylesToCheck) {
const expectedValue = expectedStyles.get(property);
const actualValue = actualStyles.get(property);
if (expectedValue !== actualValue) {
differences.push({
type: 'style',
path: `${path}.styles.${property}`,
expected: expectedValue,
actual: actualValue,
description: `Style property ${property} mismatch at ${path}`,
});
}
}
// Compare pseudo-states
const expectedPseudoStates = expected.pseudoStates || {};
const actualPseudoStates = actual.pseudoStates || {};
const allPseudoStates = new Set([
...Object.keys(expectedPseudoStates),
...Object.keys(actualPseudoStates),
]);
for (const pseudoState of allPseudoStates) {
const expectedPseudo = expectedPseudoStates[pseudoState];
const actualPseudo = actualPseudoStates[pseudoState];
if (!expectedPseudo && actualPseudo) {
differences.push({
type: 'style',
path: `${path}:${pseudoState}`,
expected: undefined,
actual: 'present',
description: `Unexpected pseudo-state ${pseudoState} at ${path}`,
});
}
else if (expectedPseudo && !actualPseudo) {
differences.push({
type: 'style',
path: `${path}:${pseudoState}`,
expected: 'present',
actual: undefined,
description: `Missing pseudo-state ${pseudoState} at ${path}`,
});
}
else if (expectedPseudo && actualPseudo) {
// Compare the styles within the pseudo-state
const expectedPseudoStylesMap = new Map(Object.entries(expectedPseudo));
const actualPseudoStylesMap = new Map(Object.entries(actualPseudo));
const pseudoStylesToCheck = options.styleProperties || Array.from(expectedPseudoStylesMap.keys());
for (const property of pseudoStylesToCheck) {
const expectedPseudoValue = expectedPseudoStylesMap.get(property);
const actualPseudoValue = actualPseudoStylesMap.get(property);
if (expectedPseudoValue !== actualPseudoValue) {
differences.push({
type: 'style',
path: `${path}:${pseudoState}.styles.${property}`,
expected: expectedPseudoValue,
actual: actualPseudoValue,
description: `Pseudo-state ${pseudoState} style property ${property} mismatch at ${path}`,
});
}
}
}
}
// Compare children count if strict structure comparison
if (options.strictStructureComparison && expected.children.length !== actual.children.length) {
differences.push({
type: 'structure',
path: `${path}.children.length`,
expected: expected.children.length,
actual: actual.children.length,
description: `Children count mismatch at ${path}`,
});
}
// Compare children recursively
const minChildrenLength = Math.min(expected.children.length, actual.children.length);
for (let i = 0; i < minChildrenLength; i++) {
const childDifferences = compareElements(expected.children[i], actual.children[i], `${path} > ${expected.children[i].uniqueSelector}`, options);
differences.push(...childDifferences);
}
return differences;
}
export function compareSnapshots(expected, actual, options = {}) {
const differences = [];
// Compare trees count
if (expected.trees.length !== actual.trees.length) {
differences.push({
type: 'structure',
path: 'trees.length',
expected: expected.trees.length,
actual: actual.trees.length,
description: 'Root elements count mismatch',
});
}
// Compare each tree
const minTreesLength = Math.min(expected.trees.length, actual.trees.length);
for (let i = 0; i < minTreesLength; i++) {
const treeDifferences = compareElements(expected.trees[i], actual.trees[i], `trees[${i}]`, options);
differences.push(...treeDifferences);
}
return {
isEqual: differences.length === 0,
differences,
};
}
export function findElementByPath(snapshot, path) {
const pathParts = path.split('.');
let current = snapshot;
for (const part of pathParts) {
if (part.includes('[') && part.includes(']')) {
const [key, indexStr] = part.split('[');
const index = Number.parseInt(indexStr.replace(']', ''), 10);
current = current[key]?.[index];
}
else {
current = current[part];
}
if (current == null) {
return null;
}
}
return current;
}
//# sourceMappingURL=comparison.js.map