match - Add css match functions with basic test cases

This commit is contained in:
2019-09-20 11:27:55 +05:30
parent 051bb1f543
commit c47a4dddbd
3 changed files with 180 additions and 15 deletions
+4
View File
@@ -4,6 +4,7 @@ import {
findNodeByComponentRef, findNodeByComponentRef,
findNodeByComponentName findNodeByComponentName
} from "./findNode"; } from "./findNode";
import { matchGenerator, matchAll, matchFirst } from "./match";
import * as Utils from "./utils"; import * as Utils from "./utils";
export { export {
@@ -12,5 +13,8 @@ export {
findNodeByComponent, findNodeByComponent,
findNodeByComponentRef, findNodeByComponentRef,
findNodeByComponentName, findNodeByComponentName,
matchGenerator,
matchAll,
matchFirst,
Utils Utils
}; };
+78 -15
View File
@@ -1,20 +1,22 @@
import CSSwhat from "css-what"; import * as CSSWhat from "css-what";
import { traverseGenerator } from "./traverse"; import { traverseGenerator } from "./traverse";
import { FiberNode } from "./mocked-types"; import { FiberNode } from "./mocked-types";
import { isNodeNotHtmlLike } from "./utils"; import { isNodeNotHtmlLike } from "./utils";
function* matchGenerator( function* matchGenerator(
node: FiberNode, node: FiberNode,
match: string | CSSwhat.Selector[][] match: string | CSSWhat.Selector[][]
): IterableIterator<FiberNode> { ): IterableIterator<FiberNode> {
// Either parse match string or allow parsed match object as it is // Either parse match string or allow parsed match object as it is
let matchParsed: CSSwhat.Selector[][]; let matchParsed: CSSWhat.Selector[][];
if (typeof match === "string") { if (typeof match === "string") {
matchParsed = CSSwhat.parse(match); matchParsed = CSSWhat.parse(match, {
lowerCaseTags: false,
lowerCaseAttributeNames: false
});
} else { } else {
matchParsed = match; matchParsed = match;
} }
// If selector is a combination of multiple basic selectors (a, b), // If selector is a combination of multiple basic selectors (a, b),
// pass them separately to matchGenerator and combine their results one after another // pass them separately to matchGenerator and combine their results one after another
const selectorsCount = matchParsed.length; const selectorsCount = matchParsed.length;
@@ -35,20 +37,46 @@ function* matchGenerator(
let currentMatchingNodes: FiberNode[] = [node]; let currentMatchingNodes: FiberNode[] = [node];
let currentMatchingSelectorPartIndex = 0; let currentMatchingSelectorPartIndex = 0;
let currentMatchingSelectorPart: CSSwhat.Selector = let lastRelationshipSelectorPart: CSSWhat.Selector | undefined = undefined;
parsedSelector[currentMatchingSelectorPartIndex];
let lastRelationshipSelectorPart: CSSwhat.Selector | undefined = undefined;
while (currentMatchingSelectorPartIndex < parsedSelector.length) { while (currentMatchingSelectorPartIndex < parsedSelector.length) {
const nextMatchingNodes: FiberNode[] = []; const nextMatchingNodes: FiberNode[] = [];
for (const currentNode of currentMatchingNodes) { for (const currentNode of currentMatchingNodes) {
const currentMatchingSelectorPart: CSSWhat.Selector =
parsedSelector[currentMatchingSelectorPartIndex];
if (["tag"].includes(currentMatchingSelectorPart.type)) { if (["tag"].includes(currentMatchingSelectorPart.type)) {
const traverseIterator = traverseGenerator(currentNode); const startParams = {
skipSelfForStartNode: true,
skipSiblingForStartNode: true
};
let nextParams: {
skipChild?: boolean;
skipSibling?: boolean;
} = {};
if (
lastRelationshipSelectorPart === undefined ||
lastRelationshipSelectorPart.type === "descendant"
) {
nextParams = { skipChild: false, skipSibling: false };
} else if (lastRelationshipSelectorPart.type === "child") {
// visit only first level of child
nextParams = { skipChild: true };
} else if (lastRelationshipSelectorPart.type === "sibling") {
// visit only siblings of start node
nextParams = { skipChild: true, skipSibling: true };
startParams.skipSiblingForStartNode = false;
}
const traverseIterator = traverseGenerator(currentNode, startParams);
// Handle supported non-traversal parts here // Handle supported non-traversal parts here
if (currentMatchingSelectorPart.type == "tag") { if (currentMatchingSelectorPart.type == "tag") {
for (const tmpNode of traverseIterator) { let tmpNode: FiberNode;
while (
!({ value: tmpNode } = traverseIterator.next(nextParams)).done
) {
if ( if (
isNodeNotHtmlLike(tmpNode) && isNodeNotHtmlLike(tmpNode) &&
tmpNode.type.name === currentMatchingSelectorPart.name tmpNode.type.name === currentMatchingSelectorPart.name
@@ -57,11 +85,15 @@ function* matchGenerator(
} }
} }
} }
traverseIterator.throw && // traverseIterator.throw &&
traverseIterator.throw(new Error("cleanup")); // traverseIterator.throw(new Error("cleanup"));
} else if (["descendant"].includes(currentMatchingSelectorPart.type)) { } else if (
// Handle traversal parts here ["descendant", "child"].includes(currentMatchingSelectorPart.type)
) {
// Handle traversal parts here - Save for look back in next part
lastRelationshipSelectorPart = currentMatchingSelectorPart; lastRelationshipSelectorPart = currentMatchingSelectorPart;
// Preserve currentMatchingNodes
nextMatchingNodes.push(...currentMatchingNodes);
} else { } else {
// For unhandled parts // For unhandled parts
lastRelationshipSelectorPart = undefined; lastRelationshipSelectorPart = undefined;
@@ -69,10 +101,41 @@ function* matchGenerator(
} }
currentMatchingNodes = nextMatchingNodes; currentMatchingNodes = nextMatchingNodes;
currentMatchingSelectorPartIndex += 1;
}
for (const tmpNode of currentMatchingNodes) {
yield tmpNode;
} }
return; return;
} }
} }
export { matchGenerator }; function matchAll(
node: FiberNode,
match: string | CSSWhat.Selector[][]
): Array<FiberNode> {
return [...matchGenerator(node, match)];
}
function matchFirst(
node: FiberNode,
match: string | CSSWhat.Selector[][]
): FiberNode | null {
const matchIterator = matchGenerator(node, match);
const firstResult = matchIterator.next();
// Cancel generator
matchIterator.throw && matchIterator.throw(new Error("Cleanup"));
// If match found, return that
if (!firstResult.done) {
return firstResult.value;
}
// Else return null
return null;
}
export { matchGenerator, matchAll, matchFirst };
+98
View File
@@ -0,0 +1,98 @@
import * as React from "react";
// Import stuff from src
import {
// matchGenerator,
matchAll
// matchFirst
} from "../src";
import {
FiberNodeForComponentClass
// FiberNodeForInstrinsicElement,
// FiberNodeForFunctionComponent
} from "../src/mocked-types";
// Import test helpers and sample components
import { mountAndGetRootNode } from "./utils/mountInEnzyme";
import {
// createClassComponents,
// createFunctionComponents,
createClassComponent,
createClassComponents
} from "./utils/createComponent";
describe("matchGenerator", () => {
let container: HTMLDivElement;
beforeEach(() => {
container = document.body.appendChild(document.createElement("div"));
});
afterEach(() => {
document.body.removeChild(container);
});
describe("basic", () => {
it("should work for depth=1 class", () => {
const C1 = createClassComponent("C1");
function CRoot() {
return <C1 />;
}
const rootNode = mountAndGetRootNode(CRoot, container);
const nodes = matchAll(rootNode, "C1");
// Can't be zero, in any case
expect(nodes.length).not.toBe(0);
// Check that only one node is yielded
expect(nodes.length).toBe(1);
// Check that type is correct
expect((nodes[0] as FiberNodeForComponentClass).type).toBe(C1);
});
it("should work for depth=2 class", () => {
const [C1, C2] = createClassComponents(["C1", "C2"]);
function CRoot() {
return (
<C1>
<C2 />
</C1>
);
}
const rootNode = mountAndGetRootNode(CRoot, container);
const nodes = matchAll(rootNode, "C2");
// Can't be zero, in any case
expect(nodes.length).not.toBe(0);
// Check that only one node is yielded
expect(nodes.length).toBe(1);
// Check that type is correct
expect((nodes[0] as FiberNodeForComponentClass).type).toBe(C2);
expect((nodes[0] as FiberNodeForComponentClass).type).not.toBe(C1);
});
it("should work for depth=2 class with descendant selector", () => {
const [C1, C2] = createClassComponents(["C1", "C2"]);
function CRoot() {
return (
<C1>
<C2 />
</C1>
);
}
const rootNode = mountAndGetRootNode(CRoot, container);
const nodes = matchAll(rootNode, "C1 C2");
// Can't be zero, in any case
expect(nodes.length).not.toBe(0);
// Check that only one node is yielded
expect(nodes.length).toBe(1);
// Check that type is correct
expect((nodes[0] as FiberNodeForComponentClass).type).toBe(C2);
expect((nodes[0] as FiberNodeForComponentClass).type).not.toBe(C1);
});
});
});