Repository: GoogleChromeLabs/ps-analysis-tool (ts)

Issue:
Add the ability to add or remove cookies table columns
## Feature Request

**Description:**
Allow adding or removing cookies table columns as per the user's need.

**User Story:**
As a developer/website owner/web user I want to be able to add or remove columns if I do not have enough space in the table width or if I am interested in fewer columns.

**Acceptance Criteria:**

- [x] Right-clicking on the table header cell opens a menu showing a list of available columns.
- [x] Show the active columns list with a checkmark.
- [x] Clicking on an inactive column item in the menu inserts that column.
- [x] Clicking on an active column item in the menu removes that column.
- [x] Allow resetting columns


Pull request description:
Feature: Add Column Visibility Functionality
## Description

<!-- What do we want to achieve with this PR? -->
This pull request introduces a new feature that enhances the Table components by adding column visibility functionality. Users can control the visibility of individual columns within the table by selecting from a menu , allowing them to tailor the data display to their specific needs.

## Relevant Technical Choices
- Excluded the `Name` attribute from the column visibility feature, as it was concluded that at least one column is required to be present inside the table.
- Included a convenient `Toggle All` checkbox that enables users to set or reset the visibility of all columns simultaneously.
<!-- Please describe your changes. -->

## Testing Instructions
- Open the terminal in the root directory of the project.
- Run the command npm run dev, and the extension's Webpack development will spin up.
- Open website `indianexpress.com`, open devtools panel and select Privacy Sandbox tab.
- Right Click on Table Header Row, it will open up a menu.
- Toggle the visibility of columns from the menu by selecting desired menu item.


<!--
How can the changes in this PR be verified?
Please provide step-by-step instructions to test the changes.
-->

## Additional Information:

<!-- Any other information. -->

## Screenshot/Screencast


https://github.com/GoogleChromeLabs/ps-analysis-tool/assets/58820001/89b718f0-5150-43e2-84c0-2e252be5f4a0



<!-- Please provide Screenshot/Screencast, if applicable -->

---

<!--
Example:

Fixes #123
Partially addresses #22
See #834
-->

Fixes #70 


Patch:
diff --git a/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx b/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
index 1e3b8b8b..fcfda34d 100644
--- a/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
@@ -17,6 +17,8 @@
  * External dependencies.
  */
 import React, { useCallback } from 'react';
+import classNames from 'classnames';
+
 /**
  * Internal dependencies
  */
