From e090eca7f016e4879bf99015c4753195c5e824b2 Mon Sep 17 00:00:00 2001 From: Pavel Vostretsov Date: Tue, 16 Jan 2024 15:23:24 +0500 Subject: [PATCH] use @skbkontur/react-selenium-testing package --- db-viewer-ui/index.tsx | 4 +- db-viewer-ui/package.json | 1 + db-viewer-ui/react-selenium-prep.ts | 30 + db-viewer-ui/react-selenium-testing.js | 558 ------------------ .../DateTimeRangePicker/TimePicker.tsx | 1 - db-viewer-ui/vite.config.ts | 2 +- db-viewer-ui/yarn.lock | 38 ++ 7 files changed, 73 insertions(+), 561 deletions(-) create mode 100644 db-viewer-ui/react-selenium-prep.ts delete mode 100644 db-viewer-ui/react-selenium-testing.js diff --git a/db-viewer-ui/index.tsx b/db-viewer-ui/index.tsx index b0de0439..ce1edeab 100644 --- a/db-viewer-ui/index.tsx +++ b/db-viewer-ui/index.tsx @@ -1,4 +1,6 @@ -import "./react-selenium-testing"; +import "./react-selenium-prep"; +import "@skbkontur/react-selenium-testing"; + import React from "react"; import ReactDom from "react-dom"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; diff --git a/db-viewer-ui/package.json b/db-viewer-ui/package.json index 42518ec6..1aeb463f 100644 --- a/db-viewer-ui/package.json +++ b/db-viewer-ui/package.json @@ -56,6 +56,7 @@ }, "devDependencies": { "@skbkontur/icons": "^1.3.0", + "@skbkontur/react-selenium-testing": "^0.2.1", "@skbkontur/react-ui": "^4.1.0", "@storybook/addon-actions": "^7.0.7", "@storybook/addons": "^7.0.7", diff --git a/db-viewer-ui/react-selenium-prep.ts b/db-viewer-ui/react-selenium-prep.ts new file mode 100644 index 00000000..186a0947 --- /dev/null +++ b/db-viewer-ui/react-selenium-prep.ts @@ -0,0 +1,30 @@ +import type { ReactSeleniumTestingType } from "@skbkontur/react-selenium-testing"; + +const customization: ReactSeleniumTestingType = { + attributeWhiteList: { + error: [/.*/], + warning: [/.*/], + disabled: [/.*/], + "data-page-number": [/.*/], + disablePortal: ["ComboBoxRenderer"], + checked: [/.*/], + items: ["RadioGroup"], + value: [/Input|Textarea|RadioGroup|FilteredInput/], + activePage: ["Paging"], + pagesCount: ["Paging"], + trigger: [/Tooltip|ValidationTooltip|ValidationWrapper|ValidationWrapperV1/], + active: [/.*/], + className: [/.*/], + "data-active": [/.*/], + }, + acceptAttribute: (prevAcceptResult, componentName, propName) => { + if (componentName === "Select" && propName === "items") { + return true; + } + return prevAcceptResult; + }, +}; + +window.global ||= window; + +global.ReactSeleniumTesting = customization; diff --git a/db-viewer-ui/react-selenium-testing.js b/db-viewer-ui/react-selenium-testing.js deleted file mode 100644 index 2a48bb9e..00000000 --- a/db-viewer-ui/react-selenium-testing.js +++ /dev/null @@ -1,558 +0,0 @@ -let customAcceptAttribute = prevResult => prevResult; -let attributeWhiteList = null; - -// Inlined from ReactTypeOfSideEffect -const PerformedWork = 1; - -if (typeof ReactSeleniumTesting !== 'undefined') { - if ( - ReactSeleniumTesting && - ReactSeleniumTesting.acceptAttribute && - typeof ReactSeleniumTesting.acceptAttribute === 'function' - ) { - customAcceptAttribute = ReactSeleniumTesting.acceptAttribute; - } - - if ( - ReactSeleniumTesting && - ReactSeleniumTesting.attributeWhiteList && - typeof ReactSeleniumTesting.attributeWhiteList === 'object' - ) { - attributeWhiteList = ReactSeleniumTesting.attributeWhiteList; - } -} - -function extendStaticObject(base, overrides) { - const oldBase = Object.assign({}, base); - for (const overrideKey of Object.keys(overrides)) { - base[overrideKey] = overrides[overrideKey](oldBase); - } -} - -function injectReactDevToolsHook(injectModule, injectFiberHanlers) { - if (!global.__REACT_DEVTOOLS_GLOBAL_HOOK__) { - global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { - inject: () => {}, - supportsFiber: true, - onCommitFiberRoot: () => {}, - }; - } - const oldInject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject; - global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = x => { - const ReactMount = x.Mount; - if (oldInject) { - var id = oldInject.call(global.__REACT_DEVTOOLS_GLOBAL_HOOK__, x); - } - injectModule(x); - // Return id for React Dev Tools - return id; - }; - injectFiberHanlers(global.__REACT_DEVTOOLS_GLOBAL_HOOK__); -} - -if (process.env.enableReactTesting) { - global.ReactTesting = { - addRenderContainer: () => {}, - removeRenderContainer: () => {}, - }; - injectReactDevToolsHook(exposeReactInternalsIntoDomHook, injectFiberHanlers); -} - -// ==================================================== -// ==================== COMMON ========================= -// ==================================================== - -function appendToSet(attrContainer, name, value) { - if (value === null) return; - const attributeStringValue = attrContainer[name]; - const set = (attributeStringValue || '').split(' ').filter(x => x !== ''); - if (!set.includes(value)) { - attrContainer[name] = (attributeStringValue ? attributeStringValue + ' ' : '') + value; - } -} - -function acceptProp(componentName, propName, propValue) { - let result = - !propName.startsWith('$$') && - !propName.startsWith('on') && - propName !== 'children' && - propName !== 'data-tid' && - typeof propValue !== 'function'; - if (!result) { - return false; - } - if (attributeWhiteList != null) { - if (attributeWhiteList[propName] == null) { - result = false; - } else { - if ( - !attributeWhiteList[propName].every(componentNamePattern => acceptPattern(componentNamePattern, componentName)) - ) { - result = false; - } - } - } - if (customAcceptAttribute != null) { - result = customAcceptAttribute(result, componentName, propName); - } - return result; -} - -function acceptPattern(pattern, value) { - if (pattern == null) { - return false; - } - if (typeof pattern === 'string') { - return value === pattern; - } - if (typeof pattern.test === 'function') { - return pattern.test(value); - } - return false; -} - -function stringifySafe(value) { - if (typeof value === 'string') { - return value; - } - if (value === undefined || value === null) { - return ''; - } - try { - return JSON.stringify(value); - } catch (e) { - try { - if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) { - return JSON.stringify(value.map(x => x[0])); - } - } catch (e) { - return ''; - } - return ''; - } -} - -// ==================================================== -// ==================== FIBER ========================= -// ==================================================== - -const ReactWorkTypes = { - FunctionComponent: 'FunctionComponent', - ClassComponent: 'ClassComponent', - HostPortal: 'HostPortal', - HostComponent: 'HostComponent', - Fragment: 'Fragment', - ContextConsumer: 'ContextConsumer', - ContextProvider: 'ContextProvider', -}; - -const WorkTagsByReactVersion = { - ['<16.5']: { - [ReactWorkTypes.FunctionComponent]: 1, - [ReactWorkTypes.ClassComponent]: 2, - [ReactWorkTypes.HostPortal]: 4, - [ReactWorkTypes.HostComponent]: 5, - [ReactWorkTypes.Fragment]: 10, - [ReactWorkTypes.ContextConsumer]: 12, - [ReactWorkTypes.ContextProvider]: 13, - }, - ['>=16.5 <16.6']: { - [ReactWorkTypes.FunctionComponent]: 0, - [ReactWorkTypes.ClassComponent]: 2, - [ReactWorkTypes.HostPortal]: 6, - [ReactWorkTypes.HostComponent]: 7, - [ReactWorkTypes.Fragment]: 9, - [ReactWorkTypes.ContextConsumer]: 11, - [ReactWorkTypes.ContextProvider]: 12, - }, - ['>=16.6']: { - [ReactWorkTypes.FunctionComponent]: 0, - [ReactWorkTypes.ClassComponent]: 1, - [ReactWorkTypes.HostPortal]: 4, - [ReactWorkTypes.HostComponent]: 5, - [ReactWorkTypes.Fragment]: 7, - [ReactWorkTypes.ContextConsumer]: 9, - [ReactWorkTypes.ContextProvider]: 10, - }, -}; - -function isFiberNodeOfType(node, type) { - if (!node) return false; - const reactVersion = node.hasOwnProperty('elementType') - ? '>=16.6' - : node.hasOwnProperty('firstContextDependency') - ? '>=16.5 <16.6' - : '<16.5'; - const ReactWorkTags = WorkTagsByReactVersion[reactVersion]; - const tag = ReactWorkTags ? ReactWorkTags[type] : null; - return node.tag === tag; -} -function injectFiberHanlers(hook) { - extendStaticObject(hook, { - onCommitFiberRoot: base => (...args) => { - handleCommitFiberRoot(...args); - if (base.onCommitFiberRoot) { - base.onCommitFiberRoot(...args); - } - }, - }); -} - -function handleCommitFiberRoot(rendererID, root) { - const current = root.current; - const alternate = current.alternate; - if (alternate) { - const wasMounted = alternate.memoizedState != null && alternate.memoizedState.element != null; - const isMounted = current.memoizedState != null && current.memoizedState.element != null; - if (!wasMounted && isMounted) { - handleMountFiber(current); - } else if (wasMounted && isMounted) { - handleUpdateFiber(current, alternate); - } else if (wasMounted && !isMounted) { - // Skip unmounting - } - } else { - handleMountFiber(current); - } -} - -function handleUpdateFiber(nextFiber, prevFiber) { - let hasChildOrderChanged = false; - if (nextFiber.child !== prevFiber.child) { - let nextChild = nextFiber.child; - let prevChildAtSameIndex = prevFiber.child; - while (nextChild) { - if (nextChild.alternate) { - const prevChild = nextChild.alternate; - handleUpdateFiber(nextChild, prevChild); - if (!hasChildOrderChanged && prevChild !== prevChildAtSameIndex) { - hasChildOrderChanged = true; - } - } else { - handleMountFiber(nextChild); - if (!hasChildOrderChanged) { - hasChildOrderChanged = true; - } - } - nextChild = nextChild.sibling; - if (!hasChildOrderChanged && prevChildAtSameIndex != null) { - prevChildAtSameIndex = prevChildAtSameIndex.sibling; - } - } - if (!hasChildOrderChanged && prevChildAtSameIndex != null) { - hasChildOrderChanged = true; - } - } - updateIfNecessaryFiberNode(nextFiber, hasChildOrderChanged); -} - -function handleMountFiber(fiber) { - let node = fiber; - outer: while (true) { - if (node.child) { - node.child.return = node; - node = node.child; - continue; - } - mountFiberNode(node); - if (node == fiber) { - return; - } - if (node.sibling) { - node.sibling.return = node.return; - node = node.sibling; - continue; - } - while (node.return) { - node = node.return; - mountFiberNode(node); - if (node == fiber) { - return; - } - if (node.sibling) { - node.sibling.return = node.return; - node = node.sibling; - continue outer; - } - } - return; - } -} - -function mountFiberNode(node) { - syncDomNodeWithFiberNode(node); -} - -function updateIfNecessaryFiberNode(node, hasChildOrderChanged) { - if (!hasChildOrderChanged && !hasDataChanged(node.alternate, node)) { - return; - } - syncDomNodeWithFiberNode(node); -} - -function syncDomNodeWithFiberNode(node) { - const attrs = {}; - const visitedNodes = []; - if (isFiberNodeOfType(node, ReactWorkTypes.HostPortal) && node.sibling) { - const domElement = findDomElementByFiberNode(node.sibling); - const targetDomElement = findDomElementByFiberNode(node.sibling.return); - fillAttrsForDomElementByFiberNodeRecursive(attrs, node.sibling, visitedNodes, domElement); - fillAttrsForDomElementByFiberNodeRecursive(attrs, node.sibling.return, visitedNodes, targetDomElement); - if (domElement != null) { - if (typeof domElement.setAttribute === 'function') { - for (var attrName in attrs) { - domElement.setAttribute(attrName, attrs[attrName]); - } - } - } - } else { - const domElement = findDomElementByFiberNode(node); - fillAttrsForDomElementByFiberNodeRecursive(attrs, node, visitedNodes, domElement); - if (domElement != null) { - if (typeof domElement.setAttribute === 'function') { - for (var attrName in attrs) { - domElement.setAttribute(attrName, attrs[attrName]); - } - } - } - } -} - -function hasDataChanged(prevFiber, nextFiber) { - if (isFiberNodeOfType(prevFiber, ReactWorkTypes.ClassComponent)) { - if ((nextFiber.effectTag & PerformedWork) !== PerformedWork) { - return false; - } - if (prevFiber.stateNode.context !== nextFiber.stateNode.context) { - return true; - } - if (nextFiber.updateQueue != null && nextFiber.updateQueue.hasForceUpdate) { - return true; - } - } - return ( - prevFiber.memoizedProps !== nextFiber.memoizedProps || - prevFiber.memoizedState !== nextFiber.memoizedState || - prevFiber.ref !== nextFiber.ref || - prevFiber._debugSource !== nextFiber._debugSource - ); -} - -function findDomElementByFiberNode(node) { - let result = null; - if (typeof node.setAttribute === 'function') { - return node; - } - if (result == null && node.stateNode) { - result = findDomElementByFiberNode(node.stateNode); - } - if (result == null && node.child) { - result = findDomElementByFiberNode(node.child); - } - return result; -} - -function fillAttrsForDomElementByFiberNodeRecursive(attrContainer, node, visitedNodes, domElement) { - if (node == null || visitedNodes.includes(node)) { - return; - } - visitedNodes.push(node); - fillAttrsForDomElementByFiberNode(attrContainer, node); - if ( - isFiberNodeOfType(node, ReactWorkTypes.FunctionComponent) || - isFiberNodeOfType(node, ReactWorkTypes.ClassComponent) || - isFiberNodeOfType(node, ReactWorkTypes.ContextConsumer) || - isFiberNodeOfType(node, ReactWorkTypes.ContextProvider) || - isFiberNodeOfType(node, ReactWorkTypes.Fragment) - ) { - fillAttrsForDomElementByFiberNodeRecursive(attrContainer, node.child, visitedNodes); - } else if (isFiberNodeOfType(node, ReactWorkTypes.HostComponent)) { - // I dont know what does it mean - } else if (isFiberNodeOfType(node, ReactWorkTypes.HostPortal)) { - // I dont know what does it mean - } else { - } - if (node.return) { - const parentDomElement = findDomElementByFiberNode(node.return); - if (parentDomElement == domElement) { - fillAttrsForDomElementByFiberNodeRecursive(attrContainer, node.return, visitedNodes, domElement); - } - } - if (node && node.memoizedProps && node.key === 'portal-ref') { - if (node.return) { - const parentDomElement = findDomElementByFiberNode(node.return); - fillAttrsForDomElementByFiberNodeRecursive(attrContainer, node.return, visitedNodes, parentDomElement); - } - } -} - -function fillAttrsForDomElementByFiberNode(attrContainer, node) { - const instanceProps = node.memoizedProps; - const componentName = getFiberComponentName(node); - if (componentName) { - appendToSet(attrContainer, 'data-comp-name', componentName); - } - if (instanceProps != null) { - if (instanceProps['data-tid']) { - appendToSet(attrContainer, 'data-tid', instanceProps['data-tid']); - } - for (const prop in instanceProps) { - if (acceptProp(componentName, prop, instanceProps[prop])) { - attrContainer[`data-prop-${prop}`] = stringifySafe(instanceProps[prop]); - } - } - } else { - } -} - -function getFiberComponentName(node) { - if (node.type) { - return node.type.name; - } -} - -// ==================================================== -// =============== REACT (<16.0.0) ==================== -// ==================================================== - -function exposeReactInternalsIntoDomHook({ Mount, Reconciler }) { - const ReactMount = Mount; - if (Reconciler == null) { - return; - } - extendStaticObject(Reconciler, { - receiveComponent: base => (instance, nextElement, transaction, context) => { - base.receiveComponent(instance, nextElement, transaction, context); - - const prevElement = instance._currentElement; - if (nextElement === prevElement && context === instance._context) { - return; - } - - if (instance._currentElement && instance._currentElement.type) { - const domElement = getTargetNode(instance, ReactMount); - updateDomElement(domElement, instance, false); - } - }, - - mountComponent: base => (instance, tr, host, hostParent, hostContainerInfo, context, ...rest) => { - const result = base.mountComponent(instance, tr, host, hostParent, hostContainerInfo, context, ...rest); - if (typeof result === 'string') { - // React 0.14.* - const resultDomElement = createDomFromString(result); - if (!resultDomElement) { - return result; - } - updateDomElement(resultDomElement, instance, true); - return resultDomElement.outerHTML; - } else if (result.node) { - // React 15.* - updateDomElement(result.node, instance, true); - } - return result; - }, - }); -} - -function createDomFromString(s) { - let rootDomElement; - if (s.startsWith(' { : { "process.env.API": isDevMode ? "'fake'" : "'real'", "process.env.enableReactTesting": "true", - global: "window", }, server: { + host: "0.0.0.0", proxy: { "/db-viewer": "http://localhost:5000", }, diff --git a/db-viewer-ui/yarn.lock b/db-viewer-ui/yarn.lock index 0e725e75..aea5f768 100644 --- a/db-viewer-ui/yarn.lock +++ b/db-viewer-ui/yarn.lock @@ -2650,6 +2650,7 @@ __metadata: resolution: "@skbkontur/db-viewer-ui@workspace:." dependencies: "@skbkontur/icons": "npm:^1.3.0" + "@skbkontur/react-selenium-testing": "npm:^0.2.1" "@skbkontur/react-stack-layout": "npm:^1.0.3" "@skbkontur/react-ui": "npm:^4.1.0" "@skbkontur/react-ui-validations": "npm:^1.8.3" @@ -2705,6 +2706,32 @@ __metadata: languageName: node linkType: hard +"@skbkontur/react-selenium-testing@npm:^0.2.1": + version: 0.2.1 + resolution: "@skbkontur/react-selenium-testing@npm:0.2.1" + dependencies: + "@skbkontur/react-sorge": "npm:^0.2.1" + checksum: cb0bade94b68b95f22611f5f3bd17f3ff6d36cc8f499b40bdc0894ad5fa98259c687844386af4cdec18f4c9b94088d06e5eb4647794742fcac6e7daaeef81e7f + languageName: node + linkType: hard + +"@skbkontur/react-sorge@npm:^0.2.1": + version: 0.2.1 + resolution: "@skbkontur/react-sorge@npm:0.2.1" + dependencies: + semver: "npm:7.3.5" + peerDependencies: + react: ">=16.7 <=17.0" + react-dom: ">=16.7 <=17.0" + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: c29a029c2cfff29c1e067a7c5e9477692a3759d222eeb15a5c9d5bfdab666605b179d39430283d872acececf0142bcdccf3e942124ee75a26a680a7c485b2afb + languageName: node + linkType: hard + "@skbkontur/react-stack-layout@npm:^1.0.3": version: 1.2.0 resolution: "@skbkontur/react-stack-layout@npm:1.2.0" @@ -10107,6 +10134,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:7.3.5": + version: 7.3.5 + resolution: "semver@npm:7.3.5" + dependencies: + lru-cache: "npm:^6.0.0" + bin: + semver: bin/semver.js + checksum: 22854378594943f2988ee853c02a7471dd02eba7bf75e286b98538114590a148dd59b22775edf42fcfb354438f304b8f32a53c136d228e99068ac52c60259324 + languageName: node + linkType: hard + "semver@npm:^6.0.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1"