Repository: GoogleChromeLabs/ps-analysis-tool
Pull request #52 (2023-06-30 04:39:38)

Add new tabs and refactor code
## Description

Add two more tabs, reorder them, refactor code and update test and its configuration.

## Relevant Technical Choices

- Add two more components: Introduce separate components for `Topics` and `Attribution`.
- Folder restructuring: Move the tab components into their own folders, such as Cookies, Topics, Attribution, BounceTracking, and Fingerprinting, to keep related files together.
- Component naming: Simplify the component names for better readability and clarity. 
- Separate folders for TabContent and TabHeader: Organize the TabContent and TabHeader components into separate folders to clearly identify related components.
- Move app structure and TABS constant: Place the basic app structure and the TABS constant in the app.tsx file to serve as a clear entry point and for easy reference.
- Test configuration: Update the test configuration to handle files such as .css, .svg, and .png. Move all test-related configuration to a dedicated tests folder in the project root for better organization.
- Improved testing approach: Utilize the data-test-id attribute instead of relying solely on specific class names or element structures in your tests. This ensures more robust and resilient testing.

## Screenshot/Screencast

<img width="1440" alt="image" src="https://github.com/GoogleChromeLabs/ps-analysis-tool/assets/6297436/33b020e3-5339-45a9-b240-04d02e5e18af">