@@ -65,11 +67,13 @@ const Accordion: React.FC<AccordionProps> = ({
     <div className="flex flex-col w-screen">
       <div
         data-testid="cookies-tab-heading-wrapper"
-        className={`flex h-full flex-row items-center pl-3 py-0.5 outline-0 ${
-          selectedIndex === index && !selectedFrame
-            ? 'bg-selected-background-color text-white'
-            : ''
-        }`}
+        className={classNames(
+          'flex h-full flex-row items-center pl-3 py-0.5 outline-0',
+          {
+            'bg-royal-blue text-white':
+              selectedIndex === index && !selectedFrame,
+          }
+        )}
         tabIndex={0}
         onClick={() => setIndex(index)}
         onKeyDown={(event) => keyboardNavigator(event)}
@@ -112,11 +116,12 @@ const Accordion: React.FC<AccordionProps> = ({
                 setSelectedFrame(key);
               }}
               role="treeitem"
-              className={`pl-9 py-0.5 h-5 flex items-center cursor-default outline-0 ${
-                selectedFrame === key
-                  ? 'bg-selected-background-color text-white'
-                  : ''
-              }`}
+              className={classNames(
+                'pl-9 py-0.5 h-5 flex items-center cursor-default outline-0',
+                {
+                  'bg-royal-blue text-white': selectedFrame === key,
+                }
+              )}
             >
               <div className="h-4">
                 {selectedFrame === key ? <CookieWhite /> : <CookieGray />}
diff --git a/packages/extension/src/view/devtools/components/sidebar/index.tsx b/packages/extension/src/view/devtools/components/sidebar/index.tsx
index 49410ee1..f92d10f4 100644
--- a/packages/extension/src/view/devtools/components/sidebar/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/index.tsx
@@ -17,6 +17,8 @@
  * External dependencies.
  */
 import React, { useCallback, useState } from 'react';
+import classNames from 'classnames';
+
 /**
  * Internal dependencies
  */
@@ -142,11 +144,13 @@ const Sidebar: React.FC<SidebarProps> = ({
         <div
           key={name}
           data-testid={name}
-          className={`flex items-center cursor-default gap-y-1.5 outline-0 ${
-            selectedIndex === index && name !== 'Cookies'
-              ? 'bg-selected-background-color text-white'
-              : ''
-          }`}
+          className={classNames(
+            'flex items-center cursor-default gap-y-1.5 outline-0',
+            {
+              'bg-royal-blue text-white':
+                selectedIndex === index && name !== 'Cookies',
+            }
+          )}
           tabIndex={0}
           onKeyDown={(event) => keyboardNavigator(event)}
         >
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/details.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/details.tsx
index 0092eaa0..0d14267a 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/details.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/details.tsx
@@ -37,6 +37,7 @@ const Details = ({ selectedCookie }: DetailsProps) => {
         <span>Cookie Value</span>
         <label className="text-granite-gray text-xs font-normal flex items-center">
           <input
+            role="checkbox"
             type="checkbox"
             className="ml-3 mr-1 cursor-pointer"
             checked={showUrlDecoded}
diff --git a/packages/extension/src/view/devtools/components/table/columnMenu/columnList.tsx b/packages/extension/src/view/devtools/components/table/columnMenu/columnList.tsx
new file mode 100644
index 00000000..65d3113c
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/table/columnMenu/columnList.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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';
+import type { Column } from '@tanstack/react-table';
+
+/**
+ * Internal dependencies.
+ */
+import ColumnListItem from './columnListItem';
+import type { TableData } from '..';
+
+interface ColumnListProps {
+  columns: Column<TableData, unknown>[];
+  handleClose: () => void;
+}
+
+const ColumnList = ({ columns, handleClose }: ColumnListProps) => {
+  return (
+    <ul className="text-basic mt-1.5">
+      {columns.map((column) => (
+        <ColumnListItem
+          key={column.id}
+          column={column}
+          handleClose={handleClose}
+        />
+      ))}
+    </ul>
+  );
+};
+
+export default ColumnList;
diff --git a/packages/extension/src/view/devtools/components/table/columnMenu/columnListItem.tsx b/packages/extension/src/view/devtools/components/table/columnMenu/columnListItem.tsx
new file mode 100644
index 00000000..84f08eeb
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/table/columnMenu/columnListItem.tsx
@@ -0,0 +1,63 @@
+/*
+ * 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';
+import type { Column } from '@tanstack/react-table';
+import classNames from 'classnames';
+
+/**
+ * Internal dependencies.
+ */
+import type { TableData } from '..';
+
+interface ColumnListItemProps {
+  column: Column<TableData, unknown>;
+  handleClose: () => void;
+}
+
+const ColumnListItem = ({ column, handleClose }: ColumnListItemProps) => {
+  return (
+    <li
+      key={column.id}
+      className={classNames({
+        'pointer-events-none opacity-50': column.columnDef.header === 'Name',
+      })}
+    >
+      <button
+        className="w-full rounded text-xs px-1 py-[3px] my-px flex items-center hover:bg-royal-blue hover:text-white select-none touch-none cursor-default"
+        onClick={(e) => {
+          column.getToggleVisibilityHandler()(e);
+          handleClose();
+        }}
+      >
+        <span
+          className={classNames(
+            'mr-2 font-semibold',
+            column.getIsVisible() ? 'opacity-100' : 'opacity-0'
+          )}
+        >
+          ✓
+        </span>
+        <span>{column.columnDef.header as string}</span>
+      </button>
+    </li>
+  );
+};
+
+export default ColumnListItem;
diff --git a/packages/extension/src/view/devtools/components/table/columnMenu/index.tsx b/packages/extension/src/view/devtools/components/table/columnMenu/index.tsx
new file mode 100644
index 00000000..e7b99714
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/table/columnMenu/index.tsx
@@ -0,0 +1,120 @@
+/*
+ * 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, { useEffect, useRef, useState } from 'react';
+import type { Column, Table } from '@tanstack/react-table';
+import classNames from 'classnames';
+
+/**
+ * Internal dependencies.
+ */
+import type { TableData } from '..';
+import { createPortal } from 'react-dom';
+import ColumnList from './columnList';
+
+interface ColumnMenuProps {
+  table: Table<TableData>;
+  columns: Column<TableData, unknown>[];
+  position: { x: number; y: number };
+  open: boolean;
+  onClose: (value: boolean) => void;
+}
+
+const ColumnMenu = ({
+  table,
+  columns,
+  position,
+  open,
+  onClose,
+}: ColumnMenuProps) => {
+  const [startAnimation, setStartAnimation] = useState(false);
+  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  const handleClose = () => {
+    document.body.style.overflow = open ? 'auto' : 'hidden';
+    setStartAnimation(true);
+
+    timeoutRef.current = setTimeout(() => {
+      onClose(!open);
+      setStartAnimation(false);
+    }, 100);
+  };
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) {
+        clearTimeout(timeoutRef.current);
+      }
+    };
+  }, []);
+
+  return (
+    <>
+      {open &&
+        createPortal(
+          <div
+            className={classNames(
+              'transition duration-100',
+              startAnimation ? 'opacity-0' : 'opacity-100'
+            )}
+            data-testid="column-menu"
+          >
+            <div
+              className="absolute z-50 text-raisin-black rounded-md backdrop-blur-2xl w-screen max-w-[13rem] p-1.5 mr-2 divide-y divide-neutral-300 max-h-[80vh] overflow-auto bg-stone-200 shadow-3xl"
+              style={{
+                left: 'min( calc( 100vw - 15rem),' + position.x + 'px )',
+                top: position.y + 'px',
+                border: '0.5px solid rgba(0, 0, 0, 0.20)',
+              }}
+            >
+              <button
+                className="block w-full text-xs rounded px-1 py-[3px] mb-1.5 flex items-center hover:bg-royal-blue hover:text-white cursor-default"
+                onClick={(e) => {
+                  table.getToggleAllColumnsVisibilityHandler()(e);
+                  handleClose();
+                }}
+              >
+                <span
+                  className={classNames(
+                    'mr-2 font-semibold',
+                    table.getIsAllColumnsVisible() ? 'opacity-100' : 'opacity-0'
+                  )}
+                >
+                  ✓
+                </span>
+                <span>Toggle All</span>
+              </button>
+              <div>
+                <ColumnList columns={columns} handleClose={handleClose} />
+              </div>
+            </div>
+
+            <div
+              data-testid="column-menu-overlay"
+              onClick={handleClose}
+              className="absolute w-screen h-screen z-10 top-0 left-0"
+            />
+          </div>,
+          document.body
+        )}
+    </>
+  );
+};
+
+export default ColumnMenu;
diff --git a/packages/extension/src/view/devtools/components/table/index.tsx b/packages/extension/src/view/devtools/components/table/index.tsx
index 01739b77..1ba17cb5 100644
--- a/packages/extension/src/view/devtools/components/table/index.tsx
+++ b/packages/extension/src/view/devtools/components/table/index.tsx
@@ -17,7 +17,7 @@
 /**
  * External dependencies.
  */
-import React from 'react';
+import React, { useCallback, useState } from 'react';
 import type { Table as ReactTable } from '@tanstack/react-table';
 
 /**
@@ -26,6 +26,7 @@ import type { Table as ReactTable } from '@tanstack/react-table';
 import TableHeader from './tableHeader';
 import TableBody from './tableBody';
 import type { CookieTableData } from '../../stateProviders/syncCookieStore';
+import ColumnMenu from './columnMenu';
 
 export type TableData = CookieTableData;
 
@@ -36,15 +37,42 @@ interface TableProps {
 }
 
 const Table = ({ table, selectedKey, onRowClick }: TableProps) => {
+  const [showColumnsMenu, setShowColumnsMenu] = useState(false);
+  const [columnPosition, setColumnPosition] = useState({
+    x: 0,
+    y: 0,
+  });
+
+  const handleRightClick = useCallback(
+    (e: React.MouseEvent<HTMLElement>) => {
+      e.preventDefault();
+      document.body.style.overflow = showColumnsMenu ? 'auto' : 'hidden';
+      setShowColumnsMenu(!showColumnsMenu);
+    },
+    [showColumnsMenu]
+  );
   return (
-    <table className="w-full">
-      <TableHeader headerGroups={table.getHeaderGroups()} />
-      <TableBody
-        rows={table.getRowModel().rows}
-        selectedKey={selectedKey}
-        onRowClick={onRowClick}
+    <>
+      <ColumnMenu
+        open={showColumnsMenu}
+        onClose={setShowColumnsMenu}
+        table={table}
+        columns={table.getAllLeafColumns()}
+        position={columnPosition}
       />
-    </table>
+      <table className="w-full">
+        <TableHeader
+          headerGroups={table.getHeaderGroups()}
+          setColumnPosition={setColumnPosition}
+          onRightClick={handleRightClick}
+        />
+        <TableBody
+          rows={table.getRowModel().rows}
+          selectedKey={selectedKey}
+          onRowClick={onRowClick}
+        />
+      </table>
+    </>
   );
 };
 
diff --git a/packages/extension/src/view/devtools/components/table/tableHeader/index.tsx b/packages/extension/src/view/devtools/components/table/tableHeader/index.tsx
index a58a166c..f791e9a9 100644
--- a/packages/extension/src/view/devtools/components/table/tableHeader/index.tsx
+++ b/packages/extension/src/view/devtools/components/table/tableHeader/index.tsx
@@ -17,7 +17,7 @@
 /**
  * External dependencies.
  */
-import React from 'react';
+import React, { useCallback } from 'react';
 import type { HeaderGroup } from '@tanstack/react-table';
 
 /**
@@ -28,11 +28,30 @@ import type { TableData } from '..';
 
 interface TableHeaderProps {
   headerGroups: HeaderGroup<TableData>[];
+  setColumnPosition: (position: { x: number; y: number }) => void;
+  onRightClick: (
+    event: React.MouseEvent<HTMLTableSectionElement, MouseEvent>
+  ) => void;
 }
 
-const TableHeader = ({ headerGroups }: TableHeaderProps) => {
+const TableHeader = ({
+  headerGroups,
+  setColumnPosition,
+  onRightClick,
+}: TableHeaderProps) => {
+  const handleRightClick = useCallback(
+    (e: React.MouseEvent<HTMLTableSectionElement>) => {
+      const x = e.clientX,
+        y = e.clientY;
+
+      setColumnPosition({ x, y });
+      onRightClick(e);
+    },
+    [onRightClick, setColumnPosition]
+  );
+
   return (
-    <thead className="sticky top-0 z-50">
+    <thead onContextMenu={handleRightClick} className="sticky top-0 z-10">
       {headerGroups.map((headerGroup) => (
         <HeaderRow key={headerGroup.id} headerGroup={headerGroup} />
       ))}
diff --git a/packages/extension/tailwind.config.cjs b/packages/extension/tailwind.config.cjs
index 73e71ee3..4e645b06 100644
--- a/packages/extension/tailwind.config.cjs
+++ b/packages/extension/tailwind.config.cjs
@@ -27,7 +27,12 @@ delete colors['blueGray'];
 module.exports = {
   content: ['./packages/extension/src/**/*.{tsx,js}'],
   theme: {
-    extend: {},
+    extend: {
+      boxShadow: {
+        '3xl':
+          '0px 38px 90px 0px rgba(0, 0, 0, 0.25), 0px 0px 2px 0px rgba(0, 0, 0, 0.05), 0px 0px 1px 0px rgba(0, 0, 0, 0.60)',
+      },
+    },
     fontFamily: {
       ...defaultTheme.fontFamily,
     },
@@ -52,10 +57,12 @@ module.exports = {
       primary: '#FFF',
       secondary: '#E5E7EB',
       tertiary: '#CBD5E1',
+      'royal-blue': '#3871E0',
       'anti-flash-white': '#F1F3F4',
       gainsboro: '#DADCE0',
       'american-silver': '#CBCDD1',
-      'selected-background-color': '#3871E0',
+      'raisin-black': '#212121',
+      cultured: '#F6F6F699',
     },
     borderColor: {
       ...colors,


Test patch:
diff --git a/packages/extension/src/view/devtools/components/sidebar/accordion/tests/index.tsx b/packages/extension/src/view/devtools/components/sidebar/accordion/tests/index.tsx
index f88c7752..d9b22e2d 100644
--- a/packages/extension/src/view/devtools/components/sidebar/accordion/tests/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/accordion/tests/index.tsx
@@ -47,7 +47,7 @@ describe('Accordion', () => {
     );
     const container = screen.getByTestId('cookies-tab-heading-wrapper');
 
-    expect(container).toHaveClass('bg-selected-background-color');
+    expect(container).toHaveClass('bg-royal-blue');
   });
 
   it('should unselect cookie header and show frame as selected', () => {
@@ -74,10 +74,8 @@ describe('Accordion', () => {
     );
     const cookieFrameContainer = screen.getByTestId('https://edition.cnn.com');
 
-    expect(cookieHeaderContainer).not.toHaveClass(
-      'bg-selected-background-color'
-    );
-    expect(cookieFrameContainer).toHaveClass('bg-selected-background-color');
+    expect(cookieHeaderContainer).not.toHaveClass('bg-royal-blue');
+    expect(cookieFrameContainer).toHaveClass('bg-royal-blue');
   });
 
   it('should not display anything when there are not tabFrames', () => {
@@ -145,8 +143,8 @@ describe('Accordion', () => {
     const firstFrame = screen.getByTestId('https://edition.cnn.com');
     const secondFrame = screen.getByTestId('https://crxd.net');
 
-    expect(firstFrame).toHaveClass('bg-selected-background-color');
-    expect(secondFrame).not.toHaveClass('bg-selected-background-color');
+    expect(firstFrame).toHaveClass('bg-royal-blue');
+    expect(secondFrame).not.toHaveClass('bg-royal-blue');
 
     //click on second frame
     fireEvent.click(secondFrame);
@@ -169,7 +167,7 @@ describe('Accordion', () => {
       />
     );
 
-    expect(firstFrame).not.toHaveClass('bg-selected-background-color');
-    expect(secondFrame).toHaveClass('bg-selected-background-color');
+    expect(firstFrame).not.toHaveClass('bg-royal-blue');
+    expect(secondFrame).toHaveClass('bg-royal-blue');
   });
 });
diff --git a/packages/extension/src/view/devtools/components/sidebar/tests/index.tsx b/packages/extension/src/view/devtools/components/sidebar/tests/index.tsx
index 54f8b4d3..1b3cad7d 100644
--- a/packages/extension/src/view/devtools/components/sidebar/tests/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/tests/index.tsx
@@ -169,7 +169,7 @@ describe('Sidebar', () => {
     );
     const container = screen.getByTestId('cookies-tab-heading-wrapper');
 
-    expect(container).toHaveClass('bg-selected-background-color');
+    expect(container).toHaveClass('bg-royal-blue');
   });
 
   it('should unselect cookie header and show other header as selected', () => {
@@ -192,10 +192,8 @@ describe('Sidebar', () => {
     );
     const attributionContainer = screen.getByTestId('Attribution');
 
-    expect(cookieHeaderContainer).toHaveClass('bg-selected-background-color');
-    expect(attributionContainer).not.toHaveClass(
-      'bg-selected-background-color'
-    );
+    expect(cookieHeaderContainer).toHaveClass('bg-royal-blue');
+    expect(attributionContainer).not.toHaveClass('bg-royal-blue');
 
     fireEvent.click(attributionContainer);
 
@@ -207,10 +205,8 @@ describe('Sidebar', () => {
       />
     );
 
-    expect(cookieHeaderContainer).not.toHaveClass(
-      'bg-selected-background-color'
-    );
-    expect(attributionContainer).toHaveClass('bg-selected-background-color');
+    expect(cookieHeaderContainer).not.toHaveClass('bg-royal-blue');
+    expect(attributionContainer).toHaveClass('bg-royal-blue');
   });
 
   it('should select cookie and show the listed frames under cookie menu.', () => {
@@ -235,11 +231,9 @@ describe('Sidebar', () => {
     );
     const mainFrame = screen.getByTestId('https://edition.cnn.com/');
 
-    expect(cookieHeaderContainer).not.toHaveClass(
-      'bg-selected-background-color'
-    );
+    expect(cookieHeaderContainer).not.toHaveClass('bg-royal-blue');
     expect(mainFrame).toBeInTheDocument();
-    expect(mainFrame).toHaveClass('bg-selected-background-color');
+    expect(mainFrame).toHaveClass('bg-royal-blue');
   });
 
   it('should select another menu and close cookie accordion', () => {
@@ -265,11 +259,9 @@ describe('Sidebar', () => {
     const mainFrame = screen.getByTestId('https://edition.cnn.com/');
     const attributionContainer = screen.getByTestId('Attribution');
 
-    expect(cookieHeaderContainer).not.toHaveClass(
-      'bg-selected-background-color'
-    );
+    expect(cookieHeaderContainer).not.toHaveClass('bg-royal-blue');
     expect(mainFrame).toBeInTheDocument();
-    expect(mainFrame).toHaveClass('bg-selected-background-color');
+    expect(mainFrame).toHaveClass('bg-royal-blue');
 
     fireEvent.click(attributionContainer);
     mockUseCookieStore.mockReturnValueOnce({
@@ -286,9 +278,7 @@ describe('Sidebar', () => {
         setIndex={() => undefined}
       />
     );
-    expect(cookieHeaderContainer).not.toHaveClass(
-      'bg-selected-background-color'
-    );
-    expect(attributionContainer).toHaveClass('bg-selected-background-color');
+    expect(cookieHeaderContainer).not.toHaveClass('bg-royal-blue');
+    expect(attributionContainer).toHaveClass('bg-royal-blue');
   });
 });
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
index c7054349..e90577e6 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
@@ -30,6 +30,7 @@ import type { CookieStoreContext } from '../../../../stateProviders/syncCookieSt
 import { emptyAnalytics } from '../../../../../../worker/findAnalyticsMatch';
 import CookieDetails from '../cookiesListing/cookieDetails';
 import { useContentPanelStore } from '../../../../stateProviders/contentPanelStore';
+import Details from '../cookiesListing/cookieDetails/details';
 
 const emptyCookie = {
   name: '',
@@ -45,7 +46,7 @@ const emptyCookie = {
 const uncategorised1pCookie: ParsedCookie = {
   ...emptyCookie,
   name: '_cb',
-  value: 'uncategorised1pCookie',
+  value: 'v1%3A168740954476563235',
   domain: '.cnn.com',
 };
 
@@ -213,6 +214,51 @@ describe('CookieTab', () => {
     ).toBeInTheDocument();
   });
 
+  it('should open column menu when right click on header cell', async () => {
+    render(<CookieTab />);
+
+    const headerCell = (await screen.findAllByTestId('header-cell'))[0];
+    fireEvent.contextMenu(headerCell);
+
+    expect(await screen.findByTestId('column-menu')).toBeInTheDocument();
+
+    const toggleAll = await screen.findByText('Toggle All');
+    fireEvent.click(toggleAll);
+
+    expect(await screen.findAllByTestId('header-cell')).toHaveLength(1);
+
+    fireEvent.contextMenu(headerCell);
+    fireEvent.click(toggleAll);
+  });
+
+  it('should remove one columne when click on column menu list item', async () => {
+    render(<CookieTab />);
+
+    const headerCell = (await screen.findAllByTestId('header-cell'))[0];
+    fireEvent.contextMenu(headerCell);
+
+    const columnMenu = await screen.findByTestId('column-menu');
+
+    const value = await within(columnMenu).findByText('Value');
+    fireEvent.click(value);
+
+    expect(await screen.findAllByTestId('header-cell')).not.toContain(value);
+  });
+
+  it('should columnMenu close when click on outside', async () => {
+    render(<CookieTab />);
+
+    const headerCell = (await screen.findAllByTestId('header-cell'))[0];
+    fireEvent.contextMenu(headerCell);
+
+    const columnMenuOverlay = await screen.findByTestId('column-menu-overlay');
+    fireEvent.click(columnMenuOverlay);
+
+    setTimeout(() => {
+      expect(screen.queryByTestId('column-menu')).not.toBeInTheDocument();
+    }, 1000);
+  });
+
   it('should render a cookie card with placeholder text when no cookie is selected', async () => {
     mockUseContentPanelStore.mockReturnValue({
       selectedCookie: null,
@@ -228,6 +274,29 @@ describe('CookieTab', () => {
     ).toBeInTheDocument();
   });
 
+  it('should decode cookie value when input show URI decoded is checked', async () => {
+    render(
+      <Details
+        selectedCookie={mockResponse.tabCookies[uncategorised1pCookie.name]}
+      />
+    );
+
+    const checkbox = screen.getByRole('checkbox', {
+      checked: false,
+    });
+    fireEvent.click(checkbox);
+
+    expect(
+      await screen.findByText(decodeURIComponent(uncategorised1pCookie.value))
+    ).toBeInTheDocument();
+
+    fireEvent.click(checkbox);
+
+    expect(
+      await screen.findByText(uncategorised1pCookie.value)
+    ).toBeInTheDocument();
+  });
+
   it('should show a cookie card with the information on first cookie in the list', async () => {
     const firstCookie =
       mockResponse.tabCookies[Object.keys(mockResponse.tabCookies)[0]];