Skip to content

Commit fe0d5cc

Browse files
committed
test: verify every generated selector resolves to its needle
1 parent 532c9e3 commit fe0d5cc

1 file changed

Lines changed: 51 additions & 13 deletions

File tree

test/scenarios.ts

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,6 @@ async function testScenario(
9898
const scenario: ParsedScenario = scenarioUtilities.parseScenario(
9999
doc.body,
100100
);
101-
const needlesById = new Map(
102-
scenario.needles.map((needle) => [needle.id, needle]),
103-
);
104-
105101
const result: ScenarioTestResult = {
106102
success: [],
107103
error: [],
@@ -115,9 +111,58 @@ async function testScenario(
115111
});
116112
}
117113

114+
const generatedByNeedle = new Map<string, string>();
115+
116+
scenario.needles.forEach(({ id, elements, root }) => {
117+
if (elements.length === 0) {
118+
return;
119+
}
120+
const generatedSelector = CssSelectorGenerator.getCssSelector(
121+
elements.length === 1 ? elements[0] : elements,
122+
root
123+
? { ...scenario.metadata.options, root }
124+
: scenario.metadata.options,
125+
);
126+
generatedByNeedle.set(id, generatedSelector);
127+
128+
// Every generated selector must resolve back to exactly the elements
129+
// it was generated for, searched from the same root the generator
130+
// used. This catches malformed and under-specified selectors that an
131+
// expected-string comparison cannot express.
132+
const searchRoot = (root ?? elements[0].getRootNode()) as ParentNode;
133+
let matched: Element[];
134+
try {
135+
matched = Array.from(searchRoot.querySelectorAll(generatedSelector));
136+
} catch {
137+
result.error.push({
138+
key: id,
139+
expectation: "a valid selector",
140+
selector: generatedSelector,
141+
});
142+
return;
143+
}
144+
145+
const isExact =
146+
matched.length === elements.length &&
147+
elements.every((element) => matched.includes(element));
148+
if (isExact) {
149+
result.success.push({
150+
key: id,
151+
expectation: "resolves to its own elements",
152+
selector: generatedSelector,
153+
});
154+
} else {
155+
result.error.push({
156+
key: id,
157+
expectation: `to resolve to its own ${String(elements.length)} element(s)`,
158+
selector: `${generatedSelector} resolved to ${String(matched.length)}`,
159+
});
160+
}
161+
});
162+
118163
scenario.expectations.forEach(({ needleId, selector, negative }) => {
119-
const needle = needlesById.get(needleId);
120-
if (!needle || needle.elements.length === 0) {
164+
const generatedSelector = generatedByNeedle.get(needleId);
165+
if (generatedSelector === undefined) {
121166
result.error.push({
122167
key: selector,
123168
expectation: selector,
@@ -126,13 +171,6 @@ async function testScenario(
126171
return;
127172
}
128173

129-
const { elements, root } = needle;
130-
const generatedSelector = CssSelectorGenerator.getCssSelector(
131-
elements.length === 1 ? elements[0] : elements,
132-
root
133-
? { ...scenario.metadata.options, root }
134-
: scenario.metadata.options,
135-
);
136174
const matches = selector === generatedSelector;
137175
result[matches !== negative ? "success" : "error"].push({
138176
expectation: negative ? `anything but ${selector}` : selector,

0 commit comments

Comments
 (0)