Patch:
diff --git a/package.json b/package.json
index cc2a1d0a..7acae443 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
 	"version": "0.0.1",
 	"description": "Cookie Analysis Tool and CLI for analysis and understanding of cookie usage on web pages.",
 	"scripts": {
-		"test": "jest --config=jest.config.js",
+		"test": "jest --config=tests/jest.config.js",
 		"dev": "webpack --config webpack.config.cjs --watch",
 		"build": "cross-env NODE_ENV=production webpack --config webpack.config.cjs",
 		"serve": "webpack serve",
diff --git a/packages/extension/src/view/devtools/app.tsx b/packages/extension/src/view/devtools/app.tsx
index 4470e7db..9c773a23 100644
--- a/packages/extension/src/view/devtools/app.tsx
+++ b/packages/extension/src/view/devtools/app.tsx
@@ -16,19 +16,64 @@
 /**
  * External dependencies.
  */
-import React from 'react';
+import React, { useState } from 'react';
 
 /**
  * Internal dependencies.
  */
 import './app.css';
-import { TabSelector } from './components';
-import { TABS } from './components/tabs';
+import {
+  Cookies,
+  Topics,
+  Attribution,
+  BounceTracking,
+  Fingerprinting,
+  TabHeader,
+} from './components';
+
+const TABS = [
+  {
+    display_name: 'Cookies',
+    Component: Cookies,
+  },
+  {
+    display_name: 'Topics',
+    Component: Topics,
+  },
+  {
+    display_name: 'Attribution',
+    Component: Attribution,
+  },
+  {
+    display_name: 'Bounce Tracking',
+    Component: BounceTracking,
+  },
+  {
+    display_name: 'Fingerprinting',
+    Component: Fingerprinting,
+  },
+];
 
 const App: React.FC = () => {
+  const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
+
+  const TabContent = TABS[selectedTabIndex].Component;
+  const tabNames = TABS.map((tab) => tab.display_name);
+
   return (
-    <div className="w-full h-screen">
-      <TabSelector tabs={TABS} />
+    <div className="w-full h-screen overflow-hidden">
+      <div className="w-full h-full flex flex-col">
+        <header className="w-full h-10 bg-slate-300 flex pt-2">
+          <TabHeader
+            tabsNames={tabNames}
+            selectedIndex={selectedTabIndex}
+            setIndex={setSelectedTabIndex}
+          />
+        </header>
+        <main style={{ height: 'calc(100% - 48px)' }}>
+          <TabContent />
+        </main>
+      </div>
     </div>
   );
 };
diff --git a/packages/extension/src/view/devtools/components/index.ts b/packages/extension/src/view/devtools/components/index.ts
index 0dbec409..a71e9285 100644
--- a/packages/extension/src/view/devtools/components/index.ts
+++ b/packages/extension/src/view/devtools/components/index.ts
@@ -13,4 +13,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-export { TabSelector } from './tabSelector';
+export * from './tabContent';
+export { default as TabHeader } from './tabHeader';
diff --git a/packages/extension/src/view/devtools/components/tabs/BounceTrackingTab.tsx b/packages/extension/src/view/devtools/components/tabContent/attribution/index.tsx
similarity index 76%
rename from packages/extension/src/view/devtools/components/tabs/BounceTrackingTab.tsx
rename to packages/extension/src/view/devtools/components/tabContent/attribution/index.tsx
index b46d8a8a..b76b2fe0 100644
--- a/packages/extension/src/view/devtools/components/tabs/BounceTrackingTab.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/attribution/index.tsx
@@ -13,16 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 /**
  * External dependencies.
  */
 import React from 'react';
 
-export const BounceTrackingTab = () => {
+const Attribution = () => {
   return (
-    <div className="w-full h-full flex justify-center items-center">
-      <p>Bounce tracking Panel</p>
+    <div
+      className="w-full h-full flex justify-center items-center"
+      data-testid="attribution-content"
+    >
+      <p>Attribution API Panel</p>
     </div>
   );
 };
+
+export default Attribution;
diff --git a/packages/extension/src/view/devtools/components/tabContent/bounceTracking/index.tsx b/packages/extension/src/view/devtools/components/tabContent/bounceTracking/index.tsx
new file mode 100644
index 00000000..6784449d
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/tabContent/bounceTracking/index.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+
+const BounceTracking = () => {
+  return (
+    <div
+      className="w-full h-full flex justify-center items-center"
+      data-testid="bounce-tracking-content"
+    >
+      <p>Bounce tracking API Panel</p>
+    </div>
+  );
+};
+
+export default BounceTracking;
diff --git a/packages/extension/src/view/devtools/components/tabs/CookieTab/cookieList.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookieList.tsx
similarity index 100%
rename from packages/extension/src/view/devtools/components/tabs/CookieTab/cookieList.tsx
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookieList.tsx
diff --git a/packages/extension/src/view/devtools/components/tabs/CookieTab/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
similarity index 85%
rename from packages/extension/src/view/devtools/components/tabs/CookieTab/index.tsx
rename to packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
index 2307f56b..2849b10d 100644
--- a/packages/extension/src/view/devtools/components/tabs/CookieTab/index.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
@@ -24,17 +24,19 @@ import React from 'react';
 import { useCookieStore } from '../../../../stateProviders/syncCookieStore';
 import CookieList from './cookieList';
 
-export const CookieTab = () => {
+const Cookies = () => {
   const { cookies, tabURL } = useCookieStore(({ state }) => ({
     cookies: state?.cookies,
     tabURL: state?.url,
   }));
 
   return (
-    <div className="w-full h-full flex flex-col ">
-      <div className="flex-1 overflow-y-scroll ">
+    <div className="w-full h-full flex flex-col" data-testid="cookies-content">
+      <div className="flex-1 overflow-y-scroll">
         <CookieList cookies={cookies} tabURL={tabURL} />
       </div>
     </div>
   );
 };
+
+export default Cookies;
diff --git a/packages/extension/src/view/devtools/components/tabs/CookieTab/listItem.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/listItem.tsx
similarity index 100%
rename from packages/extension/src/view/devtools/components/tabs/CookieTab/listItem.tsx
rename to packages/extension/src/view/devtools/components/tabContent/cookies/listItem.tsx
diff --git a/packages/extension/src/view/devtools/components/tabContent/fingerprinting/index.tsx b/packages/extension/src/view/devtools/components/tabContent/fingerprinting/index.tsx
new file mode 100644
index 00000000..534d5c7d
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/tabContent/fingerprinting/index.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+
+const Fingerprinting = () => {
+  return (
+    <div
+      className="w-full h-full flex justify-center items-center"
+      data-testid="fingerprinting-content"
+    >
+      <p>Fingerprinting API Panel</p>
+    </div>
+  );
+};
+
+export default Fingerprinting;
diff --git a/packages/extension/src/view/devtools/components/tabs/index.ts b/packages/extension/src/view/devtools/components/tabContent/index.ts
similarity index 61%
rename from packages/extension/src/view/devtools/components/tabs/index.ts
rename to packages/extension/src/view/devtools/components/tabContent/index.ts
index 6488a72e..87014ef9 100644
--- a/packages/extension/src/view/devtools/components/tabs/index.ts
+++ b/packages/extension/src/view/devtools/components/tabContent/index.ts
@@ -16,18 +16,8 @@
 /**
  * Internal dependencies.
  */
-import { BounceTrackingTab } from './BounceTrackingTab';
-import { CookieTab } from './CookieTab';
-import { FingerprintingTab } from './FingerprintingTab';
-
-export const TABS = [
-  { display_name: 'Cookies', Component: CookieTab },
-  {
-    display_name: 'Bounce Tracking',
-    Component: BounceTrackingTab,
-  },
-  {
-    display_name: 'Fingerprinting',
-    Component: FingerprintingTab,
-  },
-];
+export { default as Fingerprinting } from './fingerprinting';
+export { default as BounceTracking } from './bounceTracking';
+export { default as Cookies } from './cookies';
+export { default as Topics } from './topics';
+export { default as Attribution } from './attribution';
diff --git a/packages/extension/src/view/devtools/components/tabs/FingerprintingTab.tsx b/packages/extension/src/view/devtools/components/tabContent/topics/index.tsx
similarity index 78%
rename from packages/extension/src/view/devtools/components/tabs/FingerprintingTab.tsx
rename to packages/extension/src/view/devtools/components/tabContent/topics/index.tsx
index 6f9435c0..aed888b4 100644
--- a/packages/extension/src/view/devtools/components/tabs/FingerprintingTab.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/topics/index.tsx
@@ -13,16 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 /**
  * External dependencies.
  */
 import React from 'react';
 
-export const FingerprintingTab = () => {
+const Topics = () => {
   return (
-    <div className="w-full h-full flex justify-center items-center">
-      <p>Fingerprinting Panel</p>
+    <div
+      className="w-full h-full flex justify-center items-center"
+      data-testid="topics-content"
+    >
+      <p>Topics API Panel</p>
     </div>
   );
 };
+
+export default Topics;
diff --git a/packages/extension/src/view/devtools/components/tabHeader/index.tsx b/packages/extension/src/view/devtools/components/tabHeader/index.tsx
new file mode 100644
index 00000000..8296c1a1
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/tabHeader/index.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+
+interface TabHeaderProps {
+  tabsNames: string[];
+  selectedIndex: number;
+  setIndex: (index: number) => void;
+}
+
+const TabHeader: React.FC<TabHeaderProps> = ({
+  tabsNames,
+  selectedIndex,
+  setIndex,
+}) => {
+  return (
+    <>
+      {tabsNames.map((name, index: number) => (
+        <div
+          key={name}
+          className={`h-full mx-1 px-3 flex justify-center items-center rounded-t-lg cursor-pointer ${
+            selectedIndex === index ? 'bg-slate-100' : 'bg-slate-200'
+          }`}
+          onClick={() => setIndex(index)}
+        >
+          <p>{name}</p>
+        </div>
+      ))}
+    </>
+  );
+};
+
+export default TabHeader;
diff --git a/packages/extension/src/view/devtools/components/tabSelector.tsx b/packages/extension/src/view/devtools/components/tabSelector.tsx
deleted file mode 100644
index 36544431..00000000
--- a/packages/extension/src/view/devtools/components/tabSelector.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2023 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/**
- * External dependencies.
- */
-import React, { useState } from 'react';
-
-interface ITabSelector {
-  tabs: { display_name: string; Component: () => React.JSX.Element }[];
-}
-
-export const TabSelector = ({ tabs }: ITabSelector) => {
-  const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
-
-  const TabComponent = tabs[selectedTabIndex].Component;
-
-  return (
-    <div className="w-full h-full flex flex-col">
-      <div className="w-full h-10 bg-slate-300 flex pt-2">
-        {tabs.map(({ display_name }, index) => (
-          <div
-            key={display_name}
-            className={`h-full mx-1 px-3 flex justify-ceter items-center rounded-t-lg cursor-pointer ${
-              selectedTabIndex === index ? 'bg-slate-100' : 'bg-slate-200'
-            }`}
-            onClick={() => {
-              setSelectedTabIndex(index);
-            }}
-          >
-            <p>{display_name}</p>
-          </div>
-        ))}
-      </div>
-      <div style={{ height: 'calc(100% - 48px)' }}>
-        <TabComponent />
-      </div>
-    </div>
-  );
-};
diff --git a/jest.config.js b/tests/jest.config.js
similarity index 67%
rename from jest.config.js
rename to tests/jest.config.js
index 50961830..530b36bb 100644
--- a/jest.config.js
+++ b/tests/jest.config.js
@@ -13,19 +13,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+/**
+ * External dependencies.
+ */
+import { dirname, join } from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
 export default {
-  rootDir: './',
+  rootDir: '../',
   transform: {
     '^.+\\.[jt]sx?$': 'babel-jest',
   },
+  moduleNameMapper: {
+    '\\.svg': join(__dirname, '/svgMock.js'),
+    '\\.css': join(__dirname, '/styleMock.js'),
+    '\\.png': join(__dirname, '/imageMock.js'),
+  },
   testEnvironment: 'jsdom',
   testMatch: ['**/tests/**/*.{js,jsx,ts,tsx}'],
   globals: {},
-  setupFilesAfterEnv: ['<rootDir>/jest.setup'],
+  setupFilesAfterEnv: ['<rootDir>/tests/jest.setup'],
   testPathIgnorePatterns: [
     '<rootDir>/.git',
     '<rootDir>/dist',
     '<rootDir>/out',
     '<rootDir>/node_modules',
+    '<rootDir>/tests',
   ],
 };


Test patch:
diff --git a/packages/extension/src/view/devtools/components/tests/tabSelecter.tsx b/packages/extension/src/view/devtools/tests/app.tsx
similarity index 65%
rename from packages/extension/src/view/devtools/components/tests/tabSelecter.tsx
rename to packages/extension/src/view/devtools/tests/app.tsx
index 2e6799ed..0ca0cd44 100644
--- a/packages/extension/src/view/devtools/components/tests/tabSelecter.tsx
+++ b/packages/extension/src/view/devtools/tests/app.tsx
@@ -17,46 +17,47 @@
  * External dependencies.
  */
 import React from 'react';
-import { fireEvent, render, screen } from '@testing-library/react';
+import { render, screen, fireEvent } from '@testing-library/react';
 import '@testing-library/jest-dom';
 
 /**
  * Internal dependencies.
  */
-import { TabSelector } from '../tabSelector';
-import { TABS } from '../tabs';
-
-describe('TabSelector', () => {
-  it('should be on Cookie Panel by default', () => {
-    render(<TabSelector tabs={TABS} />);
-    // by default Cookies Panel is selected.
-    expect(screen.getByText('Cookies')).toBeInTheDocument();
+import App from '../app';
+
+describe('App', () => {
+  it('Should show cookies content by default', () => {
+    render(<App />);
+
+    expect(screen.getByTestId('cookies-content')).toBeInTheDocument();
   });
 
   it('should switch to cookie panel when tab is clicked', async () => {
-    render(<TabSelector tabs={TABS} />);
+    render(<App />);
     // Move to another tab
     fireEvent.click(screen.getByText('Bounce Tracking'));
 
     fireEvent.click(screen.getByText('Cookies'));
-    expect(await screen.findByText('Cookies')).toBeInTheDocument();
+    expect(await screen.getByTestId('cookies-content')).toBeInTheDocument();
   });
 
   it('should switch to Bounce Tracking Panel when clicked', async () => {
-    render(<TabSelector tabs={TABS} />);
+    render(<App />);
     // Click on Bounce Tracking tab
     fireEvent.click(screen.getByText('Bounce Tracking'));
 
     expect(
-      await screen.findByText('Bounce tracking Panel')
+      await screen.getByTestId('bounce-tracking-content')
     ).toBeInTheDocument();
   });
 
   it('should switch to FingerPrinting Panel when clicked', async () => {
-    render(<TabSelector tabs={TABS} />);
+    render(<App />);
     // Click on FingerPrinting tab
     fireEvent.click(screen.getByText('Fingerprinting'));
 
-    expect(await screen.findByText('Fingerprinting Panel')).toBeInTheDocument();
+    expect(
+      await screen.getByTestId('fingerprinting-content')
+    ).toBeInTheDocument();
   });
 });
diff --git a/tests/imageMock.js b/tests/imageMock.js
new file mode 100644
index 00000000..5876d683
--- /dev/null
+++ b/tests/imageMock.js
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'https://example.com/path/to/image.png';
diff --git a/jest.setup.js b/tests/jest.setup.js
similarity index 100%
rename from jest.setup.js
rename to tests/jest.setup.js
diff --git a/tests/styleMock.js b/tests/styleMock.js
new file mode 100644
index 00000000..89085bb3
--- /dev/null
+++ b/tests/styleMock.js
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default {};
diff --git a/tests/svgMock.js b/tests/svgMock.js
new file mode 100644
index 00000000..49690f42
--- /dev/null
+++ b/tests/svgMock.js
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default 'span';