Repository: microsoft/griffel
Pull request #200 (2022-08-30 17:15:50)

fix(devtools): show override when overriding rule is the same
## This PR fix 2 problems in devtools.

Here's an example showing both problems: (using devtools on fluent v9 button component):

<img width="782" alt="Screenshot 2022-08-30 at 19 07 47" src="https://user-images.githubusercontent.com/28751745/187500558-fd12cf0c-4bb1-4361-82ec-4262b77801e5.png">

- Problem 1: when overriding rule is the same rule, devtools is not able to determine the overridng rule.
In the above example, 'medium' and the 'primary' slot contains the same style for focus. The one in 'medium' overrides the one in 'primary'. But devtools failed to detect it.

- Problem 2: when selector contains className like `.fxxxx.yyyy` where `.fxxxx` is the className used by griffel, devtools failed to parse this className

After this PR, both problems are fixed. 
Additionally, when clicking on a style, devtools highlights the overriding style. But in the case of problem 1 where both styles are the same, this highlighting feature highlights both styles (because they have the same griffel className). I like it. So I updated the highlighting to always highlight both the selected style, and the overriding style. I think it makes more sense than how it works before.

After PR: 
<img width="895" alt="Screenshot 2022-08-30 at 19 20 44" src="https://user-images.githubusercontent.com/28751745/187503283-7bf47d1e-ce9f-49bf-a16d-0a7d9d487076.png">




Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index 52677a4..68a7ae4 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,7 +1,7 @@
 {
   "root": true,
   "ignorePatterns": ["**/*"],
-  "plugins": ["@nrwl/nx", "import"],
+  "plugins": ["@nrwl/nx", "import", "jest"],
   "extends": ["plugin:import/typescript"],
   "settings": {
     "import/resolver": {
@@ -12,7 +12,8 @@
     }
   },
   "rules": {
-    "import/no-extraneous-dependencies": ["error", { "devDependencies": false }]
+    "import/no-extraneous-dependencies": ["error", { "devDependencies": false }],
+    "jest/no-focused-tests": "error"
   },
   "overrides": [
     {
diff --git a/change/@griffel-core-7824828a-f980-4c5d-ba33-06d73ffb351d.json b/change/@griffel-core-7824828a-f980-4c5d-ba33-06d73ffb351d.json
new file mode 100644
index 0000000..c7eb537
--- /dev/null
+++ b/change/@griffel-core-7824828a-f980-4c5d-ba33-06d73ffb351d.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix: update debug data collection to handle duplicate overrides",
+  "packageName": "@griffel/core",
+  "email": "yuanboxue@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-devtools-dcd68b39-8976-4d6a-9d04-5aa2e794d090.json b/change/@griffel-devtools-dcd68b39-8976-4d6a-9d04-5aa2e794d090.json
new file mode 100644
index 0000000..95ac7ad
--- /dev/null
+++ b/change/@griffel-devtools-dcd68b39-8976-4d6a-9d04-5aa2e794d090.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix: show override for same rule, and highlight both selected rule and overriding rule on click; fix griffel className parsing",
+  "packageName": "@griffel/devtools",
+  "email": "yuanboxue@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/package.json b/package.json
index 9a6d2d9..4ac8b0b 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
     "eslint-config-prettier": "8.1.0",
     "eslint-import-resolver-typescript": "2.5.0",
     "eslint-plugin-import": "2.25.2",
+    "eslint-plugin-jest": "^27.0.1",
     "eslint-plugin-jsx-a11y": "6.5.1",
     "eslint-plugin-react": "7.27.0",
     "eslint-plugin-react-hooks": "4.3.0",
diff --git a/packages/core/src/devtools/getDebugTree.ts b/packages/core/src/devtools/getDebugTree.ts
index 1460c92..3841f92 100644
--- a/packages/core/src/devtools/getDebugTree.ts
+++ b/packages/core/src/devtools/getDebugTree.ts
@@ -11,7 +11,12 @@ export function getDebugTree(debugSequenceHash: SequenceHash, parentNode?: Debug
   }
 
   const parentLookupItem = parentNode ? DEFINITION_LOOKUP_TABLE[parentNode.sequenceHash] : undefined;
-  const debugClassNames = getDebugClassNames(lookupItem, parentLookupItem, parentNode?.debugClassNames);
+  const debugClassNames = getDebugClassNames(
+    lookupItem,
+    parentLookupItem,
+    parentNode?.debugClassNames,
+    parentNode?.children,
+  );
 
   const node: DebugSequence = {
     sequenceHash: debugSequenceHash,
@@ -21,12 +26,14 @@ export function getDebugTree(debugSequenceHash: SequenceHash, parentNode?: Debug
   };
 
   const childrenSequences = debugData.getChildrenSequences(node.sequenceHash);
-  childrenSequences.forEach((sequence: SequenceHash) => {
-    const child = getDebugTree(sequence, node);
-    if (child) {
-      node.children.push(child);
-    }
-  });
+  childrenSequences
+    .reverse() // first process the overriding children that are merged last
+    .forEach((sequence: SequenceHash) => {
+      const child = getDebugTree(sequence, node);
+      if (child) {
+        node.children.push(child);
+      }
+    });
 
   // if it's leaf (makeStyle node), get css rules
   if (!node.children.length) {
diff --git a/packages/core/src/devtools/utils.ts b/packages/core/src/devtools/utils.ts
index 46e798a..72cf694 100644
--- a/packages/core/src/devtools/utils.ts
+++ b/packages/core/src/devtools/utils.ts
@@ -1,5 +1,5 @@
 import { CSSClasses, LookupItem } from '../types';
-import { DebugAtomicClassName } from './types';
+import { DebugAtomicClassName, DebugSequence } from './types';
 
 function getDirectionalClassName(classes: CSSClasses, direction: 'ltr' | 'rtl'): string {
   return Array.isArray(classes) ? (direction === 'rtl' ? classes[1] : classes[0]) : classes;
@@ -9,6 +9,7 @@ export function getDebugClassNames(
   lookupItem: LookupItem,
   parentLookupItem?: LookupItem,
   parentDebugClassNames?: DebugAtomicClassName[],
+  overridingSiblings?: DebugSequence[],
 ): DebugAtomicClassName[] {
   const classesMapping = lookupItem[0];
   const direction = lookupItem[1];
@@ -21,11 +22,30 @@ export function getDebugClassNames(
       const matching = parentDebugClassNames.find(({ className: parentClassName }) => parentClassName === className);
 
       if (!matching && parentLookupItem[0][propertyHash]) {
-        // style is overriden in current merging, by rules in parent debug node
+        // parent node does not have current className (style), but has current selector:
+        // style is overriden in current merging by another rule in sibling node
         overriddenBy = getDirectionalClassName(parentLookupItem[0][propertyHash], parentLookupItem[1]);
-      } else {
-        // overridden status come from higher level of merging
-        overriddenBy = matching?.overriddenBy;
+      } else if (matching && parentLookupItem[0][propertyHash]) {
+        // parent node has current className (style), and has current selector:
+        // case 1. style is not overriden during current merging; it may be overriden in higher level of merging
+        // case 2. style is overriden in current merging by exactly the same rule in sibling nodes
+        const siblingHasSameRule = overridingSiblings
+          ? overridingSiblings.filter(
+              ({ debugClassNames }) =>
+                debugClassNames.filter(({ className: siblingClassName }) => siblingClassName === className).length > 0,
+            ).length > 0
+          : false;
+        overriddenBy = siblingHasSameRule
+          ? matching.className // case 2
+          : matching.overriddenBy; // case 1
+      } else if (!matching && !parentLookupItem[0][propertyHash]) {
+        // parent node does not have current className (style), and does not have current selector:
+        // this case is not possible
+        overriddenBy = undefined;
+      } else if (matching && !parentLookupItem[0][propertyHash]) {
+        // parent node has current className (style), but does not have current selector:
+        // this case is not possible
+        overriddenBy = undefined;
       }
     }
 
diff --git a/packages/devtools/src/MonolithicRulesView.tsx b/packages/devtools/src/MonolithicRulesView.tsx
index e53e678..f1f7e8a 100644
--- a/packages/devtools/src/MonolithicRulesView.tsx
+++ b/packages/devtools/src/MonolithicRulesView.tsx
@@ -30,20 +30,29 @@ const useSingleRuleStyles = makeStyles({
 const SingleRuleView: React.FC<{ rule: RuleDetail; indent?: boolean }> = ({ rule, indent }) => {
   const { highlightedClass, setHighlightedClass } = useViewContext();
 
+  const [selected, setSelected] = React.useState(false);
+
   const handleClick = (e: React.SyntheticEvent) => {
     e.stopPropagation();
     if (rule.overriddenBy) {
+      setSelected(true);
       setHighlightedClass(highlighted => (highlighted === rule.overriddenBy ? '' : rule.overriddenBy!));
     } else {
       setHighlightedClass('');
     }
   };
 
+  React.useEffect(() => {
+    if (!highlightedClass || highlightedClass !== rule.overriddenBy) {
+      setSelected(false);
+    }
+  }, [highlightedClass, rule.overriddenBy]);
+
   const classes = useSingleRuleStyles();
   const className = mergeClasses(
     rule.overriddenBy && classes.overriden,
     indent && classes.indent,
-    highlightedClass === rule.className && classes.highlighted,
+    (selected || highlightedClass === rule.className) && classes.highlighted,
   );
 
   return (
diff --git a/packages/devtools/src/getMonolithicCSSRules.ts b/packages/devtools/src/getMonolithicCSSRules.ts
index be43cbb..1ecd08e 100644
--- a/packages/devtools/src/getMonolithicCSSRules.ts
+++ b/packages/devtools/src/getMonolithicCSSRules.ts
@@ -1,7 +1,8 @@
 import * as stylis from 'stylis';
 import type { AtomicRules, MonolithicAtRules, MonolithicRules, RuleDetail } from './types';
 
-const griffelClassNameRegex = /^\.f[0-9a-z]+$/;
+// match className like '.fvnxzrg' or '.fvnxzrg.fui-focus-visible'
+const griffelClassNameRegex = /^\.(f[0-9a-z]+)(\..*|$)/;
 
 function parseRuleElement(element: stylis.Element, overriddenBy?: string) {
   // example of `value`: `.f3xbvq9:hover`
@@ -12,12 +13,13 @@ function parseRuleElement(element: stylis.Element, overriddenBy?: string) {
     let className: string | undefined;
     const selector = stylis
       .tokenize(classNameSelector)
-      .filter(token => {
-        if (griffelClassNameRegex.test(token)) {
-          className = token.slice(1);
-          return false;
+      .map(token => {
+        const match = token.match(griffelClassNameRegex);
+        if (match?.[1]) {
+          className = match?.[1];
+          return match[2] ?? '';
         }
-        return true;
+        return token;
       })
       .join('');
     const rules: RuleDetail[] = children.map(child => ({
diff --git a/packages/devtools/src/utils.ts b/packages/devtools/src/utils.ts
index c75f192..3fd1c98 100644
--- a/packages/devtools/src/utils.ts
+++ b/packages/devtools/src/utils.ts
@@ -24,7 +24,7 @@ export function getRulesBySlots(node: DebugResult, result: SlotInfo[] = []): Slo
     ];
   }
 
-  return node.children.reverse().reduce((acc, child) => {
+  return node.children.reduce((acc, child) => {
     return [...acc, ...getRulesBySlots(child)];
   }, result);
 }
diff --git a/yarn.lock b/yarn.lock
index dc3a131..37eac50 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6903,6 +6903,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@typescript-eslint/scope-manager@npm:5.36.2":
+  version: 5.36.2
+  resolution: "@typescript-eslint/scope-manager@npm:5.36.2"
+  dependencies:
+    "@typescript-eslint/types": 5.36.2
+    "@typescript-eslint/visitor-keys": 5.36.2
+  checksum: 93ff655f7c237c88ec6dc5911202dd8f81bd8909b27f1a758a9d77e9791040f1ee6fe2891314bde75c808ce586246e98003a1b1396937b0312f2440016dea751
+  languageName: node
+  linkType: hard
+
 "@typescript-eslint/types@npm:5.20.0":
   version: 5.20.0
   resolution: "@typescript-eslint/types@npm:5.20.0"
@@ -6917,6 +6927,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@typescript-eslint/types@npm:5.36.2":
+  version: 5.36.2
+  resolution: "@typescript-eslint/types@npm:5.36.2"
+  checksum: 736cb8a76b58f2f9a7d066933094c5510ffe31479ea8b804a829ec85942420f1b55e0eb2688fbdaaaa9c0e5b3b590fb8f14bbd745353696b4fd33fda620d417b
+  languageName: node
+  linkType: hard
+
 "@typescript-eslint/typescript-estree@npm:5.20.0":
   version: 5.20.0
   resolution: "@typescript-eslint/typescript-estree@npm:5.20.0"
@@ -6953,6 +6970,40 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@typescript-eslint/typescript-estree@npm:5.36.2":
+  version: 5.36.2
+  resolution: "@typescript-eslint/typescript-estree@npm:5.36.2"
+  dependencies:
+    "@typescript-eslint/types": 5.36.2
+    "@typescript-eslint/visitor-keys": 5.36.2
+    debug: ^4.3.4
+    globby: ^11.1.0
+    is-glob: ^4.0.3
+    semver: ^7.3.7
+    tsutils: ^3.21.0
+  peerDependenciesMeta:
+    typescript:
+      optional: true
+  checksum: 2827ff57a114b6107ea6d555f3855007133b08a7c2bafba0cfa0c935d8b99fd7b49e982d48cccc1c5ba550d95748d0239f5e2109893f12a165d76ed64a0d261b
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/utils@npm:^5.10.0":
+  version: 5.36.2
+  resolution: "@typescript-eslint/utils@npm:5.36.2"
+  dependencies:
+    "@types/json-schema": ^7.0.9
+    "@typescript-eslint/scope-manager": 5.36.2
+    "@typescript-eslint/types": 5.36.2
+    "@typescript-eslint/typescript-estree": 5.36.2
+    eslint-scope: ^5.1.1
+    eslint-utils: ^3.0.0
+  peerDependencies:
+    eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+  checksum: 45356cf55a8733e3ab1f2c3c19cdaefdb79857e35eb1433c29b81f3df071e9cef8a286bc407abe243889a21d9e793e999f92f03b9c727a0fac1c17a48e64c42a
+  languageName: node
+  linkType: hard
+
 "@typescript-eslint/utils@npm:^5.20.0":
   version: 5.20.0
   resolution: "@typescript-eslint/utils@npm:5.20.0"
@@ -6989,6 +7040,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@typescript-eslint/visitor-keys@npm:5.36.2":
+  version: 5.36.2
+  resolution: "@typescript-eslint/visitor-keys@npm:5.36.2"
+  dependencies:
+    "@typescript-eslint/types": 5.36.2
+    eslint-visitor-keys: ^3.3.0
+  checksum: 87ccdcfa5cdedaa3a1aac30d656969f4f5910b62bcaacdf80a514dbf0cbbd8e79b55f8e987eab34cc79ece8ce4b8c19d5caf8b0afb74e0b0d7ab39fb29aa8eba
+  languageName: node
+  linkType: hard
+
 "@upstash/redis@npm:^1.0.3":
   version: 1.0.3
   resolution: "@upstash/redis@npm:1.0.3"
@@ -10753,6 +10814,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"debug@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "debug@npm:4.3.4"
+  dependencies:
+    ms: 2.1.2
+  peerDependenciesMeta:
+    supports-color:
+      optional: true
+  checksum: 3dbad3f94ea64f34431a9cbf0bafb61853eda57bff2880036153438f50fb5a84f27683ba0d8e5426bf41a8c6ff03879488120cf5b3a761e77953169c0600a708
+  languageName: node
+  linkType: hard
+
 "debug@npm:~3.1.0":
   version: 3.1.0
   resolution: "debug@npm:3.1.0"
@@ -12073,6 +12146,23 @@ __metadata:
   languageName: node
   linkType: hard
 
+"eslint-plugin-jest@npm:^27.0.1":
+  version: 27.0.1
+  resolution: "eslint-plugin-jest@npm:27.0.1"
+  dependencies:
+    "@typescript-eslint/utils": ^5.10.0
+  peerDependencies:
+    "@typescript-eslint/eslint-plugin": ^5.0.0
+    eslint: ^7.0.0 || ^8.0.0
+  peerDependenciesMeta:
+    "@typescript-eslint/eslint-plugin":
+      optional: true
+    jest:
+      optional: true
+  checksum: 269d4dc46bb925eb4c19106fd9e03775a863f53e05716628cc47777abc15887775ee47d73c4f8bdd98bb26b7462d8d8f654610bb2a367f8c97881204a2c3f42e
+  languageName: node
+  linkType: hard
+
 "eslint-plugin-jsx-a11y@npm:6.5.1, eslint-plugin-jsx-a11y@npm:^6.5.1":
   version: 6.5.1
   resolution: "eslint-plugin-jsx-a11y@npm:6.5.1"
@@ -12207,6 +12297,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"eslint-visitor-keys@npm:^3.3.0":
+  version: 3.3.0
+  resolution: "eslint-visitor-keys@npm:3.3.0"
+  checksum: d59e68a7c5a6d0146526b0eec16ce87fbf97fe46b8281e0d41384224375c4e52f5ffb9e16d48f4ea50785cde93f766b0c898e31ab89978d88b0e1720fbfb7808
+  languageName: node
+  linkType: hard
+
 "eslint@npm:8.2.0":
   version: 8.2.0
   resolution: "eslint@npm:8.2.0"
@@ -13820,6 +13917,7 @@ __metadata:
     eslint-config-prettier: 8.1.0
     eslint-import-resolver-typescript: 2.5.0
     eslint-plugin-import: 2.25.2
+    eslint-plugin-jest: ^27.0.1
     eslint-plugin-jsx-a11y: 6.5.1
     eslint-plugin-react: 7.27.0
     eslint-plugin-react-hooks: 4.3.0


Test patch:
diff --git a/packages/core/src/devtools/getDebugTree.test.ts b/packages/core/src/devtools/getDebugTree.test.ts
index 38df61d..425527b 100644
--- a/packages/core/src/devtools/getDebugTree.test.ts
+++ b/packages/core/src/devtools/getDebugTree.test.ts
@@ -17,7 +17,7 @@ const options: MakeStylesOptions = {
 const findSequenceHash = (classNames: string) =>
   classNames.split(' ').find(className => className.startsWith(SEQUENCE_PREFIX));
 
-const souceURLregex = /.*\/.*:[0-9]+:[0-9]+/; // url with line and column number
+const sourceURLregex = /.*\/.*:[0-9]+:[0-9]+/; // url with line and column number
 
 describe('getDebugTree', () => {
   it.each<'ltr' | 'rtl'>(['ltr', 'rtl'])('returns styles merge tree when dir=%p', dir => {
@@ -37,6 +37,22 @@ describe('getDebugTree', () => {
 
     expect(getDebugTree(sequence2!)).toEqual({
       children: [
+        {
+          children: [],
+          debugClassNames: [
+            {
+              className: 'f13qh94s',
+              overriddenBy: undefined,
+            },
+          ],
+          direction: dir,
+          rules: {
+            f13qh94s: '.f13qh94s{display:grid;}',
+          },
+          sequenceHash: sequenceGrid,
+          slot: 'grid',
+          sourceURL: expect.stringMatching(sourceURLregex),
+        },
         {
           children: [
             {
@@ -53,7 +69,7 @@ describe('getDebugTree', () => {
               },
               sequenceHash: sequenceBlock,
               slot: 'block',
-              sourceURL: expect.stringMatching(souceURLregex),
+              sourceURL: expect.stringMatching(sourceURLregex),
             },
           ],
           debugClassNames: [
@@ -65,30 +81,105 @@ describe('getDebugTree', () => {
           direction: dir,
           sequenceHash: sequence1,
         },
+      ],
+      debugClassNames: [
+        {
+          className: 'f13qh94s',
+          overriddenBy: undefined,
+        },
+      ],
+      direction: dir,
+      sequenceHash: sequence2,
+    });
+  });
+
+  it('returns correct merging tree when style and overriding style are the same', () => {
+    const classes = makeStyles({
+      redBlock: { display: 'block', color: 'red' },
+      blueBlock: { display: 'block', color: 'blue' },
+    })(options);
+
+    const sequenceRedBlock = findSequenceHash(classes.redBlock);
+    const sequenceBlueBlock = findSequenceHash(classes.blueBlock);
+
+    const className1 = mergeClasses('ui-button', classes.redBlock);
+    const className2 = mergeClasses(className1, classes.blueBlock);
+
+    const sequence1 = findSequenceHash(className1);
+    const sequence2 = findSequenceHash(className2);
+
+    expect(getDebugTree(sequence2!)).toEqual({
+      children: [
         {
           children: [],
           debugClassNames: [
             {
-              className: 'f13qh94s',
+              className: 'ftgm304',
+              overriddenBy: undefined,
+            },
+            {
+              className: 'f163i14w',
               overriddenBy: undefined,
             },
           ],
-          direction: dir,
+          direction: 'ltr',
           rules: {
-            f13qh94s: '.f13qh94s{display:grid;}',
+            f163i14w: '.f163i14w{color:blue;}',
+            ftgm304: '.ftgm304{display:block;}',
           },
-          sequenceHash: sequenceGrid,
-          slot: 'grid',
-          sourceURL: expect.stringMatching(souceURLregex),
+          sequenceHash: sequenceBlueBlock,
+          slot: 'blueBlock',
+          sourceURL: expect.stringMatching(sourceURLregex),
+        },
+        {
+          children: [
+            {
+              children: [],
+              debugClassNames: [
+                {
+                  className: 'ftgm304',
+                  overriddenBy: 'ftgm304',
+                },
+                {
+                  className: 'fe3e8s9',
+                  overriddenBy: 'f163i14w',
+                },
+              ],
+              direction: 'ltr',
+              rules: {
+                fe3e8s9: '.fe3e8s9{color:red;}',
+                ftgm304: '.ftgm304{display:block;}',
+              },
+              sequenceHash: sequenceRedBlock,
+              slot: 'redBlock',
+              sourceURL: expect.stringMatching(sourceURLregex),
+            },
+          ],
+          debugClassNames: [
+            {
+              className: 'ftgm304',
+              overriddenBy: 'ftgm304',
+            },
+            {
+              className: 'fe3e8s9',
+              overriddenBy: 'f163i14w',
+            },
+          ],
+          direction: 'ltr',
+          sequenceHash: sequence1,
         },
       ],
       debugClassNames: [
         {
-          className: 'f13qh94s',
+          className: 'ftgm304',
+          overriddenBy: undefined,
+        },
+        {
+          className: 'f163i14w',
           overriddenBy: undefined,
         },
       ],
-      direction: dir,
+      direction: 'ltr',
       sequenceHash: sequence2,
     });
   });
diff --git a/packages/core/src/devtools/store.test.ts b/packages/core/src/devtools/store.test.ts
index 503f23b..babfafe 100644
--- a/packages/core/src/devtools/store.test.ts
+++ b/packages/core/src/devtools/store.test.ts
@@ -60,14 +60,14 @@ describe('debugData', () => {
     const sequenceBlock = findSequenceHash(classes.block);
     const sequenceGrid = findSequenceHash(classes.grid);
 
-    const souceURLregex = /.*\/.*:[0-9]+:[0-9]+/; // url with line and column number
+    const sourceURLregex = /.*\/.*:[0-9]+:[0-9]+/; // url with line and column number
     expect(debugData.getSequenceDetails(sequenceBlock!)).toEqual({
       slotName: 'block',
-      sourceURL: expect.stringMatching(souceURLregex),
+      sourceURL: expect.stringMatching(sourceURLregex),
     });
     expect(debugData.getSequenceDetails(sequenceGrid!)).toEqual({
       slotName: 'grid',
-      sourceURL: expect.stringMatching(souceURLregex),
+      sourceURL: expect.stringMatching(sourceURLregex),
     });
   });
 });
diff --git a/packages/core/src/devtools/utils.test.ts b/packages/core/src/devtools/utils.test.ts
index 06d08fc..b326390 100644
--- a/packages/core/src/devtools/utils.test.ts
+++ b/packages/core/src/devtools/utils.test.ts
@@ -1,4 +1,5 @@
-import { LookupItem } from '../types';
+import type { LookupItem } from '../types';
+import type { DebugSequence } from './types';
 import { getDebugClassNames } from './utils';
 
 describe('getDebugClassNames', () => {
@@ -24,9 +25,9 @@ describe('getDebugClassNames', () => {
       },
       'ltr',
     ];
-    const parentlLookupItem: LookupItem = [...lookupItem];
+    const parentLookupItem: LookupItem = [...lookupItem];
     const parentDebugClassNames = [{ className: 'f3xbvq9', overriddenBy: undefined }];
-    expect(getDebugClassNames(lookupItem, parentlLookupItem, parentDebugClassNames)).toEqual([
+    expect(getDebugClassNames(lookupItem, parentLookupItem, parentDebugClassNames)).toEqual([
       {
         className: 'f3xbvq9',
         overriddenBy: undefined,
@@ -41,14 +42,14 @@ describe('getDebugClassNames', () => {
       },
       'ltr',
     ];
-    const parentlLookupItem: LookupItem = [
+    const parentLookupItem: LookupItem = [
       {
         De3pzq: 'f3xbvq9',
       },
       'ltr',
     ];
     const parentDebugClassNames = [{ className: 'f3xbvq9', overriddenBy: undefined }];
-    expect(getDebugClassNames(lookupItem, parentlLookupItem, parentDebugClassNames)).toEqual([
+    expect(getDebugClassNames(lookupItem, parentLookupItem, parentDebugClassNames)).toEqual([
       {
         className: 'fdmssx0',
         overriddenBy: 'f3xbvq9',
@@ -63,13 +64,43 @@ describe('getDebugClassNames', () => {
       },
       'ltr',
     ];
-    const parentlLookupItem: LookupItem = [...lookupItem];
+    const parentLookupItem: LookupItem = [...lookupItem];
     const parentDebugClassNames = [{ className: 'fdmssx0', overriddenBy: 'f3xbvq9' }];
-    expect(getDebugClassNames(lookupItem, parentlLookupItem, parentDebugClassNames)).toEqual([
+    expect(getDebugClassNames(lookupItem, parentLookupItem, parentDebugClassNames)).toEqual([
       {
         className: 'fdmssx0',
         overriddenBy: 'f3xbvq9',
       },
     ]);
   });
+
+  it('handles parent debug node contains overriding style, but the overriding rule is the same', () => {
+    const lookupItem: LookupItem = [
+      {
+        De3pzq: 'fdmssx0',
+      },
+      'ltr',
+    ];
+    const parentLookupItem: LookupItem = [
+      {
+        De3pzq: 'fdmssx0',
+      },
+      'ltr',
+    ];
+    const parentDebugClassNames = [{ className: 'fdmssx0', overriddenBy: undefined }];
+    const overridingSiblings: DebugSequence[] = [
+      {
+        sequenceHash: '___1abcdef_1abcdef',
+        direction: 'ltr',
+        children: [],
+        debugClassNames: [{ className: 'fdmssx0' }],
+      },
+    ];
+    expect(getDebugClassNames(lookupItem, parentLookupItem, parentDebugClassNames, overridingSiblings)).toEqual([
+      {
+        className: 'fdmssx0',
+        overriddenBy: 'fdmssx0',
+      },
+    ]);
+  });
 });
diff --git a/packages/devtools/src/getMonolithicCSSRules.test.ts b/packages/devtools/src/getMonolithicCSSRules.test.ts
index 4fd5f80..bbed364 100644
--- a/packages/devtools/src/getMonolithicCSSRules.test.ts
+++ b/packages/devtools/src/getMonolithicCSSRules.test.ts
@@ -163,6 +163,7 @@ describe('getMonolithicCSSRules', () => {
       '[data-keyboard-nav] .fabcde0:focus{background-color:red;}',
       '[data-keyboard-nav] .fabcde1{color:red;}',
       '[data-keyboard-nav] .fabcde1 .class1{padding-top:10px;}',
+      '[data-keyboard-nav] .fabcde1.class1{padding-bottom:10px;}',
     ].map(cssRule => ({ cssRule }));
     expect(getMonolithicCSSRules(rules)).toEqual({
       '[data-keyboard-nav] ': [
@@ -179,6 +180,13 @@ describe('getMonolithicCSSRules', () => {
           overriddenBy: undefined,
         },
       ],
+      '[data-keyboard-nav] .class1': [
+        {
+          className: 'fabcde1',
+          css: 'padding-bottom:10px;',
+          overriddenBy: undefined,
+        },
+      ],
       '[data-keyboard-nav] :focus': [
         {
           className: 'fabcde0',
diff --git a/packages/devtools/src/utils.test.ts b/packages/devtools/src/utils.test.ts
index b50381a..b393736 100644
--- a/packages/devtools/src/utils.test.ts
+++ b/packages/devtools/src/utils.test.ts
@@ -6,14 +6,14 @@ describe('getRulesBySlots', () => {
   it('traverse debug result, gather and return all makeStyles slot name and rules', () => {
     const makeStylesNodes: DebugResult[] = [
       {
-        sequenceHash: '___abcdef0_0000000',
+        sequenceHash: '___abcdef2_0000000',
         direction: 'ltr',
         children: [],
-        debugClassNames: [{ className: 'fabcde0' }],
+        debugClassNames: [{ className: 'fabcde2' }],
         rules: {
-          fabcde0: '.fabcde0{background-color:transparent;}',
+          fabcde2: '.fabcde2{display:none;}',
         },
-        slot: 'root',
+        slot: 'slot2',
       },
       {
         sequenceHash: '___abcdef1_0000000',
@@ -26,14 +26,14 @@ describe('getRulesBySlots', () => {
         slot: 'slot1',
       },
       {
-        sequenceHash: '___abcdef2_0000000',
+        sequenceHash: '___abcdef0_0000000',
         direction: 'ltr',
         children: [],
-        debugClassNames: [{ className: 'fabcde2' }],
+        debugClassNames: [{ className: 'fabcde0' }],
         rules: {
-          fabcde2: '.fabcde2{display:none;}',
+          fabcde0: '.fabcde0{background-color:transparent;}',
         },
-        slot: 'slot2',
+        slot: 'root',
       },
     ];
     const debugResultRoot = {